fpga: x400: refactor 100GEth LBUS to AXI interface

Original-commit: a218a6a503e5e4baddb939affa57b5c960ffbc84
This commit is contained in:
Max Köhler
2025-01-31 09:08:03 -06:00
committed by Wade Fife
parent 067f150b75
commit f4b6def040
3 changed files with 62 additions and 190 deletions
+1 -1
View File
@@ -1129,7 +1129,7 @@ module eth_100g #(
lbus_t lbus_tx [3:0];
logic lbus_tx_rdyout;
eth_100g_lbus2axi #(.NUM_SEG(4)) lbus2axi (
eth_100g_lbus2axis lbus2axi (
.axis(eth100g_rx),
.lbus_in(lbus_rx)
);
+59 -187
View File
@@ -3,7 +3,7 @@
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: eth_100g_lbus2axi
// Module: eth_100g_lbus2axis
//
// Description:
// Translate from lbus (xilinx segmented ifc) to
@@ -11,20 +11,16 @@
//
// Built using example provided from Xilinx
//
// Parameters:
// - FIFO_DEPTH - FIFO will be 2** deep
// - NUM_SEG - Number of lbus segments coming in
//
// Notes on timing difficulty
// The path back to pop is challenged
// -LBUS is popped out of the FIFO (SRL read can be slow)
// -LBUS is popped out of the AXI flop
// -LBUS is rotated N to 1 Mux (N= number of segments) For 100g N=4
// -Find where EOP is (search for the first 1)
// -Unrotate the number of words and use that to calculate pop
// -pop is used to update the AXI flop registers
//
// Fifo Output
// Data starts from the SRL and is indexed by the read pointer
// Data_Valid comes from a comparison on fullness
// Data starts from the AXI flop
// Invalid control is forced to zero (necessary for algorithm)
// It's not necessary to force all the data to zero just the control plane.
//
@@ -35,16 +31,11 @@
//
// eop is specifically inspected in a 4in,4out function to find a pseudo
// one hot. this is unrotated along with enable, and combined with
// datavalid to determine the next pop, which controls incrementing of the
// rd_pointer.
// datavalid to determine the next pop.
//
import PkgEth100gLbus::*;
module eth_100g_lbus2axi #(
parameter FIFO_DEPTH = 5,
parameter NUM_SEG = 4
)
module eth_100g_lbus2axis
import PkgEth100gLbus::*;
(
// AXIS IF
@@ -68,8 +59,6 @@ module eth_100g_lbus2axi #(
//FIFO Logic
logic push;
logic [NUM_SEG-1:0] pop;
logic [NUM_SEG-1:0] full;
logic [NUM_SEG-1:0] empty;
// always push the fifo on all lanes
@@ -77,131 +66,62 @@ module eth_100g_lbus2axi #(
// For each lane of incoming data place it into a separate FIFO
generate
genvar b1,gseg1;
begin : gen_seg_fifo
for(gseg1 = 0; gseg1 < NUM_SEG; gseg1=gseg1+1) begin
for(genvar gseg1 = 0; gseg1 < NUM_SEG; gseg1=gseg1+1) begin
//////////////////////////////////////////////////////////////////////////////////
// INLINE FIFO
//////////////////////////////////////////////////////////////////////////////////
logic input_fifo_i_tready;
logic input_fifo_o_tvalid;
logic input_fifo_o_tready;
logic input_flop_o_tvalid;
lbus_t lbus_fifo;
// simulation error if we push a full fifo
// synopsys translate_off
always_comb begin
if (push) begin
assert (!full[gseg1]) else $error("Pushing full fifo!");
assert (input_fifo_i_tready) else $error("Pushing full fifo!");
end
end
// synopsys translate_on
// limit fanout to improve timing
(* max_fanout = 75 *) logic [4:0] a;
for (b1=0;b1<SEG_DATA_WIDTH;b1=b1+1) begin : gen_srl_data
SRLC32E srl_data(
.Q(lbus_fout_p[gseg1].data[b1]), .Q31(),
.A(a),
.CE(push),.CLK(axis.clk),.D(lbus_in[gseg1].data[b1])
);
end
for (b1=0;b1<SEG_MTY_WIDTH;b1=b1+1) begin : gen_srl_mty
SRLC32E srl_mty(
.Q(lbus_fout_p[gseg1].mty[b1]), .Q31(),
.A(a),
.CE(push),.CLK(axis.clk),.D(lbus_in[gseg1].mty[b1])
);
end
SRLC32E srl_err(
.Q(lbus_fout_p[gseg1].err), .Q31(),
.A(a),
.CE(push),.CLK(axis.clk),.D(lbus_in[gseg1].err)
// input FIFO used for storing data using SRLC32E primitives
axi_fifo_short #(
.WIDTH($bits(lbus_in[gseg1]))
) input_fifo (
.clk(axis.clk),
.reset(axis.rst),
.clear(1'b0),
.i_tdata(lbus_in[gseg1]),
.i_tvalid(push),
.i_tready(input_fifo_i_tready),
.o_tdata(lbus_fifo),
.o_tvalid(input_fifo_o_tvalid),
.o_tready(input_fifo_o_tready),
.space(),
.occupied()
);
// empty on prebuffer and SRL
logic my_empty;
always @(posedge axis.clk)
begin
if(axis.rst) begin
a <= 0;
my_empty <= 1;
full[gseg1] <= 0;
end else if(pop[gseg1] & ~push) begin
full[gseg1] <= 0;
if(a==0) begin
my_empty <= 1;
end else begin
a <= a - 1;
end
end else if(push & ~pop[gseg1]) begin
my_empty <= 0;
if(~my_empty) begin
a <= a + 1;
end
if(a == 30) begin
full[gseg1] <= 1;
end
end
end
// output flop attached to register to break critical timing paths
axi_fifo_flop2 #(
.WIDTH($bits(lbus_in[gseg1]))
) critical_fifo_flop (
.clk(axis.clk),
.reset(axis.rst),
.clear(1'b0),
.i_tdata(lbus_fifo),
.i_tvalid(input_fifo_o_tvalid),
.i_tready(input_fifo_o_tready),
.o_tdata(lbus_fout_p[gseg1]),
.o_tvalid(input_flop_o_tvalid),
.o_tready(pop[gseg1]),
.space(),
.occupied()
);
// FIFO for time sensitive control signals. This creates a separate 31 deep fifo from
// DFF's on just 3 signals. The data signals continue to use an SRL to save space.
// The design bellow is a FIFO followed by a single DFF regsiter that is automatically
// prefilled when the FIFO has data.
logic [4:0] w_ptr,r_ptr,r_ptr_d,fullness;
logic [31:0] ena_mem, sop_mem, eop_mem;
// Final fifo stage after memory to remove address muxing from timing path
// this adds one clock of latency to empty flag as it will take 2 clocks to propagate
// into fifo.
//push critical timing signals to final flop. This adds 1 clock of latency on
// the final empty flag, but removes muxing of the memory elements
logic push_dff;
//using r_ptr_d to avoid extra latency in fullness change
always_comb begin
if (pop[gseg1]) begin
r_ptr_d = r_ptr+1;
end else begin
r_ptr_d = r_ptr;
end
fullness = w_ptr-r_ptr_d;
push_dff = (fullness != 0) & (pop[gseg1] | empty[gseg1]);
end
// speedier fifo implementation on these three control signals
// THE goal of this complexity is to have the outputs be a direct FF output
// instead of a muxed memory output.
always @(posedge axis.clk)
begin
if(axis.rst) begin
ena_mem <= '0;
sop_mem <= '0;
eop_mem <= '0;
lbus_fout_p[gseg1].ena <= 1'b0;
lbus_fout_p[gseg1].sop <= 1'b0;
lbus_fout_p[gseg1].eop <= 1'b0;
w_ptr <= 0;
r_ptr <= 0;
empty[gseg1] <= 1'b1;
end else begin
if(push) begin
ena_mem[w_ptr] <= lbus_in[gseg1].ena;
sop_mem[w_ptr] <= lbus_in[gseg1].sop;
eop_mem[w_ptr] <= lbus_in[gseg1].eop;
w_ptr <= w_ptr+1;
end
r_ptr <= r_ptr_d;
if (push_dff) begin
empty[gseg1] <= 1'b0;
lbus_fout_p[gseg1].ena <= ena_mem[r_ptr_d];
lbus_fout_p[gseg1].sop <= sop_mem[r_ptr_d];
lbus_fout_p[gseg1].eop <= eop_mem[r_ptr_d];
end else if (pop[gseg1]) begin
empty[gseg1] <= 1'b1;
end
end
end
assign empty[gseg1] = ~input_flop_o_tvalid;
// clear the enables if this fifo segment is not valid
always_comb begin
@@ -289,74 +209,26 @@ module eth_100g_lbus2axi #(
////////////////// Calculate Pop ///////////
//////////////////////////////////////////////////////////////////////////////////
// After rotation figure out how far till eop
//==========================================================================
// one-hot to thermometer code
// The goal is to find how far down till we reach the first eop
// This represents the bytes we will trasnfer this clock
//==========================================================================
// Xilinx example
// case (in_reqs)
// 4'b1000: onehot2thermo = 4'b1111;
//
// 4'b1100: onehot2thermo = 4'b0111;
// 4'b0100: onehot2thermo = 4'b0111;
//
// 4'b1110: onehot2thermo = 4'b0011;
// 4'b0110: onehot2thermo = 4'b0011;
// 4'b0010: onehot2thermo = 4'b0011;
//
// 4'b1111: onehot2thermo = 4'b0001;
// 4'b0111: onehot2thermo = 4'b0001;
// 4'b0011: onehot2thermo = 4'b0001;
// 4'b0001: onehot2thermo = 4'b0001;
//
// default: onehot2thermo = 4'b0000;
// endcase
logic [NUM_SEG-1:0] rot_xfer_now;
logic [NUM_SEG-1:0] mask [NUM_SEG-1:0];
logic [NUM_SEG-1:0] m1hot [NUM_SEG-1:0];
logic [NUM_SEG-1:0] meop [NUM_SEG-1:0];
logic [NUM_SEG-1:0] match;
always_comb begin
rot_xfer_now = '0;
foreach (rot_eop[s]) begin
// The function
// XXX1=>0001
// XX10=>0011
// X100=>0111
// 1000=>1111
// MASK
// 2**(0+1)-1 = 0001
// 2**(1+1)-1 = 0011
// 2**(2+1)-1 = 0111
// 2**(3+1)-1 = 1111
mask[s] = 2**(s+1)-1; // Constant
// MASK
// 2**0 = 0001
// 2**1 = 0010
// 2**2 = 0100
// 2**3 = 1000
m1hot[s] = 2**s; // Constant
// Mask valid_eop
meop[s] = rot_eop & mask[s];
// compare against 1hot
match[s] = meop[s] == m1hot[s];
if (match[s]) begin
rot_xfer_now = mask[s];
end
end
end
// unrotate the values and calculate pop
logic [NUM_SEG-1:0] xfer_now;
logic [NUM_SEG-1:0] filler_seg;
always_comb begin
rot_xfer_now = '0;
if (eop) begin
for (int i = 0; i < NUM_SEG; i = i + 1) begin
rot_xfer_now[i] = '1;
if (rot_eop[i]) begin
break;
end
end
end
if (send_idle)
xfer_now = '0;
else if (no_eop | no_sop)
else if (no_eop)
xfer_now = '1;
else
// rotate left
@@ -7,7 +7,7 @@
//
// Description:
//
// Testbench for eth_100g_lbus2axi.
// Testbench for eth_100g_lbus2axis.
//
module lbus_axi_tb #(
@@ -52,7 +52,7 @@ module lbus_axi_tb #(
//----------------------------------------------------
lbus_t lbus_in [NUM_SEG-1:0];
eth_100g_lbus2axi #(.FIFO_DEPTH(5),.NUM_SEG(NUM_SEG)) DUT (
eth_100g_lbus2axis DUT (
.axis(axis),
.lbus_in(lbus_in)
);