diff --git a/srve.c b/srve.c new file mode 100644 index 0000000..ba0add5 --- /dev/null +++ b/srve.c @@ -0,0 +1,398 @@ +// Simple emulator program +#include +#include +#include +#include +#include +#define SRVE_IMPLEMENTATION +#include "srve.h" +#include "../libc/elf.h" + +// Some defines for virtio emulation +#define VIOE_UART0 0x10000000ULL +#define VIOE_UART_SIZE 0x1000ULL +#define VIOE_UART_HOLDING 0x0 +#define VIOE_UART_LINESTATUS 0x5 +#define VIOE_UART_LINESTATUS_RECEIVEABLE 1 +#define VIOE_UART_LINESTATUS_TRANSMITTABLE (1<<5) + +// Some defines for new BIOS functionality +#define VMRD_BASEADDRESS 0x0000000010001000UL +#define VMRD_SIZE 0x1000ULL +#define VMRD_REG_MAGIC 0 +#define VMRD_REG_BLKSIZE 4 +#define VMRD_REG_MEMADDR 8 +#define VMRD_REG_BLKADDR 16 +#define VMRD_REG_ACTION 24 + +// The magic number is BA5DB105, which drivers can use to distinguish +// it from virtio. +#define VMRD_MAGIC 0xBA5DB105 + +// This range needs to cover all I/O areas +#define IORANGE_BEGIN 0x10000000ULL +#define IORANGE_END 0x10002000ULL + + + +typedef struct vmrd vmrd_t; + +struct vmrd { + uint64_t memoryaddress; + uint64_t blockaddress; + uint32_t blocksize; + uint32_t action; + FILE* file; + uint64_t imagesize; +}; + +srve_core64_t core; + +// Loads binary into memory at address, returning size or -1 on failure. +int64_t loadf(unsigned char* memory, unsigned long long addr, const char* fname) { + fprintf(stderr, "loadf(%p, 0x%llx, \"%s\");\n", memory, addr, fname); + FILE* f = fopen(fname, "r"); + if (!f) { + fprintf(stderr, "Failed to open '%s'\n", fname); + return -1; + } + + unsigned long sz = 0; + size_t nread; + while ((nread = fread(memory + addr + sz, 1, 1024*1024, f)) > 0) { + sz += (unsigned long) nread; + } + + fclose(f); +} + +int uart(int devnum, uint32_t offset, void* buffer, unsigned int storing, unsigned int size) { + //fprintf(stderr, "uart(%i, 0x%x, %p, %d, %d);\n", devnum, offset, buffer, storing, size); + + switch (offset) { + case VIOE_UART_HOLDING: + if (storing) { + fflush(stderr); + //printf("char value is 0x%x\n", *((uint8_t*)buffer)); + //putc('X',stdout); + putc(*((uint8_t*)buffer), stdout); + fflush(stdout); + //exit(0); + return 0; + } else { + return -1; + } + case VIOE_UART_LINESTATUS: + if (storing) { + return -1; + } else { + *((char*)buffer) = VIOE_UART_LINESTATUS_TRANSMITTABLE; + return 0; + } + default: + fprintf(stderr, "uart: bad offset 0x%x\n", offset); + return 0; + } +} + +// Only allow one disk for now, but design as though there may be more. +#define VMRD_MAX 1 +vmrd_t* vmrd_array[VMRD_MAX]; + +uint32_t vmrd_performaction(void* memory, vmrd_t* device, uint32_t action) { + if (memory == NULL || device == NULL || device->file == NULL || device->imagesize == 0) { + return 'f'; + } + + char* membytes = memory; + + switch (action) { + case 't': + core.tracing = 1; + return 's'; + case 'e': + core.tracing = 0; + return 's'; + case 'r': + if (fseek(device->file, device->blocksize*device->blockaddress, SEEK_SET) != 0) { + return 'f'; + } + // TODO: Validate memory address... + if (fread(membytes+device->memoryaddress, device->blocksize, 1, device->file) != 1) { + return 'f'; + } + return 's'; + default: + return 'f'; + } +} + +int vmrd(void* memory, int devnum, uint32_t offset, void* buffer, unsigned int storing, unsigned int size) { + vmrd_t* device = &vmrd_array[devnum]; + switch (offset) { + case VMRD_REG_MAGIC: + if (!storing && size == 4) { + *((uint32_t*)buffer) = VMRD_MAGIC; + return 0; + } else { + goto fail; + } + case VMRD_REG_BLKADDR: + if (storing && size == 8) { + device->blockaddress = *((uint64_t*)buffer); + return 0; + } else if (!storing && size == 8) { + *((uint64_t*)buffer) = device->blockaddress; + return 0; + } else { + goto fail; + } + case VMRD_REG_MEMADDR: + if (storing && size == 8) { + device->memoryaddress = *((uint64_t*)buffer); + return 0; + } else if (!storing && size == 8) { + *((uint64_t*)buffer) = device->memoryaddress; + return 0; + } else { + goto fail; + } + case VMRD_REG_BLKSIZE: + if (storing && size == 4) { + device->blocksize = *((uint32_t*)buffer); + return 0; + } else if (!storing && size == 4) { + *((uint32_t*)buffer) = device->blocksize; + return 0; + } else { + goto fail; + } + case VMRD_REG_ACTION: + if (storing && size == 4) { + device->action = '*'; // Asterix action state indicates it's in use + device->action = vmrd_performaction(memory, device, *((uint32_t*)buffer)); + return 0; + } else if (!storing && size == 4) { + *((uint32_t*)buffer) = device->action; + return 0; + } else { + goto fail; + } + default: + fail: + device->action = 'f'; + fprintf(stderr, "vmrd: bad access at offset 0x%x\n", offset); + return -1; + } + return 0; +} + +int iofunc(void* memory, uint64_t address, void* buffer, unsigned int storing, unsigned int size) { + if (address >= VIOE_UART0 && address+size <= VIOE_UART0+VIOE_UART_SIZE) { + return uart(0, (uint32_t) (address - VIOE_UART0), buffer, storing, size); + } + if (address >= VMRD_BASEADDRESS && address+size <= VMRD_BASEADDRESS+VMRD_SIZE) { + return vmrd(memory, 0, (uint32_t) (address - VMRD_BASEADDRESS), buffer, storing, size); + } + fprintf(stderr, "iofunc: Bad I/O address 0x%llx\n", address); + return -1; +} + +int memfunc(void* memory, uint64_t address, void* buffer, unsigned int storing, unsigned int size) { + if (address >= IORANGE_BEGIN && address+size <= IORANGE_END) { + return iofunc(memory, address, buffer, storing, size); + } + //if (address % size) { + //fprintf(stderr, "memfunc(%p, 0x%lx, %p, %d, %d);\n", memory, address, buffer, storing, size); + //exit(-1); + //} + //address = (address << 32) >> 32; + //fprintf(stderr, "memfunc(%p, 0x%lx, %p, %d, %d);\n", memory, address, buffer, storing, size); + unsigned char* bmemory = memory; + if (size == 4) { + uint32_t* ibuffer = buffer; + uint32_t* istorage = (void*)(bmemory + address); + if (storing) { + *istorage = *ibuffer; + } else { + *ibuffer = *istorage; + } + return 0; + } else if (size == 8) { + uint64_t* ibuffer = buffer; + uint64_t* istorage = (void*)(bmemory + address); + if (storing) { + *istorage = *ibuffer; + } else { + *ibuffer = *istorage; + } + return 0; + } else if (size == 1) { + uint8_t* ibuffer = buffer; + uint8_t* istorage = (void*)(bmemory + address); + if (storing) { + *istorage = *ibuffer; + } else { + *ibuffer = *istorage; + } + return 0; + } else if (size == 2) { + uint16_t* ibuffer = buffer; + uint16_t* istorage = (void*)(bmemory + address); + if (storing) { + *istorage = *ibuffer; + } else { + *ibuffer = *istorage; + } + return 0; + } else { + return -1; + } +} + +int hexvalue(char ch) { + switch (ch) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + default: return -1; + } +} + +char linebuf[1000]; + +int insertsymbol(srve_debugsymbol_t** symvar, uint64_t addr, char* namebuffer) { + for (int i = 0; namebuffer[i] != 0; i++) { + if (namebuffer[i] == '.') { + return 0; // Skip adding entries for filenames and sections + } + } + srve_debugsymbol_t* sym = malloc(sizeof(srve_debugsymbol_t)); + if (!sym) { + return -1; + } + sym->addr = addr; + sym->name = strdup(namebuffer); + sym->next = NULL; + + for (int i = 0; sym->name[i] != 0; i++) { + if (sym->name[i] == '\r' || sym->name[i] == '\n') { + sym->name[i] = 0; + break; + } + } + + srve_debugsymbol_t* search = *symvar; + if (search == NULL || addr < search->addr) { + sym->next = search; + *symvar = sym; + return 0; + } + while (search->addr < addr) { + if (search->next == NULL || search->next->addr >= addr) { + sym->next = search->next; + search->next = sym; + return 0; + } + search = search->next; + } + sym->next = search->next; + search->next = sym; + return 0; +} + +int loadsymbols(srve_debugsymbol_t** symvar, char* fname) { + FILE* f = fopen(fname, "r"); + + while (fgets(linebuf, 1000, f)) { + char* s = linebuf; + uint64_t x = 0; + int digit; + while ((digit = hexvalue(*s)) >= 0) { + x *= 0x10; + x += digit; + s++; + } + while (*s == ' ') { + s++; + } + if (insertsymbol(symvar, x, s) != 0) { + return -1; + } + } + + fclose(f); + return 0; +} + +int loaddiskimage(vmrd_t* device, char* fname) { + FILE* f = fopen(fname, "r+"); + if (f) { + fseek(f, 0, SEEK_END); + device->file = f; + device->imagesize = ftell(f); + device->action = 'i'; // Initial action state + return 0; + } else { + device->file = NULL; + device->imagesize = 0; + device->action = 'f'; // Failed action state + return -1; + } +} + +int main(int argc, char** argv) { + fprintf(stderr, "SecureLang RV64/RV32 emulator, early testing version\n"); + + char* memory = calloc(4*1024*1024*1024ULL, 1); + if (memory == NULL) { + fprintf(stderr, "Setup failed!\n"); + + return -1; + } + uint64_t loadaddr = 0x8000000ULL; // TODO: Compiler bug prevents 0x80000000 as a constant, must be an integer conversion somewhere + loadaddr *= 0x10; + //printf("Loading at %dMB\n", loadaddr/(1024*1024)); + char* kfname = argv[1]; + char* sname = argv[2]; + char* fsname = argv[3]; + + loadf(memory, loadaddr, argv[1]); + + srve_debugsymbol_t* syms = NULL; + loadsymbols(&syms, sname); + + loaddiskimage(&vmrd_array[0], fsname); + + srve_debugsymbol_t* iter = syms; + while (iter) { + fprintf(stderr, "%lx %s\n", iter->addr, iter->name); + iter = iter->next; + } + + if (srve_core64_init(&core, &memfunc, memory, loadaddr) != 0) { + return -1; + } + core.symbols = syms; + core.tracing = 0; + + int64_t result; + do { + result = srve_core64_doinstr(&core); + } while (result == 0); + + return 0; +} diff --git a/srve.h b/srve.h new file mode 100644 index 0000000..8b266e6 --- /dev/null +++ b/srve.h @@ -0,0 +1,8 @@ +// This file includes a 32-bit and 64-bit implementation of the core. + +#define SRVE_CORE_32BIT +#include "srve_core.h" +#undef SRVE_CORE_32BIT +#define SRVE_CORE_64BIT +#include "srve_core.h" +#undef SRVE_CORE_64BIT diff --git a/srve_core.h b/srve_core.h new file mode 100644 index 0000000..fe08039 --- /dev/null +++ b/srve_core.h @@ -0,0 +1,126 @@ +#include + +// The layout of this file is slightly unusual due to 32+64bit support, +// it includes the files srve_core_h.h (structures) and srve_core_c.h (code) +// with some bookkeeping. + +#ifdef SRVE_CORE_64BIT + +#define SRVE_SREG_T int64_t +#define SRVE_UREG_T uint64_t +#define SRVE_CORE_T srve_core64_t +#define STRUCT_SRVE_CORE struct srve_core64 +#define SRVE_CORE_INIT srve_core64_init +#define SRVE_CORE_INSTRFUNC_T srve_core64_instrfunc_t +#define SRVE_CORE_INSTR_BAD srve_core64_instr_bad +#define SRVE_CORE_INSTR_REGALU srve_core64_instr_regalu +#define SRVE_CORE_INSTR_IMMALU srve_core64_instr_immalu +#define SRVE_CORE_INSTR_LOAD srve_core64_instr_load +#define SRVE_CORE_INSTR_STORE srve_core64_instr_store +#define SRVE_CORE_INSTR_AMO srve_core64_instr_amo +#define SRVE_CORE_INSTR_FENCE srve_core64_instr_fence +#define SRVE_CORE_INSTR_LUI srve_core64_instr_lui +#define SRVE_CORE_INSTR_AUIPC srve_core64_instr_auipc +#define SRVE_CORE_INSTR_SYSTEM srve_core64_instr_system +#define SRVE_CORE_INSTR_BRANCH srve_core64_instr_branch +#define SRVE_CORE_INSTR_JAL srve_core64_instr_jal +#define SRVE_CORE_INSTR_JALR srve_core64_instr_jalr +#define SRVE_CORE_DOALU srve_core64_doalu +#define SRVE_CORE_READCSR srve_core64_readcsr +#define SRVE_CORE_WRITECSR srve_core64_writecsr +#define SRVE_CORE_TRANSLATE srve_core64_translate +#define SRVE_CORE_LOAD srve_core64_load +#define SRVE_CORE_STORE srve_core64_store +#define SRVE_CORE_FETCH srve_core64_fetch +#define SRVE_CORE_FUNCNAME srve_core64_funcname +#define SRVE_CORE_DISASM srve_core64_disasm +#define SRVE_CORE_DOINSTR srve_core64_doinstr + +#ifndef SRVE_CORE_H64 +#define SRVE_CORE_H64 + +#include "srve_core_h.h" + +// From ifndef of SRVE_CORE_H64: +#endif +// From ifdef of SRVE_CORE_64BIT: +#else +#ifndef SRVE_CORE_32BIT +#error Either SRVE_CORE_64BIT or SRVE_CORE_32BIT must be defined to compile srve_core.h +#endif + +#define SRVE_SREG_T int32_t +#define SRVE_UREG_T uint32_t +#define SRVE_CORE_T srve_core32_t +#define STRUCT_SRVE_CORE struct srve_core32 +#define SRVE_CORE_INIT srve_core32_init +#define SRVE_CORE_INSTRFUNC_T srve_core32_instrfunc_t +#define SRVE_CORE_INSTR_BAD srve_core32_instr_bad +#define SRVE_CORE_INSTR_REGALU srve_core32_instr_regalu +#define SRVE_CORE_INSTR_IMMALU srve_core32_instr_immalu +#define SRVE_CORE_INSTR_LOAD srve_core32_instr_load +#define SRVE_CORE_INSTR_STORE srve_core32_instr_store +#define SRVE_CORE_INSTR_AMO srve_core32_instr_amo +#define SRVE_CORE_INSTR_FENCE srve_core32_instr_fence +#define SRVE_CORE_INSTR_LUI srve_core32_instr_lui +#define SRVE_CORE_INSTR_AUIPC srve_core32_instr_auipc +#define SRVE_CORE_INSTR_SYSTEM srve_core32_instr_system +#define SRVE_CORE_INSTR_BRANCH srve_core32_instr_branch +#define SRVE_CORE_INSTR_JAL srve_core32_instr_jal +#define SRVE_CORE_INSTR_JALR srve_core32_instr_jalr +#define SRVE_CORE_DOALU srve_core32_doalu +#define SRVE_CORE_READCSR srve_core32_readcsr +#define SRVE_CORE_WRITECSR srve_core32_writecsr +#define SRVE_CORE_TRANSLATE srve_core32_translate +#define SRVE_CORE_LOAD srve_core32_load +#define SRVE_CORE_STORE srve_core32_store +#define SRVE_CORE_FETCH srve_core32_fetch +#define SRVE_CORE_FUNCNAME srve_core32_funcname +#define SRVE_CORE_DISASM srve_core32_disasm +#define SRVE_CORE_DOINSTR srve_core32_doinstr + +#ifndef SRVE_CORE_H32 +#define SRVE_CORE_H32 + +#include "srve_core_h.h" + +// From ifndef of SRVE_CORE_H32: +#endif +#endif + +#ifdef SRVE_IMPLEMENTATION + +#include "srve_core_c.h" + +// From ifdef of SRVE_IMPLEMENTATION +#endif + +#undef SRVE_SREG_T +#undef SRVE_UREG_T +#undef SRVE_CORE_T +#undef SRVE_CORE_INSTRFUNC_T +#undef STRUCT_SRVE_CORE +#undef SRVE_CORE_INSTR_BAD +#undef SRVE_CORE_INSTR_REGALU +#undef SRVE_CORE_INSTR_IMMALU +#undef SRVE_CORE_INSTR_LOAD +#undef SRVE_CORE_INSTR_STORE +#undef SRVE_CORE_INSTR_AMO +#undef SRVE_CORE_INSTR_FENCE +#undef SRVE_CORE_INSTR_LUI +#undef SRVE_CORE_INSTR_AUIPC +#undef SRVE_CORE_INSTR_SYSTEM +#undef SRVE_CORE_INSTR_BRANCH +#undef SRVE_CORE_INSTR_JAL +#undef SRVE_CORE_INSTR_JALR +#undef SRVE_CORE_DOALU +#undef SRVE_CORE_READCSR +#undef SRVE_CORE_WRITECSR +#undef SRVE_CORE_INIT +#undef SRVE_CORE_TRANSLATE +#undef SRVE_CORE_LOAD +#undef SRVE_CORE_STORE +#undef SRVE_CORE_FETCH +#undef SRVE_CORE_FUNCNAME +#undef SRVE_CORE_DISASM +#undef SRVE_CORE_DOINSTR diff --git a/srve_core_c.h b/srve_core_c.h new file mode 100644 index 0000000..cae9602 --- /dev/null +++ b/srve_core_c.h @@ -0,0 +1,727 @@ +// This file contains the implementation of functions for emulating a 32-bit or +// 64-bit core, and is included by the logic in srve.h and srve_core.h with the +// appropriate defines set up. + +#define INSTR_DESTREG(i) (((i) >> 7)&31) +#define INSTR_SRCREG1(i) (((i) >> 15)&31) +#define INSTR_SRCREG2(i) (((i) >> 20)&31) +#define INSTR_FUNCT3(i) (((i) >> 12)&7) +#define INSTR_FUNCT7(i) (((i) >> 25)&127) + +#define INSTR_BTYPE_IMM(i) ((int32_t)(\ + ((((i)>>25)&63)<<5) \ + | ((((i)>>8)&15)<<1) \ + | ((((i)>>7)&1)<<11))) +#define INSTR_JTYPE_IMM(i) ((int32_t)(\ + ((((i)>>31)&1)?0xFFF00000:0) \ + | ((((i)>>21)&1023)<<1) \ + | ((((i)>>20)&1)<<11) \ + | ((((i)>>12)&255)<<12))) +#define INSTR_ITYPE_SIMM(i) ((int32_t)(((i)>>20)|((((i)>>31)&1)?(0xFFFFF<<12):0))) +#define INSTR_STYPE_SIMM(i) ((int32_t)(((INSTR_FUNCT7(i)|((((i)>>31)&1)?(0xFFFFFFF<<7):0))<<5)|(((i)>>7)&31))) +#define INSTR_ITYPE_UIMM(i) ((i)>>20) + +int SRVE_CORE_INSTR_BAD(SRVE_CORE_T* core, uint32_t instr) { + fprintf(stderr, "Bad instruction 0x%x major opcode %d (0x%x, 0b", instr, instr&127, instr&127); + for (int i = 6; i >= 0; i--) { + fprintf(stderr, (instr&(1<registers[INSTR_SRCREG1(instr)]; + SRVE_SREG_T rhs = core->registers[INSTR_SRCREG2(instr)]; + int op = INSTR_FUNCT3(instr); + SRVE_SREG_T result; + if (SRVE_CORE_DOALU(core, &result, lhs, op, rhs, INSTR_FUNCT7(instr)) != 0) { + return -1; + } + core->registers[INSTR_DESTREG(instr)] = result; + return 0; +} + +// For 64-bit, an additional set of 32-bit ALU operations is added. For +// simplicity this implementation just invokes the 32-bit ALU, as long +// as that shortcut works... +#ifdef SRVE_CORE_64BIT +int srve_core64_instr_regalu32(SRVE_CORE_T* core, uint32_t instr) { + int32_t lhs = (int32_t) core->registers[INSTR_SRCREG1(instr)]; + int32_t rhs = (int32_t) core->registers[INSTR_SRCREG2(instr)]; + int op = INSTR_FUNCT3(instr); + int32_t result; + if (srve_core32_doalu(core, &result, lhs, op, rhs, INSTR_FUNCT7(instr)) != 0) { + return -1; + } + core->registers[INSTR_DESTREG(instr)] = (SRVE_SREG_T) result; + return 0; +} +int srve_core64_instr_immalu32(SRVE_CORE_T* core, uint32_t instr) { + int32_t lhs = (int32_t) core->registers[INSTR_SRCREG1(instr)]; + int32_t rhs = (int32_t) INSTR_ITYPE_SIMM(instr); + int op = INSTR_FUNCT3(instr); + int32_t result; + if (srve_core32_doalu(core, &result, lhs, op, rhs, INSTR_FUNCT7(instr)) != 0) { + return -1; + } + core->registers[INSTR_DESTREG(instr)] = (SRVE_SREG_T) result; + return 0; +} +#endif + +int SRVE_CORE_INSTR_IMMALU(SRVE_CORE_T* core, uint32_t instr) { + SRVE_SREG_T lhs = core->registers[INSTR_SRCREG1(instr)]; + SRVE_SREG_T rhs = (SRVE_SREG_T) INSTR_ITYPE_SIMM(instr); + int op = INSTR_FUNCT3(instr); + SRVE_SREG_T result; + if (SRVE_CORE_DOALU(core, &result, lhs, op, rhs, 0) != 0) { + return -1; + } + core->registers[INSTR_DESTREG(instr)] = result; + return 0; +} + +int SRVE_CORE_INSTR_LOAD(SRVE_CORE_T* core, uint32_t instr) { + int f = INSTR_FUNCT3(instr); + SRVE_UREG_T addr = core->registers[INSTR_SRCREG1(instr)] + (SRVE_SREG_T) INSTR_ITYPE_SIMM(instr); + switch (f) { + +#ifdef SRVE_CORE_64BIT + case 0x2: { + // 32-bit load, signed + int32_t value; + if (SRVE_CORE_LOAD(core, addr, &value, 4) != 0) { + return -1; + } + core->registers[INSTR_DESTREG(instr)] = (SRVE_SREG_T) value; + return 0; + } + case 0x3: { + // 64-bit load + int i = SRVE_CORE_LOAD(core, addr, &core->registers[INSTR_DESTREG(instr)], 8); + //fprintf(stderr, "Loaded 0x%llx from 0x%llx\n", core->registers[INSTR_DESTREG(instr)], addr); + return i; + } + case 0x6: { + // 32-bit load, unsigned + uint32_t value; + if (SRVE_CORE_LOAD(core, addr, &value, 4) != 0) { + return -1; + } + core->registers[INSTR_DESTREG(instr)] = (SRVE_UREG_T) value; + return 0; + } +#endif + + case 0x0: { + // 8-bit signed load + int8_t value; + if (SRVE_CORE_LOAD(core, addr, &value, 1) != 0) { + return -1; + } + core->registers[INSTR_DESTREG(instr)] = (SRVE_SREG_T) value; + return 0; + } + + case 0x1: { + // 16-bit signed load + int16_t value; + if (SRVE_CORE_LOAD(core, addr, &value, 2) != 0) { + return -1; + } + core->registers[INSTR_DESTREG(instr)] = (SRVE_SREG_T) value; + return 0; + } + + case 0x4: { + // 8-bit unsigned load + uint8_t value; + if (SRVE_CORE_LOAD(core, addr, &value, 1) != 0) { + return -1; + } + core->registers[INSTR_DESTREG(instr)] = (SRVE_UREG_T) value; + return 0; + } + + case 0x5: { + // 16-bit unsigned load + uint16_t value; + if (SRVE_CORE_LOAD(core, addr, &value, 2) != 0) { + return -1; + } + core->registers[INSTR_DESTREG(instr)] = (SRVE_UREG_T) value; + return 0; + } + default: + fprintf(stderr, "Bad funct3 in load: 0x%x\n", f); + return -1; + } +} + +int SRVE_CORE_INSTR_STORE(SRVE_CORE_T* core, uint32_t instr) { + int f = INSTR_FUNCT3(instr); + SRVE_UREG_T addr = core->registers[INSTR_SRCREG1(instr)] + INSTR_STYPE_SIMM(instr); + switch (f) { + case 0x0: { + // 8-bit store + int8_t value = (int8_t) core->registers[INSTR_SRCREG2(instr)]; + return SRVE_CORE_STORE(core, addr, &value, 2); + } + case 0x1: { + // 16-bit store + int16_t value = (int16_t) core->registers[INSTR_SRCREG2(instr)]; + return SRVE_CORE_STORE(core, addr, &value, 2); + } + +#ifdef SRVE_CORE_64BIT + case 0x2: { + // 32-bit store + int32_t value = (int32_t) core->registers[INSTR_SRCREG2(instr)]; + return SRVE_CORE_STORE(core, addr, &value, 4); + } + case 0x3: { + // 64-bit store + int i = SRVE_CORE_STORE(core, addr, &core->registers[INSTR_SRCREG2(instr)], 8); + //fprintf(stderr, "Stored 0x%llx at 0x%llx\n", core->registers[INSTR_SRCREG2(instr)], addr); + return i; + } break; +#endif + + default: + fprintf(stderr, "Bad funct3 in store: 0x%x\n", f); + return -1; + } +} + +int SRVE_CORE_INSTR_AMO(SRVE_CORE_T* core, uint32_t instr) { + // TODO: These atomic operations should use similar atomic ops on the + // host machine, this implementation is only fit for testing on single + // core machines. + uint32_t special = instr >> 27; + uint32_t width = INSTR_FUNCT3(instr); + SRVE_UREG_T addr = core->registers[INSTR_SRCREG1(instr)]; + + if (special == 1 && width == 2) { // AMOSWAP.W + int32_t valread; + if (SRVE_CORE_LOAD(core, addr, &valread, 4) != 0) { + return -1; + } + int32_t valwritten = (int32_t) core->registers[INSTR_SRCREG2(instr)]; + if (SRVE_CORE_STORE(core, addr, &valwritten, 4) != 0) { + return -1; + } + core->registers[INSTR_DESTREG(instr)] = (SRVE_SREG_T) valread; + return 0; + } + + fprintf(stderr, "Unrecognised atomic memory operation %d (%x) width %d\n", special, special, width); + return -1; +} + +int SRVE_CORE_INSTR_FENCE(SRVE_CORE_T* core, uint32_t instr) { + // TODO: This is taken as a no-op for now but will need to be finished + // to test multicore setups. + return 0; +} + +int SRVE_CORE_INSTR_LUI(SRVE_CORE_T* core, uint32_t instr) { + int32_t constval = (instr >> 12) << 12; + //fprintf(stderr, "lui: Setting reg #%d to 0x%lx\n", INSTR_DESTREG(instr), ((SRVE_SREG_T) constval)); + core->registers[INSTR_DESTREG(instr)] = ((SRVE_SREG_T) constval); + return 0; +} + +int SRVE_CORE_INSTR_AUIPC(SRVE_CORE_T* core, uint32_t instr) { + int32_t constval = (instr >> 12) << 12; + //fprintf(stderr, "auipc: Setting reg #%d to 0x%lx\n", INSTR_DESTREG(instr), core->ip + ((SRVE_SREG_T) constval)); + core->registers[INSTR_DESTREG(instr)] = core->ip + ((SRVE_SREG_T) constval); + return 0; +} + +int SRVE_CORE_INSTR_SYSTEM(SRVE_CORE_T* core, uint32_t instr) { + //fprintf(stderr, "Got system instruction, dest reg is %d funct3 is %d immediate is %d (0x%x)\n", INSTR_DESTREG(instr), INSTR_FUNCT3(instr), INSTR_ITYPE_UIMM(instr), INSTR_ITYPE_UIMM(instr)); + int f = INSTR_FUNCT3(instr); + + if (f == 0 && INSTR_FUNCT7(instr) == 0) { // ecall + core->sepc = core->ip; + core->ip_next = (core->stvec >> 2) << 2; + core->scause = 8; + return 0; + } else if (f == 0 && INSTR_FUNCT7(instr) == 24) { // mret + core->ip_next = core->mepc; + return 0; + } else if (f == 0 && INSTR_FUNCT7(instr) == 8) { // sret + core->ip_next = core->sepc; + return 0; + } else if (f == 0 && INSTR_FUNCT7(instr) == 9) { // sfence.vma + // TODO... This is probably safe as a no-op on single core runs + return 0; + } else if (f == 0) { + printf("Unknown subfunction #%d\n", INSTR_FUNCT7(instr)); + return -1; + } + switch (f) { + case 1: { // csrrw + uint64_t tmp; + if (SRVE_CORE_READCSR(core, INSTR_ITYPE_UIMM(instr), &tmp) != 0) { + return -1; + } + SRVE_CORE_WRITECSR(core, INSTR_ITYPE_UIMM(instr), core->registers[INSTR_SRCREG1(instr)]); + core->registers[INSTR_DESTREG(instr)] = tmp; + } return 0; + case 2: { // csrrs + uint64_t tmp; + if (SRVE_CORE_READCSR(core, INSTR_ITYPE_UIMM(instr), &tmp) != 0) { + return -1; + } + SRVE_CORE_WRITECSR(core, INSTR_ITYPE_UIMM(instr), tmp | core->registers[INSTR_SRCREG1(instr)]); + core->registers[INSTR_DESTREG(instr)] = tmp; + } return 0; + default: + fprintf(stderr, "Bad funct3 in system instruction: 0x%x\n", f); + return -1; + } + + return -1; +} + +int SRVE_CORE_INSTR_BRANCH(SRVE_CORE_T* core, uint32_t instr) { + SRVE_UREG_T addr = core->ip + INSTR_BTYPE_IMM(instr); + //fprintf(stderr, "instr: "); + //for (int i = 63; i >=0; i--) { fprintf(stderr, (i%8)?"%s":"%s ", ((instr>>i)&1)?"1":"0"); } + //fprintf(stderr, " addr: "); + //for (int i = 63; i >=0; i--) { fprintf(stderr, (i%8)?"%s":"%s ", ((addr>>i)&1)?"1":"0"); } + //fprintf(stderr, "\n"); + //fprintf(stderr, "b?? %lx <%s>\n", (long) addr, SRVE_CORE_FUNCNAME(core, addr)); + + SRVE_SREG_T lhs = core->registers[INSTR_SRCREG1(instr)]; + SRVE_SREG_T rhs = core->registers[INSTR_SRCREG2(instr)]; + + switch (INSTR_FUNCT3(instr)) { + case 0: + //printf("Comparing %ld to %ld\n", lhs, rhs); + if (lhs == rhs) { + goto dojump; + } + return 0; + case 1: + if (lhs != rhs) { + goto dojump; + } + return 0; + case 4: + if (lhs < rhs) { + goto dojump; + } + return 0; + case 5: + if (lhs >= rhs) { + goto dojump; + } + return 0; + case 6: + if (((SRVE_UREG_T)lhs) < ((SRVE_UREG_T)rhs)) { + goto dojump; + } + return 0; + case 7: + if (((SRVE_UREG_T)lhs) >= ((SRVE_UREG_T)rhs)) { + goto dojump; + } + return 0; + + default: + fprintf(stderr, "Bad branch comparison %d\n", INSTR_FUNCT3(instr)); + return -1; + } + + dojump: + core->ip_next = core->ip + INSTR_BTYPE_IMM(instr); + return 0; +} + +int SRVE_CORE_INSTR_JAL(SRVE_CORE_T* core, uint32_t instr) { + SRVE_UREG_T addr = core->ip + (SRVE_SREG_T)INSTR_JTYPE_IMM(instr); + //fprintf(stderr, "instr: "); + //for (int i = 63; i >=0; i--) { fprintf(stderr, (i%8)?"%s":"%s ", ((instr>>i)&1)?"1":"0"); } + //fprintf(stderr, " addr: "); + //for (int i = 63; i >=0; i--) { fprintf(stderr, (i%8)?"%s":"%s ", ((addr>>i)&1)?"1":"0"); } + //fprintf(stderr, " 0x550: "); + //for (int i = 63; i >=0; i--) { fprintf(stderr, (i%8)?"%s":"%s ", ((0x550>>i)&1)?"1":"0"); } + //fprintf(stderr, "\n"); + //fprintf(stderr, "jal %lx <%s>\n", (long) addr, SRVE_CORE_FUNCNAME(core, addr)); + core->registers[INSTR_DESTREG(instr)] = core->ip_next; + core->ip_next = (core->ip + (SRVE_SREG_T)INSTR_JTYPE_IMM(instr)); + return 0; +} + +int SRVE_CORE_INSTR_JALR(SRVE_CORE_T* core, uint32_t instr) { + SRVE_UREG_T addr = core->registers[INSTR_SRCREG1(instr)] + (SRVE_SREG_T)INSTR_ITYPE_UIMM(instr); + if (core->tracing) { + fprintf(stderr, "instr: "); + for (int i = 63; i >=0; i--) { fprintf(stderr, (i%8)?"%s":"%s ", ((instr>>i)&1)?"1":"0"); } + fprintf(stderr, " addr: "); + for (int i = 63; i >=0; i--) { fprintf(stderr, (i%8)?"%s":"%s ", ((addr>>i)&1)?"1":"0"); } + fprintf(stderr, " 0x550: "); + for (int i = 63; i >=0; i--) { fprintf(stderr, (i%8)?"%s":"%s ", ((0x550>>i)&1)?"1":"0"); } + fprintf(stderr, "\n"); + fprintf(stderr, "jalr -> %lx <%s>\n", (long) addr, SRVE_CORE_FUNCNAME(core, addr)); + } + core->registers[INSTR_DESTREG(instr)] = core->ip_next; + core->ip_next = (core->registers[INSTR_SRCREG1(instr)] + INSTR_ITYPE_UIMM(instr)); + return 0; +} + +int SRVE_CORE_DOALU(SRVE_CORE_T* core, SRVE_SREG_T* outputvar, SRVE_SREG_T lhs, int op, SRVE_SREG_T rhs, int modifier) { + SRVE_SREG_T result; + if (modifier == 1) { // Multiplication extensions + switch (op) { + case 0: // Multiply (signed?? does this differ between 32/64-bit RISC-V? TODO: Deep dive on these issues) + result = lhs * rhs; + break; + case 4: // Division + result = lhs / rhs; + break; + case 6: // Remainder + result = lhs % rhs; + break; + default: + fprintf(stderr, "Bad ALU multiplicative op %d\n", op); + return -1; + } + } else { // Base ALU operations + switch (op) { + case 0: // Addition/subtraction + if (modifier == 0x20) { + result = lhs - rhs; + } else { + result = lhs + rhs; + } + break; + case 1: // Shift left + result = lhs << rhs; + break; + case 2: // Set less than, signed + //printf("Comparing %ld < %ld\n", lhs, rhs); + result = (((SRVE_SREG_T)lhs) < ((SRVE_SREG_T)rhs)) ? 1 : 0; + break; + case 3: // Set less than, unsigned + result = (((SRVE_UREG_T)lhs) < ((SRVE_UREG_T)rhs)) ? 1 : 0; + break; + case 4: // Exclusive OR (XOR) + //printf("xoring %ld ^ %ld\n", lhs, rhs); + result = lhs ^ rhs; + break; + case 5: // Shift right, either signed or unsigned + if (modifier) { + result = ((SRVE_SREG_T)lhs) >> rhs; + } else { + result = ((SRVE_UREG_T)lhs) >> rhs; + } + break; + case 6: // OR + result = lhs | rhs; + break; + case 7: // AND + result = lhs & rhs; + break; + default: + fprintf(stderr, "Bad ALU op %d\n", op); + return -1; + } + } + + *outputvar = result; + return 0; +} + +int SRVE_CORE_LOAD(SRVE_CORE_T* core, uint64_t addr, void* buffer, unsigned int size) { + srve_memfunc_t memfunc = core->memfunc; + addr = SRVE_CORE_TRANSLATE(core, addr); + return memfunc(core->memory, addr, buffer, 0, size); +} + +int SRVE_CORE_STORE(SRVE_CORE_T* core, uint64_t addr, void* buffer, unsigned int size) { + srve_memfunc_t memfunc = core->memfunc; + addr = SRVE_CORE_TRANSLATE(core, addr); + return memfunc(core->memory, addr, buffer, 1, size); +} + +int SRVE_CORE_READCSR(SRVE_CORE_T* core, int addr, SRVE_UREG_T* outputvar) { + switch (addr) { + case 0x100: // sstatus + *outputvar = 0; + return 0; + case 0x104: // sie + *outputvar = 0; + return 0; + case 0x105: // stvec + *outputvar = core->stvec; + return 0; + case 0x140: // sscratch + *outputvar = core->sscratch; + return 0; + case 0x141: // sepc + *outputvar = core->sepc; + return 0; + case 0x142: // scause + *outputvar = core->scause; + return 0; + case 0x14d: // stimecmp, apparently part of the Sstc extension + *outputvar = 0; + return 0; + case 0x180: // satp + *outputvar = (core->pagetable >> 12) | (core->pagetable ? (8ULL<<60) : 0); + return 0; + case 0x300: // mstatus + *outputvar = 0; + return 0; + case 0x302: // medeleg + *outputvar = 0; + return 0; + case 0x303: // mideleg + *outputvar = 0; + return 0; + case 0x304: // mie + *outputvar = 0; + return 0; + case 0x306: // mcounteren + *outputvar = 0; + return 0; + case 0x30a: // menvcfg + *outputvar = 0; + return 0; + case 0x341: // mepc + *outputvar = core->mepc; + return 0; + case 0x3a0: // pmpcfg0 + *outputvar = 0; + return 0; + case 0x3b0: // pmpaddr0 + *outputvar = 0; + return 0; + case 0xc01: // time + *outputvar = 0; + return 0; + case 0xf14: // hartid + *outputvar = 0; + return 0; + default: + fprintf(stderr, "Bad CSR #%d (0x%x)\n", addr, addr); + return -1; + } +} + +int SRVE_CORE_WRITECSR(SRVE_CORE_T* core, int addr, SRVE_UREG_T value) { + switch (addr) { + case 0x105: // stvec + core->stvec = value; + return 0; + case 0x140: // sscratch + core->sscratch = value; + return 0; + case 0x141: // sepc + core->sepc = value; + return 0; + case 0x142: // scause + core->scause = value; + return 0; + case 0x180: // satp + if ((value >> 60) == 8) { + core->pagetable = value << 12; + return 0; + } else if (value == 0) { + core->pagetable = 0; + return 0; + } else { + fprintf(stderr, "Bad page table address encoding, high part of value is %ld (0x%lx)\n", value>>60, value>>60); + return -1; + } + case 0x341: + core->mepc = value; + return 0; + default: + return -1; + } +} + +// Returns the "physical" address of the given "virtual" address. +SRVE_UREG_T SRVE_CORE_TRANSLATE(SRVE_CORE_T* core, SRVE_UREG_T vaddr) { + SRVE_UREG_T outertbl = core->pagetable; + if (!outertbl) { + // If core->pagetable is not set via the satp CSR then address + // translation is disabled, and the physical address is the same as + // the "virtual" address. + return vaddr; + } + srve_memfunc_t memfunc = core->memfunc; + SRVE_UREG_T l2idx = (vaddr>>30)&511; + SRVE_UREG_T l1idx = (vaddr>>21)&511; + SRVE_UREG_T l0idx = (vaddr>>12)&511; + SRVE_UREG_T l1tbl; + SRVE_UREG_T l0tbl; + SRVE_UREG_T result; + if (memfunc(core->memory, outertbl+(l2idx*8), &l1tbl, 0, 8)) { + return 0; + } + l1tbl = (l1tbl >> 10) << 12; + if (memfunc(core->memory, l1tbl+(l1idx*8), &l0tbl, 0, 8)) { + return 0; + } + l0tbl = (l0tbl >> 10) << 12; + if (memfunc(core->memory, l0tbl+(l0idx*8), &result, 0, 8)) { + return 0; + } + result = ((result >> 10) << 12) | (vaddr & 0xFFFULL); + //fprintf(stderr, "Translated virtual address 0x%lx to physical address 0x%lx\n", vaddr, result); + return result; +} + +int SRVE_CORE_FETCH(SRVE_CORE_T* core, uint32_t* instrvar) { + SRVE_UREG_T ip = core->ip; + if (SRVE_CORE_LOAD(core, ip, instrvar, 4) < 0) { + return -1; + } + core->ip_next = ip + 4; + return 0; +} + +const char* SRVE_CORE_FUNCNAME(SRVE_CORE_T* core, SRVE_UREG_T addr) { + srve_debugsymbol_t* sym = core->symbols; + while (sym != NULL) { + if (sym->next == NULL) { + if (sym->addr <= addr) { + return sym->name; + } else { + return "???"; + } + } + if (sym->addr <= addr && sym->next->addr > addr) { + return sym->name; + } + sym = sym->next; + } + return "???"; +} + +int SRVE_CORE_DISASM(SRVE_CORE_T* core, uint32_t instr) { + FILE* output = stderr; + switch (instr & 127) { + case 0x03: { + int f = INSTR_FUNCT3(instr); + SRVE_UREG_T addr = core->registers[INSTR_SRCREG1(instr)] + INSTR_ITYPE_SIMM(instr); + fprintf(output, "load size=%d dest=x%d src1=x%d imm=%ld\n", f, INSTR_DESTREG(instr), INSTR_SRCREG1(instr), (long) INSTR_ITYPE_SIMM(instr)); + } return 0; + case 0x13: { + SRVE_SREG_T rhs = INSTR_ITYPE_SIMM(instr); + int op = INSTR_FUNCT3(instr); + fprintf(output, "alu immediate op #%d, dest=x%d src1=x%d imm=%ld\n", op, INSTR_DESTREG(instr), INSTR_SRCREG1(instr), rhs); + } return 0; + case 0x17: { + int32_t constval = (instr >> 12) << 12; + fprintf(output, "auipc dest=x%d expanded value is 0x%x (%d)\n", INSTR_DESTREG(instr), constval, constval); + } return 0; + case 0x23: { + int f = INSTR_FUNCT3(instr); + SRVE_UREG_T addr = core->registers[INSTR_SRCREG1(instr)] + INSTR_STYPE_SIMM(instr); + fprintf(output, "store size=%d src1=x%d src2=x%d imm=%d (0x%x)\n", f, INSTR_SRCREG1(instr), INSTR_SRCREG2(instr), INSTR_STYPE_SIMM(instr), INSTR_STYPE_SIMM(instr)); + } return 0; + case 0x33: { + int op = INSTR_FUNCT3(instr); + int subop = INSTR_FUNCT7(instr); + fprintf(output, "alu register op #%d subop %d, dest=x%d src1=x%d src2=%d\n", op, subop, INSTR_DESTREG(instr), INSTR_SRCREG1(instr), INSTR_SRCREG2(instr)); + } return 0; + case 0x37: { + int32_t constval = (instr >> 12) << 12; + fprintf(output, "lui dest=x%d expanded value is 0x%x (%d)\n", INSTR_DESTREG(instr), constval, constval); + } return 0; + case 0x73: { + fprintf(output, "system instruction, dest reg is %d funct3 is %d immediate is %d (0x%x)\n", INSTR_DESTREG(instr), INSTR_FUNCT3(instr), INSTR_ITYPE_UIMM(instr), INSTR_ITYPE_UIMM(instr)); + } return 0; + case 0x63: case 0x67: case 0x6f: { + fprintf(output, "TODO: branch/jump\n"); + } return 0; + default: + fprintf(output, "Unknown Instruction 0x%x, major opcode %d (0x%x)\n", instr, instr & 127, instr & 127); + return -1; + } +} + +SRVE_SREG_T SRVE_CORE_DOINSTR(SRVE_CORE_T* core) { + uint32_t instr; + + // Rather than having if statements to check if the zero register is used, + // just set it to zero at the start of each instruction. It shouldn't matter + // if it's overwritten because it'll be set to zero again before reading. + core->registers[0] = 0; + + if (SRVE_CORE_FETCH(core, &instr) < 0) { + return -1; + } + if (core->tracing /*(core->ip & 0x3ffffff000) == 0x3ffffff000*/) { + fprintf(stderr, "%lX: in function '%s' instruction 0x%X op 0x%X\n", core->ip, SRVE_CORE_FUNCNAME(core, core->ip), instr, instr & 127); + + if (SRVE_CORE_DISASM(core, instr) != 0) { + //return -1; + } + } + // Look up instruction based on the lower 6 bits: + SRVE_CORE_INSTRFUNC_T instrfunc = core->instrfuncs[instr & 127]; + + if (instrfunc(core, instr) != 0) { + fprintf(stderr, "%lX: FAILURE in function '%s' instruction 0x%X op 0x%X\n", core->ip, SRVE_CORE_FUNCNAME(core, core->ip), instr, instr & 127); + + return -1; + } + if (core->tracing) { + for (int i = 1; i < 32; i++) { + if (core->registers[i] != 0) { + fprintf(stderr, "x%d: 0x%llx\n", i, core->registers[i]); + } + } + } + if (core->ip == core->ip_next) { + fprintf(stderr, "Infinite loop detected, exiting emulator.\n"); + exit(-1); + } + + core->ip = core->ip_next; + + return 0; +} + +int SRVE_CORE_INIT(SRVE_CORE_T* core, srve_memfunc_t memfunc, void* memdata, SRVE_UREG_T ip) { + core->memory = memdata; + core->memfunc = memfunc; + core->pagetable = 0; + core->symbols = NULL; + core->ip = ip; + core->ip_next = ip; + for (int i = 0; i < 32; i++) { + core->registers[i] = 0; + } + for (int i = 0; i < 128; i++) { + core->instrfuncs[i] = &SRVE_CORE_INSTR_BAD; + } + core->instrfuncs[0x03] = &SRVE_CORE_INSTR_LOAD; + core->instrfuncs[0x0F] = &SRVE_CORE_INSTR_FENCE; + core->instrfuncs[0x13] = &SRVE_CORE_INSTR_IMMALU; + core->instrfuncs[0x17] = &SRVE_CORE_INSTR_AUIPC; +#ifdef SRVE_CORE_64BIT + core->instrfuncs[0x1B] = &srve_core64_instr_immalu32; + core->instrfuncs[0x3B] = &srve_core64_instr_regalu32; +#endif + core->instrfuncs[0x23] = &SRVE_CORE_INSTR_STORE; + core->instrfuncs[0x2F] = &SRVE_CORE_INSTR_AMO; + core->instrfuncs[0x33] = &SRVE_CORE_INSTR_REGALU; + core->instrfuncs[0x37] = &SRVE_CORE_INSTR_LUI; + core->instrfuncs[0x63] = &SRVE_CORE_INSTR_BRANCH; + core->instrfuncs[0x67] = &SRVE_CORE_INSTR_JALR; + core->instrfuncs[0x6F] = &SRVE_CORE_INSTR_JAL; + core->instrfuncs[0x73] = &SRVE_CORE_INSTR_SYSTEM; + return 0; +} diff --git a/srve_core_h.h b/srve_core_h.h new file mode 100644 index 0000000..8dc9bc7 --- /dev/null +++ b/srve_core_h.h @@ -0,0 +1,96 @@ +// This file is included by srve_core.h to define the structures for either a +// 64-bit or 32-bit core. It is only included once for each bit size. + +// Some definitions in here are still common to both 64-bit and 32-bit code so +// they are only defined once. +#ifndef SRVE_CORE_H_COMMON +#define SRVE_CORE_H_COMMON +// This is similar to the rv_bus_cb type in Max Nurzia's RISC-V emulator. +typedef int (*srve_memfunc_t)(void* memory, uint64_t address, void* buffer, unsigned int storing, unsigned int size); +typedef struct srve_debugsymbol srve_debugsymbol_t; +struct srve_debugsymbol { + uint64_t addr; + char* name; + srve_debugsymbol_t* next; +}; +#endif + +typedef STRUCT_SRVE_CORE SRVE_CORE_T; + +// Type of a function which handles an instruction, returning 0 on success or +// non-zero on failure. These functions are called inbetween fetching and +// incrementing the instruction pointer, and there is generally one function +// per major instruction (per instruction-group) with the function pointers +// stored in the core structure. This allows for easy replacement of functions +// for special debugging purposes (e.g. for tracking jump instructions only or +// for checking load/store instructions for alignment). +typedef int (*SRVE_CORE_INSTRFUNC_T)(SRVE_CORE_T* core, uint32_t instr); + +STRUCT_SRVE_CORE { + void* memory; + srve_memfunc_t memfunc; + + srve_debugsymbol_t* symbols; // This is managed externally, but is a sorted list that can be checked for debug names + + SRVE_UREG_T ip; // Instruction pointer + SRVE_UREG_T ip_next; // Next instruction pointer + + SRVE_UREG_T pagetable; // Address of the page table (not quite the same format as when accessed in satp) + + SRVE_UREG_T mepc; // Stored machine mode instruction pointer (program counter) + SRVE_UREG_T sepc; // System mode exception instruction pointer + SRVE_UREG_T stvec; // Vector handler address (& flags in bottom two bits) + SRVE_UREG_T sscratch; // Scratch register + SRVE_UREG_T scause; + + SRVE_UREG_T tracing; + + SRVE_SREG_T registers[32]; + SRVE_CORE_INSTRFUNC_T instrfuncs[128]; // There are 128 possible major instructions +}; + +int SRVE_CORE_INIT(SRVE_CORE_T* core, srve_memfunc_t memfunc, void* memdata, SRVE_UREG_T ip); + +// This instruction function is invoked for any unimplemented functions. +int SRVE_CORE_INSTR_BAD(SRVE_CORE_T* core, uint32_t instr); + +// The main ALU instructions are divided between two major opcodes, one dealing +// with registers only and the other where immediate values are combined with +// registers. +int SRVE_CORE_INSTR_REGALU(SRVE_CORE_T* core, uint32_t instr); +int SRVE_CORE_INSTR_IMMALU(SRVE_CORE_T* core, uint32_t instr); + +// The typical memory-to-register load instructions are all one major opcode +int SRVE_CORE_INSTR_LOAD(SRVE_CORE_T* core, uint32_t instr); +int SRVE_CORE_INSTR_STORE(SRVE_CORE_T* core, uint32_t instr); + +int SRVE_CORE_INSTR_LUI(SRVE_CORE_T* core, uint32_t instr); +int SRVE_CORE_INSTR_AUIPC(SRVE_CORE_T* core, uint32_t instr); + +// The syscall instruction seems to be extended for other "system" features too +int SRVE_CORE_INSTR_SYSTEM(SRVE_CORE_T* core, uint32_t instr); + +int SRVE_CORE_INSTR_BRANCH(SRVE_CORE_T* core, uint32_t instr); +int SRVE_CORE_INSTR_JAL(SRVE_CORE_T* core, uint32_t instr); +int SRVE_CORE_INSTR_JALR(SRVE_CORE_T* core, uint32_t instr); + +int SRVE_CORE_DOALU(SRVE_CORE_T* core, SRVE_SREG_T* outputvar, SRVE_SREG_T lhs, int op, SRVE_SREG_T rhs, int modifier); + +// Returns the "physical" address of the given "virtual" address. +SRVE_UREG_T SRVE_CORE_TRANSLATE(SRVE_CORE_T* core, SRVE_UREG_T vaddr); + +int SRVE_CORE_LOAD(SRVE_CORE_T* core, uint64_t addr, void* buffer, unsigned int size); +int SRVE_CORE_STORE(SRVE_CORE_T* core, uint64_t addr, void* buffer, unsigned int size); + +int SRVE_CORE_READCSR(SRVE_CORE_T* core, int addr, SRVE_UREG_T* outputvar); +int SRVE_CORE_WRITECSR(SRVE_CORE_T* core, int addr, SRVE_UREG_T value); + +int SRVE_CORE_FETCH(SRVE_CORE_T* core, uint32_t* instrvar); + +// Looks through debug symbols to find a function name, returns the associated +// symbol or a dummy string if the symbol table or function doesn't exist. This +// is usually called with the instruction pointer as the address to reveal where +// the program is currently at. +const char* SRVE_CORE_FUNCNAME(SRVE_CORE_T* core, SRVE_UREG_T addr); + +SRVE_SREG_T SRVE_CORE_DOINSTR(SRVE_CORE_T* core);