Data Flow
CPU State
RISCVCPUState (defined in riscv_cpu_priv.h) holds all CPU context: integer registers reg[32], floating-point registers fp_reg[32], program counter pc, privilege level priv, CSRs, and three TLBs (read, write, code).
struct RISCVCPUState {
RISCVCPUCommonState common;
target_ulong pc;
target_ulong reg[32];
#if FLEN > 0
fp_uint fp_reg[32];
uint32_t fflags;
uint8_t frm;
#endif
uint8_t cur_xlen;
uint8_t priv;
uint8_t fs;
uint8_t mxl;
/* CSRs */
target_ulong mstatus, mtvec, mepc, mcause, mtval, mhartid;
uint32_t misa, mie, mip, medeleg, mideleg, mcounteren;
target_ulong stvec, sscratch, sepc, scause, stval;
target_ulong satp;
/* TLBs */
TLBEntry tlb_read[TLB_SIZE];
TLBEntry tlb_write[TLB_SIZE];
TLBEntry tlb_code[TLB_SIZE];
PhysMemoryMap *mem_map;
};Interpreter Loop
The interpreter is generated from riscv_cpu_template.h by compiling riscv_cpu.c three times with MAX_XLEN=32/64/128. The core function is riscv_cpu_interp_x<XLEN>.
static void no_inline glue(riscv_cpu_interp_x, XLEN)(RISCVCPUState *s,
int n_cycles1)
{
uint32_t opcode, insn, rd, rs1, rs2, funct3;
int32_t imm, cond, err;
target_ulong addr, val, val2;
uint64_t insn_counter_addend;
if (n_cycles1 == 0) return;
insn_counter_addend = s->insn_counter + n_cycles1;
s->n_cycles = n_cycles1;
/* check pending interrupts */
if (unlikely((s->mip & s->mie) != 0)) {
if (raise_interrupt(s)) {
s->n_cycles--;
goto done_interp;
}
}
s->pending_exception = -1;
code_ptr = NULL;
code_end = NULL;
code_to_pc_addend = s->pc;
for(;;) {
if (unlikely(code_ptr >= code_end)) {
/* TLB lookup for code fetch */
addr = s->pc;
tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1);
if (likely(s->tlb_code[tlb_idx].vaddr == (addr & ~PG_MASK))) {
ptr = (uint8_t *)(s->tlb_code[tlb_idx].mem_addend +
(uintptr_t)addr);
} else {
if (unlikely(target_read_insn_slow(s, &ptr, addr)))
goto mmu_exception;
}
code_ptr = ptr;
code_end = ptr + (PG_MASK - 1 - (addr & PG_MASK));
code_to_pc_addend = addr - (uintptr_t)code_ptr;
insn = get_insn32(code_ptr);
} else {
insn = get_insn32(code_ptr);
}
s->n_cycles--;
opcode = insn & 0x7f;
rd = (insn >> 7) & 0x1f;
rs1 = (insn >> 15) & 0x1f;
rs2 = (insn >> 20) & 0x1f;
switch(opcode) {
/* ... opcode handlers ... */
}
}
}TLB Fast Path
Memory accesses use inlined TLB checks. If the virtual page matches, the access is a direct pointer dereference. On miss, target_read_slow or target_write_slow performs the page-table walk.
static inline __exception int target_read_u32(RISCVCPUState *s,
uint32_t *pval,
target_ulong addr)
{
uint32_t tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1);
if (likely(s->tlb_read[tlb_idx].vaddr ==
(addr & ~(PG_MASK & ~3)))) {
*pval = *(uint32_t *)(s->tlb_read[tlb_idx].mem_addend +
(uintptr_t)addr);
} else {
mem_uint_t val;
int ret = target_read_slow(s, &val, addr, 2);
if (ret) return ret;
*pval = val;
}
return 0;
}Interrupts & Exceptions
The CPU checks mip & mie between instruction blocks. If an interrupt is pending and enabled, raise_interrupt() updates mcause, saves pc to mepc, and jumps to mtvec. Exceptions (page faults, illegal instructions) follow a similar path.
Floating Point (SoftFP)
TinyEMU uses its own SoftFP library (softfp.c/h) for bit-exact 32/64/128-bit floating-point emulation. The interpreter calls functions like add_sf64, mul_sf128, etc., updating fflags for rounding/exception status.