From 18794f33c92eddcbebd5b551665b0f607e12fdff Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Wed, 12 Nov 2025 17:04:07 -0800 Subject: [PATCH] apb: Add APB interconnect module and testbench Signed-off-by: Alex Forencich --- README.md | 1 + src/apb/rtl/taxi_apb_interconnect.sv | 273 ++++++++++++++++++ src/apb/tb/taxi_apb_interconnect/Makefile | 64 ++++ .../test_taxi_apb_interconnect.py | 259 +++++++++++++++++ .../test_taxi_apb_interconnect.sv | 83 ++++++ 5 files changed, 680 insertions(+) create mode 100644 src/apb/rtl/taxi_apb_interconnect.sv create mode 100644 src/apb/tb/taxi_apb_interconnect/Makefile create mode 100644 src/apb/tb/taxi_apb_interconnect/test_taxi_apb_interconnect.py create mode 100644 src/apb/tb/taxi_apb_interconnect/test_taxi_apb_interconnect.sv diff --git a/README.md b/README.md index f7ac34d..ef4d731 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ To facilitate the dual-license model, contributions to the project can only be a * APB * SV interface for APB + * Interconnect * Single-port RAM * Dual-port RAM * AXI diff --git a/src/apb/rtl/taxi_apb_interconnect.sv b/src/apb/rtl/taxi_apb_interconnect.sv new file mode 100644 index 0000000..e113e4e --- /dev/null +++ b/src/apb/rtl/taxi_apb_interconnect.sv @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * APB interconnect + */ +module taxi_apb_interconnect # +( + // Number of downstream APB interfaces + parameter M_CNT = 4, + // Width of address decoder in bits + parameter ADDR_W = 16, + // Number of regions per master interface + parameter M_REGIONS = 1, + // TODO fix parametrization once verilator issue 5890 is fixed + // Master interface base addresses + // M_CNT concatenated fields of M_REGIONS concatenated fields of ADDR_W bits + // set to zero for default addressing based on M_ADDR_W + parameter M_BASE_ADDR = '0, + // Master interface address widths + // M_CNT concatenated fields of M_REGIONS concatenated fields of 32 bits + parameter M_ADDR_W = {M_CNT{{M_REGIONS{32'd24}}}}, + // Secure master (fail operations based on awprot/arprot) + // M_CNT bits + parameter M_SECURE = {M_CNT{1'b0}} +) +( + input wire logic clk, + input wire logic rst, + + /* + * APB slave interface + */ + taxi_apb_if.slv s_apb, + + /* + * APB master interface + */ + taxi_apb_if.mst m_apb[M_CNT] +); + +// extract parameters +localparam DATA_W = s_apb.DATA_W; +// localparam ADDR_W = s_apb.ADDR_W; +localparam STRB_W = s_apb.STRB_W; +localparam logic PAUSER_EN = s_apb.PAUSER_EN && m_apb[0].PAUSER_EN; +localparam PAUSER_W = s_apb.PAUSER_W; +localparam logic PWUSER_EN = s_apb.PWUSER_EN && m_apb[0].PWUSER_EN; +localparam PWUSER_W = s_apb.PWUSER_W; +localparam logic PRUSER_EN = s_apb.PRUSER_EN && m_apb[0].PRUSER_EN; +localparam PRUSER_W = s_apb.PRUSER_W; +localparam logic PBUSER_EN = s_apb.PBUSER_EN && m_apb[0].PBUSER_EN; +localparam PBUSER_W = s_apb.PBUSER_W; + +localparam CL_M_CNT = $clog2(M_CNT); +localparam CL_M_CNT_INT = CL_M_CNT > 0 ? CL_M_CNT : 1; + +localparam [M_CNT*M_REGIONS-1:0][31:0] M_ADDR_W_INT = M_ADDR_W; +localparam [M_CNT-1:0] M_SECURE_INT = M_SECURE; + +// default address computation +function [M_CNT*M_REGIONS-1:0][ADDR_W-1:0] calcBaseAddrs(input [31:0] dummy); + logic [ADDR_W-1:0] base; + logic [ADDR_W-1:0] width; + logic [ADDR_W-1:0] size; + logic [ADDR_W-1:0] mask; + begin + calcBaseAddrs = '0; + base = 0; + for (integer i = 0; i < M_CNT*M_REGIONS; i = i + 1) begin + width = M_ADDR_W_INT[i]; + mask = {ADDR_W{1'b1}} >> (ADDR_W - width); + size = mask + 1; + if (width > 0) begin + if ((base & mask) != 0) begin + base = base + size - (base & mask); // align + end + calcBaseAddrs[i] = base; + base = base + size; // increment + end + end + end +endfunction + +localparam [M_CNT*M_REGIONS-1:0][ADDR_W-1:0] M_BASE_ADDR_INT = M_BASE_ADDR != 0 ? (M_CNT*M_REGIONS*ADDR_W)'(M_BASE_ADDR) : calcBaseAddrs(0); + +// check configuration +if (s_apb.ADDR_W != ADDR_W) + $fatal(0, "Error: Interface ADDR_W parameter mismatch (instance %m)"); + +if (m_apb.DATA_W != DATA_W) + $fatal(0, "Error: Interface DATA_W parameter mismatch (instance %m)"); + +if (m_apb.STRB_W != STRB_W) + $fatal(0, "Error: Interface STRB_W parameter mismatch (instance %m)"); + +initial begin + for (integer i = 0; i < M_CNT*M_REGIONS; i = i + 1) begin + /* verilator lint_off UNSIGNED */ + if (M_ADDR_W_INT[i] != 0 && (M_ADDR_W_INT[i] < $clog2(STRB_W) || M_ADDR_W_INT[i] > ADDR_W)) begin + $error("Error: address width out of range (instance %m)"); + $finish; + end + /* verilator lint_on UNSIGNED */ + end + + $display("Addressing configuration for apb_interconnect instance %m"); + for (integer i = 0; i < M_CNT*M_REGIONS; i = i + 1) begin + if (M_ADDR_W_INT[i] != 0) begin + $display("%2d (%2d): %x / %02d -- %x-%x", + i/M_REGIONS, i%M_REGIONS, + M_BASE_ADDR_INT[i], + M_ADDR_W_INT[i], + M_BASE_ADDR_INT[i] & ({ADDR_W{1'b1}} << M_ADDR_W_INT[i]), + M_BASE_ADDR_INT[i] | ({ADDR_W{1'b1}} >> (ADDR_W - M_ADDR_W_INT[i])) + ); + end + end + + for (integer i = 0; i < M_CNT*M_REGIONS; i = i + 1) begin + if ((M_BASE_ADDR_INT[i] & (2**M_ADDR_W_INT[i]-1)) != 0) begin + $display("Region not aligned:"); + $display("%2d (%2d): %x / %2d -- %x-%x", + i/M_REGIONS, i%M_REGIONS, + M_BASE_ADDR_INT[i], + M_ADDR_W_INT[i], + M_BASE_ADDR_INT[i] & ({ADDR_W{1'b1}} << M_ADDR_W_INT[i]), + M_BASE_ADDR_INT[i] | ({ADDR_W{1'b1}} >> (ADDR_W - M_ADDR_W_INT[i])) + ); + $error("Error: address range not aligned (instance %m)"); + $finish; + end + end + + for (integer i = 0; i < M_CNT*M_REGIONS; i = i + 1) begin + for (integer j = i+1; j < M_CNT*M_REGIONS; j = j + 1) begin + if (M_ADDR_W_INT[i] != 0 && M_ADDR_W_INT[j] != 0) begin + if (((M_BASE_ADDR_INT[i] & ({ADDR_W{1'b1}} << M_ADDR_W_INT[i])) <= (M_BASE_ADDR_INT[j] | ({ADDR_W{1'b1}} >> (ADDR_W - M_ADDR_W_INT[j])))) + && ((M_BASE_ADDR_INT[j] & ({ADDR_W{1'b1}} << M_ADDR_W_INT[j])) <= (M_BASE_ADDR_INT[i] | ({ADDR_W{1'b1}} >> (ADDR_W - M_ADDR_W_INT[i]))))) begin + $display("Overlapping regions:"); + $display("%2d (%2d): %x / %2d -- %x-%x", + i/M_REGIONS, i%M_REGIONS, + M_BASE_ADDR_INT[i], + M_ADDR_W_INT[i], + M_BASE_ADDR_INT[i] & ({ADDR_W{1'b1}} << M_ADDR_W_INT[i]), + M_BASE_ADDR_INT[i] | ({ADDR_W{1'b1}} >> (ADDR_W - M_ADDR_W_INT[i])) + ); + $display("%2d (%2d): %x / %2d -- %x-%x", + j/M_REGIONS, j%M_REGIONS, + M_BASE_ADDR_INT[j], + M_ADDR_W_INT[j], + M_BASE_ADDR_INT[j] & ({ADDR_W{1'b1}} << M_ADDR_W_INT[j]), + M_BASE_ADDR_INT[j] | ({ADDR_W{1'b1}} >> (ADDR_W - M_ADDR_W_INT[j])) + ); + $error("Error: address ranges overlap (instance %m)"); + $finish; + end + end + end + end +end + +logic [CL_M_CNT_INT-1:0] sel_reg = '0; +logic act_reg = 1'b0; + +logic s_apb_pready_reg = 1'b0; +logic [DATA_W-1:0] s_apb_prdata_reg = '0; +logic s_apb_pslverr_reg = 1'b0; +logic [PRUSER_W-1:0] s_apb_pruser_reg = '0; +logic [PBUSER_W-1:0] s_apb_pbuser_reg = '0; + +logic [ADDR_W-1:0] m_apb_paddr_reg = '0; +logic [2:0] m_apb_pprot_reg = '0; +logic [M_CNT-1:0] m_apb_psel_reg = '0; +logic m_apb_penable_reg = 1'b0; +logic m_apb_pwrite_reg = 1'b0; +logic [DATA_W-1:0] m_apb_pwdata_reg = '0; +logic [STRB_W-1:0] m_apb_pstrb_reg = '0; +logic [PAUSER_W-1:0] m_apb_pauser_reg = '0; +logic [PWUSER_W-1:0] m_apb_pwuser_reg = '0; + +wire [M_CNT-1:0] m_apb_pready; +wire [DATA_W-1:0] m_apb_prdata[M_CNT]; +wire m_apb_pslverr[M_CNT]; +wire [PRUSER_W-1:0] m_apb_pruser[M_CNT]; +wire [PBUSER_W-1:0] m_apb_pbuser[M_CNT]; + +assign s_apb.pready = s_apb_pready_reg; +assign s_apb.prdata = s_apb_prdata_reg; +assign s_apb.pslverr = s_apb_pslverr_reg; +assign s_apb.pruser = PRUSER_EN ? s_apb_pruser_reg : '0; +assign s_apb.pbuser = PWUSER_EN ? s_apb_pbuser_reg : '0; + +for (genvar n = 0; n < M_CNT; n += 1) begin + assign m_apb[n].paddr = m_apb_paddr_reg; + assign m_apb[n].pprot = m_apb_pprot_reg; + assign m_apb[n].psel = m_apb_psel_reg[n]; + assign m_apb[n].penable = m_apb_penable_reg; + assign m_apb[n].pwrite = m_apb_pwrite_reg; + assign m_apb[n].pwdata = m_apb_pwdata_reg; + assign m_apb[n].pstrb = m_apb_pstrb_reg; + assign m_apb_pready[n] = m_apb[n].pready; + assign m_apb_prdata[n] = m_apb[n].prdata; + assign m_apb_pslverr[n] = m_apb[n].pslverr; + assign m_apb[n].pauser = PAUSER_EN ? m_apb_pauser_reg : '0; + assign m_apb[n].pwuser = PWUSER_EN ? m_apb_pwuser_reg : '0; + assign m_apb_pruser[n] = m_apb[n].pruser; + assign m_apb_pbuser[n] = m_apb[n].pbuser; +end + +always_ff @(posedge clk) begin + s_apb_pready_reg <= 1'b0; + m_apb_penable_reg <= act_reg && s_apb.penable; + + s_apb_prdata_reg <= m_apb_prdata[sel_reg]; + s_apb_pslverr_reg <= m_apb_pslverr[sel_reg] | (m_apb_psel_reg == 0); + s_apb_pruser_reg <= m_apb_pruser[sel_reg]; + s_apb_pbuser_reg <= m_apb_pbuser[sel_reg]; + + if ((m_apb_psel_reg & ~m_apb_pready) == 0) begin + m_apb_psel_reg <= '0; + m_apb_penable_reg <= 1'b0; + s_apb_pready_reg <= act_reg; + act_reg <= 1'b0; + end + + if (!act_reg) begin + m_apb_paddr_reg <= s_apb.paddr; + m_apb_pprot_reg <= s_apb.pprot; + m_apb_pwrite_reg <= s_apb.pwrite; + m_apb_pwdata_reg <= s_apb.pwdata; + m_apb_pstrb_reg <= s_apb.pstrb; + m_apb_pauser_reg <= s_apb.pauser; + m_apb_pwuser_reg <= s_apb.pwuser; + + m_apb_psel_reg <= '0; + m_apb_penable_reg <= 1'b0; + + if (s_apb.psel && s_apb.penable && !s_apb_pready_reg) begin + act_reg <= 1'b1; + for (integer i = 0; i < M_CNT; i = i + 1) begin + for (integer j = 0; j < M_REGIONS; j = j + 1) begin + if (M_ADDR_W_INT[i*M_REGIONS+j] != 0 && (!M_SECURE_INT[i] || !s_apb.pprot[1]) && (s_apb.paddr >> M_ADDR_W_INT[i*M_REGIONS+j]) == (M_BASE_ADDR_INT[i*M_REGIONS+j] >> M_ADDR_W_INT[i*M_REGIONS+j])) begin + sel_reg <= CL_M_CNT_INT'(i); + m_apb_psel_reg[i] <= 1'b1; + end + end + end + end + end + + if (rst) begin + act_reg <= 1'b0; + s_apb_pready_reg <= 1'b0; + m_apb_psel_reg <= '0; + m_apb_penable_reg <= 1'b0; + end +end + +endmodule + +`resetall diff --git a/src/apb/tb/taxi_apb_interconnect/Makefile b/src/apb/tb/taxi_apb_interconnect/Makefile new file mode 100644 index 0000000..d6f1e24 --- /dev/null +++ b/src/apb/tb/taxi_apb_interconnect/Makefile @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: CERN-OHL-S-2.0 +# +# Copyright (c) 2020-2025 +# +# Authors: +# - Alex Forencich + +TOPLEVEL_LANG = verilog + +SIM ?= verilator +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +RTL_DIR = ../../rtl +LIB_DIR = ../../lib +TAXI_SRC_DIR = $(LIB_DIR)/taxi/src + +DUT = taxi_apb_interconnect +COCOTB_TEST_MODULES = test_$(DUT) +COCOTB_TOPLEVEL = test_$(DUT) +MODULE = $(COCOTB_TEST_MODULES) +TOPLEVEL = $(COCOTB_TOPLEVEL) +VERILOG_SOURCES += $(COCOTB_TOPLEVEL).sv +VERILOG_SOURCES += $(RTL_DIR)/$(DUT).sv +VERILOG_SOURCES += $(RTL_DIR)/taxi_apb_if.sv + +# handle file list files +process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1))) +process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f)) +uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1)) +VERILOG_SOURCES := $(call uniq_base,$(call process_f_files,$(VERILOG_SOURCES))) + +# module parameters +export PARAM_M_CNT := 4 +export PARAM_DATA_W := 32 +export PARAM_ADDR_W := 32 +export PARAM_STRB_W := $(shell expr $(PARAM_DATA_W) / 8 ) +export PARAM_PAUSER_EN := 0 +export PARAM_PAUSER_W := 1 +export PARAM_PWUSER_EN := 0 +export PARAM_PWUSER_W := 1 +export PARAM_PBUSER_EN := 0 +export PARAM_PBUSER_W := 1 +export PARAM_PRUSER_EN := 0 +export PARAM_PRUSER_W := 1 + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v))) +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-WIDTH + + COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v))) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + VERILATOR_TRACE = 1 + endif +endif + +include $(shell cocotb-config --makefiles)/Makefile.sim diff --git a/src/apb/tb/taxi_apb_interconnect/test_taxi_apb_interconnect.py b/src/apb/tb/taxi_apb_interconnect/test_taxi_apb_interconnect.py new file mode 100644 index 0000000..7683dd6 --- /dev/null +++ b/src/apb/tb/taxi_apb_interconnect/test_taxi_apb_interconnect.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: CERN-OHL-S-2.0 +""" + +Copyright (c) 2020-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +""" + +import itertools +import logging +import os +import random + +import cocotb +import cocotb_test.simulator +import pytest +from cocotb.clock import Clock +from cocotb.regression import TestFactory +from cocotb.triggers import RisingEdge, Timer +from cocotbext.axi import ApbBus, ApbMaster, ApbRam + + +class TB(object): + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.start_soon(Clock(dut.clk, 10, units="ns").start()) + + self.apb_master = ApbMaster(ApbBus.from_entity(dut.s_apb), dut.clk, dut.rst) + self.apb_ram = [ + ApbRam(ApbBus.from_entity(ch), dut.clk, dut.rst, size=2**16) + for ch in dut.m_apb + ] + + def set_idle_generator(self, generator=None): + if generator: + self.apb_master.set_pause_generator(generator()) + + def set_backpressure_generator(self, generator=None): + if generator: + for ram in self.apb_ram: + ram.set_pause_generator(generator()) + + async def cycle_reset(self): + self.dut.rst.setimmediatevalue(0) + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst.value = 1 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst.value = 0 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + + +async def run_test_write( + dut, data_in=None, idle_inserter=None, backpressure_inserter=None, m=0 +): + tb = TB(dut) + + byte_lanes = tb.apb_master.byte_lanes + + await tb.cycle_reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + for length in range(1, byte_lanes * 2): + for offset in range(byte_lanes): + tb.log.info("length %d, offset %d", length, offset) + ram_addr = offset + 0x1000 + addr = ram_addr + m * 0x1000000 + test_data = bytearray([x % 256 for x in range(length)]) + + tb.apb_ram[m].write(ram_addr - 128, b"\xaa" * (length + 256)) + + await tb.apb_master.write(addr, test_data) + + tb.log.debug( + "%s", + tb.apb_ram[m].hexdump_str( + (ram_addr & ~0xF) - 16, + (((ram_addr & 0xF) + length - 1) & ~0xF) + 48, + ), + ) + + assert tb.apb_ram[m].read(ram_addr, length) == test_data + assert tb.apb_ram[m].read(ram_addr - 1, 1) == b"\xaa" + assert tb.apb_ram[m].read(ram_addr + length, 1) == b"\xaa" + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +async def run_test_read( + dut, data_in=None, idle_inserter=None, backpressure_inserter=None, m=0 +): + tb = TB(dut) + + byte_lanes = tb.apb_master.byte_lanes + + await tb.cycle_reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + for length in range(1, byte_lanes * 2): + for offset in range(byte_lanes): + tb.log.info("length %d, offset %d", length, offset) + ram_addr = offset + 0x1000 + addr = ram_addr + m * 0x1000000 + test_data = bytearray([x % 256 for x in range(length)]) + + tb.apb_ram[m].write(ram_addr, test_data) + + data = await tb.apb_master.read(addr, length) + + assert data.data == test_data + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +async def run_stress_test(dut, idle_inserter=None, backpressure_inserter=None): + tb = TB(dut) + + await tb.cycle_reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + async def worker(master, offset, aperture, count=16): + for k in range(count): + m = random.randrange(len(tb.apb_ram)) + length = random.randint(1, min(32, aperture)) + addr = offset + random.randint(0, aperture - length) + m * 0x1000000 + test_data = bytearray([x % 256 for x in range(length)]) + + await Timer(random.randint(1, 100), "ns") + + await master.write(addr, test_data) + + await Timer(random.randint(1, 100), "ns") + + data = await master.read(addr, length) + assert data.data == test_data + + workers = [] + + for k in range(16): + workers.append( + cocotb.start_soon( + worker( + tb.apb_master, + k * 0x1000, + 0x1000, + count=16, + ) + ) + ) + + while workers: + await workers.pop(0).join() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +def cycle_pause(): + return itertools.cycle([1, 1, 1, 0]) + + +if getattr(cocotb, "top", None) is not None: + m_cnt = len(cocotb.top.m_apb) + + for test in [run_test_write, run_test_read]: + factory = TestFactory(test) + factory.add_option("idle_inserter", [None, cycle_pause]) + factory.add_option("backpressure_inserter", [None, cycle_pause]) + factory.add_option("m", range(min(m_cnt, 2))) + factory.generate_tests() + + factory = TestFactory(run_stress_test) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, "..", "..", "rtl")) +lib_dir = os.path.abspath(os.path.join(tests_dir, "..", "..", "lib")) +taxi_src_dir = os.path.abspath(os.path.join(lib_dir, "taxi", "src")) + + +def process_f_files(files): + lst = {} + for f in files: + if f[-2:].lower() == ".f": + with open(f, "r") as fp: + l = fp.read().split() + for f in process_f_files([os.path.join(os.path.dirname(f), x) for x in l]): + lst[os.path.basename(f)] = f + else: + lst[os.path.basename(f)] = f + return list(lst.values()) + + +@pytest.mark.parametrize("data_w", [8, 16, 32]) +@pytest.mark.parametrize("m_cnt", [1, 4]) +def test_taxi_apb_interconnect(request, m_cnt, data_w): + dut = "taxi_apb_interconnect" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = module + + verilog_sources = [ + os.path.join(tests_dir, f"{toplevel}.sv"), + os.path.join(rtl_dir, f"{dut}.sv"), + os.path.join(rtl_dir, "taxi_apb_if.sv"), + ] + + verilog_sources = process_f_files(verilog_sources) + + parameters = {} + + parameters["M_CNT"] = m_cnt + parameters["DATA_W"] = data_w + parameters["ADDR_W"] = 32 + parameters["STRB_W"] = parameters["DATA_W"] // 8 + parameters["PAUSER_EN"] = 0 + parameters["PAUSER_W"] = 1 + parameters["PWUSER_EN"] = 0 + parameters["PWUSER_W"] = 1 + parameters["PRUSER_EN"] = 0 + parameters["PRUSER_W"] = 1 + parameters["PBUSER_EN"] = 0 + parameters["PBUSER_W"] = 1 + + extra_env = {f"PARAM_{k}": str(v) for k, v in parameters.items()} + + sim_build = os.path.join( + tests_dir, "sim_build", request.node.name.replace("[", "-").replace("]", "") + ) + + cocotb_test.simulator.run( + simulator="verilator", + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/src/apb/tb/taxi_apb_interconnect/test_taxi_apb_interconnect.sv b/src/apb/tb/taxi_apb_interconnect/test_taxi_apb_interconnect.sv new file mode 100644 index 0000000..26b4fe3 --- /dev/null +++ b/src/apb/tb/taxi_apb_interconnect/test_taxi_apb_interconnect.sv @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * APB interconnect testbench + */ +module test_taxi_apb_interconnect # +( + /* verilator lint_off WIDTHTRUNC */ + parameter M_CNT = 4, + parameter DATA_W = 32, + parameter ADDR_W = 32, + parameter STRB_W = (DATA_W/8), + parameter logic PAUSER_EN = 1'b0, + parameter PAUSER_W = 1, + parameter logic PWUSER_EN = 1'b0, + parameter PWUSER_W = 1, + parameter logic PRUSER_EN = 1'b0, + parameter PRUSER_W = 1, + parameter logic PBUSER_EN = 1'b0, + parameter PBUSER_W = 1, + parameter M_REGIONS = 1, + parameter M_BASE_ADDR = '0, + parameter M_ADDR_W = {M_CNT{{M_REGIONS{32'd24}}}}, + parameter M_SECURE = {M_CNT{1'b0}} + /* verilator lint_on WIDTHTRUNC */ +) +(); + +logic clk; +logic rst; + +taxi_apb_if #( + .DATA_W(DATA_W), + .ADDR_W(ADDR_W), + .STRB_W(STRB_W), + .PAUSER_EN(PAUSER_EN), + .PAUSER_W(PAUSER_W), + .PWUSER_EN(PWUSER_EN), + .PWUSER_W(PWUSER_W), + .PRUSER_EN(PRUSER_EN), + .PRUSER_W(PRUSER_W), + .PBUSER_EN(PBUSER_EN), + .PBUSER_W(PBUSER_W) +) s_apb(), m_apb[M_CNT](); + +taxi_apb_interconnect #( + .M_CNT(M_CNT), + .ADDR_W(ADDR_W), + .M_REGIONS(M_REGIONS), + .M_BASE_ADDR(M_BASE_ADDR), + .M_ADDR_W(M_ADDR_W), + .M_SECURE(M_SECURE) +) +uut ( + .clk(clk), + .rst(rst), + + /* + * APB slave interface + */ + .s_apb(s_apb), + + /* + * APB master interface + */ + .m_apb(m_apb) +); + +endmodule + +`resetall