/////////////////////////////////////////// // testbench.sv // // Written: David_Harris@hmc.edu 9 January 2021 // Modified: // // Purpose: Wally Testbench and helper modules // Applies test programs from the riscv-arch-test and Imperas suites // // 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 "config.vh" // This is set from the command line script // `define USE_IMPERAS_DV `ifdef USE_IMPERAS_DV `include "idv/idv.svh" `endif import cvw::*; module testbench; parameter DEBUG=0; `ifdef USE_IMPERAS_DV import idvPkg::*; import rvviApiPkg::*; import idvApiPkg::*; `endif `include "parameter-defs.vh" logic clk; logic reset_ext, reset; logic [P.XLEN-1:0] testadr, testadrNoBase; string InstrFName, InstrDName, InstrEName, InstrMName, InstrWName; logic [31:0] InstrW; logic [3:0] dummy; logic [P.AHBW-1:0] HRDATAEXT; logic HREADYEXT, HRESPEXT; logic HSELEXTSDC; logic [P.PA_BITS-1:0] HADDR; logic [P.AHBW-1:0] HWDATA; logic [P.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 HCLK, HRESETn; logic [P.XLEN-1:0] PCW; logic [31:0] NextInstrE, InstrM; string ProgramAddrMapFile, ProgramLabelMapFile; integer ProgramAddrLabelArray [string] = '{ "begin_signature" : 0, "tohost" : 0 }; logic DCacheFlushDone, DCacheFlushStart; string testName; string memfilename, testDir, adrstr, elffilename; logic [31:0] GPIOIN, GPIOOUT, GPIOEN; logic UARTSin, UARTSout; logic SPIIn, SPIOut; logic [3:0] SPICS; logic SDCIntr; logic HREADY; logic HSELEXT; logic InitializingMemories; integer ResetCount, ResetThreshold; logic InReset; // Imperas look here. initial begin ResetCount = 0; ResetThreshold = 2; InReset = 1; testadr = 0; testadrNoBase = 0; if ($value$plusargs("testDir=%s", testDir)) begin memfilename = {testDir, "/ref/ref.elf.memfile"}; elffilename = {testDir, "/ref/ref.elf"}; $display($sformatf("%m @ t=%0t: loading testDir %0s", $time, testDir)); end else begin $error("Must specify test directory using plusarg testDir"); end if (P.BUS_SUPPORTED) $readmemh(memfilename, dut.uncore.uncore.ram.ram.memory.RAM); else $error("Imperas test bench requires BUS."); ProgramAddrMapFile = {testDir, "/ref/ref.elf.objdump.addr"}; ProgramLabelMapFile = {testDir, "/ref/ref.elf.objdump.lab"}; // declare memory labels that interest us, the updateProgramAddrLabelArray task will find the addr of each label and fill the array // to expand, add more elements to this array and initialize them to zero (also initilaize them to zero at the start of the next test) updateProgramAddrLabelArray(ProgramAddrMapFile, ProgramLabelMapFile, ProgramAddrLabelArray); $display("Read memfile %s", memfilename); end `ifdef USE_IMPERAS_DV rvviTrace #(.XLEN(P.XLEN), .FLEN(P.FLEN)) rvvi(); wallyTracer #(P) 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 IDV_MAX_ERRORS = 3; // Initialize REF (do this before initializing the DUT) if (!rvviVersionCheck(RVVI_API_VERSION)) begin $display($sformatf("%m @ t=%0t: Expecting RVVI API version %0d.", $time, RVVI_API_VERSION)); $fatal; 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, 39)); void'(rvviRefConfigSetInt(IDV_CONFIG_MAX_NET_LATENCY_RETIREMENTS, 6)); if (!rvviRefInit(elffilename)) begin $display($sformatf("%m @ t=%0t: rvviRefInit failed", $time)); $fatal; 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 // 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 (P.CLINT_SUPPORTED) begin void'(rvviRefMemorySetVolatile(P.CLINT_BASE, (P.CLINT_BASE + P.CLINT_RANGE))); end if (P.GPIO_SUPPORTED) begin void'(rvviRefMemorySetVolatile(P.GPIO_BASE, (P.GPIO_BASE + P.GPIO_RANGE))); end if (P.UART_SUPPORTED) begin void'(rvviRefMemorySetVolatile(P.UART_BASE, (P.UART_BASE + P.UART_RANGE))); end if (P.PLIC_SUPPORTED) begin void'(rvviRefMemorySetVolatile(P.PLIC_BASE, (P.PLIC_BASE + P.PLIC_RANGE))); end if (P.SDC_SUPPORTED) begin void'(rvviRefMemorySetVolatile(P.SDC_BASE, (P.SDC_BASE + P.SDC_RANGE))); end if (P.SPI_SUPPORTED) begin void'(rvviRefMemorySetVolatile(P.SPI_BASE, (P.SPI_BASE + P.SPI_RANGE))); end if(P.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!!!! end always @(dut.core.priv.priv.csr.csri.MIP_REGW[7]) void'(rvvi.net_push("MTimerInterrupt", dut.core.priv.priv.csr.csri.MIP_REGW[7])); always @(dut.core.priv.priv.csr.csri.MIP_REGW[11]) void'(rvvi.net_push("MExternalInterrupt", dut.core.priv.priv.csr.csri.MIP_REGW[11])); always @(dut.core.priv.priv.csr.csri.MIP_REGW[9]) void'(rvvi.net_push("SExternalInterrupt", dut.core.priv.priv.csr.csri.MIP_REGW[9])); always @(dut.core.priv.priv.csr.csri.MIP_REGW[3]) void'(rvvi.net_push("MSWInterrupt", dut.core.priv.priv.csr.csri.MIP_REGW[3])); always @(dut.core.priv.priv.csr.csri.MIP_REGW[1]) void'(rvvi.net_push("SSWInterrupt", dut.core.priv.priv.csr.csri.MIP_REGW[1])); always @(dut.core.priv.priv.csr.csri.MIP_REGW[5]) void'(rvvi.net_push("STimerInterrupt", dut.core.priv.priv.csr.csri.MIP_REGW[5])); final begin void'(rvviRefShutdown()); end `endif flopenr #(P.XLEN) PCWReg(clk, reset, ~dut.core.ieu.dp.StallW, dut.core.ifu.PCM, PCW); flopenr #(32) InstrWReg(clk, reset, ~dut.core.ieu.dp.StallW, InstrM, InstrW); // check assertions for a legal configuration riscvassertions #(P) riscvassertions(); // instantiate device to be tested assign GPIOIN = 0; assign UARTSin = 1; if(P.EXT_MEM_SUPPORTED) begin ram_ahb #(.BASE(P.EXT_MEM_BASE), .RANGE(P.EXT_MEM_RANGE)) ram (.HCLK, .HRESETn, .HADDR, .HWRITE, .HTRANS, .HWDATA, .HSELRam(HSELEXT), .HREADRam(HRDATAEXT), .HREADYRam(HREADYEXT), .HRESPRam(HRESPEXT), .HREADY, .HWSTRB); end else begin assign HREADYEXT = 1; assign HRESPEXT = 0; assign HRDATAEXT = 0; end if(P.SDC_SUPPORTED) begin : sdcard // *** fix later /* -----\/----- EXCLUDED -----\/----- sdModel sdcard (.sdClk(SDCCLK), .cmd(SDCCmd), .dat(SDCDat)); assign SDCCmd = SDCCmdOE ? SDCCmdOut : 1'bz; assign SDCCmdIn = SDCCmd; assign SDCDatIn = SDCDat; -----/\----- EXCLUDED -----/\----- */ assign SDCIntr = 0; end else begin assign SDCIntr = 0; end wallypipelinedsoc #(P) dut(.clk, .reset_ext, .reset, .HRDATAEXT, .HREADYEXT, .HRESPEXT, .HSELEXT, .HSELEXTSDC, .HCLK, .HRESETn, .HADDR, .HWDATA, .HWSTRB, .HWRITE, .HSIZE, .HBURST, .HPROT, .HTRANS, .HMASTLOCK, .HREADY, .TIMECLK(1'b0), .GPIOIN, .GPIOOUT, .GPIOEN, .UARTSin, .UARTSout, .SDCIntr, .SPICS, .SPIOut, .SPIIn); // Track names of instructions instrTrackerTB it(clk, reset, dut.core.ieu.dp.FlushE, dut.core.ifu.InstrRawF[31:0], dut.core.ifu.InstrD, dut.core.ifu.InstrE, InstrM, InstrW, InstrFName, InstrDName, InstrEName, InstrMName, InstrWName); // initialize tests // generate clock to sequence tests always begin clk = 1; # 5; clk = 0; # 5; // if ($time % 100000 == 0) $display("Time is %0t", $time); end // check results assign reset_ext = InReset; always @(negedge clk) begin InitializingMemories = 0; if(InReset == 1) begin // once the test inidicates it's done we need to immediately hold reset for a number of cycles. if(ResetCount < ResetThreshold) ResetCount = ResetCount + 1; else begin // hit reset threshold so we remove reset. InReset = 0; ResetCount = 0; end end end // always @ (negedge clk) // track the current function or global label if (DEBUG == 1) begin : FunctionName FunctionName #(P) FunctionName(.reset(reset), .clk(clk), .ProgramAddrMapFile(ProgramAddrMapFile), .ProgramLabelMapFile(ProgramLabelMapFile)); end // Duplicate copy of pipeline registers that are optimized out of some configurations mux2 #(32) FlushInstrMMux(dut.core.ifu.InstrE, dut.core.ifu.nop, dut.core.ifu.FlushM, NextInstrE); flopenr #(32) InstrMReg(clk, reset, ~dut.core.ifu.StallM, NextInstrE, InstrM); // Termination condition // terminate on a specific ECALL after li x3,1 for old Imperas tests, *** remove this when old imperas tests are removed // or sw gp,-56(t0) for new Imperas tests // or sd gp, -56(t0) // or on a jump to self infinite loop (6f) for RISC-V Arch tests logic ecf; // remove this once we don't rely on old Imperas tests with Ecalls if (P.ZICSR_SUPPORTED) assign ecf = dut.core.priv.priv.EcallFaultM; else assign ecf = 0; assign DCacheFlushStart = ecf & (dut.core.ieu.dp.regf.rf[3] == 1 | (dut.core.ieu.dp.regf.we3 & dut.core.ieu.dp.regf.a3 == 3 & dut.core.ieu.dp.regf.wd3 == 1)) | ((InstrM == 32'h6f | InstrM == 32'hfc32a423 | InstrM == 32'hfc32a823) & dut.core.ieu.c.InstrValidM ) | ((dut.core.lsu.IEUAdrM == ProgramAddrLabelArray["tohost"]) & InstrMName == "SW" ); DCacheFlushFSM #(P) DCacheFlushFSM(.clk(clk), .reset(reset), .start(DCacheFlushStart), .done(DCacheFlushDone)); // initialize the branch predictor if (P.BPRED_SUPPORTED == 1) begin genvar adrindex; // Initializing all zeroes into the branch predictor memory. for(adrindex = 0; adrindex < 1024; adrindex++) begin initial begin force dut.core.ifu.bpred.bpred.Predictor.DirPredictor.PHT.mem[adrindex] = 0; force dut.core.ifu.bpred.bpred.TargetPredictor.memory.mem[adrindex] = 0; #1; release dut.core.ifu.bpred.bpred.Predictor.DirPredictor.PHT.mem[adrindex]; release dut.core.ifu.bpred.bpred.TargetPredictor.memory.mem[adrindex]; end end end watchdog #(P.XLEN, 1000000) watchdog(.clk, .reset); // check if PCW is stuck endmodule /* verilator lint_on STMTDLY */ /* verilator lint_on WIDTH */ task automatic updateProgramAddrLabelArray; input string ProgramAddrMapFile, ProgramLabelMapFile; inout integer ProgramAddrLabelArray [string]; // Gets the memory location of begin_signature integer ProgramLabelMapFP, ProgramAddrMapFP; ProgramLabelMapFP = $fopen(ProgramLabelMapFile, "r"); ProgramAddrMapFP = $fopen(ProgramAddrMapFile, "r"); if (ProgramLabelMapFP & ProgramAddrMapFP) begin // check we found both files while (!$feof(ProgramLabelMapFP)) begin string label, adrstr; integer returncode; returncode = $fscanf(ProgramLabelMapFP, "%s\n", label); returncode = $fscanf(ProgramAddrMapFP, "%s\n", adrstr); if (ProgramAddrLabelArray.exists(label)) ProgramAddrLabelArray[label] = adrstr.atohex(); end end $fclose(ProgramLabelMapFP); $fclose(ProgramAddrMapFP); endtask