implement UART, CLINT, device tree, opensbi build

...plus some comfort improvements, more testing.

This now successfully boots OpenSBI when built like specified in the
Makefile!
This commit is contained in:
Stefan 2021-06-03 22:42:59 +02:00
parent 9d170cc8d3
commit ed82c5487f
18 changed files with 600 additions and 70 deletions

2
.gitignore vendored
View File

@ -5,3 +5,5 @@ rvc
*.o *.o
elfy/target elfy/target
elfy/Cargo.lock elfy/Cargo.lock
fw_payload.*
*.dtb

3
.gitmodules vendored
View File

@ -7,3 +7,6 @@
[submodule "riscv-rust"] [submodule "riscv-rust"]
path = riscv-rust path = riscv-rust
url = https://github.com/takahirox/riscv-rust url = https://github.com/takahirox/riscv-rust
[submodule "opensbi"]
path = opensbi
url = https://github.com/riscv/opensbi

View File

@ -11,7 +11,7 @@ INC_DIRS := $(shell find $(SRC_DIRS) -type d)
INC_FLAGS := $(addprefix -I,$(INC_DIRS)) INC_FLAGS := $(addprefix -I,$(INC_DIRS))
$(TARGET): $(OBJS) $(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -I./elfy/elfy.h $(OBJS) -o $@ $(LOADLIBES) $(LDLIBS) -L./elfy/target/debug/ -Wl,--no-as-needed -ldl -lpthread -lelfy $(CC) $(LDFLAGS) -I./elfy/elfy.h $(OBJS) -o $@ $(LOADLIBES) $(LDLIBS) -L./elfy/target/release/ -Wl,--no-as-needed -ldl -lpthread -lelfy
.PHONY: clean .PHONY: clean
clean: clean:
@ -19,3 +19,21 @@ clean:
-include $(DEPS) -include $(DEPS)
# build device tree
dts.dtb: dts.dts
dtc -o $@ $<
OPENSBI_BUILD="opensbi/build/platform/generic/firmware"
fw_payload.bin: fw_payload.elf
cp $(OPENSBI_BUILD)/fw_payload.bin .
fw_payload.elf: opensbi
env CROSS_COMPILE=riscv64-elf- \
PLATFORM=generic \
PLATFORM_RISCV_XLEN=32 \
PLATFORM_RISCV_ISA=rv32ima \
PLATFORM_RISCV_ABI=ilp32 \
FW_PIC=n \
ELFFLAGS=-L/usr/lib/gcc/riscv64-elf/10.2.0/rv32im/ilp32 \
$(MAKE) -C opensbi all
cp $(OPENSBI_BUILD)/fw_payload.elf .

69
dts.dts Normal file
View File

@ -0,0 +1,69 @@
/dts-v1/;
/ {
#address-cells = <0x2>;
#size-cells = <0x2>;
compatible = "riscv-virtio";
model = "riscv-virtio,qemu";
chosen {
bootargs = "ttyS0";
stdout-path = "/uart@10000000";
};
uart@10000000 {
interrupts = <0x1>;
interrupt-parent = <0x2>;
clock-frequency = <0x384000>;
reg = <0x0 0x10000000 0x0 0x100>;
compatible = "ns16550a";
};
cpus {
#address-cells = <0x1>;
#size-cells = <0x0>;
timebase-frequency = <0x989680>;
cpu-map {
cluster0 {
core0 {
cpu = <0x1>;
};
};
};
cpu@0 {
phandle = <0x1>;
device_type = "cpu";
reg = <0x0>;
status = "okay";
compatible = "riscv";
riscv,isa = "rv64imasu";
interrupt-controller {
phandle = <0x2>;
#interrupt-cells = <0x1>;
interrupt-controller;
compatible = "riscv,cpu-intc";
};
};
};
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x8000000>;
};
soc {
#address-cells = <0x2>;
#size-cells = <0x2>;
compatible = "simple-bus";
ranges;
clint@2000000 {
interrupts-extended = <0x2 0x3 0x2 0x7>;
reg = <0x0 0x2000000 0x0 0x10000>;
compatible = "riscv,clint0";
};
};
};

81
dts.dts.final Normal file
View File

@ -0,0 +1,81 @@
/dts-v1/;
/ {
#address-cells = <0x2>;
#size-cells = <0x2>;
compatible = "riscv-virtio";
model = "riscv-virtio,qemu";
chosen {
bootargs = "ttyS0";
stdout-path = "/uart@10000000";
};
uart@10000000 {
interrupts = <0x1>;
interrupt-parent = <0x3>;
clock-frequency = <0x384000>;
reg = <0x0 0x10000000 0x0 0x100>;
compatible = "ns16550a";
};
cpus {
#address-cells = <0x1>;
#size-cells = <0x0>;
timebase-frequency = <0x989680>;
cpu-map {
cluster0 {
core0 {
cpu = <0x1>;
};
};
};
cpu@0 {
phandle = <0x1>;
device_type = "cpu";
reg = <0x0>;
status = "okay";
compatible = "riscv";
riscv,isa = "rv64imasu";
mmu-type = "riscv,sv32";
interrupt-controller {
phandle = <0x2>;
#interrupt-cells = <0x1>;
interrupt-controller;
compatible = "riscv,cpu-intc";
};
};
};
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x8000000>;
};
soc {
#address-cells = <0x2>;
#size-cells = <0x2>;
compatible = "simple-bus";
ranges;
interrupt-controller@c000000 {
phandle = <0x3>;
riscv,ndev = <0x35>;
reg = <0x0 0xc000000 0x0 0x4000000>;
interrupts-extended = <0x2 0xb 0x2 0x9>;
interrupt-controller;
compatible = "riscv,plic0";
#interrupt-cells = <0x1>;
#address-cells = <0x0>;
};
clint@2000000 {
interrupts-extended = <0x2 0x3 0x2 0x7>;
reg = <0x0 0x2000000 0x0 0x10000>;
compatible = "riscv,clint0";
};
};
};

View File

@ -3,4 +3,8 @@
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
void load_elf(const char *path, uint64_t path_len, uint8_t *data, uint64_t data_len); int32_t load_elf(const char *path,
uint64_t path_len,
uint8_t *data,
uint64_t data_len,
bool verbose);

View File

@ -4,12 +4,24 @@ use std::os::unix::ffi::OsStrExt;
use std::os::raw::c_char; use std::os::raw::c_char;
#[no_mangle] #[no_mangle]
pub extern "C" fn load_elf(path: *const c_char, path_len: u64, data: *mut u8, data_len: u64) { pub extern "C" fn load_elf(path: *const c_char, path_len: u64, data: *mut u8, data_len: u64, verbose: bool) -> i32 {
let res = std::panic::catch_unwind(|| {
do_load_elf(path, path_len, data, data_len, verbose);
});
match res {
Ok(()) => 0,
_ => 1,
}
}
fn do_load_elf(path: *const c_char, path_len: u64, data: *mut u8, data_len: u64, verbose: bool) {
let path = unsafe { CStr::from_bytes_with_nul_unchecked(std::slice::from_raw_parts(path as *const u8, path_len as usize)) }; let path = unsafe { CStr::from_bytes_with_nul_unchecked(std::slice::from_raw_parts(path as *const u8, path_len as usize)) };
let path = OsStr::from_bytes(path.to_bytes()); let path = OsStr::from_bytes(path.to_bytes());
let path = PathBuf::from(path); let path = PathBuf::from(path);
println!("Loading ELF binary '{}'", path.display()); if verbose {
println!("Loading ELF binary '{}'", path.display());
}
let elf = elf::File::open_path(path).unwrap(); let elf = elf::File::open_path(path).unwrap();
@ -22,7 +34,25 @@ pub extern "C" fn load_elf(path: *const c_char, path_len: u64, data: *mut u8, da
let addr = section.shdr.addr; let addr = section.shdr.addr;
let addr_real = addr & 0x7FFFFFFF; let addr_real = addr & 0x7FFFFFFF;
let size = section.data.len() as u64; let size = section.data.len() as u64;
println!("Loading ELF section '{}' @{:#x} (@{:#x}) size={}", name, addr, addr_real, size);
if name == ".comment" {
if verbose {
let comment = String::from_utf8_lossy(&section.data);
println!("ELF comment: {}", comment);
}
continue;
}
if addr == 0 {
if verbose {
println!("Skipping 'zero address' ELF section '{}' @{:#x} (@{:#x}) size={}", name, addr, addr_real, size);
}
continue;
}
if verbose {
println!("Loading ELF section '{}' @{:#x} (@{:#x}) size={}", name, addr, addr_real, size);
}
if addr_real + size > data_len { if addr_real + size > data_len {
panic!("ELF section too big or offset to great"); panic!("ELF section too big or offset to great");

1
opensbi Submodule

@ -0,0 +1 @@
Subproject commit f30b18944e900174bfa9a372ed9d344256891e3a

@ -1 +1 @@
Subproject commit 09cfdaacd9322cf0ac94818d8c852e1f4dc5bc4f Subproject commit 1b2c3ea84a7f8d8a833fca4d2b9aebb7d1ba4269

View File

@ -8,26 +8,31 @@
#include "emu.h" #include "emu.h"
#include "csr.h" #include "csr.h"
cpu_t cpu_init(uint8_t *mem) { cpu_t cpu_init(uint8_t *mem, uint8_t *dtb) {
cpu_t ret; cpu_t ret;
ret.clock = 0; ret.clock = 0;
for (uint i = 0; i < 32; i++) { for (uint i = 0; i < 32; i++) {
ret.xreg[i] = 0; ret.xreg[i] = 0;
} }
ret.xreg[0xb] = 0x1020; // linux? ret.xreg[0xb] = 0x1020; // linux? device tree?
ret.pc = 0x80000000; ret.pc = 0x80000000;
ret.mem = mem; ret.mem = mem;
ret.reservation_en = false; ret.reservation_en = false;
init_csrs(&ret); init_csrs(&ret);
ret.debug_single_step = ret.dtb = dtb;
#ifdef SINGLE_STEP
true ret.clint.msip = false;
#else ret.clint.mtimecmp_lo = 0;
false ret.clint.mtimecmp_hi = 0;
#endif ret.clint.mtime_lo = 0;
; ret.clint.mtime_hi = 0;
ret.uart.rbr_thr_ier_iir = 0;
ret.uart.lcr_mcr_lsr_scr = 0x00200000; // LSR_THR_EMPTY is set
ret.uart.thre_ip = false;
ret.uart.interrupting = false;
return ret; return ret;
} }

View File

@ -55,9 +55,9 @@ uint read_csr_raw(cpu_t *cpu, uint address) {
case CSR_SSTATUS: return cpu->csr.data[CSR_MSTATUS] & 0x000de162; case CSR_SSTATUS: return cpu->csr.data[CSR_MSTATUS] & 0x000de162;
case CSR_SIE: return cpu->csr.data[CSR_MIE] & 0x222; case CSR_SIE: return cpu->csr.data[CSR_MIE] & 0x222;
case CSR_SIP: return cpu->csr.data[CSR_MIP] & 0x222; case CSR_SIP: return cpu->csr.data[CSR_MIP] & 0x222;
case CSR_TIME: return cpu->clint.mtime_lo;
case CSR_CYCLE: return cpu->clock; case CSR_CYCLE: return cpu->clock;
case CSR_MHARTID: return 0; // this has to be 0, always case CSR_MHARTID: return 0;
/* case CSR_TIME => self.mmu.get_clint().read_mtime(), */
default: return cpu->csr.data[address & 0xffff]; default: return cpu->csr.data[address & 0xffff];
} }
} }
@ -87,6 +87,9 @@ void write_csr_raw(cpu_t *cpu, uint address, uint value) {
/* CSR_TIME => { */ /* CSR_TIME => { */
/* self.mmu.get_mut_clint().write_mtime(value); */ /* self.mmu.get_mut_clint().write_mtime(value); */
/* }, */ /* }, */
case CSR_TIME:
// ignore writes
break;
default: cpu->csr.data[address] = value; break; default: cpu->csr.data[address] = value; break;
}; };
} }
@ -95,9 +98,8 @@ void write_csr_raw(cpu_t *cpu, uint address, uint value) {
uint get_csr(cpu_t *cpu, uint address, ins_ret *ret) { uint get_csr(cpu_t *cpu, uint address, ins_ret *ret) {
if (has_csr_access_privilege(cpu, address)) { if (has_csr_access_privilege(cpu, address)) {
uint r = read_csr_raw(cpu, address); uint r = read_csr_raw(cpu, address);
#ifdef VERBOSE if (VERBOSE >= 2)
printf("CSR read @%03x = %08x\n", address, r); printf("CSR read @%03x = %08x\n", address, r);
#endif
return r; return r;
} else { } else {
ret->trap.en = true; ret->trap.en = true;
@ -108,9 +110,8 @@ uint get_csr(cpu_t *cpu, uint address, ins_ret *ret) {
} }
void set_csr(cpu_t *cpu, uint address, uint value, ins_ret *ret) { void set_csr(cpu_t *cpu, uint address, uint value, ins_ret *ret) {
#ifdef VERBOSE if (VERBOSE >= 2)
printf("CSR write @%03x = %08x\n", address, value); printf("CSR write @%03x = %08x\n", address, value);
#endif
if (has_csr_access_privilege(cpu, address)) { if (has_csr_access_privilege(cpu, address)) {
bool read_only = ((address >> 10) & 0x3) == 0x3; bool read_only = ((address >> 10) & 0x3) == 0x3;
if (read_only) { if (read_only) {
@ -118,10 +119,13 @@ void set_csr(cpu_t *cpu, uint address, uint value, ins_ret *ret) {
ret->trap.type = trap_IllegalInstruction; ret->trap.type = trap_IllegalInstruction;
ret->trap.value = cpu->pc; ret->trap.value = cpu->pc;
} else { } else {
write_csr_raw(cpu, address, value);
if (address == CSR_SATP) { if (address == CSR_SATP) {
// TODO: update MMU addressing mode // TODO: update MMU addressing mode
if (VERBOSE >= 1)
printf("WARN: Ignoring write to CSR_SATP\n");
return;
} }
write_csr_raw(cpu, address, value);
} }
} else { } else {
ret->trap.en = true; ret->trap.en = true;

View File

@ -413,11 +413,7 @@ DEF(xori, FormatI, { // rv32i
* END INSTRUCTIONS * END INSTRUCTIONS
*/ */
#ifdef VERBOSE #define pr_ins(name) if (VERBOSE >= 3) printf("INS %s (%08x)\n", #name, ins_word);
#define pr_ins(name) printf("INS %s (%08x)\n", #name, ins_word);
#else
#define pr_ins(name) {}
#endif
#define RUN(name, data, insf) case data : { \ #define RUN(name, data, insf) case data : { \
pr_ins(name) \ pr_ins(name) \
@ -538,10 +534,11 @@ ins_ret ins_select(cpu_t *cpu, uint ins_word) {
RUN(wfi, 0x10500073, ins_FormatEmpty) RUN(wfi, 0x10500073, ins_FormatEmpty)
} }
printf("Invalid instruction: %08x\n", ins_word); if (VERBOSE >= 1)
printf("Invalid instruction: %08x\n", ins_word);
ret.trap.en = true; ret.trap.en = true;
ret.trap.type = trap_IllegalInstruction; ret.trap.type = trap_IllegalInstruction;
ret.trap.value = cpu->pc; ret.trap.value = ins_word;
return ret; return ret;
} }
@ -567,10 +564,26 @@ void emulate(cpu_t *cpu) {
ret.trap.value = cpu->pc; ret.trap.value = cpu->pc;
} }
if (ret.trap.en) { // handle CLINT IRQs
handle_trap(cpu, &ret, false); if (cpu->clint.msip) {
cpu->csr.data[CSR_MIP] |= MIP_MSIP;
} }
cpu->clint.mtime_lo++;
cpu->clint.mtime_hi += cpu->clint.mtime_lo == 0 ? 1 : 0;
if (cpu->clint.mtimecmp_lo != 0 && cpu->clint.mtimecmp_hi != 0 && (cpu->clint.mtime_hi > cpu->clint.mtimecmp_hi || (cpu->clint.mtime_hi == cpu->clint.mtimecmp_hi && cpu->clint.mtime_lo >= cpu->clint.mtimecmp_lo))) {
cpu->csr.data[CSR_MIP] |= MIP_MTIP;
}
uart_tick(cpu);
if (cpu->uart.interrupting) {
uint cur_mip = read_csr_raw(cpu, CSR_MIP);
write_csr_raw(cpu, CSR_MIP, cur_mip | MIP_SEIP);
}
handle_irq_and_trap(cpu, &ret);
// ret.pc_val should be set to pc+4 by default // ret.pc_val should be set to pc+4 by default
cpu->pc = ret.pc_val; cpu->pc = ret.pc_val;
} }

View File

@ -1,6 +1,3 @@
/* #define VERBOSE */
/* #define SINGLE_STEP */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/mman.h> #include <sys/mman.h>
@ -14,44 +11,97 @@
#include "../elfy/elfy.h" #include "../elfy/elfy.h"
// lots of inspiration from:
// https://github.com/takahirox/riscv-rust/blob/master/src/cpu.rs
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
const int MEM_SIZE = 1024 * 1024 * 128; const int MEM_SIZE = 1024 * 1024 * 128;
size_t get_filesize(const char* filename) {
struct stat st;
stat(filename, &st);
return st.st_size;
}
uint8_t* get_mmap_ptr(const char* filename) {
size_t filesize = get_filesize(filename);
int fd = open(filename, O_RDONLY, 0);
uint8_t* mmapped_data = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0);
return mmapped_data;
}
void usage() {
printf("Usage: rvc (-e <ELF binary>|-b <raw binary>) [-d <device tree binary>] [-v (0|1|2|3)] [-s]\n");
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
if (argc > 2 || (argc == 2 && !strcmp(argv[1], "--help"))) { char *elf = NULL;
printf("Usage: rvc [<ELF binary>]\n"); char *bin = NULL;
exit(EXIT_FAILURE); char *dtb = NULL;
int c;
while ((c = getopt (argc, argv, "e:b:d:v:s")) != -1) {
switch (c)
{
case 'e':
elf = optarg;
break;
case 'b':
bin = optarg;
break;
case 'd':
dtb = optarg;
break;
case 'v':
VERBOSE = atoi(optarg);
break;
case 's':
SINGLE_STEP = 1;
break;
case '?':
default:
usage();
}
}
if ((!elf && !bin) || (elf && bin)) {
usage();
} }
uint8_t *mem = malloc(MEM_SIZE); uint8_t *mem = malloc(MEM_SIZE);
if (elf) {
if (argc == 2) { if (load_elf(elf, strlen(elf) + 1, mem, MEM_SIZE, VERBOSE >= 1)) {
load_elf(argv[1], strlen(argv[1]) + 1, mem, MEM_SIZE); exit(EXIT_FAILURE+1);
}
} else if (bin) {
uint8_t *bin_ptr = get_mmap_ptr(bin);
memcpy(mem, bin_ptr, get_filesize(bin));
} }
cpu_t cpu = cpu_init(mem); uint8_t *dtb_ptr = get_mmap_ptr("./dts.dtb");
printf("CPU initialized!\n"); cpu_t cpu = cpu_init(mem, dtb_ptr);
#ifdef VERBOSE if (VERBOSE >= 1)
cpu_dump(&cpu); printf("CPU initialized!\n");
#endif
if (VERBOSE >= 3)
cpu_dump(&cpu);
while (1) { while (1) {
cpu_tick(&cpu); cpu_tick(&cpu);
#ifdef VERBOSE
cpu_dump(&cpu);
#endif
if (cpu.debug_single_step) { if (VERBOSE >= 3)
cpu_dump(&cpu);
if (SINGLE_STEP) {
fflush(stdout); fflush(stdout);
fflush(stdin); fflush(stdin);
while(getchar()!='\n'); char ch;
gc: ch = getchar();
switch (ch) {
case '\n': break;
case 'c': SINGLE_STEP = 0; break;
default: goto gc;
}
} }
} }

119
src/mem.h
View File

@ -1,13 +1,70 @@
#ifndef MEM_H #ifndef MEM_H
#define MEM_H #define MEM_H
#include <stdio.h>
#include <assert.h> #include <assert.h>
#include "types.h" #include "types.h"
#include "uart.h"
// little endian, zero extended // little endian, zero extended
uint mem_get_byte(cpu_t *cpu, uint addr) { uint mem_get_byte(cpu_t *cpu, uint addr) {
/* printf("TRACE: mem_get_byte(%d)\n", addr); */ if (VERBOSE >= 3)
assert(addr & 0x80000000); printf("mem_get_byte(%08x)\n", addr);
if (cpu->dtb != NULL && addr >= 0x1020 && addr <= 0x1fff) {
if (VERBOSE >= 2)
printf("DTB read @%04x/%04x\n", addr, addr - 0x1020);
return cpu->dtb[addr - 0x1020];
}
switch (addr) {
// CLINT
case 0x02000000: return cpu->clint.msip ? 1 : 0;
case 0x02000001: return 0;
case 0x02000002: return 0;
case 0x02000003: return 0;
case 0x02004000: return (cpu->clint.mtimecmp_lo >> 0) & 0xFF;
case 0x02004001: return (cpu->clint.mtimecmp_lo >> 8) & 0xFF;
case 0x02004002: return (cpu->clint.mtimecmp_lo >> 16) & 0xFF;
case 0x02004003: return (cpu->clint.mtimecmp_lo >> 24) & 0xFF;
case 0x02004004: return (cpu->clint.mtimecmp_hi >> 0) & 0xFF;
case 0x02004005: return (cpu->clint.mtimecmp_hi >> 8) & 0xFF;
case 0x02004006: return (cpu->clint.mtimecmp_hi >> 16) & 0xFF;
case 0x02004007: return (cpu->clint.mtimecmp_hi >> 24) & 0xFF;
case 0x0200bff8: return (cpu->clint.mtime_lo >> 0) & 0xFF;
case 0x0200bff9: return (cpu->clint.mtime_lo >> 8) & 0xFF;
case 0x0200bffa: return (cpu->clint.mtime_lo >> 16) & 0xFF;
case 0x0200bffb: return (cpu->clint.mtime_lo >> 24) & 0xFF;
case 0x0200bffc: return (cpu->clint.mtime_hi >> 0) & 0xFF;
case 0x0200bffd: return (cpu->clint.mtime_hi >> 8) & 0xFF;
case 0x0200bffe: return (cpu->clint.mtime_hi >> 16) & 0xFF;
case 0x0200bfff: return (cpu->clint.mtime_hi >> 24) & 0xFF;
// UART (first has rbr_thr_ier_iir, second has lcr_mcr_lsr_scr)
case 0x10000000:
if ((UART_GET2(LCR) >> 7) == 0) {
uint rbr = UART_GET1(RBR);
UART_SET1(RBR, 0);
UART_SET2(LSR, (UART_GET2(LSR) & ~LSR_DATA_AVAILABLE));
uart_update_iir(cpu);
return rbr;
} else {
return 0;
}
case 0x10000001: return UART_GET2(LCR) >> 7 == 0 ? UART_GET1(IER) : 0;
case 0x10000002: return UART_GET1(IIR);
case 0x10000003: return UART_GET2(LCR);
case 0x10000004: return UART_GET2(MCR);
case 0x10000005: return UART_GET2(LSR);
case 0x10000007: return UART_GET2(SCR);
}
if ((addr & 0x80000000) == 0) {
return 0;
}
return cpu->mem[addr & 0x7FFFFFFF]; return cpu->mem[addr & 0x7FFFFFFF];
} }
@ -23,7 +80,63 @@ uint mem_get_word(cpu_t *cpu, uint addr) {
} }
void mem_set_byte(cpu_t *cpu, uint addr, uint val) { void mem_set_byte(cpu_t *cpu, uint addr, uint val) {
assert(addr & 0x80000000); if (VERBOSE >= 3)
printf("mem_set_byte(%08x, %08x)\n", addr, val);
switch (addr) {
// CLINT
case 0x02000000: cpu->clint.msip = (val & 1) != 0; return;
case 0x02000001: return;
case 0x02000002: return;
case 0x02000003: return;
case 0x02004000: cpu->clint.mtimecmp_lo = (cpu->clint.mtimecmp_lo & ~(0xff << 0)) | (val << 0); return;
case 0x02004001: cpu->clint.mtimecmp_lo = (cpu->clint.mtimecmp_lo & ~(0xff << 8)) | (val << 8); return;
case 0x02004002: cpu->clint.mtimecmp_lo = (cpu->clint.mtimecmp_lo & ~(0xff << 16)) | (val << 16); return;
case 0x02004003: cpu->clint.mtimecmp_lo = (cpu->clint.mtimecmp_lo & ~(0xff << 24)) | (val << 24); return;
case 0x02004004: cpu->clint.mtimecmp_hi = (cpu->clint.mtimecmp_hi & ~(0xff << 0)) | (val << 0); return;
case 0x02004005: cpu->clint.mtimecmp_hi = (cpu->clint.mtimecmp_hi & ~(0xff << 8)) | (val << 8); return;
case 0x02004006: cpu->clint.mtimecmp_hi = (cpu->clint.mtimecmp_hi & ~(0xff << 16)) | (val << 16); return;
case 0x02004007: cpu->clint.mtimecmp_hi = (cpu->clint.mtimecmp_hi & ~(0xff << 24)) | (val << 24); return;
case 0x0200bff8: cpu->clint.mtime_lo = (cpu->clint.mtime_lo & ~(0xff << 0)) | (val << 0); return;
case 0x0200bff9: cpu->clint.mtime_lo = (cpu->clint.mtime_lo & ~(0xff << 8)) | (val << 8); return;
case 0x0200bffa: cpu->clint.mtime_lo = (cpu->clint.mtime_lo & ~(0xff << 16)) | (val << 16); return;
case 0x0200bffb: cpu->clint.mtime_lo = (cpu->clint.mtime_lo & ~(0xff << 24)) | (val << 24); return;
case 0x0200bffc: cpu->clint.mtime_hi = (cpu->clint.mtime_hi & ~(0xff << 0)) | (val << 0); return;
case 0x0200bffd: cpu->clint.mtime_hi = (cpu->clint.mtime_hi & ~(0xff << 8)) | (val << 8); return;
case 0x0200bffe: cpu->clint.mtime_hi = (cpu->clint.mtime_hi & ~(0xff << 16)) | (val << 16); return;
case 0x0200bfff: cpu->clint.mtime_hi = (cpu->clint.mtime_hi & ~(0xff << 24)) | (val << 24); return;
// UART (first has rbr_thr_ier_iir, second has lcr_mcr_lsr_scr)
case 0x10000000:
if ((UART_GET2(LCR) >> 7) == 0) {
UART_SET1(THR, val);
UART_SET2(LSR, (UART_GET2(LSR) & ~LSR_THR_EMPTY));
uart_update_iir(cpu);
}
return;
case 0x10000001:
if (UART_GET2(LCR) >> 7 == 0) {
if ((UART_GET1(IER) & IER_THREINT_BIT) == 0 &&
(val & IER_THREINT_BIT) != 0 &&
UART_GET1(THR) == 0)
{
cpu->uart.thre_ip = true;
}
UART_SET1(IER, val);
uart_update_iir(cpu);
}
return;
case 0x10000003: UART_SET2(LCR, val); return;
case 0x10000004: UART_SET2(MCR, val); return;
case 0x10000007: UART_SET2(SCR, val); return;
}
if ((addr & 0x80000000) == 0) {
return;
}
cpu->mem[addr & 0x7FFFFFFF] = val; cpu->mem[addr & 0x7FFFFFFF] = val;
} }

View File

@ -33,6 +33,14 @@ const uint trap_UserExternalInterrupt = interrupt_offset + 8;
const uint trap_SupervisorExternalInterrupt = interrupt_offset + 9; const uint trap_SupervisorExternalInterrupt = interrupt_offset + 9;
const uint trap_MachineExternalInterrupt = interrupt_offset + 11; const uint trap_MachineExternalInterrupt = interrupt_offset + 11;
const uint MIP_MEIP = 0x800;
const uint MIP_MTIP = 0x080;
const uint MIP_MSIP = 0x008;
const uint MIP_SEIP = 0x200;
const uint MIP_STIP = 0x020;
const uint MIP_SSIP = 0x002;
const uint MIP_ALL = MIP_MEIP | MIP_MTIP | MIP_MSIP | MIP_SEIP | MIP_STIP | MIP_SSIP;
// include after trap_ definitions // include after trap_ definitions
#include "csr.h" #include "csr.h"
@ -133,12 +141,39 @@ bool handle_trap(cpu_t *cpu, ins_ret *ret, bool is_interrupt) {
write_csr_raw(cpu, CSR_SSTATUS, new_status); write_csr_raw(cpu, CSR_SSTATUS, new_status);
} }
#ifdef VERBOSE if (VERBOSE >= 1)
printf("trap: type=%08x value=%08x (IRQ: %d) moved PC from @%08x to @%08x\n", t.type, t.value, is_interrupt, cpu->pc, ret->pc_val); printf("trap: type=%08x value=%08x (IRQ: %d) moved PC from @%08x to @%08x\n", t.type, t.value, is_interrupt, cpu->pc, ret->pc_val);
#endif
/* cpu->debug_single_step = true; */
return true; return true;
} }
void handle_irq_and_trap(cpu_t *cpu, ins_ret *ret) {
bool trap = ret->trap.en;
uint mip_reset = MIP_ALL;
uint cur_mip = read_csr_raw(cpu, CSR_MIP);
if (!trap) {
uint mirq = cur_mip & read_csr_raw(cpu, CSR_MIE);
#define HANDLE(mip, ttype) case mip: mip_reset = mip; ret->trap.en = true; ret->trap.type = ttype; break;
switch (mirq & MIP_ALL) {
HANDLE(MIP_MEIP, trap_MachineExternalInterrupt)
HANDLE(MIP_MSIP, trap_MachineSoftwareInterrupt)
HANDLE(MIP_MTIP, trap_MachineTimerInterrupt)
HANDLE(MIP_SEIP, trap_SupervisorExternalInterrupt)
HANDLE(MIP_SSIP, trap_SupervisorSoftwareInterrupt)
HANDLE(MIP_STIP, trap_SupervisorTimerInterrupt)
}
#undef HANDLE
}
bool irq = mip_reset != MIP_ALL;
if (trap || irq) {
bool handled = handle_trap(cpu, ret, irq);
if (handled && irq) {
// reset MIP value since IRQ was handled
write_csr_raw(cpu, CSR_MIP, cur_mip & ~mip_reset);
}
}
}
#endif #endif

View File

@ -5,6 +5,9 @@
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
static int VERBOSE = 0;
static bool SINGLE_STEP = false;
typedef uint32_t uint; typedef uint32_t uint;
typedef uint32_t uint; typedef uint32_t uint;
@ -13,21 +16,38 @@ typedef struct {
uint privilege; uint privilege;
} csr_state; } csr_state;
typedef struct {
uint rbr_thr_ier_iir;
uint lcr_mcr_lsr_scr;
bool thre_ip;
bool interrupting;
} uart_state;
typedef struct {
bool msip;
uint mtimecmp_lo;
uint mtimecmp_hi;
uint mtime_lo;
uint mtime_hi;
} clint_state;
typedef struct { typedef struct {
uint clock; uint clock;
uint xreg[32]; uint xreg[32];
uint pc; uint pc;
uint8_t *mem; uint8_t *mem;
uint8_t *dtb;
csr_state csr; csr_state csr;
clint_state clint;
uart_state uart;
bool reservation_en; bool reservation_en;
uint reservation_addr; uint reservation_addr;
bool debug_single_step;
} cpu_t; } cpu_t;
typedef struct { typedef struct {
bool en; bool en;
bool irq;
uint type; uint type;
uint value; uint value;
} trap; } trap;

72
src/uart.h Normal file
View File

@ -0,0 +1,72 @@
#ifndef UART_H
#define UART_H
#include <stdio.h>
#include "types.h"
const uint SHIFT_RBR = 0;
const uint SHIFT_THR = 8;
const uint SHIFT_IER = 16;
const uint SHIFT_IIR = 24;
const uint SHIFT_LCR = 0;
const uint SHIFT_MCR = 8;
const uint SHIFT_LSR = 16;
const uint SHIFT_SCR = 24;
#define UART_GET1(x) ((cpu->uart.rbr_thr_ier_iir >> SHIFT_##x) & 0xff)
#define UART_GET2(x) ((cpu->uart.lcr_mcr_lsr_scr >> SHIFT_##x) & 0xff)
#define UART_SET1(x, val) cpu->uart.rbr_thr_ier_iir = (cpu->uart.rbr_thr_ier_iir & ~(0xff << SHIFT_##x)) | (val << SHIFT_##x)
#define UART_SET2(x, val) cpu->uart.lcr_mcr_lsr_scr = (cpu->uart.lcr_mcr_lsr_scr & ~(0xff << SHIFT_##x)) | (val << SHIFT_##x)
const uint IER_RXINT_BIT = 0x1;
const uint IER_THREINT_BIT = 0x2;
const uint IIR_THR_EMPTY = 0x2;
const uint IIR_RD_AVAILABLE = 0x4;
const uint IIR_NO_INTERRUPT = 0x7;
const uint LSR_DATA_AVAILABLE = 0x1;
const uint LSR_THR_EMPTY = 0x20;
void uart_update_iir(cpu_t *cpu) {
bool rx_ip = (UART_GET1(IER) & IER_RXINT_BIT) != 0 && UART_GET1(RBR) != 0;
bool thre_ip = (UART_GET1(IER) & IER_THREINT_BIT) != 0 && UART_GET1(THR) == 0;
UART_SET1(IIR, (rx_ip ? IIR_RD_AVAILABLE : (thre_ip ? IIR_THR_EMPTY : IIR_NO_INTERRUPT)));
}
void uart_tick(cpu_t *cpu) {
bool rx_ip = false;
if ((cpu->clock % 0x38400) == 0 && UART_GET1(RBR) == 0) {
uint value = 0; // TODO: Add actual input logic
if (value != 0) {
UART_SET1(RBR, value);
UART_SET2(LSR, (UART_GET2(LSR) | LSR_DATA_AVAILABLE));
uart_update_iir(cpu);
if ((UART_GET1(IER) & IER_RXINT_BIT) != 0) {
rx_ip = true;
}
}
}
uint thr = UART_GET1(THR);
if ((cpu->clock & 0x16) == 0 && thr != 0) {
printf("%c", (char)thr);
UART_SET1(THR, 0);
UART_SET2(LSR, (UART_GET2(LSR) | LSR_THR_EMPTY));
uart_update_iir(cpu);
if ((UART_GET1(IER) & IER_THREINT_BIT) != 0) {
cpu->uart.thre_ip = true;
}
}
if (cpu->uart.thre_ip || rx_ip) {
cpu->uart.interrupting = true;
cpu->uart.thre_ip = false;
} else {
cpu->uart.interrupting = false;
}
}
#endif

16
test.sh
View File

@ -19,8 +19,8 @@ function run_test {
popd popd
echo "Running: ./rvc \"./riscv-tests/isa/$1\"" echo "Running: ./rvc -e \"./riscv-tests/isa/$1\""
timeout 5s ./rvc "./riscv-tests/isa/$1" timeout 5s ./rvc -e "./riscv-tests/isa/$1"
if [ $? -gt 0 ]; then if [ $? -gt 0 ]; then
echo "Test failed!" echo "Test failed!"
@ -88,7 +88,17 @@ rv32ua-p-amoswap_w
rv32ua-p-amoxor_w rv32ua-p-amoxor_w
rv32mi-p-mcsr rv32mi-p-mcsr
rv32mi-p-csr rv32mi-p-csr
rv32si-p-csr" rv32si-p-csr
rv32si-p-scall"
# excluded tests:
# rv32mi-p-scall, successful, but defined as "success if never returning"
# rv32mi-p-shamt, newer spec says ignoring is OK, so we do that instead
# rv32mi-p-sbreak, breakpoint, I believe from debug spec, no need
# rv32mi-p-illegal, requires virtual machine paging? (TVM)
# rv32mi-p-ma_addr, misaligned address access is ignored (this one passes though?)
# rv32mi-p-ma_fetch, ignored for same reason, fails though
for t in $TESTS; do for t in $TESTS; do
if [[ "$t" =~ ^# ]]; then continue; fi if [[ "$t" =~ ^# ]]; then continue; fi
echo echo