Initial git commit of my new RV64/RV32 emulator code. Not documented or fully working yet.

This commit is contained in:
2025-06-05 03:27:25 +10:00
parent b524879f7e
commit e40457ee47
5 changed files with 1355 additions and 0 deletions

398
srve.c Normal file
View File

@@ -0,0 +1,398 @@
// Simple emulator program
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#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;
}

8
srve.h Normal file
View File

@@ -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

126
srve_core.h Normal file
View File

@@ -0,0 +1,126 @@
#include <stdint.h>
// 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

727
srve_core_c.h Normal file
View File

@@ -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<<i)) ? "1" : "0");
}
fprintf(stderr, ")\n");
if ((instr & 3) != 3) {
fprintf(stderr, "This appears to be a compressed instruction, these are not supported in this version of the debugger!\n");
}
return -1;
}
int SRVE_CORE_INSTR_REGALU(SRVE_CORE_T* core, uint32_t instr) {
SRVE_SREG_T lhs = core->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;
}

96
srve_core_h.h Normal file
View File

@@ -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);