Add timer and soft IRQ support to rvcpp. Relevant sw_testcases now pass.
This commit is contained in:
parent
b1be56fe94
commit
a313493371
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue