Initial git commit of my new RV64/RV32 emulator code. Not documented or fully working yet.
This commit is contained in:
398
srve.c
Normal file
398
srve.c
Normal 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
8
srve.h
Normal 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
126
srve_core.h
Normal 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
727
srve_core_c.h
Normal 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
96
srve_core_h.h
Normal 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);
|
Reference in New Issue
Block a user