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;