Skip to content

Commit 4599b1d

Browse files
authored
Merge pull request #438 from ChinYikMing/mmu
Preliminary support for MMU emulation
2 parents 5e67220 + 6317605 commit 4599b1d

File tree

17 files changed

+1526
-236
lines changed

17 files changed

+1526
-236
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ jobs:
6868
run: |
6969
make -C tests/system/alignment/
7070
make distclean && make ENABLE_EXT_C=0 ENABLE_SYSTEM=1 misalign-in-blk-emu -j$(nproc)
71+
- name: MMU test
72+
run: |
73+
make -C tests/system/mmu/
74+
make distclean && make ENABLE_SYSTEM=1 mmu-test -j$(nproc)
7175
- name: gdbstub test
7276
run: |
7377
make distclean && make ENABLE_GDBSTUB=1 gdbstub-test -j$(nproc)

Makefile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ CFLAGS += $(CFLAGS_NO_CET)
4545

4646
OBJS_EXT :=
4747

48+
ifeq ($(call has, SYSTEM), 1)
49+
OBJS_EXT += system.o
50+
endif
51+
4852
# Integer Multiplication and Division instructions
4953
ENABLE_EXT_M ?= 1
5054
$(call set-feature, EXT_M)
@@ -299,6 +303,16 @@ misalign-in-blk-emu: $(BIN)
299303
exit 1; \
300304
fi;
301305

306+
EXPECTED_mmu = STORE PAGE FAULT TEST PASSED!
307+
mmu-test: $(BIN)
308+
$(Q)$(PRINTF) "Running vm.elf ... "; \
309+
if [ "$(shell $(BIN) tests/system/mmu/vm.elf | tail -n 2)" = "$(strip $(EXPECTED_mmu)) inferior exit code 0" ]; then \
310+
$(call notice, [OK]); \
311+
else \
312+
$(PRINTF) "Failed.\n"; \
313+
exit 1; \
314+
fi;
315+
302316
# Non-trivial demonstration programs
303317
ifeq ($(call has, SDL), 1)
304318
doom_action := (cd $(OUT); ../$(BIN) riscv32/doom)

src/decode.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -907,9 +907,8 @@ static inline bool op_system(rv_insn_t *ir, const uint32_t insn)
907907
default: /* illegal instruction */
908908
return false;
909909
}
910-
if (!csr_is_writable(ir->imm) && ir->rs1 != rv_reg_zero)
911-
return false;
912-
return true;
910+
911+
return csr_is_writable(ir->imm) || (ir->rs1 == rv_reg_zero);
913912
}
914913

915914
/* MISC-MEM: I-type

src/emulate.c

Lines changed: 98 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -41,130 +41,15 @@ extern struct target_ops gdbstub_ops;
4141
#define IF_rs2(i, r) (i->rs2 == rv_reg_##r)
4242
#define IF_imm(i, v) (i->imm == v)
4343

44-
/* RISC-V exception code list */
45-
/* clang-format off */
46-
#define RV_TRAP_LIST \
47-
IIF(RV32_HAS(EXT_C))(, \
48-
_(insn_misaligned, 0) /* Instruction address misaligned */ \
49-
) \
50-
_(illegal_insn, 2) /* Illegal instruction */ \
51-
_(breakpoint, 3) /* Breakpoint */ \
52-
_(load_misaligned, 4) /* Load address misaligned */ \
53-
_(store_misaligned, 6) /* Store/AMO address misaligned */ \
54-
IIF(RV32_HAS(SYSTEM))(, \
55-
_(ecall_M, 11) /* Environment call from M-mode */ \
56-
)
57-
/* clang-format on */
58-
59-
enum {
60-
#define _(type, code) rv_trap_code_##type = code,
61-
RV_TRAP_LIST
62-
#undef _
63-
};
64-
6544
static void rv_trap_default_handler(riscv_t *rv)
6645
{
6746
rv->csr_mepc += rv->compressed ? 2 : 4;
6847
rv->PC = rv->csr_mepc; /* mret */
6948
}
7049

71-
/*
72-
* Trap might occurs during block emulation. For instance, page fault.
73-
* In order to handle trap, we have to escape from block and execute
74-
* registered trap handler. This trap_handler function helps to execute
75-
* the registered trap handler, PC by PC. Once the trap is handled,
76-
* resume the previous execution flow where cause the trap.
77-
*
78-
* Since the system emulation has not yet included in rv32emu, the page
79-
* fault is not practical in current test suite. Instead, we try to
80-
* emulate the misaligned handling in the test suite.
81-
*/
8250
#if RV32_HAS(SYSTEM)
83-
static void trap_handler(riscv_t *rv);
84-
#endif
85-
86-
/* When a trap occurs in M-mode/S-mode, m/stval is either initialized to zero or
87-
* populated with exception-specific details to assist software in managing
88-
* the trap. Otherwise, the implementation never modifies m/stval, although
89-
* software can explicitly write to it. The hardware platform will define
90-
* which exceptions are required to informatively set mtval and which may
91-
* consistently set it to zero.
92-
*
93-
* When a hardware breakpoint is triggered or an exception like address
94-
* misalignment, access fault, or page fault occurs during an instruction
95-
* fetch, load, or store operation, m/stval is updated with the virtual address
96-
* that caused the fault. In the case of an illegal instruction trap, m/stval
97-
* might be updated with the first XLEN or ILEN bits of the offending
98-
* instruction. For all other traps, m/stval is simply set to zero. However,
99-
* it is worth noting that a future standard could redefine how m/stval is
100-
* handled for different types of traps.
101-
*
102-
* For simplicity and clarity, abstracting stval and mtval into a single
103-
* identifier called tval, as both are handled by TRAP_HANDLER_IMPL.
104-
*/
105-
#define TRAP_HANDLER_IMPL(type, code) \
106-
static void rv_trap_##type(riscv_t *rv, uint32_t tval) \
107-
{ \
108-
/* m/stvec (Machine/Supervisor Trap-Vector Base Address Register) \
109-
* m/stvec[MXLEN-1:2]: vector base address \
110-
* m/stvec[1:0] : vector mode \
111-
* m/sepc (Machine/Supervisor Exception Program Counter) \
112-
* m/stval (Machine/Supervisor Trap Value Register) \
113-
* m/scause (Machine/Supervisor Cause Register): store exception code \
114-
* m/sstatus (Machine/Supervisor Status Register): keep track of and \
115-
* controls the hart’s current operating state \
116-
*/ \
117-
uint32_t base; \
118-
uint32_t mode; \
119-
/* user or supervisor */ \
120-
if (RV_PRIV_IS_U_OR_S_MODE()) { \
121-
const uint32_t sstatus_sie = \
122-
(rv->csr_sstatus & SSTATUS_SIE) >> SSTATUS_SIE_SHIFT; \
123-
rv->csr_sstatus |= (sstatus_sie << SSTATUS_SPIE_SHIFT); \
124-
rv->csr_sstatus &= ~(SSTATUS_SIE); \
125-
rv->csr_sstatus |= (rv->priv_mode << SSTATUS_SPP_SHIFT); \
126-
rv->priv_mode = RV_PRIV_S_MODE; \
127-
base = rv->csr_stvec & ~0x3; \
128-
mode = rv->csr_stvec & 0x3; \
129-
rv->csr_sepc = rv->PC; \
130-
rv->csr_stval = tval; \
131-
rv->csr_scause = code; \
132-
} else { /* machine */ \
133-
const uint32_t mstatus_mie = \
134-
(rv->csr_mstatus & MSTATUS_MIE) >> MSTATUS_MIE_SHIFT; \
135-
rv->csr_mstatus |= (mstatus_mie << MSTATUS_MPIE_SHIFT); \
136-
rv->csr_mstatus &= ~(MSTATUS_MIE); \
137-
rv->csr_mstatus |= (rv->priv_mode << MSTATUS_MPP_SHIFT); \
138-
rv->priv_mode = RV_PRIV_M_MODE; \
139-
base = rv->csr_mtvec & ~0x3; \
140-
mode = rv->csr_mtvec & 0x3; \
141-
rv->csr_mepc = rv->PC; \
142-
rv->csr_mtval = tval; \
143-
rv->csr_mcause = code; \
144-
if (!rv->csr_mtvec) { /* in case CSR is not configured */ \
145-
rv_trap_default_handler(rv); \
146-
return; \
147-
} \
148-
} \
149-
switch (mode) { \
150-
/* DIRECT: All traps set PC to base */ \
151-
case 0: \
152-
rv->PC = base; \
153-
break; \
154-
/* VECTORED: Asynchronous traps set PC to base + 4 * code */ \
155-
case 1: \
156-
/* MSB of code is used to indicate whether the trap is interrupt \
157-
* or exception, so it is not considered as the 'real' code */ \
158-
rv->PC = base + 4 * (code & MASK(31)); \
159-
break; \
160-
} \
161-
IIF(RV32_HAS(SYSTEM))(if (rv->is_trapped) trap_handler(rv);, ) \
162-
}
163-
164-
/* RISC-V exception handlers */
165-
#define _(type, code) TRAP_HANDLER_IMPL(type, code)
166-
RV_TRAP_LIST
167-
#undef _
51+
static void __trap_handler(riscv_t *rv);
52+
#endif /* RV32_HAS(SYSTEM) */
16853

16954
/* wrap load/store and insn misaligned handler
17055
* @mask_or_pc: mask for load/store and pc for insn misaligned handler.
@@ -180,8 +65,8 @@ RV_TRAP_LIST
18065
rv->compressed = compress; \
18166
rv->csr_cycle = cycle; \
18267
rv->PC = PC; \
183-
IIF(RV32_HAS(SYSTEM))(rv->is_trapped = true, ); \
184-
rv_trap_##type##_misaligned(rv, IIF(IO)(addr, mask_or_pc)); \
68+
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, type##_MISALIGNED, \
69+
IIF(IO)(addr, mask_or_pc)); \
18570
return false; \
18671
}
18772

@@ -531,8 +416,8 @@ static bool do_fuse3(riscv_t *rv, rv_insn_t *ir, uint64_t cycle, uint32_t PC)
531416
*/
532417
for (int i = 0; i < ir->imm2; i++) {
533418
uint32_t addr = rv->X[fuse[i].rs1] + fuse[i].imm;
534-
RV_EXC_MISALIGN_HANDLER(3, store, false, 1);
535-
rv->io.mem_write_w(addr, rv->X[fuse[i].rs2]);
419+
RV_EXC_MISALIGN_HANDLER(3, STORE, false, 1);
420+
rv->io.mem_write_w(rv, addr, rv->X[fuse[i].rs2]);
536421
}
537422
PC += ir->imm2 * 4;
538423
if (unlikely(RVOP_NO_NEXT(ir))) {
@@ -555,8 +440,8 @@ static bool do_fuse4(riscv_t *rv, rv_insn_t *ir, uint64_t cycle, uint32_t PC)
555440
*/
556441
for (int i = 0; i < ir->imm2; i++) {
557442
uint32_t addr = rv->X[fuse[i].rs1] + fuse[i].imm;
558-
RV_EXC_MISALIGN_HANDLER(3, load, false, 1);
559-
rv->X[fuse[i].rd] = rv->io.mem_read_w(addr);
443+
RV_EXC_MISALIGN_HANDLER(3, LOAD, false, 1);
444+
rv->X[fuse[i].rd] = rv->io.mem_read_w(rv, addr);
560445
}
561446
PC += ir->imm2 * 4;
562447
if (unlikely(RVOP_NO_NEXT(ir))) {
@@ -666,12 +551,12 @@ static void block_translate(riscv_t *rv, block_t *block)
666551
prev_ir->next = ir;
667552

668553
/* fetch the next instruction */
669-
const uint32_t insn = rv->io.mem_ifetch(block->pc_end);
554+
const uint32_t insn = rv->io.mem_ifetch(rv, block->pc_end);
670555

671556
/* decode the instruction */
672557
if (!rv_decode(ir, insn)) {
673558
rv->compressed = is_compressed(insn);
674-
rv_trap_illegal_insn(rv, insn);
559+
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, ILLEGAL_INSN, insn);
675560
break;
676561
}
677562
ir->impl = dispatch_table[ir->opcode];
@@ -1122,15 +1007,14 @@ void rv_step(void *arg)
11221007
}
11231008

11241009
#if RV32_HAS(SYSTEM)
1125-
static void trap_handler(riscv_t *rv)
1010+
static void __trap_handler(riscv_t *rv)
11261011
{
11271012
rv_insn_t *ir = mpool_alloc(rv->block_ir_mp);
11281013
assert(ir);
11291014

1130-
/* set to false by sret/mret implementation */
1131-
uint32_t insn;
1015+
/* set to false by sret implementation */
11321016
while (rv->is_trapped && !rv_has_halted(rv)) {
1133-
insn = rv->io.mem_ifetch(rv->PC);
1017+
uint32_t insn = rv->io.mem_ifetch(rv, rv->PC);
11341018
assert(insn);
11351019

11361020
rv_decode(ir, insn);
@@ -1139,12 +1023,94 @@ static void trap_handler(riscv_t *rv)
11391023
ir->impl(rv, ir, rv->csr_cycle, rv->PC);
11401024
}
11411025
}
1142-
#endif
1026+
#endif /* RV32_HAS(SYSTEM) */
1027+
1028+
/* When a trap occurs in M-mode/S-mode, m/stval is either initialized to zero or
1029+
* populated with exception-specific details to assist software in managing
1030+
* the trap. Otherwise, the implementation never modifies m/stval, although
1031+
* software can explicitly write to it. The hardware platform will define
1032+
* which exceptions are required to informatively set mtval and which may
1033+
* consistently set it to zero.
1034+
*
1035+
* When a hardware breakpoint is triggered or an exception like address
1036+
* misalignment, access fault, or page fault occurs during an instruction
1037+
* fetch, load, or store operation, m/stval is updated with the virtual address
1038+
* that caused the fault. In the case of an illegal instruction trap, m/stval
1039+
* might be updated with the first XLEN or ILEN bits of the offending
1040+
* instruction. For all other traps, m/stval is simply set to zero. However,
1041+
* it is worth noting that a future standard could redefine how m/stval is
1042+
* handled for different types of traps.
1043+
*
1044+
*/
1045+
static void _trap_handler(riscv_t *rv)
1046+
{
1047+
/* m/stvec (Machine/Supervisor Trap-Vector Base Address Register)
1048+
* m/stvec[MXLEN-1:2]: vector base address
1049+
* m/stvec[1:0] : vector mode
1050+
* m/sepc (Machine/Supervisor Exception Program Counter)
1051+
* m/stval (Machine/Supervisor Trap Value Register)
1052+
* m/scause (Machine/Supervisor Cause Register): store exception code
1053+
* m/sstatus (Machine/Supervisor Status Register): keep track of and
1054+
* controls the hart’s current operating state
1055+
*
1056+
* m/stval and m/scause are set in SET_CAUSE_AND_TVAL_THEN_TRAP
1057+
*/
1058+
uint32_t base;
1059+
uint32_t mode;
1060+
uint32_t cause;
1061+
/* user or supervisor */
1062+
if (RV_PRIV_IS_U_OR_S_MODE()) {
1063+
const uint32_t sstatus_sie =
1064+
(rv->csr_sstatus & SSTATUS_SIE) >> SSTATUS_SIE_SHIFT;
1065+
rv->csr_sstatus |= (sstatus_sie << SSTATUS_SPIE_SHIFT);
1066+
rv->csr_sstatus &= ~(SSTATUS_SIE);
1067+
rv->csr_sstatus |= (rv->priv_mode << SSTATUS_SPP_SHIFT);
1068+
rv->priv_mode = RV_PRIV_S_MODE;
1069+
base = rv->csr_stvec & ~0x3;
1070+
mode = rv->csr_stvec & 0x3;
1071+
cause = rv->csr_scause;
1072+
rv->csr_sepc = rv->PC;
1073+
} else { /* machine */
1074+
const uint32_t mstatus_mie =
1075+
(rv->csr_mstatus & MSTATUS_MIE) >> MSTATUS_MIE_SHIFT;
1076+
rv->csr_mstatus |= (mstatus_mie << MSTATUS_MPIE_SHIFT);
1077+
rv->csr_mstatus &= ~(MSTATUS_MIE);
1078+
rv->csr_mstatus |= (rv->priv_mode << MSTATUS_MPP_SHIFT);
1079+
rv->priv_mode = RV_PRIV_M_MODE;
1080+
base = rv->csr_mtvec & ~0x3;
1081+
mode = rv->csr_mtvec & 0x3;
1082+
cause = rv->csr_mcause;
1083+
rv->csr_mepc = rv->PC;
1084+
if (!rv->csr_mtvec) { /* in case CSR is not configured */
1085+
rv_trap_default_handler(rv);
1086+
return;
1087+
}
1088+
}
1089+
switch (mode) {
1090+
/* DIRECT: All traps set PC to base */
1091+
case 0:
1092+
rv->PC = base;
1093+
break;
1094+
/* VECTORED: Asynchronous traps set PC to base + 4 * code */
1095+
case 1:
1096+
/* MSB of code is used to indicate whether the trap is interrupt
1097+
* or exception, so it is not considered as the 'real' code */
1098+
rv->PC = base + 4 * (cause & MASK(31));
1099+
break;
1100+
}
1101+
IIF(RV32_HAS(SYSTEM))(if (rv->is_trapped) __trap_handler(rv);, )
1102+
}
1103+
1104+
void trap_handler(riscv_t *rv)
1105+
{
1106+
assert(rv);
1107+
_trap_handler(rv);
1108+
}
11431109

11441110
void ebreak_handler(riscv_t *rv)
11451111
{
11461112
assert(rv);
1147-
rv_trap_breakpoint(rv, rv->PC);
1113+
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, BREAKPOINT, rv->PC);
11481114
}
11491115

11501116
void ecall_handler(riscv_t *rv)
@@ -1154,7 +1120,7 @@ void ecall_handler(riscv_t *rv)
11541120
syscall_handler(rv);
11551121
rv->PC += 4;
11561122
#else
1157-
rv_trap_ecall_M(rv, 0);
1123+
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, ECALL_M, 0);
11581124
syscall_handler(rv);
11591125
#endif
11601126
}

src/feature.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,10 @@
6363
#define RV32_FEATURE_T2C 0
6464
#endif
6565

66+
/* System */
67+
#ifndef RV32_FEATURE_SYSTEM
68+
#define RV32_FEATURE_SYSTEM 0
69+
#endif
70+
6671
/* Feature test macro */
6772
#define RV32_HAS(x) RV32_FEATURE_##x

src/gdbstub.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ static int rv_read_mem(void *args, size_t addr, size_t len, void *val)
5555
* an invalid address. We may have to do error handling in the
5656
* mem_read_* function directly.
5757
*/
58-
*((uint8_t *) val + i) = rv->io.mem_read_b(addr + i);
58+
*((uint8_t *) val + i) = rv->io.mem_read_b(rv, addr + i);
5959
}
6060

6161
return err;
@@ -66,7 +66,7 @@ static int rv_write_mem(void *args, size_t addr, size_t len, void *val)
6666
riscv_t *rv = (riscv_t *) args;
6767

6868
for (size_t i = 0; i < len; i++)
69-
rv->io.mem_write_b(addr + i, *((uint8_t *) val + i));
69+
rv->io.mem_write_b(rv, addr + i, *((uint8_t *) val + i));
7070

7171
return 0;
7272
}

0 commit comments

Comments
 (0)