From 34dd338acf3f5b15b6f7d5c5985b27a0dcdfab91 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Tue, 11 Nov 2025 10:20:26 -0800 Subject: [PATCH] axi: Add AXI lite interconnect module and testbench Signed-off-by: Alex Forencich --- README.md | 1 + src/axi/rtl/taxi_axil_interconnect.f | 6 + src/axi/rtl/taxi_axil_interconnect.sv | 119 +++++ src/axi/rtl/taxi_axil_interconnect_rd.sv | 437 +++++++++++++++ src/axi/rtl/taxi_axil_interconnect_wr.sv | 502 ++++++++++++++++++ src/axi/tb/taxi_axil_interconnect/Makefile | 67 +++ .../test_taxi_axil_interconnect.py | 260 +++++++++ .../test_taxi_axil_interconnect.sv | 111 ++++ 8 files changed, 1503 insertions(+) create mode 100644 src/axi/rtl/taxi_axil_interconnect.f create mode 100644 src/axi/rtl/taxi_axil_interconnect.sv create mode 100644 src/axi/rtl/taxi_axil_interconnect_rd.sv create mode 100644 src/axi/rtl/taxi_axil_interconnect_wr.sv create mode 100644 src/axi/tb/taxi_axil_interconnect/Makefile create mode 100644 src/axi/tb/taxi_axil_interconnect/test_taxi_axil_interconnect.py create mode 100644 src/axi/tb/taxi_axil_interconnect/test_taxi_axil_interconnect.sv diff --git a/README.md b/README.md index fba93a9..904d3b5 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ To facilitate the dual-license model, contributions to the project can only be a * SV interface for AXI lite * AXI lite to AXI adapter * AXI lite to APB adapter + * Interconnect * Register slice * Width converter * Single-port RAM diff --git a/src/axi/rtl/taxi_axil_interconnect.f b/src/axi/rtl/taxi_axil_interconnect.f new file mode 100644 index 0000000..7070c5b --- /dev/null +++ b/src/axi/rtl/taxi_axil_interconnect.f @@ -0,0 +1,6 @@ +taxi_axil_interconnect.sv +taxi_axil_interconnect_rd.sv +taxi_axil_interconnect_wr.sv +taxi_axil_if.sv +../lib/taxi/src/prim/rtl/taxi_arbiter.sv +../lib/taxi/src/prim/rtl/taxi_penc.sv diff --git a/src/axi/rtl/taxi_axil_interconnect.sv b/src/axi/rtl/taxi_axil_interconnect.sv new file mode 100644 index 0000000..cc8fda7 --- /dev/null +++ b/src/axi/rtl/taxi_axil_interconnect.sv @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2021-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4 lite interconnect + */ +module taxi_axil_interconnect # +( + // Number of AXI inputs (slave interfaces) + parameter S_COUNT = 4, + // Number of AXI outputs (master interfaces) + parameter M_COUNT = 4, + // Address width in bits for address decoding + parameter ADDR_W = 32, + // TODO fix parametrization once verilator issue 5890 is fixed + // Number of concurrent operations for each slave interface + // S_COUNT concatenated fields of 32 bits + // Number of regions per master interface + parameter M_REGIONS = 1, + // Master interface base addresses + // M_COUNT 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_COUNT concatenated fields of M_REGIONS concatenated fields of 32 bits + parameter M_ADDR_W = {M_COUNT{{M_REGIONS{32'd24}}}}, + // Read connections between interfaces + // M_COUNT concatenated fields of S_COUNT bits + parameter M_CONNECT_RD = {M_COUNT{{S_COUNT{1'b1}}}}, + // Write connections between interfaces + // M_COUNT concatenated fields of S_COUNT bits + parameter M_CONNECT_WR = {M_COUNT{{S_COUNT{1'b1}}}}, + // Number of concurrent operations for each master interface + // M_COUNT concatenated fields of 32 bits + // Secure master (fail operations based on awprot/arprot) + // M_COUNT bits + parameter M_SECURE = {M_COUNT{1'b0}} +) +( + input wire logic clk, + input wire logic rst, + + /* + * AXI4-lite slave interfaces + */ + taxi_axil_if.wr_slv s_axil_wr[S_COUNT], + taxi_axil_if.rd_slv s_axil_rd[S_COUNT], + + /* + * AXI4-lite master interfaces + */ + taxi_axil_if.wr_mst m_axil_wr[M_COUNT], + taxi_axil_if.rd_mst m_axil_rd[M_COUNT] +); + +taxi_axil_interconnect_wr #( + .S_COUNT(S_COUNT), + .M_COUNT(M_COUNT), + .ADDR_W(ADDR_W), + .M_REGIONS(M_REGIONS), + .M_BASE_ADDR(M_BASE_ADDR), + .M_ADDR_W(M_ADDR_W), + .M_CONNECT(M_CONNECT_WR), + .M_SECURE(M_SECURE) +) +wr_inst ( + .clk(clk), + .rst(rst), + + /* + * AXI4-lite slave interfaces + */ + .s_axil_wr(s_axil_wr), + + /* + * AXI4-lite master interfaces + */ + .m_axil_wr(m_axil_wr) +); + +taxi_axil_interconnect_rd #( + .S_COUNT(S_COUNT), + .M_COUNT(M_COUNT), + .ADDR_W(ADDR_W), + .M_REGIONS(M_REGIONS), + .M_BASE_ADDR(M_BASE_ADDR), + .M_ADDR_W(M_ADDR_W), + .M_CONNECT(M_CONNECT_RD), + .M_SECURE(M_SECURE) +) +rd_inst ( + .clk(clk), + .rst(rst), + + /* + * AXI4-lite slave interfaces + */ + .s_axil_rd(s_axil_rd), + + /* + * AXI4-lite master interfaces + */ + .m_axil_rd(m_axil_rd) +); + +endmodule + +`resetall diff --git a/src/axi/rtl/taxi_axil_interconnect_rd.sv b/src/axi/rtl/taxi_axil_interconnect_rd.sv new file mode 100644 index 0000000..1c5a2a7 --- /dev/null +++ b/src/axi/rtl/taxi_axil_interconnect_rd.sv @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2018-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4 lite interconnect (read) + */ +module taxi_axil_interconnect_rd # +( + // Number of AXI inputs (slave interfaces) + parameter S_COUNT = 4, + // Number of AXI outputs (master interfaces) + parameter M_COUNT = 4, + // Address width in bits for address decoding + parameter ADDR_W = 32, + // Number of regions per master interface + parameter M_REGIONS = 1, + // TODO fix parametrization once verilator issue 5890 is fixed + // Master interface base addresses + // M_COUNT 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_COUNT concatenated fields of M_REGIONS concatenated fields of 32 bits + parameter M_ADDR_W = {M_COUNT{{M_REGIONS{32'd24}}}}, + // Read connections between interfaces + // M_COUNT concatenated fields of S_COUNT bits + parameter M_CONNECT = {M_COUNT{{S_COUNT{1'b1}}}}, + // Secure master (fail operations based on awprot/arprot) + // M_COUNT bits + parameter M_SECURE = {M_COUNT{1'b0}} +) +( + input wire logic clk, + input wire logic rst, + + /* + * AXI4-lite slave interfaces + */ + taxi_axil_if.rd_slv s_axil_rd[S_COUNT], + + /* + * AXI4-lite master interfaces + */ + taxi_axil_if.rd_mst m_axil_rd[M_COUNT] +); + +// extract parameters +localparam DATA_W = s_axil_rd.DATA_W; +localparam S_ADDR_W = s_axil_rd.ADDR_W; +localparam STRB_W = s_axil_rd.STRB_W; +localparam logic ARUSER_EN = s_axil_rd.ARUSER_EN && m_axil_rd.ARUSER_EN; +localparam ARUSER_W = s_axil_rd.ARUSER_W; +localparam logic RUSER_EN = s_axil_rd.RUSER_EN && m_axil_rd.RUSER_EN; +localparam RUSER_W = s_axil_rd.RUSER_W; + +localparam CL_S_COUNT = $clog2(S_COUNT); +localparam CL_M_COUNT = $clog2(M_COUNT); +localparam CL_S_COUNT_INT = CL_S_COUNT > 0 ? CL_S_COUNT : 1; +localparam CL_M_COUNT_INT = CL_M_COUNT > 0 ? CL_M_COUNT : 1; + +localparam [M_COUNT*M_REGIONS-1:0][31:0] M_ADDR_W_INT = M_ADDR_W; +localparam [M_COUNT-1:0][S_COUNT-1:0] M_CONNECT_INT = M_CONNECT; +localparam [M_COUNT-1:0] M_SECURE_INT = M_SECURE; + +// default address computation +function [M_COUNT*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_COUNT*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_COUNT*M_REGIONS-1:0][ADDR_W-1:0] M_BASE_ADDR_INT = M_BASE_ADDR != 0 ? (M_COUNT*M_REGIONS*ADDR_W)'(M_BASE_ADDR) : calcBaseAddrs(0); + +// check configuration +if (s_axil_rd.ADDR_W != ADDR_W) + $fatal(0, "Error: Interface ADDR_W parameter mismatch (instance %m)"); + +if (m_axil_rd.DATA_W != DATA_W) + $fatal(0, "Error: Interface DATA_W parameter mismatch (instance %m)"); + +if (m_axil_rd.STRB_W != STRB_W) + $fatal(0, "Error: Interface STRB_W parameter mismatch (instance %m)"); + +initial begin + for (integer i = 0; i < M_COUNT*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 axil_interconnect instance %m"); + for (integer i = 0; i < M_COUNT*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_COUNT*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_COUNT*M_REGIONS; i = i + 1) begin + for (integer j = i+1; j < M_COUNT*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 + +localparam logic [1:0] + STATE_IDLE = 2'd0, + STATE_DECODE = 2'd1, + STATE_READ = 2'd2, + STATE_WAIT_IDLE = 2'd3; + +logic [1:0] state_reg = STATE_IDLE, state_next; + +logic match; + +logic [CL_M_COUNT_INT-1:0] m_select_reg = '0, m_select_next; +logic [ADDR_W-1:0] axil_araddr_reg = '0, axil_araddr_next; +logic axil_araddr_valid_reg = 1'b0, axil_araddr_valid_next; +logic [2:0] axil_arprot_reg = 3'b000, axil_arprot_next; +logic [ARUSER_W-1:0] axil_aruser_reg = '0, axil_aruser_next; +logic [DATA_W-1:0] axil_rdata_reg = '0, axil_rdata_next; +logic [1:0] axil_rresp_reg = 2'b00, axil_rresp_next; +logic [RUSER_W-1:0] axil_ruser_reg = '0, axil_ruser_next; + +logic [S_COUNT-1:0] s_axil_arready_reg = '0, s_axil_arready_next; +logic [S_COUNT-1:0] s_axil_rvalid_reg = '0, s_axil_rvalid_next; + +logic [M_COUNT-1:0] m_axil_arvalid_reg = '0, m_axil_arvalid_next; +logic [M_COUNT-1:0] m_axil_rready_reg = '0, m_axil_rready_next; + +// unpack interface array +wire [ADDR_W-1:0] s_axil_araddr[S_COUNT]; +wire [2:0] s_axil_arprot[S_COUNT]; +wire [ARUSER_W-1:0] s_axil_aruser[S_COUNT]; +wire [S_COUNT-1:0] s_axil_arvalid; +wire [S_COUNT-1:0] s_axil_rready; + +wire [M_COUNT-1:0] m_axil_arready; +wire [DATA_W-1:0] m_axil_rdata[M_COUNT]; +wire [1:0] m_axil_rresp[M_COUNT]; +wire [RUSER_W-1:0] m_axil_ruser[M_COUNT]; +wire [M_COUNT-1:0] m_axil_rvalid; + +for (genvar n = 0; n < S_COUNT; n = n + 1) begin + assign s_axil_araddr[n] = s_axil_rd[n].araddr; + assign s_axil_arprot[n] = s_axil_rd[n].arprot; + assign s_axil_aruser[n] = s_axil_rd[n].aruser; + assign s_axil_arvalid[n] = s_axil_rd[n].arvalid; + assign s_axil_rd[n].arready = s_axil_arready_reg[n]; + assign s_axil_rd[n].rdata = axil_rdata_reg; + assign s_axil_rd[n].rresp = axil_rresp_reg; + assign s_axil_rd[n].ruser = RUSER_EN ? axil_ruser_reg : '0; + assign s_axil_rd[n].rvalid = s_axil_rvalid_reg[n]; + assign s_axil_rready[n] = s_axil_rd[n].rready; +end + +for (genvar n = 0; n < M_COUNT; n = n + 1) begin + assign m_axil_rd[n].araddr = axil_araddr_reg; + assign m_axil_rd[n].arprot = axil_arprot_reg; + assign m_axil_rd[n].aruser = ARUSER_EN ? axil_aruser_reg : '0; + assign m_axil_rd[n].arvalid = m_axil_arvalid_reg[n]; + assign m_axil_arready[n] = m_axil_rd[n].arready; + assign m_axil_rdata[n] = m_axil_rd[n].rdata; + assign m_axil_rresp[n] = m_axil_rd[n].rresp; + assign m_axil_ruser[n] = m_axil_rd[n].ruser; + assign m_axil_rvalid[n] = m_axil_rd[n].rvalid; + assign m_axil_rd[n].rready = m_axil_rready_reg[n]; +end + +// slave side mux +wire [CL_S_COUNT_INT-1:0] s_select; + +wire [ADDR_W-1:0] current_s_axil_araddr = s_axil_araddr[s_select]; +wire [2:0] current_s_axil_arprot = s_axil_arprot[s_select]; +wire [ARUSER_W-1:0] current_s_axil_aruser = s_axil_aruser[s_select]; +wire current_s_axil_arvalid = s_axil_arvalid[s_select]; +wire current_s_axil_rready = s_axil_rready[s_select]; + +// master side mux +wire current_m_axil_arready = m_axil_arready[m_select_reg]; +wire [DATA_W-1:0] current_m_axil_rdata = m_axil_rdata[m_select_reg]; +wire [1:0] current_m_axil_rresp = m_axil_rresp[m_select_reg]; +wire [RUSER_W-1:0] current_m_axil_ruser = m_axil_ruser[m_select_reg]; +wire current_m_axil_rvalid = m_axil_rvalid[m_select_reg]; + +// arbiter instance +wire [S_COUNT-1:0] req; +wire [S_COUNT-1:0] ack; +wire [S_COUNT-1:0] grant; +wire grant_valid; +wire [CL_S_COUNT_INT-1:0] grant_index; + +assign s_select = grant_index; + +if (S_COUNT > 1) begin : arb + + taxi_arbiter #( + .PORTS(S_COUNT), + .ARB_ROUND_ROBIN(1), + .ARB_BLOCK(1), + .ARB_BLOCK_ACK(1), + .LSB_HIGH_PRIO(1) + ) + arb_inst ( + .clk(clk), + .rst(rst), + .req(req), + .ack(ack), + .grant(grant), + .grant_valid(grant_valid), + .grant_index(grant_index) + ); + +end else begin + + logic grant_valid_reg = 1'b0; + + always @(posedge clk) begin + if (req) begin + grant_valid_reg <= 1'b1; + end + + if (ack || rst) begin + grant_valid_reg <= 1'b0; + end + end + + assign grant_valid = grant_valid_reg; + assign grant = '1; + assign grant_index = '0; + +end + +// req generation +assign req = s_axil_arvalid; +assign ack = grant & s_axil_rvalid_reg & s_axil_rready; + +always_comb begin + state_next = STATE_IDLE; + + match = 1'b0; + + m_select_next = m_select_reg; + axil_araddr_next = axil_araddr_reg; + axil_araddr_valid_next = axil_araddr_valid_reg; + axil_arprot_next = axil_arprot_reg; + axil_aruser_next = axil_aruser_reg; + axil_rdata_next = axil_rdata_reg; + axil_rresp_next = axil_rresp_reg; + axil_ruser_next = axil_ruser_reg; + + s_axil_arready_next = '0; + s_axil_rvalid_next = s_axil_rvalid_reg & ~s_axil_rready; + + m_axil_arvalid_next = m_axil_arvalid_reg & ~m_axil_arready; + m_axil_rready_next = '0; + + case (state_reg) + STATE_IDLE: begin + // idle state; wait for arbitration + axil_araddr_valid_next = 1'b1; + axil_araddr_next = current_s_axil_araddr; + axil_arprot_next = current_s_axil_arprot; + axil_aruser_next = current_s_axil_aruser; + + if (grant_valid) begin + s_axil_arready_next[s_select] = 1'b1; + state_next = STATE_DECODE; + end else begin + state_next = STATE_IDLE; + end + end + STATE_DECODE: begin + // decode state; determine master interface + + match = 1'b0; + for (integer i = 0; i < M_COUNT; 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] || !axil_arprot_reg[1]) && M_CONNECT_INT[i][s_select] && (axil_araddr_reg >> 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 + m_select_next = CL_M_COUNT_INT'(i); + match = 1'b1; + end + end + end + + axil_rdata_next = '0; + axil_rresp_next = 2'b11; + + if (match) begin + m_axil_rready_next[m_select_next] = 1'b1; + state_next = STATE_READ; + end else begin + // no match; return decode error + s_axil_rvalid_next[s_select] = 1'b1; + state_next = STATE_WAIT_IDLE; + end + end + STATE_READ: begin + // read state; store and forward read response + m_axil_rready_next[m_select_reg] = 1'b1; + + if (axil_araddr_valid_reg) begin + m_axil_arvalid_next[m_select_reg] = 1'b1; + end + axil_araddr_valid_next = 1'b0; + + if (m_axil_rready_reg != 0 && current_m_axil_rvalid) begin + m_axil_rready_next[m_select_reg] = 1'b0; + axil_rdata_next = current_m_axil_rdata; + axil_rresp_next = current_m_axil_rresp; + axil_ruser_next = current_m_axil_ruser; + s_axil_rvalid_next[s_select] = 1'b1; + state_next = STATE_WAIT_IDLE; + end else begin + state_next = STATE_READ; + end + end + STATE_WAIT_IDLE: begin + // wait for idle state; wait until grant valid is deasserted + if (grant_valid == 0 || ack != 0) begin + state_next = STATE_IDLE; + end else begin + state_next = STATE_WAIT_IDLE; + end + end + default: begin + // invalid state + state_next = STATE_IDLE; + end + endcase +end + +always_ff @(posedge clk) begin + state_reg <= state_next; + + m_select_reg <= m_select_next; + + axil_araddr_reg <= axil_araddr_next; + axil_araddr_valid_reg <= axil_araddr_valid_next; + axil_arprot_reg <= axil_arprot_next; + axil_aruser_reg <= axil_aruser_next; + axil_rdata_reg <= axil_rdata_next; + axil_rresp_reg <= axil_rresp_next; + axil_ruser_reg <= axil_ruser_next; + + s_axil_arready_reg <= s_axil_arready_next; + s_axil_rvalid_reg <= s_axil_rvalid_next; + + m_axil_arvalid_reg <= m_axil_arvalid_next; + m_axil_rready_reg <= m_axil_rready_next; + + if (rst) begin + state_reg <= STATE_IDLE; + + s_axil_arready_reg <= '0; + s_axil_rvalid_reg <= '0; + + m_axil_arvalid_reg <= '0; + m_axil_rready_reg <= '0; + end +end + +endmodule + +`resetall diff --git a/src/axi/rtl/taxi_axil_interconnect_wr.sv b/src/axi/rtl/taxi_axil_interconnect_wr.sv new file mode 100644 index 0000000..ea2e0de --- /dev/null +++ b/src/axi/rtl/taxi_axil_interconnect_wr.sv @@ -0,0 +1,502 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2018-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4 lite interconnect (write) + */ +module taxi_axil_interconnect_wr # +( + // Number of AXI inputs (slave interfaces) + parameter S_COUNT = 4, + // Number of AXI outputs (master interfaces) + parameter M_COUNT = 4, + // Address width in bits for address decoding + parameter ADDR_W = 32, + // Number of regions per master interface + parameter M_REGIONS = 1, + // TODO fix parametrization once verilator issue 5890 is fixed + // Master interface base addresses + // M_COUNT 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_COUNT concatenated fields of M_REGIONS concatenated fields of 32 bits + parameter M_ADDR_W = {M_COUNT{{M_REGIONS{32'd24}}}}, + // Write connections between interfaces + // M_COUNT concatenated fields of S_COUNT bits + parameter M_CONNECT = {M_COUNT{{S_COUNT{1'b1}}}}, + // Secure master (fail operations based on awprot/arprot) + // M_COUNT bits + parameter M_SECURE = {M_COUNT{1'b0}} +) +( + input wire logic clk, + input wire logic rst, + + /* + * AXI4-lite slave interfaces + */ + taxi_axil_if.wr_slv s_axil_wr[S_COUNT], + + /* + * AXI4-lite master interfaces + */ + taxi_axil_if.wr_mst m_axil_wr[M_COUNT] +); + +// extract parameters +localparam DATA_W = s_axil_wr.DATA_W; +localparam S_ADDR_W = s_axil_wr.ADDR_W; +localparam STRB_W = s_axil_wr.STRB_W; +localparam logic AWUSER_EN = s_axil_wr.AWUSER_EN && m_axil_wr.AWUSER_EN; +localparam AWUSER_W = s_axil_wr.AWUSER_W; +localparam logic WUSER_EN = s_axil_wr.WUSER_EN && m_axil_wr.WUSER_EN; +localparam WUSER_W = s_axil_wr.WUSER_W; +localparam logic BUSER_EN = s_axil_wr.BUSER_EN && m_axil_wr.BUSER_EN; +localparam BUSER_W = s_axil_wr.BUSER_W; + +localparam CL_S_COUNT = $clog2(S_COUNT); +localparam CL_M_COUNT = $clog2(M_COUNT); +localparam CL_S_COUNT_INT = CL_S_COUNT > 0 ? CL_S_COUNT : 1; +localparam CL_M_COUNT_INT = CL_M_COUNT > 0 ? CL_M_COUNT : 1; + +localparam [M_COUNT*M_REGIONS-1:0][31:0] M_ADDR_W_INT = M_ADDR_W; +localparam [M_COUNT-1:0][S_COUNT-1:0] M_CONNECT_INT = M_CONNECT; +localparam [M_COUNT-1:0] M_SECURE_INT = M_SECURE; + +// default address computation +function [M_COUNT*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_COUNT*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_COUNT*M_REGIONS-1:0][ADDR_W-1:0] M_BASE_ADDR_INT = M_BASE_ADDR != 0 ? (M_COUNT*M_REGIONS*ADDR_W)'(M_BASE_ADDR) : calcBaseAddrs(0); + +// check configuration +if (s_axil_wr.ADDR_W != ADDR_W) + $fatal(0, "Error: Interface ADDR_W parameter mismatch (instance %m)"); + +if (m_axil_wr.DATA_W != DATA_W) + $fatal(0, "Error: Interface DATA_W parameter mismatch (instance %m)"); + +if (m_axil_wr.STRB_W != STRB_W) + $fatal(0, "Error: Interface STRB_W parameter mismatch (instance %m)"); + +initial begin + for (integer i = 0; i < M_COUNT*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 axil_interconnect instance %m"); + for (integer i = 0; i < M_COUNT*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_COUNT*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_COUNT*M_REGIONS; i = i + 1) begin + for (integer j = i+1; j < M_COUNT*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 + +localparam logic [2:0] + STATE_IDLE = 3'd0, + STATE_DECODE = 3'd1, + STATE_WRITE = 3'd2, + STATE_WRITE_RESP = 3'd3, + STATE_WRITE_DROP = 3'd4, + STATE_WAIT_IDLE = 3'd5; + +logic [2:0] state_reg = STATE_IDLE, state_next; + +logic match; + +logic [CL_M_COUNT_INT-1:0] m_select_reg = '0, m_select_next; +logic [ADDR_W-1:0] axil_awaddr_reg = '0, axil_awaddr_next; +logic axil_awaddr_valid_reg = 1'b0, axil_awaddr_valid_next; +logic [2:0] axil_awprot_reg = 3'b000, axil_awprot_next; +logic [AWUSER_W-1:0] axil_awuser_reg = '0, axil_awuser_next; +logic [DATA_W-1:0] axil_wdata_reg = '0, axil_wdata_next; +logic [STRB_W-1:0] axil_wstrb_reg = '0, axil_wstrb_next; +logic [WUSER_W-1:0] axil_wuser_reg = '0, axil_wuser_next; +logic [1:0] axil_bresp_reg = 2'b00, axil_bresp_next; +logic [BUSER_W-1:0] axil_buser_reg = '0, axil_buser_next; + +logic [S_COUNT-1:0] s_axil_awready_reg = '0, s_axil_awready_next; +logic [S_COUNT-1:0] s_axil_wready_reg = '0, s_axil_wready_next; +logic [S_COUNT-1:0] s_axil_bvalid_reg = '0, s_axil_bvalid_next; + +logic [M_COUNT-1:0] m_axil_awvalid_reg = '0, m_axil_awvalid_next; +logic [M_COUNT-1:0] m_axil_wvalid_reg = '0, m_axil_wvalid_next; +logic [M_COUNT-1:0] m_axil_bready_reg = '0, m_axil_bready_next; + +// unpack interface array +wire [ADDR_W-1:0] s_axil_awaddr[S_COUNT]; +wire [2:0] s_axil_awprot[S_COUNT]; +wire [AWUSER_W-1:0] s_axil_awuser[S_COUNT]; +wire [S_COUNT-1:0] s_axil_awvalid; +wire [DATA_W-1:0] s_axil_wdata[S_COUNT]; +wire [STRB_W-1:0] s_axil_wstrb[S_COUNT]; +wire [WUSER_W-1:0] s_axil_wuser[S_COUNT]; +wire [S_COUNT-1:0] s_axil_wvalid; +wire [S_COUNT-1:0] s_axil_bready; + +wire [M_COUNT-1:0] m_axil_awready; +wire [M_COUNT-1:0] m_axil_wready; +wire [1:0] m_axil_bresp[M_COUNT]; +wire [BUSER_W-1:0] m_axil_buser[M_COUNT]; +wire [M_COUNT-1:0] m_axil_bvalid; + +for (genvar n = 0; n < S_COUNT; n = n + 1) begin + assign s_axil_awaddr[n] = s_axil_wr[n].awaddr; + assign s_axil_awprot[n] = s_axil_wr[n].awprot; + assign s_axil_awuser[n] = s_axil_wr[n].awuser; + assign s_axil_awvalid[n] = s_axil_wr[n].awvalid; + assign s_axil_wr[n].awready = s_axil_awready_reg[n]; + assign s_axil_wdata[n] = s_axil_wr[n].wdata; + assign s_axil_wstrb[n] = s_axil_wr[n].wstrb; + assign s_axil_wuser[n] = s_axil_wr[n].wuser; + assign s_axil_wvalid[n] = s_axil_wr[n].wvalid; + assign s_axil_wr[n].wready = s_axil_wready_reg[n]; + assign s_axil_wr[n].bresp = axil_bresp_reg; + assign s_axil_wr[n].buser = BUSER_EN ? axil_buser_reg : '0; + assign s_axil_wr[n].bvalid = s_axil_bvalid_reg[n]; + assign s_axil_bready[n] = s_axil_wr[n].bready; +end + +for (genvar n = 0; n < M_COUNT; n = n + 1) begin + assign m_axil_wr[n].awaddr = axil_awaddr_reg; + assign m_axil_wr[n].awprot = axil_awprot_reg; + assign m_axil_wr[n].awuser = AWUSER_EN ? axil_awuser_reg : '0; + assign m_axil_wr[n].awvalid = m_axil_awvalid_reg[n]; + assign m_axil_awready[n] = m_axil_wr[n].awready; + assign m_axil_wr[n].wdata = axil_wdata_reg; + assign m_axil_wr[n].wstrb = axil_wstrb_reg; + assign m_axil_wr[n].wuser = AWUSER_EN ? axil_wuser_reg : '0; + assign m_axil_wr[n].wvalid = m_axil_wvalid_reg[n]; + assign m_axil_wready[n] = m_axil_wr[n].wready; + assign m_axil_bresp[n] = m_axil_wr[n].bresp; + assign m_axil_buser[n] = m_axil_wr[n].buser; + assign m_axil_bvalid[n] = m_axil_wr[n].bvalid; + assign m_axil_wr[n].bready = m_axil_bready_reg[n]; +end + +// slave side mux +wire [CL_S_COUNT_INT-1:0] s_select; + +wire [ADDR_W-1:0] current_s_axil_awaddr = s_axil_awaddr[s_select]; +wire [2:0] current_s_axil_awprot = s_axil_awprot[s_select]; +wire [AWUSER_W-1:0] current_s_axil_awuser = s_axil_awuser[s_select]; +wire current_s_axil_awvalid = s_axil_awvalid[s_select]; +wire [DATA_W-1:0] current_s_axil_wdata = s_axil_wdata[s_select]; +wire [STRB_W-1:0] current_s_axil_wstrb = s_axil_wstrb[s_select]; +wire [WUSER_W-1:0] current_s_axil_wuser = s_axil_wuser[s_select]; +wire current_s_axil_wvalid = s_axil_wvalid[s_select]; +wire current_s_axil_bready = s_axil_bready[s_select]; + +// master side mux +wire current_m_axil_awready = m_axil_awready[m_select_reg]; +wire current_m_axil_wready = m_axil_wready[m_select_reg]; +wire [1:0] current_m_axil_bresp = m_axil_bresp[m_select_reg]; +wire [BUSER_W-1:0] current_m_axil_buser = m_axil_buser[m_select_reg]; +wire current_m_axil_bvalid = m_axil_bvalid[m_select_reg]; + +// arbiter instance +wire [S_COUNT-1:0] req; +wire [S_COUNT-1:0] ack; +wire [S_COUNT-1:0] grant; +wire grant_valid; +wire [CL_S_COUNT_INT-1:0] grant_index; + +assign s_select = grant_index; + +if (S_COUNT > 1) begin : arb + + taxi_arbiter #( + .PORTS(S_COUNT), + .ARB_ROUND_ROBIN(1), + .ARB_BLOCK(1), + .ARB_BLOCK_ACK(1), + .LSB_HIGH_PRIO(1) + ) + arb_inst ( + .clk(clk), + .rst(rst), + .req(req), + .ack(ack), + .grant(grant), + .grant_valid(grant_valid), + .grant_index(grant_index) + ); + +end else begin + + logic grant_valid_reg = 1'b0; + + always @(posedge clk) begin + if (req) begin + grant_valid_reg <= 1'b1; + end + + if (ack || rst) begin + grant_valid_reg <= 1'b0; + end + end + + assign grant_valid = grant_valid_reg; + assign grant = '1; + assign grant_index = '0; + +end + +assign req = s_axil_awvalid; +assign ack = grant & s_axil_bvalid_reg & s_axil_bready; + +always_comb begin + state_next = STATE_IDLE; + + match = 1'b0; + + m_select_next = m_select_reg; + axil_awaddr_next = axil_awaddr_reg; + axil_awaddr_valid_next = axil_awaddr_valid_reg; + axil_awprot_next = axil_awprot_reg; + axil_awuser_next = axil_awuser_reg; + axil_wdata_next = axil_wdata_reg; + axil_wstrb_next = axil_wstrb_reg; + axil_wuser_next = axil_wuser_reg; + axil_bresp_next = axil_bresp_reg; + axil_buser_next = axil_buser_reg; + + s_axil_awready_next = '0; + s_axil_wready_next = '0; + s_axil_bvalid_next = s_axil_bvalid_reg & ~s_axil_bready; + + m_axil_awvalid_next = m_axil_awvalid_reg & ~m_axil_awready; + m_axil_wvalid_next = m_axil_wvalid_reg & ~m_axil_wready; + m_axil_bready_next = '0; + + case (state_reg) + STATE_IDLE: begin + // idle state; wait for arbitration + axil_awaddr_valid_next = 1'b1; + axil_awaddr_next = current_s_axil_awaddr; + axil_awprot_next = current_s_axil_awprot; + axil_awuser_next = current_s_axil_awuser; + + if (grant_valid) begin + s_axil_awready_next[grant_index] = 1'b1; + state_next = STATE_DECODE; + end else begin + state_next = STATE_IDLE; + end + end + STATE_DECODE: begin + // decode state; determine master interface + + match = 1'b0; + for (integer i = 0; i < M_COUNT; 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] || !axil_awprot_reg[1]) && M_CONNECT_INT[i][s_select] && (axil_awaddr_reg >> 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 + m_select_next = CL_M_COUNT_INT'(i); + match = 1'b1; + end + end + end + + s_axil_wready_next[s_select] = 1'b1; + + if (match) begin + state_next = STATE_WRITE; + end else begin + // no match; return decode error + state_next = STATE_WRITE_DROP; + end + end + STATE_WRITE: begin + // write state; store and forward write data + s_axil_wready_next[s_select] = 1'b1; + + if (axil_awaddr_valid_reg) begin + m_axil_awvalid_next[m_select_reg] = 1'b1; + end + axil_awaddr_valid_next = 1'b0; + + axil_wdata_next = current_s_axil_wdata; + axil_wstrb_next = current_s_axil_wstrb; + axil_wuser_next = current_s_axil_wuser; + + if (s_axil_wready_reg != 0 && current_s_axil_wvalid) begin + s_axil_wready_next[s_select] = 1'b0; + m_axil_wvalid_next[m_select_reg] = 1'b1; + m_axil_bready_next[m_select_reg] = 1'b1; + state_next = STATE_WRITE_RESP; + end else begin + state_next = STATE_WRITE; + end + end + STATE_WRITE_RESP: begin + // write response state; store and forward write response + m_axil_bready_next[m_select_reg] = 1'b1; + + axil_bresp_next = current_m_axil_bresp; + axil_buser_next = current_m_axil_buser; + + if (m_axil_bready_reg != 0 && current_m_axil_bvalid) begin + m_axil_bready_next[m_select_reg] = 1'b0; + s_axil_bvalid_next[s_select] = 1'b1; + state_next = STATE_WAIT_IDLE; + end else begin + state_next = STATE_WRITE_RESP; + end + end + STATE_WRITE_DROP: begin + // write drop state; drop write data + s_axil_wready_next[s_select] = 1'b1; + + axil_awaddr_valid_next = 1'b0; + + axil_bresp_next = 2'b11; + axil_buser_next = '0; + + if (s_axil_wready_reg != 0 && current_s_axil_wvalid) begin + s_axil_wready_next[s_select] = 1'b0; + s_axil_bvalid_next[s_select] = 1'b1; + state_next = STATE_WAIT_IDLE; + end else begin + state_next = STATE_WRITE_DROP; + end + end + STATE_WAIT_IDLE: begin + // wait for idle state; wait until grant valid is deasserted + + if (grant_valid == 0 || ack != 0) begin + state_next = STATE_IDLE; + end else begin + state_next = STATE_WAIT_IDLE; + end + end + default: begin + // invalid state + state_next = STATE_IDLE; + end + endcase +end + +always_ff @(posedge clk) begin + state_reg <= state_next; + + m_select_reg <= m_select_next; + + axil_awaddr_reg <= axil_awaddr_next; + axil_awaddr_valid_reg <= axil_awaddr_valid_next; + axil_awprot_reg <= axil_awprot_next; + axil_awuser_reg <= axil_awuser_next; + axil_wdata_reg <= axil_wdata_next; + axil_wstrb_reg <= axil_wstrb_next; + axil_wuser_reg <= axil_wuser_next; + axil_bresp_reg <= axil_bresp_next; + axil_buser_reg <= axil_buser_next; + + s_axil_awready_reg <= s_axil_awready_next; + s_axil_wready_reg <= s_axil_wready_next; + s_axil_bvalid_reg <= s_axil_bvalid_next; + + m_axil_awvalid_reg <= m_axil_awvalid_next; + m_axil_wvalid_reg <= m_axil_wvalid_next; + m_axil_bready_reg <= m_axil_bready_next; + + if (rst) begin + state_reg <= STATE_IDLE; + + s_axil_awready_reg <= '0; + s_axil_wready_reg <= '0; + s_axil_bvalid_reg <= '0; + + m_axil_awvalid_reg <= '0; + m_axil_wvalid_reg <= '0; + m_axil_bready_reg <= '0; + end +end + +endmodule + +`resetall diff --git a/src/axi/tb/taxi_axil_interconnect/Makefile b/src/axi/tb/taxi_axil_interconnect/Makefile new file mode 100644 index 0000000..9aca02a --- /dev/null +++ b/src/axi/tb/taxi_axil_interconnect/Makefile @@ -0,0 +1,67 @@ +# 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_axil_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).f + +# 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))) + +REG_TYPE ?= 1 + +# module parameters +export PARAM_DATA_W := 32 +export PARAM_ADDR_W := 32 +export PARAM_STRB_W := $(shell expr $(PARAM_DATA_W) / 8 ) +export PARAM_AWUSER_EN := 0 +export PARAM_AWUSER_W := 1 +export PARAM_WUSER_EN := 0 +export PARAM_WUSER_W := 1 +export PARAM_BUSER_EN := 0 +export PARAM_BUSER_W := 1 +export PARAM_ARUSER_EN := 0 +export PARAM_ARUSER_W := 1 +export PARAM_RUSER_EN := 0 +export PARAM_RUSER_W := 1 +export PARAM_M_REGIONS := 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/axi/tb/taxi_axil_interconnect/test_taxi_axil_interconnect.py b/src/axi/tb/taxi_axil_interconnect/test_taxi_axil_interconnect.py new file mode 100644 index 0000000..0f94148 --- /dev/null +++ b/src/axi/tb/taxi_axil_interconnect/test_taxi_axil_interconnect.py @@ -0,0 +1,260 @@ +#!/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_test.simulator +import pytest + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge, Timer +from cocotb.regression import TestFactory + +from cocotbext.axi import AxiLiteBus, AxiLiteMaster, AxiLiteRam + + +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.axil_master = [AxiLiteMaster(AxiLiteBus.from_entity(ch), dut.clk, dut.rst) for ch in dut.s_axil] + self.axil_ram = [AxiLiteRam(AxiLiteBus.from_entity(ch), dut.clk, dut.rst, size=2**16) for ch in dut.m_axil] + + def set_idle_generator(self, generator=None): + if generator: + for master in self.axil_master: + master.write_if.aw_channel.set_pause_generator(generator()) + master.write_if.w_channel.set_pause_generator(generator()) + master.read_if.ar_channel.set_pause_generator(generator()) + for ram in self.axil_ram: + ram.write_if.b_channel.set_pause_generator(generator()) + ram.read_if.r_channel.set_pause_generator(generator()) + + def set_backpressure_generator(self, generator=None): + if generator: + for master in self.axil_master: + master.write_if.b_channel.set_pause_generator(generator()) + master.read_if.r_channel.set_pause_generator(generator()) + for ram in self.axil_ram: + ram.write_if.aw_channel.set_pause_generator(generator()) + ram.write_if.w_channel.set_pause_generator(generator()) + ram.read_if.ar_channel.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, s=0, m=0): + + tb = TB(dut) + + byte_lanes = tb.axil_master[s].write_if.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.axil_ram[m].write(ram_addr-128, b'\xaa'*(length+256)) + + await tb.axil_master[s].write(addr, test_data) + + tb.log.debug("%s", tb.axil_ram[m].hexdump_str((ram_addr & ~0xf)-16, (((ram_addr & 0xf)+length-1) & ~0xf)+48)) + + assert tb.axil_ram[m].read(ram_addr, length) == test_data + assert tb.axil_ram[m].read(ram_addr-1, 1) == b'\xaa' + assert tb.axil_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, s=0, m=0): + + tb = TB(dut) + + byte_lanes = tb.axil_master[s].write_if.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.axil_ram[m].write(ram_addr, test_data) + + data = await tb.axil_master[s].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.axil_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.axil_master[k % len(tb.axil_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: + + s_count = len(cocotb.top.s_axil) + m_count = len(cocotb.top.m_axil) + + 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("s", range(min(s_count, 2))) + factory.add_option("m", range(min(m_count, 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_count", [1, 4]) +@pytest.mark.parametrize("s_count", [1, 4]) +def test_taxi_axil_interconnect(request, s_count, m_count, data_w): + dut = "taxi_axil_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}.f"), + ] + + verilog_sources = process_f_files(verilog_sources) + + parameters = {} + + parameters['S_COUNT'] = s_count + parameters['M_COUNT'] = m_count + parameters['DATA_W'] = data_w + parameters['ADDR_W'] = 32 + parameters['STRB_W'] = parameters['DATA_W'] // 8 + parameters['AWUSER_EN'] = 0 + parameters['AWUSER_W'] = 1 + parameters['WUSER_EN'] = 0 + parameters['WUSER_W'] = 1 + parameters['BUSER_EN'] = 0 + parameters['BUSER_W'] = 1 + parameters['ARUSER_EN'] = 0 + parameters['ARUSER_W'] = 1 + parameters['RUSER_EN'] = 0 + parameters['RUSER_W'] = 1 + parameters['M_REGIONS'] = 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/axi/tb/taxi_axil_interconnect/test_taxi_axil_interconnect.sv b/src/axi/tb/taxi_axil_interconnect/test_taxi_axil_interconnect.sv new file mode 100644 index 0000000..3874252 --- /dev/null +++ b/src/axi/tb/taxi_axil_interconnect/test_taxi_axil_interconnect.sv @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4-lite interconnect testbench + */ +module test_taxi_axil_interconnect # +( + /* verilator lint_off WIDTHTRUNC */ + parameter S_COUNT = 4, + parameter M_COUNT = 4, + parameter DATA_W = 32, + parameter ADDR_W = 32, + parameter STRB_W = (DATA_W/8), + parameter logic AWUSER_EN = 1'b0, + parameter AWUSER_W = 1, + parameter logic WUSER_EN = 1'b0, + parameter WUSER_W = 1, + parameter logic BUSER_EN = 1'b0, + parameter BUSER_W = 1, + parameter logic ARUSER_EN = 1'b0, + parameter ARUSER_W = 1, + parameter logic RUSER_EN = 1'b0, + parameter RUSER_W = 1, + parameter M_REGIONS = 1, + parameter M_BASE_ADDR = '0, + parameter M_ADDR_W = {M_COUNT{{M_REGIONS{32'd24}}}}, + parameter M_CONNECT_RD = {M_COUNT{{S_COUNT{1'b1}}}}, + parameter M_CONNECT_WR = {M_COUNT{{S_COUNT{1'b1}}}}, + parameter M_SECURE = {M_COUNT{1'b0}} + /* verilator lint_on WIDTHTRUNC */ +) +(); + +logic clk; +logic rst; + +taxi_axil_if #( + .DATA_W(DATA_W), + .ADDR_W(ADDR_W), + .STRB_W(STRB_W), + .AWUSER_EN(AWUSER_EN), + .AWUSER_W(AWUSER_W), + .WUSER_EN(WUSER_EN), + .WUSER_W(WUSER_W), + .BUSER_EN(BUSER_EN), + .BUSER_W(BUSER_W), + .ARUSER_EN(ARUSER_EN), + .ARUSER_W(ARUSER_W), + .RUSER_EN(RUSER_EN), + .RUSER_W(RUSER_W) +) s_axil[S_COUNT](); + +taxi_axil_if #( + .DATA_W(DATA_W), + .ADDR_W(ADDR_W), + .STRB_W(STRB_W), + .AWUSER_EN(AWUSER_EN), + .AWUSER_W(AWUSER_W), + .WUSER_EN(WUSER_EN), + .WUSER_W(WUSER_W), + .BUSER_EN(BUSER_EN), + .BUSER_W(BUSER_W), + .ARUSER_EN(ARUSER_EN), + .ARUSER_W(ARUSER_W), + .RUSER_EN(RUSER_EN), + .RUSER_W(RUSER_W) +) m_axil[M_COUNT](); + +taxi_axil_interconnect #( + .S_COUNT(S_COUNT), + .M_COUNT(M_COUNT), + .ADDR_W(ADDR_W), + .M_REGIONS(M_REGIONS), + .M_BASE_ADDR(M_BASE_ADDR), + .M_ADDR_W(M_ADDR_W), + .M_CONNECT_RD(M_CONNECT_RD), + .M_CONNECT_WR(M_CONNECT_WR), + .M_SECURE(M_SECURE) +) +uut ( + .clk(clk), + .rst(rst), + + /* + * AXI4-lite slave interface + */ + .s_axil_wr(s_axil), + .s_axil_rd(s_axil), + + /* + * AXI4-lite master interface + */ + .m_axil_wr(m_axil), + .m_axil_rd(m_axil) +); + +endmodule + +`resetall