Add timer and soft IRQ support to rvcpp. Relevant sw_testcases now pass.

This commit is contained in:
Luke Wren 2024-03-22 00:52:01 +00:00
parent b1be56fe94
commit a313493371
6 changed files with 223 additions and 31 deletions

View File

@ -5,7 +5,7 @@ EXECUTABLE:=rvcpp
.PHONY: all clean
all:
g++ -std=c++17 -O3 -Wall -Wno-parentheses -I $(PWD)/include $(SRCS) -o $(EXECUTABLE)
g++ -std=c++17 -O3 -Wall -Wno-parentheses -I include $(SRCS) -o $(EXECUTABLE)
clean:
rm -f $(EXECUTABLE)

View File

@ -3,6 +3,12 @@
#include "rv_types.h"
class RVCSR {
// Latched IRQ signals into core
bool irq_t;
bool irq_s;
bool irq_e;
// Current core privilege level (M/S/U)
uint priv;
@ -22,6 +28,11 @@ class RVCSR {
std::optional<ux_t> pending_write_addr;
ux_t pending_write_data;
ux_t get_effective_xip();
// Internal interface for updating trap state. Returns trap target pc.
ux_t trap_enter(uint xcause, ux_t xepc);
public:
enum {
@ -31,12 +42,15 @@ public:
};
RVCSR() {
irq_t = false;
irq_s = false;
irq_e = false;
priv = 3;
mcycle = 0;
mcycleh = 0;
minstret = 0;
minstreth = 0;
mcountinhibit = 0;
mcountinhibit = 0x5;
mstatus = 0;
mie = 0;
mip = 0;
@ -55,13 +69,36 @@ public:
// Returns false on permission/decode fail
bool write(uint16_t addr, ux_t data, uint op=WRITE);
// Update trap state (including change of privilege level), return trap target PC
ux_t trap_enter(uint xcause, ux_t xepc);
// Determine target privilege level of an exception, update trap state
// (including change of privilege level), return trap target PC
ux_t trap_enter_exception(uint xcause, ux_t xepc);
// If there is currently a pending IRQ that must be entered, then
// determine its target privilege level, update trap state, and return
// trap target PC. Otherwise return None.
std::optional<ux_t> trap_check_enter_irq(ux_t xepc);
// Update trap state, return mepc:
ux_t trap_mret();
uint getpriv() {
uint get_true_priv() {
return priv;
}
void set_irq_t(bool irq) {
irq_t = irq;
}
void set_irq_s(bool irq) {
irq_s = irq;
}
void set_irq_e(bool irq) {
irq_e = irq;
}
ux_t get_xcause() {
return mcause;
}
};

View File

@ -78,21 +78,104 @@ struct TBExitException {
};
struct TBMemIO: MemBase32 {
enum {
IO_PRINT_CHAR = 0x000,
IO_PRINT_U32 = 0x004,
IO_EXIT = 0x008,
IO_SET_SOFTIRQ = 0x010,
IO_CLR_SOFTIRQ = 0x014,
IO_GLOBMON_EN = 0x018,
IO_SET_IRQ = 0x020,
IO_CLR_IRQ = 0x030,
IO_MTIME = 0x100,
IO_MTIMEH = 0x104,
IO_MTIMECMP = 0x108,
IO_MTIMECMPH = 0x10c,
};
uint64_t mtime;
uint64_t mtimecmp;
bool softirq;
bool trace;
TBMemIO(bool trace_) {
mtime = 0;
mtimecmp = 0; // -1 would be better, but match tb and tests
softirq = false;
trace = trace_;
}
virtual bool w32(ux_t addr, uint32_t data) {
switch (addr) {
case 0x0:
printf("%c", (char)data);
case IO_PRINT_CHAR:
if (trace)
printf("IO_PRINT_CHAR: %c\n", (char)data);
else
printf("%c", (char)data);
return true;
case 0x4:
printf("%08x\n", data);
case IO_PRINT_U32:
if (trace)
printf("IO_PRINT_U32: %08x\n", data);
else
printf("%08x\n", data);
return true;
case 0x8:
case IO_EXIT:
throw TBExitException(data);
return true;
case IO_SET_SOFTIRQ:
softirq = softirq || (data & 0x1);
return true;
case IO_CLR_SOFTIRQ:
softirq = softirq && !(data & 0x1);
return true;
case IO_MTIME:
mtime = (mtime & 0xffffffff00000000ull) | data;
return true;
case IO_MTIMEH:
mtime = (mtime & 0x00000000ffffffffull) | ((uint64_t)data << 32);
return true;
case IO_MTIMECMP:
mtimecmp = (mtimecmp & 0xffffffff00000000ull) | data;
return true;
case IO_MTIMECMPH:
mtimecmp = (mtimecmp & 0x00000000ffffffffull) | ((uint64_t)data << 32);
return true;
default:
return false;
}
}
virtual std::optional<uint32_t> r32(ux_t addr) {
switch(addr) {
case IO_MTIME:
return mtime & 0xffffffffull;
case IO_MTIMEH:
return mtime >> 32;
case IO_MTIMECMP:
return mtimecmp & 0xffffffffull;
case IO_MTIMECMPH:
return mtimecmp >> 32;
case IO_SET_SOFTIRQ:
case IO_CLR_SOFTIRQ:
return softirq;
default:
return {};
}
}
void step() {
mtime++;
}
bool timer_irq_pending() {
return mtime >= mtimecmp;
}
bool soft_irq_pending() {
return softirq;
}
};
struct MemMap32: MemBase32 {

View File

@ -106,9 +106,9 @@ int main(int argc, char **argv) {
}
}
TBMemIO io;
TBMemIO io(trace_execution);
MemMap32 mem;
mem.add(0x80000000u, 12, &io);
mem.add(0x80000000u, 0x1000, &io);
RVCore core(mem, RAM_BASE + 0x40, RAM_BASE, ram_size);
@ -126,8 +126,12 @@ int main(int argc, char **argv) {
int64_t cyc;
int rc = 0;
try {
for (cyc = 0; cyc < max_cycles; ++cyc)
for (cyc = 0; cyc < max_cycles; ++cyc) {
core.step(trace_execution);
io.step();
core.csr.set_irq_t(io.timer_irq_pending());
core.csr.set_irq_s(io.soft_irq_pending());
}
if (propagate_return_code)
rc = -1;
}

View File

@ -131,6 +131,10 @@ void RVCore::step(bool trace) {
std::optional<uint> exception_cause;
uint regnum_rd = 0;
std::optional<ux_t> trace_csr_addr;
std::optional<ux_t> trace_csr_result;
std::optional<uint> trace_priv;
std::optional<uint16_t> fetch0 = r16(pc);
std::optional<uint16_t> fetch1 = r16(pc + 2);
uint32_t instr = *fetch0 | ((uint32_t)*fetch1 << 16);
@ -139,7 +143,10 @@ void RVCore::step(bool trace) {
uint funct3 = instr >> 12 & 0x7;
uint funct7 = instr >> 25 & 0x7f;
if (!fetch0 || ((*fetch0 & 0x3) == 0x3 && !fetch1)) {
std::optional<ux_t> irq_target_pc = csr.trap_check_enter_irq(pc);
if (irq_target_pc) {
// Replace current instruction with IRQ entry
} else if (!fetch0 || ((*fetch0 & 0x3) == 0x3 && !fetch1)) {
exception_cause = XCAUSE_INSTR_FAULT;
} else if ((instr & 0x3) == 0x3) {
// 32-bit instruction
@ -547,18 +554,33 @@ void RVCore::step(bool trace) {
else if (funct3 >= 0b101 && funct3 <= 0b111) {
// csrrwi, csrrsi, csrrci
uint write_op = funct3 - 0b101;
if (write_op != RVCSR::WRITE || regnum_rd != 0)
if (write_op != RVCSR::WRITE || regnum_rd != 0) {
rd_wdata = csr.read(csr_addr);
if (write_op == RVCSR::WRITE || regnum_rs1 != 0)
csr.write(csr_addr, regnum_rs1, write_op);
if (!rd_wdata) {
exception_cause = XCAUSE_INSTR_ILLEGAL;
}
if (trace && !exception_cause) {
trace_csr_addr = csr_addr;
}
}
if (write_op == RVCSR::WRITE || regnum_rs1 != 0) {
if (!csr.write(csr_addr, regnum_rs1, write_op)) {
exception_cause = XCAUSE_INSTR_ILLEGAL;
}
if (trace && !exception_cause) {
trace_csr_addr = csr_addr;
trace_csr_result = csr.read(csr_addr, false);
}
}
} else if (RVOPC_MATCH(instr, MRET)) {
if (csr.getpriv() == PRV_M) {
if (csr.get_true_priv() == PRV_M) {
pc_wdata = csr.trap_mret();
trace_priv = csr.get_true_priv();
} else {
exception_cause = XCAUSE_INSTR_ILLEGAL;
}
} else if (RVOPC_MATCH(instr, ECALL)) {
exception_cause = XCAUSE_ECALL_U + csr.getpriv();
exception_cause = XCAUSE_ECALL_U + csr.get_true_priv();
} else if (RVOPC_MATCH(instr, EBREAK)) {
exception_cause = XCAUSE_EBREAK;
} else {
@ -774,30 +796,49 @@ void RVCore::step(bool trace) {
}
if (trace) {
if (trace && !irq_target_pc) {
printf("%08x: ", pc);
if ((instr & 0x3) == 0x3) {
printf("%08x : ", instr);
} else {
printf(" %04x : ", instr & 0xffffu);
}
if (regnum_rd != 0 && rd_wdata) {
printf("%-3s <- %08x ", friendly_reg_names[regnum_rd], *rd_wdata);
bool gpr_writeback = regnum_rd != 0 && rd_wdata;
if (gpr_writeback) {
printf("%-3s <- %08x :\n", friendly_reg_names[regnum_rd], *rd_wdata);
} else if (pc_wdata) {
printf("pc <- %08x <\n", *pc_wdata);
} else {
printf(" ");
printf(" :\n");
}
if (pc_wdata) {
printf(": pc <- %08x\n", *pc_wdata);
} else {
printf(":\n");
if (pc_wdata && gpr_writeback) {
printf(" : pc <- %08x <\n", *pc_wdata);
}
if (trace_csr_result) {
printf(" : #%03x <- %08x :\n", *trace_csr_addr, *trace_csr_result);
}
}
// Ensure pending CSR writes are applied before checking IRQ conditions
csr.step();
if (exception_cause) {
pc_wdata = csr.trap_enter(*exception_cause, pc);
pc_wdata = csr.trap_enter_exception(*exception_cause, pc);
if (trace) {
printf("Trap cause %2u: pc <- %08x\n", *exception_cause, *pc_wdata);
printf("^^^ Trap : cause <- %-2u :\n", *exception_cause);
printf("||| : pc <- %08x <\n", *pc_wdata);
trace_priv = csr.get_true_priv();
}
} else if (irq_target_pc) {
pc_wdata = irq_target_pc;
if (trace) {
printf("^^^ IRQ : cause <- IRQ + %-2u :\n", csr.get_xcause() & ((1u << 31) - 1));
printf("||| : pc <- %08x <\n", *pc_wdata);
trace_priv = csr.get_true_priv();
}
}
if (trace && trace_priv) {
printf("||| : priv <- %c :\n", "US.M"[*trace_priv & 0x3]);
}
if (pc_wdata)
@ -806,5 +847,4 @@ void RVCore::step(bool trace) {
pc = pc + ((instr & 0x3) == 0x3 ? 4 : 2);
if (rd_wdata && regnum_rd != 0)
regs[regnum_rd] = *rd_wdata;
csr.step();
}

View File

@ -9,6 +9,14 @@
#define GETBITS(x, msb, lsb) (((x) & BITRANGE(msb, lsb)) >> (lsb))
#define GETBIT(x, bit) (((x) >> (bit)) & 1u)
ux_t RVCSR::get_effective_xip() {
return mip |
(irq_s ? MIP_MSIP : 0) |
(irq_t ? MIP_MTIP : 0) |
(irq_e ? MIP_MEIP : 0);
}
void RVCSR::step() {
uint64_t mcycle_64 = ((uint64_t)mcycleh << 32) | mcycle;
uint64_t minstret_64 = ((uint64_t)minstreth << 32) | minstret;
@ -66,7 +74,7 @@ std::optional<ux_t> RVCSR::read(uint16_t addr, bool side_effect) {
case CSR_MSTATUS: return mstatus;
case CSR_MIE: return mie;
case CSR_MIP: return mip;
case CSR_MIP: return get_effective_xip();
case CSR_MTVEC: return mtvec;
case CSR_MSCRATCH: return mscratch;
case CSR_MEPC: return mepc;
@ -126,6 +134,23 @@ bool RVCSR::write(uint16_t addr, ux_t data, uint op) {
return true;
}
ux_t RVCSR::trap_enter_exception(uint xcause, ux_t xepc) {
assert(xcause < 32);
assert(!pending_write_addr);
return trap_enter(xcause, xepc);
}
std::optional<ux_t> RVCSR::trap_check_enter_irq(ux_t xepc) {
ux_t m_targeted_irqs = get_effective_xip() & mie;
bool take_m_irq = m_targeted_irqs && ((mstatus & MSTATUS_MIE) || priv < PRV_M);
if (take_m_irq) {
ux_t cause = (1u << 31) | __builtin_ctz(m_targeted_irqs);
return trap_enter(cause, xepc);
} else {
return std::nullopt;
}
}
// Update trap state (including change of privilege level), return trap target PC
ux_t RVCSR::trap_enter(uint xcause, ux_t xepc) {
mstatus = (mstatus & ~MSTATUS_MPP) | (priv << 11);
@ -147,6 +172,9 @@ ux_t RVCSR::trap_enter(uint xcause, ux_t xepc) {
// Update trap state, return mepc:
ux_t RVCSR::trap_mret() {
priv = GETBITS(mstatus, 12, 11);
mstatus &= ~MSTATUS_MPP;
if (priv != PRV_M)
mstatus &= ~MSTATUS_MPRV;
if (mstatus & MSTATUS_MPIE)
mstatus |= MSTATUS_MIE;