// // Copyright 2021 Ettus Research, A National Instruments Brand // // SPDX-License-Identifier: LGPL-3.0-or-later // // Module: eth_100g_lbus2axi // // Description: // Translate from lbus (xilinx segmented ifc) to // AXI4S. // // 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 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 // // Fifo Output // Data starts from the SRL and is indexed by the read pointer // Data_Valid comes from a comparison on fullness // Invalid control is forced to zero (necessary for algorithm) // It's not necessary to force all the data to zero just the control plane. // // Fifo output data is rotated (4 to 1) mux then reinterpreted as lbus data // // The rotated control signals are analyzed to determine // no_eop, no_sop, some_empty, no_ena // // 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. // import PkgEth100gLbus::*; module eth_100g_lbus2axi #( parameter FIFO_DEPTH = 5, parameter NUM_SEG = 4 ) ( // AXIS IF AxiStreamIf.master axis, // Lbus Segments input lbus_t lbus_in [NUM_SEG-1:0] ); localparam SEG_BYTES = SEG_DATA_WIDTH/8; localparam SEG_MTY_WIDTH = $clog2(SEG_BYTES); localparam SEG_SHMEAR_WIDTH = SEG_DATA_WIDTH + SEG_MTY_WIDTH + 4; ////////////////////////////////////////////////////////////////////////////////// ////////////////// Data Input to FIFO /////////// ////////////////////////////////////////////////////////////////////////////////// lbus_t lbus_fout_p[NUM_SEG-1:0]; //{ena,err,eop,sop,mty,data} lbus_t lbus_fout[NUM_SEG-1:0]; //{ena,err,eop,sop,mty,data} //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 assign push = lbus_in[0].ena; // 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 ////////////////////////////////////////////////////////////////////////////////// // INLINE FIFO ////////////////////////////////////////////////////////////////////////////////// // simulation error if we push a full fifo always_comb begin if (push) begin assert (!full[gseg1]) else $error("Pushing full fifo!"); end end // limit fanout to improve timing (* max_fanout = 75 *) logic [4:0] a; for (b1=0;b1> rot; //rotate_right // Copy slice back to the struct for(gseg2 = 0; gseg2 < NUM_SEG; gseg2=gseg2+1) begin assign lbus_rot[gseg2][b2] = slice_rotated[gseg2]; end end end : rotate_lbus endgenerate always_comb begin : rotate_data_valid rot_empty = {empty,empty} >> rot; //rotate_right end : rotate_data_valid ////////////////////////////////////////////////////////////////////////////////// ////////////////// LBUS out DFF ///////////////////// ////////////////////////////////////////////////////////////////////////////////// // This pipe stage is mainly to allow suming MTY bits and to add space for // Vivado to try to pipeline the output // post rotation lbus signals lbus_t lbus_out [NUM_SEG-1:0]; logic [NUM_SEG-1:0] axi_seg_valid; always_ff @(posedge axis.clk) begin if (axis.rst) begin foreach (lbus_out[seg]) begin : segment_loop lbus_out[seg] <= '0; end axi_seg_valid <= '0; end else begin lbus_out <= lbus_rot; if (send_idle) axi_seg_valid <= '0; else if (no_eop) axi_seg_valid <= '1; else axi_seg_valid <= rot_xfer_now; end end //////////////////////////////////////////////////////////////////////////// // Generate AXI //////////////////////////////////////////////////////////////////////////// logic [axis.DATA_WIDTH - 1:0] axis_tdata_w; logic [$clog2(axis.DATA_WIDTH/8) - 1:0] axis_tuser_bytes_w; logic [axis.DATA_WIDTH/8 - 1:0] axis_tkeep_w; logic [NUM_SEG-1:0] axis_tlast_w; logic [NUM_SEG-1:0] axis_tvalid_w; logic [NUM_SEG-1:0] axis_tuser_err_w; always_comb begin : axis_translate axis_tuser_bytes_w = 'd0; // init to zero before summing foreach (axis_tvalid_w[seg]) begin : segment_loop axis_tvalid_w[seg] = lbus_out[seg].ena & axi_seg_valid[seg]; axis_tlast_w[seg] = lbus_out[seg].eop; axis_tuser_err_w[seg] = lbus_out[seg].err; // sum all the segment mty vectors if (lbus_out[seg].ena && axi_seg_valid[seg]) begin axis_tuser_bytes_w += SEG_DATA_WIDTH/8 - lbus_out[seg].mty; end // 512 bit word = 64 bytes = 4 X 128 bit(16 byte) segments // assign bytes : LbusOrder // S0 : S0B0..S0B15 // S1 : S1B0..S1B15 // S2 : S2B0..S2B15 // S3 : S3B0..S3B15 // AXI (swap Endianess on each segment) // AXI = S3B15..S3B0, S2B15..S2B0, S1B15..S1B0, S0B15..S0B0 for(int b = 0; b < SEG_BYTES; b=b+1) begin : tdata_loop // ( 1 * 128 )-8- 0*8) 120+:8 = S0B0 // ( 1 * 128 )-8- 1*8) 112+:8 = S0B1 // ... // ( 1 * 128 )-8-14*8) 8+:8 = S0B14 // ( 1 * 128 )-8-15*8) 0+:8 = S0B15 //////////////////////////////////// // ( 2 * 128 )-8- 0*8) 248+:8 = S1B0 // ( 2 * 128 )-8- 1*8) 240+:8 = S1B1 // ... // ( 2 * 128 )-8-14*8) 136+:8 = S1B14 // ( 2 * 128 )-8-15*8) 128+:8 = S1B15 //////////////////////////////////// // ... //////////////////////////////////// // ( 4 * 128 )-8- 0*8) 504+:8 = S3B0 // ( 4 * 128 )-8- 1*8) 496+:8 = S3B1 // ... // ( 4 * 128 )-8-14*8) 136+:8 = S3B14 // ( 4 * 128 )-8-15*8) 384+:8 = S3B15 axis_tdata_w[((seg+1)*axis.DATA_WIDTH/NUM_SEG-8-b*8) +: 8] = lbus_out[seg].data[b*8 +: 8]; end : tdata_loop end : segment_loop end : axis_translate // convert bytes to keep always_comb begin axis_tkeep_w = '1; if (axis_tlast_w != 0 && axis_tuser_bytes_w != 0) begin foreach(axis_tkeep_w[b]) begin axis_tkeep_w[b] = axis_tuser_bytes_w > b; end end end ////////////////////////////////////////////////////////////////////////////////// ////////////////// AXIS output flop ///////////////////// ////////////////////////////////////////////////////////////////////////////////// localparam AXIS_MTY_WIDTH = $clog2(axis.BYTES_PER_WORD); always_ff @(posedge axis.clk) begin if (axis.rst) begin axis.tdata <= '0; axis.tvalid <= 1'b0; axis.tlast <= 1'b0; axis.tuser <= '0; axis.tkeep <= '0; end else begin axis.tdata <= axis_tdata_w; axis.tvalid <= |axis_tvalid_w; axis.tlast <= |axis_tlast_w; if (axis.TKEEP == 1) begin axis.tkeep <= axis_tkeep_w; end else begin axis.tkeep <= 'X; end // trailing bytes in last word axis.tuser[AXIS_MTY_WIDTH-1:0] <= axis_tuser_bytes_w; // MSB is error axis.tuser[AXIS_MTY_WIDTH] <= |axis_tuser_err_w; end end endmodule