refactor: 重构前端代码并添加日志功能
- 重命名和重构了多个文件,包括 lexer、parser 和 AST 相关代码 - 添加了日志功能,使用 LOG_* 宏替代原有的 error 和 warn 函数 - 优化了错误处理和内存分配方式 - 调整了代码结构,提高了模块化和可读性
This commit is contained in:
8
ccompiler/backend/riscv32/rv_vm/Makefile
Normal file
8
ccompiler/backend/riscv32/rv_vm/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
CC = gcc
|
||||
CFLAGS = -g -Wall
|
||||
|
||||
all = rv32-vm
|
||||
|
||||
CFLAGS += -DDEFAULT_FILE='\"flat.bin\"'
|
||||
rv32-vm:
|
||||
$(CC) $(CFLAGS) -g -o rv32-vm .\ripes-vm.c
|
520
ccompiler/backend/riscv32/rv_vm/mini-rv32ima.c
Normal file
520
ccompiler/backend/riscv32/rv_vm/mini-rv32ima.c
Normal file
@ -0,0 +1,520 @@
|
||||
// Copyright 2022 Charles Lohr, you may use this file or any portions herein under any of the BSD, MIT, or CC0 licenses.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "default64mbdtc.h"
|
||||
|
||||
// Just default RAM amount is 64MB.
|
||||
uint32_t ram_amt = 64*1024*1024;
|
||||
int fail_on_all_faults = 0;
|
||||
|
||||
static int64_t SimpleReadNumberInt( const char * number, int64_t defaultNumber );
|
||||
static uint64_t GetTimeMicroseconds();
|
||||
static void ResetKeyboardInput();
|
||||
static void CaptureKeyboardInput();
|
||||
static uint32_t HandleException( uint32_t ir, uint32_t retval );
|
||||
static uint32_t HandleControlStore( uint32_t addy, uint32_t val );
|
||||
static uint32_t HandleControlLoad( uint32_t addy );
|
||||
static void HandleOtherCSRWrite( uint8_t * image, uint16_t csrno, uint32_t value );
|
||||
static int32_t HandleOtherCSRRead( uint8_t * image, uint16_t csrno );
|
||||
static void MiniSleep();
|
||||
static int IsKBHit();
|
||||
static int ReadKBByte();
|
||||
|
||||
// This is the functionality we want to override in the emulator.
|
||||
// think of this as the way the emulator's processor is connected to the outside world.
|
||||
#define MINIRV32WARN( x... ) printf( x );
|
||||
#define MINIRV32_DECORATE static
|
||||
#define MINI_RV32_RAM_SIZE ram_amt
|
||||
#define MINIRV32_IMPLEMENTATION
|
||||
#define MINIRV32_POSTEXEC( pc, ir, retval ) { if( retval > 0 ) { if( fail_on_all_faults ) { printf( "FAULT\n" ); return 3; } else retval = HandleException( ir, retval ); } }
|
||||
#define MINIRV32_HANDLE_MEM_STORE_CONTROL( addy, val ) if( HandleControlStore( addy, val ) ) return val;
|
||||
#define MINIRV32_HANDLE_MEM_LOAD_CONTROL( addy, rval ) rval = HandleControlLoad( addy );
|
||||
#define MINIRV32_OTHERCSR_WRITE( csrno, value ) HandleOtherCSRWrite( image, csrno, value );
|
||||
#define MINIRV32_OTHERCSR_READ( csrno, value ) value = HandleOtherCSRRead( image, csrno );
|
||||
|
||||
#include "mini-rv32ima.h"
|
||||
|
||||
uint8_t * ram_image = 0;
|
||||
struct MiniRV32IMAState * core;
|
||||
const char * kernel_command_line = 0;
|
||||
|
||||
static void DumpState( struct MiniRV32IMAState * core, uint8_t * ram_image );
|
||||
|
||||
int main( int argc, char ** argv )
|
||||
{
|
||||
int i;
|
||||
long long instct = -1;
|
||||
int show_help = 0;
|
||||
int time_divisor = 1;
|
||||
int fixed_update = 0;
|
||||
int do_sleep = 1;
|
||||
int single_step = 0;
|
||||
int dtb_ptr = 0;
|
||||
const char * image_file_name = 0;
|
||||
const char * dtb_file_name = 0;
|
||||
for( i = 1; i < argc; i++ )
|
||||
{
|
||||
const char * param = argv[i];
|
||||
int param_continue = 0; // Can combine parameters, like -lpt x
|
||||
do
|
||||
{
|
||||
if( param[0] == '-' || param_continue )
|
||||
{
|
||||
switch( param[1] )
|
||||
{
|
||||
case 'm': if( ++i < argc ) ram_amt = SimpleReadNumberInt( argv[i], ram_amt ); break;
|
||||
case 'c': if( ++i < argc ) instct = SimpleReadNumberInt( argv[i], -1 ); break;
|
||||
case 'k': if( ++i < argc ) kernel_command_line = argv[i]; break;
|
||||
case 'f': image_file_name = (++i<argc)?argv[i]:0; break;
|
||||
case 'b': dtb_file_name = (++i<argc)?argv[i]:0; break;
|
||||
case 'l': param_continue = 1; fixed_update = 1; break;
|
||||
case 'p': param_continue = 1; do_sleep = 0; break;
|
||||
case 's': param_continue = 1; single_step = 1; break;
|
||||
case 'd': param_continue = 1; fail_on_all_faults = 1; break;
|
||||
case 't': if( ++i < argc ) time_divisor = SimpleReadNumberInt( argv[i], 1 ); break;
|
||||
default:
|
||||
if( param_continue )
|
||||
param_continue = 0;
|
||||
else
|
||||
show_help = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
show_help = 1;
|
||||
break;
|
||||
}
|
||||
param++;
|
||||
} while( param_continue );
|
||||
}
|
||||
if( show_help || image_file_name == 0 || time_divisor <= 0 )
|
||||
{
|
||||
fprintf( stderr, "./mini-rv32imaf [parameters]\n\t-m [ram amount]\n\t-f [running image]\n\t-k [kernel command line]\n\t-b [dtb file, or 'disable']\n\t-c instruction count\n\t-s single step with full processor state\n\t-t time divion base\n\t-l lock time base to instruction count\n\t-p disable sleep when wfi\n\t-d fail out immediately on all faults\n" );
|
||||
return 1;
|
||||
}
|
||||
|
||||
ram_image = malloc( ram_amt );
|
||||
if( !ram_image )
|
||||
{
|
||||
fprintf( stderr, "Error: could not allocate system image.\n" );
|
||||
return -4;
|
||||
}
|
||||
|
||||
restart:
|
||||
{
|
||||
FILE * f = fopen( image_file_name, "rb" );
|
||||
if( !f || ferror( f ) )
|
||||
{
|
||||
fprintf( stderr, "Error: \"%s\" not found\n", image_file_name );
|
||||
return -5;
|
||||
}
|
||||
fseek( f, 0, SEEK_END );
|
||||
long flen = ftell( f );
|
||||
fseek( f, 0, SEEK_SET );
|
||||
if( flen > ram_amt )
|
||||
{
|
||||
fprintf( stderr, "Error: Could not fit RAM image (%ld bytes) into %d\n", flen, ram_amt );
|
||||
return -6;
|
||||
}
|
||||
|
||||
memset( ram_image, 0, ram_amt );
|
||||
if( fread( ram_image, flen, 1, f ) != 1)
|
||||
{
|
||||
fprintf( stderr, "Error: Could not load image.\n" );
|
||||
return -7;
|
||||
}
|
||||
fclose( f );
|
||||
|
||||
if( dtb_file_name )
|
||||
{
|
||||
if( strcmp( dtb_file_name, "disable" ) == 0 )
|
||||
{
|
||||
// No DTB reading.
|
||||
}
|
||||
else
|
||||
{
|
||||
f = fopen( dtb_file_name, "rb" );
|
||||
if( !f || ferror( f ) )
|
||||
{
|
||||
fprintf( stderr, "Error: \"%s\" not found\n", dtb_file_name );
|
||||
return -5;
|
||||
}
|
||||
fseek( f, 0, SEEK_END );
|
||||
long dtblen = ftell( f );
|
||||
fseek( f, 0, SEEK_SET );
|
||||
dtb_ptr = ram_amt - dtblen - sizeof( struct MiniRV32IMAState );
|
||||
if( fread( ram_image + dtb_ptr, dtblen, 1, f ) != 1 )
|
||||
{
|
||||
fprintf( stderr, "Error: Could not open dtb \"%s\"\n", dtb_file_name );
|
||||
return -9;
|
||||
}
|
||||
fclose( f );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load a default dtb.
|
||||
dtb_ptr = ram_amt - sizeof(default64mbdtb) - sizeof( struct MiniRV32IMAState );
|
||||
memcpy( ram_image + dtb_ptr, default64mbdtb, sizeof( default64mbdtb ) );
|
||||
if( kernel_command_line )
|
||||
{
|
||||
strncpy( (char*)( ram_image + dtb_ptr + 0xc0 ), kernel_command_line, 54 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CaptureKeyboardInput();
|
||||
|
||||
// The core lives at the end of RAM.
|
||||
core = (struct MiniRV32IMAState *)(ram_image + ram_amt - sizeof( struct MiniRV32IMAState ));
|
||||
core->pc = MINIRV32_RAM_IMAGE_OFFSET;
|
||||
core->regs[10] = 0x00; //hart ID
|
||||
core->regs[11] = dtb_ptr?(dtb_ptr+MINIRV32_RAM_IMAGE_OFFSET):0; //dtb_pa (Must be valid pointer) (Should be pointer to dtb)
|
||||
core->extraflags |= 3; // Machine-mode.
|
||||
|
||||
if( dtb_file_name == 0 )
|
||||
{
|
||||
// Update system ram size in DTB (but if and only if we're using the default DTB)
|
||||
// Warning - this will need to be updated if the skeleton DTB is ever modified.
|
||||
uint32_t * dtb = (uint32_t*)(ram_image + dtb_ptr);
|
||||
if( dtb[0x13c/4] == 0x00c0ff03 )
|
||||
{
|
||||
uint32_t validram = dtb_ptr;
|
||||
dtb[0x13c/4] = (validram>>24) | ((( validram >> 16 ) & 0xff) << 8 ) | (((validram>>8) & 0xff ) << 16 ) | ( ( validram & 0xff) << 24 );
|
||||
}
|
||||
}
|
||||
|
||||
// Image is loaded.
|
||||
uint64_t rt;
|
||||
uint64_t lastTime = (fixed_update)?0:(GetTimeMicroseconds()/time_divisor);
|
||||
int instrs_per_flip = single_step?1:1024;
|
||||
for( rt = 0; rt < instct+1 || instct < 0; rt += instrs_per_flip )
|
||||
{
|
||||
uint64_t * this_ccount = ((uint64_t*)&core->cyclel);
|
||||
uint32_t elapsedUs = 0;
|
||||
if( fixed_update )
|
||||
elapsedUs = *this_ccount / time_divisor - lastTime;
|
||||
else
|
||||
elapsedUs = GetTimeMicroseconds()/time_divisor - lastTime;
|
||||
lastTime += elapsedUs;
|
||||
|
||||
if( single_step )
|
||||
DumpState( core, ram_image);
|
||||
|
||||
int ret = MiniRV32IMAStep( core, ram_image, 0, elapsedUs, instrs_per_flip ); // Execute upto 1024 cycles before breaking out.
|
||||
switch( ret )
|
||||
{
|
||||
case 0: break;
|
||||
case 1: if( do_sleep ) MiniSleep(); *this_ccount += instrs_per_flip; break;
|
||||
case 3: instct = 0; break;
|
||||
case 0x7777: goto restart; //syscon code for restart
|
||||
case 0x5555: printf( "POWEROFF@0x%08x%08x\n", core->cycleh, core->cyclel ); return 0; //syscon code for power-off
|
||||
default: printf( "Unknown failure\n" ); break;
|
||||
}
|
||||
}
|
||||
|
||||
DumpState( core, ram_image);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Platform-specific functionality
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#if defined(WINDOWS) || defined(WIN32) || defined(_WIN32)
|
||||
|
||||
#include <windows.h>
|
||||
#include <conio.h>
|
||||
|
||||
#define strtoll _strtoi64
|
||||
|
||||
static void CaptureKeyboardInput()
|
||||
{
|
||||
system(""); // Poorly documented tick: Enable VT100 Windows mode.
|
||||
}
|
||||
|
||||
static void ResetKeyboardInput()
|
||||
{
|
||||
}
|
||||
|
||||
static void MiniSleep()
|
||||
{
|
||||
Sleep(1);
|
||||
}
|
||||
|
||||
static uint64_t GetTimeMicroseconds()
|
||||
{
|
||||
static LARGE_INTEGER lpf;
|
||||
LARGE_INTEGER li;
|
||||
|
||||
if( !lpf.QuadPart )
|
||||
QueryPerformanceFrequency( &lpf );
|
||||
|
||||
QueryPerformanceCounter( &li );
|
||||
return ((uint64_t)li.QuadPart * 1000000LL) / (uint64_t)lpf.QuadPart;
|
||||
}
|
||||
|
||||
|
||||
static int IsKBHit()
|
||||
{
|
||||
return _kbhit();
|
||||
}
|
||||
|
||||
static int ReadKBByte()
|
||||
{
|
||||
// This code is kind of tricky, but used to convert windows arrow keys
|
||||
// to VT100 arrow keys.
|
||||
static int is_escape_sequence = 0;
|
||||
int r;
|
||||
if( is_escape_sequence == 1 )
|
||||
{
|
||||
is_escape_sequence++;
|
||||
return '[';
|
||||
}
|
||||
|
||||
r = _getch();
|
||||
|
||||
if( is_escape_sequence )
|
||||
{
|
||||
is_escape_sequence = 0;
|
||||
switch( r )
|
||||
{
|
||||
case 'H': return 'A'; // Up
|
||||
case 'P': return 'B'; // Down
|
||||
case 'K': return 'D'; // Left
|
||||
case 'M': return 'C'; // Right
|
||||
case 'G': return 'H'; // Home
|
||||
case 'O': return 'F'; // End
|
||||
default: return r; // Unknown code.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch( r )
|
||||
{
|
||||
case 13: return 10; //cr->lf
|
||||
case 224: is_escape_sequence = 1; return 27; // Escape arrow keys
|
||||
default: return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
static void CtrlC()
|
||||
{
|
||||
DumpState( core, ram_image);
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
// Override keyboard, so we can capture all keyboard input for the VM.
|
||||
static void CaptureKeyboardInput()
|
||||
{
|
||||
// Hook exit, because we want to re-enable keyboard.
|
||||
atexit(ResetKeyboardInput);
|
||||
signal(SIGINT, CtrlC);
|
||||
|
||||
struct termios term;
|
||||
tcgetattr(0, &term);
|
||||
term.c_lflag &= ~(ICANON | ECHO); // Disable echo as well
|
||||
tcsetattr(0, TCSANOW, &term);
|
||||
}
|
||||
|
||||
static void ResetKeyboardInput()
|
||||
{
|
||||
// Re-enable echo, etc. on keyboard.
|
||||
struct termios term;
|
||||
tcgetattr(0, &term);
|
||||
term.c_lflag |= ICANON | ECHO;
|
||||
tcsetattr(0, TCSANOW, &term);
|
||||
}
|
||||
|
||||
static void MiniSleep()
|
||||
{
|
||||
usleep(500);
|
||||
}
|
||||
|
||||
static uint64_t GetTimeMicroseconds()
|
||||
{
|
||||
struct timeval tv;
|
||||
gettimeofday( &tv, 0 );
|
||||
return tv.tv_usec + ((uint64_t)(tv.tv_sec)) * 1000000LL;
|
||||
}
|
||||
|
||||
static int is_eofd;
|
||||
|
||||
static int ReadKBByte()
|
||||
{
|
||||
if( is_eofd ) return 0xffffffff;
|
||||
char rxchar = 0;
|
||||
int rread = read(fileno(stdin), (char*)&rxchar, 1);
|
||||
|
||||
if( rread > 0 ) // Tricky: getchar can't be used with arrow keys.
|
||||
return rxchar;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int IsKBHit()
|
||||
{
|
||||
if( is_eofd ) return -1;
|
||||
int byteswaiting;
|
||||
ioctl(0, FIONREAD, &byteswaiting);
|
||||
if( !byteswaiting && write( fileno(stdin), 0, 0 ) != 0 ) { is_eofd = 1; return -1; } // Is end-of-file for
|
||||
return !!byteswaiting;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Rest of functions functionality
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static uint32_t HandleException( uint32_t ir, uint32_t code )
|
||||
{
|
||||
// Weird opcode emitted by duktape on exit.
|
||||
if( code == 3 )
|
||||
{
|
||||
// Could handle other opcodes here.
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
static uint32_t HandleControlStore( uint32_t addy, uint32_t val )
|
||||
{
|
||||
if( addy == 0x10000000 ) //UART 8250 / 16550 Data Buffer
|
||||
{
|
||||
printf( "%c", val );
|
||||
fflush( stdout );
|
||||
}
|
||||
else if( addy == 0x11004004 ) //CLNT
|
||||
core->timermatchh = val;
|
||||
else if( addy == 0x11004000 ) //CLNT
|
||||
core->timermatchl = val;
|
||||
else if( addy == 0x11100000 ) //SYSCON (reboot, poweroff, etc.)
|
||||
{
|
||||
core->pc = core->pc + 4;
|
||||
return val; // NOTE: PC will be PC of Syscon.
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static uint32_t HandleControlLoad( uint32_t addy )
|
||||
{
|
||||
// Emulating a 8250 / 16550 UART
|
||||
if( addy == 0x10000005 )
|
||||
return 0x60 | IsKBHit();
|
||||
else if( addy == 0x10000000 && IsKBHit() )
|
||||
return ReadKBByte();
|
||||
else if( addy == 0x1100bffc ) // https://chromitem-soc.readthedocs.io/en/latest/clint.html
|
||||
return core->timerh;
|
||||
else if( addy == 0x1100bff8 )
|
||||
return core->timerl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void HandleOtherCSRWrite( uint8_t * image, uint16_t csrno, uint32_t value )
|
||||
{
|
||||
if( csrno == 0x136 )
|
||||
{
|
||||
printf( "%d", value ); fflush( stdout );
|
||||
}
|
||||
if( csrno == 0x137 )
|
||||
{
|
||||
printf( "%08x", value ); fflush( stdout );
|
||||
}
|
||||
else if( csrno == 0x138 )
|
||||
{
|
||||
//Print "string"
|
||||
uint32_t ptrstart = value - MINIRV32_RAM_IMAGE_OFFSET;
|
||||
uint32_t ptrend = ptrstart;
|
||||
if( ptrstart >= ram_amt )
|
||||
printf( "DEBUG PASSED INVALID PTR (%08x)\n", value );
|
||||
while( ptrend < ram_amt )
|
||||
{
|
||||
if( image[ptrend] == 0 ) break;
|
||||
ptrend++;
|
||||
}
|
||||
if( ptrend != ptrstart )
|
||||
fwrite( image + ptrstart, ptrend - ptrstart, 1, stdout );
|
||||
}
|
||||
else if( csrno == 0x139 )
|
||||
{
|
||||
putchar( value ); fflush( stdout );
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t HandleOtherCSRRead( uint8_t * image, uint16_t csrno )
|
||||
{
|
||||
if( csrno == 0x140 )
|
||||
{
|
||||
if( !IsKBHit() ) return -1;
|
||||
return ReadKBByte();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int64_t SimpleReadNumberInt( const char * number, int64_t defaultNumber )
|
||||
{
|
||||
if( !number || !number[0] ) return defaultNumber;
|
||||
int radix = 10;
|
||||
if( number[0] == '0' )
|
||||
{
|
||||
char nc = number[1];
|
||||
number+=2;
|
||||
if( nc == 0 ) return 0;
|
||||
else if( nc == 'x' ) radix = 16;
|
||||
else if( nc == 'b' ) radix = 2;
|
||||
else { number--; radix = 8; }
|
||||
}
|
||||
char * endptr;
|
||||
uint64_t ret = strtoll( number, &endptr, radix );
|
||||
if( endptr == number )
|
||||
{
|
||||
return defaultNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
static void DumpState( struct MiniRV32IMAState * core, uint8_t * ram_image )
|
||||
{
|
||||
uint32_t pc = core->pc;
|
||||
uint32_t pc_offset = pc - MINIRV32_RAM_IMAGE_OFFSET;
|
||||
uint32_t ir = 0;
|
||||
|
||||
printf( "PC: %08x ", pc );
|
||||
if( pc_offset >= 0 && pc_offset < ram_amt - 3 )
|
||||
{
|
||||
ir = *((uint32_t*)(&((uint8_t*)ram_image)[pc_offset]));
|
||||
printf( "[0x%08x] ", ir );
|
||||
}
|
||||
else
|
||||
printf( "[xxxxxxxxxx] " );
|
||||
uint32_t * regs = core->regs;
|
||||
printf( "Z:%08x ra:%08x sp:%08x gp:%08x tp:%08x t0:%08x t1:%08x t2:%08x s0:%08x s1:%08x a0:%08x a1:%08x a2:%08x a3:%08x a4:%08x a5:%08x ",
|
||||
regs[0], regs[1], regs[2], regs[3], regs[4], regs[5], regs[6], regs[7],
|
||||
regs[8], regs[9], regs[10], regs[11], regs[12], regs[13], regs[14], regs[15] );
|
||||
printf( "a6:%08x a7:%08x s2:%08x s3:%08x s4:%08x s5:%08x s6:%08x s7:%08x s8:%08x s9:%08x s10:%08x s11:%08x t3:%08x t4:%08x t5:%08x t6:%08x\n",
|
||||
regs[16], regs[17], regs[18], regs[19], regs[20], regs[21], regs[22], regs[23],
|
||||
regs[24], regs[25], regs[26], regs[27], regs[28], regs[29], regs[30], regs[31] );
|
||||
}
|
||||
|
547
ccompiler/backend/riscv32/rv_vm/mini-rv32ima.h
Normal file
547
ccompiler/backend/riscv32/rv_vm/mini-rv32ima.h
Normal file
@ -0,0 +1,547 @@
|
||||
// Copyright 2022 Charles Lohr, you may use this file or any portions herein under any of the BSD, MIT, or CC0 licenses.
|
||||
|
||||
#ifndef _MINI_RV32IMAH_H
|
||||
#define _MINI_RV32IMAH_H
|
||||
|
||||
/**
|
||||
To use mini-rv32ima.h for the bare minimum, the following:
|
||||
|
||||
#define MINI_RV32_RAM_SIZE ram_amt
|
||||
#define MINIRV32_IMPLEMENTATION
|
||||
|
||||
#include "mini-rv32ima.h"
|
||||
|
||||
Though, that's not _that_ interesting. You probably want I/O!
|
||||
|
||||
|
||||
Notes:
|
||||
* There is a dedicated CLNT at 0x10000000.
|
||||
* There is free MMIO from there to 0x12000000.
|
||||
* You can put things like a UART, or whatever there.
|
||||
* Feel free to override any of the functionality with macros.
|
||||
*/
|
||||
|
||||
#ifndef MINIRV32WARN
|
||||
#define MINIRV32WARN( x... );
|
||||
#endif
|
||||
|
||||
#ifndef MINIRV32_DECORATE
|
||||
#define MINIRV32_DECORATE static
|
||||
#endif
|
||||
|
||||
#ifndef MINIRV32_RAM_IMAGE_OFFSET
|
||||
#define MINIRV32_RAM_IMAGE_OFFSET 0x80000000
|
||||
#endif
|
||||
|
||||
#ifndef MINIRV32_MMIO_RANGE
|
||||
#define MINIRV32_MMIO_RANGE(n) (0x10000000 <= (n) && (n) < 0x12000000)
|
||||
#endif
|
||||
|
||||
#ifndef MINIRV32_POSTEXEC
|
||||
#define MINIRV32_POSTEXEC(...);
|
||||
#endif
|
||||
|
||||
#ifndef MINIRV32_HANDLE_MEM_STORE_CONTROL
|
||||
#define MINIRV32_HANDLE_MEM_STORE_CONTROL(...);
|
||||
#endif
|
||||
|
||||
#ifndef MINIRV32_HANDLE_MEM_LOAD_CONTROL
|
||||
#define MINIRV32_HANDLE_MEM_LOAD_CONTROL(...);
|
||||
#endif
|
||||
|
||||
#ifndef MINIRV32_OTHERCSR_WRITE
|
||||
#define MINIRV32_OTHERCSR_WRITE(...);
|
||||
#endif
|
||||
|
||||
#ifndef MINIRV32_OTHERCSR_READ
|
||||
#define MINIRV32_OTHERCSR_READ(...);
|
||||
#endif
|
||||
|
||||
#ifndef MINIRV32_CUSTOM_MEMORY_BUS
|
||||
#define MINIRV32_STORE4( ofs, val ) *(uint32_t*)(image + ofs) = val
|
||||
#define MINIRV32_STORE2( ofs, val ) *(uint16_t*)(image + ofs) = val
|
||||
#define MINIRV32_STORE1( ofs, val ) *(uint8_t*)(image + ofs) = val
|
||||
#define MINIRV32_LOAD4( ofs ) *(uint32_t*)(image + ofs)
|
||||
#define MINIRV32_LOAD2( ofs ) *(uint16_t*)(image + ofs)
|
||||
#define MINIRV32_LOAD1( ofs ) *(uint8_t*)(image + ofs)
|
||||
#define MINIRV32_LOAD2_SIGNED( ofs ) *(int16_t*)(image + ofs)
|
||||
#define MINIRV32_LOAD1_SIGNED( ofs ) *(int8_t*)(image + ofs)
|
||||
#endif
|
||||
|
||||
// As a note: We quouple-ify these, because in HLSL, we will be operating with
|
||||
// uint4's. We are going to uint4 data to/from system RAM.
|
||||
//
|
||||
// We're going to try to keep the full processor state to 12 x uint4.
|
||||
struct MiniRV32IMAState
|
||||
{
|
||||
uint32_t regs[32];
|
||||
|
||||
uint32_t pc;
|
||||
uint32_t mstatus;
|
||||
uint32_t cyclel;
|
||||
uint32_t cycleh;
|
||||
|
||||
uint32_t timerl;
|
||||
uint32_t timerh;
|
||||
uint32_t timermatchl;
|
||||
uint32_t timermatchh;
|
||||
|
||||
uint32_t mscratch;
|
||||
uint32_t mtvec;
|
||||
uint32_t mie;
|
||||
uint32_t mip;
|
||||
|
||||
uint32_t mepc;
|
||||
uint32_t mtval;
|
||||
uint32_t mcause;
|
||||
|
||||
// Note: only a few bits are used. (Machine = 3, User = 0)
|
||||
// Bits 0..1 = privilege.
|
||||
// Bit 2 = WFI (Wait for interrupt)
|
||||
// Bit 3+ = Load/Store reservation LSBs.
|
||||
uint32_t extraflags;
|
||||
};
|
||||
|
||||
#ifndef MINIRV32_STEPPROTO
|
||||
MINIRV32_DECORATE int32_t MiniRV32IMAStep( struct MiniRV32IMAState * state, uint8_t * image, uint32_t vProcAddress, uint32_t elapsedUs, int count );
|
||||
#endif
|
||||
|
||||
#ifdef MINIRV32_IMPLEMENTATION
|
||||
|
||||
#ifndef MINIRV32_CUSTOM_INTERNALS
|
||||
#define CSR( x ) state->x
|
||||
#define SETCSR( x, val ) { state->x = val; }
|
||||
#define REG( x ) state->regs[x]
|
||||
#define REGSET( x, val ) { state->regs[x] = val; }
|
||||
#endif
|
||||
|
||||
#ifndef MINIRV32_STEPPROTO
|
||||
MINIRV32_DECORATE int32_t MiniRV32IMAStep( struct MiniRV32IMAState * state, uint8_t * image, uint32_t vProcAddress, uint32_t elapsedUs, int count )
|
||||
#else
|
||||
MINIRV32_STEPPROTO
|
||||
#endif
|
||||
{
|
||||
uint32_t new_timer = CSR( timerl ) + elapsedUs;
|
||||
if( new_timer < CSR( timerl ) ) CSR( timerh )++;
|
||||
CSR( timerl ) = new_timer;
|
||||
|
||||
// Handle Timer interrupt.
|
||||
if( ( CSR( timerh ) > CSR( timermatchh ) || ( CSR( timerh ) == CSR( timermatchh ) && CSR( timerl ) > CSR( timermatchl ) ) ) && ( CSR( timermatchh ) || CSR( timermatchl ) ) )
|
||||
{
|
||||
CSR( extraflags ) &= ~4; // Clear WFI
|
||||
CSR( mip ) |= 1<<7; //MTIP of MIP // https://stackoverflow.com/a/61916199/2926815 Fire interrupt.
|
||||
}
|
||||
else
|
||||
CSR( mip ) &= ~(1<<7);
|
||||
|
||||
// If WFI, don't run processor.
|
||||
if( CSR( extraflags ) & 4 )
|
||||
return 1;
|
||||
|
||||
uint32_t trap = 0;
|
||||
uint32_t rval = 0;
|
||||
uint32_t pc = CSR( pc );
|
||||
uint32_t cycle = CSR( cyclel );
|
||||
|
||||
if( ( CSR( mip ) & (1<<7) ) && ( CSR( mie ) & (1<<7) /*mtie*/ ) && ( CSR( mstatus ) & 0x8 /*mie*/) )
|
||||
{
|
||||
// Timer interrupt.
|
||||
trap = 0x80000007;
|
||||
pc -= 4;
|
||||
}
|
||||
else // No timer interrupt? Execute a bunch of instructions.
|
||||
for( int icount = 0; icount < count; icount++ )
|
||||
{
|
||||
uint32_t ir = 0;
|
||||
rval = 0;
|
||||
cycle++;
|
||||
uint32_t ofs_pc = pc - MINIRV32_RAM_IMAGE_OFFSET;
|
||||
|
||||
if( ofs_pc >= MINI_RV32_RAM_SIZE )
|
||||
{
|
||||
trap = 1 + 1; // Handle access violation on instruction read.
|
||||
break;
|
||||
}
|
||||
else if( ofs_pc & 3 )
|
||||
{
|
||||
trap = 1 + 0; //Handle PC-misaligned access
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
ir = MINIRV32_LOAD4( ofs_pc );
|
||||
uint32_t rdid = (ir >> 7) & 0x1f;
|
||||
|
||||
switch( ir & 0x7f )
|
||||
{
|
||||
case 0x37: // LUI (0b0110111)
|
||||
rval = ( ir & 0xfffff000 );
|
||||
break;
|
||||
case 0x17: // AUIPC (0b0010111)
|
||||
rval = pc + ( ir & 0xfffff000 );
|
||||
break;
|
||||
case 0x6F: // JAL (0b1101111)
|
||||
{
|
||||
int32_t reladdy = ((ir & 0x80000000)>>11) | ((ir & 0x7fe00000)>>20) | ((ir & 0x00100000)>>9) | ((ir&0x000ff000));
|
||||
if( reladdy & 0x00100000 ) reladdy |= 0xffe00000; // Sign extension.
|
||||
rval = pc + 4;
|
||||
pc = pc + reladdy - 4;
|
||||
break;
|
||||
}
|
||||
case 0x67: // JALR (0b1100111)
|
||||
{
|
||||
uint32_t imm = ir >> 20;
|
||||
int32_t imm_se = imm | (( imm & 0x800 )?0xfffff000:0);
|
||||
rval = pc + 4;
|
||||
pc = ( (REG( (ir >> 15) & 0x1f ) + imm_se) & ~1) - 4;
|
||||
break;
|
||||
}
|
||||
case 0x63: // Branch (0b1100011)
|
||||
{
|
||||
uint32_t immm4 = ((ir & 0xf00)>>7) | ((ir & 0x7e000000)>>20) | ((ir & 0x80) << 4) | ((ir >> 31)<<12);
|
||||
if( immm4 & 0x1000 ) immm4 |= 0xffffe000;
|
||||
int32_t rs1 = REG((ir >> 15) & 0x1f);
|
||||
int32_t rs2 = REG((ir >> 20) & 0x1f);
|
||||
immm4 = pc + immm4 - 4;
|
||||
rdid = 0;
|
||||
switch( ( ir >> 12 ) & 0x7 )
|
||||
{
|
||||
// BEQ, BNE, BLT, BGE, BLTU, BGEU
|
||||
case 0: if( rs1 == rs2 ) pc = immm4; break;
|
||||
case 1: if( rs1 != rs2 ) pc = immm4; break;
|
||||
case 4: if( rs1 < rs2 ) pc = immm4; break;
|
||||
case 5: if( rs1 >= rs2 ) pc = immm4; break; //BGE
|
||||
case 6: if( (uint32_t)rs1 < (uint32_t)rs2 ) pc = immm4; break; //BLTU
|
||||
case 7: if( (uint32_t)rs1 >= (uint32_t)rs2 ) pc = immm4; break; //BGEU
|
||||
default: trap = (2+1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x03: // Load (0b0000011)
|
||||
{
|
||||
uint32_t rs1 = REG((ir >> 15) & 0x1f);
|
||||
uint32_t imm = ir >> 20;
|
||||
int32_t imm_se = imm | (( imm & 0x800 )?0xfffff000:0);
|
||||
uint32_t rsval = rs1 + imm_se;
|
||||
|
||||
rsval -= MINIRV32_RAM_IMAGE_OFFSET;
|
||||
if( rsval >= MINI_RV32_RAM_SIZE-3 )
|
||||
{
|
||||
rsval += MINIRV32_RAM_IMAGE_OFFSET;
|
||||
if( MINIRV32_MMIO_RANGE( rsval ) ) // UART, CLNT
|
||||
{
|
||||
MINIRV32_HANDLE_MEM_LOAD_CONTROL( rsval, rval );
|
||||
}
|
||||
else
|
||||
{
|
||||
trap = (5+1);
|
||||
rval = rsval;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch( ( ir >> 12 ) & 0x7 )
|
||||
{
|
||||
//LB, LH, LW, LBU, LHU
|
||||
case 0: rval = MINIRV32_LOAD1_SIGNED( rsval ); break;
|
||||
case 1: rval = MINIRV32_LOAD2_SIGNED( rsval ); break;
|
||||
case 2: rval = MINIRV32_LOAD4( rsval ); break;
|
||||
case 4: rval = MINIRV32_LOAD1( rsval ); break;
|
||||
case 5: rval = MINIRV32_LOAD2( rsval ); break;
|
||||
default: trap = (2+1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x23: // Store 0b0100011
|
||||
{
|
||||
uint32_t rs1 = REG((ir >> 15) & 0x1f);
|
||||
uint32_t rs2 = REG((ir >> 20) & 0x1f);
|
||||
uint32_t addy = ( ( ir >> 7 ) & 0x1f ) | ( ( ir & 0xfe000000 ) >> 20 );
|
||||
if( addy & 0x800 ) addy |= 0xfffff000;
|
||||
addy += rs1 - MINIRV32_RAM_IMAGE_OFFSET;
|
||||
rdid = 0;
|
||||
|
||||
if( addy >= MINI_RV32_RAM_SIZE-3 )
|
||||
{
|
||||
addy += MINIRV32_RAM_IMAGE_OFFSET;
|
||||
if( MINIRV32_MMIO_RANGE( addy ) )
|
||||
{
|
||||
MINIRV32_HANDLE_MEM_STORE_CONTROL( addy, rs2 );
|
||||
}
|
||||
else
|
||||
{
|
||||
trap = (7+1); // Store access fault.
|
||||
rval = addy;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch( ( ir >> 12 ) & 0x7 )
|
||||
{
|
||||
//SB, SH, SW
|
||||
case 0: MINIRV32_STORE1( addy, rs2 ); break;
|
||||
case 1: MINIRV32_STORE2( addy, rs2 ); break;
|
||||
case 2: MINIRV32_STORE4( addy, rs2 ); break;
|
||||
default: trap = (2+1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x13: // Op-immediate 0b0010011
|
||||
case 0x33: // Op 0b0110011
|
||||
{
|
||||
uint32_t imm = ir >> 20;
|
||||
imm = imm | (( imm & 0x800 )?0xfffff000:0);
|
||||
uint32_t rs1 = REG((ir >> 15) & 0x1f);
|
||||
uint32_t is_reg = !!( ir & 0x20 );
|
||||
uint32_t rs2 = is_reg ? REG(imm & 0x1f) : imm;
|
||||
|
||||
if( is_reg && ( ir & 0x02000000 ) )
|
||||
{
|
||||
switch( (ir>>12)&7 ) //0x02000000 = RV32M
|
||||
{
|
||||
case 0: rval = rs1 * rs2; break; // MUL
|
||||
#ifndef CUSTOM_MULH // If compiling on a system that doesn't natively, or via libgcc support 64-bit math.
|
||||
case 1: rval = ((int64_t)((int32_t)rs1) * (int64_t)((int32_t)rs2)) >> 32; break; // MULH
|
||||
case 2: rval = ((int64_t)((int32_t)rs1) * (uint64_t)rs2) >> 32; break; // MULHSU
|
||||
case 3: rval = ((uint64_t)rs1 * (uint64_t)rs2) >> 32; break; // MULHU
|
||||
#else
|
||||
CUSTOM_MULH
|
||||
#endif
|
||||
case 4: if( rs2 == 0 ) rval = -1; else rval = ((int32_t)rs1 == INT32_MIN && (int32_t)rs2 == -1) ? rs1 : ((int32_t)rs1 / (int32_t)rs2); break; // DIV
|
||||
case 5: if( rs2 == 0 ) rval = 0xffffffff; else rval = rs1 / rs2; break; // DIVU
|
||||
case 6: if( rs2 == 0 ) rval = rs1; else rval = ((int32_t)rs1 == INT32_MIN && (int32_t)rs2 == -1) ? 0 : ((uint32_t)((int32_t)rs1 % (int32_t)rs2)); break; // REM
|
||||
case 7: if( rs2 == 0 ) rval = rs1; else rval = rs1 % rs2; break; // REMU
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch( (ir>>12)&7 ) // These could be either op-immediate or op commands. Be careful.
|
||||
{
|
||||
case 0: rval = (is_reg && (ir & 0x40000000) ) ? ( rs1 - rs2 ) : ( rs1 + rs2 ); break;
|
||||
case 1: rval = rs1 << (rs2 & 0x1F); break;
|
||||
case 2: rval = (int32_t)rs1 < (int32_t)rs2; break;
|
||||
case 3: rval = rs1 < rs2; break;
|
||||
case 4: rval = rs1 ^ rs2; break;
|
||||
case 5: rval = (ir & 0x40000000 ) ? ( ((int32_t)rs1) >> (rs2 & 0x1F) ) : ( rs1 >> (rs2 & 0x1F) ); break;
|
||||
case 6: rval = rs1 | rs2; break;
|
||||
case 7: rval = rs1 & rs2; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x0f: // 0b0001111
|
||||
rdid = 0; // fencetype = (ir >> 12) & 0b111; We ignore fences in this impl.
|
||||
break;
|
||||
case 0x73: // Zifencei+Zicsr (0b1110011)
|
||||
{
|
||||
uint32_t csrno = ir >> 20;
|
||||
uint32_t microop = ( ir >> 12 ) & 0x7;
|
||||
if( (microop & 3) ) // It's a Zicsr function.
|
||||
{
|
||||
int rs1imm = (ir >> 15) & 0x1f;
|
||||
uint32_t rs1 = REG(rs1imm);
|
||||
uint32_t writeval = rs1;
|
||||
|
||||
// https://raw.githubusercontent.com/riscv/virtual-memory/main/specs/663-Svpbmt.pdf
|
||||
// Generally, support for Zicsr
|
||||
switch( csrno )
|
||||
{
|
||||
case 0x340: rval = CSR( mscratch ); break;
|
||||
case 0x305: rval = CSR( mtvec ); break;
|
||||
case 0x304: rval = CSR( mie ); break;
|
||||
case 0xC00: rval = cycle; break;
|
||||
case 0x344: rval = CSR( mip ); break;
|
||||
case 0x341: rval = CSR( mepc ); break;
|
||||
case 0x300: rval = CSR( mstatus ); break; //mstatus
|
||||
case 0x342: rval = CSR( mcause ); break;
|
||||
case 0x343: rval = CSR( mtval ); break;
|
||||
case 0xf11: rval = 0xff0ff0ff; break; //mvendorid
|
||||
case 0x301: rval = 0x40401101; break; //misa (XLEN=32, IMA+X)
|
||||
//case 0x3B0: rval = 0; break; //pmpaddr0
|
||||
//case 0x3a0: rval = 0; break; //pmpcfg0
|
||||
//case 0xf12: rval = 0x00000000; break; //marchid
|
||||
//case 0xf13: rval = 0x00000000; break; //mimpid
|
||||
//case 0xf14: rval = 0x00000000; break; //mhartid
|
||||
default:
|
||||
MINIRV32_OTHERCSR_READ( csrno, rval );
|
||||
break;
|
||||
}
|
||||
|
||||
switch( microop )
|
||||
{
|
||||
case 1: writeval = rs1; break; //CSRRW
|
||||
case 2: writeval = rval | rs1; break; //CSRRS
|
||||
case 3: writeval = rval & ~rs1; break; //CSRRC
|
||||
case 5: writeval = rs1imm; break; //CSRRWI
|
||||
case 6: writeval = rval | rs1imm; break; //CSRRSI
|
||||
case 7: writeval = rval & ~rs1imm; break; //CSRRCI
|
||||
}
|
||||
|
||||
switch( csrno )
|
||||
{
|
||||
case 0x340: SETCSR( mscratch, writeval ); break;
|
||||
case 0x305: SETCSR( mtvec, writeval ); break;
|
||||
case 0x304: SETCSR( mie, writeval ); break;
|
||||
case 0x344: SETCSR( mip, writeval ); break;
|
||||
case 0x341: SETCSR( mepc, writeval ); break;
|
||||
case 0x300: SETCSR( mstatus, writeval ); break; //mstatus
|
||||
case 0x342: SETCSR( mcause, writeval ); break;
|
||||
case 0x343: SETCSR( mtval, writeval ); break;
|
||||
//case 0x3a0: break; //pmpcfg0
|
||||
//case 0x3B0: break; //pmpaddr0
|
||||
//case 0xf11: break; //mvendorid
|
||||
//case 0xf12: break; //marchid
|
||||
//case 0xf13: break; //mimpid
|
||||
//case 0xf14: break; //mhartid
|
||||
//case 0x301: break; //misa
|
||||
default:
|
||||
MINIRV32_OTHERCSR_WRITE( csrno, writeval );
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if( microop == 0x0 ) // "SYSTEM" 0b000
|
||||
{
|
||||
rdid = 0;
|
||||
if( ( ( csrno & 0xff ) == 0x02 ) ) // MRET
|
||||
{
|
||||
//https://raw.githubusercontent.com/riscv/virtual-memory/main/specs/663-Svpbmt.pdf
|
||||
//Table 7.6. MRET then in mstatus/mstatush sets MPV=0, MPP=0, MIE=MPIE, and MPIE=1. La
|
||||
// Should also update mstatus to reflect correct mode.
|
||||
uint32_t startmstatus = CSR( mstatus );
|
||||
uint32_t startextraflags = CSR( extraflags );
|
||||
SETCSR( mstatus , (( startmstatus & 0x80) >> 4) | ((startextraflags&3) << 11) | 0x80 );
|
||||
SETCSR( extraflags, (startextraflags & ~3) | ((startmstatus >> 11) & 3) );
|
||||
pc = CSR( mepc ) -4;
|
||||
} else {
|
||||
switch (csrno) {
|
||||
case 0:
|
||||
#ifndef ECALL_HANDLER
|
||||
trap = ( CSR( extraflags ) & 3) ? (11+1) : (8+1); // ECALL; 8 = "Environment call from U-mode"; 11 = "Environment call from M-mode"
|
||||
#else
|
||||
ECALL_HANDLER(state);
|
||||
trap = 0;
|
||||
#endif
|
||||
break;
|
||||
case 1:
|
||||
trap = (3+1); break; // EBREAK 3 = "Breakpoint"
|
||||
case 0x105: //WFI (Wait for interrupts)
|
||||
CSR( mstatus ) |= 8; //Enable interrupts
|
||||
CSR( extraflags ) |= 4; //Infor environment we want to go to sleep.
|
||||
SETCSR( pc, pc + 4 );
|
||||
return 1;
|
||||
default:
|
||||
trap = (2+1); break; // Illegal opcode.
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
trap = (2+1); // Note micrrop 0b100 == undefined.
|
||||
break;
|
||||
}
|
||||
case 0x2f: // RV32A (0b00101111)
|
||||
{
|
||||
uint32_t rs1 = REG((ir >> 15) & 0x1f);
|
||||
uint32_t rs2 = REG((ir >> 20) & 0x1f);
|
||||
uint32_t irmid = ( ir>>27 ) & 0x1f;
|
||||
|
||||
rs1 -= MINIRV32_RAM_IMAGE_OFFSET;
|
||||
|
||||
// We don't implement load/store from UART or CLNT with RV32A here.
|
||||
|
||||
if( rs1 >= MINI_RV32_RAM_SIZE-3 )
|
||||
{
|
||||
trap = (7+1); //Store/AMO access fault
|
||||
rval = rs1 + MINIRV32_RAM_IMAGE_OFFSET;
|
||||
}
|
||||
else
|
||||
{
|
||||
rval = MINIRV32_LOAD4( rs1 );
|
||||
|
||||
// Referenced a little bit of https://github.com/franzflasch/riscv_em/blob/master/src/core/core.c
|
||||
uint32_t dowrite = 1;
|
||||
switch( irmid )
|
||||
{
|
||||
case 2: //LR.W (0b00010)
|
||||
dowrite = 0;
|
||||
CSR( extraflags ) = (CSR( extraflags ) & 0x07) | (rs1<<3);
|
||||
break;
|
||||
case 3: //SC.W (0b00011) (Make sure we have a slot, and, it's valid)
|
||||
rval = ( CSR( extraflags ) >> 3 != ( rs1 & 0x1fffffff ) ); // Validate that our reservation slot is OK.
|
||||
dowrite = !rval; // Only write if slot is valid.
|
||||
break;
|
||||
case 1: break; //AMOSWAP.W (0b00001)
|
||||
case 0: rs2 += rval; break; //AMOADD.W (0b00000)
|
||||
case 4: rs2 ^= rval; break; //AMOXOR.W (0b00100)
|
||||
case 12: rs2 &= rval; break; //AMOAND.W (0b01100)
|
||||
case 8: rs2 |= rval; break; //AMOOR.W (0b01000)
|
||||
case 16: rs2 = ((int32_t)rs2<(int32_t)rval)?rs2:rval; break; //AMOMIN.W (0b10000)
|
||||
case 20: rs2 = ((int32_t)rs2>(int32_t)rval)?rs2:rval; break; //AMOMAX.W (0b10100)
|
||||
case 24: rs2 = (rs2<rval)?rs2:rval; break; //AMOMINU.W (0b11000)
|
||||
case 28: rs2 = (rs2>rval)?rs2:rval; break; //AMOMAXU.W (0b11100)
|
||||
default: trap = (2+1); dowrite = 0; break; //Not supported.
|
||||
}
|
||||
if( dowrite ) MINIRV32_STORE4( rs1, rs2 );
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: trap = (2+1); // Fault: Invalid opcode.
|
||||
}
|
||||
|
||||
// If there was a trap, do NOT allow register writeback.
|
||||
if( trap ) {
|
||||
SETCSR( pc, pc );
|
||||
MINIRV32_POSTEXEC( pc, ir, trap );
|
||||
break;
|
||||
}
|
||||
|
||||
if( rdid )
|
||||
{
|
||||
REGSET( rdid, rval ); // Write back register.
|
||||
}
|
||||
}
|
||||
|
||||
MINIRV32_POSTEXEC( pc, ir, trap );
|
||||
|
||||
pc += 4;
|
||||
}
|
||||
|
||||
// Handle traps and interrupts.
|
||||
if( trap )
|
||||
{
|
||||
if( trap & 0x80000000 ) // If prefixed with 1 in MSB, it's an interrupt, not a trap.
|
||||
{
|
||||
SETCSR( mcause, trap );
|
||||
SETCSR( mtval, 0 );
|
||||
pc += 4; // PC needs to point to where the PC will return to.
|
||||
}
|
||||
else
|
||||
{
|
||||
SETCSR( mcause, trap - 1 );
|
||||
SETCSR( mtval, (trap > 5 && trap <= 8)? rval : pc );
|
||||
}
|
||||
SETCSR( mepc, pc ); //TRICKY: The kernel advances mepc automatically.
|
||||
//CSR( mstatus ) & 8 = MIE, & 0x80 = MPIE
|
||||
// On an interrupt, the system moves current MIE into MPIE
|
||||
SETCSR( mstatus, (( CSR( mstatus ) & 0x08) << 4) | (( CSR( extraflags ) & 3 ) << 11) );
|
||||
pc = (CSR( mtvec ) - 4);
|
||||
|
||||
// If trapping, always enter machine mode.
|
||||
CSR( extraflags ) |= 3;
|
||||
|
||||
trap = 0;
|
||||
pc += 4;
|
||||
}
|
||||
|
||||
if( CSR( cyclel ) > cycle ) CSR( cycleh )++;
|
||||
SETCSR( cyclel, cycle );
|
||||
SETCSR( pc, pc );
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
192
ccompiler/backend/riscv32/rv_vm/mini-rv32ima.md
Normal file
192
ccompiler/backend/riscv32/rv_vm/mini-rv32ima.md
Normal file
@ -0,0 +1,192 @@
|
||||
# riscv_emufun (mini-rv32ima)
|
||||
|
||||
Click below for the YouTube video introducing this project:
|
||||
|
||||
[](https://www.youtube.com/watch?v=YT5vB3UqU_E) [](https://www.youtube.com/watch?v=uZMNK17VCMU)
|
||||
|
||||
## What
|
||||
|
||||
mini-rv32ima is a single-file-header, [mini-rv32ima.h](https://github.com/cnlohr/riscv_emufun/blob/master/mini-rv32ima/mini-rv32ima.h), in the [STB Style library](https://github.com/nothings/stb) that:
|
||||
* Implements a RISC-V **rv32ima/Zifencei†+Zicsr** (and partial su), with CLINT and MMIO.
|
||||
* Is about **400 lines** of actual code.
|
||||
* Has **no dependencies**, not even libc.
|
||||
* Is **easily extensible**. So you can easily add CSRs, instructions, MMIO, etc!
|
||||
* Is pretty **performant**. (~450 coremark on my laptop, about 1/2 the speed of QEMU)
|
||||
* Is human-readable and in **basic C** code.
|
||||
* Is "**incomplete**" in that it didn't implement the tons of the spec that Linux doesn't (and you shouldn't) use.
|
||||
* Is trivially **embeddable** in applications.
|
||||
|
||||
It has a [demo wrapper](https://github.com/cnlohr/riscv_emufun/blob/master/mini-rv32ima/mini-rv32ima.c) that:
|
||||
* Implements a CLI, SYSCON, UART, DTB and Kernel image loading.
|
||||
* And it only around **250 lines** of code, itself.
|
||||
* Compiles down to a **~18kB executable** and only relies on libc.
|
||||
|
||||
†: Zifence+RV32A are stubbed. So, tweaks will need to be made if you want to emulate a multiprocessor system with this emulator.
|
||||
|
||||
Just see the `mini-rv32ima` folder.
|
||||
|
||||
It's "fully functional" now in that I can run Linux, apps, etc. Compile flat binaries and drop them in an image.
|
||||
|
||||
## Why
|
||||
|
||||
I'm working on a really really simple C Risc-V emulator. So simple it doesn't even have an MMU (Memory Management Unit). I have a few goals, they include:
|
||||
* Furthering RV32-NOMMU work to improve Linux support for RV32-NOMMU. (Imagine if we could run Linux on the $1 ESP32-C3)
|
||||
* Learning more about RV32 and writing emulators.
|
||||
* Being further inspired by @pimaker's amazing work on [Running Linux in a Pixel Shader](https://blog.pimaker.at/texts/rvc1/) and having the sneaking suspicion performance could be even better!
|
||||
* Hoping to port it to some weird places.
|
||||
* Understand the *most simplistic* system you can run Linux on and trying to push that boundary.
|
||||
* Continue to include my [education of people about assembly language](https://www.youtube.com/watch?v=Gelf0AyVGy4).
|
||||
|
||||
## How
|
||||
|
||||
Windows instructions (Just playing with the image)
|
||||
* Clone this repo.
|
||||
* Install or have TinyCC. [Powershell Installer](https://github.com/cntools/Install-TCC) or [Regular Windows Installer](https://github.com/cnlohr/tinycc-win64-installer/releases/tag/v0_0.9.27)
|
||||
* Run `winrun.ps` in the `windows` folder.
|
||||
|
||||
WSL (For full toolchain and image build:
|
||||
* You will need to remove all spaces from your path i.e. `export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/mnt/c/Windows/system32:/snap/bin` and continue the instructions. P.S. What in the world was Windows thinking, putting a space between "Program" and "Files"??!?
|
||||
|
||||
Linux instructions (both):
|
||||
* Clone this repo.
|
||||
* Install `git build-essential` and/or whatever other requirements are in place for [buildroot](https://buildroot.org/).
|
||||
* `make testdlimage`
|
||||
* It automatically downloads the image (~1MB) and runs the emulator.
|
||||
* Should be up and running in about 2.5s depending on internet speed.
|
||||
|
||||
You can do in-depth work on Linux by:
|
||||
* `make everything`
|
||||
|
||||
If you want to play with the bare metal system, see below, or if you have the toolchain installed, just:
|
||||
* `make testbare`
|
||||
|
||||
If you just want to play emdoom, and use the prebuilt image:
|
||||
* On Windows, run `windows\winrundoom.ps1`
|
||||
* On Linux, `cd mini-rv32ima`, and type `make testdoom`
|
||||
|
||||
## Questions?
|
||||
* Why not rv64?
|
||||
* Because then I can't run it as easily in a pixel shader if I ever hope to.
|
||||
* Can I add an MMU?
|
||||
* Yes. It actually probably wouldn't be too difficult.
|
||||
* Should I add an MMU?
|
||||
* No. It is important to further support for nommu systems to empower minimal Risc-V designs!
|
||||
|
||||
Everything else: Contact us on my Discord: https://discord.com/invite/CCeyWyZ
|
||||
|
||||
## How do I use this in my own project?
|
||||
|
||||
You shoud not need to modify `mini-rv32ima.h`, but instead, use `mini-rv32ima.c` as a template for what you are trying to do in your own project.
|
||||
|
||||
You can override all functionality by defining the following macros. Here are examples of what `mini-rv32ima.c` does with them. You can see the definition of the functions, or augment their definitions, by altering `mini-rv32ima.c`.
|
||||
|
||||
| Macro | Definition / Comment |
|
||||
| --- | --- |
|
||||
| `MINIRV32WARN( x... )` | `printf( x );` <br> Warnings emitted from mini-rv32ima.h |
|
||||
| `MINIRV32_DECORATE` | `static` <br> How to decorate the functions. |
|
||||
| `MINI_RV32_RAM_SIZE` | `ram_amt` <br> A variable, how big is system RAM? |
|
||||
| `MINIRV32_IMPLEMENTATION` | If using mini-rv32ima.h, need to define this. |
|
||||
| `MINIRV32_POSTEXEC( pc, ir, retval )` | `{ if( retval > 0 ) { if( fail_on_all_faults ) { printf( "FAULT\n" ); return 3; } else retval = HandleException( ir, retval ); } }` <br> If you want to execute something every time slice. |
|
||||
| `MINIRV32_HANDLE_MEM_STORE_CONTROL( addy, val )` | `if( HandleControlStore( addy, val ) ) return val;` <br> Called on non-RAM memory access. |
|
||||
| `MINIRV32_HANDLE_MEM_LOAD_CONTROL( addy, rval )` | `rval = HandleControlLoad( addy );` <br> Called on non-RAM memory access return a value. |
|
||||
| `MINIRV32_OTHERCSR_WRITE( csrno, value )` | `HandleOtherCSRWrite( image, csrno, value );` <br> You can use CSRs for control requests. |
|
||||
| `MINIRV32_OTHERCSR_READ( csrno, value )` | `value = HandleOtherCSRRead( image, csrno );` <br> You can use CSRs for control requests. |
|
||||
|
||||
## Hopeful goals?
|
||||
* Further drive down needed features to run Linux.
|
||||
* Remove need for RV32A extension on systems with only one CPU.
|
||||
* Support for relocatable ELF executables.
|
||||
* Add support for an unreal UART. One that's **much** simpler than the current 8250 driver.
|
||||
* Maybe run this in a pixelshader too!
|
||||
* Get opensbi working with this.
|
||||
* Be able to "embed" rv32 emulators in random projects.
|
||||
* Can I use early console to be a full system console?
|
||||
* Can I increase the maximum contiguous memory allocatable?
|
||||
|
||||
## Special Thanks
|
||||
* For @regymm and their [patches to buildroot](https://github.com/regymm/buildroot) and help!
|
||||
* callout: Regymm's [quazisoc project](https://github.com/regymm/quasiSoC/).
|
||||
* Buildroot (For being so helpful).
|
||||
* @vowstar and their team working on [k210-linux-nommu](https://github.com/vowstar/k210-linux-nommu).
|
||||
* This [guide](https://jborza.com/emulation/2020/04/09/riscv-environment.html)
|
||||
* [rvcodecjs](https://luplab.gitlab.io/rvcodecjs/) I probably went through over 1,000 codes here.
|
||||
* @splinedrive from the [KianV RISC-V noMMU SoC](https://github.com/splinedrive/kianRiscV/tree/master/linux_socs/kianv_harris_mcycle_edition?s=09) project.
|
||||
|
||||
## More details
|
||||
|
||||
If you want to build the kernel yourself:
|
||||
* `make everything`
|
||||
* About 20 minutes. (Or 4+ hours if you're on [Windows Subsytem for Linux 2](https://github.com/microsoft/WSL/issues/4197))
|
||||
* And you should be dropped into a Linux busybox shell with some little tools that were compiled here.
|
||||
|
||||
## Emdoom notes
|
||||
* Emdoom building is in the `experiments/emdoom` folder
|
||||
* You *MUST* build your kernel with `MAX_ORDER` set to >12 in `buildroot/output/build/linux-5.19/include/linux/mmzone.h` if you are building your own image.
|
||||
* You CAN use the pre-existing image that is described above.
|
||||
* On Windows, it will be very slow. Not sure why.
|
||||
|
||||
If you want to use bare metal to build your binaries so you don't need buildroot, you can use the rv64 gcc in 32-bit mode built into Ubuntu 20.04 and up.
|
||||
```
|
||||
sudo apt-get install gcc-multilib gcc-riscv64-unknown-elf make
|
||||
```
|
||||
|
||||
## Links
|
||||
* "Hackaday Supercon 2022: Charles Lohr - Assembly in 2022: Yes! We Still Use it and Here's Why" : https://www.youtube.com/watch?v=Gelf0AyVGy4
|
||||
|
||||
## Attic
|
||||
|
||||
|
||||
## General notes:
|
||||
* https://github.com/cnlohr/riscv_emufun/commit/2f09cdeb378dc0215c07eb63f5a6fb43dbbf1871#diff-b48ccd795ae9aced07d022bf010bf9376232c4d78210c3113d90a8d349c59b3dL440
|
||||
|
||||
|
||||
(These things don't currently work)
|
||||
|
||||
### Building Tests
|
||||
|
||||
(This does not work, now)
|
||||
```
|
||||
cd riscv-tests
|
||||
export CROSS_COMPILE=riscv64-linux-gnu-
|
||||
export PLATFORM_RISCV_XLEN=32
|
||||
CC=riscv64-linux-gnu-gcc ./configure
|
||||
make XLEN=32 RISCV_PREFIX=riscv64-unknown-elf- RISCV_GCC_OPTS="-g -O1 -march=rv32imaf -mabi=ilp32f -I/usr/include"
|
||||
```
|
||||
|
||||
### Building OpenSBI
|
||||
|
||||
(This does not currently work!)
|
||||
```
|
||||
cd opensbi
|
||||
export CROSS_COMPILE=riscv64-unknown-elf-
|
||||
export PLATFORM_RISCV_XLEN=32
|
||||
make
|
||||
```
|
||||
|
||||
### Extra links
|
||||
* Clear outline of CSRs: https://five-embeddev.com/riscv-isa-manual/latest/priv-csrs.html
|
||||
* Fonts used in videos: https://audiolink.dev/
|
||||
|
||||
### Using custom build
|
||||
|
||||
Where yminpatch is the patch from the mailing list.
|
||||
```
|
||||
rm -rf buildroot
|
||||
git clone git://git.buildroot.net/buildroot
|
||||
cd buildroot
|
||||
git am < ../yminpatch.txt
|
||||
make qemu_riscv32_nommu_virt_defconfig
|
||||
make
|
||||
# Or use our configs.
|
||||
```
|
||||
|
||||
Note: For emdoom you will need to modify include/linux/mmzone.h and change MAX_ORDER to 13.
|
||||
|
||||
### Buildroot Notes
|
||||
|
||||
Add this:
|
||||
https://github.com/cnlohr/buildroot/pull/1/commits/bc890f74354e7e2f2b1cf7715f6ef334ff6ed1b2
|
||||
|
||||
Use this:
|
||||
https://github.com/cnlohr/buildroot/commit/e97714621bfae535d947817e98956b112eb80a75
|
||||
|
143
ccompiler/backend/riscv32/rv_vm/ripes-vm.c
Normal file
143
ccompiler/backend/riscv32/rv_vm/ripes-vm.c
Normal file
@ -0,0 +1,143 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
struct MiniRV32IMAState;
|
||||
void ecall_handler(struct MiniRV32IMAState *state);
|
||||
#define ECALL_HANDLER(state) ecall_handler(state)
|
||||
#define MINIRV32WARN( x... ) printf( x );
|
||||
#define MINIRV32_DECORATE static
|
||||
#define MINI_RV32_RAM_SIZE (32 * 1024 * 1024)
|
||||
#define MINIRV32_IMPLEMENTATION
|
||||
|
||||
#define MINIRV32_RAM_IMAGE_OFFSET 0x0
|
||||
#include "mini-rv32ima.h"
|
||||
|
||||
#define SYSCALL(num) (1025 + num)
|
||||
void ecall_handler(struct MiniRV32IMAState *state) {
|
||||
uint32_t a0 = REG(10);
|
||||
uint32_t a1 = REG(11);
|
||||
switch (state->regs[17]) // x17 | a7
|
||||
{
|
||||
case 1:
|
||||
// PrintInt
|
||||
printf("%d", a0);
|
||||
break;
|
||||
case 4:
|
||||
// PrintString
|
||||
printf("%s", a0);
|
||||
break;
|
||||
case 10:
|
||||
fprintf(stderr, "\nexit: %d\n", a0);
|
||||
exit(a0);
|
||||
case 11:
|
||||
// PrintChar
|
||||
printf("%c", a0);
|
||||
break;
|
||||
case 93:
|
||||
fprintf(stderr, "\nmain return code: %d\n", a0);
|
||||
exit(a0);
|
||||
case SYSCALL(0):
|
||||
// getchar();
|
||||
REGSET(10, getchar());
|
||||
case SYSCALL(1):
|
||||
// putchar
|
||||
putchar(a0);
|
||||
break;
|
||||
case SYSCALL(4):
|
||||
// input int
|
||||
scanf("%d", &a0);
|
||||
REGSET(10, a0);
|
||||
break;
|
||||
case SYSCALL(5):
|
||||
// input string
|
||||
scanf("%s", a0);
|
||||
REGSET(10, a0);
|
||||
break;
|
||||
default:
|
||||
MINIRV32WARN("Unhandled ECALL: %d\n", state->regs[17]);
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// gcc -DDEFAULT_FILE='\"flat.bin\"' .\ripes-vm.c -o rv32-vm.exe
|
||||
struct MiniRV32IMAState state;
|
||||
uint8_t *image = (uint8_t *)malloc(MINI_RV32_RAM_SIZE);
|
||||
|
||||
// 初始化状态
|
||||
memset(&state, 0, sizeof(state));
|
||||
state.pc = 0; // 程序计数器从0开始
|
||||
state.mstatus = 0x80000000; // 设置机器模式
|
||||
state.mtvec = 0x1000;
|
||||
state.mie = 0x7; // 启用所有中断
|
||||
|
||||
// 初始化内存
|
||||
memset(image, 0, MINI_RV32_RAM_SIZE);
|
||||
|
||||
#ifndef DEFAULT_FILE
|
||||
#define DEFAULT_FILE "../ccompiler/backend/test_rv.bin"
|
||||
#endif
|
||||
const char* filename = DEFAULT_FILE;
|
||||
// 加载 flatbin 文件
|
||||
if (argc == 2) {
|
||||
filename = argv[1];
|
||||
}
|
||||
|
||||
FILE *file = fopen(filename, "rb");
|
||||
if (!file) {
|
||||
fprintf(stderr, "Usage: %s <flatbin_file>\n", argv[0]);
|
||||
printf("Failed to open file %s\n", filename);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long flen = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
if (flen > MINI_RV32_RAM_SIZE) {
|
||||
fprintf(stderr, "Flatbin file is too large\n");
|
||||
fclose(file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fread(image, flen, 1, file);
|
||||
fclose(file);
|
||||
|
||||
// 运行模拟器
|
||||
while (1) {
|
||||
int32_t ret = MiniRV32IMAStep(&state, image, MINIRV32_RAM_IMAGE_OFFSET, 0, 1);
|
||||
if (ret != 0) {
|
||||
printf("Exception or interrupt occurred at PC: %d\n", state.pc);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
free(image);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// static void DumpState( struct MiniRV32IMAState * core, uint8_t * ram_image )
|
||||
// {
|
||||
// uint32_t pc = core->pc;
|
||||
// uint32_t pc_offset = pc - MINIRV32_RAM_IMAGE_OFFSET;
|
||||
// uint32_t ir = 0;
|
||||
|
||||
// printf( "PC: %08x ", pc );
|
||||
// if( pc_offset >= 0 && pc_offset < ram_amt - 3 )
|
||||
// {
|
||||
// ir = *((uint32_t*)(&((uint8_t*)ram_image)[pc_offset]));
|
||||
// printf( "[0x%08x] ", ir );
|
||||
// }
|
||||
// else
|
||||
// printf( "[xxxxxxxxxx] " );
|
||||
// uint32_t * regs = core->regs;
|
||||
// printf( "Z:%08x ra:%08x sp:%08x gp:%08x tp:%08x t0:%08x t1:%08x t2:%08x s0:%08x s1:%08x a0:%08x a1:%08x a2:%08x a3:%08x a4:%08x a5:%08x ",
|
||||
// regs[0], regs[1], regs[2], regs[3], regs[4], regs[5], regs[6], regs[7],
|
||||
// regs[8], regs[9], regs[10], regs[11], regs[12], regs[13], regs[14], regs[15] );
|
||||
// printf( "a6:%08x a7:%08x s2:%08x s3:%08x s4:%08x s5:%08x s6:%08x s7:%08x s8:%08x s9:%08x s10:%08x s11:%08x t3:%08x t4:%08x t5:%08x t6:%08x\n",
|
||||
// regs[16], regs[17], regs[18], regs[19], regs[20], regs[21], regs[22], regs[23],
|
||||
// regs[24], regs[25], regs[26], regs[27], regs[28], regs[29], regs[30], regs[31] );
|
||||
// }
|
Reference in New Issue
Block a user