diff --git a/.github/workflows/test-uarch.yml b/.github/workflows/test-uarch.yml index c98e14ebf8c..87e02ca8365 100644 --- a/.github/workflows/test-uarch.yml +++ b/.github/workflows/test-uarch.yml @@ -21,6 +21,7 @@ jobs: - "block/exu_mul" - "block/exu_div" - "block/iccm" + - "block/dccm" env: CCACHE_DIR: "/opt/verification/.cache/" VERILATOR_VERSION: v5.010 diff --git a/verification/block/dccm/Makefile b/verification/block/dccm/Makefile new file mode 100644 index 00000000000..745009fb3a3 --- /dev/null +++ b/verification/block/dccm/Makefile @@ -0,0 +1,23 @@ + +null := +space := $(null) # +comma := , + +CURDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +SRCDIR := $(abspath $(CURDIR)../../../../design) + +TEST_FILES = $(sort $(wildcard test_*.py)) + +MODULE ?= $(subst $(space),$(comma),$(subst .py,,$(TEST_FILES))) +TOPLEVEL = el2_lsu_dccm_mem_wrapper + +VERILOG_SOURCES = \ + $(CURDIR)/dccm/el2_lsu_dccm_mem_wrapper.sv \ + $(SRCDIR)/lsu/el2_lsu_dccm_mem.sv \ + $(SRCDIR)/lib/mem_lib.sv + +# Undefine the VERILATOR macro to make the code use actual RAM cells instead +# of simulation models +EXTRA_ARGS += -UVERILATOR + +include $(CURDIR)/../common.mk diff --git a/verification/block/dccm/el2_lsu_dccm_mem_wrapper.sv b/verification/block/dccm/el2_lsu_dccm_mem_wrapper.sv new file mode 100644 index 00000000000..90b1a7d7cdd --- /dev/null +++ b/verification/block/dccm/el2_lsu_dccm_mem_wrapper.sv @@ -0,0 +1,55 @@ +module el2_lsu_dccm_mem_wrapper + import el2_pkg::*; +#( + `include "el2_param.vh" +) +( + input logic clk, + input logic active_clk, + input logic rst_l, + input logic clk_override, + + input logic dccm_wren, + input logic dccm_rden, + input logic [pt.DCCM_BITS-1:0] dccm_wr_addr_lo, + input logic [pt.DCCM_BITS-1:0] dccm_wr_addr_hi, + input logic [pt.DCCM_BITS-1:0] dccm_rd_addr_lo, + input logic [pt.DCCM_BITS-1:0] dccm_rd_addr_hi, + input logic [pt.DCCM_FDATA_WIDTH-1:0] dccm_wr_data_lo, + input logic [pt.DCCM_FDATA_WIDTH-1:0] dccm_wr_data_hi, + + // el2_dccm_ext_in_pkt_t + input logic dccm_ext_in_pkt_TEST1, + input logic dccm_ext_in_pkt_RME, + input logic [3:0] dccm_ext_in_pkt_RM, + input logic dccm_ext_in_pkt_LS, + input logic dccm_ext_in_pkt_DS, + input logic dccm_ext_in_pkt_SD, + input logic dccm_ext_in_pkt_TEST_RNM, + input logic dccm_ext_in_pkt_BC1, + input logic dccm_ext_in_pkt_BC2, + + output logic [pt.DCCM_FDATA_WIDTH-1:0] dccm_rd_data_lo, + output logic [pt.DCCM_FDATA_WIDTH-1:0] dccm_rd_data_hi, + + input logic scan_mode +); + + // Pack dccm_ext_in_pkt + el2_dccm_ext_in_pkt_t [pt.DCCM_NUM_BANKS-1:0] dccm_ext_in_pkt; + + for (genvar i = 0; i < pt.DCCM_NUM_BANKS; i++) begin + assign dccm_ext_in_pkt[i].TEST1 = dccm_ext_in_pkt_TEST1; + assign dccm_ext_in_pkt[i].RME = dccm_ext_in_pkt_RME; + assign dccm_ext_in_pkt[i].RM = dccm_ext_in_pkt_RM; + assign dccm_ext_in_pkt[i].LS = dccm_ext_in_pkt_LS; + assign dccm_ext_in_pkt[i].DS = dccm_ext_in_pkt_DS; + assign dccm_ext_in_pkt[i].SD = dccm_ext_in_pkt_SD; + assign dccm_ext_in_pkt[i].TEST_RNM = dccm_ext_in_pkt_TEST_RNM; + assign dccm_ext_in_pkt[i].BC1 = dccm_ext_in_pkt_BC1; + assign dccm_ext_in_pkt[i].BC2 = dccm_ext_in_pkt_BC2; + end + + el2_lsu_dccm_mem mem (.*); + +endmodule diff --git a/verification/block/dccm/test_readwrite.py b/verification/block/dccm/test_readwrite.py new file mode 100644 index 00000000000..df5661daa20 --- /dev/null +++ b/verification/block/dccm/test_readwrite.py @@ -0,0 +1,61 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 +import random + +import pyuvm +from cocotb.triggers import ClockCycles +from pyuvm import * +from testbench import BaseTest, MemReadItem, MemWriteItem + +# ============================================================================= + + +class ReadWriteSequence(uvm_sequence): + """ + A sequencer that issues a random sequence of writes followed by a + randomized sequence of reads containing the same addresses previously + written to. + """ + + def __init__(self, name): + super().__init__(name) + + async def body(self): + count = ConfigDB().get(None, "", "TEST_ITERATIONS") + burst = ConfigDB().get(None, "", "TEST_BURST_LEN") + + awidth = ConfigDB().get(None, "", "DCCM_BITS") + dwidth = ConfigDB().get(None, "", "DCCM_FDATA_WIDTH") + + for i in range(count): + + # Randomize unique addresses (aligned) + addrs = set([ + random.randrange(0, 1 << awidth) & 0xFFFFFFFC + for i in range(burst) + ]) + + # Issue writes, randomize data + for addr in addrs: + data = random.randrange(0, 1 << dwidth) + + item = MemWriteItem(addr, data) + await self.start_item(item) + await self.finish_item(item) + + # Issue random reads for written addresses + random.shuffle(list(set(addrs))) + for addr in addrs: + item = MemReadItem(addr, data) + await self.start_item(item) + await self.finish_item(item) + + +@pyuvm.test() +class TestReadWrite(BaseTest): + def end_of_elaboration_phase(self): + super().end_of_elaboration_phase() + self.seq = ReadWriteSequence("stimulus") + + async def run(self): + await self.seq.start(self.env.mem_seqr) diff --git a/verification/block/dccm/testbench.py b/verification/block/dccm/testbench.py new file mode 100644 index 00000000000..8332e8a9282 --- /dev/null +++ b/verification/block/dccm/testbench.py @@ -0,0 +1,295 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import os + +import pyuvm +from cocotb.clock import Clock +from cocotb.triggers import ( + ClockCycles, + FallingEdge, + RisingEdge, +) +from pyuvm import * + +# ============================================================================== + + +class MemWriteItem(uvm_sequence_item): + """ + Memory write item + """ + + def __init__(self, addr, data): + super().__init__("MemWriteItem") + self.addr = addr + self.data = data + + +class MemReadItem(uvm_sequence_item): + """ + Memory read item + """ + + def __init__(self, addr, data=None): + super().__init__("MemReadItem") + self.addr = addr + self.data = data + + +# ============================================================================== + + +class MemDriver(uvm_driver): + """ + Memory interface driver + """ + + def __init__(self, *args, **kwargs): + self.dut = kwargs["dut"] + del kwargs["dut"] + super().__init__(*args, **kwargs) + + async def run_phase(self): + while True: + it = await self.seq_item_port.get_next_item() + + # Write + if isinstance(it, MemWriteItem): + # Wait for rising edge, do the write + await RisingEdge(self.dut.clk) + self.dut.dccm_wren.value = 1 + + self.dut.dccm_wr_addr_lo.value = it.addr + self.dut.dccm_wr_data_lo.value = it.data + + self.dut.dccm_wr_addr_hi.value = it.addr + self.dut.dccm_wr_data_hi.value = it.data + + # Wait for rising edge, deassert write + await RisingEdge(self.dut.clk) + self.dut.dccm_wren.value = 0 + + # Read + elif isinstance(it, MemReadItem): + # Wait for rising edge, do the read + await RisingEdge(self.dut.clk) + self.dut.dccm_rden.value = 1 + + self.dut.dccm_rd_addr_lo.value = it.addr + self.dut.dccm_rd_addr_hi.value = it.addr + + # Wait for rising edge, deassert read + await RisingEdge(self.dut.clk) + self.dut.dccm_rden.value = 0 + + else: + raise RuntimeError("Unknown item '{}'".format(type(it))) + + self.seq_item_port.item_done() + + +class MemMonitor(uvm_component): + """ + Memory interface monitor + """ + + def __init__(self, *args, **kwargs): + self.dut = kwargs["dut"] + del kwargs["dut"] + super().__init__(*args, **kwargs) + + def build_phase(self): + self.ap = uvm_analysis_port("ap", self) + + async def run_phase(self): + while True: + # Act on rising edges + await RisingEdge(self.dut.clk) + + # Since the driver drives both lo and hi with the same values + # here we sample only lo + + # Write + if self.dut.dccm_wren.value: + addr = int(self.dut.dccm_wr_addr_lo) + data = int(self.dut.dccm_wr_data_lo) + self.ap.write(MemWriteItem(addr, data)) + + # Read + if self.dut.dccm_rden.value: + addr = int(self.dut.dccm_rd_addr_lo) + + # Wait additional clock cycle + await RisingEdge(self.dut.clk) + + data = int(self.dut.dccm_rd_data_lo) + self.ap.write(MemReadItem(addr, data)) + + +# ============================================================================== + + +class Scoreboard(uvm_component): + """ + A scoreboard that tracks memory writes and compares them agains data read + from the memory. It also checks if both reads and writes took place + """ + + def __init__(self, name, parent): + super().__init__(name, parent) + self.passed = None + + def build_phase(self): + self.fifo = uvm_tlm_analysis_fifo("fifo", self) + self.port = uvm_get_port("port", self) + + def connect_phase(self): + self.port.connect(self.fifo.get_export) + + def check_phase(self): + did_write = False + did_read = False + mem_content = dict() + + # Process items + while self.port.can_get(): + # Get an item + got_item, item = self.port.try_get() + assert got_item + + # Initially pass + if self.passed is None: + self.passed = True + + # Memory write + if isinstance(item, MemWriteItem): + mem_content[item.addr] = item.data + did_write = True + + self.logger.debug("[0x{:08X}] <= 0x{:08X}".format(item.addr, item.data)) + + # Memory read + elif isinstance(item, MemReadItem): + data = mem_content.get(item.addr, None) + did_read = True + + self.logger.debug( + "[0x{:08X}] == 0x{:08X} vs. 0x{:08X} {}".format( + item.addr, item.data, data, item.data == data + ) + ) + + if data != item.data: + self.logger.error( + "Data mismatch, mem[0x{:08X}] is 0x{:08X}, should be 0x{:08X}".format( + item.addr, item.data, data + ) + ) + self.passed = False + + # There were no writes + if not did_write: + self.logger.error("There were no writes") + self.passed = False + + # There were no reads + if not did_read: + self.logger.error("There were no reads") + self.passed = False + + def final_phase(self): + if not self.passed: + self.logger.critical("{} reports a failure".format(type(self))) + assert False + + +# ============================================================================== + + +class BaseEnv(uvm_env): + """ + Base PyUVM test environment + """ + + def build_phase(self): + # Config + ConfigDB().set(None, "*", "TEST_CLK_PERIOD", 1) + ConfigDB().set(None, "*", "TEST_ITERATIONS", 50) + ConfigDB().set(None, "*", "TEST_BURST_LEN", 10) + + ConfigDB().set(None, "*", "DCCM_BITS", 0x10) + ConfigDB().set(None, "*", "DCCM_FDATA_WIDTH", 0x20) + + # Sequencers + self.mem_seqr = uvm_sequencer("mem_seqr", self) + + # Driver + self.mem_drv = MemDriver("mem_drv", self, dut=cocotb.top) + + # Monitor + self.mem_mon = MemMonitor("mem_mon", self, dut=cocotb.top) + + # Scoreboard + self.scoreboard = Scoreboard("scoreboard", self) + + def connect_phase(self): + self.mem_drv.seq_item_port.connect(self.mem_seqr.seq_item_export) + self.mem_mon.ap.connect(self.scoreboard.fifo.analysis_export) + + +# ============================================================================== + + +class BaseTest(uvm_test): + """ + Base test for the module + """ + + def __init__(self, name, parent, env_class=BaseEnv): + super().__init__(name, parent) + self.env_class = env_class + + # Synchronize pyuvm logging level with cocotb logging level. Unclear + # why it does not happen automatically. + level = logging.getLevelName(os.environ.get("COCOTB_LOG_LEVEL", "INFO")) + uvm_report_object.set_default_logging_level(level) + + def build_phase(self): + self.env = self.env_class("env", self) + + def start_clock(self, name): + period = ConfigDB().get(None, "", "TEST_CLK_PERIOD") + sig = getattr(cocotb.top, name) + clock = Clock(sig, period, units="ns") + cocotb.start_soon(clock.start(start_high=False)) + + async def do_reset(self): + cocotb.top.rst_l.value = 0 + await ClockCycles(cocotb.top.clk, 2) + await FallingEdge(cocotb.top.clk) + cocotb.top.rst_l.value = 1 + + async def run_phase(self): + self.raise_objection() + + # Start clocks + self.start_clock("clk") + self.start_clock("active_clk") + + # Issue reset + await self.do_reset() + + # Wait some cycles + await ClockCycles(cocotb.top.clk, 2) + + # Run the actual test + await self.run() + + # Wait some cycles + await ClockCycles(cocotb.top.clk, 10) + + self.drop_objection() + + async def run(self): + raise NotImplementedError() diff --git a/verification/block/noxfile.py b/verification/block/noxfile.py index c09bb40e645..41ab3b32487 100644 --- a/verification/block/noxfile.py +++ b/verification/block/noxfile.py @@ -168,7 +168,6 @@ def pic_gw_verify(session, blockName, testName, coverage): def dma_verify(session, blockName, testName, coverage): verify_block(session, blockName, testName, coverage) - @nox.session(tags=["tests"]) @nox.parametrize("blockName", ["ifu_compress"]) @nox.parametrize("testName", ["test_compress"]) @@ -227,6 +226,17 @@ def exu_div_verify(session, blockName, testName, coverage): def iccm_verify(session, blockName, testName, coverage): verify_block(session, blockName, testName, coverage) +@nox.session(tags=["tests"]) +@nox.parametrize("blockName", ["dccm"]) +@nox.parametrize( + "testName", + [ + "test_readwrite", + ], +) +@nox.parametrize("coverage", coverageTypes) +def dccm_verify(session, blockName, testName, coverage): + verify_block(session, blockName, testName, coverage) @nox.session() def isort(session: nox.Session) -> None: