Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use as an embedded application loader #33

Open
twitchyliquid64 opened this issue May 3, 2023 · 2 comments
Open

Use as an embedded application loader #33

twitchyliquid64 opened this issue May 3, 2023 · 2 comments

Comments

@twitchyliquid64
Copy link

Have you played with the flipper zero? They have a runtime application system where they load an ELF file which has the 'kernel API' represented using jump relocations. They copy it into memory, perform the relocations, and jump to it.

I was wondering if you had thoughts around:

  1. Using mini-rv32ima as a bytecode interpreter, for instance on an esp32; and
  2. Loading the rv32 code from an ELF file and doing the relocations or whatever (or maybe we could do the same with CSRs and skip ELF loading entirely?)

My personal interest is that the stable Rust compiler can compile to risc-v, and I could make a runtime for my esp32's in C but then put the main logic in Rust code (which compiles to risc-v for interpretation). Having an indirection between firmware and main code would also help with pushing updates, writing tests, and propagating updates over a mesh network (or maybe I'm just too excited about ESP-NOW)

Yeah, thoughts?

@twitchyliquid64
Copy link
Author

This is what I've come up with so far.

Some comments:

  1. mini-rv32ima.h is perfect for embedding with one exception: I had to move the struct def outside. Can we add an ifndef for that?
  2. This might be my new-ness to risc-v, but its really hard to tell when its okay to call MiniRV32IMAStep or when some state needs to be handled. Can we add a blocked() function or something like that?

Main sketch:

#include "Arduino.h"
#include "emulator.h"

#define LED_PIN 38


static uint8_t static_ram[] = {
  0x6f, 0x00, 0xC0, 0x00, //jal 8
  0x48, 0x69, 0x20, 0x3A, // "Hi :"
  0x29, 0x00, 0x00, 0x00, // ")"
  0x13, 0x01, 0x10, 0x00, //addi x2, x0, 1 - syscall number 1
  0x13, 0x02, 0x40, 0x00, //addi x4, x0, 4 - addr 0x04
  0x73, 0x00, 0x00, 0x00, //ecall
};
Emulator myEmu((void*) static_ram, sizeof(static_ram));

void setup() {
  Serial.begin(5000000);
  Serial.println("Hello T-Display-S3");
  pinMode(LED_PIN, OUTPUT);

  digitalWrite(LED_PIN, HIGH);
  myEmu.step(20);
  myEmu.step(20);
  digitalWrite(LED_PIN, LOW);
  delay(100);
}

void loop() {

}

emulator.cpp

#include "Arduino.h"
#include "emulator.h"

// configures mini-rv32ima.h
#define MINIRV32_DECORATE
#define MINIRV32_IMPLEMENTATION
#define MINIRV32_STEPPROTO MINIRV32_DECORATE int32_t Emulator::MiniRV32IMAStep( struct MiniRV32IMAState * state, uint8_t * image, uint32_t elapsedUs, int count )
#define MINI_RV32_RAM_SIZE this->_ramSize
#define MINIRV32_RAM_IMAGE_OFFSET 0x0

#define MINIRV32_POSTEXEC(pc, ir, trap) this->dumpState(state, image, pc);

#include "mini-rv32ima.h"


Emulator::Emulator(void* ram, uint32_t ramSize) {
	_ram = ram;
	_ramSize = ramSize;
	memset((void*) &_m, 0, sizeof(MiniRV32IMAState));
}

void Emulator::step(int numSteps) {
  if (this->noTrap()) {
  	uint32_t ret = MiniRV32IMAStep(&this->_m, (uint8_t*) this->_ram, micros(), numSteps);
    Serial.printf("got return: %08x\n", ret);
    dumpState(&this->_m, (uint8_t*) this->_ram, this->_m.pc);
  }

  // Handle any fault or exception.
  switch (this->_m.mcause) {
    case 0x08: // ECALL (syscall) - U-Mode
      handleSyscall();
      break;
  }
}

void Emulator::handleSyscall() {
  uint8_t fault = 0;
  uint32_t retval = 0;
  
  switch (this->_m.regs[2]) { // syscall number in x2

    case 1: // print null-terminated string
      // TODO(tom): Check memory bounds
      {
        void* base = this->_ram + this->_m.regs[4];
        Serial.println((const char*) base);
      }
      break;

    default:
      fault = 1;
      retval = -1;
      break;
  }

  if (!fault) {
    this->_m.regs[2] = retval;
  } else {
    this->_m.regs[7] = retval; // error code
  }

  this->_m.pc = this->_m.mtval;
  this->_m.mtval = 0;
  this->_m.mcause = 0;
}

bool Emulator::noTrap() {
  return this->_m.mcause == 0;
}


void Emulator::dumpState( struct MiniRV32IMAState * core, uint8_t * ram_image, uint32_t pc )
{
	uint32_t pc_offset = pc - MINIRV32_RAM_IMAGE_OFFSET;
	uint32_t ir = 0;

	Serial.printf( "PC: %08x ", pc );
	if( pc_offset >= 0 && pc_offset < MINI_RV32_RAM_SIZE - 3 )
	{
		ir = *((uint32_t*)(&((uint8_t*)ram_image)[pc_offset]));
		Serial.printf( "[0x%08x]", ir ); 
	}
	else
		Serial.printf( "[xxxxxxxxxx]" ); 

  Serial.println();
  delay(100);
   
	uint32_t * regs = core->regs;
	Serial.printf( "\tZ:%08x ra:%08x sp:%08x gp:%08x tp:%08x\n\tt0:%08x t1:%08x t2:%08x s0:%08x s1:%08x a0:%08x a1:%08x a2:%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]);
  Serial.println();
  delay(100);
  Serial.printf( "\ta3:%08x a4:%08x a5:%08x a6:%08x a7:%08x",
    regs[13], regs[14], regs[15], regs[16], regs[17]);
  Serial.println();
  delay(100);

  Serial.printf("\tmstatus=%08x mcause=%08x mtval=%08x", core->mstatus, core->mcause, core->mtval);
  Serial.println();
}

emulator.h

#ifndef Emulator_h
#define Emulator_h

// 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;
};

class Emulator {
public:
	Emulator(void* _ram, uint32_t _ramSize);

  // step executes at most numStep instructions or one syscall.
	void step(int numSteps);
  bool noTrap();

private:
	void* _ram;
	uint32_t _ramSize;
	MiniRV32IMAState _m;

	// defined in mini-rv32ima.h
	int32_t MiniRV32IMAStep( struct MiniRV32IMAState * state, uint8_t * image, uint32_t elapsedUs, int count );

	void dumpState( struct MiniRV32IMAState * core, uint8_t * ram_image, uint32_t pc );
	void handleSyscall();
};

#endif

@cnlohr
Copy link
Owner

cnlohr commented May 4, 2023

You can do this. If you are on Discord, feel free to discuss on the mini-rv32ima channel on my Discord. But, I believe this is possible, though it feels a little odd to me. I can't put my finger on a better way to do it instead though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants