Skip to content

Commit b50f948

Browse files
authored
Merge pull request #99 from sysprog21/mmu-caching
Implement MMU translation caching with 2-way load
2 parents 44e3e10 + 9079095 commit b50f948

File tree

3 files changed

+192
-23
lines changed

3 files changed

+192
-23
lines changed

main.c

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
#include <sys/mman.h>
99
#include <sys/stat.h>
1010
#include <unistd.h>
11+
#ifdef MMU_CACHE_STATS
12+
#include <sys/time.h>
13+
#endif
1114

1215
#include "device.h"
1316
#include "mini-gdbstub/include/gdbstub.h"
@@ -395,11 +398,11 @@ static inline sbi_ret_t handle_sbi_ecall_RFENCE(hart_t *hart, int32_t fid)
395398
hart_mask_base = (uint64_t) hart->x_regs[RV_R_A1];
396399
if (hart_mask_base == 0xFFFFFFFFFFFFFFFF) {
397400
for (uint32_t i = 0; i < hart->vm->n_hart; i++) {
398-
hart->vm->hart[i]->cache_fetch.n_pages = 0xFFFFFFFF;
401+
mmu_invalidate(hart->vm->hart[i]);
399402
}
400403
} else {
401404
for (int i = hart_mask_base; hart_mask; hart_mask >>= 1, i++) {
402-
hart->vm->hart[i]->cache_fetch.n_pages = 0xFFFFFFFF;
405+
mmu_invalidate(hart->vm->hart[i]);
403406
}
404407
}
405408
return (sbi_ret_t){SBI_SUCCESS, 0};
@@ -792,9 +795,57 @@ static int semu_step(emu_state_t *emu)
792795
return 0;
793796
}
794797

798+
#ifdef MMU_CACHE_STATS
799+
static void print_mmu_cache_stats(vm_t *vm)
800+
{
801+
fprintf(stderr, "\n=== MMU Cache Statistics ===\n");
802+
for (uint32_t i = 0; i < vm->n_hart; i++) {
803+
hart_t *hart = vm->hart[i];
804+
uint64_t fetch_total =
805+
hart->cache_fetch.hits + hart->cache_fetch.misses;
806+
807+
/* Combine 2-way load cache statistics */
808+
uint64_t load_hits =
809+
hart->cache_load[0].hits + hart->cache_load[1].hits;
810+
uint64_t load_misses =
811+
hart->cache_load[0].misses + hart->cache_load[1].misses;
812+
uint64_t load_total = load_hits + load_misses;
813+
814+
uint64_t store_total =
815+
hart->cache_store.hits + hart->cache_store.misses;
816+
817+
fprintf(stderr, "\nHart %u:\n", i);
818+
fprintf(stderr, " Fetch: %12llu hits, %12llu misses",
819+
hart->cache_fetch.hits, hart->cache_fetch.misses);
820+
if (fetch_total > 0)
821+
fprintf(stderr, " (%.2f%% hit rate)",
822+
100.0 * hart->cache_fetch.hits / fetch_total);
823+
fprintf(stderr, "\n");
824+
825+
fprintf(stderr, " Load: %12llu hits, %12llu misses (2-way)",
826+
load_hits, load_misses);
827+
if (load_total > 0)
828+
fprintf(stderr, " (%.2f%% hit rate)",
829+
100.0 * load_hits / load_total);
830+
fprintf(stderr, "\n");
831+
832+
fprintf(stderr, " Store: %12llu hits, %12llu misses",
833+
hart->cache_store.hits, hart->cache_store.misses);
834+
if (store_total > 0)
835+
fprintf(stderr, " (%.2f%% hit rate)",
836+
100.0 * hart->cache_store.hits / store_total);
837+
fprintf(stderr, "\n");
838+
}
839+
}
840+
#endif
841+
795842
static int semu_run(emu_state_t *emu)
796843
{
797844
int ret;
845+
#ifdef MMU_CACHE_STATS
846+
struct timeval start_time, current_time;
847+
gettimeofday(&start_time, NULL);
848+
#endif
798849

799850
/* Emulate */
800851
while (!emu->stopped) {
@@ -829,6 +880,23 @@ static int semu_run(emu_state_t *emu)
829880
ret = semu_step(emu);
830881
if (ret)
831882
return ret;
883+
#ifdef MMU_CACHE_STATS
884+
/* Exit after running for 15 seconds to collect statistics */
885+
gettimeofday(&current_time, NULL);
886+
long elapsed_sec = current_time.tv_sec - start_time.tv_sec;
887+
long elapsed_usec = current_time.tv_usec - start_time.tv_usec;
888+
if (elapsed_usec < 0) {
889+
elapsed_sec--;
890+
elapsed_usec += 1000000;
891+
}
892+
long elapsed = elapsed_sec + (elapsed_usec > 0 ? 1 : 0);
893+
if (elapsed >= 15) {
894+
fprintf(stderr,
895+
"\n[MMU_CACHE_STATS] Reached 15 second time limit, "
896+
"exiting...\n");
897+
return 0;
898+
}
899+
#endif
832900
}
833901
}
834902

@@ -960,7 +1028,13 @@ int main(int argc, char **argv)
9601028
return ret;
9611029

9621030
if (emu.debug)
963-
return semu_run_debug(&emu);
1031+
ret = semu_run_debug(&emu);
1032+
else
1033+
ret = semu_run(&emu);
1034+
1035+
#ifdef MMU_CACHE_STATS
1036+
print_mmu_cache_stats(&emu.vm);
1037+
#endif
9641038

965-
return semu_run(&emu);
1039+
return ret;
9661040
}

riscv.c

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@
55
#include "riscv.h"
66
#include "riscv_private.h"
77

8+
#if !defined(__GNUC__) && !defined(__clang__)
9+
/* Portable parity implementation for non-GCC/Clang compilers */
10+
static inline unsigned int __builtin_parity(unsigned int x)
11+
{
12+
x ^= x >> 16;
13+
x ^= x >> 8;
14+
x ^= x >> 4;
15+
x ^= x >> 2;
16+
x ^= x >> 1;
17+
return x & 1;
18+
}
19+
#endif
20+
821
/* Return the string representation of an error code identifier */
922
static const char *vm_error_str(vm_error_t err)
1023
{
@@ -169,9 +182,12 @@ static inline uint32_t read_rs2(const hart_t *vm, uint32_t insn)
169182

170183
/* virtual addressing */
171184

172-
static void mmu_invalidate(hart_t *vm)
185+
void mmu_invalidate(hart_t *vm)
173186
{
174187
vm->cache_fetch.n_pages = 0xFFFFFFFF;
188+
vm->cache_load[0].n_pages = 0xFFFFFFFF;
189+
vm->cache_load[1].n_pages = 0xFFFFFFFF;
190+
vm->cache_store.n_pages = 0xFFFFFFFF;
175191
}
176192

177193
/* Pre-verify the root page table to minimize page table access during
@@ -284,6 +300,9 @@ static void mmu_fetch(hart_t *vm, uint32_t addr, uint32_t *value)
284300
{
285301
uint32_t vpn = addr >> RV_PAGE_SHIFT;
286302
if (unlikely(vpn != vm->cache_fetch.n_pages)) {
303+
#ifdef MMU_CACHE_STATS
304+
vm->cache_fetch.misses++;
305+
#endif
287306
mmu_translate(vm, &addr, (1 << 3), (1 << 6), false, RV_EXC_FETCH_FAULT,
288307
RV_EXC_FETCH_PFAULT);
289308
if (vm->error)
@@ -295,6 +314,11 @@ static void mmu_fetch(hart_t *vm, uint32_t addr, uint32_t *value)
295314
vm->cache_fetch.n_pages = vpn;
296315
vm->cache_fetch.page_addr = page_addr;
297316
}
317+
#ifdef MMU_CACHE_STATS
318+
else {
319+
vm->cache_fetch.hits++;
320+
}
321+
#endif
298322
*value = vm->cache_fetch.page_addr[(addr >> 2) & MASK(RV_PAGE_SHIFT - 2)];
299323
}
300324

@@ -304,17 +328,41 @@ static void mmu_load(hart_t *vm,
304328
uint32_t *value,
305329
bool reserved)
306330
{
307-
mmu_translate(vm, &addr, (1 << 1) | (vm->sstatus_mxr ? (1 << 3) : 0),
308-
(1 << 6), vm->sstatus_sum && vm->s_mode, RV_EXC_LOAD_FAULT,
309-
RV_EXC_LOAD_PFAULT);
310-
if (vm->error)
311-
return;
312-
vm->mem_load(vm, addr, width, value);
331+
uint32_t vpn = addr >> RV_PAGE_SHIFT;
332+
uint32_t phys_addr;
333+
/* 2-entry direct-mapped cache: use parity hash to select entry */
334+
uint32_t index = __builtin_parity(vpn) & 0x1;
335+
336+
if (unlikely(vpn != vm->cache_load[index].n_pages)) {
337+
/* Cache miss: do full translation */
338+
#ifdef MMU_CACHE_STATS
339+
vm->cache_load[index].misses++;
340+
#endif
341+
phys_addr = addr;
342+
mmu_translate(vm, &phys_addr,
343+
(1 << 1) | (vm->sstatus_mxr ? (1 << 3) : 0), (1 << 6),
344+
vm->sstatus_sum && vm->s_mode, RV_EXC_LOAD_FAULT,
345+
RV_EXC_LOAD_PFAULT);
346+
if (vm->error)
347+
return;
348+
/* Cache physical page number (not a pointer) */
349+
vm->cache_load[index].n_pages = vpn;
350+
vm->cache_load[index].phys_ppn = phys_addr >> RV_PAGE_SHIFT;
351+
} else {
352+
/* Cache hit: reconstruct physical address from cached PPN */
353+
#ifdef MMU_CACHE_STATS
354+
vm->cache_load[index].hits++;
355+
#endif
356+
phys_addr = (vm->cache_load[index].phys_ppn << RV_PAGE_SHIFT) |
357+
(addr & MASK(RV_PAGE_SHIFT));
358+
}
359+
360+
vm->mem_load(vm, phys_addr, width, value);
313361
if (vm->error)
314362
return;
315363

316364
if (unlikely(reserved))
317-
vm->lr_reservation = addr | 1;
365+
vm->lr_reservation = phys_addr | 1;
318366
}
319367

320368
static bool mmu_store(hart_t *vm,
@@ -323,23 +371,43 @@ static bool mmu_store(hart_t *vm,
323371
uint32_t value,
324372
bool cond)
325373
{
326-
mmu_translate(vm, &addr, (1 << 2), (1 << 6) | (1 << 7),
327-
vm->sstatus_sum && vm->s_mode, RV_EXC_STORE_FAULT,
328-
RV_EXC_STORE_PFAULT);
329-
if (vm->error)
330-
return false;
374+
uint32_t vpn = addr >> RV_PAGE_SHIFT;
375+
uint32_t phys_addr;
376+
377+
if (unlikely(vpn != vm->cache_store.n_pages)) {
378+
/* Cache miss: do full translation */
379+
#ifdef MMU_CACHE_STATS
380+
vm->cache_store.misses++;
381+
#endif
382+
phys_addr = addr;
383+
mmu_translate(vm, &phys_addr, (1 << 2), (1 << 6) | (1 << 7),
384+
vm->sstatus_sum && vm->s_mode, RV_EXC_STORE_FAULT,
385+
RV_EXC_STORE_PFAULT);
386+
if (vm->error)
387+
return false;
388+
/* Cache physical page number (not a pointer) */
389+
vm->cache_store.n_pages = vpn;
390+
vm->cache_store.phys_ppn = phys_addr >> RV_PAGE_SHIFT;
391+
} else {
392+
/* Cache hit: reconstruct physical address from cached PPN */
393+
#ifdef MMU_CACHE_STATS
394+
vm->cache_store.hits++;
395+
#endif
396+
phys_addr = (vm->cache_store.phys_ppn << RV_PAGE_SHIFT) |
397+
(addr & MASK(RV_PAGE_SHIFT));
398+
}
331399

332400
if (unlikely(cond)) {
333-
if ((vm->lr_reservation != (addr | 1)))
401+
if ((vm->lr_reservation != (phys_addr | 1)))
334402
return false;
335403
}
336404

337405
for (uint32_t i = 0; i < vm->vm->n_hart; i++) {
338406
if (unlikely(vm->vm->hart[i]->lr_reservation & 1) &&
339-
(vm->vm->hart[i]->lr_reservation & ~3) == (addr & ~3))
407+
(vm->vm->hart[i]->lr_reservation & ~3) == (phys_addr & ~3))
340408
vm->vm->hart[i]->lr_reservation = 0;
341409
}
342-
vm->mem_store(vm, addr, width, value);
410+
vm->mem_store(vm, phys_addr, width, value);
343411
return true;
344412
}
345413

@@ -513,13 +581,19 @@ static void csr_write(hart_t *vm, uint16_t addr, uint32_t value)
513581
}
514582

515583
switch (addr) {
516-
case RV_CSR_SSTATUS:
584+
case RV_CSR_SSTATUS: {
585+
bool old_sum = vm->sstatus_sum;
586+
bool old_mxr = vm->sstatus_mxr;
517587
vm->sstatus_sie = (value & (1 << (1))) != 0;
518588
vm->sstatus_spie = (value & (1 << (5))) != 0;
519589
vm->sstatus_spp = (value & (1 << (8))) != 0;
520590
vm->sstatus_sum = (value & (1 << (18))) != 0;
521591
vm->sstatus_mxr = (value & (1 << (19))) != 0;
592+
/* Invalidate load/store TLB if SUM or MXR changed */
593+
if (vm->sstatus_sum != old_sum || vm->sstatus_mxr != old_mxr)
594+
mmu_invalidate(vm);
522595
break;
596+
}
523597
case RV_CSR_SIE:
524598
value &= SIE_MASK;
525599
vm->sie = value;

riscv.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,25 @@ typedef enum {
3131
ERR_USER, /**< user-specific error */
3232
} vm_error_t;
3333

34+
/* Instruction fetch cache: stores host memory pointers for direct access */
3435
typedef struct {
3536
uint32_t n_pages;
3637
uint32_t *page_addr;
37-
} mmu_cache_t;
38+
#ifdef MMU_CACHE_STATS
39+
uint64_t hits;
40+
uint64_t misses;
41+
#endif
42+
} mmu_fetch_cache_t;
43+
44+
/* Load/store cache: stores physical page numbers (not pointers) */
45+
typedef struct {
46+
uint32_t n_pages;
47+
uint32_t phys_ppn; /* Physical page number */
48+
#ifdef MMU_CACHE_STATS
49+
uint64_t hits;
50+
uint64_t misses;
51+
#endif
52+
} mmu_addr_cache_t;
3853

3954
/* To use the emulator, start by initializing a hart_t object with zero values,
4055
* invoke vm_init(), and set the required environment-supplied callbacks. You
@@ -85,7 +100,10 @@ struct __hart_internal {
85100
*/
86101
uint32_t exc_cause, exc_val;
87102

88-
mmu_cache_t cache_fetch;
103+
mmu_fetch_cache_t cache_fetch;
104+
/* 2-entry direct-mapped with hash-based indexing */
105+
mmu_addr_cache_t cache_load[2];
106+
mmu_addr_cache_t cache_store;
89107

90108
/* Supervisor state */
91109
bool s_mode;
@@ -160,3 +178,6 @@ void hart_trap(hart_t *vm);
160178

161179
/* Return a readable description for a RISC-V exception cause */
162180
void vm_error_report(const hart_t *vm);
181+
182+
/* Invalidate all MMU translation caches (fetch, load, store) */
183+
void mmu_invalidate(hart_t *vm);

0 commit comments

Comments
 (0)