From a31349337140a538344968b31bb8e03e71853ffb Mon Sep 17 00:00:00 2001 From: Luke Wren <wren6991@gmail.com> Date: Fri, 22 Mar 2024 00:52:01 +0000 Subject: [PATCH] Add timer and soft IRQ support to rvcpp. Relevant sw_testcases now pass. --- test/sim/rvcpp/Makefile | 2 +- test/sim/rvcpp/include/rv_csr.h | 45 ++++++++++++++-- test/sim/rvcpp/include/rv_mem.h | 93 +++++++++++++++++++++++++++++++-- test/sim/rvcpp/main.cpp | 10 ++-- test/sim/rvcpp/rv_core.cpp | 74 ++++++++++++++++++++------ test/sim/rvcpp/rv_csr.cpp | 30 ++++++++++- 6 files changed, 223 insertions(+), 31 deletions(-) diff --git a/test/sim/rvcpp/Makefile b/test/sim/rvcpp/Makefile index 5a35120..199b758 100644 --- a/test/sim/rvcpp/Makefile +++ b/test/sim/rvcpp/Makefile @@ -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) diff --git a/test/sim/rvcpp/include/rv_csr.h b/test/sim/rvcpp/include/rv_csr.h index 4d41df3..4dd3733 100644 --- a/test/sim/rvcpp/include/rv_csr.h +++ b/test/sim/rvcpp/include/rv_csr.h @@ -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; + } + }; diff --git a/test/sim/rvcpp/include/rv_mem.h b/test/sim/rvcpp/include/rv_mem.h index 49a22e6..cdd81e2 100644 --- a/test/sim/rvcpp/include/rv_mem.h +++ b/test/sim/rvcpp/include/rv_mem.h @@ -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 { diff --git a/test/sim/rvcpp/main.cpp b/test/sim/rvcpp/main.cpp index 5ac4cb4..66c39b8 100644 --- a/test/sim/rvcpp/main.cpp +++ b/test/sim/rvcpp/main.cpp @@ -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; } diff --git a/test/sim/rvcpp/rv_core.cpp b/test/sim/rvcpp/rv_core.cpp index 4a3a6a8..0878e20 100644 --- a/test/sim/rvcpp/rv_core.cpp +++ b/test/sim/rvcpp/rv_core.cpp @@ -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(); } diff --git a/test/sim/rvcpp/rv_csr.cpp b/test/sim/rvcpp/rv_csr.cpp index ca4f3b2..c642bc5 100644 --- a/test/sim/rvcpp/rv_csr.cpp +++ b/test/sim/rvcpp/rv_csr.cpp @@ -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;