diff --git a/bin/wsim b/bin/wsim index e0a20a283c..66155577ac 100755 --- a/bin/wsim +++ b/bin/wsim @@ -192,6 +192,10 @@ def isDesignUpToDate(target): return True +def targetMtime(target): + return target.stat().st_mtime if target.exists() else None + + def runSim(args, flags, prefix): if args.sim == "questa": runQuesta(args, flags, prefix) @@ -220,8 +224,12 @@ def runQuesta(args, flags, prefix): # Phase 1: Compile (only once per config/flags/params/defines combo) print(f"Running Questa on {args.config} {args.testsuite}") wkdir_path = WALLY / "sim" / "questa" / wkdir + target = wkdir_path / "testbenchopt" + target_mtime = targetMtime(target) + needs_compile = target_mtime is None or not isDesignUpToDate(target) with compileLock("questa", f"{args.config}_{args.tb}{'_' + bhash if bhash else ''}"): - if not isDesignUpToDate(wkdir_path / "testbenchopt"): + current_mtime = targetMtime(target) + if needs_compile and current_mtime == target_mtime: compile_cmd = f'do wally-compile.do {args.config} {args.tb} {wkdir} {compile_flags}' compile_cmd = f'cd {sim_dir}; {prefix} vsim -c -do "{compile_cmd}"' if os.system(compile_cmd): @@ -252,8 +260,11 @@ def runVerilator(args): f' DEFINE_ARGS="{args.define}"' f' BUILD_HASH="{bhash}"' ) + target_mtime = targetMtime(binary) + needs_compile = target_mtime is None or not isDesignUpToDate(binary) with compileLock("verilator", f"{args.config}_{args.tb}{'_' + bhash if bhash else ''}"): - if not isDesignUpToDate(binary): + current_mtime = targetMtime(binary) + if needs_compile and current_mtime == target_mtime: if os.system(compile_cmd): return @@ -283,8 +294,11 @@ def runVCS(args, flags, prefix): compile_flags += f' --define "{args.define}"' # Phase 1: Compile (only once per config/flags/params/defines combo) + target_mtime = targetMtime(binary) + needs_compile = target_mtime is None or not isDesignUpToDate(binary) with compileLock("vcs", f"{args.config}_{args.tb}{'_' + bhash if bhash else ''}"): - if not isDesignUpToDate(binary): + current_mtime = targetMtime(binary) + if needs_compile and current_mtime == target_mtime: compile_cmd = f'cd {sim_dir}; {prefix} ./run_vcs compile {args.config} {args.testsuite} --wkdir {wkdir} {compile_flags}' if os.system(compile_cmd): return diff --git a/testbench/testbench.sv b/testbench/testbench.sv index 4f43496570..bcf1fcdaee 100644 --- a/testbench/testbench.sv +++ b/testbench/testbench.sv @@ -226,6 +226,10 @@ module testbench; "arch64pmp": if (P.PMP_ENTRIES > 0) tests = arch64pmp; "arch64vm_sv39": if (P.SV39_SUPPORTED) tests = arch64vm_sv39; "arch64vm_sv48": if (P.SV48_SUPPORTED) tests = arch64vm_sv48; + "arch64vm_sv48_a": if (P.SV48_SUPPORTED) tests = arch64vm_sv48_a; + "arch64vm_sv48_b": if (P.SV48_SUPPORTED) tests = arch64vm_sv48_b; + "arch64vm_sv39_isolate": if (P.SV39_SUPPORTED) tests = arch64vm_sv39_isolate; + "arch64vm_sv48_mxr_isolate": if (P.SV48_SUPPORTED) tests = arch64vm_sv48_mxr_isolate; "arch64vm_sv57": if (P.SV57_SUPPORTED) tests = arch64vm_sv57; endcase end else begin // RV32 diff --git a/testbench/tests.vh b/testbench/tests.vh index 2294b7a4f6..6ff573f090 100644 --- a/testbench/tests.vh +++ b/testbench/tests.vh @@ -55,6 +55,8 @@ string coverage64gc[] = '{ "tlbNAPOT", "tlbASID", "tlbGLB", + "tlbGLBASID", + "tlbGLBHIT", "tlbMP", "tlbGP", "tlbTP", @@ -73,7 +75,11 @@ string coverage64gc[] = '{ "pmppriority", "pmpcbo", "pmpadrdecs", - "btbthrash" + "btbthrash", + "fpuReservedRM", + "decompReserved", + "pmpTOR7", + "cacheInval" }; string buildroot[] = '{ @@ -446,6 +452,28 @@ string arch64vm_sv48[] = '{ "rv64i_m/vm_pmp/src/sv48/sv48_pmp_on_pte_U_mode.S" }; +string arch64vm_sv48_a[] = '{ + `RISCVARCHTEST, + "rv64i_m/vm_sv48/src/sv48_res_global_pte_U_mode.S", + "rv64i_m/vm_sv48/src/sv48_pte_reserved_field_S_mode.S" +}; + +string arch64vm_sv48_b[] = '{ + `RISCVARCHTEST, + "rv64i_m/vm_sv48/src/sv48_pte_reserved_field_S_mode.S", + "rv64i_m/vm_sv48/src/sv48_res_global_pte_U_mode.S" +}; + +string arch64vm_sv39_isolate[] = '{ + `RISCVARCHTEST, + "rv64i_m/vm_sv39/src/vm_VA_all_zeros_S_mode.S" +}; + +string arch64vm_sv48_mxr_isolate[] = '{ + `RISCVARCHTEST, + "rv64i_m/vm_sv48/src/sv48_mxr_S_mode.S" +}; + string arch64vm_sv57[] = '{ `RISCVARCHTEST, //"rv64i_m/vm_sv57/src/sv57_A_and_D_S_mode.S", // Disable until fixed; Might be due to Issue#1538 ***TODO: Zain diff --git a/tests/coverage/cacheInval.S b/tests/coverage/cacheInval.S new file mode 100644 index 0000000000..b7cf82e460 --- /dev/null +++ b/tests/coverage/cacheInval.S @@ -0,0 +1,113 @@ +/////////////////////////////////////////// +// cacheInval.S +// +// Written: David_Harris@hmc.edu 29 May 2026 +// +// Purpose: Coverage test for the shared cache invalidate path. +// Targets the FEC condition (InvalidateCache & ~InvalidateFlushStage) +// in src/cache/cacheLRU.sv (~140), cachefsm.sv (~128), cacheway.sv (~155). +// +// For the instruction cache, InvalidateCache is driven by InvalidateICacheM +// (asserted by fence.i while it is in the Memory stage) and InvalidateFlushStage +// is driven by FlushW. The previously uncovered FEC row is InvalidateFlushStage_1: +// InvalidateCache=1 while InvalidateFlushStage=1. A plain fence.i only flushes +// D/E/M (CSRWriteFenceM) and never asserts FlushW, so it always hits the _0 row. +// FlushW is asserted by FlushWCause = TrapM. A trap (machine timer interrupt) is +// asynchronous and attaches to whatever instruction occupies the Memory stage when +// it is taken; if that instruction is a fence.i, then InvalidateICacheM=1 and +// TrapM=1 (hence FlushW=1) in the same cycle, producing the unhit polarity. +// +// Strategy: arm the machine timer to fire very soon, then execute a long burst of +// back-to-back fence.i instructions so that the interrupt is overwhelmingly likely +// to be recognized while a fence.i sits in the Memory stage. The fence.i burst is +// preceded by cache priming (loads + branches) so the icache has valid lines whose +// LRU/valid state is invalidated, and the burst is long enough to absorb timer +// jitter. +// +// A component of the CORE-V-WALLY configurable RISC-V project. +// https://github.com/openhwgroup/cvw +// +// Copyright (C) 2021-23 Harvey Mudd College & Oklahoma State University +// +// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +// +// Licensed under the Solderpad Hardware License v 2.1 (the “License”); you may not use this file +// except in compliance with the License, or, at your option, the Apache License version 2.0. You +// may obtain a copy of the License at +// +// https://solderpad.org/licenses/SHL-2.1/ +// +// Unless required by applicable law or agreed to in writing, any work distributed under the +// License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific language governing permissions +// and limitations under the License. +//////////////////////////////////////////////////////////////////////////////////////////////// + +#include "WALLY-init-lib.h" + +main: + // Prime the instruction cache by executing code spread across several + // cache lines, then a fence.i, repeated, so the invalidate path operates + // on valid/LRU state (covers the InvalidateCache=1 / InvalidateFlushStage=0 rows). + jal primer + fence.i + jal primer + fence.i + fence.i // back-to-back fence.i + + // Prime the data cache with valid lines (for context; D$ InvalidateCache is tied 0). + li t0, 0x80108000 + sd zero, 0(t0) + ld t1, 0(t0) + li t0, 0x80109000 + sd zero, 0(t0) + ld t1, 0(t0) + + // ---- Arm the machine timer to fire during the fence.i burst ---- + // CLINT: MTIME @ 0x0200BFF8, MTIMECMP @ 0x02004000. MTIE and global MIE + // are already enabled by WALLY-init-lib.h. + li t2, 0x0200BFF8 // address of MTIME + ld t3, 0(t2) // current time + addi t3, t3, 8 // small delta so the interrupt fires shortly into the burst + li t4, 0x02004000 // address of MTIMECMP + sd t3, 0(t4) // arm timer interrupt + + // ---- fence.i burst ---- + // A long run of back-to-back fence.i. Each fence.i spends multiple cycles in the + // Memory stage asserting InvalidateICacheM; with the timer armed to fire inside this + // window, the trap (TrapM -> FlushW) is taken while a fence.i is in M, hitting + // InvalidateFlushStage_1 (InvalidateCache=1 & InvalidateFlushStage=1). + .rept 256 + fence.i + .endr + + // The trap_handler in WALLY-init-lib.h disables the timer (mtimecmp = -1) and + // returns into the middle of the burst, which then runs to completion. + + // A few more fence.i interleaved with control flow for good measure. + fence.i + jal primer + fence.i + + j done + +// Subroutine spread over several .align'd cache lines so executing it fills +// multiple icache sets/ways before invalidation. +.align 6 +primer: + nop + nop + nop + nop + jal t6, primer2 + nop + nop + ret + +.align 6 +primer2: + nop + nop + nop + nop + jr t6 diff --git a/tests/coverage/decompReserved.S b/tests/coverage/decompReserved.S new file mode 100644 index 0000000000..444186be0d --- /dev/null +++ b/tests/coverage/decompReserved.S @@ -0,0 +1,46 @@ +/////////////////////////////////////////// +// decompReserved.S +// +// Written: david_harris@hmc.edu 29 May 2026 +// +// Purpose: Coverage for the compressed-instruction decoder (src/ifu/decompress.sv). +// Closes the uncovered branch at decompress.sv:164 ("All False") and the +// condition (rds1 != 5'b0)_0 in the c.jr decode: +// 5'b10100: if (instr16[12]==0) if (rs2==0) if (rds1 != 5'b0) ... // c.jr +// c.jr requires rs1 != 0; the rs1==0 form (instr 0x8002) is a RESERVED +// encoding that no compiler emits, so the rds1==0 / all-false path was never +// exercised. Emitting the raw 16-bit reserved instruction drives the decoder +// down that path (it raises an illegal-instruction trap, which the harness +// trap handler skips by advancing mepc past the 2-byte instruction). +// +// A component of the CORE-V-WALLY configurable RISC-V project. +// https://github.com/openhwgroup/cvw +// +// Copyright (C) 2021-23 Harvey Mudd College & Oklahoma State University +// +// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +// +// Licensed under the Solderpad Hardware License v 2.1 (the “License”); you may not use this file +// except in compliance with the License, or, at your option, the Apache License version 2.0. You +// may obtain a copy of the License at +// +// https://solderpad.org/licenses/SHL-2.1/ +// +// Unless required by applicable law or agreed to in writing, any work distributed under the +// License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific language governing permissions +// and limitations under the License. +//////////////////////////////////////////////////////////////////////////////////////////////// + + +#include "WALLY-init-lib.h" + +main: + # c.jr x0 : funct3=100, instr16[12]=0, rs1=00000, rs2=00000, op=10 => 0x8002 + # This is the RESERVED c.jr encoding (c.jr is only defined for rs1 != 0). + # The decompressor evaluates `if (rds1 != 5'b0)` at decompress.sv:164 with rds1==0, + # taking the previously-uncovered all-false branch / (rds1!=0)_0 condition. + # It decodes to an illegal instruction; the trap handler advances mepc by 2 and returns. + .2byte 0x8002 + + j done diff --git a/tests/coverage/fpuReservedRM.S b/tests/coverage/fpuReservedRM.S new file mode 100644 index 0000000000..07159734bb --- /dev/null +++ b/tests/coverage/fpuReservedRM.S @@ -0,0 +1,69 @@ +/////////////////////////////////////////// +// fpuReservedRM.S +// +// Written: david_harris@hmc.edu +// +// Purpose: Directed coverage test for src/fpu/fctrl.sv line 98. +// Hits the focused-expression input terms (FRM_REGW == 5)_1 and +// (FRM_REGW == 6)_1 in the SupportedRM assignment. +// +// fctrl.sv:98: +// assign SupportedRM = ~(Funct3D == 3'b101 | Funct3D == 3'b110 | +// (Funct3D == 3'b111 & (FRM_REGW == 3'b101 | +// FRM_REGW == 3'b110 | FRM_REGW == 3'b111))) | ... +// +// To make (FRM_REGW == 5) and (FRM_REGW == 6) each evaluate true while +// the rest of the SupportedRM term is exercised, we set frm (fcsr[7:5]) +// to the reserved values 5 then 6, and execute an OP-FP instruction that +// uses dynamic rounding (funct3 = 0b111). This makes SupportedRM false, +// which marks the instruction illegal. The library trap handler advances +// mepc past the illegal instruction so the test continues to "done". +// +// A component of the CORE-V-WALLY configurable RISC-V project. +// https://github.com/openhwgroup/cvw +// +// Copyright (C) 2021-23 Harvey Mudd College & Oklahoma State University +// +// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +// +// Licensed under the Solderpad Hardware License v 2.1 (the “License”); you may not use this file +// except in compliance with the License, or, at your option, the Apache License version 2.0. You +// may obtain a copy of the License at +// +// https://solderpad.org/licenses/SHL-2.1/ +// +// Unless required by applicable law or agreed to in writing, any work distributed under the +// License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific language governing permissions +// and limitations under the License. +//////////////////////////////////////////////////////////////////////////////////////////////// + +// load code to initialize stack, handle interrupts, terminate +#include "WALLY-init-lib.h" + +main: + + bseti t0, zero, 14 # mstatus.FS = 0b01 (turn on FPU) + csrs mstatus, t0 + + # A couple of legal FP ops to keep FS live and the FPU exercised + fli.s f1, 1 + fli.s f2, 1 + fadd.s f3, f1, f2 # legal add with implicit (static) rounding + + # ---- Set frm = 5 (reserved), then issue a dynamic-rounding OP-FP op ---- + li t0, 5 + csrw frm, t0 # frm (CSR 0x002) = 0b101 -> fcsr[7:5] = 5 + fadd.s f3, f1, f2, dyn # funct3=0b111 (dyn); reserved because FRM_REGW==5 -> illegal + + # ---- Set frm = 6 (reserved), then issue a dynamic-rounding OP-FP op ---- + li t0, 6 + csrw frm, t0 # frm (CSR 0x002) = 0b110 -> fcsr[7:5] = 6 + fadd.s f3, f1, f2, dyn # funct3=0b111 (dyn); reserved because FRM_REGW==6 -> illegal + + # Restore a legal rounding mode and do a legal op so FPU still works + li t0, 0 + csrw frm, t0 # frm = rne + fadd.s f3, f1, f2 + + j done diff --git a/tests/coverage/pmpTOR7.S b/tests/coverage/pmpTOR7.S new file mode 100644 index 0000000000..a24b254b48 --- /dev/null +++ b/tests/coverage/pmpTOR7.S @@ -0,0 +1,86 @@ +/////////////////////////////////////////// +// pmpTOR7.S +// +// Written: David_Harris@hmc.edu +// +// Purpose: Directed coverage test for PMP entry 7 TOR address decoder. +// +// Target gap: src/mmu/pmpadrdec.sv line 73, focused-expression term +// TORMatch for PMP entry 7, instance .../ifu/immu/immu/pmp/pmpchecker/pmpadrdecs[7]. +// +// pmpadrdec.sv computes (line 58-60): +// PAltPMPAdr = PhysicalAddress < {PMPAdr,2'b00} +// PAgePMPAdrIn = (cascaded ">= previous entry's PMPAdr") +// TORMatch = PAgePMPAdrIn & PAltPMPAdr +// and line 73 selects Match = (AdrMode==TOR) ? TORMatch : ... +// +// For a TOR entry i, PAgePMPAdrIn is the cascaded ">= pmpaddr[i-1]" term, so +// entry 7 matches when pmpaddr6<<2 <= PA < pmpaddr7<<2. Entry 7 had only ever +// been exercised with the checked PA OUTSIDE its TOR range (TORMatch=0). This +// test brackets a block of *instruction* memory with pmpaddr6/pmpaddr7 in TOR +// mode and then fetches/executes instructions whose PA lies inside that range, +// so the IMMU's pmpadrdecs[7] evaluates TORMatch=1. +// +// The decoder is combinational and independent of PMP priority: TORMatch[7] is +// driven for whatever PA the checker is evaluating regardless of which entry +// wins the priorityonehot. Entry 0 (set up by WALLY-init-lib.h to TOR RWX over +// the whole address space) keeps winning priority and granting X, so the fetch +// succeeds in machine mode while entry 7's TORMatch still toggles to 1. +// +// A component of the CORE-V-WALLY configurable RISC-V project. +// https://github.com/openhwgroup/cvw +// +// Copyright (C) 2021-23 Harvey Mudd College & Oklahoma State University +// +// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +//////////////////////////////////////////////////////////////////////////////////////////////// + +#include "WALLY-init-lib.h" + +main: + # ------------------------------------------------------------------ + # Program PMP entry 7 as a TOR region that brackets the executable + # block labelled [tor_lo, tor_hi). pmpaddr registers hold addr>>2. + # pmpaddr6 = tor_lo >> 2 (lower bound, cascaded into entry 7) + # pmpaddr7 = tor_hi >> 2 (upper bound of entry 7's TOR range) + # ------------------------------------------------------------------ + la t0, tor_lo + srli t0, t0, 2 + csrw pmpaddr6, t0 # pmpaddr6 = tor_lo>>2 -> entry 7 lower bound + + la t1, tor_hi + srli t1, t1, 2 + csrw pmpaddr7, t1 # pmpaddr7 = tor_hi>>2 -> entry 7 upper bound + + # ------------------------------------------------------------------ + # Configure pmpcfg entry 7 = TOR (A=01), R/W/X, L (locked). + # On RV64 pmpcfg0 holds entries 0..7, one byte each; entry 7 = bits[63:56]. + # Byte value = L(1)<<7 | A(01)<<3 | X(1)<<2 | W(1)<<1 | R(1) + # = 0x80 | 0x08 | 0x07 = 0x8F. + # Read-modify-write pmpcfg0 so the lower entries the init lib set up + # (entry 0 = 0x0F TOR RWX over all space) are preserved. + # ------------------------------------------------------------------ + li t2, 0x8F + slli t2, t2, 56 # place in entry-7 byte position [63:56] + csrr t3, pmpcfg0 # read current pmpcfg0 (preserve entries 0-6) + or t3, t3, t2 # OR in entry-7 = TOR RWX L + csrw pmpcfg0, t3 + + # ------------------------------------------------------------------ + # Now execute instructions whose fetch PA is inside [tor_lo, tor_hi). + # Each fetch drives the IMMU PhysicalAddress through pmpadrdecs[7], where + # pmpaddr6<<2 <= PA < pmpaddr7<<2 ==> TORMatch[7] = 1 (pmpadrdec.sv:60,73) + # Entry 0 still wins priority and grants X, so the fetch is allowed. + # ------------------------------------------------------------------ +.align 3 +tor_lo: # lower bound of TOR region (PMP-bracketed code) + nop + nop + addi t4, x0, 1 + addi t4, t4, 1 + nop + nop +.align 3 +tor_hi: # upper bound: addresses >= here are NOT in entry 7's range + + j done diff --git a/tests/coverage/tlbGLBASID.S b/tests/coverage/tlbGLBASID.S new file mode 100644 index 0000000000..1a6fede41c --- /dev/null +++ b/tests/coverage/tlbGLBASID.S @@ -0,0 +1,222 @@ +/////////////////////////////////////////// +// tlbGLBASID.S +// +// Written: david_harris@hmc.edu 29 May 2026 +// +// Purpose: Coverage for the tlbcamline ShouldFlush expression (issue #1538 global preservation): +// assign ShouldFlush = TLBFlush & (~PTE_G | TLBFlushAll); +// Fills every TLB entry (ITLB and DTLB) with global (PTE_G=1) mappings, then issues both +// an ASID-scoped sfence.vma (rs2 != x0 => TLBFlushAll=0) and a full sfence.vma +// (rs2 == x0 => TLBFlushAll=1) while those global entries are resident. This exercises the +// three focused-expression rows that no existing test reached for most camlines: +// PTE_G_1 (non-masking TLBFlush && ~TLBFlushAll) +// TLBFlushAll_0 (non-masking TLBFlush && PTE_G) +// TLBFlushAll_1 (non-masking TLBFlush && PTE_G) +// tlbGLB.S installs global entries but never issues sfence.vma; tlbASID.S issues sfence.vma +// but with no global entry resident. Page table reused from tlbGLB.S. +// +// A component of the CORE-V-WALLY configurable RISC-V project. +// https://github.com/openhwgroup/cvw +// +// Copyright (C) 2021-23 Harvey Mudd College & Oklahoma State University +// +// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +// +// Licensed under the Solderpad Hardware License v 2.1 (the “License”); you may not use this file +// except in compliance with the License, or, at your option, the Apache License version 2.0. You +// may obtain a copy of the License at +// +// https://solderpad.org/licenses/SHL-2.1/ +// +// Unless required by applicable law or agreed to in writing, any work distributed under the +// License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific language governing permissions +// and limitations under the License. +//////////////////////////////////////////////////////////////////////////////////////////////// + + +// load code to initialize stack, handle interrupts, terminate + +#include "WALLY-init-lib.h" + +# run-elf.bash find this in project description +main: + # Page table root address at 0x80080000, ASID = 0 (SV48) + li t5, 0x9000000000080080 + csrw satp, t5 + + # switch to supervisor mode + li a0, 1 + ecall + + li t3, 48 # distinct pages per fill (> TLB entries so replacement churns every line) + li t4, 0x1000 # offset between pages + li t1, 0x00008067 # "ret" (jalr x0, x1, 0) instruction word + + # Each round fills every ITLB and DTLB entry with global (G=1) pages (store a ret into each page + # => DTLB fill; fetch/execute it => ITLB fill) and then issues an sfence.vma while those global + # entries are resident. We alternate full and ASID-scoped fences across four rounds so that, + # regardless of replacement order, every camline holds a global entry during both a + # TLBFlushAll=1 fence (covers the TLBFlushAll_1 row) and a TLBFlushAll=0 fence (covers the + # PTE_G_1 and TLBFlushAll_0 rows) of: assign ShouldFlush = TLBFlush & (~PTE_G | TLBFlushAll); + + # ---- round 1: fill, then FULL sfence.vma (rs2 = x0 => TLBFlushAll = 1) ---- + li t0, 0xC0000000 + li t2, 0 +fillA: + bge t2, t3, flushA + sw t1, 0(t0) + fence.i + jalr t0 + add t0, t0, t4 + addi t2, t2, 1 + j fillA +flushA: + sfence.vma + + # ---- round 2: fill, then ASID-scoped sfence.vma (rs2 = t2 != x0 => TLBFlushAll = 0) ---- + li t0, 0xC0000000 + li t2, 0 +fillB: + bge t2, t3, flushB + sw t1, 0(t0) + fence.i + jalr t0 + add t0, t0, t4 + addi t2, t2, 1 + j fillB +flushB: + li t2, 1 + sfence.vma x0, t2 + + # ---- round 3: fill, then FULL sfence.vma ---- + li t0, 0xC0000000 + li t2, 0 +fillC: + bge t2, t3, flushC + sw t1, 0(t0) + fence.i + jalr t0 + add t0, t0, t4 + addi t2, t2, 1 + j fillC +flushC: + sfence.vma + + # ---- round 4: fill, then ASID-scoped sfence.vma ---- + li t0, 0xC0000000 + li t2, 0 +fillD: + bge t2, t3, flushD + sw t1, 0(t0) + fence.i + jalr t0 + add t0, t0, t4 + addi t2, t2, 1 + j fillD +flushD: + li t2, 1 + sfence.vma x0, t2 + + j done + +.data +.align 19 +# level 3 Page table situated at 0x80080000, points to 0x80081000 +pagetable: + .8byte 0x20020401 + +.align 12 // level 2 page table, contains direction to a gigapage + .8byte 0x0 + .8byte 0x0 + .8byte 0x200000EF // gigapage that starts at 8000 0000 goes to C000 0000 + .8byte 0x20020821 // pointer to next page table entry at 8008 2000 + +.align 12 // level 1 page table, points to level 0 page table + .8byte 0x20020C21 + +.align 12 // level 0 page table, contains 64 global kilopages + .8byte 0x200000EF // access xC000 0000 + .8byte 0x200004EF // access xC000 1000 + .8byte 0x200008EF // access xC000 2000 + .8byte 0x20000CEF // access xC000 3000 + + .8byte 0x200010EF // access xC000 4000 + .8byte 0x200014EF + .8byte 0x200018EF + .8byte 0x20001CEF + + .8byte 0x200020EF // access xC000 8000 + .8byte 0x200024EF + .8byte 0x200028EF + .8byte 0x20002CEF + + .8byte 0x200030EF // access xC000 C000 + .8byte 0x200034EF + .8byte 0x200038EF + .8byte 0x20003CEF + + .8byte 0x200040EF // access xC001 0000 + .8byte 0x200044EF + .8byte 0x200048EF + .8byte 0x20004CEF + + .8byte 0x200050EF // access xC001 4000 + .8byte 0x200054EF + .8byte 0x200058EF + .8byte 0x20005CEF + + .8byte 0x200060EF // access xC001 8000 + .8byte 0x200064EF + .8byte 0x200068EF + .8byte 0x20006CEF + + .8byte 0x200070EF // access xC001 C000 + .8byte 0x200074eF + .8byte 0x200078EF + .8byte 0x20007CEF + + .8byte 0x200080EF // access xC002 0000 + .8byte 0x200084EF + .8byte 0x200088EF + .8byte 0x20008CEF + + .8byte 0x200010EF // access xC000 4000 + .8byte 0x200014EF + .8byte 0x200018EF + .8byte 0x20001CEF + + .8byte 0x200020EF // access xC000 8000 + .8byte 0x200024EF + .8byte 0x200028EF + .8byte 0x20002CEF + + .8byte 0x200030EF // access xC000 C000 + .8byte 0x200034EF + .8byte 0x200038EF + .8byte 0x20003CEF + + .8byte 0x200040EF // access xC001 0000 + .8byte 0x200044EF + .8byte 0x200048EF + .8byte 0x20004CEF + + .8byte 0x200050EF // access xC001 4000 + .8byte 0x200054EF + .8byte 0x200058EF + .8byte 0x20005CEF + + .8byte 0x200060EF // access xC001 8000 + .8byte 0x200064EF + .8byte 0x200068EF + .8byte 0x20006CEF + + .8byte 0x200070EF // access xC001 C000 + .8byte 0x200074eF + .8byte 0x200078EF + .8byte 0x20007CEF + + .8byte 0x200080EF // access xC002 0000 + .8byte 0x200084EF + .8byte 0x200088EF + .8byte 0x20008CEF diff --git a/tests/coverage/tlbGLBHIT.S b/tests/coverage/tlbGLBHIT.S new file mode 100644 index 0000000000..55e724b346 --- /dev/null +++ b/tests/coverage/tlbGLBHIT.S @@ -0,0 +1,211 @@ +/////////////////////////////////////////// +// tlbGLBHIT.S +// +// Written: David_Harris@hmc.edu 30 May 2026 +// +// Purpose: Close the last uncovered FEC row in IFU TLB camline slot 2: +// tlbcamline.sv:67 assign MatchASID = (SATP_ASID == Key_ASID) | PTE_G; +// Row 4: PTE_G_1 with non-masking condition ~(SATP_ASID == Key_ASID) -- i.e. a *global* +// entry that produces a TLB *hit* purely because PTE_G=1 while the queried ASID differs +// from the stored Key_ASID. +// +// The existing tlbGLBASID.S fills global entries and then sfences them (covering the +// ShouldFlush PTE_G rows), but never re-queries a resident global entry under a mismatched +// ASID, so the line-67 PTE_G_1 hit row stays uncovered for some camlines (slot 2). +// This test fills every ITLB line with global (G=1) pages under one ASID, switches ASID +// WITHOUT an sfence (global entries survive), then re-fetches the same pages so each +// resident global entry is hit while SATP_ASID != Key_ASID. Two rounds alternate the +// ASID pair so that, regardless of replacement order, slot 2 holds a global entry that is +// hit under a mismatched ASID. +// +// A component of the CORE-V-WALLY configurable RISC-V project. +// https://github.com/openhwgroup/cvw +// +// Copyright (C) 2021-23 Harvey Mudd College & Oklahoma State University +// +// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +// +// Licensed under the Solderpad Hardware License v 2.1 (the “License”); you may not use this file +// except in compliance with the License, or, at your option, the Apache License version 2.0. You +// may obtain a copy of the License at +// +// https://solderpad.org/licenses/SHL-2.1/ +// +// Unless required by applicable law or agreed to in writing, any work distributed under the +// License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific language governing permissions +// and limitations under the License. +//////////////////////////////////////////////////////////////////////////////////////////////// + +// load code to initialize stack, handle interrupts, terminate + +#include "WALLY-init-lib.h" + +# run-elf.bash find this in project description +main: + # Page table root address at 0x80080000, ASID = 0 (SV48) + li t5, 0x9000000000080080 + csrw satp, t5 + + # switch to supervisor mode + li a0, 1 + ecall + + li t3, 48 # distinct pages per fill (> TLB entries so every line churns) + li t4, 0x1000 # offset between pages + li t1, 0x00008067 # "ret" (jalr x0, x1, 0) instruction word + + # ---- round 1: fill global pages under ASID=0, then re-fetch under ASID=1 ---- + li t5, 0x9000000000080080 # ASID = 0 + csrw satp, t5 + li t0, 0xC0000000 + li t2, 0 +fill0: + bge t2, t3, swap0 + sw t1, 0(t0) # store a ret into the page (DTLB fill) + fence.i + jalr t0 # fetch+execute it (ITLB fill, global entry, Key_ASID=0) + add t0, t0, t4 + addi t2, t2, 1 + j fill0 +swap0: + li t5, 0x9001000000080080 # switch to ASID=1, NO sfence: global entries stay resident + csrw satp, t5 + li t0, 0xC0000000 + li t2, 0 +refetch0: + bge t2, t3, round2 + jalr t0 # ITLB hit on global entry (Key_ASID=0) under SATP_ASID=1 => PTE_G_1 row + add t0, t0, t4 + addi t2, t2, 1 + j refetch0 + + # ---- round 2: fill global pages under ASID=1, then re-fetch under ASID=0 ---- +round2: + li t5, 0x9001000000080080 # ASID = 1 + csrw satp, t5 + li t0, 0xC0000000 + li t2, 0 +fill1: + bge t2, t3, swap1 + sw t1, 0(t0) + fence.i + jalr t0 # ITLB fill, global entry, Key_ASID=1 + add t0, t0, t4 + addi t2, t2, 1 + j fill1 +swap1: + li t5, 0x9000000000080080 # switch to ASID=0, NO sfence + csrw satp, t5 + li t0, 0xC0000000 + li t2, 0 +refetch1: + bge t2, t3, finished + jalr t0 # ITLB hit on global entry (Key_ASID=1) under SATP_ASID=0 => PTE_G_1 row + add t0, t0, t4 + addi t2, t2, 1 + j refetch1 + +finished: + j done + +.data +.align 19 +# level 3 Page table situated at 0x80080000, points to 0x80081000 +pagetable: + .8byte 0x20020401 + +.align 12 // level 2 page table, contains direction to a gigapage + .8byte 0x0 + .8byte 0x0 + .8byte 0x200000EF // gigapage that starts at 8000 0000 goes to C000 0000 + .8byte 0x20020821 // pointer to next page table entry at 8008 2000 + +.align 12 // level 1 page table, points to level 0 page table + .8byte 0x20020C21 + +.align 12 // level 0 page table, contains 64 global kilopages + .8byte 0x200000EF // access xC000 0000 + .8byte 0x200004EF // access xC000 1000 + .8byte 0x200008EF // access xC000 2000 + .8byte 0x20000CEF // access xC000 3000 + + .8byte 0x200010EF // access xC000 4000 + .8byte 0x200014EF + .8byte 0x200018EF + .8byte 0x20001CEF + + .8byte 0x200020EF // access xC000 8000 + .8byte 0x200024EF + .8byte 0x200028EF + .8byte 0x20002CEF + + .8byte 0x200030EF // access xC000 C000 + .8byte 0x200034EF + .8byte 0x200038EF + .8byte 0x20003CEF + + .8byte 0x200040EF // access xC001 0000 + .8byte 0x200044EF + .8byte 0x200048EF + .8byte 0x20004CEF + + .8byte 0x200050EF // access xC001 4000 + .8byte 0x200054EF + .8byte 0x200058EF + .8byte 0x20005CEF + + .8byte 0x200060EF // access xC001 8000 + .8byte 0x200064EF + .8byte 0x200068EF + .8byte 0x20006CEF + + .8byte 0x200070EF // access xC001 C000 + .8byte 0x200074EF + .8byte 0x200078EF + .8byte 0x20007CEF + + .8byte 0x200080EF // access xC002 0000 + .8byte 0x200084EF + .8byte 0x200088EF + .8byte 0x20008CEF + + .8byte 0x200010EF // access xC000 4000 + .8byte 0x200014EF + .8byte 0x200018EF + .8byte 0x20001CEF + + .8byte 0x200020EF // access xC000 8000 + .8byte 0x200024EF + .8byte 0x200028EF + .8byte 0x20002CEF + + .8byte 0x200030EF // access xC000 C000 + .8byte 0x200034EF + .8byte 0x200038EF + .8byte 0x20003CEF + + .8byte 0x200040EF // access xC001 0000 + .8byte 0x200044EF + .8byte 0x200048EF + .8byte 0x20004CEF + + .8byte 0x200050EF // access xC001 4000 + .8byte 0x200054EF + .8byte 0x200058EF + .8byte 0x20005CEF + + .8byte 0x200060EF // access xC001 8000 + .8byte 0x200064EF + .8byte 0x200068EF + .8byte 0x20006CEF + + .8byte 0x200070EF // access xC001 C000 + .8byte 0x200074EF + .8byte 0x200078EF + .8byte 0x20007CEF + + .8byte 0x200080EF // access xC002 0000 + .8byte 0x200084EF + .8byte 0x200088EF + .8byte 0x20008CEF diff --git a/tests/coverage/vm64check.S b/tests/coverage/vm64check.S index b732098c7d..e6cc837e54 100644 --- a/tests/coverage/vm64check.S +++ b/tests/coverage/vm64check.S @@ -101,6 +101,23 @@ check14: check15: li t0, 0x00001001800F0000 # unimplemented memory with upper all zero, lower mixed + la ra, check15a + jalr t0 + + # The checks above leave three SV39 FEC terms in vm64check uncovered because no + # VA exercises these exact upper-bit field patterns. Add them explicitly: +check15a: # all0_55_47_0: bits[63:56]=0, bits[55:47]!=0 (bit47), bits[46:38]=0 + li t0, 0x00008000800F0000 + la ra, check15b + jalr t0 + +check15b: # all1_46_38_0: bits[63:47]=all1, bits[46:38]!=all1 (=0) + li t0, 0xFFFF8000800F0000 + la ra, check15c + jalr t0 + +check15c: # all1_55_47_0: bits[63:56]=all1, bits[55:47]!=all1 (=0), bits[46:38]=all1 + li t0, 0xFF007FC0800F0000 la ra, check16 jalr t0