Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
308 changes: 308 additions & 0 deletions ethernet/EthMacCore/rtl/EthMacCrcAxiStream.vhd
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
-------------------------------------------------------------------------------
-- Company : SLAC National Accelerator Laboratory
-------------------------------------------------------------------------------
-- Description: AXI-Stream CRC-32 (IEEE 802.3) calculator around
-- surf.EthCrc32Parallel. Sinks a 16-byte EMAC-style stream and
-- emits ONE 4-byte beat per packet carrying the CRC result.
-- CRC_MODE_G = "SEND": tData(31:0) = CRC-32 of the payload,
-- first-on-wire CRC byte in tData(7:0) (zlib.crc32 order).
-- CRC_MODE_G = "RECV": input is payload||receivedCRC;
-- tData(31:0) = x"00000000" iff the trailer is correct
-- (residue method: engine output xor RECV_RESIDUE_C).
-------------------------------------------------------------------------------
-- Generated by: bsv-icrc-to-vhdl Stage 4 (vhdl-emit)
-- BSV source : src-bsv/blue-crc/src/CrcAxiStream.bsv
-- (mkCrcAxiStreamFifoOut, mkCrcAxiStream, mkCrcRawAxiStream)
-- src-bsv/blue-crc/src/CrcAxiStreamCustom.bsv (tops)
-- Substitution: per SURF-CRC-REPLACEMENT-PLAN.md the blue-crc 8-stage
-- table-driven engine (36 file-initialized ROMs) is REPLACED by
-- surf.EthCrc32Parallel (poly 0x04C11DB7, reflect-in/out and
-- final inversion hard-coded in the engine); this wrapper only
-- reproduces blue-crc's external AXI-Stream contract.
-------------------------------------------------------------------------------
-- 2026-07-15 rework (udploop-crc-stall-frame-loss-issue.md): the original
-- 4-state FSM held sAxisSlave.tReady LOW from tLast until its single result
-- beat was consumed. On the MAC RX path the consumer (EthMacRxCheckICrc)
-- consumes a result only when it STARTS draining that frame, so the stall
-- stretched to a full frame-drain; the RoCE RX branch has no upstream flow
-- control (EthMacRxRoCEv2 DeMux slave open) and beats presented meanwhile
-- were silently destroyed. blue-crc never back-pressured (pipelined engine +
-- result FIFO: mkCrcAxiStreamFifoOut). This rework restores that contract:
-- * input is ALWAYS ready (guarded only by result-FIFO prog_full, which is
-- unreachable in practice: results outstanding <= frames resident in the
-- downstream delay FIFO);
-- * every accepted beat is folded immediately; a packet's final CRC is
-- captured 2 cycles after its tLast beat (beat presented at N folds
-- during N+1; r.crc final and stable during N+2 even if the next packet
-- folds back-to-back, since that overwrite lands on the N+2 -> N+3 edge)
-- into a 256-deep result FIFO;
-- * the engine is re-seeded per packet by a registered crcReset pulse
-- aligned to the FOLD cycle of each packet's first beat (crcReset is a
-- combinational prevCrc mux in the fold cycle; in idle cycles it
-- synchronously loads CRC_INIT, which also covers null first beats).
-------------------------------------------------------------------------------
-- This file is part of 'SLAC Firmware Standard Library'.
-- It is subject to the license terms in the LICENSE.txt file found in the
-- top-level directory of this distribution and at:
-- https://confluence.slac.stanford.edu/display/ppareg/LICENSE.html.
-- No part of 'SLAC Firmware Standard Library', including this file,
-- may be copied, modified, propagated, or distributed except according to
-- the terms contained in the LICENSE.txt file.
-------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;

library surf;
use surf.StdRtlPkg.all;
use surf.AxiStreamPkg.all;

entity EthMacCrcAxiStream is
generic (
TPD_G : time := 1 ns;
RST_POLARITY_G : sl := '1'; -- '1' for active HIGH reset, '0' for active LOW reset
RST_ASYNC_G : boolean := false;
CRC_MODE_G : string := "SEND"); -- "SEND" or "RECV"
port (
-- Clock and Reset
ethClk : in sl;
ethRst : in sl;
-- Inbound data to be CRC'd (16-byte EMAC-style stream, TKEEP_COMP_C)
sAxisMaster : in AxiStreamMasterType;
sAxisSlave : out AxiStreamSlaveType;
-- Outbound CRC result (one 4-byte beat per packet)
mAxisMaster : out AxiStreamMasterType;
mAxisSlave : in AxiStreamSlaveType);
end EthMacCrcAxiStream;

architecture rtl of EthMacCrcAxiStream is

-- Input stream geometry (only TDATA_BYTES_C/TKEEP_MODE_C are consumed,
-- via getTKeep); upstream guarantees LSB-contiguous tKeep (TKEEP_COMP_C)
constant CRC_IN_AXIS_CONFIG_C : AxiStreamConfigType := (
TSTRB_EN_C => false,
TDATA_BYTES_C => 16,
TDEST_BITS_C => 0,
TID_BITS_C => 0,
TKEEP_MODE_C => TKEEP_COMP_C,
TUSER_BITS_C => 1,
TUSER_MODE_C => TUSER_NONE_C);

-- Output stream geometry: single 4-byte beat (tKeep = x"F")
constant CRC_OUT_AXIS_CONFIG_C : AxiStreamConfigType := (
TSTRB_EN_C => false,
TDATA_BYTES_C => 4,
TDEST_BITS_C => 0,
TID_BITS_C => 0,
TKEEP_MODE_C => TKEEP_COMP_C,
TUSER_BITS_C => 1,
TUSER_MODE_C => TUSER_NONE_C);

-- Reflected CRC-32 residue of message||correctCRC: for any correct RECV
-- packet the engine output equals this constant, so XOR-ing it yields the
-- zero-on-pass contract of EthMacRxCheckICrc. Verified numerically by the
-- Stage 5 golden-vector generator (zlib.crc32(msg + crc_le) = 0x2144DF1C).
constant RECV_RESIDUE_C : slv(31 downto 0) := x"2144DF1C";

-- Result FIFO sizing: results outstanding are bounded by whole frames
-- resident in the RX delay FIFO (1024 beats / >=4-beat min frame = 256).
-- prog_full backs tReady off with margin for the 2 in-flight captures.
constant RESULT_FIFO_ADDR_WIDTH_C : positive := 8;
constant RESULT_FIFO_FULL_THRES_C : positive := 2**RESULT_FIFO_ADDR_WIDTH_C - 8;

type RegType is record
sofPending : sl; -- next accepted beat starts a packet
seedPulse : sl; -- high during that beat's FOLD cycle
lastD1 : sl; -- tLast accepted 1 cycle ago
lastD2 : sl; -- tLast accepted 2 cycles ago: capture
txMaster : AxiStreamMasterType;
end record RegType;

constant REG_INIT_C : RegType := (
sofPending => '1',
seedPulse => '0',
lastD1 => '0',
lastD2 => '0',
txMaster => AXI_STREAM_MASTER_INIT_C);

signal r : RegType := REG_INIT_C;
signal rin : RegType;

signal crcRst : sl;
signal crcDataValid : sl;
signal crcDataWidth : slv(3 downto 0);
signal crcIn : slv(127 downto 0);
signal crcOut : slv(31 downto 0);

signal fifoWrEn : sl;
signal fifoDin : slv(31 downto 0);
signal fifoProgFull : sl;
signal fifoRdEn : sl;
signal fifoDout : slv(31 downto 0);
signal fifoValid : sl;

begin

assert (CRC_MODE_G = "SEND") or (CRC_MODE_G = "RECV")
report "CRC_MODE_G must be ""SEND"" or ""RECV""" severity failure;

-- First-on-wire byte tData(7:0) lands in the engine's MSByte lane
-- (analogue of blue-crc's swapEndian in rule preProcess); inactive byte
-- lanes are zeroed inside the engine via crcDataWidth (no bitMask needed)
crcIn <= endianSwap(sAxisMaster.tData(127 downto 0));

U_Crc : entity surf.EthCrc32Parallel
generic map (
TPD_G => TPD_G,
RST_POLARITY_G => '1', -- crcRst generated active-high below
USE_DSP_G => false, -- true is untested upstream
CRC_INIT_G => x"FFFFFFFF",
BYTE_WIDTH_G => 16)
port map (
crcClk => ethClk, -- [in]
crcReset => crcRst, -- [in]
crcDataValid => crcDataValid, -- [in]
crcDataWidth => crcDataWidth, -- [in]
crcIn => crcIn, -- [in]
crcOut => crcOut); -- [out]

-- Per-packet result queue (blue-crc's finalCrcResBuf analogue). rd side is
-- the registered mAxis output stage below; wr side is the lastD2 capture.
U_ResultFifo : entity surf.Fifo
generic map (
TPD_G => TPD_G,
RST_POLARITY_G => RST_POLARITY_G,
RST_ASYNC_G => RST_ASYNC_G,
GEN_SYNC_FIFO_G => true,
FWFT_EN_G => true,
MEMORY_TYPE_G => "distributed",
DATA_WIDTH_G => 32,
ADDR_WIDTH_G => RESULT_FIFO_ADDR_WIDTH_C,
FULL_THRES_G => RESULT_FIFO_FULL_THRES_C)
port map (
rst => ethRst,
wr_clk => ethClk,
wr_en => fifoWrEn,
din => fifoDin,
wr_data_count => open,
wr_ack => open,
overflow => open,
prog_full => fifoProgFull,
almost_full => open,
full => open,
not_full => open,
rd_clk => ethClk,
rd_en => fifoRdEn,
dout => fifoDout,
rd_data_count => open,
valid => fifoValid,
underflow => open,
prog_empty => open,
almost_empty => open,
empty => open);

comb : process (crcOut, ethRst, fifoDout, fifoProgFull, fifoValid,
mAxisSlave, r, sAxisMaster) is
variable v : RegType;
variable keepVar : natural range 0 to 16;
variable tReadyVar : sl;
variable acceptVar : sl;
variable resultVar : slv(31 downto 0);
begin
-- Latch the current value
v := r;

-- Input always ready; prog_full is unreachable in normal operation and
-- only guards the capture pipeline against a pathological result burst
if (fifoProgFull = '0') and (ethRst /= RST_POLARITY_G) then
tReadyVar := '1';
else
tReadyVar := '0';
end if;
acceptVar := sAxisMaster.tValid and tReadyVar;

-- Valid byte count of the current input beat (TKEEP_COMP_C)
keepVar := getTKeep(sAxisMaster.tKeep(15 downto 0), CRC_IN_AXIS_CONFIG_C);

-- Engine drives: fold every accepted non-null beat (the engine
-- registers its inputs; the fold happens the NEXT cycle)
crcDataValid <= '0';
if (keepVar /= 0) then
crcDataWidth <= toSlv(keepVar-1, 4);
else
-- don't care: crcDataValid is gated off for null beats
crcDataWidth <= (others => '0');
end if;
if (acceptVar = '1') and (keepVar /= 0) then
crcDataValid <= '1';
end if;

-- Packet tracking:
-- seedPulse -> crcReset during the fold cycle of a first beat (for a
-- null first beat there is no fold and the engine's idle path loads
-- CRC_INIT instead — same outcome);
-- lastD1/lastD2 -> capture r.crc two cycles after the tLast beat.
v.seedPulse := acceptVar and r.sofPending;
v.lastD1 := acceptVar and sAxisMaster.tLast;
v.lastD2 := r.lastD1;
if (acceptVar = '1') then
v.sofPending := sAxisMaster.tLast;
end if;

-- Result capture into the FIFO (crcOut = final CRC of the packet whose
-- tLast was accepted 2 cycles ago; a back-to-back next fold overwrites
-- r.crc only on this cycle's closing edge, after din is sampled)
if (CRC_MODE_G = "RECV") then
resultVar := endianSwap(crcOut) xor RECV_RESIDUE_C;
else
resultVar := endianSwap(crcOut);
end if;
fifoWrEn <= r.lastD2;
fifoDin <= resultVar;

-- Registered output stage: one 4-byte beat per queued result
fifoRdEn <= '0';
if (mAxisSlave.tReady = '1') then
v.txMaster.tValid := '0';
end if;
if (v.txMaster.tValid = '0') and (fifoValid = '1') then
fifoRdEn <= '1';
v.txMaster := axiStreamMasterInit(CRC_OUT_AXIS_CONFIG_C);
v.txMaster.tValid := '1';
v.txMaster.tLast := '1';
v.txMaster.tData(31 downto 0) := fifoDout;
end if;

-- Combinational engine re-seed: fold-cycle pulse per packet, plus the
-- whole duration of ethRst (recovers mid-packet aborts; the engine has
-- no reset port of its own — while idle it synchronously loads INIT)
if (r.seedPulse = '1') or (ethRst = RST_POLARITY_G) then
crcRst <= '1';
else
crcRst <= '0';
end if;

-- Synchronous reset
if (RST_ASYNC_G = false) and (ethRst = RST_POLARITY_G) then
v := REG_INIT_C;
end if;

-- Register the variable for next clock cycle
rin <= v;

-- Outputs
sAxisSlave.tReady <= tReadyVar;
mAxisMaster <= r.txMaster;

end process comb;

seq : process (ethClk, ethRst) is
begin
if (RST_ASYNC_G) and (ethRst = RST_POLARITY_G) then
r <= REG_INIT_C after TPD_G;
elsif rising_edge(ethClk) then
r <= rin after TPD_G;
end if;
end process seq;

end rtl;
19 changes: 0 additions & 19 deletions ethernet/RoCEv2/README.md

This file was deleted.

Loading