/rv

RV32IMC in ~600 lines of C89

Primary LanguageC

rv

RISC-V CPU core written in ANSI C.

Features:

  • RV32IMC user-level implementation
  • Passes all supported tests in riscv-tests
  • ~600 lines of code
  • Doesn't use any integer types larger than 32 bits, even for multiplication
  • Simple API (two functions, plus two memory callback functions that you provide)
  • No memory allocations

API

/* Memory access callbacks: data is input/output, return RV_BAD on fault, 0 otherwise */
typedef rv_res (*rv_store_cb)(void *user, rv_u32 addr, rv_u8 data);
typedef rv_res (*rv_load_cb)(void *user, rv_u32 addr, rv_u8 *data);

/* Initialize CPU. */
void rv_init(rv *cpu, void *user, rv_load_cb load_cb, rv_store_cb store_cb);

/* Single-step CPU. Returns 0 on success, one of RV_E* on exception. */
rv_u32 rv_step(rv *cpu);

Usage

#include <stdio.h>
#include <string.h>

#include "rv.h"

rv_res load_cb(void *user, rv_u32 addr, rv_u8 *data) {
  if (addr - 0x80000000 > 0x10000) /* Reset vector is 0x80000000 */
    return RV_BAD;
  *data = ((rv_u8 *)(user))[addr - 0x80000000];
  return RV_OK;
}

rv_res store_cb(void *user, rv_u32 addr, rv_u8 data) {
  if (addr - 0x80000000 > 0x10000)
    return RV_BAD;
  ((rv_u8 *)(user))[addr - 0x80000000] = data;
  return RV_OK;
}

rv_u32 program[2] = {
    /* _start: */
    0x02A88893, /* add a7, a7, 42 */
    0x00000073  /* ecall */
};

int main(void) {
  rv_u8 mem[0x10000];
  rv cpu;
  rv_init(&cpu, (void *)mem, &load_cb, &store_cb);
  memcpy((void *)mem, (void *)program, sizeof(program));
  while (rv_step(&cpu) != RV_EECALL) {
  }
  printf("Environment call @ %08X: %u\n", cpu.pc, cpu.r[17]);
  return 0;
}

Targeting rv

Use riscv-gnu-toolchain with tools/link.ld.

Suggested GCC commandline:

riscv64-unknown-elf-gcc example.S -nostdlib -nostartfiles -Tlink.ld -march=rv32imc -mabi=ilp32 -o example.o -e _start -g -no-pie

To dump a binary starting at 0x80000000 that can be directly loaded by rv as in the above example:

riscv64-unknown-elf-objcopy -g -O binary example.o example.bin

Instruction List

Click an instruction to see its implementation in rv.c.

Caveats

  • Written in C89.
  • Not actually written in C89, since it uses external names longer than 6 characters.
  • Doesn't use any integer types larger than 32 bits, even for multiplication, because it's written in C89.
  • Assumes width of integer types in a way that's not completely compliant with C89/99. Fix for this is coming soon, I'm working on a watertight <stdint.h> for C89.
  • Written in C89.