mirror of
https://github.com/fpganinja/taxi.git
synced 2026-04-07 04:38:42 -07:00
pcie: Add MSI-X module with APB interface
Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
@@ -184,6 +184,7 @@ The Taxi transport library contains many smaller components that can be composed
|
|||||||
* PCIe AXI lite master for Xilinx UltraScale
|
* PCIe AXI lite master for Xilinx UltraScale
|
||||||
* MSI shim for Xilinx UltraScale
|
* MSI shim for Xilinx UltraScale
|
||||||
* MSI-X with AXI lite control interface
|
* MSI-X with AXI lite control interface
|
||||||
|
* MSI-X with APB control interface
|
||||||
* Primitives
|
* Primitives
|
||||||
* Arbiter
|
* Arbiter
|
||||||
* Priority encoder
|
* Priority encoder
|
||||||
|
|||||||
466
src/pcie/rtl/taxi_pcie_msix_apb.sv
Normal file
466
src/pcie/rtl/taxi_pcie_msix_apb.sv
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
// SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||||
|
/*
|
||||||
|
|
||||||
|
Copyright (c) 2022-2026 FPGA Ninja, LLC
|
||||||
|
|
||||||
|
Authors:
|
||||||
|
- Alex Forencich
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
`resetall
|
||||||
|
`timescale 1ns / 1ps
|
||||||
|
`default_nettype none
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PCIe MSI-X module with APB interface
|
||||||
|
*/
|
||||||
|
module taxi_pcie_msix_apb #
|
||||||
|
(
|
||||||
|
// TLP interface configuration
|
||||||
|
parameter logic TLP_FORCE_64_BIT_ADDR = 1'b0
|
||||||
|
)
|
||||||
|
(
|
||||||
|
input wire logic clk,
|
||||||
|
input wire logic rst,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* APB interface for MSI-X tables
|
||||||
|
*/
|
||||||
|
taxi_apb_if.slv s_apb,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Interrupt request input
|
||||||
|
*/
|
||||||
|
taxi_axis_if.snk s_axis_irq,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Memory write TLP output
|
||||||
|
*/
|
||||||
|
taxi_pcie_tlp_if.src tx_wr_req_tlp,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Configuration
|
||||||
|
*/
|
||||||
|
input wire logic [7:0] bus_num,
|
||||||
|
input wire logic [7:0] func_num,
|
||||||
|
input wire logic msix_enable,
|
||||||
|
input wire logic msix_mask
|
||||||
|
);
|
||||||
|
|
||||||
|
// extract parameters
|
||||||
|
localparam TLP_SEGS = tx_wr_req_tlp.SEGS;
|
||||||
|
localparam TLP_SEG_DATA_W = tx_wr_req_tlp.SEG_DATA_W;
|
||||||
|
localparam TLP_SEG_EMPTY_W = tx_wr_req_tlp.SEG_EMPTY_W;
|
||||||
|
localparam TLP_DATA_W = TLP_SEGS*TLP_SEG_DATA_W;
|
||||||
|
localparam TLP_HDR_W = tx_wr_req_tlp.HDR_W;
|
||||||
|
localparam FUNC_NUM_W = tx_wr_req_tlp.FUNC_NUM_W;
|
||||||
|
|
||||||
|
localparam APB_DATA_W = s_apb.DATA_W;
|
||||||
|
localparam APB_ADDR_W = s_apb.ADDR_W;
|
||||||
|
localparam APB_STRB_W = s_apb.STRB_W;
|
||||||
|
|
||||||
|
localparam IRQ_INDEX_W = s_axis_irq.DATA_W;
|
||||||
|
|
||||||
|
localparam TLP_DATA_W_B = TLP_DATA_W/8;
|
||||||
|
localparam TLP_DATA_W_DW = TLP_DATA_W/32;
|
||||||
|
|
||||||
|
localparam TBL_ADDR_W = IRQ_INDEX_W+1;
|
||||||
|
localparam PBA_ADDR_W = IRQ_INDEX_W > 6 ? IRQ_INDEX_W-6 : 0;
|
||||||
|
localparam PBA_ADDR_W_INT = PBA_ADDR_W > 0 ? PBA_ADDR_W : 1;
|
||||||
|
|
||||||
|
localparam INDEX_SHIFT = $clog2(64/8);
|
||||||
|
localparam WORD_SELECT_SHIFT = $clog2(APB_DATA_W/8);
|
||||||
|
localparam WORD_SELECT_W = 64 > APB_DATA_W ? $clog2((64+7)/8) - $clog2(APB_DATA_W/8) : 0;
|
||||||
|
|
||||||
|
// bus width assertions
|
||||||
|
if (APB_STRB_W * 8 != APB_DATA_W)
|
||||||
|
$fatal(0, "Error: APB interface requires byte (8-bit) granularity (instance %m)");
|
||||||
|
|
||||||
|
if (APB_DATA_W > 64)
|
||||||
|
$fatal(0, "Error: APB data width must be 64 or less (instance %m)");
|
||||||
|
|
||||||
|
if (APB_ADDR_W < IRQ_INDEX_W+5)
|
||||||
|
$fatal(0, "Error: APB address width too narrow (instance %m)");
|
||||||
|
|
||||||
|
if (IRQ_INDEX_W > 11)
|
||||||
|
$fatal(0, "Error: IRQ index width must be 11 or less (instance %m)");
|
||||||
|
|
||||||
|
localparam [2:0]
|
||||||
|
TLP_FMT_3DW = 3'b000,
|
||||||
|
TLP_FMT_4DW = 3'b001,
|
||||||
|
TLP_FMT_3DW_DATA = 3'b010,
|
||||||
|
TLP_FMT_4DW_DATA = 3'b011,
|
||||||
|
TLP_FMT_PREFIX = 3'b100;
|
||||||
|
|
||||||
|
localparam [1:0]
|
||||||
|
STATE_IDLE = 2'd0,
|
||||||
|
STATE_READ_TBL_1 = 2'd1,
|
||||||
|
STATE_READ_TBL_2 = 2'd2,
|
||||||
|
STATE_SEND_TLP = 2'd3;
|
||||||
|
|
||||||
|
logic [1:0] state_reg = STATE_IDLE, state_next;
|
||||||
|
|
||||||
|
logic [IRQ_INDEX_W-1:0] irq_index_reg = '0, irq_index_next;
|
||||||
|
|
||||||
|
logic [63:0] vec_addr_reg = '0, vec_addr_next;
|
||||||
|
logic [31:0] vec_data_reg = '0, vec_data_next;
|
||||||
|
logic vec_mask_reg = 1'b0, vec_mask_next;
|
||||||
|
|
||||||
|
logic [127:0] tlp_hdr;
|
||||||
|
|
||||||
|
logic tbl_apb_mem_rd_en;
|
||||||
|
logic tbl_apb_mem_wr_en;
|
||||||
|
logic [7:0] tbl_apb_mem_wr_be;
|
||||||
|
logic [63:0] tbl_apb_mem_wr_data;
|
||||||
|
logic pba_apb_mem_rd_en;
|
||||||
|
|
||||||
|
logic tbl_mem_rd_en;
|
||||||
|
logic [TBL_ADDR_W-1:0] tbl_mem_addr;
|
||||||
|
logic pba_mem_rd_en;
|
||||||
|
logic pba_mem_wr_en;
|
||||||
|
logic [PBA_ADDR_W-1:0] pba_mem_addr;
|
||||||
|
logic [63:0] pba_mem_wr_data;
|
||||||
|
|
||||||
|
logic s_apb_pready_reg = 1'b0, s_apb_pready_next;
|
||||||
|
logic [APB_DATA_W-1:0] s_apb_prdata_reg = '0, s_apb_prdata_next;
|
||||||
|
|
||||||
|
logic irq_ready_reg = 1'b0, irq_ready_next;
|
||||||
|
|
||||||
|
logic [31:0] tx_wr_req_tlp_data_reg = '0, tx_wr_req_tlp_data_next;
|
||||||
|
logic [TLP_HDR_W-1:0] tx_wr_req_tlp_hdr_reg = '0, tx_wr_req_tlp_hdr_next;
|
||||||
|
logic tx_wr_req_tlp_valid_reg = 0, tx_wr_req_tlp_valid_next;
|
||||||
|
|
||||||
|
logic msix_enable_reg = 1'b0;
|
||||||
|
logic msix_mask_reg = 1'b0;
|
||||||
|
|
||||||
|
// MSI-X table
|
||||||
|
(* ramstyle = "no_rw_check, mlab" *)
|
||||||
|
logic [63:0] tbl_mem[2**TBL_ADDR_W];
|
||||||
|
|
||||||
|
// MSI-X PBA
|
||||||
|
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||||
|
logic [63:0] pba_mem[2**PBA_ADDR_W];
|
||||||
|
|
||||||
|
logic tbl_rd_data_valid_reg = 1'b0, tbl_rd_data_valid_next;
|
||||||
|
logic pba_rd_data_valid_reg = 1'b0, pba_rd_data_valid_next;
|
||||||
|
logic [WORD_SELECT_W-1:0] rd_data_shift_reg = '0, rd_data_shift_next;
|
||||||
|
|
||||||
|
logic [63:0] tbl_mem_rd_data_reg = '0;
|
||||||
|
logic [63:0] pba_mem_rd_data_reg = '0;
|
||||||
|
logic [63:0] tbl_apb_mem_rd_data_reg = '0;
|
||||||
|
logic [63:0] pba_apb_mem_rd_data_reg = '0;
|
||||||
|
|
||||||
|
wire [TBL_ADDR_W-1:0] s_apb_paddr_index = s_apb.paddr[INDEX_SHIFT +: TBL_ADDR_W];
|
||||||
|
wire [WORD_SELECT_W-1:0] s_apb_paddr_word = APB_DATA_W < 64 ? s_apb.paddr[WORD_SELECT_SHIFT +: WORD_SELECT_W] : 0;
|
||||||
|
|
||||||
|
assign s_apb.pready = s_apb_pready_reg;
|
||||||
|
assign s_apb.prdata = s_apb_prdata_reg;
|
||||||
|
assign s_apb.pslverr = 1'b0;
|
||||||
|
assign s_apb.pruser = '0;
|
||||||
|
assign s_apb.pbuser = '0;
|
||||||
|
|
||||||
|
assign s_axis_irq.tready = irq_ready_reg;
|
||||||
|
|
||||||
|
assign tx_wr_req_tlp.data = TLP_DATA_W'(tx_wr_req_tlp_data_reg);
|
||||||
|
assign tx_wr_req_tlp.empty = '1;
|
||||||
|
assign tx_wr_req_tlp.hdr = tx_wr_req_tlp_hdr_reg;
|
||||||
|
assign tx_wr_req_tlp.seq = '0;
|
||||||
|
assign tx_wr_req_tlp.bar_id = '0;
|
||||||
|
assign tx_wr_req_tlp.func_num = '0;
|
||||||
|
assign tx_wr_req_tlp.error = '0;
|
||||||
|
assign tx_wr_req_tlp.valid = tx_wr_req_tlp_valid_reg;
|
||||||
|
assign tx_wr_req_tlp.sop = 1'b1;
|
||||||
|
assign tx_wr_req_tlp.eop = 1'b1;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
for (integer i = 0; i < 2**TBL_ADDR_W; i = i + 1) begin
|
||||||
|
tbl_mem[i] = '0;
|
||||||
|
end
|
||||||
|
for (integer i = 0; i < 2**PBA_ADDR_W; i = i + 1) begin
|
||||||
|
pba_mem[i] = '0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
always_comb begin
|
||||||
|
state_next = STATE_IDLE;
|
||||||
|
|
||||||
|
tbl_mem_rd_en = 1'b0;
|
||||||
|
tbl_mem_addr = {irq_index_reg, 1'b0};
|
||||||
|
|
||||||
|
pba_mem_rd_en = 1'b0;
|
||||||
|
pba_mem_wr_en = 1'b0;
|
||||||
|
pba_mem_addr = PBA_ADDR_W_INT'(irq_index_reg >> 6);
|
||||||
|
pba_mem_wr_data = '0;
|
||||||
|
|
||||||
|
irq_index_next = irq_index_reg;
|
||||||
|
|
||||||
|
vec_addr_next = vec_addr_reg;
|
||||||
|
vec_data_next = vec_data_reg;
|
||||||
|
vec_mask_next = vec_mask_reg;
|
||||||
|
|
||||||
|
irq_ready_next = 1'b0;
|
||||||
|
|
||||||
|
tx_wr_req_tlp_data_next = tx_wr_req_tlp_data_reg;
|
||||||
|
tx_wr_req_tlp_hdr_next = tx_wr_req_tlp_hdr_reg;
|
||||||
|
tx_wr_req_tlp_valid_next = tx_wr_req_tlp_valid_reg && !tx_wr_req_tlp.ready;
|
||||||
|
|
||||||
|
// TLP header
|
||||||
|
// DW 0
|
||||||
|
if (((vec_addr_reg[63:2] >> 30) != 0) || TLP_FORCE_64_BIT_ADDR) begin
|
||||||
|
tlp_hdr[127:125] = TLP_FMT_4DW_DATA; // fmt - 4DW with data
|
||||||
|
end else begin
|
||||||
|
tlp_hdr[127:125] = TLP_FMT_3DW_DATA; // fmt - 3DW with data
|
||||||
|
end
|
||||||
|
tlp_hdr[124:120] = 5'b00000; // type - write
|
||||||
|
tlp_hdr[119] = 1'b0; // T9
|
||||||
|
tlp_hdr[118:116] = 3'b000; // TC
|
||||||
|
tlp_hdr[115] = 1'b0; // T8
|
||||||
|
tlp_hdr[114] = 1'b0; // attr
|
||||||
|
tlp_hdr[113] = 1'b0; // LN
|
||||||
|
tlp_hdr[112] = 1'b0; // TH
|
||||||
|
tlp_hdr[111] = 1'b0; // TD
|
||||||
|
tlp_hdr[110] = 1'b0; // EP
|
||||||
|
tlp_hdr[109:108] = 2'b00; // attr
|
||||||
|
tlp_hdr[107:106] = 2'b00; // AT
|
||||||
|
tlp_hdr[105:96] = 10'd1; // length
|
||||||
|
// DW 1
|
||||||
|
tlp_hdr[95:88] = bus_num; // requester ID (bus number)
|
||||||
|
tlp_hdr[87:80] = func_num; // requester ID (device/function number)
|
||||||
|
tlp_hdr[79:72] = 8'd0; // tag
|
||||||
|
tlp_hdr[71:68] = 4'b0000; // last BE
|
||||||
|
tlp_hdr[67:64] = 4'b1111; // first BE
|
||||||
|
if ((vec_addr_reg[63:32] != 0) || TLP_FORCE_64_BIT_ADDR) begin
|
||||||
|
// DW 2+3
|
||||||
|
tlp_hdr[63:2] = vec_addr_reg[63:2]; // address
|
||||||
|
tlp_hdr[1:0] = 2'b00; // PH
|
||||||
|
end else begin
|
||||||
|
// DW 2
|
||||||
|
tlp_hdr[63:34] = vec_addr_reg[31:2]; // address
|
||||||
|
tlp_hdr[33:32] = 2'b00; // PH
|
||||||
|
// DW 3
|
||||||
|
tlp_hdr[31:0] = 32'd0;
|
||||||
|
end
|
||||||
|
|
||||||
|
case (state_reg)
|
||||||
|
STATE_IDLE: begin
|
||||||
|
irq_ready_next = 1'b1;
|
||||||
|
|
||||||
|
if (s_axis_irq.tvalid && s_axis_irq.tready) begin
|
||||||
|
// new request
|
||||||
|
irq_ready_next = 1'b0;
|
||||||
|
irq_index_next = s_axis_irq.tdata;
|
||||||
|
|
||||||
|
tbl_mem_rd_en = 1'b1;
|
||||||
|
tbl_mem_addr = {irq_index_next, 1'b0};
|
||||||
|
|
||||||
|
pba_mem_rd_en = 1'b1;
|
||||||
|
pba_mem_addr = PBA_ADDR_W_INT'(irq_index_next >> 6);
|
||||||
|
|
||||||
|
state_next = STATE_READ_TBL_1;
|
||||||
|
end else if (!s_axis_irq.tvalid && msix_enable_reg && !msix_mask_reg) begin
|
||||||
|
// no new request waiting, scan PBA for masked requests
|
||||||
|
|
||||||
|
if (pba_mem_rd_data_reg[6'(irq_index_reg)] != 0) begin
|
||||||
|
// PBA bit for current index is set, try issuing it
|
||||||
|
irq_ready_next = 1'b0;
|
||||||
|
|
||||||
|
tbl_mem_rd_en = 1'b1;
|
||||||
|
tbl_mem_addr = {irq_index_next, 1'b0};
|
||||||
|
|
||||||
|
pba_mem_rd_en = 1'b1;
|
||||||
|
pba_mem_addr = PBA_ADDR_W_INT'(irq_index_next >> 6);
|
||||||
|
|
||||||
|
state_next = STATE_READ_TBL_1;
|
||||||
|
end else begin
|
||||||
|
// PBA bit for current index is not set
|
||||||
|
if (pba_mem_rd_data_reg != 0) begin
|
||||||
|
// at least one bit set in current group, move to next index
|
||||||
|
irq_index_next = irq_index_reg + 1;
|
||||||
|
end else begin
|
||||||
|
// no bits set in current group, move to next group
|
||||||
|
irq_index_next = (irq_index_reg & ({IRQ_INDEX_W{1'b1}} << 6)) + 'd64;
|
||||||
|
end
|
||||||
|
|
||||||
|
pba_mem_rd_en = 1'b1;
|
||||||
|
pba_mem_addr = PBA_ADDR_W_INT'(irq_index_next >> 6);
|
||||||
|
|
||||||
|
state_next = STATE_IDLE;
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
state_next = STATE_IDLE;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
STATE_READ_TBL_1: begin
|
||||||
|
// handle first table read
|
||||||
|
tbl_mem_rd_en = 1'b1;
|
||||||
|
tbl_mem_addr = {irq_index_reg, 1'b1};
|
||||||
|
|
||||||
|
vec_addr_next = {tbl_mem_rd_data_reg[63:2], 2'b00};
|
||||||
|
|
||||||
|
state_next = STATE_READ_TBL_2;
|
||||||
|
end
|
||||||
|
STATE_READ_TBL_2: begin
|
||||||
|
// handle second table read
|
||||||
|
vec_data_next = tbl_mem_rd_data_reg[31:0];
|
||||||
|
vec_mask_next = tbl_mem_rd_data_reg[32];
|
||||||
|
|
||||||
|
if (msix_enable_reg && !msix_mask_reg && !vec_mask_next) begin
|
||||||
|
// send TLP
|
||||||
|
state_next = STATE_SEND_TLP;
|
||||||
|
end else begin
|
||||||
|
// set PBA bit
|
||||||
|
pba_mem_wr_en = 1'b1;
|
||||||
|
pba_mem_wr_data = pba_mem_rd_data_reg | (1 << 6'(irq_index_reg));
|
||||||
|
irq_ready_next = 1'b1;
|
||||||
|
state_next = STATE_IDLE;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
STATE_SEND_TLP: begin
|
||||||
|
if (!tx_wr_req_tlp.valid || tx_wr_req_tlp.ready) begin
|
||||||
|
// send TLP
|
||||||
|
tx_wr_req_tlp_data_next = vec_data_reg;
|
||||||
|
tx_wr_req_tlp_hdr_next = tlp_hdr;
|
||||||
|
tx_wr_req_tlp_valid_next = 1'b1;
|
||||||
|
|
||||||
|
// clear PBA bit
|
||||||
|
pba_mem_wr_en = 1'b1;
|
||||||
|
pba_mem_wr_data = pba_mem_rd_data_reg & ~(1 << 6'(irq_index_reg));
|
||||||
|
|
||||||
|
// increment index so we don't check the same PBA bit immediately
|
||||||
|
irq_index_next = irq_index_reg + 1;
|
||||||
|
|
||||||
|
irq_ready_next = 1'b1;
|
||||||
|
state_next = STATE_IDLE;
|
||||||
|
end else begin
|
||||||
|
state_next = STATE_SEND_TLP;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
state_reg <= state_next;
|
||||||
|
|
||||||
|
irq_index_reg <= irq_index_next;
|
||||||
|
|
||||||
|
vec_addr_reg <= vec_addr_next;
|
||||||
|
vec_data_reg <= vec_data_next;
|
||||||
|
vec_mask_reg <= vec_mask_next;
|
||||||
|
|
||||||
|
irq_ready_reg <= irq_ready_next;
|
||||||
|
|
||||||
|
tx_wr_req_tlp_data_reg <= tx_wr_req_tlp_data_next;
|
||||||
|
tx_wr_req_tlp_hdr_reg <= tx_wr_req_tlp_hdr_next;
|
||||||
|
tx_wr_req_tlp_valid_reg <= tx_wr_req_tlp_valid_next;
|
||||||
|
|
||||||
|
msix_enable_reg <= msix_enable;
|
||||||
|
msix_mask_reg <= msix_mask;
|
||||||
|
|
||||||
|
if (tbl_mem_rd_en) begin
|
||||||
|
tbl_mem_rd_data_reg <= tbl_mem[tbl_mem_addr];
|
||||||
|
end
|
||||||
|
|
||||||
|
if (pba_mem_wr_en) begin
|
||||||
|
pba_mem[pba_mem_addr] <= pba_mem_wr_data;
|
||||||
|
end else if (pba_mem_rd_en) begin
|
||||||
|
pba_mem_rd_data_reg <= pba_mem[pba_mem_addr];
|
||||||
|
end
|
||||||
|
|
||||||
|
if (rst) begin
|
||||||
|
state_reg <= STATE_IDLE;
|
||||||
|
|
||||||
|
irq_ready_reg <= 1'b0;
|
||||||
|
|
||||||
|
tx_wr_req_tlp_valid_reg <= 1'b0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// APB interface
|
||||||
|
always_comb begin
|
||||||
|
tbl_apb_mem_rd_en = 1'b0;
|
||||||
|
tbl_apb_mem_wr_en = 1'b0;
|
||||||
|
tbl_apb_mem_wr_be = 8'(s_apb.pstrb << (s_apb_paddr_word * APB_STRB_W));
|
||||||
|
tbl_apb_mem_wr_data = {2**WORD_SELECT_W{s_apb.pwdata}};
|
||||||
|
pba_apb_mem_rd_en = 1'b0;
|
||||||
|
|
||||||
|
tbl_rd_data_valid_next = 1'b0;
|
||||||
|
pba_rd_data_valid_next = 1'b0;
|
||||||
|
rd_data_shift_next = rd_data_shift_reg;
|
||||||
|
|
||||||
|
s_apb_pready_next = 1'b0;
|
||||||
|
s_apb_prdata_next = s_apb_prdata_reg;
|
||||||
|
|
||||||
|
if (tbl_rd_data_valid_reg || pba_rd_data_valid_reg) begin
|
||||||
|
s_apb_pready_next = !s_apb_pready_reg;
|
||||||
|
tbl_rd_data_valid_next = 1'b0;
|
||||||
|
pba_rd_data_valid_next = 1'b0;
|
||||||
|
|
||||||
|
if (tbl_rd_data_valid_reg) begin
|
||||||
|
if (APB_DATA_W < 64) begin
|
||||||
|
s_apb_prdata_next = APB_DATA_W'(tbl_apb_mem_rd_data_reg >> rd_data_shift_reg*APB_DATA_W);
|
||||||
|
end else begin
|
||||||
|
s_apb_prdata_next = APB_DATA_W'(tbl_apb_mem_rd_data_reg);
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
if (APB_DATA_W < 64) begin
|
||||||
|
s_apb_prdata_next = APB_DATA_W'(pba_apb_mem_rd_data_reg >> rd_data_shift_reg*APB_DATA_W);
|
||||||
|
end else begin
|
||||||
|
s_apb_prdata_next = APB_DATA_W'(pba_apb_mem_rd_data_reg);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if (s_apb.psel && s_apb.penable) begin
|
||||||
|
rd_data_shift_next = s_apb_paddr_word;
|
||||||
|
|
||||||
|
if (s_apb.pwrite) begin
|
||||||
|
s_apb_pready_next = !s_apb_pready_reg;
|
||||||
|
if (s_apb.paddr[IRQ_INDEX_W+5-1] == 0) begin
|
||||||
|
tbl_apb_mem_wr_en = !s_apb_pready_reg;
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
if (s_apb.paddr[IRQ_INDEX_W+5-1] == 0) begin
|
||||||
|
tbl_apb_mem_rd_en = 1'b1;
|
||||||
|
tbl_rd_data_valid_next = !s_apb_pready_reg;
|
||||||
|
end else begin
|
||||||
|
pba_apb_mem_rd_en = 1'b1;
|
||||||
|
pba_rd_data_valid_next = !s_apb_pready_reg;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
tbl_rd_data_valid_reg <= tbl_rd_data_valid_next;
|
||||||
|
pba_rd_data_valid_reg <= pba_rd_data_valid_next;
|
||||||
|
rd_data_shift_reg <= rd_data_shift_next;
|
||||||
|
|
||||||
|
s_apb_pready_reg <= s_apb_pready_next;
|
||||||
|
s_apb_prdata_reg <= s_apb_prdata_next;
|
||||||
|
|
||||||
|
if (tbl_apb_mem_rd_en) begin
|
||||||
|
tbl_apb_mem_rd_data_reg <= tbl_mem[s_apb_paddr_index];
|
||||||
|
end else begin
|
||||||
|
for (integer i = 0; i < 8; i = i + 1) begin
|
||||||
|
if (tbl_apb_mem_wr_en && tbl_apb_mem_wr_be[i]) begin
|
||||||
|
tbl_mem[s_apb_paddr_index][8*i +: 8] <= tbl_apb_mem_wr_data[8*i +: 8];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if (pba_apb_mem_rd_en) begin
|
||||||
|
pba_apb_mem_rd_data_reg <= pba_mem[s_apb_paddr_index[PBA_ADDR_W-1:0]];
|
||||||
|
end
|
||||||
|
|
||||||
|
if (rst) begin
|
||||||
|
tbl_rd_data_valid_reg <= 1'b0;
|
||||||
|
pba_rd_data_valid_reg <= 1'b0;
|
||||||
|
|
||||||
|
s_apb_pready_reg <= 1'b0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
`resetall
|
||||||
56
src/pcie/tb/taxi_pcie_msix_apb/Makefile
Normal file
56
src/pcie/tb/taxi_pcie_msix_apb/Makefile
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||||
|
#
|
||||||
|
# Copyright (c) 2021-2026 FPGA Ninja, LLC
|
||||||
|
#
|
||||||
|
# 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_pcie_msix_apb
|
||||||
|
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_pcie_tlp_if.sv
|
||||||
|
VERILOG_SOURCES += $(TAXI_SRC_DIR)/axis/rtl/taxi_axis_if.sv
|
||||||
|
VERILOG_SOURCES += $(TAXI_SRC_DIR)/apb/rtl/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_IRQ_INDEX_W := 11
|
||||||
|
export PARAM_APB_DATA_W := 32
|
||||||
|
export PARAM_APB_ADDR_W := $(shell expr $(PARAM_IRQ_INDEX_W) + 5 )
|
||||||
|
export PARAM_TLP_FORCE_64_BIT_ADDR := 0
|
||||||
|
|
||||||
|
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 += $(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
|
||||||
1
src/pcie/tb/taxi_pcie_msix_apb/pcie_if.py
Symbolic link
1
src/pcie/tb/taxi_pcie_msix_apb/pcie_if.py
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../pcie_if.py
|
||||||
349
src/pcie/tb/taxi_pcie_msix_apb/test_taxi_pcie_msix_apb.py
Normal file
349
src/pcie/tb/taxi_pcie_msix_apb/test_taxi_pcie_msix_apb.py
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 2022-2026 FPGA Ninja, LLC
|
||||||
|
|
||||||
|
Authors:
|
||||||
|
- Alex Forencich
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
import cocotb_test.simulator
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import cocotb
|
||||||
|
from cocotb.clock import Clock
|
||||||
|
from cocotb.triggers import RisingEdge
|
||||||
|
from cocotb.regression import TestFactory
|
||||||
|
|
||||||
|
from cocotbext.axi import ApbBus, ApbMaster
|
||||||
|
from cocotbext.axi import AxiStreamBus, AxiStreamSource
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pcie_if import PcieIfSink, PcieIfTxBus
|
||||||
|
except ImportError:
|
||||||
|
# attempt import from current directory
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
|
||||||
|
try:
|
||||||
|
from pcie_if import PcieIfSink, PcieIfTxBus
|
||||||
|
finally:
|
||||||
|
del sys.path[0]
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def assert_raises(exc_type, pattern=None):
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except exc_type as e:
|
||||||
|
if pattern:
|
||||||
|
assert re.match(pattern, str(e)), \
|
||||||
|
"Correct exception type caught, but message did not match pattern"
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise AssertionError("{} was not raised".format(exc_type.__name__))
|
||||||
|
|
||||||
|
|
||||||
|
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, 4, units="ns").start())
|
||||||
|
|
||||||
|
self.apb_master = ApbMaster(ApbBus.from_entity(dut.s_apb), dut.clk, dut.rst)
|
||||||
|
|
||||||
|
self.irq_source = AxiStreamSource(AxiStreamBus.from_entity(dut.s_axis_irq), dut.clk, dut.rst)
|
||||||
|
|
||||||
|
self.tlp_sink = PcieIfSink(PcieIfTxBus.from_entity(dut.tx_wr_req_tlp), dut.clk, dut.rst)
|
||||||
|
|
||||||
|
dut.bus_num.setimmediatevalue(0)
|
||||||
|
dut.func_num.setimmediatevalue(0)
|
||||||
|
dut.msix_enable.setimmediatevalue(0)
|
||||||
|
dut.msix_mask.setimmediatevalue(0)
|
||||||
|
|
||||||
|
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:
|
||||||
|
self.tlp_sink.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_table_write(dut, data_in=None, idle_inserter=None, backpressure_inserter=None):
|
||||||
|
|
||||||
|
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*4):
|
||||||
|
for offset in range(byte_lanes):
|
||||||
|
tb.log.info("length %d, offset %d", length, offset)
|
||||||
|
addr = offset+0x100
|
||||||
|
test_data = bytearray([x % 256 for x in range(length)])
|
||||||
|
|
||||||
|
await tb.apb_master.write(addr-4, b'\xaa'*(length+8))
|
||||||
|
|
||||||
|
await tb.apb_master.write(addr, test_data)
|
||||||
|
|
||||||
|
data = await tb.apb_master.read(addr-1, length+2)
|
||||||
|
|
||||||
|
assert data.data == b'\xaa'+test_data+b'\xaa'
|
||||||
|
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_test_table_read(dut, data_in=None, idle_inserter=None, backpressure_inserter=None):
|
||||||
|
|
||||||
|
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*4):
|
||||||
|
for offset in range(byte_lanes):
|
||||||
|
tb.log.info("length %d, offset %d", length, offset)
|
||||||
|
addr = offset+0x100
|
||||||
|
test_data = bytearray([x % 256 for x in range(length)])
|
||||||
|
|
||||||
|
await tb.apb_master.write(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_test_msix(dut, idle_inserter=None, backpressure_inserter=None):
|
||||||
|
|
||||||
|
tb = TB(dut)
|
||||||
|
|
||||||
|
tbl_offset = 0
|
||||||
|
pba_offset = 2**(tb.apb_master.address_width-1)
|
||||||
|
|
||||||
|
await tb.cycle_reset()
|
||||||
|
|
||||||
|
tb.set_idle_generator(idle_inserter)
|
||||||
|
tb.set_backpressure_generator(backpressure_inserter)
|
||||||
|
|
||||||
|
dut.msix_enable.value = 1
|
||||||
|
|
||||||
|
tb.log.info("Init table")
|
||||||
|
|
||||||
|
for k in range(2**len(dut.s_axis_irq.tdata)):
|
||||||
|
await tb.apb_master.write_qword(tbl_offset+k*16+0, 0x1234567800000000 + k*4)
|
||||||
|
await tb.apb_master.write_dword(tbl_offset+k*16+8, k)
|
||||||
|
await tb.apb_master.write_dword(tbl_offset+k*16+12, 0)
|
||||||
|
|
||||||
|
tb.log.info("Test unmasked interrupts")
|
||||||
|
|
||||||
|
for k in range(8):
|
||||||
|
await tb.irq_source.send([k])
|
||||||
|
|
||||||
|
for k in range(8):
|
||||||
|
frame = await tb.tlp_sink.recv()
|
||||||
|
tlp = frame.to_tlp()
|
||||||
|
|
||||||
|
tb.log.info("TLP: %s", tlp)
|
||||||
|
|
||||||
|
assert tlp.address == 0x1234567800000000 + k*4
|
||||||
|
assert tlp.data == k.to_bytes(4, 'little')
|
||||||
|
assert tlp.first_be == 0xf
|
||||||
|
|
||||||
|
val = await tb.apb_master.read_dword(pba_offset+0)
|
||||||
|
|
||||||
|
tb.log.info("PBA value: 0x%02x", val)
|
||||||
|
|
||||||
|
assert val == 0x00
|
||||||
|
|
||||||
|
tb.log.info("Test global mask")
|
||||||
|
|
||||||
|
dut.msix_mask.value = 1
|
||||||
|
|
||||||
|
for k in range(8):
|
||||||
|
await tb.irq_source.send([k])
|
||||||
|
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
while int(dut.s_axis_irq.tvalid.value):
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
for k in range(10):
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
|
||||||
|
val = await tb.apb_master.read_dword(pba_offset+0)
|
||||||
|
|
||||||
|
tb.log.info("PBA value: 0x%02x", val)
|
||||||
|
|
||||||
|
assert val == 0xff
|
||||||
|
|
||||||
|
dut.msix_mask.value = 0
|
||||||
|
|
||||||
|
for k in range(8):
|
||||||
|
frame = await tb.tlp_sink.recv()
|
||||||
|
tlp = frame.to_tlp()
|
||||||
|
|
||||||
|
tb.log.info("TLP: %s", tlp)
|
||||||
|
|
||||||
|
assert tlp.address == 0x1234567800000000 + k*4
|
||||||
|
assert tlp.data == k.to_bytes(4, 'little')
|
||||||
|
assert tlp.first_be == 0xf
|
||||||
|
|
||||||
|
val = await tb.apb_master.read_dword(pba_offset+0)
|
||||||
|
|
||||||
|
tb.log.info("PBA value: 0x%02x", val)
|
||||||
|
|
||||||
|
assert val == 0x00
|
||||||
|
|
||||||
|
tb.log.info("Test vector masks")
|
||||||
|
|
||||||
|
for k in range(8):
|
||||||
|
await tb.apb_master.write_dword(tbl_offset+k*16+12, 1)
|
||||||
|
|
||||||
|
for k in range(8):
|
||||||
|
await tb.irq_source.send([k])
|
||||||
|
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
while int(dut.s_axis_irq.tvalid.value):
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
for k in range(10):
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
|
||||||
|
val = await tb.apb_master.read_dword(pba_offset+0)
|
||||||
|
|
||||||
|
tb.log.info("PBA value: 0x%02x", val)
|
||||||
|
|
||||||
|
assert val == 0xff
|
||||||
|
|
||||||
|
for k in range(8):
|
||||||
|
await tb.apb_master.write_dword(tbl_offset+k*16+12, 0)
|
||||||
|
|
||||||
|
for k in range(8):
|
||||||
|
frame = await tb.tlp_sink.recv()
|
||||||
|
tlp = frame.to_tlp()
|
||||||
|
|
||||||
|
tb.log.info("TLP: %s", tlp)
|
||||||
|
|
||||||
|
assert tlp.address == 0x1234567800000000 + k*4
|
||||||
|
assert tlp.data == k.to_bytes(4, 'little')
|
||||||
|
assert tlp.first_be == 0xf
|
||||||
|
|
||||||
|
val = await tb.apb_master.read_dword(pba_offset+0)
|
||||||
|
|
||||||
|
tb.log.info("PBA value: 0x%02x", val)
|
||||||
|
|
||||||
|
assert val == 0x00
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
for test in [
|
||||||
|
run_test_table_write,
|
||||||
|
run_test_table_read,
|
||||||
|
run_test_msix
|
||||||
|
]:
|
||||||
|
|
||||||
|
factory = TestFactory(test)
|
||||||
|
factory.add_option("idle_inserter", [None, cycle_pause])
|
||||||
|
factory.add_option("backpressure_inserter", [None, cycle_pause])
|
||||||
|
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("apb_data_w", [32, 64])
|
||||||
|
def test_taxi_pcie_msix_apb(request, apb_data_w):
|
||||||
|
dut = "taxi_pcie_msix_apb"
|
||||||
|
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_pcie_tlp_if.sv"),
|
||||||
|
os.path.join(taxi_src_dir, "axis", "rtl", "taxi_axis_if.sv"),
|
||||||
|
os.path.join(taxi_src_dir, "apb", "rtl", "taxi_apb_if.sv"),
|
||||||
|
]
|
||||||
|
|
||||||
|
parameters = {}
|
||||||
|
|
||||||
|
parameters['IRQ_INDEX_W'] = 11
|
||||||
|
parameters['APB_DATA_W'] = apb_data_w
|
||||||
|
parameters['APB_ADDR_W'] = parameters['IRQ_INDEX_W']+5
|
||||||
|
parameters['TLP_FORCE_64_BIT_ADDR'] = 0
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
93
src/pcie/tb/taxi_pcie_msix_apb/test_taxi_pcie_msix_apb.sv
Normal file
93
src/pcie/tb/taxi_pcie_msix_apb/test_taxi_pcie_msix_apb.sv
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
// SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||||
|
/*
|
||||||
|
|
||||||
|
Copyright (c) 2026 FPGA Ninja, LLC
|
||||||
|
|
||||||
|
Authors:
|
||||||
|
- Alex Forencich
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
`resetall
|
||||||
|
`timescale 1ns / 1ps
|
||||||
|
`default_nettype none
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PCIe MSI-X module testbench
|
||||||
|
*/
|
||||||
|
module test_taxi_pcie_msix_apb #
|
||||||
|
(
|
||||||
|
/* verilator lint_off WIDTHTRUNC */
|
||||||
|
parameter IRQ_INDEX_W = 11,
|
||||||
|
parameter TLP_SEG_DATA_W = 64,
|
||||||
|
parameter TLP_SEGS = 1,
|
||||||
|
parameter APB_DATA_W = 32,
|
||||||
|
parameter APB_ADDR_W = IRQ_INDEX_W+5,
|
||||||
|
parameter logic TLP_FORCE_64_BIT_ADDR = 1'b0
|
||||||
|
/* verilator lint_on WIDTHTRUNC */
|
||||||
|
)
|
||||||
|
();
|
||||||
|
|
||||||
|
logic clk;
|
||||||
|
logic rst;
|
||||||
|
|
||||||
|
taxi_apb_if #(
|
||||||
|
.DATA_W(APB_DATA_W),
|
||||||
|
.ADDR_W(APB_ADDR_W),
|
||||||
|
.PAUSER_EN(1'b0),
|
||||||
|
.PWUSER_EN(1'b0),
|
||||||
|
.PBUSER_EN(1'b0),
|
||||||
|
.PRUSER_EN(1'b0)
|
||||||
|
) s_apb();
|
||||||
|
|
||||||
|
taxi_axis_if #(
|
||||||
|
.DATA_W(IRQ_INDEX_W),
|
||||||
|
.KEEP_EN(0),
|
||||||
|
.KEEP_W(1)
|
||||||
|
) s_axis_irq();
|
||||||
|
|
||||||
|
taxi_pcie_tlp_if #(
|
||||||
|
.SEGS(TLP_SEGS),
|
||||||
|
.SEG_DATA_W(TLP_SEG_DATA_W),
|
||||||
|
.FUNC_NUM_W(8)
|
||||||
|
) tx_wr_req_tlp();
|
||||||
|
|
||||||
|
logic [7:0] bus_num;
|
||||||
|
logic [7:0] func_num;
|
||||||
|
logic msix_enable;
|
||||||
|
logic msix_mask;
|
||||||
|
|
||||||
|
taxi_pcie_msix_apb #(
|
||||||
|
.TLP_FORCE_64_BIT_ADDR(TLP_FORCE_64_BIT_ADDR)
|
||||||
|
)
|
||||||
|
uut (
|
||||||
|
.clk(clk),
|
||||||
|
.rst(rst),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* APB interface for MSI-X tables
|
||||||
|
*/
|
||||||
|
.s_apb(s_apb),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Interrupt request input
|
||||||
|
*/
|
||||||
|
.s_axis_irq(s_axis_irq),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Memory write TLP output
|
||||||
|
*/
|
||||||
|
.tx_wr_req_tlp(tx_wr_req_tlp),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Configuration
|
||||||
|
*/
|
||||||
|
.bus_num(bus_num),
|
||||||
|
.func_num(func_num),
|
||||||
|
.msix_enable(msix_enable),
|
||||||
|
.msix_mask(msix_mask)
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
`resetall
|
||||||
Reference in New Issue
Block a user