/////////////////////////////////////////// // testbench-linux.sv // // Written: nboorstin@g.hmc.edu 2021 // Modified: // // Purpose: Testbench for Buildroot Linux // // A component of the Wally configurable RISC-V project. // // Copyright (C) 2021 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-config.vh" // This is set from the command line script // `define USE_IMPERAS_DV `ifdef USE_IMPERAS_DV `include "rvvi/imperasDV.svh" `endif `define DEBUG_TRACE 0 // Debug Levels // 0: don't check against QEMU // 1: print disagreements with QEMU, but only halt on PCW disagreements // 2: halt on any disagreement with QEMU except CSRs // 3: halt on all disagreements with QEMU // 4: print memory accesses whenever they happen // 5: print everything module testbench; /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////// CONFIG //////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // Recommend setting all of these in 'do' script using -G option parameter INSTR_LIMIT = 0; // # of instructions at which to stop parameter INSTR_WAVEON = 0; // # of instructions at which to turn on waves in graphical sim parameter CHECKPOINT = 0; parameter RISCV_DIR = "/opt/riscv"; parameter NO_SPOOFING = 0; `ifdef USE_IMPERAS_DV import rvviPkg::*; import rvviApiPkg::*; import idvApiPkg::*; `endif //////////////////////////////////////////////////////////////////////////////////// //////////////////////// SIGNAL / VAR / MACRO DECLARATIONS ///////////////////////// //////////////////////////////////////////////////////////////////////////////////// // ========== Testbench Core ========== integer warningCount = 0; integer errorCount = 0; integer fault; string ProgramAddrMapFile, ProgramLabelMapFile; // ========== Initialization ========== string testvectorDir; string linuxImageDir; integer memFile; integer readResult; // ========== Checkpointing ========== string checkpointDir; logic [1:0] initPriv; // ========== Trace parsing & checking ========== integer garbageInt; string garbageString; `define DECLARE_TRACE_SCANNER_SIGNALS(STAGE) \ integer traceFile``STAGE; \ integer matchCount``STAGE; \ string line``STAGE; \ string token``STAGE; \ string ExpectedTokens``STAGE [31:0]; \ integer index``STAGE; \ integer StartIndex``STAGE, EndIndex``STAGE; \ integer TokenIndex``STAGE; \ integer MarkerIndex``STAGE; \ integer NumCSR``STAGE; \ logic [`XLEN-1:0] ExpectedPC``STAGE; \ logic [31:0] ExpectedInstr``STAGE; \ string text``STAGE; \ string MemOp``STAGE; \ string RegWrite``STAGE; \ integer ExpectedRegAdr``STAGE; \ logic [`XLEN-1:0] ExpectedRegValue``STAGE; \ logic [`XLEN-1:0] ExpectedIEUAdr``STAGE, ExpectedMemReadData``STAGE, ExpectedMemWriteData``STAGE; \ string ExpectedCSRArray``STAGE[10:0]; \ logic [`XLEN-1:0] ExpectedCSRArrayValue``STAGE[10:0]; // *** might be redundant? `DECLARE_TRACE_SCANNER_SIGNALS(E) `DECLARE_TRACE_SCANNER_SIGNALS(M) // M-stage expected values logic checkInstrM; integer MIPexpected, SIPexpected; string name; logic [`AHBW-1:0] readDataExpected; // W-stage expected values logic checkInstrW; logic [`XLEN-1:0] ExpectedPCW; logic [31:0] ExpectedInstrW; string textW; string RegWriteW; integer ExpectedRegAdrW; logic [`XLEN-1:0] ExpectedRegValueW; string MemOpW; logic [`XLEN-1:0] ExpectedIEUAdrW, ExpectedMemReadDataW, ExpectedMemWriteDataW; integer NumCSRW; string ExpectedCSRArrayW[10:0]; logic [`XLEN-1:0] ExpectedCSRArrayValueW[10:0]; logic [`XLEN-1:0] ExpectedIntType; integer NumCSRWIndex; integer NumCSRPostWIndex; logic [`XLEN-1:0] InstrCountW; // ========== Interrupt parsing & spoofing ========== string interrupt; string interruptLine; integer interruptFile; integer interruptInstrCount; integer interruptHartVal; integer interruptAsyncVal; longint interruptCauseVal; longint interruptEpcVal; longint interruptTVal; string interruptDesc; integer NextMIPexpected, NextSIPexpected; integer NextMepcExpected; logic [`XLEN-1:0] AttemptedInstructionCount; // ========== Misc Aliases ========== `define RF dut.core.ieu.dp.regf.rf `define PC dut.core.ifu.pcreg.q `define PRIV_BASE dut.core.priv.priv `define PRIV `PRIV_BASE.privmode.privmode.privmodereg.q `define CSR_BASE `PRIV_BASE.csr `define MEIP `PRIV_BASE.MExtInt `define SEIP `PRIV_BASE.SExtInt `define MTIP `PRIV_BASE.MTimerInt `define HPMCOUNTER `CSR_BASE.counters.counters.HPMCOUNTER_REGW `define MEDELEG `CSR_BASE.csrm.deleg.MEDELEGreg.q `define MIDELEG `CSR_BASE.csrm.deleg.MIDELEGreg.q `define MIE `CSR_BASE.csri.MIE_REGW `define MIP `CSR_BASE.csri.MIP_REGW_writeable `define MCAUSE `CSR_BASE.csrm.MCAUSEreg.q `define SCAUSE `CSR_BASE.csrs.csrs.SCAUSEreg.q `define MEPC `CSR_BASE.csrm.MEPCreg.q `define SEPC `CSR_BASE.csrs.csrs.SEPCreg.q `define MCOUNTEREN `CSR_BASE.csrm.mcounteren.MCOUNTERENreg.q `define SCOUNTEREN `CSR_BASE.csrs.csrs.SCOUNTERENreg.q `define MSCRATCH `CSR_BASE.csrm.MSCRATCHreg.q `define SSCRATCH `CSR_BASE.csrs.csrs.SSCRATCHreg.q `define MTVEC `CSR_BASE.csrm.MTVECreg.q `define STVEC `CSR_BASE.csrs.csrs.STVECreg.q `define SATP `CSR_BASE.csrs.csrs.genblk1.SATPreg.q `define INSTRET `CSR_BASE.counters.counters.HPMCOUNTER_REGW[2] `define MSTATUS `CSR_BASE.csrsr.MSTATUS_REGW `define SSTATUS `CSR_BASE.csrsr.SSTATUS_REGW `define STATUS_TSR `CSR_BASE.csrsr.STATUS_TSR_INT `define STATUS_TW `CSR_BASE.csrsr.STATUS_TW_INT `define STATUS_TVM `CSR_BASE.csrsr.STATUS_TVM_INT `define STATUS_MXR `CSR_BASE.csrsr.STATUS_MXR_INT `define STATUS_SUM `CSR_BASE.csrsr.STATUS_SUM_INT `define STATUS_MPRV `CSR_BASE.csrsr.STATUS_MPRV_INT `define STATUS_FS `CSR_BASE.csrsr.STATUS_FS_INT `define STATUS_MPP `CSR_BASE.csrsr.STATUS_MPP `define STATUS_SPP `CSR_BASE.csrsr.STATUS_SPP `define STATUS_MPIE `CSR_BASE.csrsr.STATUS_MPIE `define STATUS_SPIE `CSR_BASE.csrsr.STATUS_SPIE `define STATUS_MIE `CSR_BASE.csrsr.STATUS_MIE `define STATUS_SIE `CSR_BASE.csrsr.STATUS_SIE `define UART dut.uncore.uncore.uart.uart.u `define UART_IER `UART.IER `define UART_LCR `UART.LCR `define UART_MCR `UART.MCR `define UART_SCR `UART.SCR `define UART_IP `UART.INTR `define PLIC dut.uncore.uncore.plic.plic `define PLIC_INT_PRIORITY `PLIC.intPriority `define PLIC_INT_ENABLE `PLIC.intEn `define PLIC_THRESHOLD `PLIC.intThreshold `define PCM dut.core.ifu.PCM // ========== COMMON MACROS ========== // Needed for initialization and core `define SCAN_NEW_INTERRUPT \ begin \ $fgets(interruptLine, interruptFile); \ //$display("Time %t, interruptLine %x", $time, interruptLine); \ $fgets(interruptLine, interruptFile); \ $sscanf(interruptLine, "%d", interruptInstrCount); \ $fgets(interruptLine, interruptFile); \ $sscanf(interruptLine, "%d", interruptHartVal); \ $fgets(interruptLine, interruptFile); \ $sscanf(interruptLine, "%d", interruptAsyncVal); \ $fgets(interruptLine, interruptFile); \ $sscanf(interruptLine, "%x", interruptCauseVal); \ $fgets(interruptLine, interruptFile); \ $sscanf(interruptLine, "%x", interruptEpcVal); \ $fgets(interruptLine, interruptFile); \ $sscanf(interruptLine, "%x", interruptTVal); \ $fgets(interruptLine, interruptFile); \ $sscanf(interruptLine, "%s", interruptDesc); \ end /////////////////////////////////////////////////////////////////////////////// /////////////////////////////// Cache Issue /////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// logic probe; if (NO_SPOOFING) assign probe = testbench.dut.core.PCM == 64'hffffffff80200c8c & testbench.dut.core.InstrM != 32'h14021273 & testbench.dut.core.InstrValidM; /////////////////////////////////////////////////////////////////////////////// ////////////////////////////////// HARDWARE /////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // Clock and Reset logic clk, reset_ext; logic reset; initial begin reset_ext <= 1; # 22; reset_ext <= 0; end always begin clk <= 1; # 5; clk <= 0; # 5; end // Wally Interface logic [`AHBW-1:0] HRDATAEXT; logic HREADYEXT, HRESPEXT; logic HCLK, HRESETn; logic HREADY; logic HSELEXT; logic [`PA_BITS-1:0] HADDR; logic [`AHBW-1:0] HWDATA; logic [`XLEN/8-1:0] HWSTRB; logic HWRITE; logic [2:0] HSIZE; logic [2:0] HBURST; logic [3:0] HPROT; logic [1:0] HTRANS; logic HMASTLOCK; logic [31:0] GPIOIN; logic [31:0] GPIOOUT, GPIOEN; logic UARTSin, UARTSout; // FPGA-specific Stuff logic SDCCLK; logic SDCCmdIn; logic SDCCmdOut; logic SDCCmdOE; logic [3:0] SDCDatIn; // Hardwire UART, GPIO pins assign GPIOPinsIn = 0; assign UARTSin = 1; `ifdef USE_IMPERAS_DV logic DCacheFlushDone, DCacheFlushStart; rvviTrace #(.XLEN(`XLEN), .FLEN(`FLEN)) rvvi(); wallyTracer wallyTracer(rvvi); trace2log idv_trace2log(rvvi); // trace2cov idv_trace2cov(rvvi); // enabling of comparison types trace2api #(.CMP_PC (1), .CMP_INS (1), .CMP_GPR (1), .CMP_FPR (1), .CMP_VR (0), .CMP_CSR (1) ) idv_trace2api(rvvi); initial begin int iter; #1; MAX_ERRS = 3; // Initialize REF (do this before initializing the DUT) if (!rvviVersionCheck(RVVI_API_VERSION)) begin msgfatal($sformatf("%m @ t=%0t: Expecting RVVI API version %0d.", $time, RVVI_API_VERSION)); end void'(rvviRefConfigSetString(IDV_CONFIG_MODEL_VENDOR, "riscv.ovpworld.org")); void'(rvviRefConfigSetString(IDV_CONFIG_MODEL_NAME, "riscv")); void'(rvviRefConfigSetString(IDV_CONFIG_MODEL_VARIANT, "RV64GC")); void'(rvviRefConfigSetInt(IDV_CONFIG_MODEL_ADDRESS_BUS_WIDTH, 56)); void'(rvviRefConfigSetInt(IDV_CONFIG_MAX_NET_LATENCY_RETIREMENTS, 6)); if (!rvviRefInit("")) begin msgfatal($sformatf("%m @ t=%0t: rvviRefInit failed", $time)); end // Volatile CSRs void'(rvviRefCsrSetVolatile(0, 32'hC00)); // CYCLE void'(rvviRefCsrSetVolatile(0, 32'hB00)); // MCYCLE void'(rvviRefCsrSetVolatile(0, 32'hC02)); // INSTRET void'(rvviRefCsrSetVolatile(0, 32'hB02)); // MINSTRET void'(rvviRefCsrSetVolatile(0, 32'hC01)); // TIME // User HPMCOUNTER3 - HPMCOUNTER31 for (iter='hC03; iter<='hC1F; iter++) begin void'(rvviRefCsrSetVolatile(0, iter)); // HPMCOUNTERx end // Machine MHPMCOUNTER3 - MHPMCOUNTER31 for (iter='hB03; iter<='hB1F; iter++) begin void'(rvviRefCsrSetVolatile(0, iter)); // MHPMCOUNTERx end // cannot predict this register due to latency between // pending and taken void'(rvviRefCsrSetVolatile(0, 32'h344)); // MIP void'(rvviRefCsrSetVolatile(0, 32'h144)); // SIP // Privileges for PMA are set in the imperas.ic // volatile (IO) regions are defined here // only real ROM/RAM areas are BOOTROM and UNCORE_RAM if (`CLINT_SUPPORTED) begin void'(rvviRefMemorySetVolatile(`CLINT_BASE, (`CLINT_BASE + `CLINT_RANGE))); end if (`GPIO_SUPPORTED) begin void'(rvviRefMemorySetVolatile(`GPIO_BASE, (`GPIO_BASE + `GPIO_RANGE))); end if (`UART_SUPPORTED) begin void'(rvviRefMemorySetVolatile(`UART_BASE, (`UART_BASE + `UART_RANGE))); end if (`PLIC_SUPPORTED) begin void'(rvviRefMemorySetVolatile(`PLIC_BASE, (`PLIC_BASE + `PLIC_RANGE))); end if (`SDC_SUPPORTED) begin void'(rvviRefMemorySetVolatile(`SDC_BASE, (`SDC_BASE + `SDC_RANGE))); end if(`XLEN==32) begin void'(rvviRefCsrSetVolatile(0, 32'hC80)); // CYCLEH void'(rvviRefCsrSetVolatile(0, 32'hB80)); // MCYCLEH void'(rvviRefCsrSetVolatile(0, 32'hC82)); // INSTRETH void'(rvviRefCsrSetVolatile(0, 32'hB82)); // MINSTRETH end void'(rvviRefCsrSetVolatile(0, 32'h104)); // SIE - Temporary!!!! // Load memory begin longint x64; int x32[2]; longint index; $sformat(testvectorDir,"%s/linux-testvectors/",RISCV_DIR); $display("RVVI Loading bootmem.bin"); memFile = $fopen({testvectorDir,"bootmem.bin"}, "rb"); index = 'h1000 - 8; while(!$feof(memFile)) begin index+=8; readResult = $fread(x64, memFile); if (x64 == 0) continue; x32[0] = x64 & 'hffffffff; x32[1] = x64 >> 32; rvviRefMemoryWrite(0, index+0, x32[0], 4); rvviRefMemoryWrite(0, index+4, x32[1], 4); //$display("boot %08X x32[0]=%08X x32[1]=%08X", index, x32[0], x32[1]); end $fclose(memFile); $display("RVVI Loading ram.bin"); memFile = $fopen({testvectorDir,"ram.bin"}, "rb"); index = 'h80000000 - 8; while(!$feof(memFile)) begin index+=8; readResult = $fread(x64, memFile); if (x64 == 0) continue; x32[0] = x64 & 'hffffffff; x32[1] = x64 >> 32; rvviRefMemoryWrite(0, index+0, x32[0], 4); rvviRefMemoryWrite(0, index+4, x32[1], 4); //$display("ram %08X x32[0]=%08X x32[1]=%08X", index, x32[0], x32[1]); end $fclose(memFile); $display("RVVI Loading Complete"); void'(rvviRefPcSet(0, 'h1000)); // set BOOTROM address end end always @(dut.core.MTimerInt) void'(rvvi.net_push("MTimerInterrupt", dut.core.MTimerInt)); always @(dut.core.MExtInt) void'(rvvi.net_push("MExternalInterrupt", dut.core.MExtInt)); always @(dut.core.SExtInt) void'(rvvi.net_push("SExternalInterrupt", dut.core.SExtInt)); always @(dut.core.MSwInt) void'(rvvi.net_push("MSWInterrupt", dut.core.MSwInt)); always @(dut.core.priv.priv.csr.csrs.csrs.STimerInt) void'(rvvi.net_push("STimerInterrupt", dut.core.priv.priv.csr.csrs.csrs.STimerInt)); final begin void'(rvviRefShutdown()); end `endif // Wally wallypipelinedsoc dut(.clk, .reset, .reset_ext, .HRDATAEXT, .HREADYEXT, .HREADY, .HSELEXT, .HRESPEXT, .HCLK, .HRESETn, .HADDR, .HWDATA, .HWRITE, .HWSTRB, .HSIZE, .HBURST, .HPROT, .HTRANS, .HMASTLOCK, .TIMECLK('0), .GPIOIN, .GPIOOUT, .GPIOEN, .UARTSin, .UARTSout, .SDCCLK, .SDCCmdIn, .SDCCmdOut, .SDCCmdOE, .SDCDatIn); // W-stage hardware not needed by Wally itself parameter nop = 'h13; logic [`XLEN-1:0] PCW; logic [31:0] InstrW; logic InstrValidW; logic [`XLEN-1:0] IEUAdrW, WriteDataW; logic TrapW; `define FLUSHW dut.core.FlushW `define STALLW dut.core.StallW flopenrc #(`XLEN) PCWReg(clk, reset, `FLUSHW, ~`STALLW, `PCM, PCW); flopenr #(32) InstrWReg(clk, reset, ~`STALLW, `FLUSHW ? nop : dut.core.ifu.InstrM, InstrW); flopenrc #(1) controlregW(clk, reset, `FLUSHW, ~`STALLW, dut.core.ieu.c.InstrValidM, InstrValidW); flopenrc #(`XLEN) IEUAdrWReg(clk, reset, `FLUSHW, ~`STALLW, dut.core.IEUAdrM, IEUAdrW); flopenrc #(`XLEN) WriteDataWReg(clk, reset, `FLUSHW, ~`STALLW, dut.core.lsu.WriteDataM, WriteDataW); flopenr #(1) TrapWReg(clk, reset, ~`STALLW, dut.core.hzu.TrapM, TrapW); /////////////////////////////////////////////////////////////////////////////// /////////////////////////////// INITIALIZATION //////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // ========== CHECKPOINTING ========== `define MAKE_CHECKPOINT_INIT_SIGNAL(SIGNAL,DIM,ARRAY_MAX,ARRAY_MIN) \ logic DIM init``SIGNAL[ARRAY_MAX:ARRAY_MIN]; \ initial begin \ #1; \ if (CHECKPOINT!=0) $readmemh({checkpointDir,"checkpoint-",`"SIGNAL`"}, init``SIGNAL); \ end `define INIT_CHECKPOINT_SIMPLE_ARRAY(SIGNAL,DIM,ARRAY_MAX,ARRAY_MIN) \ `MAKE_CHECKPOINT_INIT_SIGNAL(SIGNAL,DIM,ARRAY_MAX,ARRAY_MIN) \ initial begin \ if (CHECKPOINT!=0) begin \ force `SIGNAL = init``SIGNAL[ARRAY_MAX:ARRAY_MIN]; \ while (reset!==1) #1; \ while (reset!==0) #1; \ #1; \ release `SIGNAL; \ end \ end `define INIT_CHECKPOINT_PACKED_ARRAY(SIGNAL,DIM,ARRAY_MAX,ARRAY_MIN) \ `MAKE_CHECKPOINT_INIT_SIGNAL(SIGNAL,DIM,ARRAY_MAX,ARRAY_MIN) \ for (i=ARRAY_MIN; i= 2)) fault = 1; \ end `define checkCSR(CSR) \ begin \ if (CSR != ExpectedCSRArrayValueW[NumCSRPostWIndex]) begin \ $display("%tns, %d instrs: CSR %s = %016x, does not equal expected value %016x", $time, AttemptedInstructionCount, ExpectedCSRArrayW[NumCSRPostWIndex], CSR, ExpectedCSRArrayValueW[NumCSRPostWIndex]); \ if(`DEBUG_TRACE >= 3) fault = 1; \ end \ end // =========== CORE =========== assign checkInstrM = dut.core.ieu.InstrValidM & ~dut.core.priv.priv.trap.InstrPageFaultM & ~dut.core.priv.priv.trap.InterruptM & ~dut.core.StallM; always @(negedge clk) begin `SCAN_NEW_INSTR_FROM_TRACE(E) `SCAN_NEW_INSTR_FROM_TRACE(M) end // step 1: register expected state into the write back stage. always @(posedge clk) begin if (reset) begin ExpectedPCW <= '0; ExpectedInstrW <= '0; textW <= ""; RegWriteW <= ""; ExpectedRegAdrW <= '0; ExpectedRegValueW <= '0; ExpectedIEUAdrW <= '0; MemOpW <= ""; ExpectedMemWriteDataW <= '0; ExpectedMemReadDataW <= '0; NumCSRW <= '0; end else if(~dut.core.StallW) begin if(dut.core.FlushW) begin ExpectedPCW <= '0; ExpectedInstrW <= '0; textW <= ""; RegWriteW <= ""; ExpectedRegAdrW <= '0; ExpectedRegValueW <= '0; ExpectedIEUAdrW <= '0; MemOpW <= ""; ExpectedMemWriteDataW <= '0; ExpectedMemReadDataW <= '0; NumCSRW <= '0; end else if (dut.core.ieu.c.InstrValidM) begin ExpectedPCW <= ExpectedPCM; ExpectedInstrW <= ExpectedInstrM; textW <= textM; RegWriteW <= RegWriteM; ExpectedRegAdrW <= ExpectedRegAdrM; ExpectedRegValueW <= ExpectedRegValueM; ExpectedIEUAdrW <= ExpectedIEUAdrM; MemOpW <= MemOpM; ExpectedMemWriteDataW <= ExpectedMemWriteDataM; ExpectedMemReadDataW <= ExpectedMemReadDataM; NumCSRW <= NumCSRM; for(NumCSRWIndex = 0; NumCSRWIndex < NumCSRM; NumCSRWIndex++) begin ExpectedCSRArrayW[NumCSRWIndex] = ExpectedCSRArrayM[NumCSRWIndex]; ExpectedCSRArrayValueW[NumCSRWIndex] = ExpectedCSRArrayValueM[NumCSRWIndex]; end end #1; // override on special conditions if(~dut.core.StallW) begin if(textW.substr(0,5) == "rdtime") begin //$display("%tns, %d instrs: Releasing force of MTIME_CLINT.", $time, AttemptedInstructionCount); if(!NO_SPOOFING) release dut.uncore.uncore.clint.clint.MTIME; end //if (ExpectedIEUAdrM == 'h10000005) begin //$display("%tns, %d instrs: releasing force of ReadDataM.", $time, AttemptedInstructionCount); //release dut.core.ieu.dp.ReadDataM; //end end end end // step2: make all checks in the write back stage. assign checkInstrW = InstrValidW & ~dut.core.StallW; // trapW will already be invalid in there was an InstrPageFault in the previous instruction. always @(negedge clk) begin #1; // small delay allows interrupt spoofing to happen first // always check PC, instruction bits if (checkInstrW) begin InstrCountW += 1; // print progress message if (AttemptedInstructionCount % 'd100000 == 0) $display("Reached %d instructions", AttemptedInstructionCount); // turn on waves if (AttemptedInstructionCount == INSTR_WAVEON) $stop; // end sim if ((AttemptedInstructionCount == INSTR_LIMIT) & (INSTR_LIMIT!=0)) begin $stop; $stop; end fault = 0; if (`DEBUG_TRACE >= 1) begin `checkEQ("PCW",PCW,ExpectedPCW) //`checkEQ("InstrW",InstrW,ExpectedInstrW) <-- not viable because of // compressed to uncompressed conversion `checkEQ("Instr Count",dut.core.priv.priv.csr.counters.counters.HPMCOUNTER_REGW[2],InstrCountW) #2; // delay 2 ns. if(`DEBUG_TRACE >= 5) begin $display("%tns, %d instrs: Reg Write Address %02d ? expected value: %02d", $time, AttemptedInstructionCount, dut.core.ieu.dp.regf.a3, ExpectedRegAdrW); $display("%tns, %d instrs: RF[%02d] %016x ? expected value: %016x", $time, AttemptedInstructionCount, ExpectedRegAdrW, dut.core.ieu.dp.regf.rf[ExpectedRegAdrW], ExpectedRegValueW); end if (RegWriteW == "GPR") begin `checkEQ("Reg Write Address",dut.core.ieu.dp.regf.a3,ExpectedRegAdrW) $sformat(name,"RF[%02d]",ExpectedRegAdrW); `checkEQ(name, dut.core.ieu.dp.regf.rf[ExpectedRegAdrW], ExpectedRegValueW) end if (MemOpW.substr(0,2) == "Mem") begin if(`DEBUG_TRACE >= 4) $display("\tIEUAdrW: %016x ? expected: %016x", IEUAdrW, ExpectedIEUAdrW); `checkEQ("IEUAdrW",IEUAdrW,ExpectedIEUAdrW) if(MemOpW == "MemR" | MemOpW == "MemRW") begin if(`DEBUG_TRACE >= 4) $display("\tReadDataW: %016x ? expected: %016x", dut.core.ieu.dp.ReadDataW, ExpectedMemReadDataW); `checkEQ("ReadDataW",dut.core.ieu.dp.ReadDataW,ExpectedMemReadDataW) end else if(MemOpW == "MemW" | MemOpW == "MemRW") begin if(`DEBUG_TRACE >= 4) $display("\tWriteDataW: %016x ? expected: %016x", WriteDataW, ExpectedMemWriteDataW); `checkEQ("WriteDataW",ExpectedMemWriteDataW,ExpectedMemWriteDataW) end end // check csr for(NumCSRPostWIndex = 0; NumCSRPostWIndex < NumCSRW; NumCSRPostWIndex++) begin case(ExpectedCSRArrayW[NumCSRPostWIndex]) "mhartid": `checkCSR(`CSR_BASE.csrm.MHARTID_REGW) "mstatus": `checkCSR(`CSR_BASE.csrm.MSTATUS_REGW) "sstatus": `checkCSR(`CSR_BASE.csrs.csrs.SSTATUS_REGW) "mtvec": `checkCSR(`CSR_BASE.csrm.MTVEC_REGW) "mie": `checkCSR(`CSR_BASE.csrm.MIE_REGW) "mideleg": `checkCSR(`CSR_BASE.csrm.MIDELEG_REGW) "medeleg": `checkCSR(`CSR_BASE.csrm.MEDELEG_REGW) "mepc": `checkCSR(`CSR_BASE.csrm.MEPC_REGW) "mtval": `checkCSR(`CSR_BASE.csrm.MTVAL_REGW) "sepc": `checkCSR(`CSR_BASE.csrs.csrs.SEPC_REGW) "scause": `checkCSR(`CSR_BASE.csrs.csrs.SCAUSE_REGW) "stvec": `checkCSR(`CSR_BASE.csrs.csrs.STVEC_REGW) "stval": `checkCSR(`CSR_BASE.csrs.csrs.STVAL_REGW) "mip": begin `checkCSR(`CSR_BASE.csrm.MIP_REGW) if(!NO_SPOOFING) begin if ((ExpectedCSRArrayValueW[NumCSRPostWIndex] & 1<<11) == 0) force `MEIP = 0; if ((ExpectedCSRArrayValueW[NumCSRPostWIndex] & 1<<09) == 0) force `SEIP = 0; if ((ExpectedCSRArrayValueW[NumCSRPostWIndex] & ((1<<11) | (1<<09))) == 0) force `UART_IP = 0; if ((ExpectedCSRArrayValueW[NumCSRPostWIndex] & 1<<07) == 0) force `MTIP = 0; end end endcase end if (fault == 1) begin errorCount +=1; $display("processed %0d instructions with %0d warnings", AttemptedInstructionCount, warningCount); $stop; $stop; end end // if (`DEBUG_TRACE >= 1) end // if (checkInstrW) end // always @ (negedge clk) // New IP spoofing logic globalIntsBecomeEnabled; assign globalIntsBecomeEnabled = (`CSR_BASE.csrm.WriteMSTATUSM || `CSR_BASE.csrs.csrs.WriteSSTATUSM) && (|(`CSR_BASE.CSRWriteValM & (~`CSR_BASE.csrm.MSTATUS_REGW) & 32'h22)); logic checkInterruptM; assign checkInterruptM = dut.core.ieu.InstrValidM & ~dut.core.priv.priv.trap.InstrPageFaultM & ~dut.core.priv.priv.trap.InterruptM; always @(negedge clk) begin if(checkInterruptM) begin if((interruptInstrCount+1) == AttemptedInstructionCount) begin if(!NO_SPOOFING) begin case (interruptCauseVal) 11: begin force `MEIP = 1; force `UART_IP = 1; end 09: begin force `SEIP = 1; force `UART_IP = 1; end 07: force `MTIP = 1; default: $display("Unsupported interrupt in interrupts.txt. cause = %0d",interruptCauseVal); endcase $display("Forcing interrupt."); end `SCAN_NEW_INTERRUPT if (globalIntsBecomeEnabled) begin $display("Enabled global interrupts"); // The idea here is if a CSR instruction causes an interrupt by // enabling interrupts, that CSR instruction will commit. end else begin // Other instructions, however, will get interrupted and not // commit, so we don't want our W-stage checker to look for them // and get confused when it doesn't find them. garbageInt = $fgets(garbageString,traceFileE); garbageInt = $fgets(garbageString,traceFileM); AttemptedInstructionCount += 1; end end end end /////////////////////////////////////////////////////////////////////////////// //////////////////////////////// Extra Features /////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // Function Tracking FunctionName FunctionName(.reset(reset), .clk(clk), .ProgramAddrMapFile(ProgramAddrMapFile), .ProgramLabelMapFile(ProgramLabelMapFile)); // Instr Opcode Tracking // For waveview convenience string InstrFName, InstrDName, InstrEName, InstrMName, InstrWName; instrTrackerTB it(clk, reset, dut.core.ieu.dp.FlushE, dut.core.ifu.InstrRawF[31:0], dut.core.ifu.InstrD, dut.core.ifu.InstrE, dut.core.ifu.InstrM, InstrW, InstrFName, InstrDName, InstrEName, InstrMName, InstrWName); // ------------------ // Address Translator // ------------------ /** * Walk the page table stored in ram according to sv39 logic and translate a * virtual address to a physical address. * * See section 4.3.2 of the RISC-V Privileged specification for a full * explanation of the below algorithm. */ logic SvMode, PTE_R, PTE_X; logic [`XLEN-1:0] SATP, PTE; logic [55:0] BaseAdr, PAdr; logic [8:0] VPN [2:0]; logic [11:0] Offset; function logic [`XLEN-1:0] adrTranslator( input logic [`XLEN-1:0] adrIn); begin int i; // Grab the SATP register from privileged unit SATP = dut.core.priv.priv.csr.SATP_REGW; // Split the virtual address into page number segments and offset VPN[2] = adrIn[38:30]; VPN[1] = adrIn[29:21]; VPN[0] = adrIn[20:12]; Offset = adrIn[11:0]; // We do not support sv48; only sv39 SvMode = SATP[63]; // Only perform translation if translation is on and the processor is not // in machine mode if (SvMode & (dut.core.priv.priv.PrivilegeModeW != `M_MODE)) begin BaseAdr = SATP[43:0] << 12; for (i = 2; i >= 0; i--) begin PAdr = BaseAdr + (VPN[i] << 3); // ram.memory.RAM is 64-bit addressed. PAdr specifies a byte. We right shift // by 3 (the PTE size) to get the requested 64-bit PTE. PTE = dut.uncore.uncore.ram.ram.memory.RAM[PAdr >> 3]; PTE_R = PTE[1]; PTE_X = PTE[3]; if (PTE_R | PTE_X) begin // Leaf page found break; end else begin // Go to next level of table BaseAdr = PTE[53:10] << 12; end end // Determine which parts of the PTE page number to use based on the // level of the page table we reached. if (i == 2) begin // Gigapage assign adrTranslator = {8'b0, PTE[53:28], VPN[1], VPN[0], Offset}; end else if (i == 1) begin // Megapage assign adrTranslator = {8'b0, PTE[53:19], VPN[0], Offset}; end else begin // Kilopage assign adrTranslator = {8'b0, PTE[53:10], Offset}; end end else begin // Direct translation if address translation is not on assign adrTranslator = adrIn; end end endfunction endmodule