diff --git a/protocols/coaxpress/core/rtl/CoaXPressCore.vhd b/protocols/coaxpress/core/rtl/CoaXPressCore.vhd index 204e2405c1..6007553a07 100755 --- a/protocols/coaxpress/core/rtl/CoaXPressCore.vhd +++ b/protocols/coaxpress/core/rtl/CoaXPressCore.vhd @@ -93,6 +93,7 @@ architecture mapping of CoaXPressCore is signal eventAck : sl; signal eventTag : slv(7 downto 0); + signal eventMaster : AxiStreamMasterType; signal trigAck : sl; signal txLsRateInt : sl; @@ -177,6 +178,8 @@ begin cfgClk => cfgClk, cfgRst => cfgRst, cfgRxMaster => cfgRxMaster, + eventMaster => eventMaster, + eventSlave => AXI_STREAM_SLAVE_FORCE_C, -- Event ACK Interface (cfgClk domain) eventAck => eventAck, eventTag => eventTag, diff --git a/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridge.vhd b/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridge.vhd index 718ff9e4e3..a8c99ea9a7 100644 --- a/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridge.vhd +++ b/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridge.vhd @@ -45,10 +45,11 @@ entity CoaXPressOverFiberBridge is txLsLaneEn : in slv(3 downto 0); txLsRate : in sl; -- CXP RX interface (rxClk312 domain) - rxClk312 : in sl; - rxRst312 : in sl; - rxData : out slv(31 downto 0); - rxDataK : out slv(3 downto 0)); + rxClk312 : in sl; + rxRst312 : in sl; + rxData : out slv(31 downto 0); + rxDataK : out slv(3 downto 0); + rxStatus : out CxpofRxStatusType); end entity CoaXPressOverFiberBridge; architecture mapping of CoaXPressOverFiberBridge is @@ -92,7 +93,9 @@ begin xgmiiRxc => rxc, -- CXP interface rxData => rxData, - rxDataK => rxDataK); + rxDataK => rxDataK, + -- Status Interface + rxStatus => rxStatus); GEN_TX : if (LANE0_G = true) generate diff --git a/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeAxiL.vhd b/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeAxiL.vhd new file mode 100644 index 0000000000..5d5f9cf4cf --- /dev/null +++ b/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeAxiL.vhd @@ -0,0 +1,220 @@ +------------------------------------------------------------------------------- +-- Title : CXP Over Fiber Bridge AXI-Lite Status +------------------------------------------------------------------------------- +-- Company : SLAC National Accelerator Laboratory +------------------------------------------------------------------------------- +-- Description: AXI-Lite status and counters for CoaXPress-over-Fiber bridge RX +------------------------------------------------------------------------------- +-- 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; +use ieee.std_logic_unsigned.all; +use ieee.std_logic_arith.all; + +library surf; +use surf.StdRtlPkg.all; +use surf.AxiLitePkg.all; +use surf.CoaXPressPkg.all; + +entity CoaXPressOverFiberBridgeAxiL is + generic ( + TPD_G : time := 1 ns; + CNT_WIDTH_G : positive range 1 to 32 := 16); + port ( + -- Bridge RX status clock domain + rxClk : in sl; + rxRst : in sl; + rxStatus : in CxpofRxStatusType; + -- AXI-Lite Register Interface + axilClk : in sl; + axilRst : in sl; + axilReadMaster : in AxiLiteReadMasterType; + axilReadSlave : out AxiLiteReadSlaveType; + axilWriteMaster : in AxiLiteWriteMasterType; + axilWriteSlave : out AxiLiteWriteSlaveType); +end entity CoaXPressOverFiberBridgeAxiL; + +architecture rtl of CoaXPressOverFiberBridgeAxiL is + + constant STATUS_WIDTH_C : positive := 6; + + constant RX_ERROR_INDEX_C : natural := 0; + constant RX_ABORT_INDEX_C : natural := 1; + constant SEQ_VALID_INDEX_C : natural := 2; + constant SEQ_ERROR_INDEX_C : natural := 3; + constant HKP_VALID_INDEX_C : natural := 4; + constant HKP_ERROR_INDEX_C : natural := 5; + + type RegType is record + cntRst : sl; + stickyStatus : slv(STATUS_WIDTH_C-1 downto 0); + statusCnt : Slv32Array(STATUS_WIDTH_C-1 downto 0); + rxErrorCode : slv(3 downto 0); + seqData : slv(23 downto 0); + seqExpected : slv(23 downto 0); + seqErrorExpected : slv(23 downto 0); + hkpData : slv(31 downto 0); + hkpWordCount : slv(7 downto 0); + hkpKCodeMask : slv(3 downto 0); + hkpKCodeValid : sl; + hkpType : slv(3 downto 0); + axilWriteSlave : AxiLiteWriteSlaveType; + axilReadSlave : AxiLiteReadSlaveType; + end record RegType; + + constant REG_INIT_C : RegType := ( + cntRst => '1', + stickyStatus => (others => '0'), + statusCnt => (others => (others => '0')), + rxErrorCode => CXPOF_RX_ERR_NONE_C, + seqData => (others => '0'), + seqExpected => (others => '0'), + seqErrorExpected => (others => '0'), + hkpData => (others => '0'), + hkpWordCount => (others => '0'), + hkpKCodeMask => (others => '0'), + hkpKCodeValid => '0', + hkpType => CXPOF_HKP_TYPE_NONE_C, + axilWriteSlave => AXI_LITE_WRITE_SLAVE_INIT_C, + axilReadSlave => AXI_LITE_READ_SLAVE_INIT_C); + + signal r : RegType := REG_INIT_C; + signal rin : RegType; + + signal statusIn : slv(STATUS_WIDTH_C-1 downto 0); + + signal rxAxilReadMaster : AxiLiteReadMasterType; + signal rxAxilReadSlave : AxiLiteReadSlaveType; + signal rxAxilWriteMaster : AxiLiteWriteMasterType; + signal rxAxilWriteSlave : AxiLiteWriteSlaveType; + +begin + + statusIn(RX_ERROR_INDEX_C) <= rxStatus.rxError; + statusIn(RX_ABORT_INDEX_C) <= rxStatus.rxAbort; + statusIn(SEQ_VALID_INDEX_C) <= rxStatus.seqValid; + statusIn(SEQ_ERROR_INDEX_C) <= rxStatus.seqError; + statusIn(HKP_VALID_INDEX_C) <= rxStatus.hkpValid; + statusIn(HKP_ERROR_INDEX_C) <= rxStatus.hkpError; + + U_AxiLiteAsync : entity surf.AxiLiteAsync + generic map ( + TPD_G => TPD_G) + port map ( + -- Slave Interface + sAxiClk => axilClk, + sAxiClkRst => axilRst, + sAxiReadMaster => axilReadMaster, + sAxiReadSlave => axilReadSlave, + sAxiWriteMaster => axilWriteMaster, + sAxiWriteSlave => axilWriteSlave, + -- Master Interface + mAxiClk => rxClk, + mAxiClkRst => rxRst, + mAxiReadMaster => rxAxilReadMaster, + mAxiReadSlave => rxAxilReadSlave, + mAxiWriteMaster => rxAxilWriteMaster, + mAxiWriteSlave => rxAxilWriteSlave); + + comb : process (r, rxAxilReadMaster, rxAxilWriteMaster, rxRst, rxStatus, + statusIn) is + variable v : RegType; + variable axilEp : AxiLiteEndpointType; + variable hkpStatus : slv(31 downto 0); + begin + -- Latch the current value + v := r; + + -- Reset strobes + v.cntRst := '0'; + + -- Accumulate sticky status bits and event counters in the RX domain. + v.stickyStatus := r.stickyStatus or statusIn; + for i in 0 to STATUS_WIDTH_C-1 loop + if (statusIn(i) = '1') then + v.statusCnt(i)(CNT_WIDTH_G-1 downto 0) := r.statusCnt(i)(CNT_WIDTH_G-1 downto 0) + 1; + end if; + end loop; + + if (r.cntRst = '1') then + v.stickyStatus := (others => '0'); + v.statusCnt := (others => (others => '0')); + end if; + + if (rxStatus.rxError = '1') then + v.rxErrorCode := rxStatus.rxErrorCode; + end if; + + if (rxStatus.seqValid = '1') then + v.seqData := rxStatus.seqData; + v.seqExpected := rxStatus.seqExpected; + end if; + + if (rxStatus.seqError = '1') then + v.seqErrorExpected := rxStatus.seqErrorExpected; + end if; + + if (rxStatus.hkpValid = '1') or (rxStatus.hkpError = '1') then + v.hkpData := rxStatus.hkpData; + v.hkpWordCount := rxStatus.hkpWordCount; + v.hkpKCodeMask := rxStatus.hkpKCodeMask; + v.hkpKCodeValid := rxStatus.hkpKCodeValid; + v.hkpType := rxStatus.hkpType; + end if; + + hkpStatus := (others => '0'); + hkpStatus(7 downto 0) := r.hkpWordCount; + hkpStatus(11 downto 8) := r.hkpKCodeMask; + hkpStatus(12) := r.hkpKCodeValid; + hkpStatus(19 downto 16) := r.hkpType; + + ------------------------ + -- AXI-Lite Transactions + ------------------------ + axiSlaveWaitTxn(axilEp, rxAxilWriteMaster, rxAxilReadMaster, v.axilWriteSlave, v.axilReadSlave); + + axiSlaveRegisterR(axilEp, x"000", 0, r.stickyStatus); + axiSlaveRegisterR(axilEp, x"004", 0, r.rxErrorCode); + axiSlaveRegisterR(axilEp, x"008", 0, r.seqData); + axiSlaveRegisterR(axilEp, x"00C", 0, r.seqExpected); + axiSlaveRegisterR(axilEp, x"010", 0, r.seqErrorExpected); + axiSlaveRegisterR(axilEp, x"014", 0, r.hkpData); + axiSlaveRegisterR(axilEp, x"018", 0, hkpStatus); + + for i in 0 to STATUS_WIDTH_C-1 loop + axiSlaveRegisterR(axilEp, x"020"+toSlv(i*4, 12), 0, r.statusCnt(i)); + end loop; + + axiSlaveRegister(axilEp, x"03C", 0, v.cntRst); + + axiSlaveDefault(axilEp, v.axilWriteSlave, v.axilReadSlave, AXI_RESP_DECERR_C); + + -- Outputs + rxAxilReadSlave <= r.axilReadSlave; + rxAxilWriteSlave <= r.axilWriteSlave; + + -- Reset + if (rxRst = '1') then + v := REG_INIT_C; + end if; + + rin <= v; + + end process comb; + + seq : process (rxClk) is + begin + if (rising_edge(rxClk)) then + r <= rin after TPD_G; + end if; + end process seq; + +end rtl; diff --git a/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeRx.vhd b/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeRx.vhd index e647e52e1a..21e0046d15 100644 --- a/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeRx.vhd +++ b/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeRx.vhd @@ -35,7 +35,9 @@ entity CoaXPressOverFiberBridgeRx is xgmiiRxc : in slv(3 downto 0); -- Rx PHY Interface rxData : out slv(31 downto 0); - rxDataK : out slv(3 downto 0)); + rxDataK : out slv(3 downto 0); + -- Status Interface + rxStatus : out CxpofRxStatusType); end entity CoaXPressOverFiberBridgeRx; architecture rtl of CoaXPressOverFiberBridgeRx is @@ -46,17 +48,19 @@ architecture rtl of CoaXPressOverFiberBridgeRx is PAYLOAD_S); type RegType is record - errDet : sl; + status : CxpofRxStatusType; + seqLocked : sl; rxData : Slv32Array(1 downto 0); rxDataK : Slv4Array(1 downto 0); state : StateType; end record RegType; constant REG_INIT_C : RegType := ( - errDet => '0', - rxData => (others => CXP_IDLE_C), - rxDataK => (others => CXP_IDLE_K_C), - state => IDLE_S); + status => CXPOF_RX_STATUS_INIT_C, + seqLocked => '0', + rxData => (others => CXP_IDLE_C), + rxDataK => (others => CXP_IDLE_K_C), + state => IDLE_S); signal r : RegType := REG_INIT_C; signal rin : RegType; @@ -73,7 +77,15 @@ begin v := r; -- Reset strobe - v.errDet := '0'; + v.status.rxError := '0'; + v.status.rxAbort := '0'; + v.status.rxErrorCode := CXPOF_RX_ERR_NONE_C; + v.status.seqValid := '0'; + v.status.seqError := '0'; + v.status.hkpValid := '0'; + v.status.hkpEop := '0'; + v.status.hkpSof := '0'; + v.status.hkpError := '0'; -- Update shift register v.rxDataK(1) := CXP_IDLE_K_C; @@ -86,10 +98,11 @@ begin ---------------------------------------------------------------------- when IDLE_S => -- Check for SOP - if (xgmiiRxc = "0001") and (xgmiiRxd(15 downto 9) = "1000000") and (xgmiiRxd(7 downto 0) = CXPOF_START_C) then + if (xgmiiRxc = CXPOF_XGMII_LANE0_CTRL_C) and (xgmiiRxd(15 downto 9) = CXPOF_SOP_CTRL_HS_PREFIX_C) and (xgmiiRxd(7 downto 0) = CXPOF_START_C) then -- Check for HKP condition - if (xgmiiRxd(8) = '1') then + if (xgmiiRxd(8 + CXPOF_SOP_CTRL_HKP_BIT_C) = '1') then + v.status.hkpWordCount := (others => '0'); -- Next State v.state := HKP_S; else @@ -97,25 +110,26 @@ begin -- Check if data is being overwritten if (v.rxDataK(0) /= CXP_IDLE_K_C) or (v.rxData(0) /= CXP_IDLE_C) then -- Set the flag - v.errDet := '1'; + v.status.rxError := '1'; + v.status.rxErrorCode := CXPOF_RX_ERR_OVERWRITE_C; end if; -- Check for SOP if (xgmiiRxd(23 downto 16) = CXP_SOP_C(7 downto 0)) then -- Send SOP - v.rxDataK(0) := x"F"; + v.rxDataK(0) := CXP_ALL_CTRL_K_C; v.rxData(0) := CXP_SOP_C; -- Send type - v.rxDataK(1) := x"0"; + v.rxDataK(1) := CXP_ALL_DATA_K_C; v.rxData(1) := xgmiiRxd(31 downto 24) & xgmiiRxd(31 downto 24) & xgmiiRxd(31 downto 24) & xgmiiRxd(31 downto 24); -- Check for I/O ACK elsif (xgmiiRxd(23 downto 16) = CXP_IO_ACK_C(7 downto 0)) then -- Send I/O ACK inductor - v.rxDataK(1) := x"F"; + v.rxDataK(1) := CXP_ALL_CTRL_K_C; v.rxData(1) := CXP_IO_ACK_C; end if; @@ -125,17 +139,62 @@ begin end if; - elsif (xgmiiRxc /= x"F") or (xgmiiRxd /= x"07_07_07_07") then + -- Check for lane-0 sequence ordered set + elsif (xgmiiRxc = CXPOF_XGMII_LANE0_CTRL_C) and (xgmiiRxd(7 downto 0) = CXPOF_SEQ_C) then + + -- Publish the sequence data without reconstructing a CXP word. + v.status.seqValid := '1'; + v.status.seqData := xgmiiRxd(31 downto 8); + if (r.seqLocked = '1') and (xgmiiRxd(31 downto 8) /= r.status.seqExpected) then + v.status.rxError := '1'; + v.status.seqError := '1'; + v.status.seqErrorExpected := r.status.seqExpected; + v.status.rxErrorCode := CXPOF_RX_ERR_SEQ_MISMATCH_C; + end if; + v.seqLocked := '1'; + v.status.seqExpected := xgmiiRxd(31 downto 8) + 1; + + -- Check for lane-0 error ordered set while idle + elsif (xgmiiRxc = CXPOF_XGMII_LANE0_CTRL_C) and (xgmiiRxd(7 downto 0) = CXPOF_ERROR_C) then + + -- Publish an error pulse even when no packet payload is active. + v.status.rxError := '1'; + v.status.rxAbort := '1'; + v.status.rxErrorCode := CXPOF_RX_ERR_IDLE_ERROR_C; + + elsif (xgmiiRxc /= CXPOF_XGMII_ALL_CTRL_C) or (xgmiiRxd /= CXPOF_IDLE_WORD_C) then -- Set the flag - v.errDet := '1'; + v.status.rxError := '1'; + v.status.rxErrorCode := CXPOF_RX_ERR_BAD_CONTROL_C; end if; ---------------------------------------------------------------------- when HKP_S => -- Send HKP - v.rxDataK(1) := x"F"; + v.rxDataK(1) := CXP_ALL_CTRL_K_C; v.rxData(1) := xgmiiRxd; + v.status.hkpValid := '1'; + v.status.hkpData := xgmiiRxd; + v.status.hkpSof := '1'; + v.status.hkpWordCount := r.status.hkpWordCount + 1; + v.status.hkpKCodeMask := cxpKCodeMask(xgmiiRxd); + v.status.hkpType := cxpHkpType(xgmiiRxd); + if (v.status.hkpKCodeMask = CXP_ALL_CTRL_K_C) then + v.status.hkpKCodeValid := '1'; + else + v.status.hkpKCodeValid := '0'; + end if; + if (xgmiiRxc /= CXPOF_XGMII_ALL_DATA_C) then + v.status.rxError := '1'; + v.status.hkpError := '1'; + v.status.rxErrorCode := CXPOF_RX_ERR_HKP_MALFORMED_C; + elsif (v.status.hkpKCodeValid = '0') then + v.status.rxError := '1'; + v.status.hkpError := '1'; + v.status.rxErrorCode := CXPOF_RX_ERR_HKP_BAD_K_CODE_C; + end if; -- Check for EOP if (xgmiiRxd = CXP_EOP_C) then + v.status.hkpEop := '1'; -- Next State v.state := IDLE_S; else @@ -145,18 +204,27 @@ begin ---------------------------------------------------------------------- when PAYLOAD_S => -- Check for data word - if (xgmiiRxc = "0000") then + if (xgmiiRxc = CXPOF_XGMII_ALL_DATA_C) then -- Send Type - v.rxDataK(1) := x"0"; + v.rxDataK(1) := CXP_ALL_DATA_K_C; v.rxData(1) := xgmiiRxd; + -- Check for error ordered set + elsif (xgmiiRxc = CXPOF_XGMII_LANE0_CTRL_C) and (xgmiiRxd(7 downto 0) = CXPOF_ERROR_C) then + + -- Abort the active packet without synthesizing a CXP EOP. + v.status.rxError := '1'; + v.status.rxAbort := '1'; + v.status.rxErrorCode := CXPOF_RX_ERR_PAYLOAD_ABORT_C; + v.state := IDLE_S; + -- Check for EOP - elsif (xgmiiRxc = "1100") and (xgmiiRxd(31 downto 8) = x"07_FD_00") then + elsif (xgmiiRxc = CXPOF_XGMII_LANE2_3_CTRL_C) and (xgmiiRxd(31 downto 8) = CXPOF_TERM_SUFFIX_C) then -- Check for non-zero value - if (xgmiiRxd(7 downto 0) /= 0) then + if (xgmiiRxd(7 downto 0) /= CXPOF_RESERVED_BYTE_C) then -- Send EOP - v.rxDataK(1) := x"F"; + v.rxDataK(1) := CXP_ALL_CTRL_K_C; v.rxData(1) := xgmiiRxd(7 downto 0) & xgmiiRxd(7 downto 0) & xgmiiRxd(7 downto 0) & xgmiiRxd(7 downto 0); else @@ -171,7 +239,8 @@ begin -- Undefined state else -- Set the flag - v.errDet := '1'; + v.status.rxError := '1'; + v.status.rxErrorCode := CXPOF_RX_ERR_BAD_CONTROL_C; -- Next State v.state := IDLE_S; end if; @@ -181,6 +250,7 @@ begin -- Outputs rxDataK <= r.rxDataK(0); rxData <= r.rxData(0); + rxStatus <= r.status; -- Reset if (rst = '1') then diff --git a/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeTx.vhd b/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeTx.vhd index 91296002de..4e1fe2d136 100644 --- a/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeTx.vhd +++ b/protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeTx.vhd @@ -68,7 +68,7 @@ architecture rtl of CoaXPressOverFiberBridgeTx is txLsData => (others => '0'), txLsDataK => '0', xgmiiTxd => CXPOF_IDLE_WORD_C, - xgmiiTxc => x"F", + xgmiiTxc => CXPOF_XGMII_ALL_CTRL_C, state => IDLE_S); signal r : RegType := REG_INIT_C; @@ -97,7 +97,7 @@ begin ---------------------------------------------------------------------- when IDLE_S => -- Send the start word - v.xgmiiTxc := x"F"; + v.xgmiiTxc := CXPOF_XGMII_ALL_CTRL_C; v.xgmiiTxd := CXPOF_IDLE_WORD_C; -- Check for new low speed byte @@ -111,7 +111,7 @@ begin ---------------------------------------------------------------------- when LS_SOP_S => -- Set the char marker - v.xgmiiTxc := "0001"; + v.xgmiiTxc := CXPOF_XGMII_LANE0_CTRL_C; -- Lane[0] = Start[7:0] v.xgmiiTxd(0+7 downto 0+0) := CXPOF_START_C; @@ -120,28 +120,28 @@ begin v.update := '0'; -- Lane[1] = SopCtrl[7] - Packet type: "0" => Low-speed packet - v.xgmiiTxd(8+7) := '0'; + v.xgmiiTxd(8+CXPOF_SOP_CTRL_PACKET_TYPE_BIT_C) := CXPOF_SOP_CTRL_LOW_SPEED_C; -- Lane[1] = SopCtrl[6:4] - Reserved v.xgmiiTxd(8+6 downto 8+4) := "000"; -- Lane[1] = SopCtrl[3] - Update flag - v.xgmiiTxd(8+3) := r.update; + v.xgmiiTxd(8+CXPOF_SOP_CTRL_UPDATE_BIT_C) := r.update; -- Lane[1] = SopCtrl[2] - Reserved v.xgmiiTxd(8+2) := '0'; -- Lane[1] = SopCtrl[1] - Low-speed rate: When '0'=> 20.83 Mbps, When '1'=> 41.6 Mbps - v.xgmiiTxd(8+1) := r.txLsRate; + v.xgmiiTxd(8+CXPOF_SOP_CTRL_LS_RATE_BIT_C) := r.txLsRate; -- Lane[1] = SopCtrl[0] - High-speed upconnection state - v.xgmiiTxd(8+0) := '0'; + v.xgmiiTxd(8+CXPOF_SOP_CTRL_HKP_BIT_C) := '0'; -- Lane[2] = SopData0[7:0] - reserved - v.xgmiiTxd(16+7 downto 16+0) := x"00"; + v.xgmiiTxd(16+7 downto 16+0) := CXPOF_RESERVED_BYTE_C; -- Lane[3] = SopData1[7:0] - reserved - v.xgmiiTxd(24+7 downto 24+0) := x"00"; + v.xgmiiTxd(24+7 downto 24+0) := CXPOF_RESERVED_BYTE_C; -- Next State v.state := LS_PAYLOAD_S; @@ -151,7 +151,7 @@ begin v.cnt := r.cnt + 1; -- Reset the data and char bus - v.xgmiiTxc := (others => '0'); + v.xgmiiTxc := CXPOF_XGMII_ALL_DATA_C; v.xgmiiTxd := (others => '0'); -- Check for LS Stream @@ -165,9 +165,9 @@ begin -- LS CTRL if (r.txLsDataK = '0') then - v.xgmiiTxd(16*i+7 downto 16*i) := x"01"; -- data + v.xgmiiTxd(16*i+7 downto 16*i) := CXPOF_LS_CTRL_DATA_C; else - v.xgmiiTxd(16*i+7 downto 16*i) := x"02"; -- k-code + v.xgmiiTxd(16*i+7 downto 16*i) := CXPOF_LS_CTRL_K_CODE_C; end if; -- LS Char @@ -178,9 +178,9 @@ begin -- LS CTRL if (CXP_IDLE_K_C(r.idle) = '0') then - v.xgmiiTxd(16*i+7 downto 16*i) := x"01"; -- data + v.xgmiiTxd(16*i+7 downto 16*i) := CXPOF_LS_CTRL_DATA_C; else - v.xgmiiTxd(16*i+7 downto 16*i) := x"02"; -- k-code + v.xgmiiTxd(16*i+7 downto 16*i) := CXPOF_LS_CTRL_K_CODE_C; end if; -- LS Char @@ -195,13 +195,13 @@ begin v.cnt := 0; -- Set the char marker - v.xgmiiTxc := "1100"; + v.xgmiiTxc := CXPOF_XGMII_LANE2_3_CTRL_C; -- Lane[0] = Reserved - v.xgmiiTxd(7 downto 0) := x"00"; + v.xgmiiTxd(7 downto 0) := CXPOF_RESERVED_BYTE_C; -- Lane[1] = Reserved - v.xgmiiTxd(15 downto 8) := x"00"; + v.xgmiiTxd(15 downto 8) := CXPOF_RESERVED_BYTE_C; -- Lane[2] = Terminate v.xgmiiTxd(23 downto 16) := CXPOF_TERM_C; diff --git a/protocols/coaxpress/core/rtl/CoaXPressPkg.vhd b/protocols/coaxpress/core/rtl/CoaXPressPkg.vhd index 901def36d8..1c231244b2 100644 --- a/protocols/coaxpress/core/rtl/CoaXPressPkg.vhd +++ b/protocols/coaxpress/core/rtl/CoaXPressPkg.vhd @@ -34,6 +34,11 @@ package CoaXPressPkg is constant CXP_TRIG_C : slv(31 downto 0) := K_28_2_C & K_28_2_C & K_28_2_C & K_28_2_C; -- 0x5C5C5C5C constant CXP_IO_ACK_C : slv(31 downto 0) := K_28_6_C & K_28_6_C & K_28_6_C & K_28_6_C; -- 0xDCDCDCDC constant CXP_MARKER_C : slv(31 downto 0) := K_28_3_C & K_28_3_C & K_28_3_C & K_28_3_C; -- 0x7C7C7C7C + constant CXP_ALL_DATA_K_C : slv(3 downto 0) := x"0"; + constant CXP_ALL_CTRL_K_C : slv(3 downto 0) := x"F"; + + constant CXP_RX_STREAM_TUSER_BITS_C : positive := 8; + constant CXP_RX_STREAM_TRAILER_TUSER_C : natural := 4; constant CXP_TX_IDLE_C : Slv8Array(3 downto 0) := ( 0 => CXP_IDLE_C(7 downto 0), @@ -57,8 +62,131 @@ package CoaXPressPkg is constant CXPOF_IDLE_WORD_C : slv(31 downto 0) := CXPOF_IDLE_C & CXPOF_IDLE_C & CXPOF_IDLE_C & CXPOF_IDLE_C; + constant CXPOF_XGMII_ALL_DATA_C : slv(3 downto 0) := x"0"; + constant CXPOF_XGMII_ALL_CTRL_C : slv(3 downto 0) := x"F"; + constant CXPOF_XGMII_LANE0_CTRL_C : slv(3 downto 0) := "0001"; + constant CXPOF_XGMII_LANE2_3_CTRL_C : slv(3 downto 0) := "1100"; + + constant CXPOF_RESERVED_BYTE_C : slv(7 downto 0) := x"00"; + + constant CXPOF_SOP_CTRL_PACKET_TYPE_BIT_C : natural := 7; + constant CXPOF_SOP_CTRL_UPDATE_BIT_C : natural := 3; + constant CXPOF_SOP_CTRL_LS_RATE_BIT_C : natural := 1; + constant CXPOF_SOP_CTRL_HKP_BIT_C : natural := 0; + + constant CXPOF_SOP_CTRL_LOW_SPEED_C : sl := '0'; + constant CXPOF_SOP_CTRL_HIGH_SPEED_C : sl := '1'; + + constant CXPOF_SOP_CTRL_HS_PREFIX_C : slv(6 downto 0) := CXPOF_SOP_CTRL_HIGH_SPEED_C & "000000"; + + constant CXPOF_LS_CTRL_DATA_C : slv(7 downto 0) := x"01"; + constant CXPOF_LS_CTRL_K_CODE_C : slv(7 downto 0) := x"02"; + + constant CXPOF_TERM_SUFFIX_C : slv(23 downto 0) := CXPOF_IDLE_C & CXPOF_TERM_C & CXPOF_RESERVED_BYTE_C; + + constant CXPOF_RX_ERR_NONE_C : slv(3 downto 0) := x"0"; + constant CXPOF_RX_ERR_SEQ_MISMATCH_C : slv(3 downto 0) := x"1"; + constant CXPOF_RX_ERR_IDLE_ERROR_C : slv(3 downto 0) := x"2"; + constant CXPOF_RX_ERR_PAYLOAD_ABORT_C : slv(3 downto 0) := x"3"; + constant CXPOF_RX_ERR_BAD_CONTROL_C : slv(3 downto 0) := x"4"; + constant CXPOF_RX_ERR_OVERWRITE_C : slv(3 downto 0) := x"5"; + constant CXPOF_RX_ERR_HKP_MALFORMED_C : slv(3 downto 0) := x"6"; + constant CXPOF_RX_ERR_HKP_BAD_K_CODE_C : slv(3 downto 0) := x"7"; + + constant CXPOF_HKP_TYPE_NONE_C : slv(3 downto 0) := x"0"; + constant CXPOF_HKP_TYPE_K_CODE_C : slv(3 downto 0) := x"1"; + constant CXPOF_HKP_TYPE_SOP_C : slv(3 downto 0) := x"2"; + constant CXPOF_HKP_TYPE_EOP_C : slv(3 downto 0) := x"3"; + constant CXPOF_HKP_TYPE_TRIG_C : slv(3 downto 0) := x"4"; + constant CXPOF_HKP_TYPE_IO_ACK_C : slv(3 downto 0) := x"5"; + constant CXPOF_HKP_TYPE_MARKER_C : slv(3 downto 0) := x"6"; + constant CXPOF_HKP_TYPE_INVALID_C : slv(3 downto 0) := x"F"; + + type CxpofRxStatusType is record + rxError : sl; + rxAbort : sl; + rxErrorCode : slv(3 downto 0); + seqValid : sl; + seqData : slv(23 downto 0); + seqError : sl; + seqExpected : slv(23 downto 0); + seqErrorExpected : slv(23 downto 0); + hkpValid : sl; + hkpData : slv(31 downto 0); + hkpEop : sl; + hkpSof : sl; + hkpError : sl; + hkpWordCount : slv(7 downto 0); + hkpKCodeMask : slv(3 downto 0); + hkpKCodeValid : sl; + hkpType : slv(3 downto 0); + end record CxpofRxStatusType; + + constant CXPOF_RX_STATUS_INIT_C : CxpofRxStatusType := ( + rxError => '0', + rxAbort => '0', + rxErrorCode => CXPOF_RX_ERR_NONE_C, + seqValid => '0', + seqData => (others => '0'), + seqError => '0', + seqExpected => (others => '0'), + seqErrorExpected => (others => '0'), + hkpValid => '0', + hkpData => (others => '0'), + hkpEop => '0', + hkpSof => '0', + hkpError => '0', + hkpWordCount => (others => '0'), + hkpKCodeMask => (others => '0'), + hkpKCodeValid => '0', + hkpType => CXPOF_HKP_TYPE_NONE_C); + + function cxpIsKCode (data : slv(7 downto 0)) return sl; + function cxpKCodeMask (data : slv(31 downto 0)) return slv; + function cxpHkpType (data : slv(31 downto 0)) return slv; + end package CoaXPressPkg; package body CoaXPressPkg is + function cxpIsKCode (data : slv(7 downto 0)) return sl is + begin + case data is + when K_28_0_C | K_28_1_C | K_28_2_C | K_28_3_C | + K_28_4_C | K_28_5_C | K_28_6_C | K_28_7_C | + K_23_7_C | K_27_7_C | K_29_7_C | K_30_7_C => + return '1'; + when others => + return '0'; + end case; + end function cxpIsKCode; + + function cxpKCodeMask (data : slv(31 downto 0)) return slv is + variable ret : slv(3 downto 0); + begin + for i in 0 to 3 loop + ret(i) := cxpIsKCode(data((8*i)+7 downto (8*i))); + end loop; + return ret; + end function cxpKCodeMask; + + function cxpHkpType (data : slv(31 downto 0)) return slv is + begin + if (cxpKCodeMask(data) /= CXP_ALL_CTRL_K_C) then + return CXPOF_HKP_TYPE_INVALID_C; + elsif (data = CXP_SOP_C) then + return CXPOF_HKP_TYPE_SOP_C; + elsif (data = CXP_EOP_C) then + return CXPOF_HKP_TYPE_EOP_C; + elsif (data = CXP_TRIG_C) then + return CXPOF_HKP_TYPE_TRIG_C; + elsif (data = CXP_IO_ACK_C) then + return CXPOF_HKP_TYPE_IO_ACK_C; + elsif (data = CXP_MARKER_C) then + return CXPOF_HKP_TYPE_MARKER_C; + else + return CXPOF_HKP_TYPE_K_CODE_C; + end if; + end function cxpHkpType; + end package body CoaXPressPkg; diff --git a/protocols/coaxpress/core/rtl/CoaXPressRx.vhd b/protocols/coaxpress/core/rtl/CoaXPressRx.vhd index 62371849bd..b9b53c135f 100644 --- a/protocols/coaxpress/core/rtl/CoaXPressRx.vhd +++ b/protocols/coaxpress/core/rtl/CoaXPressRx.vhd @@ -43,6 +43,8 @@ entity CoaXPressRx is cfgClk : in sl; cfgRst : in sl; cfgRxMaster : out AxiStreamMasterType; + eventMaster : out AxiStreamMasterType; + eventSlave : in AxiStreamSlaveType := AXI_STREAM_SLAVE_FORCE_C; -- Event ACK Interface (cfgClk domain) eventAck : out sl; eventTag : out slv(7 downto 0); @@ -70,7 +72,7 @@ architecture mapping of CoaXPressRx is TDEST_BITS_C => 0, TID_BITS_C => 0, TKEEP_MODE_C => TKEEP_NORMAL_C, - TUSER_BITS_C => 1, + TUSER_BITS_C => CXP_RX_STREAM_TUSER_BITS_C, TUSER_MODE_C => TUSER_NORMAL_C); constant WIDE_AXIS_CONFIG_C : AxiStreamConfigType := ( @@ -82,9 +84,21 @@ architecture mapping of CoaXPressRx is TUSER_BITS_C => NARROW_AXIS_CONFIG_C.TUSER_BITS_C, TUSER_MODE_C => NARROW_AXIS_CONFIG_C.TUSER_MODE_C); + constant EVENT_AXIS_CONFIG_C : AxiStreamConfigType := ( + TSTRB_EN_C => false, + TDATA_BYTES_C => 4, + TDEST_BITS_C => 8, + TID_BITS_C => 0, + TKEEP_MODE_C => TKEEP_NORMAL_C, + -- TUSER_NORMAL_C is per-byte, so 4 data bytes x 8 bits preserves the 32-bit event ID. + TUSER_BITS_C => 8, + TUSER_MODE_C => TUSER_NORMAL_C); + signal ioAck : slv(NUM_LANES_G-1 downto 0); signal eventAckVec : slv(NUM_LANES_G-1 downto 0); signal eventTagVec : Slv8Array(NUM_LANES_G-1 downto 0); + signal eventMasters : AxiStreamMasterArray(NUM_LANES_G-1 downto 0); + signal rxLaneError : slv(NUM_LANES_G-1 downto 0); signal cfgMasters : AxiStreamMasterArray(NUM_LANES_G-1 downto 0); signal dataMasters : AxiStreamMasterArray(NUM_LANES_G-1 downto 0); @@ -100,18 +114,43 @@ architecture mapping of CoaXPressRx is signal fsmMaster : AxiStreamMasterType; signal hdrMaster : AxiStreamMasterType; signal hdrCtrl : AxiStreamCtrlType; + signal hsFsmError : sl; signal dataIntMaster : AxiStreamMasterType; signal dataIntSlave : AxiStreamSlaveType; signal overflowData : slv(NUM_LANES_G-1 downto 0); + signal rxFsmRstSync : slv(NUM_LANES_G-1 downto 0); + signal rxLaneRst : slv(NUM_LANES_G-1 downto 0); + signal rxPathRst : sl; + signal dataPathRst : sl; begin rxOverflow <= uOr(overflowData) or rxCtrl.overflow or hdrCtrl.overflow; + rxFsmError <= hsFsmError or uOr(rxLaneError); + rxPathRst <= rxRst(0) or rxFsmRst; + + U_DataPathRst : entity surf.RstSync + generic map ( + TPD_G => TPD_G) + port map ( + clk => dataClk, + asyncRst => dataRst or rxFsmRst, + syncRst => dataPathRst); GEN_LANE : for i in NUM_LANES_G-1 downto 0 generate + U_RxFsmRstSync : entity surf.RstSync + generic map ( + TPD_G => TPD_G) + port map ( + clk => rxClk(i), + asyncRst => rxFsmRst, + syncRst => rxFsmRstSync(i)); + + rxLaneRst(i) <= rxRst(i) or rxFsmRstSync(i); + U_Lane : entity surf.CoaXPressRxLane generic map ( TPD_G => TPD_G) @@ -123,10 +162,13 @@ begin cfgMaster => cfgMasters(i), -- Data Interface dataMaster => dataMasters(i), + -- Event payload Interface + eventMaster => eventMasters(i), -- ACK Interface ioAck => ioAck(i), eventAck => eventAckVec(i), eventTag => eventTagVec(i), + rxError => rxLaneError(i), -- RX PHY Interface rxData => rxData(i), rxDataK => rxDataK(i), @@ -146,7 +188,7 @@ begin port map ( -- Slave Port sAxisClk => rxClk(i), - sAxisRst => rxRst(i), + sAxisRst => rxLaneRst(i), sAxisMaster => dataMasters(i), sAxisCtrl => dataCtrls(i), -- Master Port @@ -188,7 +230,7 @@ begin rxRst => rxRst(0), -- Config/Status Interface rxFsmRst => rxFsmRst, - rxFsmError => rxFsmError, + rxFsmError => hsFsmError, -- Inbound Stream Interface rxMaster => rxMaster, rxSlave => rxSlave, @@ -210,14 +252,14 @@ begin SLAVE_AXI_CONFIG_G => ssiAxiStreamConfig(dataBytes => (224/8), tDestBits => 0), MASTER_AXI_CONFIG_G => AXIS_CONFIG_G) port map ( - -- Slave Port + -- Slave Port sAxisClk => rxClk(0), - sAxisRst => rxRst(0), + sAxisRst => rxPathRst, sAxisMaster => hdrMaster, sAxisCtrl => hdrCtrl, - -- Master Port + -- Master Port mAxisClk => dataClk, - mAxisRst => dataRst, + mAxisRst => dataPathRst, mAxisMaster => imageHdrMaster, mAxisSlave => imageHdrSlave); @@ -227,17 +269,17 @@ begin SLAVE_READY_EN_G => false, GEN_SYNC_FIFO_G => false, FIFO_ADDR_WIDTH_G => 9, - SLAVE_AXI_CONFIG_G => ssiAxiStreamConfig(dataBytes => (4*NUM_LANES_G), tDestBits => 0), + SLAVE_AXI_CONFIG_G => WIDE_AXIS_CONFIG_C, MASTER_AXI_CONFIG_G => AXIS_CONFIG_G) port map ( - -- INbound Interface + -- INbound Interface sAxisClk => rxClk(0), - sAxisRst => rxRst(0), + sAxisRst => rxPathRst, sAxisMaster => fsmMaster, sAxisCtrl => rxCtrl, - -- Outbound Interface + -- Outbound Interface mAxisClk => dataClk, - mAxisRst => dataRst, + mAxisRst => dataPathRst, mAxisMaster => dataIntMaster, mAxisSlave => dataIntSlave); @@ -245,6 +287,7 @@ begin generic map ( -- General Configurations TPD_G => TPD_G, + TUSER_MASK_G => (others => '0'), -- FIFO configurations COMMON_CLK_G => true, SLAVE_FIFO_G => false, @@ -253,14 +296,14 @@ begin SLAVE_AXI_CONFIG_G => AXIS_CONFIG_G, MASTER_AXI_CONFIG_G => AXIS_CONFIG_G) port map ( - -- Slave Port + -- Slave Port sAxisClk => dataClk, - sAxisRst => dataRst, + sAxisRst => dataPathRst, sAxisMaster => dataIntMaster, sAxisSlave => dataIntSlave, - -- Master Port + -- Master Port mAxisClk => dataClk, - mAxisRst => dataRst, + mAxisRst => dataPathRst, mAxisMaster => dataMaster, mAxisSlave => dataSlave); @@ -311,4 +354,27 @@ begin valid => eventAck, dout => eventTag); + U_EventPayload : entity surf.AxiStreamFifoV2 + generic map ( + -- General Configurations + TPD_G => TPD_G, + SLAVE_READY_EN_G => false, + -- FIFO configurations + MEMORY_TYPE_G => "distributed", + GEN_SYNC_FIFO_G => false, + FIFO_ADDR_WIDTH_G => 4, + -- AXI Stream Port Configurations + SLAVE_AXI_CONFIG_G => EVENT_AXIS_CONFIG_C, + MASTER_AXI_CONFIG_G => EVENT_AXIS_CONFIG_C) + port map ( + -- Slave Port + sAxisClk => rxClk(0), + sAxisRst => rxPathRst, + sAxisMaster => eventMasters(0), + -- Master Port + mAxisClk => cfgClk, + mAxisRst => cfgRst, + mAxisMaster => eventMaster, + mAxisSlave => eventSlave); + end mapping; diff --git a/protocols/coaxpress/core/rtl/CoaXPressRxHsFsm.vhd b/protocols/coaxpress/core/rtl/CoaXPressRxHsFsm.vhd index d65e729954..30cf1846d0 100644 --- a/protocols/coaxpress/core/rtl/CoaXPressRxHsFsm.vhd +++ b/protocols/coaxpress/core/rtl/CoaXPressRxHsFsm.vhd @@ -93,9 +93,24 @@ architecture rtl of CoaXPressRxHsFsm is subtype LineRemType is natural range 0 to NUM_LANES_G+1; + constant WIDE_AXIS_CONFIG_C : AxiStreamConfigType := ssiAxiStreamConfig( + dataBytes => (4*NUM_LANES_G), + tKeepMode => TKEEP_NORMAL_C, + tUserMode => TUSER_NORMAL_C, + tDestBits => 0, + tUserBits => CXP_RX_STREAM_TUSER_BITS_C); + type RegType is record endOfLine : sl; + frameEofe : sl; hdrValid : sl; + pendingValid : sl; + parseBeat : sl; + trailerMarker : sl; + trailerEofe : sl; + trailerSeen : sl; + waitTrailer : sl; + waitFrameTrailer : sl; yCnt : slv(RX_FSM_CNT_WIDTH_G-1 downto 0); dCnt : slv(RX_FSM_CNT_WIDTH_G-1 downto 0); lineRem : LineRemType; @@ -105,12 +120,23 @@ architecture rtl of CoaXPressRxHsFsm is wrd : natural range 0 to NUM_LANES_G-1; hdrMaster : AxiStreamMasterType; rxSlave : AxiStreamSlaveType; + packOut : AxiStreamSlaveType; dataMasters : AxiStreamMasterArray(1 downto 0); + dataMaster : AxiStreamMasterType; + pendingMaster : AxiStreamMasterType; state : StateType; end record RegType; constant REG_INIT_C : RegType := ( endOfLine => '0', + frameEofe => '0', hdrValid => '0', + pendingValid => '0', + parseBeat => '0', + trailerMarker => '0', + trailerEofe => '0', + trailerSeen => '0', + waitTrailer => '0', + waitFrameTrailer => '0', yCnt => (others => '0'), dCnt => (others => '0'), lineRem => 0, @@ -120,7 +146,10 @@ architecture rtl of CoaXPressRxHsFsm is wrd => 0, hdrMaster => AXI_STREAM_MASTER_INIT_C, rxSlave => AXI_STREAM_SLAVE_FORCE_C, + packOut => AXI_STREAM_SLAVE_FORCE_C, dataMasters => (others => AXI_STREAM_MASTER_INIT_C), + dataMaster => AXI_STREAM_MASTER_INIT_C, + pendingMaster => AXI_STREAM_MASTER_INIT_C, state => IDLE_S); signal r : RegType := REG_INIT_C; @@ -129,6 +158,11 @@ architecture rtl of CoaXPressRxHsFsm is signal pipeMaster : AxiStreamMasterType; signal pipeSlave : AxiStreamSlaveType; + signal packRawMaster : AxiStreamMasterType; + signal packRawSlave : AxiStreamSlaveType; + signal packMaster : AxiStreamMasterType; + signal packInSlave : AxiStreamSlaveType; + signal packOutSlave : AxiStreamSlaveType; -- attribute dont_touch : string; -- attribute dont_touch of r : signal is "TRUE"; @@ -137,7 +171,9 @@ begin packRst <= rxRst or rxFsmRst; - comb : process (pipeMaster, r, rxFsmRst, rxRst) is + packOutSlave <= rin.packOut; + + comb : process (packInSlave, packMaster, pipeMaster, r, rxFsmRst, rxRst) is variable v : RegType; variable tData : slv(31 downto 0); variable eolBeat : sl; @@ -149,7 +185,13 @@ begin v := r; -- Init Variable - tData := pipeMaster.tData(32*r.wrd+31 downto 32*r.wrd); + tData := pipeMaster.tData(32*r.wrd+31 downto 32*r.wrd); + v.trailerMarker := pipeMaster.tValid and pipeMaster.tLast and ( + pipeMaster.tUser(CXP_RX_STREAM_TRAILER_TUSER_C) or + axiStreamGetUserBit(WIDE_AXIS_CONFIG_C, pipeMaster, CXP_RX_STREAM_TRAILER_TUSER_C)); + v.trailerEofe := ssiGetUserEofe(WIDE_AXIS_CONFIG_C, pipeMaster) or pipeMaster.tUser(SSI_EOFE_C); + v.waitTrailer := r.pendingValid or (r.waitFrameTrailer and not v.trailerMarker and not r.trailerSeen) or + (packMaster.tValid and packMaster.tLast and not v.trailerMarker and not r.trailerSeen); -- Reset strobes v.dbg.errDet := '0'; @@ -169,18 +211,40 @@ begin v.hdrMaster.tUser(SSI_SOF_C) := '1'; -- single word write -- Init data stream - v.dataMasters(0).tValid := '0'; -- Reset strobe - v.dataMasters(0).tData := pipeMaster.tData; - -- Check if state is not STEP_S - if (r.state /= STEP_S) then - v.dataMasters(0).tKeep := (others => '0'); -- Reset bus + if (packInSlave.tReady = '1') then + v.dataMasters(0).tValid := '0'; -- Reset strobe + v.dataMasters(0).tData := pipeMaster.tData; + -- Check if state is not STEP_S + if (r.state /= STEP_S) then + v.dataMasters(0).tKeep := (others => '0'); -- Reset bus + end if; + else + v.dataMasters(0) := r.dataMasters(0); end if; -- Flow Control v.rxSlave.tReady := '0'; + v.packOut := AXI_STREAM_SLAVE_FORCE_C; + if (v.waitTrailer = '1') then + v.packOut.tReady := '0'; + end if; + v.parseBeat := pipeMaster.tValid and not v.trailerMarker and not v.waitTrailer and packInSlave.tReady; -- Check for valid data - if (pipeMaster.tValid = '1') then + if (pipeMaster.tValid = '1') and (v.trailerMarker = '1') then + -- Consume the lane's trailer verdict beat. Payload has + -- already streamed through; this only annotates the eventual SSI EOF. + v.rxSlave.tReady := '1'; + if (v.trailerEofe = '1') then + v.frameEofe := '1'; + end if; + if (r.waitFrameTrailer = '1') or (r.pendingValid = '1') or (r.endOfLine = '1') or + (r.dataMasters(0).tValid = '1') or (r.dataMasters(1).tValid = '1') or + ((packMaster.tValid = '1') and (packMaster.tLast = '1')) then + v.trailerSeen := '1'; + end if; + + elsif (v.parseBeat = '1') then -- State Machine case r.state is @@ -200,6 +264,10 @@ begin end if; ---------------------------------------------------------------------- when TYPE_S => + if (r.waitFrameTrailer = '0') and (r.pendingValid = '0') then + v.trailerSeen := '0'; + end if; + -- Check for "Rectangular image header indication" if (tData = x"01_01_01_01") then @@ -405,11 +473,17 @@ begin end if; - -- Shift the pipeline and convert tKEEP to count (helps with making timing in byte packer) - v.dataMasters(1) := r.dataMasters(0); + -- Shift the parser output into the word packer only when the packer can + -- accept it. This keeps the final packed EOF beat stable while the FSM + -- waits for the lane trailer verdict. + if (packInSlave.tReady = '1') then + v.dataMasters(1) := r.dataMasters(0); + else + v.dataMasters(1) := r.dataMasters(1); + end if; -- Check for end of line in the previous cycle - if (r.endOfLine = '1') then + if (packInSlave.tReady = '1') and (r.endOfLine = '1') then -- Reset flag v.endOfLine := '0'; @@ -421,6 +495,7 @@ begin if (v.yCnt = r.hdr.ySize(RX_FSM_CNT_WIDTH_G-1 downto 0)) then -- Terminate the frame v.dataMasters(1).tLast := '1'; + v.waitFrameTrailer := '1'; end if; end if; @@ -441,44 +516,46 @@ begin end if; -- Update header based on counter - case r.hdrCnt is - ---------------------------------------------------------------- - when 3 => v.hdr.steamId(7 downto 0) := tData(7 downto 0); - ---------------------------------------------------------------- - when 4 => v.hdr.sourceTag(15 downto 8) := tData(7 downto 0); - when 5 => v.hdr.sourceTag(7 downto 0) := tData(7 downto 0); - ---------------------------------------------------------------- - when 6 => v.hdr.xSize(23 downto 16) := tData(7 downto 0); - when 7 => v.hdr.xSize(15 downto 8) := tData(7 downto 0); - when 8 => v.hdr.xSize(7 downto 0) := tData(7 downto 0); - ---------------------------------------------------------------- - when 9 => v.hdr.xOffs(23 downto 16) := tData(7 downto 0); - when 10 => v.hdr.xOffs(15 downto 8) := tData(7 downto 0); - when 11 => v.hdr.xOffs(7 downto 0) := tData(7 downto 0); - ---------------------------------------------------------------- - when 12 => v.hdr.ySize(23 downto 16) := tData(7 downto 0); - when 13 => v.hdr.ySize(15 downto 8) := tData(7 downto 0); - when 14 => v.hdr.ySize(7 downto 0) := tData(7 downto 0); - ---------------------------------------------------------------- - when 15 => v.hdr.yOffs(23 downto 16) := tData(7 downto 0); - when 16 => v.hdr.yOffs(15 downto 8) := tData(7 downto 0); - when 17 => v.hdr.yOffs(7 downto 0) := tData(7 downto 0); - ---------------------------------------------------------------- - when 18 => v.hdr.dsizeL(23 downto 16) := tData(7 downto 0); - when 19 => v.hdr.dsizeL(15 downto 8) := tData(7 downto 0); - when 20 => v.hdr.dsizeL(7 downto 0) := tData(7 downto 0); - ---------------------------------------------------------------- - when 21 => v.hdr.pixelF(15 downto 8) := tData(7 downto 0); - when 22 => v.hdr.pixelF(7 downto 0) := tData(7 downto 0); - ---------------------------------------------------------------- - when 23 => v.hdr.tapG(15 downto 8) := tData(7 downto 0); - when 24 => v.hdr.tapG(7 downto 0) := tData(7 downto 0); - ---------------------------------------------------------------- - when 25 => v.hdr.flags(7 downto 0) := tData(7 downto 0); - ---------------------------------------------------------------- - when others => - null; - end case; + if (v.parseBeat = '1') then + case r.hdrCnt is + ---------------------------------------------------------------- + when 3 => v.hdr.steamId(7 downto 0) := tData(7 downto 0); + ---------------------------------------------------------------- + when 4 => v.hdr.sourceTag(15 downto 8) := tData(7 downto 0); + when 5 => v.hdr.sourceTag(7 downto 0) := tData(7 downto 0); + ---------------------------------------------------------------- + when 6 => v.hdr.xSize(23 downto 16) := tData(7 downto 0); + when 7 => v.hdr.xSize(15 downto 8) := tData(7 downto 0); + when 8 => v.hdr.xSize(7 downto 0) := tData(7 downto 0); + ---------------------------------------------------------------- + when 9 => v.hdr.xOffs(23 downto 16) := tData(7 downto 0); + when 10 => v.hdr.xOffs(15 downto 8) := tData(7 downto 0); + when 11 => v.hdr.xOffs(7 downto 0) := tData(7 downto 0); + ---------------------------------------------------------------- + when 12 => v.hdr.ySize(23 downto 16) := tData(7 downto 0); + when 13 => v.hdr.ySize(15 downto 8) := tData(7 downto 0); + when 14 => v.hdr.ySize(7 downto 0) := tData(7 downto 0); + ---------------------------------------------------------------- + when 15 => v.hdr.yOffs(23 downto 16) := tData(7 downto 0); + when 16 => v.hdr.yOffs(15 downto 8) := tData(7 downto 0); + when 17 => v.hdr.yOffs(7 downto 0) := tData(7 downto 0); + ---------------------------------------------------------------- + when 18 => v.hdr.dsizeL(23 downto 16) := tData(7 downto 0); + when 19 => v.hdr.dsizeL(15 downto 8) := tData(7 downto 0); + when 20 => v.hdr.dsizeL(7 downto 0) := tData(7 downto 0); + ---------------------------------------------------------------- + when 21 => v.hdr.pixelF(15 downto 8) := tData(7 downto 0); + when 22 => v.hdr.pixelF(7 downto 0) := tData(7 downto 0); + ---------------------------------------------------------------- + when 23 => v.hdr.tapG(15 downto 8) := tData(7 downto 0); + when 24 => v.hdr.tapG(7 downto 0) := tData(7 downto 0); + ---------------------------------------------------------------- + when 25 => v.hdr.flags(7 downto 0) := tData(7 downto 0); + ---------------------------------------------------------------- + when others => + null; + end case; + end if; -- Register a small saturated remaining-word count. This breaks the wide -- dCnt-to-wrd path when a line ends mid-beat and the next marker is held @@ -523,10 +600,42 @@ begin v.hdrMaster.tData(207 downto 192) := r.hdr.pixelF(15 downto 0); v.hdrMaster.tData(223 downto 208) := r.hdr.tapG(15 downto 0); + -- Hold only the packed SSI EOF beat until the lane trailer verdict + -- arrives. Payload still streams as soon as it is parsed. + v.dataMaster.tValid := '0'; + + if (r.pendingValid = '1') then + v.packOut.tReady := '0'; + if (v.trailerMarker = '1') or (r.trailerSeen = '1') then + v.dataMaster := r.pendingMaster; + ssiSetUserEofe(WIDE_AXIS_CONFIG_C, v.dataMaster, v.frameEofe or v.trailerEofe); + v.dataMaster.tUser(SSI_EOFE_C) := v.frameEofe or v.trailerEofe; + v.pendingMaster := AXI_STREAM_MASTER_INIT_C; + v.pendingValid := '0'; + v.frameEofe := '0'; + v.trailerSeen := '0'; + v.waitFrameTrailer := '0'; + end if; + elsif (packMaster.tValid = '1') and (packMaster.tLast = '1') and + (v.trailerMarker = '0') and (r.trailerSeen = '0') then + v.pendingMaster := packMaster; + v.pendingValid := '1'; + elsif (packMaster.tValid = '1') and (packMaster.tLast = '1') then + v.dataMaster := packMaster; + ssiSetUserEofe(WIDE_AXIS_CONFIG_C, v.dataMaster, v.frameEofe or v.trailerEofe); + v.dataMaster.tUser(SSI_EOFE_C) := v.frameEofe or v.trailerEofe; + v.frameEofe := '0'; + v.trailerSeen := '0'; + v.waitFrameTrailer := '0'; + elsif (packMaster.tValid = '1') then + v.dataMaster := packMaster; + end if; + -- Outputs - pipeSlave <= v.rxSlave; - hdrMaster <= r.hdrMaster; - rxFsmError <= r.dbg.errDet; + pipeSlave <= v.rxSlave; + hdrMaster <= r.hdrMaster; + dataMaster <= r.dataMaster; + rxFsmError <= r.dbg.errDet; -- Reset if (rxRst = '1') or (rxFsmRst = '1') then @@ -565,6 +674,20 @@ begin rxClk => rxClk, rxRst => packRst, sAxisMaster => r.dataMasters(1), - mAxisMaster => dataMaster); + sAxisSlave => packInSlave, + mAxisMaster => packRawMaster, + mAxisSlave => packRawSlave); + + U_PackPipe : entity surf.AxiStreamPipeline + generic map ( + TPD_G => TPD_G, + PIPE_STAGES_G => 1) + port map ( + axisClk => rxClk, + axisRst => packRst, + sAxisMaster => packRawMaster, + sAxisSlave => packRawSlave, + mAxisMaster => packMaster, + mAxisSlave => packOutSlave); end rtl; diff --git a/protocols/coaxpress/core/rtl/CoaXPressRxLane.vhd b/protocols/coaxpress/core/rtl/CoaXPressRxLane.vhd index 3e873bf008..f8fb0edf14 100644 --- a/protocols/coaxpress/core/rtl/CoaXPressRxLane.vhd +++ b/protocols/coaxpress/core/rtl/CoaXPressRxLane.vhd @@ -40,12 +40,15 @@ entity CoaXPressRxLane is dataMaster : out AxiStreamMasterType; -- Heartbeat Interface heatbeatMaster : out AxiStreamMasterType; + -- Event payload Interface + eventMaster : out AxiStreamMasterType; -- Image header Interface imageHdrMaster : out AxiStreamMasterType; -- ACK Interface ioAck : out sl; eventAck : out sl; eventTag : out slv(7 downto 0); + rxError : out sl; -- RX PHY Interface rxData : in slv(31 downto 0); rxDataK : in slv(3 downto 0); @@ -54,13 +57,26 @@ end entity CoaXPressRxLane; architecture rtl of CoaXPressRxLane is + constant EVENT_BUFFER_DEPTH_C : positive := 16; + constant EVENT_BUFFER_ADDR_WIDTH_C : positive := 4; + constant STREAM_AXIS_CONFIG_C : AxiStreamConfigType := ssiAxiStreamConfig( + dataBytes => 4, + tKeepMode => TKEEP_NORMAL_C, + tUserMode => TUSER_NORMAL_C, + tDestBits => 0, + tUserBits => CXP_RX_STREAM_TUSER_BITS_C); + type StateType is ( IO_ACK_S, IDLE_S, TYPE_S, CTRL_ACK_TAG_S, CTRL_ACK_S, + CTRL_ACK_CRC_S, + CTRL_ACK_EOP_S, HEARTBEAT_S, + HEARTBEAT_CRC_S, + HEARTBEAT_EOP_S, EVENT_ACK_S, EVENT_DSIZE_UPPER_S, EVENT_DSIZE_LOWER_S, @@ -71,7 +87,9 @@ architecture rtl of CoaXPressRxLane is PACKET_TAG_S, DSIZE_UPPER_S, DSIZE_LOWER_S, - STREAM_DATA_S); + STREAM_DATA_S, + STREAM_CRC_S, + STREAM_EOP_S); type RegType is record errDet : sl; @@ -80,6 +98,13 @@ architecture rtl of CoaXPressRxLane is eventAck : sl; eventTag : slv(7 downto 0); ackCnt : natural range 0 to 15; + eventId : slv(31 downto 0); + eventPending : sl; + eventReleaseCnt : natural range 0 to EVENT_BUFFER_DEPTH_C-1; + eventRamWrEn : sl; + eventRamWrAddr : slv(EVENT_BUFFER_ADDR_WIDTH_C-1 downto 0); + eventRamWrData : slv(31 downto 0); + eventReadAddr : slv(EVENT_BUFFER_ADDR_WIDTH_C-1 downto 0); -- Stream data payload streamID : slv(7 downto 0); packetTag : slv(7 downto 0); @@ -91,6 +116,7 @@ architecture rtl of CoaXPressRxLane is cfgMaster : AxiStreamMasterType; dataMaster : AxiStreamMasterType; heatbeatMaster : AxiStreamMasterType; + eventMaster : AxiStreamMasterType; -- State Types saved : StateType; state : StateType; @@ -102,6 +128,13 @@ architecture rtl of CoaXPressRxLane is eventAck => '0', eventTag => (others => '0'), ackCnt => 0, + eventId => (others => '0'), + eventPending => '0', + eventReleaseCnt => 0, + eventRamWrEn => '0', + eventRamWrAddr => (others => '0'), + eventRamWrData => (others => '0'), + eventReadAddr => (others => '0'), -- Stream data payload streamID => (others => '0'), packetTag => (others => '0'), @@ -113,6 +146,7 @@ architecture rtl of CoaXPressRxLane is cfgMaster => AXI_STREAM_MASTER_INIT_C, dataMaster => AXI_STREAM_MASTER_INIT_C, heatbeatMaster => AXI_STREAM_MASTER_INIT_C, + eventMaster => AXI_STREAM_MASTER_INIT_C, -- State Types saved => IDLE_S, state => IDLE_S); @@ -120,6 +154,8 @@ architecture rtl of CoaXPressRxLane is signal r : RegType := REG_INIT_C; signal rin : RegType; + signal eventRamRdData : slv(31 downto 0); + function cxpCrcUpdate ( crcIn : slv(31 downto 0); data : slv(31 downto 0)) @@ -148,12 +184,23 @@ architecture rtl of CoaXPressRxLane is return endianSwap(retVar); end function cxpCrcFinal; + function cxpAckIsSuccess ( + data : slv(31 downto 0)) + return boolean is + begin + return ( + (data = x"01_01_01_01") or + (data = x"04_04_04_04") or + (data = x"00_00_00_01") or + (data = x"00_00_00_04")); + end function cxpAckIsSuccess; + -- attribute dont_touch : string; -- attribute dont_touch of r : signal is "TRUE"; begin - comb : process (r, rxData, rxDataK, rxLinkUp, rxRst) is + comb : process (eventRamRdData, r, rxData, rxDataK, rxLinkUp, rxRst) is variable v : RegType; begin -- Latch the current value @@ -166,7 +213,34 @@ begin v.cfgMaster.tValid := '0'; v.dataMaster.tValid := '0'; v.dataMaster.tLast := '0'; + v.dataMaster.tKeep := (others => '0'); + v.dataMaster.tUser := (others => '0'); v.heatbeatMaster.tValid := '0'; + v.eventMaster.tValid := '0'; + v.eventMaster.tLast := '0'; + v.eventRamWrEn := '0'; + + -- Release event payload only after the packet CRC and EOP have passed. + if (r.eventPending = '1') then + v.eventMaster.tValid := '1'; + v.eventMaster.tData(31 downto 0) := eventRamRdData; + v.eventMaster.tKeep(3 downto 0) := x"F"; + v.eventMaster.tDest(7 downto 0) := r.eventTag; + v.eventMaster.tUser(31 downto 0) := r.eventId; + if (r.eventReleaseCnt = (conv_integer(r.dsize)-1)) then + v.eventMaster.tLast := '1'; + v.eventPending := '0'; + v.eventReleaseCnt := 0; + v.eventReadAddr := (others => '0'); + else + v.eventReleaseCnt := r.eventReleaseCnt + 1; + if (r.eventReleaseCnt < (conv_integer(r.dsize)-2)) then + v.eventReadAddr := r.eventReadAddr + 1; + else + v.eventReadAddr := (others => '0'); + end if; + end if; + end if; -- Check for I/O if (rxDataK = x"F") and (rxData = CXP_IO_ACK_C) then @@ -210,31 +284,54 @@ begin -- Check for "Stream data packet" if (rxData = x"01_01_01_01") then + -- Reset stream packet parser + v.dcnt := (others => '0'); + v.dsize := (others => '0'); + v.crc := x"FFFFFFFF"; -- Next State v.state := STREAM_ID_S; -- Check for "control acknowledge with no tag" elsif (rxData = x"03_03_03_03") then + -- Reset control acknowledgment parser + v.ackCnt := 0; + v.crc := x"FFFFFFFF"; -- Next State v.state := CTRL_ACK_S; -- Check for "control acknowledge with tag" elsif (rxData = x"06_06_06_06") then + -- Reset control acknowledgment parser + v.ackCnt := 0; + v.crc := x"FFFFFFFF"; -- Next State v.state := CTRL_ACK_TAG_S; -- Check for "Event packet" elsif (rxData = x"07_07_07_07") then - -- Reset event parser counters - v.ackCnt := 0; - v.dcnt := (others => '0'); - v.dsize := (others => '0'); - v.crc := x"FFFFFFFF"; - -- Next State - v.state := EVENT_ACK_S; + -- Check for pending event payload release + if (r.eventPending = '0') then + -- Reset event parser counters + v.ackCnt := 0; + v.dcnt := (others => '0'); + v.dsize := (others => '0'); + v.eventReleaseCnt := 0; + v.eventReadAddr := (others => '0'); + v.crc := x"FFFFFFFF"; + -- Next State + v.state := EVENT_ACK_S; + else + -- Set the flag + v.errDet := '1'; + -- Next State + v.state := IDLE_S; + end if; -- Check for "Heartbeat Payload" elsif (rxData = x"09_09_09_09") then + -- Reset heartbeat parser + v.ackCnt := 0; + v.crc := x"FFFFFFFF"; -- Next State v.state := HEARTBEAT_S; @@ -253,8 +350,13 @@ begin end if; ---------------------------------------------------------------------- when CTRL_ACK_TAG_S => - -- Check for non-k word - if (rxDataK = x"0") then + -- Check for repeated-byte tag word + if (rxDataK = x"0") + and (rxData(7 downto 0) = rxData(15 downto 8)) + and (rxData(7 downto 0) = rxData(23 downto 16)) + and (rxData(7 downto 0) = rxData(31 downto 24)) then + -- Include packet tag word in the CRC + v.crc := cxpCrcUpdate(r.crc, rxData); -- Next State v.state := CTRL_ACK_S; else @@ -267,41 +369,88 @@ begin when CTRL_ACK_S => -- Check for non-k word if (rxDataK = x"0") then - -- Increment the counter v.ackCnt := r.ackCnt + 1; -- "Acknowledgment code" index if (r.ackCnt = 0) then + -- Include acknowledgment code in the CRC + v.crc := cxpCrcUpdate(r.crc, rxData); -- Save the response code v.cfgMaster.tData(31 downto 0) := rxData; -- Check for Success ACK - if (rxData = x"01_01_01_01") or (rxData = x"04_04_04_04") then + if (cxpAckIsSuccess(rxData)) then -- Always send ZERO for successful ACK v.cfgMaster.tData(31 downto 0) := (others => '0'); end if; + -- "Size field" index: check if data follows + elsif (r.ackCnt = 1) then + + -- Some cameras return write ACK as code+CRC+EOP, + -- without an explicit zero-size word. + if (rxData = cxpCrcFinal(r.crc)) then + -- Next State + v.state := CTRL_ACK_EOP_S; + + -- If DSize=0 (write ACK, no data word follows), skip to CRC + elsif (rxData(31 downto 8) = 0) then + -- Include size word in the CRC + v.crc := cxpCrcUpdate(r.crc, rxData); + -- Next State + v.state := CTRL_ACK_CRC_S; + + else + -- Include size word in the CRC + v.crc := cxpCrcUpdate(r.crc, rxData); + end if; + -- "Data field" index elsif (r.ackCnt = 2) then + -- Include data word in the CRC + v.crc := cxpCrcUpdate(r.crc, rxData); -- Save the data field v.cfgMaster.tData(63 downto 32) := rxData; - -- Forward the response - v.cfgMaster.tValid := '1'; - -- Next State - v.state := IDLE_S; + v.state := CTRL_ACK_CRC_S; end if; else + -- Set the flag + v.errDet := '1'; + -- Next State + v.state := IDLE_S; + end if; + ---------------------------------------------------------------------- + when CTRL_ACK_CRC_S => + -- Check for CRC word + if (rxDataK = x"0") and (rxData = cxpCrcFinal(r.crc)) then + -- Next State + v.state := CTRL_ACK_EOP_S; + else + -- Set the flag + v.errDet := '1'; + -- Next State + v.state := IDLE_S; + end if; + ---------------------------------------------------------------------- + when CTRL_ACK_EOP_S => + -- Check for end of packet indication + if (rxDataK = x"F") and (rxData = CXP_EOP_C) then -- Forward the response v.cfgMaster.tValid := '1'; -- Next State v.state := IDLE_S; + else + -- Set the flag + v.errDet := '1'; + -- Next State + v.state := IDLE_S; end if; ---------------------------------------------------------------------- when EVENT_ACK_S => @@ -319,7 +468,13 @@ begin v.ackCnt := r.ackCnt + 1; -- Packet Tag index - if (r.ackCnt = 4) then + if (r.ackCnt < 4) then + + -- Save the event identifier bytes for payload sideband use. + v.eventId(8*r.ackCnt+7 downto 8*r.ackCnt) := rxData(7 downto 0); + + -- Packet Tag index + elsif (r.ackCnt = 4) then -- Save the packet tag v.eventTag := rxData(7 downto 0); @@ -367,8 +522,11 @@ begin -- Next State if (r.dsize(15 downto 8) = 0) and (rxData(7 downto 0) = 0) then v.state := EVENT_CRC_S; - else + elsif (r.dsize(15 downto 8) = 0) and (conv_integer(rxData(7 downto 0)) <= EVENT_BUFFER_DEPTH_C) then v.state := EVENT_PAYLOAD_S; + else + v.errDet := '1'; + v.state := IDLE_S; end if; else -- Set the flag @@ -380,7 +538,10 @@ begin when EVENT_PAYLOAD_S => -- Check for event payload word if (rxDataK = x"0") then - v.crc := cxpCrcUpdate(r.crc, rxData); + v.eventRamWrEn := '1'; + v.eventRamWrAddr := r.dcnt(EVENT_BUFFER_ADDR_WIDTH_C-1 downto 0); + v.eventRamWrData := rxData; + v.crc := cxpCrcUpdate(r.crc, rxData); -- Check the counter if (r.dcnt = (r.dsize-1)) then -- Next State @@ -415,6 +576,15 @@ begin v.eventAck := '1'; -- Next State v.state := IDLE_S; + if (r.dsize /= 0) then + v.eventPending := '1'; + v.eventReleaseCnt := 0; + if (r.dsize = 1) then + v.eventReadAddr := (others => '0'); + else + v.eventReadAddr := toSlv(1, EVENT_BUFFER_ADDR_WIDTH_C); + end if; + end if; else -- Set the flag v.errDet := '1'; @@ -423,8 +593,14 @@ begin end if; ---------------------------------------------------------------------- when HEARTBEAT_S => - -- Check for non-k word - if (rxDataK = x"0") then + -- Check for repeated-byte heartbeat payload word + if (rxDataK = x"0") + and (rxData(7 downto 0) = rxData(15 downto 8)) + and (rxData(7 downto 0) = rxData(23 downto 16)) + and (rxData(7 downto 0) = rxData(31 downto 24)) then + + -- Include heartbeat payload word in the CRC + v.crc := cxpCrcUpdate(r.crc, rxData); -- Increment the counter v.ackCnt := r.ackCnt + 1; @@ -435,12 +611,8 @@ begin -- "Acknowledgment code" index if (r.ackCnt = 11) then - -- Forward the response - v.heatbeatMaster.tValid := '1'; - v.heatbeatMaster.tLast := '1'; - -- Next State - v.state := IDLE_S; + v.state := HEARTBEAT_CRC_S; end if; @@ -451,6 +623,33 @@ begin v.state := IDLE_S; end if; ---------------------------------------------------------------------- + when HEARTBEAT_CRC_S => + -- Check for CRC word + if (rxDataK = x"0") and (rxData = cxpCrcFinal(r.crc)) then + -- Next State + v.state := HEARTBEAT_EOP_S; + else + -- Set the flag + v.errDet := '1'; + -- Next State + v.state := IDLE_S; + end if; + ---------------------------------------------------------------------- + when HEARTBEAT_EOP_S => + -- Check for end of packet indication + if (rxDataK = x"F") and (rxData = CXP_EOP_C) then + -- Forward the response + v.heatbeatMaster.tValid := '1'; + v.heatbeatMaster.tLast := '1'; + -- Next State + v.state := IDLE_S; + else + -- Set the flag + v.errDet := '1'; + -- Next State + v.state := IDLE_S; + end if; + ---------------------------------------------------------------------- when STREAM_ID_S => -- Check for non-k word if (rxDataK = x"0") @@ -459,6 +658,7 @@ begin and (rxData(7 downto 0) = rxData(31 downto 24)) then -- Save the value v.streamID := rxData(7 downto 0); + v.crc := cxpCrcUpdate(r.crc, rxData); -- Next State v.state := PACKET_TAG_S; else @@ -476,6 +676,7 @@ begin and (rxData(7 downto 0) = rxData(31 downto 24)) then -- Save the value v.packetTag := rxData(7 downto 0); + v.crc := cxpCrcUpdate(r.crc, rxData); -- Next State v.state := DSIZE_UPPER_S; else @@ -493,6 +694,7 @@ begin and (rxData(7 downto 0) = rxData(31 downto 24)) then -- Set the TDEST to the packet tag v.dsize(15 downto 8) := rxData(7 downto 0); + v.crc := cxpCrcUpdate(r.crc, rxData); -- Next State v.state := DSIZE_LOWER_S; else @@ -510,8 +712,14 @@ begin and (rxData(7 downto 0) = rxData(31 downto 24)) then -- Set the TDEST to the packet tag v.dsize(7 downto 0) := rxData(7 downto 0); + v.dcnt := (others => '0'); + v.crc := cxpCrcUpdate(r.crc, rxData); -- Next State - v.state := STREAM_DATA_S; + if (r.dsize(15 downto 8) = 0) and (rxData(7 downto 0) = 0) then + v.state := STREAM_CRC_S; + else + v.state := STREAM_DATA_S; + end if; else -- Set the flag v.errDet := '1'; @@ -523,7 +731,9 @@ begin -- Move the data v.dataMaster.tValid := '1'; v.dataMaster.tData(31 downto 0) := rxData; + v.dataMaster.tKeep(3 downto 0) := x"F"; v.dataMaster.tUser(3 downto 0) := rxDataK; + v.crc := cxpCrcUpdate(r.crc, rxData); -- Increment counter v.dbgCnt := r.dbgCnt + 1; @@ -534,12 +744,64 @@ begin v.dataMaster.tLast := '1'; -- Next State - v.state := IDLE_S; + v.state := STREAM_CRC_S; else -- Increment counter v.dcnt := r.dcnt + 1; end if; + ---------------------------------------------------------------------- + when STREAM_CRC_S => + -- Check for CRC word + if (rxDataK = x"0") and (rxData = cxpCrcFinal(r.crc)) then + -- Next State + v.state := STREAM_EOP_S; + else + -- Publish an in-order SSI-style trailer verdict marker for + -- the image FSM. The raw stream payload has already moved. + v.dataMaster.tValid := '1'; + v.dataMaster.tData(31 downto 0) := (others => '0'); + v.dataMaster.tKeep(3 downto 0) := x"F"; + v.dataMaster.tLast := '1'; + v.dataMaster.tUser(CXP_RX_STREAM_TRAILER_TUSER_C) := '1'; + axiStreamSetUserBit(STREAM_AXIS_CONFIG_C, v.dataMaster, CXP_RX_STREAM_TRAILER_TUSER_C, '1'); + ssiSetUserEofe(STREAM_AXIS_CONFIG_C, v.dataMaster, '1'); + v.dataMaster.tUser(SSI_EOFE_C) := '1'; + -- Set the flag + v.errDet := '1'; + -- Next State + v.state := IDLE_S; + end if; + ---------------------------------------------------------------------- + when STREAM_EOP_S => + -- Check for end of packet indication + if (rxDataK = x"F") and (rxData = CXP_EOP_C) then + -- Publish a clean in-order trailer verdict marker. + v.dataMaster.tValid := '1'; + v.dataMaster.tData(31 downto 0) := (others => '0'); + v.dataMaster.tKeep(3 downto 0) := x"F"; + v.dataMaster.tLast := '1'; + v.dataMaster.tUser(CXP_RX_STREAM_TRAILER_TUSER_C) := '1'; + axiStreamSetUserBit(STREAM_AXIS_CONFIG_C, v.dataMaster, CXP_RX_STREAM_TRAILER_TUSER_C, '1'); + ssiSetUserEofe(STREAM_AXIS_CONFIG_C, v.dataMaster, '0'); + v.dataMaster.tUser(SSI_EOFE_C) := '0'; + -- Next State + v.state := IDLE_S; + else + -- Publish a bad in-order trailer verdict marker. + v.dataMaster.tValid := '1'; + v.dataMaster.tData(31 downto 0) := (others => '0'); + v.dataMaster.tKeep(3 downto 0) := x"F"; + v.dataMaster.tLast := '1'; + v.dataMaster.tUser(CXP_RX_STREAM_TRAILER_TUSER_C) := '1'; + axiStreamSetUserBit(STREAM_AXIS_CONFIG_C, v.dataMaster, CXP_RX_STREAM_TRAILER_TUSER_C, '1'); + ssiSetUserEofe(STREAM_AXIS_CONFIG_C, v.dataMaster, '1'); + v.dataMaster.tUser(SSI_EOFE_C) := '1'; + -- Set the flag + v.errDet := '1'; + -- Next State + v.state := IDLE_S; + end if; ---------------------------------------------------------------------- end case; @@ -549,9 +811,11 @@ begin cfgMaster <= r.cfgMaster; dataMaster <= r.dataMaster; heatbeatMaster <= r.heatbeatMaster; + eventMaster <= r.eventMaster; ioAck <= r.ioAck; eventAck <= r.eventAck; eventTag <= r.eventTag; + rxError <= r.errDet; -- Reset if (rxRst = '1') or (rxLinkUp = '0') then @@ -570,4 +834,20 @@ begin end if; end process seq; + U_EventBuffer : entity surf.SimpleDualPortRam + generic map ( + TPD_G => TPD_G, + MEMORY_TYPE_G => "distributed", + DATA_WIDTH_G => 32, + ADDR_WIDTH_G => EVENT_BUFFER_ADDR_WIDTH_C) + port map ( + clka => rxClk, + wea => r.eventRamWrEn, + addra => r.eventRamWrAddr, + dina => r.eventRamWrData, + clkb => rxClk, + rstb => rxRst, + addrb => r.eventReadAddr, + doutb => eventRamRdData); + end rtl; diff --git a/protocols/coaxpress/core/rtl/CoaXPressRxLaneMux.vhd b/protocols/coaxpress/core/rtl/CoaXPressRxLaneMux.vhd index a9f5e9b020..0efde57af0 100644 --- a/protocols/coaxpress/core/rtl/CoaXPressRxLaneMux.vhd +++ b/protocols/coaxpress/core/rtl/CoaXPressRxLaneMux.vhd @@ -22,6 +22,8 @@ use ieee.std_logic_arith.all; library surf; use surf.StdRtlPkg.all; use surf.AxiStreamPkg.all; +use surf.SsiPkg.all; +use surf.CoaXPressPkg.all; entity CoaXPressRxLaneMux is generic ( @@ -44,21 +46,33 @@ end entity CoaXPressRxLaneMux; architecture rtl of CoaXPressRxLaneMux is + constant WIDE_AXIS_CONFIG_C : AxiStreamConfigType := ssiAxiStreamConfig( + dataBytes => (4*NUM_LANES_G), + tKeepMode => TKEEP_NORMAL_C, + tUserMode => TUSER_NORMAL_C, + tDestBits => 0, + tUserBits => CXP_RX_STREAM_TUSER_BITS_C); + type RegType is record numOfLane : slv(2 downto 0); lane : natural range 0 to NUM_LANES_G-1; + pendingTrailer : sl; + trailerMarker : sl; rxSlaves : AxiStreamSlaveArray(NUM_LANES_G-1 downto 0); pipeMaster : AxiStreamMasterType; end record RegType; constant REG_INIT_C : RegType := ( numOfLane => (others => '0'), lane => 0, + pendingTrailer => '0', + trailerMarker => '0', rxSlaves => (others => AXI_STREAM_SLAVE_FORCE_C), pipeMaster => AXI_STREAM_MASTER_INIT_C); signal r : RegType := REG_INIT_C; signal rin : RegType; + signal validVec : slv(NUM_LANES_G-1 downto 0); signal pipeSlave : AxiStreamSlaveType; -- attribute dont_touch : string; @@ -66,11 +80,16 @@ architecture rtl of CoaXPressRxLaneMux is begin - comb : process (numOfLane, pipeSlave, r, rxFsmRst, rxMasters, rxRst) is + GEN_VALID : for i in NUM_LANES_G-1 downto 0 generate + validVec(i) <= rxMasters(i).tValid when (i <= r.numOfLane) else '0'; + end generate GEN_VALID; + + comb : process (numOfLane, pipeSlave, r, rxFsmRst, rxMasters, rxRst, validVec) is variable v : RegType; begin -- Latch the current value v := r; + v.trailerMarker := '0'; -- Flow Control for i in 0 to NUM_LANES_G-1 loop @@ -89,6 +108,9 @@ begin -- Check for valid data if (rxMasters(r.lane).tValid = '1') and (v.pipeMaster.tValid = '0') then + v.trailerMarker := rxMasters(r.lane).tLast and ( + rxMasters(r.lane).tUser(CXP_RX_STREAM_TRAILER_TUSER_C) or + axiStreamGetUserBit(WIDE_AXIS_CONFIG_C, rxMasters(r.lane), CXP_RX_STREAM_TRAILER_TUSER_C)); -- Accept inbound data v.rxSlaves(r.lane).tReady := '1'; @@ -96,8 +118,16 @@ begin -- Move the outbound data v.pipeMaster := rxMasters(r.lane); - -- Check for tLast and more than 1 lane - if (rxMasters(r.lane).tLast = '1') and (NUM_LANES_G > 1) then + -- Rotate only after the lane trailer verdict marker. The preceding + -- payload TLAST still belongs to this lane until the trailer has been + -- consumed by the image FSM. + if (v.trailerMarker = '1') then + v.pendingTrailer := '0'; + elsif (rxMasters(r.lane).tLast = '1') then + v.pendingTrailer := '1'; + end if; + + if (v.trailerMarker = '1') and (NUM_LANES_G > 1) then -- Check for roll over if (r.lane = r.numOfLane) then @@ -110,6 +140,18 @@ begin end if; + -- Check for idle lane and more than 1 lane + elsif (v.pipeMaster.tValid = '0') and (NUM_LANES_G > 1) and (r.pendingTrailer = '0') and (uOr(validVec) = '1') then + + -- Check for roll over + if (r.lane = r.numOfLane) then + -- Reset counter + v.lane := 0; + else + -- Increment counter + v.lane := r.lane + 1; + end if; + end if; -- Outputs diff --git a/protocols/coaxpress/core/rtl/CoaXPressRxWordPacker.vhd b/protocols/coaxpress/core/rtl/CoaXPressRxWordPacker.vhd index 56772d7fb4..01a2f07061 100644 --- a/protocols/coaxpress/core/rtl/CoaXPressRxWordPacker.vhd +++ b/protocols/coaxpress/core/rtl/CoaXPressRxWordPacker.vhd @@ -22,6 +22,8 @@ use ieee.std_logic_unsigned.all; library surf; use surf.StdRtlPkg.all; use surf.AxiStreamPkg.all; +use surf.SsiPkg.all; +use surf.CoaXPressPkg.all; entity CoaXPressRxWordPacker is generic ( @@ -33,48 +35,73 @@ entity CoaXPressRxWordPacker is rxRst : in sl; -- Inbound frame sAxisMaster : in AxiStreamMasterType; + sAxisSlave : out AxiStreamSlaveType; -- Outbound frame - mAxisMaster : out AxiStreamMasterType); + mAxisMaster : out AxiStreamMasterType; + mAxisSlave : in AxiStreamSlaveType); end CoaXPressRxWordPacker; architecture rtl of CoaXPressRxWordPacker is + constant WIDE_AXIS_CONFIG_C : AxiStreamConfigType := ssiAxiStreamConfig( + dataBytes => (4*NUM_LANES_G), + tKeepMode => TKEEP_NORMAL_C, + tUserMode => TUSER_NORMAL_C, + tDestBits => 0, + tUserBits => CXP_RX_STREAM_TUSER_BITS_C); + type RegType is record wordCount : natural range 0 to NUM_LANES_G-1; firstWord : natural range 0 to NUM_LANES_G-1; lastWord : natural range 0 to NUM_LANES_G-1; - inMaster : AxiStreamMasterType; + beatValid : sl; + beatLast : sl; + beatData : slv(31 downto 0); + sAxisSlave : AxiStreamSlaveType; curMaster : AxiStreamMasterType; nxtMaster : AxiStreamMasterType; - outMaster : AxiStreamMasterType; end record RegType; constant REG_INIT_C : RegType := ( wordCount => 0, firstWord => 0, lastWord => 0, - inMaster => AXI_STREAM_MASTER_INIT_C, + beatValid => '0', + beatLast => '0', + beatData => (others => '0'), + sAxisSlave => AXI_STREAM_SLAVE_INIT_C, curMaster => AXI_STREAM_MASTER_INIT_C, - nxtMaster => AXI_STREAM_MASTER_INIT_C, - outMaster => AXI_STREAM_MASTER_INIT_C); + nxtMaster => AXI_STREAM_MASTER_INIT_C); signal r : RegType := REG_INIT_C; signal rin : RegType; begin - comb : process (r, rxRst, sAxisMaster) is - variable v : RegType; - variable valid : sl; - variable last : sl; - variable data : slv(31 downto 0); + sAxisSlave <= rin.sAxisSlave; + + comb : process (mAxisSlave, r, rxRst, sAxisMaster) is + variable v : RegType; begin v := r; - -- Register input - v.inMaster := sAxisMaster; + v.beatValid := '0'; + v.beatLast := '0'; + v.beatData := (others => '0'); + + -- Pop the completed output beat only when the downstream stage accepts it. + if (mAxisSlave.tReady = '1') and (r.curMaster.tValid = '1') then + v.curMaster := r.nxtMaster; + v.nxtMaster := AXI_STREAM_MASTER_INIT_C; + v.nxtMaster.tKeep := (others => '0'); + end if; + + -- The input beat can expand into at most the current partial word plus one + -- next word, so the next slot being empty is sufficient capacity. + v.sAxisSlave.tReady := not v.nxtMaster.tValid; -- Find location of last word + v.lastWord := 0; for i in 0 to NUM_LANES_G-1 loop if (sAxisMaster.tKeep(4*i) = '1') then v.lastWord := i; @@ -82,55 +109,52 @@ begin end loop; -- Find location of first word + v.firstWord := 0; for i in NUM_LANES_G-1 downto 0 loop if (sAxisMaster.tKeep(4*i) = '1') then v.firstWord := i; end if; end loop; - -- Pending output from current - if r.curMaster.tValid = '1' then - v.outMaster := r.curMaster; - v.curMaster := r.nxtMaster; - v.nxtMaster := AXI_STREAM_MASTER_INIT_C; - v.nxtMaster.tKeep := (others => '0'); - else - v.outMaster := AXI_STREAM_MASTER_INIT_C; - end if; - - -- Data is valid - if r.inMaster.tValid = '1' then + -- Data is valid and there is room to accept the whole input beat. + if (sAxisMaster.tValid = '1') and (v.sAxisSlave.tReady = '1') then -- Process each input word for i in 0 to NUM_LANES_G-1 loop - if (i >= r.firstWord) and (i <= r.lastWord) then + if (i >= v.firstWord) and (i <= v.lastWord) and (sAxisMaster.tKeep(4*i) = '1') then -- Extract values for each iteration - last := r.inMaster.tLast and toSl(i = r.lastWord); - valid := toSl(v.wordCount = NUM_LANES_G-1) or last; - data := r.inMaster.tData(i*32+31 downto i*32); + v.beatLast := sAxisMaster.tLast and toSl(i = v.lastWord); + v.beatValid := toSl(v.wordCount = NUM_LANES_G-1) or v.beatLast; + v.beatData := sAxisMaster.tData(i*32+31 downto i*32); -- Still filling current data if v.curMaster.tValid = '0' then - v.curMaster.tData(v.wordCount*32+31 downto v.wordCount*32) := data; + v.curMaster.tData(v.wordCount*32+31 downto v.wordCount*32) := v.beatData; v.curMaster.tKeep(v.wordCount*4+3 downto v.wordCount*4) := x"F"; - v.curMaster.tValid := valid; - v.curMaster.tLast := last; + v.curMaster.tValid := v.beatValid; + v.curMaster.tLast := v.beatLast; + if (v.beatLast = '1') then + ssiSetUserEofe(WIDE_AXIS_CONFIG_C, v.curMaster, ssiGetUserEofe(WIDE_AXIS_CONFIG_C, sAxisMaster)); + end if; -- Filling next data elsif v.nxtMaster.tValid = '0' then - v.nxtMaster.tData(v.wordCount*32+31 downto v.wordCount*32) := data; + v.nxtMaster.tData(v.wordCount*32+31 downto v.wordCount*32) := v.beatData; v.nxtMaster.tKeep(v.wordCount*4+3 downto v.wordCount*4) := x"F"; - v.nxtMaster.tValid := valid; - v.nxtMaster.tLast := last; + v.nxtMaster.tValid := v.beatValid; + v.nxtMaster.tLast := v.beatLast; + if (v.beatLast = '1') then + ssiSetUserEofe(WIDE_AXIS_CONFIG_C, v.nxtMaster, ssiGetUserEofe(WIDE_AXIS_CONFIG_C, sAxisMaster)); + end if; end if; - if v.wordCount = NUM_LANES_G-1 or last = '1' then + if v.wordCount = NUM_LANES_G-1 or v.beatLast = '1' then v.wordCount := 0; else v.wordCount := v.wordCount + 1; @@ -148,7 +172,7 @@ begin rin <= v; - mAxisMaster <= r.outMaster; + mAxisMaster <= r.curMaster; end process; @@ -160,4 +184,3 @@ begin end process; end rtl; - diff --git a/protocols/coaxpress/core/wrappers/CoaXPressCoreDebugWrapper.vhd b/protocols/coaxpress/core/wrappers/CoaXPressCoreDebugWrapper.vhd index 2d0d583bf7..2d097cb870 100644 --- a/protocols/coaxpress/core/wrappers/CoaXPressCoreDebugWrapper.vhd +++ b/protocols/coaxpress/core/wrappers/CoaXPressCoreDebugWrapper.vhd @@ -144,6 +144,7 @@ architecture rtl of CoaXPressCoreDebugWrapper is signal eventAck : sl; signal eventTag : slv(7 downto 0); + signal eventMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; signal trigAck : sl; signal txLsRateInt : sl; @@ -379,6 +380,8 @@ begin cfgClk => cfgClk, cfgRst => cfgRst, cfgRxMaster => cfgRxMaster, + eventMaster => eventMaster, + eventSlave => AXI_STREAM_SLAVE_FORCE_C, eventAck => eventAck, eventTag => eventTag, txClk => txClk, diff --git a/protocols/coaxpress/core/wrappers/CoaXPressOverFiberBridgeAxiLWrapper.vhd b/protocols/coaxpress/core/wrappers/CoaXPressOverFiberBridgeAxiLWrapper.vhd new file mode 100644 index 0000000000..f47954741b --- /dev/null +++ b/protocols/coaxpress/core/wrappers/CoaXPressOverFiberBridgeAxiLWrapper.vhd @@ -0,0 +1,150 @@ +------------------------------------------------------------------------------- +-- Company : SLAC National Accelerator Laboratory +------------------------------------------------------------------------------- +-- Description: Cocotb-facing wrapper for CoaXPressOverFiberBridgeAxiL +------------------------------------------------------------------------------- +-- 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.AxiLitePkg.all; +use surf.CoaXPressPkg.all; + +entity CoaXPressOverFiberBridgeAxiLWrapper is + port ( + -- Bridge RX status clock domain + rxClk : in sl; + rxRst : in sl; + rxError : in sl; + rxAbort : in sl; + rxErrorCode : in slv(3 downto 0); + seqValid : in sl; + seqData : in slv(23 downto 0); + seqError : in sl; + seqExpected : in slv(23 downto 0); + seqErrorExpected : in slv(23 downto 0); + hkpValid : in sl; + hkpData : in slv(31 downto 0); + hkpEop : in sl; + hkpSof : in sl; + hkpError : in sl; + hkpWordCount : in slv(7 downto 0); + hkpKCodeMask : in slv(3 downto 0); + hkpKCodeValid : in sl; + hkpType : in slv(3 downto 0); + -- AXI-Lite Register Interface + S_AXI_ACLK : in std_logic; + S_AXI_ARESETN : in std_logic; + S_AXI_AWADDR : in std_logic_vector(11 downto 0); + S_AXI_AWPROT : in std_logic_vector(2 downto 0); + S_AXI_AWVALID : in std_logic; + S_AXI_AWREADY : out std_logic; + S_AXI_WDATA : in std_logic_vector(31 downto 0); + S_AXI_WSTRB : in std_logic_vector(3 downto 0); + S_AXI_WVALID : in std_logic; + S_AXI_WREADY : out std_logic; + S_AXI_BRESP : out std_logic_vector(1 downto 0); + S_AXI_BVALID : out std_logic; + S_AXI_BREADY : in std_logic; + S_AXI_ARADDR : in std_logic_vector(11 downto 0); + S_AXI_ARPROT : in std_logic_vector(2 downto 0); + S_AXI_ARVALID : in std_logic; + S_AXI_ARREADY : out std_logic; + S_AXI_RDATA : out std_logic_vector(31 downto 0); + S_AXI_RRESP : out std_logic_vector(1 downto 0); + S_AXI_RVALID : out std_logic; + S_AXI_RREADY : in std_logic); +end entity CoaXPressOverFiberBridgeAxiLWrapper; + +architecture mapping of CoaXPressOverFiberBridgeAxiLWrapper is + + signal axilClk : sl; + signal axilRst : sl; + signal axilReadMaster : AxiLiteReadMasterType := AXI_LITE_READ_MASTER_INIT_C; + signal axilReadSlave : AxiLiteReadSlaveType := AXI_LITE_READ_SLAVE_INIT_C; + signal axilWriteMaster : AxiLiteWriteMasterType := AXI_LITE_WRITE_MASTER_INIT_C; + signal axilWriteSlave : AxiLiteWriteSlaveType := AXI_LITE_WRITE_SLAVE_INIT_C; + + signal rxStatus : CxpofRxStatusType := CXPOF_RX_STATUS_INIT_C; + +begin + + rxStatus.rxError <= rxError; + rxStatus.rxAbort <= rxAbort; + rxStatus.rxErrorCode <= rxErrorCode; + rxStatus.seqValid <= seqValid; + rxStatus.seqData <= seqData; + rxStatus.seqError <= seqError; + rxStatus.seqExpected <= seqExpected; + rxStatus.seqErrorExpected <= seqErrorExpected; + rxStatus.hkpValid <= hkpValid; + rxStatus.hkpData <= hkpData; + rxStatus.hkpEop <= hkpEop; + rxStatus.hkpSof <= hkpSof; + rxStatus.hkpError <= hkpError; + rxStatus.hkpWordCount <= hkpWordCount; + rxStatus.hkpKCodeMask <= hkpKCodeMask; + rxStatus.hkpKCodeValid <= hkpKCodeValid; + rxStatus.hkpType <= hkpType; + + U_ShimLayer : entity surf.SlaveAxiLiteIpIntegrator + generic map ( + INTERFACENAME => "S_AXI", + HAS_PROT => 1, + HAS_WSTRB => 1, + ADDR_WIDTH => 12) + port map ( + S_AXI_ACLK => S_AXI_ACLK, + S_AXI_ARESETN => S_AXI_ARESETN, + S_AXI_AWADDR => S_AXI_AWADDR, + S_AXI_AWPROT => S_AXI_AWPROT, + S_AXI_AWVALID => S_AXI_AWVALID, + S_AXI_AWREADY => S_AXI_AWREADY, + S_AXI_WDATA => S_AXI_WDATA, + S_AXI_WSTRB => S_AXI_WSTRB, + S_AXI_WVALID => S_AXI_WVALID, + S_AXI_WREADY => S_AXI_WREADY, + S_AXI_BRESP => S_AXI_BRESP, + S_AXI_BVALID => S_AXI_BVALID, + S_AXI_BREADY => S_AXI_BREADY, + S_AXI_ARADDR => S_AXI_ARADDR, + S_AXI_ARPROT => S_AXI_ARPROT, + S_AXI_ARVALID => S_AXI_ARVALID, + S_AXI_ARREADY => S_AXI_ARREADY, + S_AXI_RDATA => S_AXI_RDATA, + S_AXI_RRESP => S_AXI_RRESP, + S_AXI_RVALID => S_AXI_RVALID, + S_AXI_RREADY => S_AXI_RREADY, + axilClk => axilClk, + axilRst => axilRst, + axilReadMaster => axilReadMaster, + axilReadSlave => axilReadSlave, + axilWriteMaster => axilWriteMaster, + axilWriteSlave => axilWriteSlave); + + U_DUT : entity surf.CoaXPressOverFiberBridgeAxiL + generic map ( + TPD_G => 1 ns, + CNT_WIDTH_G => 16) + port map ( + rxClk => rxClk, + rxRst => rxRst, + rxStatus => rxStatus, + axilClk => axilClk, + axilRst => axilRst, + axilReadMaster => axilReadMaster, + axilReadSlave => axilReadSlave, + axilWriteMaster => axilWriteMaster, + axilWriteSlave => axilWriteSlave); + +end architecture mapping; diff --git a/protocols/coaxpress/core/wrappers/CoaXPressOverFiberBridgeRxStatusWrapper.vhd b/protocols/coaxpress/core/wrappers/CoaXPressOverFiberBridgeRxStatusWrapper.vhd new file mode 100644 index 0000000000..49b727188c --- /dev/null +++ b/protocols/coaxpress/core/wrappers/CoaXPressOverFiberBridgeRxStatusWrapper.vhd @@ -0,0 +1,93 @@ +------------------------------------------------------------------------------- +-- Company : SLAC National Accelerator Laboratory +------------------------------------------------------------------------------- +-- Description: Cocotb-facing status wrapper for CoaXPressOverFiberBridgeRx +------------------------------------------------------------------------------- +-- 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.CoaXPressPkg.all; + +entity CoaXPressOverFiberBridgeRxStatusWrapper is + port ( + -- Clock and Reset + clk : in sl; + rst : in sl; + -- XGMII interface + xgmiiRxd : in slv(31 downto 0); + xgmiiRxc : in slv(3 downto 0); + -- Rx PHY Interface + rxData : out slv(31 downto 0); + rxDataK : out slv(3 downto 0); + -- Flattened Status Interface + rxError : out sl; + rxAbort : out sl; + rxErrorCode : out slv(3 downto 0); + seqValid : out sl; + seqData : out slv(23 downto 0); + seqError : out sl; + seqExpected : out slv(23 downto 0); + seqErrorExpected : out slv(23 downto 0); + hkpValid : out sl; + hkpData : out slv(31 downto 0); + hkpEop : out sl; + hkpSof : out sl; + hkpError : out sl; + hkpWordCount : out slv(7 downto 0); + hkpKCodeMask : out slv(3 downto 0); + hkpKCodeValid : out sl; + hkpType : out slv(3 downto 0)); +end entity CoaXPressOverFiberBridgeRxStatusWrapper; + +architecture mapping of CoaXPressOverFiberBridgeRxStatusWrapper is + + signal rxStatus : CxpofRxStatusType := CXPOF_RX_STATUS_INIT_C; + +begin + + rxError <= rxStatus.rxError; + rxAbort <= rxStatus.rxAbort; + rxErrorCode <= rxStatus.rxErrorCode; + seqValid <= rxStatus.seqValid; + seqData <= rxStatus.seqData; + seqError <= rxStatus.seqError; + seqExpected <= rxStatus.seqExpected; + seqErrorExpected <= rxStatus.seqErrorExpected; + hkpValid <= rxStatus.hkpValid; + hkpData <= rxStatus.hkpData; + hkpEop <= rxStatus.hkpEop; + hkpSof <= rxStatus.hkpSof; + hkpError <= rxStatus.hkpError; + hkpWordCount <= rxStatus.hkpWordCount; + hkpKCodeMask <= rxStatus.hkpKCodeMask; + hkpKCodeValid <= rxStatus.hkpKCodeValid; + hkpType <= rxStatus.hkpType; + + U_DUT : entity surf.CoaXPressOverFiberBridgeRx + generic map ( + TPD_G => 1 ns) + port map ( + -- Clock and Reset + clk => clk, + rst => rst, + -- XGMII interface + xgmiiRxd => xgmiiRxd, + xgmiiRxc => xgmiiRxc, + -- Rx PHY Interface + rxData => rxData, + rxDataK => rxDataK, + -- Status Interface + rxStatus => rxStatus); + +end architecture mapping; diff --git a/protocols/coaxpress/core/wrappers/CoaXPressOverFiberBridgeStatusWrapper.vhd b/protocols/coaxpress/core/wrappers/CoaXPressOverFiberBridgeStatusWrapper.vhd new file mode 100644 index 0000000000..312bfe45b7 --- /dev/null +++ b/protocols/coaxpress/core/wrappers/CoaXPressOverFiberBridgeStatusWrapper.vhd @@ -0,0 +1,119 @@ +------------------------------------------------------------------------------- +-- Company : SLAC National Accelerator Laboratory +------------------------------------------------------------------------------- +-- Description: Cocotb-facing status wrapper for CoaXPressOverFiberBridge +------------------------------------------------------------------------------- +-- 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.CoaXPressPkg.all; + +entity CoaXPressOverFiberBridgeStatusWrapper is + generic ( + LANE0_G : boolean := true); + port ( + -- XGMII TX interface (txClk156 domain) + txClk156 : in sl; + xgmiiTxd : out slv(63 downto 0); + xgmiiTxc : out slv(7 downto 0); + -- XGMII RX interface (rxClk156 domain) + rxClk156 : in sl; + xgmiiRxd : in slv(63 downto 0); + xgmiiRxc : in slv(7 downto 0); + -- CXP TX interface (txClk312 domain) + txClk312 : in sl; + txRst312 : in sl; + txLsValid : in sl; + txLsData : in slv(7 downto 0); + txLsDataK : in sl; + txLsLaneEn : in slv(3 downto 0); + txLsRate : in sl; + -- CXP RX interface (rxClk312 domain) + rxClk312 : in sl; + rxRst312 : in sl; + rxData : out slv(31 downto 0); + rxDataK : out slv(3 downto 0); + -- Flattened Status Interface + rxError : out sl; + rxAbort : out sl; + rxErrorCode : out slv(3 downto 0); + seqValid : out sl; + seqData : out slv(23 downto 0); + seqError : out sl; + seqExpected : out slv(23 downto 0); + seqErrorExpected : out slv(23 downto 0); + hkpValid : out sl; + hkpData : out slv(31 downto 0); + hkpEop : out sl; + hkpSof : out sl; + hkpError : out sl; + hkpWordCount : out slv(7 downto 0); + hkpKCodeMask : out slv(3 downto 0); + hkpKCodeValid : out sl; + hkpType : out slv(3 downto 0)); +end entity CoaXPressOverFiberBridgeStatusWrapper; + +architecture mapping of CoaXPressOverFiberBridgeStatusWrapper is + + signal rxStatus : CxpofRxStatusType := CXPOF_RX_STATUS_INIT_C; + +begin + + rxError <= rxStatus.rxError; + rxAbort <= rxStatus.rxAbort; + rxErrorCode <= rxStatus.rxErrorCode; + seqValid <= rxStatus.seqValid; + seqData <= rxStatus.seqData; + seqError <= rxStatus.seqError; + seqExpected <= rxStatus.seqExpected; + seqErrorExpected <= rxStatus.seqErrorExpected; + hkpValid <= rxStatus.hkpValid; + hkpData <= rxStatus.hkpData; + hkpEop <= rxStatus.hkpEop; + hkpSof <= rxStatus.hkpSof; + hkpError <= rxStatus.hkpError; + hkpWordCount <= rxStatus.hkpWordCount; + hkpKCodeMask <= rxStatus.hkpKCodeMask; + hkpKCodeValid <= rxStatus.hkpKCodeValid; + hkpType <= rxStatus.hkpType; + + U_DUT : entity surf.CoaXPressOverFiberBridge + generic map ( + TPD_G => 1 ns, + LANE0_G => LANE0_G) + port map ( + -- XGMII TX interface (txClk156 domain) + txClk156 => txClk156, + xgmiiTxd => xgmiiTxd, + xgmiiTxc => xgmiiTxc, + -- XGMII RX interface (rxClk156 domain) + rxClk156 => rxClk156, + xgmiiRxd => xgmiiRxd, + xgmiiRxc => xgmiiRxc, + -- CXP TX interface (txClk312 domain) + txClk312 => txClk312, + txRst312 => txRst312, + txLsValid => txLsValid, + txLsData => txLsData, + txLsDataK => txLsDataK, + txLsLaneEn => txLsLaneEn, + txLsRate => txLsRate, + -- CXP RX interface (rxClk312 domain) + rxClk312 => rxClk312, + rxRst312 => rxRst312, + rxData => rxData, + rxDataK => rxDataK, + rxStatus => rxStatus); + +end architecture mapping; diff --git a/protocols/coaxpress/core/wrappers/CoaXPressRxCorePathWrapper.vhd b/protocols/coaxpress/core/wrappers/CoaXPressRxCorePathWrapper.vhd index f8b49f1a28..1f7c2100dc 100644 --- a/protocols/coaxpress/core/wrappers/CoaXPressRxCorePathWrapper.vhd +++ b/protocols/coaxpress/core/wrappers/CoaXPressRxCorePathWrapper.vhd @@ -54,6 +54,13 @@ entity CoaXPressRxCorePathWrapper is cfgTData : out slv(63 downto 0); cfgTKeep : out slv(7 downto 0); cfgTLast : out sl; + eventTValid : out sl; + eventTData : out slv(31 downto 0); + eventTKeep : out slv(3 downto 0); + eventTDest : out slv(7 downto 0); + eventTUser : out slv(31 downto 0); + eventTLast : out sl; + eventTReady : in sl; eventAck : out sl; eventTag : out slv(7 downto 0); trigAck : out sl; @@ -70,6 +77,8 @@ architecture rtl of CoaXPressRxCorePathWrapper is signal imageHdrMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; signal imageHdrSlave : AxiStreamSlaveType := AXI_STREAM_SLAVE_FORCE_C; signal cfgRxMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; + signal eventMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; + signal eventSlave : AxiStreamSlaveType := AXI_STREAM_SLAVE_FORCE_C; signal rxClkVec : slv(NUM_LANES_G-1 downto 0); signal rxRstVec : slv(NUM_LANES_G-1 downto 0); @@ -98,6 +107,14 @@ begin cfgTKeep <= cfgRxMaster.tKeep(7 downto 0); cfgTLast <= cfgRxMaster.tLast; + eventSlave.tReady <= eventTReady; + eventTValid <= eventMaster.tValid; + eventTData <= eventMaster.tData(31 downto 0); + eventTKeep <= eventMaster.tKeep(3 downto 0); + eventTDest <= eventMaster.tDest(7 downto 0); + eventTUser <= eventMaster.tUser(31 downto 0); + eventTLast <= eventMaster.tLast; + U_Data : entity surf.MasterAxiStreamIpIntegrator generic map ( INTERFACENAME => "M_DATA", @@ -170,6 +187,8 @@ begin cfgClk => cfgClk, cfgRst => cfgRst, cfgRxMaster => cfgRxMaster, + eventMaster => eventMaster, + eventSlave => eventSlave, eventAck => eventAck, eventTag => eventTag, txClk => txClk, diff --git a/protocols/coaxpress/core/wrappers/CoaXPressRxHsFsmWrapper.vhd b/protocols/coaxpress/core/wrappers/CoaXPressRxHsFsmWrapper.vhd index 184b114452..0ac9f64129 100644 --- a/protocols/coaxpress/core/wrappers/CoaXPressRxHsFsmWrapper.vhd +++ b/protocols/coaxpress/core/wrappers/CoaXPressRxHsFsmWrapper.vhd @@ -19,6 +19,7 @@ library surf; use surf.StdRtlPkg.all; use surf.AxiStreamPkg.all; use surf.SsiPkg.all; +use surf.CoaXPressPkg.all; entity CoaXPressRxHsFsmWrapper is generic ( @@ -31,6 +32,7 @@ entity CoaXPressRxHsFsmWrapper is sAxisTValid : in sl; sAxisTData : in slv(32*NUM_LANES_G-1 downto 0); sAxisTKeep : in slv(4*NUM_LANES_G-1 downto 0); + sAxisTUser : in slv(CXP_RX_STREAM_TUSER_BITS_C-1 downto 0); sAxisTLast : in sl; sAxisTReady : out sl; hdrTValid : out sl; @@ -54,13 +56,14 @@ architecture rtl of CoaXPressRxHsFsmWrapper is begin -- Present the flattened source beat as one wide AXI-stream record. - sAxisComb : process (sAxisTData, sAxisTKeep, sAxisTLast, sAxisTValid) is + sAxisComb : process (sAxisTData, sAxisTKeep, sAxisTLast, sAxisTUser, sAxisTValid) is variable v : AxiStreamMasterType; begin v := AXI_STREAM_MASTER_INIT_C; v.tValid := sAxisTValid; v.tData(32*NUM_LANES_G-1 downto 0) := sAxisTData; v.tKeep(4*NUM_LANES_G-1 downto 0) := sAxisTKeep; + v.tUser(CXP_RX_STREAM_TUSER_BITS_C-1 downto 0) := sAxisTUser; v.tLast := sAxisTLast; sAxisMaster <= v; end process sAxisComb; diff --git a/protocols/coaxpress/core/wrappers/CoaXPressRxLaneMuxWrapper.vhd b/protocols/coaxpress/core/wrappers/CoaXPressRxLaneMuxWrapper.vhd index 69785e1578..1a4ba721d5 100644 --- a/protocols/coaxpress/core/wrappers/CoaXPressRxLaneMuxWrapper.vhd +++ b/protocols/coaxpress/core/wrappers/CoaXPressRxLaneMuxWrapper.vhd @@ -18,6 +18,7 @@ use ieee.std_logic_1164.all; library surf; use surf.StdRtlPkg.all; use surf.AxiStreamPkg.all; +use surf.CoaXPressPkg.all; entity CoaXPressRxLaneMuxWrapper is generic ( @@ -30,6 +31,7 @@ entity CoaXPressRxLaneMuxWrapper is sAxisTValid : in slv(NUM_LANES_G-1 downto 0); sAxisTData : in slv(32*NUM_LANES_G*NUM_LANES_G-1 downto 0); sAxisTKeep : in slv(4*NUM_LANES_G*NUM_LANES_G-1 downto 0); + sAxisTUser : in slv(CXP_RX_STREAM_TUSER_BITS_C*NUM_LANES_G-1 downto 0); sAxisTLast : in slv(NUM_LANES_G-1 downto 0); sAxisTReady : out slv(NUM_LANES_G-1 downto 0); mAxisTValid : out sl; @@ -49,7 +51,7 @@ architecture rtl of CoaXPressRxLaneMuxWrapper is begin -- Rebuild the per-lane record array from the concatenated cocotb ports. - sAxisComb : process (sAxisTData, sAxisTKeep, sAxisTLast, sAxisTValid) is + sAxisComb : process (sAxisTData, sAxisTKeep, sAxisTLast, sAxisTUser, sAxisTValid) is variable masters : AxiStreamMasterArray(NUM_LANES_G-1 downto 0); begin masters := (others => AXI_STREAM_MASTER_INIT_C); @@ -59,6 +61,8 @@ begin sAxisTData(32*NUM_LANES_G*(i+1)-1 downto 32*NUM_LANES_G*i); masters(i).tKeep(4*NUM_LANES_G-1 downto 0) := sAxisTKeep(4*NUM_LANES_G*(i+1)-1 downto 4*NUM_LANES_G*i); + masters(i).tUser(CXP_RX_STREAM_TUSER_BITS_C-1 downto 0) := + sAxisTUser(CXP_RX_STREAM_TUSER_BITS_C*(i+1)-1 downto CXP_RX_STREAM_TUSER_BITS_C*i); masters(i).tLast := sAxisTLast(i); end loop; rxMasters <= masters; diff --git a/protocols/coaxpress/core/wrappers/CoaXPressRxLaneWrapper.vhd b/protocols/coaxpress/core/wrappers/CoaXPressRxLaneWrapper.vhd index 1da89a8fde..5b1badfd93 100644 --- a/protocols/coaxpress/core/wrappers/CoaXPressRxLaneWrapper.vhd +++ b/protocols/coaxpress/core/wrappers/CoaXPressRxLaneWrapper.vhd @@ -30,14 +30,21 @@ entity CoaXPressRxLaneWrapper is cfgTData : out slv(63 downto 0); dataTValid : out sl; dataTData : out slv(31 downto 0); + dataTKeep : out slv(3 downto 0); dataTUser : out slv(3 downto 0); dataTLast : out sl; heartbeatTValid : out sl; heartbeatTData : out slv(95 downto 0); heartbeatTLast : out sl; + eventTValid : out sl; + eventTData : out slv(31 downto 0); + eventTDest : out slv(7 downto 0); + eventTUser : out slv(31 downto 0); + eventTLast : out sl; ioAck : out sl; eventAck : out sl; - eventTag : out slv(7 downto 0)); + eventTag : out slv(7 downto 0); + rxError : out sl); end entity CoaXPressRxLaneWrapper; architecture rtl of CoaXPressRxLaneWrapper is @@ -45,6 +52,7 @@ architecture rtl of CoaXPressRxLaneWrapper is signal cfgMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; signal dataMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; signal heartbeatMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; + signal eventMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; signal imageHdrMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; begin @@ -54,11 +62,17 @@ begin cfgTData <= cfgMaster.tData(63 downto 0); dataTValid <= dataMaster.tValid; dataTData <= dataMaster.tData(31 downto 0); + dataTKeep <= dataMaster.tKeep(3 downto 0); dataTUser <= dataMaster.tUser(3 downto 0); dataTLast <= dataMaster.tLast; heartbeatTValid <= heartbeatMaster.tValid; heartbeatTData <= heartbeatMaster.tData(95 downto 0); heartbeatTLast <= heartbeatMaster.tLast; + eventTValid <= eventMaster.tValid; + eventTData <= eventMaster.tData(31 downto 0); + eventTDest <= eventMaster.tDest(7 downto 0); + eventTUser <= eventMaster.tUser(31 downto 0); + eventTLast <= eventMaster.tLast; -- Instantiate the real receive-lane decoder with the flattened ports. U_DUT : entity surf.CoaXPressRxLane @@ -70,10 +84,12 @@ begin cfgMaster => cfgMaster, dataMaster => dataMaster, heatbeatMaster => heartbeatMaster, + eventMaster => eventMaster, imageHdrMaster => imageHdrMaster, ioAck => ioAck, eventAck => eventAck, eventTag => eventTag, + rxError => rxError, rxData => rxData, rxDataK => rxDataK, rxLinkUp => rxLinkUp); diff --git a/protocols/coaxpress/core/wrappers/CoaXPressRxWordPackerWrapper.vhd b/protocols/coaxpress/core/wrappers/CoaXPressRxWordPackerWrapper.vhd index 1b2d092b8c..502599fc5e 100644 --- a/protocols/coaxpress/core/wrappers/CoaXPressRxWordPackerWrapper.vhd +++ b/protocols/coaxpress/core/wrappers/CoaXPressRxWordPackerWrapper.vhd @@ -38,6 +38,7 @@ end entity CoaXPressRxWordPackerWrapper; architecture rtl of CoaXPressRxWordPackerWrapper is signal sAxisMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; + signal sAxisSlave : AxiStreamSlaveType := AXI_STREAM_SLAVE_INIT_C; signal mAxisMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; begin @@ -69,6 +70,8 @@ begin rxClk => rxClk, rxRst => rxRst, sAxisMaster => sAxisMaster, - mAxisMaster => mAxisMaster); + sAxisSlave => sAxisSlave, + mAxisMaster => mAxisMaster, + mAxisSlave => AXI_STREAM_SLAVE_FORCE_C); end architecture rtl; diff --git a/protocols/coaxpress/core/wrappers/CoaXPressRxWrapper.vhd b/protocols/coaxpress/core/wrappers/CoaXPressRxWrapper.vhd index 46e79717a9..1e90c9d1b0 100644 --- a/protocols/coaxpress/core/wrappers/CoaXPressRxWrapper.vhd +++ b/protocols/coaxpress/core/wrappers/CoaXPressRxWrapper.vhd @@ -54,6 +54,13 @@ entity CoaXPressRxWrapper is cfgTData : out slv(63 downto 0); cfgTKeep : out slv(7 downto 0); cfgTLast : out sl; + eventTValid : out sl; + eventTData : out slv(31 downto 0); + eventTKeep : out slv(3 downto 0); + eventTDest : out slv(7 downto 0); + eventTUser : out slv(31 downto 0); + eventTLast : out sl; + eventTReady : in sl; eventAck : out sl; eventTag : out slv(7 downto 0); trigAck : out sl; @@ -70,6 +77,8 @@ architecture rtl of CoaXPressRxWrapper is signal imageHdrMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; signal imageHdrSlave : AxiStreamSlaveType := AXI_STREAM_SLAVE_FORCE_C; signal cfgRxMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; + signal eventMaster : AxiStreamMasterType := AXI_STREAM_MASTER_INIT_C; + signal eventSlave : AxiStreamSlaveType := AXI_STREAM_SLAVE_FORCE_C; signal rxClkVec : slv(NUM_LANES_G-1 downto 0); signal rxRstVec : slv(NUM_LANES_G-1 downto 0); @@ -90,12 +99,13 @@ begin dataSlave.tReady <= dataTReady; imageHdrSlave.tReady <= hdrTReady; + eventSlave.tReady <= eventTReady; dataTValid <= dataMaster.tValid; dataTData <= dataMaster.tData(31 downto 0); dataTKeep <= dataMaster.tKeep(3 downto 0); dataTLast <= dataMaster.tLast; - dataTUser <= dataMaster.tUser(0 downto 0); + dataTUser(0) <= ssiGetUserEofe(AXIS_CONFIG_C, dataMaster); hdrTValid <= imageHdrMaster.tValid; hdrTData <= imageHdrMaster.tData(31 downto 0); @@ -108,6 +118,13 @@ begin cfgTKeep <= cfgRxMaster.tKeep(7 downto 0); cfgTLast <= cfgRxMaster.tLast; + eventTValid <= eventMaster.tValid; + eventTData <= eventMaster.tData(31 downto 0); + eventTKeep <= eventMaster.tKeep(3 downto 0); + eventTDest <= eventMaster.tDest(7 downto 0); + eventTUser <= eventMaster.tUser(31 downto 0); + eventTLast <= eventMaster.tLast; + U_DUT : entity surf.CoaXPressRx generic map ( TPD_G => 1 ns, @@ -124,6 +141,8 @@ begin cfgClk => cfgClk, cfgRst => cfgRst, cfgRxMaster => cfgRxMaster, + eventMaster => eventMaster, + eventSlave => eventSlave, eventAck => eventAck, eventTag => eventTag, txClk => txClk, diff --git a/protocols/coaxpress/gthUs+/rtl/CoaXPressOverFiberGthUsIpWrapper.vhd b/protocols/coaxpress/gthUs+/rtl/CoaXPressOverFiberGthUsIpWrapper.vhd index a400f7e691..958d2984ce 100755 --- a/protocols/coaxpress/gthUs+/rtl/CoaXPressOverFiberGthUsIpWrapper.vhd +++ b/protocols/coaxpress/gthUs+/rtl/CoaXPressOverFiberGthUsIpWrapper.vhd @@ -55,7 +55,7 @@ entity CoaXPressOverFiberGthUsIpWrapper is rxDispErr : out sl := '0'; rxDecErr : out sl := '0'; rxLinkUp : out sl; - -- AXI-Lite DRP Interface + -- AXI-Lite Bridge Status Interface axilClk : in sl; axilRst : in sl; axilReadMaster : in AxiLiteReadMasterType; @@ -156,6 +156,7 @@ architecture mapping of CoaXPressOverFiberGthUsIpWrapper is signal xgmiiRxc : slv(7 downto 0); signal gtResetAll : sl; + signal bridgeRxStatus : CxpofRxStatusType; begin @@ -357,6 +358,21 @@ begin rxClk312 => phyClk312, rxRst312 => phyRst312, rxData => rxData, - rxDataK => rxDataK); + rxDataK => rxDataK, + rxStatus => bridgeRxStatus); + + U_BridgeAxiL : entity surf.CoaXPressOverFiberBridgeAxiL + generic map ( + TPD_G => TPD_G) + port map ( + rxClk => phyClk312, + rxRst => phyRst312, + rxStatus => bridgeRxStatus, + axilClk => axilClk, + axilRst => axilRst, + axilReadMaster => axilReadMaster, + axilReadSlave => axilReadSlave, + axilWriteMaster => axilWriteMaster, + axilWriteSlave => axilWriteSlave); end mapping; diff --git a/protocols/coaxpress/gthUs/rtl/CoaXPressOverFiberGthUsIpWrapper.vhd b/protocols/coaxpress/gthUs/rtl/CoaXPressOverFiberGthUsIpWrapper.vhd index a400f7e691..958d2984ce 100755 --- a/protocols/coaxpress/gthUs/rtl/CoaXPressOverFiberGthUsIpWrapper.vhd +++ b/protocols/coaxpress/gthUs/rtl/CoaXPressOverFiberGthUsIpWrapper.vhd @@ -55,7 +55,7 @@ entity CoaXPressOverFiberGthUsIpWrapper is rxDispErr : out sl := '0'; rxDecErr : out sl := '0'; rxLinkUp : out sl; - -- AXI-Lite DRP Interface + -- AXI-Lite Bridge Status Interface axilClk : in sl; axilRst : in sl; axilReadMaster : in AxiLiteReadMasterType; @@ -156,6 +156,7 @@ architecture mapping of CoaXPressOverFiberGthUsIpWrapper is signal xgmiiRxc : slv(7 downto 0); signal gtResetAll : sl; + signal bridgeRxStatus : CxpofRxStatusType; begin @@ -357,6 +358,21 @@ begin rxClk312 => phyClk312, rxRst312 => phyRst312, rxData => rxData, - rxDataK => rxDataK); + rxDataK => rxDataK, + rxStatus => bridgeRxStatus); + + U_BridgeAxiL : entity surf.CoaXPressOverFiberBridgeAxiL + generic map ( + TPD_G => TPD_G) + port map ( + rxClk => phyClk312, + rxRst => phyRst312, + rxStatus => bridgeRxStatus, + axilClk => axilClk, + axilRst => axilRst, + axilReadMaster => axilReadMaster, + axilReadSlave => axilReadSlave, + axilWriteMaster => axilWriteMaster, + axilWriteSlave => axilWriteSlave); end mapping; diff --git a/protocols/coaxpress/gtyUs+/rtl/CoaXPressOverFiberGtyUsIpWrapper.vhd b/protocols/coaxpress/gtyUs+/rtl/CoaXPressOverFiberGtyUsIpWrapper.vhd index bc78349c51..25ffe225c1 100755 --- a/protocols/coaxpress/gtyUs+/rtl/CoaXPressOverFiberGtyUsIpWrapper.vhd +++ b/protocols/coaxpress/gtyUs+/rtl/CoaXPressOverFiberGtyUsIpWrapper.vhd @@ -55,7 +55,7 @@ entity CoaXPressOverFiberGtyUsIpWrapper is rxDispErr : out sl := '0'; rxDecErr : out sl := '0'; rxLinkUp : out sl; - -- AXI-Lite DRP Interface + -- AXI-Lite Bridge Status Interface axilClk : in sl; axilRst : in sl; axilReadMaster : in AxiLiteReadMasterType; @@ -156,6 +156,7 @@ architecture mapping of CoaXPressOverFiberGtyUsIpWrapper is signal xgmiiRxc : slv(7 downto 0); signal gtResetAll : sl; + signal bridgeRxStatus : CxpofRxStatusType; begin @@ -357,6 +358,21 @@ begin rxClk312 => phyClk312, rxRst312 => phyRst312, rxData => rxData, - rxDataK => rxDataK); + rxDataK => rxDataK, + rxStatus => bridgeRxStatus); + + U_BridgeAxiL : entity surf.CoaXPressOverFiberBridgeAxiL + generic map ( + TPD_G => TPD_G) + port map ( + rxClk => phyClk312, + rxRst => phyRst312, + rxStatus => bridgeRxStatus, + axilClk => axilClk, + axilRst => axilRst, + axilReadMaster => axilReadMaster, + axilReadSlave => axilReadSlave, + axilWriteMaster => axilWriteMaster, + axilWriteSlave => axilWriteSlave); end mapping; diff --git a/python/surf/protocols/coaxpress/_Bootstrap.py b/python/surf/protocols/coaxpress/_Bootstrap.py index b34f72b65d..2966faa7a3 100644 --- a/python/surf/protocols/coaxpress/_Bootstrap.py +++ b/python/surf/protocols/coaxpress/_Bootstrap.py @@ -13,11 +13,20 @@ import time class Bootstrap(pr.Device): - def __init__(self, GenDc=False, CoaXPressAxiL=None, **kwargs): + _HOST_VERSION_BITS = ( + (0x00020001, 'Version2p1Supported', 'v2.1'), + (0x00020000, 'Version2p0Supported', 'v2.0'), + (0x00010001, 'Version1p1Supported', 'v1.1'), + (0x00010000, 'Version1p0Supported', 'v1.0'), + ) + + def __init__(self, GenDc=False, CoaXPressAxiL=None, simpleDiscovery=False, **kwargs): super().__init__(**kwargs) rogue.Version.minVersion('6.13.0') + self._simpleDiscovery = simpleDiscovery + self.CoaXPressAxiL = CoaXPressAxiL # Default write guard: no-op until setAcquisitionMonitor() provides a camera. @@ -362,7 +371,8 @@ def _write_guard(path, value, state): description = 'This register shall hold the maximum stream packet size the Host can accept. The size is defined in bytes, and shall be a multiple of 4 bytes. The Device can use any packet size it wants to up to this size. The defined size is that of the entire packet, not just the payload.', offset = 0x00004010, base = pr.UIntBE, - mode = 'RW', + mode = 'RO' if simpleDiscovery else 'RW', + verify = False, disp = '{:d}', )) @@ -604,15 +614,16 @@ def _write_guard(path, value, state): mode = 'RO', )) - self.add(pr.RemoteVariable( - name = 'VersionUsedCmd', - description = 'This register shall hold a valid combination of the Device connection speed and number of active downconnections. Writing to this register shall set the connection speeds on the specified connections. It may also result in a corresponding speed change of the low speed upconnection.', - offset = 0x00004048, - base = pr.UIntBE, - mode = 'WO', - hidden = True, - overlapEn = True, - )) + if not simpleDiscovery: + self.add(pr.RemoteVariable( + name = 'VersionUsedCmd', + description = 'This register shall hold a valid combination of the Device connection speed and number of active downconnections. Writing to this register shall set the connection speeds on the specified connections. It may also result in a corresponding speed change of the low speed upconnection.', + offset = 0x00004048, + base = pr.UIntBE, + mode = 'WO', + hidden = True, + overlapEn = True, + )) self.add(pr.RemoteVariable( name = 'MajorVersionUsed', @@ -656,6 +667,39 @@ def setAcquisitionMonitor(self, camera): """ self._acq_var = camera.IsAcquiring + def _negotiateVersionUsed(self): + """Return the CoaXPress version value selected during discovery. + + CXP v1.x devices keep the ConnectionReset default and may not accept a + write to VersionUsed. For v2.x and later, the host must choose a common + version from VersionsSupported before switching to tagged packets. + + Some devices implicitly negotiate after ConnectionReset, so we check + the read-back first and only write if the version doesn't match. + If the write fails (some cameras don't ACK), fall back to whatever + version the camera already reports. + """ + revision = self.Revision.value() + + if revision < 0x00020000: + return revision + + current = (self.MajorVersionUsed.value() << 16) | self.MinorVersionUsed.value() + + for value, bit_name, _ in self._HOST_VERSION_BITS: + if getattr(self, bit_name).value(): + if current != value: + try: + self.VersionUsedCmd.set(value) + except Exception: + return current + return value + + raise RuntimeError( + f"Device reports revision 0x{revision:08X}, but no common " + "CoaXPress protocol version is set in VersionsSupported" + ) + def DeviceDiscovery(self, arg=None): # Updates all the local device register values self.CoaXPressAxiL.readBlocks(recurse=True) @@ -685,19 +729,43 @@ def DeviceDiscovery(self, arg=None): "Try power cycling the camera and verifying all connections before restarting the software." ) from e - # Match the device revision to host revision - self.VersionUsedCmd.set(self.Revision.value()) - - # The Host shall read the ConnectionConfigDefault register to find the required bit rate - # and number of connections operating at this bit rate - ConnectionConfigDefault = arg if arg is not None else self.ConnectionConfigDefault.value() - self.ConnectionConfig.set(ConnectionConfigDefault) - - # If the new high speed connection bit rate requires a change in low speed connection bit rate, - # it shall also change the low speed upconnection speed to the value defined in Table 6. - if (ConnectionConfigDefault & 0xFF) >= 0x50: - # Switch to 41.66 Mb/s mode - self.CoaXPressAxiL.TxLsRate.set(1) + if self._simpleDiscovery: + # Simple discovery: no version negotiation, no tags. + # Still must write ConnectionConfig to activate all downconnection lanes. + ConnectionConfigDefault = arg if arg is not None else self.ConnectionConfigDefault.value() + try: + self.ConnectionConfig.set(ConnectionConfigDefault) + except Exception: + pass + + if (ConnectionConfigDefault & 0xFF) >= 0x50: + self.CoaXPressAxiL.TxLsRate.set(1) + + else: + # Negotiate the CXP protocol version before using later bootstrap + # writes. CXP v1.x stays on the reset default; v2.x switches to tags. + version_used = self._negotiateVersionUsed() + self.CoaXPressAxiL.ConfigPktTag.set(1 if version_used >= 0x00020000 else 0) + + # The Host shall read the ConnectionConfigDefault register to find the required bit rate + # and number of connections operating at this bit rate + ConnectionConfigDefault = arg if arg is not None else self.ConnectionConfigDefault.value() + try: + self.ConnectionConfig.set(ConnectionConfigDefault) + except Exception: + pass + + # If the new high speed connection bit rate requires a change in low speed connection bit rate, + # it shall also change the low speed upconnection speed to the value defined in Table 6. + if (ConnectionConfigDefault & 0xFF) >= 0x50: + # Switch to 41.66 Mb/s mode + self.CoaXPressAxiL.TxLsRate.set(1) + + # Setup for 4KB packets + try: + self.StreamPacketSizeMax.set(4096) + except Exception: + pass # After it is sending a stable low speed upconnection at the defined rate the Host # shall wait 200ms to allow the Device to complete connection re-configuration. @@ -709,6 +777,3 @@ def DeviceDiscovery(self, arg=None): # Reset the RX lane index pointer and flush the elastic buffers self.CoaXPressAxiL.RxFsmRst() - - # Setup for 4KB packets - self.StreamPacketSizeMax.set(4096) diff --git a/python/surf/protocols/coaxpress/_CoaXPressOverFiberBridgeAxiL.py b/python/surf/protocols/coaxpress/_CoaXPressOverFiberBridgeAxiL.py new file mode 100644 index 0000000000..1b564cf606 --- /dev/null +++ b/python/surf/protocols/coaxpress/_CoaXPressOverFiberBridgeAxiL.py @@ -0,0 +1,177 @@ +#----------------------------------------------------------------------------- +# 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. +#----------------------------------------------------------------------------- + +import pyrogue as pr + + +class CoaXPressOverFiberBridgeAxiL(pr.Device): + def __init__(self, + statusCountBits = 16, + **kwargs): + super().__init__(**kwargs) + + rxErrorCodeEnum = { + 0x0: 'None', + 0x1: 'SeqMismatch', + 0x2: 'IdleError', + 0x3: 'PayloadAbort', + 0x4: 'BadControl', + 0x5: 'Overwrite', + 0x6: 'HkpMalformed', + 0x7: 'HkpBadKCode', + } + + hkpTypeEnum = { + 0x0: 'None', + 0x1: 'KCode', + 0x2: 'SOP', + 0x3: 'EOP', + 0x4: 'Trigger', + 0x5: 'IoAck', + 0x6: 'Marker', + 0xF: 'Invalid', + } + + statusBits = [ + ('RxError', 'Bridge RX error pulse'), + ('RxAbort', 'Bridge RX abort pulse'), + ('SeqValid', 'Received CXPoF /Q/ sequence ordered set'), + ('SeqError', 'CXPoF /Q/ sequence mismatch'), + ('HkpValid', 'Received CXPoF HKP word'), + ('HkpError', 'Malformed CXPoF HKP word'), + ] + + for bit, (name, description) in enumerate(statusBits): + self.add(pr.RemoteVariable( + name = f'{name}Sticky', + description = f'Sticky status bit: {description}', + offset = 0x000, + bitSize = 1, + bitOffset = bit, + mode = 'RO', + base = pr.Bool, + pollInterval = 1, + )) + + self.add(pr.RemoteVariable( + name = 'RxErrorCode', + description = 'Last bridge RX error cause code', + offset = 0x004, + bitSize = 4, + mode = 'RO', + enum = rxErrorCodeEnum, + pollInterval = 1, + )) + + self.add(pr.RemoteVariable( + name = 'SeqData', + description = 'Last received CXPoF /Q/ sequence value', + offset = 0x008, + bitSize = 24, + mode = 'RO', + disp = '0x{:06X}', + pollInterval = 1, + )) + + self.add(pr.RemoteVariable( + name = 'SeqExpected', + description = 'Next expected CXPoF /Q/ sequence value', + offset = 0x00C, + bitSize = 24, + mode = 'RO', + disp = '0x{:06X}', + pollInterval = 1, + )) + + self.add(pr.RemoteVariable( + name = 'SeqErrorExpected', + description = 'Expected CXPoF /Q/ sequence value captured on mismatch', + offset = 0x010, + bitSize = 24, + mode = 'RO', + disp = '0x{:06X}', + pollInterval = 1, + )) + + self.add(pr.RemoteVariable( + name = 'HkpData', + description = 'Last received CXPoF High-Speed K-Code Payload word', + offset = 0x014, + bitSize = 32, + mode = 'RO', + disp = '0x{:08X}', + pollInterval = 1, + )) + + self.add(pr.RemoteVariable( + name = 'HkpWordCount', + description = 'Word count of the last received CXPoF HKP word', + offset = 0x018, + bitSize = 8, + bitOffset = 0, + mode = 'RO', + disp = '{:d}', + pollInterval = 1, + )) + + self.add(pr.RemoteVariable( + name = 'HkpKCodeMask', + description = 'Per-byte CXP K-code mask for the last received HKP word', + offset = 0x018, + bitSize = 4, + bitOffset = 8, + mode = 'RO', + disp = '0x{:X}', + pollInterval = 1, + )) + + self.add(pr.RemoteVariable( + name = 'HkpKCodeValid', + description = 'True when every byte of the last received HKP word is a CXP K-code', + offset = 0x018, + bitSize = 1, + bitOffset = 12, + mode = 'RO', + base = pr.Bool, + pollInterval = 1, + )) + + self.add(pr.RemoteVariable( + name = 'HkpType', + description = 'Classified type of the last received HKP K-code word', + offset = 0x018, + bitSize = 4, + bitOffset = 16, + mode = 'RO', + enum = hkpTypeEnum, + pollInterval = 1, + )) + + for i, (name, description) in enumerate(statusBits): + self.add(pr.RemoteVariable( + name = f'{name}Cnt', + description = f'Counter for {description}', + offset = 0x020 + (4*i), + bitSize = statusCountBits, + mode = 'RO', + disp = '{:d}', + pollInterval = 1, + )) + + self.add(pr.RemoteCommand( + name = 'CountReset', + description = 'Reset bridge RX sticky status and counters', + offset = 0x03C, + bitSize = 1, + function = pr.BaseCommand.touchOne, + )) + + def countReset(self): + self.CountReset() diff --git a/python/surf/protocols/coaxpress/__init__.py b/python/surf/protocols/coaxpress/__init__.py index d90f51e984..e41d5c94d1 100644 --- a/python/surf/protocols/coaxpress/__init__.py +++ b/python/surf/protocols/coaxpress/__init__.py @@ -8,6 +8,7 @@ ## the terms contained in the LICENSE.txt file. ############################################################################## from surf.protocols.coaxpress._CoaXPressAxiL import * +from surf.protocols.coaxpress._CoaXPressOverFiberBridgeAxiL import * from surf.protocols.coaxpress._Bootstrap import * from surf.protocols.coaxpress._PhantomS991 import * diff --git a/tests/protocols/coaxpress/README.md b/tests/protocols/coaxpress/README.md index f67dab1496..6b360e03e1 100644 --- a/tests/protocols/coaxpress/README.md +++ b/tests/protocols/coaxpress/README.md @@ -49,19 +49,20 @@ intentional limitation, not as silent proof of complete spec compliance. | Test file | DUT surface | Main spec relation | Status | | --- | --- | --- | --- | -| `test_CoaXPressRxWordPacker.py` | `CoaXPressRxWordPacker` | Internal packing helper for receive-path word assembly; not a direct protocol-surface spec bench | RTL-contract | -| `test_CoaXPressRxLaneMux.py` | `CoaXPressRxLaneMux` | Internal lane arbitration and frame-boundary behavior; not a direct protocol-surface spec bench | RTL-contract | -| `test_CoaXPressRxLane.py` | `CoaXPressRxLane` | `CXP-001-2021` packet-type decode, `IO_ACK`, control acknowledgments, heartbeat prefix handling, truncated-event guardrails, stream header fields | Partial protocol | -| `test_CoaXPressRxHsFsm.py` | `CoaXPressRxHsFsm` | Rectangular image header and line marker handling from section `10.4.6.2` / `10.4.6.3`, including a dual-lane step/alignment case | Near-normative subset | -| `test_CoaXPressRx.py` | `CoaXPressRx` | One-lane control/event assembly plus dual-lane receive rotation/alignment through the lane mux and HS FSM | Partial protocol | +| `test_CoaXPressRxWordPacker.py` | `CoaXPressRxWordPacker` | Internal packing helper for receive-path word assembly, including handshake hold behavior and SSI `EOFE` propagation; not a direct protocol-surface spec bench | RTL-contract | +| `test_CoaXPressRxLaneMux.py` | `CoaXPressRxLaneMux` | Internal lane arbitration and trailer-marker-gated frame-boundary behavior; not a direct protocol-surface spec bench | RTL-contract | +| `test_CoaXPressRxLane.py` | `CoaXPressRxLane` | `CXP-001-2021` packet-type decode, `IO_ACK`, control acknowledgments, heartbeat payload/trailer handling, bounded event payload validate-before-release plus trailer-gated ACK, stream header/trailer framing, and malformed-packet `rxError` pulses | Partial protocol | +| `test_CoaXPressRxHsFsm.py` | `CoaXPressRxHsFsm` | Rectangular image header and line marker handling from section `10.4.6.2` / `10.4.6.3`, including dual-lane step/alignment, incomplete-frame new-header detection, and trailer-verdict-gated SSI `EOFE` on the final image beat | Near-normative subset | +| `test_CoaXPressRx.py` | `CoaXPressRx` | One-lane control/event-payload assembly plus multi-lane receive rotation/alignment through the lane mux and HS FSM, including malformed stream-trailer `EOFE` recovery | Partial protocol | | `test_CoaXPressEventAckMsg.py` | `CoaXPressEventAckMsg` | Event acknowledgment wire format, section `9.8.3`, Table 30 | Near-normative subset | | `test_CoaXPressTxLsFsm.py` | `CoaXPressTxLsFsm` | Low-speed idle cadence and default trigger serialization, section `9.3.1.1` / Table 15 | Partial protocol | | `test_CoaXPressTx.py` | `CoaXPressTx` | Control/event-acknowledgment arbitration and software-trigger path across the TX assembly | RTL-contract with spec packet classes | -| `test_CoaXPressConfig.py` | `CoaXPressConfig` | Control command packet formatting, CRC generation, tag handling, and SRPv3 response completion through the real `SrpV3AxiLite` ingress path, section `9.6.1.2` / `9.6.2` | Near-normative subset | +| `test_CoaXPressConfig.py` | `CoaXPressConfig` | Control command packet formatting, CRC generation, tag handling, timeout/status-error responses, and SRPv3 response completion through the real `SrpV3AxiLite` ingress path, section `9.6.1.2` / `9.6.2` | Near-normative subset | | `test_CoaXPressCore.py` | `CoaXPressCore` | AXI-Lite control of tagged config request generation plus software-visible `RxOverflowCnt` / `RxFsmErrorCnt` status behavior at the full-core boundary | RTL-contract with spec request prefix and top-level error-status checks | | `test_CoaXPressOverFiberBridgeTx.py` | `CoaXPressOverFiberBridgeTx` | CXPoF start/control/payload/terminate words, section `6.3.1` to `6.3.6` in `CXPR-008-2021` | Near-normative subset | -| `test_CoaXPressOverFiberBridgeRx.py` | `CoaXPressOverFiberBridgeRx` | CXPoF start-word decode back into CoaXPress packet and `IO_ACK` words | Partial protocol | -| `test_CoaXPressOverFiberBridge.py` | `CoaXPressOverFiberBridge` | Top-level 32b/64b gearbox integration around the bridge leaf mapping | RTL-contract with spec framing | +| `test_CoaXPressOverFiberBridgeRx.py` | `CoaXPressOverFiberBridgeRx` | CXPoF start-word decode back into CoaXPress packet and `IO_ACK` words, `/Q/` sequence tracking, classified `/E/` abort/error status, and HKP status parsing | Partial protocol | +| `test_CoaXPressOverFiberBridge.py` | `CoaXPressOverFiberBridge` | Top-level 32b/64b gearbox integration around the bridge leaf mapping and RX bridge status forwarding | RTL-contract with spec framing | +| `test_CoaXPressOverFiberBridgeAxiL.py` | `CoaXPressOverFiberBridgeAxiL` | Software-visible sticky bridge RX status, last-observed sequence/HKP fields, event counters, reset behavior, and HKP classification readback | RTL-contract status consumer | ## Spec Section Notes @@ -105,29 +106,36 @@ exposed by the current checked-in RTL. The current checked-in coverage is split: - `test_CoaXPressConfig.py` - - checks untagged read and tagged write control-command formatting for - section `9.6.1.2` and `9.6.2` + - checks all four tagged/untagged read/write control-command formatting + quadrants for section `9.6.1.2` and `9.6.2` - drives requests through the real `CoaXPressConfig` / `SrpV3AxiLite` ingress path and validates both the serialized config packet and the completed SRPv3 response + - covers config-response timeout and nonzero control-ack status mapping into + the local SRPv3 AXI-Lite error footer - `test_CoaXPressRxLane.py` and `test_CoaXPressRx.py` - now drive fuller control-ack shapes on the wire: code, size, reply data, - CRC placeholder, and `EOP` - - these benches prove the subset the current receive RTL actually consumes + CRC, and `EOP` + - prove that receive-side control acknowledgments are forwarded only after + CRC and `EOP` validation pass Important limitation: -- `CoaXPressRxLane` does not currently validate full normative acknowledgment - semantics end to end -- it consumes only the reduced subset needed by the present receive assembly +- `CoaXPressRxLane` now validates the acknowledgment packet trailer before + pulsing `cfgMaster`, and malformed acknowledgment trailers pulse `rxError`, + but it still consumes only the reduced code/size/data subset needed by the + present receive assembly rather than exposing a richer application-facing + acknowledgment parser ### Heartbeat and event traffic Heartbeat and event handling is still intentionally narrow, but the receive -event parser now checks complete packet framing before acknowledging: +parsers now check complete packet framing before producing output pulses: - `test_CoaXPressRxLane.py` - checks the current 12-byte heartbeat payload collector + - validates heartbeat CRC/`EOP` before forwarding the heartbeat word and + suppresses bad-CRC heartbeat packets - `test_CoaXPressEventAckMsg.py` - covers event acknowledgment generation on the transmit side - `test_CoaXPressRxLane.py` and `test_CoaXPressRx.py` @@ -135,8 +143,8 @@ event parser now checks complete packet framing before acknowledging: payload words, CRC, and `EOP` - `CoaXPressRxLane` now acknowledges an event only after the CRC and `EOP` pass, suppresses bad-CRC events, and recovers for a later clean event - - event payload is validated for framing/CRC but is not exported through a - receive-side payload interface + - event payload is exported through the receive-side event stream with the + packet tag and event ID preserved as stream metadata That means these benches now cover the parser/acknowledgment subset of: @@ -144,8 +152,10 @@ That means these benches now cover the parser/acknowledgment subset of: - section `9.8.2` event payload parsing - event-payload CRC/trailer handling -They still do not prove an application-facing event-payload delivery contract, -because the current RTL exposes only `eventAck` and `eventTag`. +They now prove a bounded validate-before-release event-payload delivery contract: +payload words are withheld until the trailing CRC and `EOP` pass, bad-CRC events +do not leak payload words, and oversized events are rejected instead of being +partially forwarded. ### Stream data and rectangular image traffic @@ -154,16 +164,44 @@ The image-path benches are the strongest spec-aligned receive tests today: - `test_CoaXPressRxHsFsm.py` - validates rectangular image header and line marker handling against section `10.4.6.2` and `10.4.6.3` + - detects a new image header arriving before the previously declared frame's + line count has completed - `test_CoaXPressRx.py` - validates both the original one-lane top-level receive assembly and a dual-lane lane-rotation path around the same traffic - - also carries opt-in four-lane investigation benches behind - `RUN_KNOWN_ISSUE_TESTS=1`; those are intentionally not part of the - merge-ready passing slice yet + - validates four-lane short-frame rotation, malformed-header recovery, and + repeated single-line image-frame boundaries at the top-level receive + assembly + - also carries opt-in four-lane overflow stress benches behind + `RUN_STRESS_TESTS=1` because those workloads are intentionally heavier than + the normal regression slice `test_CoaXPressRxLane.py` also exercises stream packet handling using -spec-shaped stream headers, but the emphasis there is on receive-lane state -behavior rather than on a full normative stream CRC checker. +spec-shaped stream headers and CRC/`EOP` trailers. The RTL forwards payload as +it arrives, then publishes an in-order trailer verdict marker after the CRC and +`EOP` check. The receive assembly holds only the final packed SSI image beat +until that verdict arrives. If the stream trailer is malformed, the final image +beat is released with SSI `EOFE` set in `TUSER`; if the trailer is clean, the +final beat is released as a normal SSI EOF. This is not a buffered bad-payload +drop contract: earlier payload words may already have been forwarded, and the +downstream consumer is expected to reject or quarantine the frame based on the +terminal `EOFE` bit. Bad stream trailers pulse the lane-level `rxError`, which +the receive assembly aggregates into `rxFsmError` and the core exposes through +the existing `RxFsmErrorCnt` software counter. + +### Receive event payload stream + +`CoaXPressRxLane` now exposes event payload words on an AXI-stream style +`eventMaster` interface while preserving the legacy `eventAck/eventTag` trailer +completion pulse. The lane buffers up to 16 event payload words and releases +them only after the event CRC and `EOP` trailer pass. The event payload stream +uses `TDEST[7:0]` for the packet tag and publishes the event ID bytes on +`TUSER[31:0]` at the lane boundary. The `CoaXPressRx` assembly crosses that +payload stream into the `cfgClk` domain with an `AxiStreamFifoV2`. + +This is a bounded receive-side payload contract, not an unbounded event +transport. Event payloads longer than the internal store are reported as +malformed and suppressed. ### Software-visible overflow and FSM-error status @@ -179,14 +217,14 @@ software through `CoaXPressAxiL`: checks that the top-level counter increments, then verifies the count stays stable during idle cycles and that a later clean image transaction is still accepted -- `coaxpress_core_rx_overflow_does_not_trigger_fsm_error_storm_known_issue_test` - - checked in as an opt-in skipped investigation bench - - drives sustained receive-data backpressure with repeated one-line image - frames and encodes the expected software-facing behavior: overflow should - count first, `RxFsmErrorCnt` should stay at zero, idle should not create an - error storm, and a later clean frame should still pass - - enable locally with `RUN_KNOWN_ISSUE_TESTS=1` and optionally narrow the - stress volume with `CXP_RX_OVERFLOW_STORM_FRAME_COUNT` +- `coaxpress_core_rx_overflow_does_not_trigger_fsm_error_storm_test` + - drives sustained receive-data backpressure with long image lines and encodes + the expected software-facing behavior: overflow should count, + `RxFsmErrorCnt` should stay at zero, idle should not create an error storm, + and a later clean frame should still pass + - the default workload is sized to overflow the RX data path directly; it can + be tuned with `CXP_RX_OVERFLOW_STORM_FRAME_COUNT` and + `CXP_RX_OVERFLOW_STORM_LINE_WORD_COUNT` This is intentionally a top-level software-facing check, not a replacement for the lower-level malformed-header coverage in `test_CoaXPressRxHsFsm.py`. @@ -216,20 +254,75 @@ Current checked-in coverage: - embedded EOP K-code reconstruction for stream marker and packet-end words - HKP forwarding, including a housekeeping-to-payload transition and an HKP-carried CXP EOP word + - HKP K-code semantics: all-data nGMII control-mask enforcement, + per-byte K-code validation through `hkpKCodeMask/hkpKCodeValid`, and + whole-word classification through `hkpType` + - `hkpValid/hkpData/hkpEop/hkpSof/hkpWordCount` status for HKP words that are + forwarded on the reconstructed CXP side, plus `hkpError` for malformed HKP + control masks or invalid HKP K-code bytes + - lane-0 `/Q/` sequence tracking through `seqValid/seqData/seqExpected`, with + `seqError/seqErrorExpected` on skipped sequence values while preserving + no-output behavior on the CXP word stream + - classified `/E/` status through `rxError/rxAbort/rxErrorCode` for idle and + active-packet error ordered sets - negative lane-placement checks for `/S/`, `/Q/`, `/T/`, and `/E/` - - lane-0 `/Q/` no-output guardrail, `/E/` packet abort behavior before and + - lane-0 `/Q/` no-output behavior, `/E/` packet abort behavior before and after payload, and recovery to a following valid low-speed packet - `test_CoaXPressOverFiberBridge.py` - top-level 32b/64b gearbox integration around the bridge leaves - - RX-side 64b gearbox coverage for `/E/` abort/recovery, HKP-to-payload - transition, and lane-0 `/Q/` no-output/recovery guardrails + - RX-side 64b gearbox coverage for classified `/E/` abort/recovery, + HKP-to-payload status, and lane-0 `/Q/` sequence mismatch/no-output/recovery + guardrails +- `test_CoaXPressOverFiberBridgeAxiL.py` + - AXI-Lite readback of sticky bridge RX status bits, last-observed + sequence/HKP fields, and event counters + - write-one reset coverage for sticky status and counters + - named HKP K-code classification sweep through the packed HKP status register + +The product-facing bridge status contract is the `CxpofRxStatusType` record in +`CoaXPressPkg.vhd`. The cocotb benches use thin wrapper entities to flatten +that record back to scalar ports only because GHDL/cocotb does not reliably +expose top-level VHDL record fields as child handles. Those wrappers are a test +surface, not the intended RTL integration contract. + +`CoaXPressOverFiberBridgeAxiL` makes the bridge RX status software-visible for +the GT wrapper integrations. The GTH/GTY wrapper AXI-Lite port now reports +sticky status, last observed sequence/HKP fields, and counters instead of +returning only a default decode error. The current AxiL regression also sweeps +the named HKP K-code classifications through the packed HKP readback register +so software-visible consumers are not only checked against one EOP case. The +current register map is: + +- `0x000`: sticky status bits for `rxError`, `rxAbort`, `seqValid`, + `seqError`, `hkpValid`, and `hkpError` +- `0x004`: last `rxErrorCode` +- `0x008`: last `seqData` +- `0x00C`: last `seqExpected` +- `0x010`: last `seqErrorExpected` +- `0x014`: last `hkpData` +- `0x018`: packed HKP status: `hkpWordCount[7:0]`, `hkpKCodeMask[11:8]`, + `hkpKCodeValid[12]`, and `hkpType[19:16]` +- `0x020` to `0x034`: event counters for the six sticky status bits above +- `0x03C`: write-one counter/sticky reset strobe -Still open on the bridge side: +Current RTL support limits observed while expanding the bridge tests: -- normative `/Q/` sequence handling beyond the current no-output/recovery guardrails -- fuller `/E/` error semantics beyond malformed-placement and abort-and-recover guardrails -- full housekeeping protocol semantics beyond raw HKP forwarding and the current - HKP-to-payload transition check +- `/Q/` ordered sets are not decoded into the CXP-side word stream. The current + contract initializes on the first sequence word, expects a 24-bit increment on + each later `/Q/`, pulses `seqError` on mismatch, reports the expected value, + and resynchronizes from the received value. +- `/E/` is published through `rxError`, `rxAbort`, and `rxErrorCode` status. Idle + `/E/`, active-payload `/E/`, malformed control placement, overwrite, sequence + mismatch, and malformed HKP conditions now have distinct cause codes. When + `/E/` appears during a packet, the RX bridge aborts the active nGMII packet + and returns to idle; if the start word was already accepted, the CXP `SOP` and + packet-type words may already have been emitted, but no synthetic CXP `EOP` is + generated. +- HKP handling now follows the CXPoF High-Speed K-Code Payload contract: HKP is + received with nGMII control flags clear, reconstructed on the CXP side with + K-code flags asserted, validated as K-code bytes, and classified as known CXP + K-code words where possible. HKP does not define a separate command opcode + layer in this bridge contract. Current RTL support limits observed while expanding the bridge tests: @@ -252,25 +345,36 @@ compliance coverage. The most important open limits are: -- `CoaXPressRxHsFsm` still has an open bonded-receive issue on back-to-back - short four-lane image frames: later one-word tails can miss `TLAST`, which - merges or truncates adjacent frames -- the gated four-lane `CoaXPressRx` investigation benches are therefore still - opt-in only; they exist to track clean-rotation, malformed-header recovery, - and backpressure/overflow recovery once the short-tail boundary bug is fixed -- the checked-in known-issue core bench for overflow-vs-FSM-error behavior is - skipped by default until the receive-side backpressure interaction is - understood and fixed -- receive-side event payload is validated for framing/CRC before ACK, but is not - exposed through an application-facing payload interface +- the four-lane overflow recovery checks are opt-in stress benches because they + intentionally fill and drain deep receive FIFOs; enable them with + `RUN_STRESS_TESTS=1` +- receive-side event payload is validated for framing/CRC before ACK and + released through a bounded application-facing payload interface +- the receive stream-data path now validates CRC/`EOP` trailer framing before + accepting the next packet and marks malformed frames with SSI `EOFE` on the + final image beat, but it still streams payload before the trailer result is + known instead of buffering and dropping a bad frame internally - trigger coverage still does not include the broader low-speed extra modes or the full high-speed trigger matrix, though the low-speed FSM now covers active-pulse shortening through a runtime `txPulseWidth` update -- CXPoF bridge coverage still does not exhaustively cover normative `/Q/`, - `/E/`, and full housekeeping protocol semantics +- CXPoF bridge coverage now includes `/Q/` sequence mismatch policy, classified + `/E/` causes, and HKP K-code validation/classification ## Running The Slice +Latest focused validation for the current receive-side SSI `EOFE` work: + +```bash +./.venv/bin/python -m pytest -q tests/protocols/coaxpress/test_CoaXPressRx.py +./.venv/bin/python -m pytest -q tests/protocols/coaxpress/test_CoaXPressRxHsFsm.py +./.venv/bin/python -m pytest -q \ + tests/protocols/coaxpress/test_CoaXPressRxLane.py \ + tests/protocols/coaxpress/test_CoaXPressRxWordPacker.py \ + tests/protocols/coaxpress/test_CoaXPressRxLaneMux.py +./.venv/bin/python -m pytest -q \ + tests/protocols/coaxpress/test_CoaXPressCore.py::test_CoaXPressCore +``` + Typical local commands: ```bash diff --git a/tests/protocols/coaxpress/coaxpress_test_utils.py b/tests/protocols/coaxpress/coaxpress_test_utils.py index fc0d6ce969..2c15070222 100644 --- a/tests/protocols/coaxpress/coaxpress_test_utils.py +++ b/tests/protocols/coaxpress/coaxpress_test_utils.py @@ -23,6 +23,8 @@ CXP_IDLE = 0xB53C3CBC CXP_IDLE_K = 0x7 +CXP_ALL_DATA_K = 0x0 +CXP_ALL_CTRL_K = 0xF CXP_SOP = 0xFBFBFBFB CXP_EOP = 0xFDFDFDFD CXP_TRIG = 0x5C5C5C5C @@ -56,6 +58,30 @@ CXPOF_TERM = 0xFD CXPOF_ERROR = 0xFE +CXPOF_SOP_CTRL_LOW_SPEED = 0x00 +CXPOF_SOP_CTRL_HIGH_SPEED = 0x80 +CXPOF_SOP_CTRL_HKP = 0x01 +CXPOF_SOP_CTRL_UPDATE_BIT = 3 +CXPOF_SOP_CTRL_LS_RATE_BIT = 1 + +CXPOF_RX_ERR_NONE = 0x0 +CXPOF_RX_ERR_SEQ_MISMATCH = 0x1 +CXPOF_RX_ERR_IDLE_ERROR = 0x2 +CXPOF_RX_ERR_PAYLOAD_ABORT = 0x3 +CXPOF_RX_ERR_BAD_CONTROL = 0x4 +CXPOF_RX_ERR_OVERWRITE = 0x5 +CXPOF_RX_ERR_HKP_MALFORMED = 0x6 +CXPOF_RX_ERR_HKP_BAD_K_CODE = 0x7 + +CXPOF_HKP_TYPE_NONE = 0x0 +CXPOF_HKP_TYPE_K_CODE = 0x1 +CXPOF_HKP_TYPE_SOP = 0x2 +CXPOF_HKP_TYPE_EOP = 0x3 +CXPOF_HKP_TYPE_TRIG = 0x4 +CXPOF_HKP_TYPE_IO_ACK = 0x5 +CXPOF_HKP_TYPE_MARKER = 0x6 +CXPOF_HKP_TYPE_INVALID = 0xF + CXP_CRC32_POLY = 0x04C11DB7 diff --git a/tests/protocols/coaxpress/test_CoaXPressConfig.py b/tests/protocols/coaxpress/test_CoaXPressConfig.py index 9e5d598bf1..addfc09bb0 100644 --- a/tests/protocols/coaxpress/test_CoaXPressConfig.py +++ b/tests/protocols/coaxpress/test_CoaXPressConfig.py @@ -9,8 +9,8 @@ ############################################################################## # Test methodology: -# - Sweep: Cover the two request-serialization branches that are unique to -# `CoaXPressConfig`: untagged reads and tagged writes. +# - Sweep: Cover all four config request-format quadrants plus local timeout +# and nonzero control-ack response error handling. # - Stimulus: Drive wide SRPv3 request frames into `cfgIb`, capture the emitted # CoaXPress low-speed byte stream on `cfgTx`, and feed the completion side # with one config receive acknowledgment. @@ -48,6 +48,11 @@ ) +CONFIG_READ_ERROR_FOOTER = 0x1 +CONFIG_WRITE_ERROR_FOOTER = 0x2 + + + async def _drive_cfg_rx_completion(dut, value: int, *, hold_cycles: int = 8) -> None: dut.cfgRxTData.value = value dut.cfgRxTValid.value = 1 @@ -187,6 +192,139 @@ async def coaxpress_config_tagged_write_tag_increment_test(dut): ) + +@cocotb.test() +async def coaxpress_config_tagged_read_and_untagged_write_request_test(dut): + # Cover the two request-format quadrants not hit by the original directed + # cases: tagged read and untagged write. + axis = await _setup_config_bench(dut, config_pkt_tag=1) + + read_request = SrpV3Request(SRP_READ, 0x12340001, 0x00000120, 4) + read_data = 0x13579BDF + read_tx_task = cocotb.start_soon( + collect_stream_bytes( + dut, + clk=dut.cfgClk, + valid_name="M_CFG_TX_TVALID", + data_name="M_CFG_TX_TDATA", + ready_name="M_CFG_TX_TREADY", + count=28, + timeout_cycles=8000, + ) + ) + read_response_task = cocotb.start_soon(axis.recv_response(timeout_time=20)) + await axis.send_words(srpv3_frame(read_request)) + + read_tx_bytes = await with_timeout(read_tx_task, 20, "us") + assert read_tx_bytes[:4] == bytes(word_to_bytes(CXP_SOP)) + assert read_tx_bytes[4:8] == bytes([0x05] * 4) + assert read_tx_bytes[8:12] == bytes([0x00] * 4) + assert read_tx_bytes[12:16] == bytes(word_to_bytes(0x04000000)) + assert read_tx_bytes[16:20] == bytes(word_to_bytes(endian_swap32(read_request.address))) + expected_read_crc = cxp_crc_word( + [0x00000000, 0x04000000, endian_swap32(read_request.address)] + ) + assert read_tx_bytes[20:24] == bytes(word_to_bytes(expected_read_crc)) + assert read_tx_bytes[-4:] == bytes(word_to_bytes(CXP_EOP)) + + await _drive_cfg_rx_completion(dut, read_data << 32) + assert_srpv3_response(await read_response_task, read_request, [read_data]) + + dut.configPktTag.value = 0 + await RisingEdge(dut.cfgClk) + await Timer(1, unit="ns") + + write_request = SrpV3Request(SRP_WRITE, 0x12340002, 0x00000124, 4) + write_data = 0x2468ACE0 + write_tx_task = cocotb.start_soon( + collect_stream_bytes( + dut, + clk=dut.cfgClk, + valid_name="M_CFG_TX_TVALID", + data_name="M_CFG_TX_TDATA", + ready_name="M_CFG_TX_TREADY", + count=28, + timeout_cycles=8000, + ) + ) + write_response_task = cocotb.start_soon(axis.recv_response(timeout_time=20)) + await axis.send_words(srpv3_frame(write_request, [write_data])) + + write_tx_bytes = await with_timeout(write_tx_task, 20, "us") + assert write_tx_bytes[:4] == bytes(word_to_bytes(CXP_SOP)) + assert write_tx_bytes[4:8] == bytes([0x02] * 4) + assert write_tx_bytes[8:12] == bytes(word_to_bytes(0x04000001)) + assert write_tx_bytes[12:16] == bytes(word_to_bytes(endian_swap32(write_request.address))) + assert write_tx_bytes[16:20] == bytes(word_to_bytes(write_data)) + expected_write_crc = cxp_crc_word( + [0x04000001, endian_swap32(write_request.address), write_data] + ) + assert write_tx_bytes[20:24] == bytes(word_to_bytes(expected_write_crc)) + assert write_tx_bytes[-4:] == bytes(word_to_bytes(CXP_EOP)) + + await _drive_cfg_rx_completion(dut, 0) + assert_srpv3_response(await write_response_task, write_request, [write_data]) + + +@cocotb.test() +async def coaxpress_config_response_error_paths_test(dut): + # The current RTL maps either a local config-response timeout or a nonzero + # control-ack status word into the local SRPv3 AXI-Lite error footer when + # `configErrResp` is asserted. + axis = await _setup_config_bench(dut, config_pkt_tag=0) + dut.configTimerSize.value = 8 + + timeout_request = SrpV3Request(SRP_READ, 0xABC00001, 0x00000200, 4) + timeout_tx_task = cocotb.start_soon( + collect_stream_bytes( + dut, + clk=dut.cfgClk, + valid_name="M_CFG_TX_TVALID", + data_name="M_CFG_TX_TDATA", + ready_name="M_CFG_TX_TREADY", + count=24, + timeout_cycles=8000, + ) + ) + timeout_response_task = cocotb.start_soon(axis.recv_response(timeout_time=20)) + await axis.send_words(srpv3_frame(timeout_request)) + await with_timeout(timeout_tx_task, 20, "us") + assert_srpv3_response( + await timeout_response_task, + timeout_request, + [], + footer_mask=CONFIG_READ_ERROR_FOOTER, + footer_value=CONFIG_READ_ERROR_FOOTER, + ) + + dut.configTimerSize.value = 4096 + status_request = SrpV3Request(SRP_WRITE, 0xABC00002, 0x00000204, 4) + status_write_data = 0xA5A55A5A + status_tx_task = cocotb.start_soon( + collect_stream_bytes( + dut, + clk=dut.cfgClk, + valid_name="M_CFG_TX_TVALID", + data_name="M_CFG_TX_TDATA", + ready_name="M_CFG_TX_TREADY", + count=28, + timeout_cycles=8000, + ) + ) + status_response_task = cocotb.start_soon(axis.recv_response(timeout_time=20)) + await axis.send_words(srpv3_frame(status_request, [status_write_data])) + await with_timeout(status_tx_task, 20, "us") + + await _drive_cfg_rx_completion(dut, 0x00000001) + assert_srpv3_response( + await status_response_task, + status_request, + [status_write_data], + footer_mask=CONFIG_WRITE_ERROR_FOOTER, + footer_value=CONFIG_WRITE_ERROR_FOOTER, + ) + + def test_CoaXPressConfig(): run_surf_vhdl_test( test_file=__file__, diff --git a/tests/protocols/coaxpress/test_CoaXPressCore.py b/tests/protocols/coaxpress/test_CoaXPressCore.py index 3770cb0b80..6454a6d784 100644 --- a/tests/protocols/coaxpress/test_CoaXPressCore.py +++ b/tests/protocols/coaxpress/test_CoaXPressCore.py @@ -38,11 +38,14 @@ CXP_IDLE, CXP_IDLE_K, CXP_MARKER, + CXP_EOP, CXP_PKT_IMAGE_HEADER, CXP_PKT_IMAGE_LINE, + CXP_PKT_STREAM_DATA, CXP_SOP, cycle, collect_stream_bytes, + cxp_crc_word, endian_swap32, find_subsequence, pack_u32_words_le, @@ -132,18 +135,34 @@ async def _read_counter(axil: AxiLiteMaster, dut, offset: int) -> int: return await axil.read_dword(offset) -async def _send_stream_packet_words(dut, payload_words: list[int], *, stream_id: int = 0x22, packet_tag: int = 0x33) -> None: +async def _send_stream_packet_words( + dut, + payload_words: list[int], + *, + stream_id: int = 0x22, + packet_tag: int = 0x33, + corrupt_crc: bool = False, +) -> None: + crc_inputs = [ + repeat_byte(stream_id), + repeat_byte(packet_tag), + repeat_byte((len(payload_words) >> 8) & 0xFF), + repeat_byte(len(payload_words) & 0xFF), + *payload_words, + ] words = [ CXP_SOP, - repeat_byte(0x01), + repeat_byte(CXP_PKT_STREAM_DATA), repeat_byte(stream_id), repeat_byte(packet_tag), repeat_byte((len(payload_words) >> 8) & 0xFF), repeat_byte(len(payload_words) & 0xFF), *payload_words, + cxp_crc_word(crc_inputs) ^ (0x00000001 if corrupt_crc else 0x00000000), + CXP_EOP, ] for word in words: - await send_rx_word(dut, data=word, data_k=0xF if word == CXP_SOP else 0x0, clk=dut.rxClk) + await send_rx_word(dut, data=word, data_k=0xF if word in (CXP_SOP, CXP_EOP) else 0x0, clk=dut.rxClk) async def _collect_core_outputs(dut, *, cycles: int) -> tuple[list[int], list[int]]: @@ -242,14 +261,16 @@ async def coaxpress_core_tagged_config_tx_path_test(dut): await send_axis_payload(dut, clk=dut.cfgClk, prefix="S_CFG_IB", payload=request_payload, width_bytes=32, tuser=0x2) tx_bytes = await with_timeout(tx_task, 20, "us") - expected_request = ( + expected_packet = ( bytes(word_to_bytes(CXP_SOP)) + bytes([0x05] * 4) + b"\x00\x00\x00\x00" + bytes(word_to_bytes(0x04000000)) + bytes(word_to_bytes(endian_swap32(addr))) + + bytes(word_to_bytes(cxp_crc_word([0x00000000, 0x04000000, endian_swap32(addr)]))) + + bytes(word_to_bytes(CXP_EOP)) ) - request_start = find_subsequence(tx_bytes, expected_request) + request_start = find_subsequence(tx_bytes, expected_packet) assert request_start is not None, tx_bytes @@ -302,17 +323,55 @@ async def coaxpress_core_rx_fsm_error_counter_and_recovery_test(dut): assert await _read_counter(axil, dut, 0x824) == first_error_count -@cocotb.test(skip=os.getenv("RUN_KNOWN_ISSUE_TESTS") != "1") -async def coaxpress_core_rx_overflow_does_not_trigger_fsm_error_storm_known_issue_test(dut): +@cocotb.test() +async def coaxpress_core_rx_lane_crc_error_counter_test(dut): + axil = await _setup_core(dut) + + assert await _read_counter(axil, dut, 0x824) == 0 + + await _send_stream_packet_words( + dut, + _header_payload(y_size=1, dsize_l=1), + stream_id=0x52, + packet_tag=0x77, + ) + await _send_stream_packet_words( + dut, + _line_payload(0x12345678), + stream_id=0x53, + packet_tag=0x78, + corrupt_crc=True, + ) + + lane_error_count = await _read_counter(axil, dut, 0x824) + assert lane_error_count > 0 + + await _collect_core_outputs(dut, cycles=32) + assert await _read_counter(axil, dut, 0x824) == lane_error_count + + await _send_image_frame( + dut, + stream_id=0x54, + packet_tag=0x79, + y_size=1, + dsize_l=1, + line_words=[0x87654321], + ) + _hdr_words, data_words = await _collect_core_outputs(dut, cycles=64) + assert 0x87654321 in data_words + assert await _read_counter(axil, dut, 0x824) == lane_error_count + + +@cocotb.test() +async def coaxpress_core_rx_overflow_does_not_trigger_fsm_error_storm_test(dut): axil = await _setup_core(dut, data_ready=0, hdr_ready=1) assert await _read_counter(axil, dut, 0x820) == 0 assert await _read_counter(axil, dut, 0x824) == 0 - frame_count = env_int("CXP_RX_OVERFLOW_STORM_FRAME_COUNT", default=96) - # Hold opaque stream metadata constant to separate a true backpressure issue - # from metadata-sensitive parsing behavior in the exploratory bench. - fixed_packet_fields = env_flag("CXP_CORE_KNOWN_ISSUE_FIXED_PACKET_FIELDS", default=False) + frame_count = env_int("CXP_RX_OVERFLOW_STORM_FRAME_COUNT", default=8) + line_word_count = env_int("CXP_RX_OVERFLOW_STORM_LINE_WORD_COUNT", default=96) + vary_packet_fields = env_flag("CXP_CORE_RX_OVERFLOW_VARY_PACKET_FIELDS", default=True) signal_counts = { "core_rx_fsm_error": 0, "core_rx_overflow": 0, @@ -365,15 +424,15 @@ async def coaxpress_core_rx_overflow_does_not_trigger_fsm_error_storm_known_issu for index in range(frame_count): phase_trace["frame_index"] = index - stream_id = 0x50 if fixed_packet_fields else (0x50 + (2 * index)) & 0xFF - packet_tag = 0x70 if fixed_packet_fields else (0x70 + (2 * index)) & 0xFF + stream_id = (0x50 + (2 * index)) & 0xFF if vary_packet_fields else 0x50 + packet_tag = (0x70 + (2 * index)) & 0xFF if vary_packet_fields else 0x70 await _send_image_frame( dut, stream_id=stream_id, packet_tag=packet_tag, y_size=1, - dsize_l=1, - line_words=[0x10000000 + index], + dsize_l=line_word_count, + line_words=[0x10000000 | (index << 12) | word_index for word_index in range(line_word_count)], ) phase_trace["label"] = "idle_quiesce" @@ -387,10 +446,6 @@ async def coaxpress_core_rx_overflow_does_not_trigger_fsm_error_storm_known_issu for task in monitor_tasks: await task - # Known issue under investigation: - # with the current 72-frame workload the realistic core path still shows no - # RX overflow. The remaining anomaly is a late lone RxFsmError pulse that - # only appears when the bench sweeps packet stream/tag fields. assert overflow_count > 0, ( f"overflow_count={overflow_count} first_error_count={first_error_count} " f"core_overflow={signal_counts['core_rx_overflow']} core_error={signal_counts['core_rx_fsm_error']} " @@ -421,6 +476,8 @@ async def coaxpress_core_rx_overflow_does_not_trigger_fsm_error_storm_known_issu released_error_count = await _read_counter(axil, dut, 0x824) assert released_error_count == first_error_count + await _collect_core_outputs(dut, cycles=env_int("CXP_RX_OVERFLOW_STORM_DRAIN_CYCLES", default=1024)) + await _send_image_frame( dut, stream_id=0xE0, diff --git a/tests/protocols/coaxpress/test_CoaXPressOverFiberBridge.py b/tests/protocols/coaxpress/test_CoaXPressOverFiberBridge.py index 75e655f89b..4ef1e069bf 100644 --- a/tests/protocols/coaxpress/test_CoaXPressOverFiberBridge.py +++ b/tests/protocols/coaxpress/test_CoaXPressOverFiberBridge.py @@ -30,13 +30,21 @@ from tests.common.regression_utils import run_surf_vhdl_test from tests.protocols.coaxpress.coaxpress_test_utils import ( CXP_EOP, + CXP_ALL_CTRL_K, CXP_IDLE, CXP_IDLE_K, CXP_PKT_EVENT_ACK, CXP_SOP, CXPOF_ERROR, + CXPOF_HKP_TYPE_K_CODE, CXPOF_IDLE, + CXPOF_RX_ERR_PAYLOAD_ABORT, + CXPOF_RX_ERR_SEQ_MISMATCH, CXPOF_SEQ, + CXPOF_SOP_CTRL_HIGH_SPEED, + CXPOF_SOP_CTRL_HKP, + CXPOF_SOP_CTRL_LS_RATE_BIT, + CXPOF_SOP_CTRL_UPDATE_BIT, CXPOF_START, CXPOF_TERM, cycle, @@ -49,15 +57,15 @@ def _tx_start_word(rate: int, update: int) -> int: - return CXPOF_START | (((update << 3) | (rate << 1)) << 8) + return CXPOF_START | (((update << CXPOF_SOP_CTRL_UPDATE_BIT) | (rate << CXPOF_SOP_CTRL_LS_RATE_BIT)) << 8) def _rx_start_word(packet_byte: int) -> int: - return CXPOF_START | (0x80 << 8) | ((CXP_SOP & 0xFF) << 16) | (packet_byte << 24) + return CXPOF_START | (CXPOF_SOP_CTRL_HIGH_SPEED << 8) | ((CXP_SOP & 0xFF) << 16) | (packet_byte << 24) def _rx_hkp_start_word() -> int: - return CXPOF_START | (0x81 << 8) + return CXPOF_START | ((CXPOF_SOP_CTRL_HIGH_SPEED | CXPOF_SOP_CTRL_HKP) << 8) def _idle64() -> int: @@ -181,6 +189,15 @@ async def coaxpress_over_fiber_bridge_top_rx_error_abort_and_recovery_test(dut): await _setup_bridge(dut) rx_capture = cocotb.start_soon(_capture_rx_words(dut, cycles=64)) + abort_samples: list[tuple[int, int]] = [] + + async def capture_abort(cycles: int) -> None: + for _ in range(cycles): + await RisingEdge(dut.rxClk312) + await Timer(1, unit="ns") + if int(dut.rxAbort.value) == 1: + abort_samples.append((int(dut.rxError.value), int(dut.rxErrorCode.value))) + abort_capture = cocotb.start_soon(capture_abort(64)) # Start a valid low-speed packet, then inject `/E/` as the next 32-bit word. # The first packet must not receive a synthetic CXP EOP. @@ -201,6 +218,7 @@ async def coaxpress_over_fiber_bridge_top_rx_error_abort_and_recovery_test(dut): await _drive_rx64(dut, 0x07FD00FD | (repeat_byte(CXPOF_IDLE) << 32), 0xFC) await _drive_rx64(dut, _idle64(), 0xFF) + await abort_capture rx_observed = await rx_capture rx_expected = [ (CXP_SOP, 0xF), @@ -214,6 +232,7 @@ async def coaxpress_over_fiber_bridge_top_rx_error_abort_and_recovery_test(dut): assert find_subsequence(rx_observed, rx_expected) is not None, ( f"missing RX /E/ recovery sequence: {rx_observed}" ) + assert abort_samples == [(1, CXPOF_RX_ERR_PAYLOAD_ABORT)] @cocotb.test() @@ -221,18 +240,39 @@ async def coaxpress_over_fiber_bridge_top_rx_hkp_then_payload_mix_test(dut): await _setup_bridge(dut) rx_capture = cocotb.start_soon(_capture_rx_words(dut, cycles=48)) + hkp_samples: list[tuple[int, int, int, int, int, int, int]] = [] + + async def capture_hkp(cycles: int) -> None: + for _ in range(cycles): + await RisingEdge(dut.rxClk312) + await Timer(1, unit="ns") + if int(dut.hkpValid.value) == 1: + hkp_samples.append( + ( + int(dut.hkpData.value), + int(dut.hkpEop.value), + int(dut.hkpSof.value), + int(dut.hkpWordCount.value), + int(dut.hkpKCodeMask.value), + int(dut.hkpKCodeValid.value), + int(dut.hkpType.value), + ) + ) + hkp_capture = cocotb.start_soon(capture_hkp(48)) hkp_word = 0x9C5C3CBC - await _drive_rx64(dut, _rx_hkp_start_word() | (hkp_word << 32), 0xF1) + await _drive_rx64(dut, _rx_hkp_start_word() | (hkp_word << 32), 0x01) await _drive_rx64(dut, 0x10203040 | (0x07FD00FD << 32), 0xC0) await _drive_rx64(dut, _idle64(), 0xFF) + await hkp_capture rx_observed = await rx_capture rx_expected = [ (hkp_word, 0xF), (0x10203040, 0x0), (CXP_EOP, 0xF), ] + assert hkp_samples == [(hkp_word, 0, 1, 1, CXP_ALL_CTRL_K, 1, CXPOF_HKP_TYPE_K_CODE)] assert find_subsequence(rx_observed, rx_expected) is not None, f"missing RX HKP/data sequence: {rx_observed}" @@ -241,21 +281,50 @@ async def coaxpress_over_fiber_bridge_top_rx_sequence_no_output_recovery_test(du await _setup_bridge(dut) rx_capture = cocotb.start_soon(_capture_rx_words(dut, cycles=64)) + seq_samples: list[int] = [] + seq_errors: list[tuple[int, int, int]] = [] - # Lane-0 `/Q/` is not decoded into a CXP word by the current RX bridge. The - # top-level gearbox should preserve that no-output guardrail and allow a - # later valid low-speed packet to recover. + async def capture_seq(cycles: int) -> None: + for _ in range(cycles): + await RisingEdge(dut.rxClk312) + await Timer(1, unit="ns") + if int(dut.seqValid.value) == 1: + seq_samples.append(int(dut.seqData.value)) + if int(dut.seqError.value) == 1: + seq_errors.append( + ( + int(dut.seqData.value), + int(dut.seqErrorExpected.value), + int(dut.rxErrorCode.value), + ) + ) + seq_capture = cocotb.start_soon(capture_seq(64)) + + # Lane-0 `/Q/` is published through the status interface, not decoded into a + # CXP word. The top-level gearbox should preserve that no-output guardrail + # and allow a later valid low-speed packet to recover. await _drive_rx64( dut, (CXPOF_SEQ | (0x12 << 16) | (0x34 << 24)) | (_idle64() & 0xFFFFFFFF00000000), 0xF1, ) + await _drive_rx64( + dut, + (CXPOF_SEQ | (0x01 << 8) | (0x12 << 16) | (0x34 << 24)) | (_idle64() & 0xFFFFFFFF00000000), + 0xF1, + ) + await _drive_rx64( + dut, + (CXPOF_SEQ | (0x03 << 8) | (0x12 << 16) | (0x34 << 24)) | (_idle64() & 0xFFFFFFFF00000000), + 0xF1, + ) await _drive_rx64(dut, _idle64(), 0xFF) await _drive_rx64(dut, _rx_start_word(CXP_PKT_EVENT_ACK) | (0xA1B2C3D4 << 32), 0x01) await _drive_rx64(dut, 0x07FD00FD | (repeat_byte(CXPOF_IDLE) << 32), 0xFC) await _drive_rx64(dut, _idle64(), 0xFF) + await seq_capture rx_observed = await rx_capture rx_expected = [ (CXP_SOP, 0xF), @@ -263,19 +332,22 @@ async def coaxpress_over_fiber_bridge_top_rx_sequence_no_output_recovery_test(du (0xA1B2C3D4, 0x0), (CXP_EOP, 0xF), ] + assert seq_samples == [0x341200, 0x341201, 0x341203] + assert seq_errors == [(0x341203, 0x341202, CXPOF_RX_ERR_SEQ_MISMATCH)] assert rx_observed == rx_expected def test_CoaXPressOverFiberBridge(): run_surf_vhdl_test( test_file=__file__, - toplevel="surf.coaxpressoverfiberbridge", + toplevel="surf.coaxpressoverfiberbridgestatuswrapper", extra_vhdl_sources={ "surf": [ "protocols/coaxpress/core/rtl/CoaXPressPkg.vhd", "protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeRx.vhd", "protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeTx.vhd", "protocols/coaxpress/core/rtl/CoaXPressOverFiberBridge.vhd", + "protocols/coaxpress/core/wrappers/CoaXPressOverFiberBridgeStatusWrapper.vhd", ] }, ) diff --git a/tests/protocols/coaxpress/test_CoaXPressOverFiberBridgeAxiL.py b/tests/protocols/coaxpress/test_CoaXPressOverFiberBridgeAxiL.py new file mode 100644 index 0000000000..3f6f363d6f --- /dev/null +++ b/tests/protocols/coaxpress/test_CoaXPressOverFiberBridgeAxiL.py @@ -0,0 +1,278 @@ +############################################################################## +## 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. +############################################################################## + +# Test methodology: +# - Sweep: Exercise the software-visible CXPoF bridge RX status register file +# through the AXI-Lite clock crossing. +# - Stimulus: Pulse representative receive-domain status events for `/Q/` +# sequence tracking, sequence mismatch, named HKP K-code classifications, +# and `/E/` abort. +# - Checks: AXI-Lite reads must report sticky status, last-observed status +# fields, event counters, and write-one counter/sticky reset behavior. +# - Timing: AXI-Lite and RX clocks run asynchronously so the bench covers the +# intended `AxiLiteAsync` path, not only a common-clock register file. + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge, Timer, with_timeout +from cocotbext.axi import AxiLiteBus, AxiLiteMaster + +from tests.axi.utils import axil_read_u32, axil_write_u32 +from tests.common.regression_utils import run_surf_vhdl_test +from tests.protocols.coaxpress.coaxpress_test_utils import ( + CXP_ALL_CTRL_K, + CXP_EOP, + CXP_IO_ACK, + CXP_MARKER, + CXP_SOP, + CXP_TRIG, + CXPOF_HKP_TYPE_EOP, + CXPOF_HKP_TYPE_IO_ACK, + CXPOF_HKP_TYPE_K_CODE, + CXPOF_HKP_TYPE_MARKER, + CXPOF_HKP_TYPE_SOP, + CXPOF_HKP_TYPE_TRIG, + CXPOF_RX_ERR_PAYLOAD_ABORT, + CXPOF_RX_ERR_SEQ_MISMATCH, +) + + +STATUS_RX_ERROR = 1 << 0 +STATUS_RX_ABORT = 1 << 1 +STATUS_SEQ_VALID = 1 << 2 +STATUS_SEQ_ERROR = 1 << 3 +STATUS_HKP_VALID = 1 << 4 +STATUS_HKP_ERROR = 1 << 5 + + +async def _rx_cycle(dut, count: int = 1) -> None: + for _ in range(count): + await RisingEdge(dut.rxClk) + await Timer(1, unit="ns") + + +async def _axil_read(axil, address: int) -> int: + return await with_timeout(axil_read_u32(axil, address), 2, "us") + + +async def _axil_write(axil, address: int, value: int) -> None: + await with_timeout(axil_write_u32(axil, address, value), 2, "us") + + +def _clear_status_inputs(dut) -> None: + for name in ( + "rxError", + "rxAbort", + "rxErrorCode", + "seqValid", + "seqData", + "seqError", + "seqExpected", + "seqErrorExpected", + "hkpValid", + "hkpData", + "hkpEop", + "hkpSof", + "hkpError", + "hkpWordCount", + "hkpKCodeMask", + "hkpKCodeValid", + "hkpType", + ): + getattr(dut, name).value = 0 + + +async def _pulse_status(dut, **values: int) -> None: + _clear_status_inputs(dut) + for name, value in values.items(): + getattr(dut, name).value = value + await _rx_cycle(dut) + _clear_status_inputs(dut) + + +async def _read_until(axil, address: int, expected: int, *, mask: int = 0xFFFFFFFF) -> int: + for _ in range(64): + value = await _axil_read(axil, address) + if (value & mask) == expected: + return value + raise AssertionError(f"AXI-Lite 0x{address:03X} did not reach 0x{expected:X}") + + +async def _setup_status_axil(dut) -> AxiLiteMaster: + cocotb.start_soon(Clock(dut.rxClk, 4.0, unit="ns").start()) + cocotb.start_soon(Clock(dut.S_AXI_ACLK, 7.0, unit="ns").start()) + + dut.rxRst.setimmediatevalue(1) + dut.S_AXI_ARESETN.setimmediatevalue(0) + _clear_status_inputs(dut) + + axil = AxiLiteMaster( + AxiLiteBus.from_prefix(dut, "S_AXI"), + dut.S_AXI_ACLK, + dut.S_AXI_ARESETN, + reset_active_level=False, + ) + + await _rx_cycle(dut, 8) + dut.S_AXI_ARESETN.value = 1 + dut.rxRst.value = 0 + await _rx_cycle(dut, 8) + return axil + + +@cocotb.test() +async def coaxpress_over_fiber_bridge_axil_status_registers_test(dut): + axil = await _setup_status_axil(dut) + + assert await _axil_read(axil, 0x000) == 0 + assert await _axil_read(axil, 0x020) == 0 + + await _pulse_status( + dut, + seqValid=1, + seqData=0x341200, + seqExpected=0x341201, + ) + + await _pulse_status( + dut, + rxError=1, + rxErrorCode=CXPOF_RX_ERR_SEQ_MISMATCH, + seqValid=1, + seqData=0x341203, + seqExpected=0x341204, + seqError=1, + seqErrorExpected=0x341202, + ) + + await _pulse_status( + dut, + hkpValid=1, + hkpData=CXP_EOP, + hkpEop=1, + hkpSof=1, + hkpWordCount=1, + hkpKCodeMask=CXP_ALL_CTRL_K, + hkpKCodeValid=1, + hkpType=CXPOF_HKP_TYPE_EOP, + ) + + await _pulse_status( + dut, + rxError=1, + rxAbort=1, + rxErrorCode=CXPOF_RX_ERR_PAYLOAD_ABORT, + ) + + expected_sticky = ( + STATUS_RX_ERROR + | STATUS_RX_ABORT + | STATUS_SEQ_VALID + | STATUS_SEQ_ERROR + | STATUS_HKP_VALID + ) + assert (await _read_until(axil, 0x000, expected_sticky, mask=0x3F)) & 0x3F == expected_sticky + + assert (await _read_until(axil, 0x004, CXPOF_RX_ERR_PAYLOAD_ABORT, mask=0xF)) & 0xF == CXPOF_RX_ERR_PAYLOAD_ABORT + assert (await _read_until(axil, 0x008, 0x341203, mask=0xFFFFFF)) & 0xFFFFFF == 0x341203 + assert (await _read_until(axil, 0x00C, 0x341204, mask=0xFFFFFF)) & 0xFFFFFF == 0x341204 + assert (await _read_until(axil, 0x010, 0x341202, mask=0xFFFFFF)) & 0xFFFFFF == 0x341202 + assert await _read_until(axil, 0x014, CXP_EOP) == CXP_EOP + + hkp_status = await _read_until(axil, 0x018, 1, mask=0xFF) + assert (hkp_status >> 0) & 0xFF == 1 + assert (hkp_status >> 8) & 0xF == CXP_ALL_CTRL_K + assert (hkp_status >> 12) & 0x1 == 1 + assert (hkp_status >> 16) & 0xF == CXPOF_HKP_TYPE_EOP + + assert await _read_until(axil, 0x020, 2) == 2 + assert await _read_until(axil, 0x024, 1) == 1 + assert await _read_until(axil, 0x028, 2) == 2 + assert await _read_until(axil, 0x02C, 1) == 1 + assert await _read_until(axil, 0x030, 1) == 1 + assert await _read_until(axil, 0x034, 0) == 0 + + await _axil_write(axil, 0x03C, 1) + assert (await _read_until(axil, 0x000, 0, mask=0x3F)) & 0x3F == 0 + assert await _read_until(axil, 0x020, 0) == 0 + + await _pulse_status( + dut, + rxError=1, + rxErrorCode=0x7, + hkpValid=1, + hkpError=1, + hkpData=0x11223344, + hkpWordCount=1, + hkpKCodeMask=0, + hkpKCodeValid=0, + hkpType=CXPOF_HKP_TYPE_K_CODE, + ) + + expected_hkp_error = STATUS_RX_ERROR | STATUS_HKP_VALID | STATUS_HKP_ERROR + assert (await _read_until(axil, 0x000, expected_hkp_error, mask=0x3F)) & 0x3F == expected_hkp_error + assert await _read_until(axil, 0x030, 1) == 1 + assert await _read_until(axil, 0x034, 1) == 1 + + +@cocotb.test() +async def coaxpress_over_fiber_bridge_axil_hkp_classification_sweep_test(dut): + axil = await _setup_status_axil(dut) + + hkp_cases = [ + (CXP_SOP, CXPOF_HKP_TYPE_SOP), + (CXP_EOP, CXPOF_HKP_TYPE_EOP), + (CXP_TRIG, CXPOF_HKP_TYPE_TRIG), + (CXP_IO_ACK, CXPOF_HKP_TYPE_IO_ACK), + (CXP_MARKER, CXPOF_HKP_TYPE_MARKER), + (0x9C5C3CBC, CXPOF_HKP_TYPE_K_CODE), + ] + + for index, (word, hkp_type) in enumerate(hkp_cases, start=1): + # The bridge RX leaf classifies HKP K-code payloads; this AXI-Lite block + # is the downstream software-visible consumer. Each pulse must replace + # the last-HKP registers after the async crossing without raising the + # HKP-error sticky bit or counter. + await _pulse_status( + dut, + hkpValid=1, + hkpData=word, + hkpWordCount=index, + hkpKCodeMask=CXP_ALL_CTRL_K, + hkpKCodeValid=1, + hkpType=hkp_type, + ) + + assert await _read_until(axil, 0x014, word) == word + hkp_status = await _read_until(axil, 0x018, index, mask=0xFF) + assert (hkp_status >> 0) & 0xFF == index + assert (hkp_status >> 8) & 0xF == CXP_ALL_CTRL_K + assert (hkp_status >> 12) & 0x1 == 1 + assert (hkp_status >> 16) & 0xF == hkp_type + + assert await _read_until(axil, 0x030, index) == index + assert await _read_until(axil, 0x034, 0) == 0 + + assert (await _read_until(axil, 0x000, STATUS_HKP_VALID, mask=0x30)) & 0x30 == STATUS_HKP_VALID + + +def test_CoaXPressOverFiberBridgeAxiL(): + run_surf_vhdl_test( + test_file=__file__, + toplevel="surf.coaxpressoverfiberbridgeaxilwrapper", + extra_vhdl_sources={ + "surf": [ + "axi/axi-lite/ip_integrator/SlaveAxiLiteIpIntegrator.vhd", + "protocols/coaxpress/core/rtl/CoaXPressPkg.vhd", + "protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeAxiL.vhd", + "protocols/coaxpress/core/wrappers/CoaXPressOverFiberBridgeAxiLWrapper.vhd", + ] + }, + ) diff --git a/tests/protocols/coaxpress/test_CoaXPressOverFiberBridgeRx.py b/tests/protocols/coaxpress/test_CoaXPressOverFiberBridgeRx.py index e95b1a83e5..5f84097071 100644 --- a/tests/protocols/coaxpress/test_CoaXPressOverFiberBridgeRx.py +++ b/tests/protocols/coaxpress/test_CoaXPressOverFiberBridgeRx.py @@ -29,6 +29,7 @@ from tests.common.regression_utils import run_surf_vhdl_test from tests.protocols.coaxpress.coaxpress_test_utils import ( CXP_EOP, + CXP_ALL_CTRL_K, CXP_IDLE, CXP_IDLE_K, CXP_IO_ACK, @@ -36,8 +37,18 @@ CXP_PKT_EVENT_ACK, CXP_SOP, CXPOF_ERROR, + CXPOF_HKP_TYPE_EOP, + CXPOF_HKP_TYPE_INVALID, + CXPOF_HKP_TYPE_K_CODE, CXPOF_IDLE, + CXPOF_RX_ERR_HKP_BAD_K_CODE, + CXPOF_RX_ERR_HKP_MALFORMED, + CXPOF_RX_ERR_IDLE_ERROR, + CXPOF_RX_ERR_PAYLOAD_ABORT, + CXPOF_RX_ERR_SEQ_MISMATCH, CXPOF_SEQ, + CXPOF_SOP_CTRL_HIGH_SPEED, + CXPOF_SOP_CTRL_HKP, CXPOF_START, CXPOF_TERM, cycle, @@ -48,7 +59,7 @@ def _cxp_start_word(packet_byte: int) -> int: - return CXPOF_START | (0x80 << 8) | ((CXP_SOP & 0xFF) << 16) | (packet_byte << 24) + return CXPOF_START | (CXPOF_SOP_CTRL_HIGH_SPEED << 8) | ((CXP_SOP & 0xFF) << 16) | (packet_byte << 24) def _control_in_lane(control_byte: int, lane: int) -> int: @@ -85,7 +96,7 @@ async def drive(rxd: int, rxc: int) -> None: await drive(0x07070707, 0xF) # Separate IO_ACK indication with no terminal payload word emitted. - dut.xgmiiRxd.value = CXPOF_START | (0x80 << 8) | ((CXP_IO_ACK & 0xFF) << 16) + dut.xgmiiRxd.value = CXPOF_START | (CXPOF_SOP_CTRL_HIGH_SPEED << 8) | ((CXP_IO_ACK & 0xFF) << 16) dut.xgmiiRxc.value = 0x1 await cycle(dut.clk, 1) sample = (int(dut.rxData.value), int(dut.rxDataK.value)) @@ -130,9 +141,9 @@ async def drive(rxd: int, rxc: int) -> None: await drive(0x07070707, 0xF) await drive(0x07070707, 0xF) - await drive(CXPOF_START | (0x81 << 8), 0x1) - await drive(0x5C5C5C5C, 0xF) - await drive(CXP_EOP, 0xF) + await drive(CXPOF_START | ((CXPOF_SOP_CTRL_HIGH_SPEED | CXPOF_SOP_CTRL_HKP) << 8), 0x1) + await drive(0x5C5C5C5C, 0x0) + await drive(0x07FD00FD, 0xC) await drive(0x07070707, 0xF) await drive(0x07070707, 0xF) @@ -144,6 +155,7 @@ async def drive(rxd: int, rxc: int) -> None: assert observed == [ (0x5C5C5C5C, 0xF), + (CXP_EOP, 0xF), (CXP_SOP, 0xF), (repeat_byte(CXP_PKT_EVENT_ACK), 0x0), (0x55667788, 0x0), @@ -153,10 +165,11 @@ async def drive(rxd: int, rxc: int) -> None: @cocotb.test() async def coaxpress_over_fiber_bridge_rx_sequence_error_and_recovery_test(dut): - # The current bridge RX does not implement a normative /Q/ ordered-set path; - # lock it down as a no-output guardrail, then prove an explicit /E/ in a - # payload aborts the packet without emitting a synthetic CXP EOP and the next - # packet still decodes cleanly. + # `/Q/` is tracked as a sequence counter: the first value initializes the + # expected sequence, a following increment is accepted, and a skipped value + # raises a sequence error while resynchronizing to later traffic. Then prove + # an explicit `/E/` in a payload reports a classified abort without emitting + # a synthetic CXP EOP and that the next packet still decodes cleanly. start_clock(dut.clk) dut.rst.setimmediatevalue(1) dut.xgmiiRxd.setimmediatevalue(0x07070707) @@ -164,16 +177,32 @@ async def coaxpress_over_fiber_bridge_rx_sequence_error_and_recovery_test(dut): await reset_dut(dut, clk_name="clk", reset_names=("rst",)) observed: list[tuple[int, int]] = [] + seq_samples: list[int] = [] + seq_errors: list[tuple[int, int]] = [] + error_codes: list[int] = [] + abort_pulses = 0 + error_pulses = 0 async def drive(rxd: int, rxc: int) -> None: + nonlocal abort_pulses, error_pulses dut.xgmiiRxd.value = rxd dut.xgmiiRxc.value = rxc await cycle(dut.clk, 1) sample = (int(dut.rxData.value), int(dut.rxDataK.value)) if sample != (CXP_IDLE, CXP_IDLE_K): observed.append(sample) + if int(dut.seqValid.value) == 1: + seq_samples.append(int(dut.seqData.value)) + if int(dut.seqError.value) == 1: + seq_errors.append((int(dut.seqData.value), int(dut.seqErrorExpected.value))) + if int(dut.rxError.value) == 1: + error_codes.append(int(dut.rxErrorCode.value)) + abort_pulses += int(dut.rxAbort.value) + error_pulses += int(dut.rxError.value) await drive(CXPOF_SEQ | (0x00 << 8) | (0x12 << 16) | (0x34 << 24), 0x1) + await drive(CXPOF_SEQ | (0x01 << 8) | (0x12 << 16) | (0x34 << 24), 0x1) + await drive(CXPOF_SEQ | (0x03 << 8) | (0x12 << 16) | (0x34 << 24), 0x1) await drive(0x07070707, 0xF) await drive(0x07070707, 0xF) @@ -189,6 +218,11 @@ async def drive(rxd: int, rxc: int) -> None: await drive(0x07070707, 0xF) await drive(0x07070707, 0xF) + assert seq_samples == [0x341200, 0x341201, 0x341203] + assert seq_errors == [(0x341203, 0x341202)] + assert abort_pulses == 1 + assert error_pulses == 2 + assert error_codes == [CXPOF_RX_ERR_SEQ_MISMATCH, CXPOF_RX_ERR_PAYLOAD_ABORT] assert observed == [ (CXP_SOP, 0xF), (repeat_byte(CXP_PKT_EVENT_ACK), 0x0), @@ -262,6 +296,7 @@ async def coaxpress_over_fiber_bridge_rx_hkp_eop_kcode_test(dut): await reset_dut(dut, clk_name="clk", reset_names=("rst",)) observed: list[tuple[int, int]] = [] + hkp_samples: list[tuple[int, int, int, int, int, int, int]] = [] async def drive(rxd: int, rxc: int) -> None: dut.xgmiiRxd.value = rxd @@ -270,8 +305,20 @@ async def drive(rxd: int, rxc: int) -> None: sample = (int(dut.rxData.value), int(dut.rxDataK.value)) if sample != (CXP_IDLE, CXP_IDLE_K): observed.append(sample) - - await drive(CXPOF_START | (0x81 << 8), 0x1) + if int(dut.hkpValid.value) == 1: + hkp_samples.append( + ( + int(dut.hkpData.value), + int(dut.hkpEop.value), + int(dut.hkpSof.value), + int(dut.hkpWordCount.value), + int(dut.hkpKCodeMask.value), + int(dut.hkpKCodeValid.value), + int(dut.hkpType.value), + ) + ) + + await drive(CXPOF_START | ((CXPOF_SOP_CTRL_HIGH_SPEED | CXPOF_SOP_CTRL_HKP) << 8), 0x1) await drive(CXP_EOP, 0x0) await drive(0x07070707, 0xF) await drive(0x07070707, 0xF) @@ -282,6 +329,7 @@ async def drive(rxd: int, rxc: int) -> None: await drive(0x07070707, 0xF) await drive(0x07070707, 0xF) + assert hkp_samples == [(CXP_EOP, 1, 1, 1, CXP_ALL_CTRL_K, 1, CXPOF_HKP_TYPE_EOP)] assert observed == [ (CXP_EOP, 0xF), (CXP_SOP, 0xF), @@ -304,14 +352,20 @@ async def coaxpress_over_fiber_bridge_rx_error_after_sop_recovery_test(dut): await reset_dut(dut, clk_name="clk", reset_names=("rst",)) observed: list[tuple[int, int]] = [] + abort_pulses = 0 + error_codes: list[int] = [] async def drive(rxd: int, rxc: int) -> None: + nonlocal abort_pulses dut.xgmiiRxd.value = rxd dut.xgmiiRxc.value = rxc await cycle(dut.clk, 1) sample = (int(dut.rxData.value), int(dut.rxDataK.value)) if sample != (CXP_IDLE, CXP_IDLE_K): observed.append(sample) + abort_pulses += int(dut.rxAbort.value) + if int(dut.rxError.value) == 1: + error_codes.append(int(dut.rxErrorCode.value)) await drive(_cxp_start_word(CXP_PKT_EVENT_ACK), 0x1) await drive(CXPOF_ERROR | (CXPOF_IDLE << 8) | (CXPOF_IDLE << 16) | (CXPOF_IDLE << 24), 0x1) @@ -324,6 +378,8 @@ async def drive(rxd: int, rxc: int) -> None: await drive(0x07070707, 0xF) await drive(0x07070707, 0xF) + assert abort_pulses == 1 + assert error_codes == [CXPOF_RX_ERR_PAYLOAD_ABORT] assert observed == [ (CXP_SOP, 0xF), (repeat_byte(CXP_PKT_EVENT_ACK), 0x0), @@ -334,11 +390,39 @@ async def drive(rxd: int, rxc: int) -> None: ] +@cocotb.test() +async def coaxpress_over_fiber_bridge_rx_idle_error_code_test(dut): + # `/E/` while idle is still an error/abort indication, but it has a distinct + # cause code from an active-packet abort. + start_clock(dut.clk) + dut.rst.setimmediatevalue(1) + dut.xgmiiRxd.setimmediatevalue(0x07070707) + dut.xgmiiRxc.setimmediatevalue(0xF) + await reset_dut(dut, clk_name="clk", reset_names=("rst",)) + + error_codes: list[int] = [] + abort_pulses = 0 + + async def drive(rxd: int, rxc: int) -> None: + nonlocal abort_pulses + dut.xgmiiRxd.value = rxd + dut.xgmiiRxc.value = rxc + await cycle(dut.clk, 1) + abort_pulses += int(dut.rxAbort.value) + if int(dut.rxError.value) == 1: + error_codes.append(int(dut.rxErrorCode.value)) + + await drive(CXPOF_ERROR | (CXPOF_IDLE << 8) | (CXPOF_IDLE << 16) | (CXPOF_IDLE << 24), 0x1) + await drive(0x07070707, 0xF) + + assert abort_pulses == 1 + assert error_codes == [CXPOF_RX_ERR_IDLE_ERROR] + + @cocotb.test() async def coaxpress_over_fiber_bridge_rx_hkp_then_payload_mix_test(dut): - # A housekeeping start word may be followed by one raw K-coded HKP word and - # then normal data/EOP handling. This locks down the current RTL contract for - # the HKP-to-payload transition without claiming full housekeeping semantics. + # A housekeeping start word may be followed by one K-code HKP word with + # nGMII control flags clear and then normal data/EOP handling. start_clock(dut.clk) dut.rst.setimmediatevalue(1) dut.xgmiiRxd.setimmediatevalue(0x07070707) @@ -346,6 +430,7 @@ async def coaxpress_over_fiber_bridge_rx_hkp_then_payload_mix_test(dut): await reset_dut(dut, clk_name="clk", reset_names=("rst",)) observed: list[tuple[int, int]] = [] + hkp_samples: list[tuple[int, int, int, int, int, int, int]] = [] async def drive(rxd: int, rxc: int) -> None: dut.xgmiiRxd.value = rxd @@ -354,15 +439,28 @@ async def drive(rxd: int, rxc: int) -> None: sample = (int(dut.rxData.value), int(dut.rxDataK.value)) if sample != (CXP_IDLE, CXP_IDLE_K): observed.append(sample) + if int(dut.hkpValid.value) == 1: + hkp_samples.append( + ( + int(dut.hkpData.value), + int(dut.hkpEop.value), + int(dut.hkpSof.value), + int(dut.hkpWordCount.value), + int(dut.hkpKCodeMask.value), + int(dut.hkpKCodeValid.value), + int(dut.hkpType.value), + ) + ) hkp_word = 0x9C5C3CBC - await drive(CXPOF_START | (0x81 << 8), 0x1) - await drive(hkp_word, 0xF) + await drive(CXPOF_START | ((CXPOF_SOP_CTRL_HIGH_SPEED | CXPOF_SOP_CTRL_HKP) << 8), 0x1) + await drive(hkp_word, 0x0) await drive(0x10203040, 0x0) await drive(0x07FD00FD, 0xC) await drive(0x07070707, 0xF) await drive(0x07070707, 0xF) + assert hkp_samples == [(hkp_word, 0, 1, 1, CXP_ALL_CTRL_K, 1, CXPOF_HKP_TYPE_K_CODE)] assert observed == [ (hkp_word, 0xF), (0x10203040, 0x0), @@ -370,6 +468,73 @@ async def drive(rxd: int, rxc: int) -> None: ] +@cocotb.test() +async def coaxpress_over_fiber_bridge_rx_hkp_malformed_status_test(dut): + # HKP words carry K-code byte values without nGMII control flags. A nonzero + # control mask is malformed even when the byte values themselves are K codes. + start_clock(dut.clk) + dut.rst.setimmediatevalue(1) + dut.xgmiiRxd.setimmediatevalue(0x07070707) + dut.xgmiiRxc.setimmediatevalue(0xF) + await reset_dut(dut, clk_name="clk", reset_names=("rst",)) + + hkp_errors: list[int] = [] + error_codes: list[int] = [] + + async def drive(rxd: int, rxc: int) -> None: + dut.xgmiiRxd.value = rxd + dut.xgmiiRxc.value = rxc + await cycle(dut.clk, 1) + if int(dut.hkpError.value) == 1: + hkp_errors.append(int(dut.hkpWordCount.value)) + if int(dut.rxError.value) == 1: + error_codes.append(int(dut.rxErrorCode.value)) + + await drive(CXPOF_START | ((CXPOF_SOP_CTRL_HIGH_SPEED | CXPOF_SOP_CTRL_HKP) << 8), 0x1) + await drive(0x5C5C3CBC, 0x5) + await drive(0x07070707, 0xF) + + assert hkp_errors == [1] + assert error_codes == [CXPOF_RX_ERR_HKP_MALFORMED] + + +@cocotb.test() +async def coaxpress_over_fiber_bridge_rx_hkp_bad_kcode_status_test(dut): + # HKP is a K-code payload, not arbitrary raw data. Non-K-code bytes are + # forwarded for observability but classified separately from bad control + # masks. + start_clock(dut.clk) + dut.rst.setimmediatevalue(1) + dut.xgmiiRxd.setimmediatevalue(0x07070707) + dut.xgmiiRxc.setimmediatevalue(0xF) + await reset_dut(dut, clk_name="clk", reset_names=("rst",)) + + hkp_samples: list[tuple[int, int, int]] = [] + error_codes: list[int] = [] + + async def drive(rxd: int, rxc: int) -> None: + dut.xgmiiRxd.value = rxd + dut.xgmiiRxc.value = rxc + await cycle(dut.clk, 1) + if int(dut.hkpValid.value) == 1: + hkp_samples.append( + ( + int(dut.hkpKCodeMask.value), + int(dut.hkpKCodeValid.value), + int(dut.hkpType.value), + ) + ) + if int(dut.rxError.value) == 1: + error_codes.append(int(dut.rxErrorCode.value)) + + await drive(CXPOF_START | ((CXPOF_SOP_CTRL_HIGH_SPEED | CXPOF_SOP_CTRL_HKP) << 8), 0x1) + await drive(0x11223344, 0x0) + await drive(0x07070707, 0xF) + + assert hkp_samples == [(0x0, 0, CXPOF_HKP_TYPE_INVALID)] + assert error_codes == [CXPOF_RX_ERR_HKP_BAD_K_CODE] + + @cocotb.test() async def coaxpress_over_fiber_bridge_rx_control_lane_guardrail_sweep_test(dut): # `/S/`, `/Q/`, `/T/`, and `/E/` are lane-sensitive XGMII control bytes. @@ -420,11 +585,12 @@ async def drive(rxd: int, rxc: int) -> None: def test_CoaXPressOverFiberBridgeRx(): run_surf_vhdl_test( test_file=__file__, - toplevel="surf.coaxpressoverfiberbridgerx", + toplevel="surf.coaxpressoverfiberbridgerxstatuswrapper", extra_vhdl_sources={ "surf": [ "protocols/coaxpress/core/rtl/CoaXPressPkg.vhd", "protocols/coaxpress/core/rtl/CoaXPressOverFiberBridgeRx.vhd", + "protocols/coaxpress/core/wrappers/CoaXPressOverFiberBridgeRxStatusWrapper.vhd", ] }, ) diff --git a/tests/protocols/coaxpress/test_CoaXPressRx.py b/tests/protocols/coaxpress/test_CoaXPressRx.py index fcef05d0e0..f2fcd74130 100644 --- a/tests/protocols/coaxpress/test_CoaXPressRx.py +++ b/tests/protocols/coaxpress/test_CoaXPressRx.py @@ -40,6 +40,7 @@ CXP_PKT_EVENT, CXP_PKT_IMAGE_HEADER, CXP_PKT_IMAGE_LINE, + CXP_PKT_STREAM_DATA, CXP_SOP, append_snapshot_if_valid, cxp_crc_word, @@ -114,7 +115,6 @@ 0x00200010, ] - def _event_crc_words(*, event_bytes: tuple[int, int, int, int], packet_tag: int, payload_words: list[int]) -> list[int]: crc_inputs = [ *[repeat_byte(byte) for byte in event_bytes], @@ -129,6 +129,41 @@ def _event_crc_words(*, event_bytes: tuple[int, int, int, int], packet_tag: int, ] +def _control_ack_crc_words(*, ack_code: int, size_word: int, data_word: int) -> list[int]: + crc_inputs = [repeat_byte(ack_code), size_word, data_word] + return [ + *crc_inputs, + cxp_crc_word(crc_inputs), + ] + + +def _stream_packet_sequence( + *, + stream_id: int, + packet_tag: int, + payload_items: list[tuple[int, int]], +) -> list[tuple[int, int]]: + payload_words = [data for data, _data_k in payload_items] + crc_inputs = [ + repeat_byte(stream_id), + repeat_byte(packet_tag), + repeat_byte((len(payload_words) >> 8) & 0xFF), + repeat_byte(len(payload_words) & 0xFF), + *payload_words, + ] + return [ + (CXP_SOP, 0xF), + (repeat_byte(CXP_PKT_STREAM_DATA), 0x0), + (repeat_byte(stream_id), 0x0), + (repeat_byte(packet_tag), 0x0), + (repeat_byte((len(payload_words) >> 8) & 0xFF), 0x0), + (repeat_byte(len(payload_words) & 0xFF), 0x0), + *payload_items, + (cxp_crc_word(crc_inputs), 0x0), + (CXP_EOP, 0xF), + ] + + def _pack_lane_nibbles(values: list[int]) -> int: packed = 0 for index, value in enumerate(values): @@ -186,10 +221,12 @@ def _capture_outputs( event_tags: list[int], trig_ack_cycles: list[int], cycle_index: int, + event_beats: list[tuple[int, int, int, int, int]] | None = None, ) -> None: cfg_samples: list[dict[str, int]] = [] data_samples: list[dict[str, int]] = [] hdr_samples: list[dict[str, int]] = [] + event_samples: list[dict[str, int]] = [] append_snapshot_if_valid(cfg_samples, dut, valid_name="cfgTValid", field_names=("cfgTData", "cfgTKeep", "cfgTLast")) append_snapshot_if_valid( data_samples, @@ -203,6 +240,13 @@ def _capture_outputs( valid_name="hdrTValid", field_names=("hdrTData", "hdrTKeep", "hdrTLast", "hdrTUser"), ) + if event_beats is not None: + append_snapshot_if_valid( + event_samples, + dut, + valid_name="eventTValid", + field_names=("eventTData", "eventTKeep", "eventTLast", "eventTDest", "eventTUser"), + ) cfg_beats.extend((sample["cfgTData"], sample["cfgTKeep"], sample["cfgTLast"]) for sample in cfg_samples) data_beats.extend( (sample["dataTData"], sample["dataTKeep"], sample["dataTLast"], sample["dataTUser"]) for sample in data_samples @@ -210,6 +254,17 @@ def _capture_outputs( hdr_beats.extend( (sample["hdrTData"], sample["hdrTKeep"], sample["hdrTLast"], sample["hdrTUser"]) for sample in hdr_samples ) + if event_beats is not None: + event_beats.extend( + ( + sample["eventTData"], + sample["eventTKeep"], + sample["eventTLast"], + sample["eventTDest"], + sample["eventTUser"], + ) + for sample in event_samples + ) if int(dut.eventAck.value) == 1: event_tags.append(int(dut.eventTag.value)) if int(dut.trigAck.value) == 1: @@ -261,24 +316,24 @@ def _isolated_lane_frame_sequence( header_payload_words[corrupt_header_index] = corrupt_header_word return [ - (CXP_SOP, 0xF), - (repeat_byte(0x01), 0x0), - (repeat_byte(stream_id), 0x0), - (repeat_byte(packet_tag), 0x0), - (repeat_byte((len(header_payload_words) + 2) >> 8), 0x0), - (repeat_byte((len(header_payload_words) + 2) & 0xFF), 0x0), - (CXP_MARKER, 0xF), - (repeat_byte(CXP_PKT_IMAGE_HEADER), 0x0), - *[(word, 0xF) for word in header_payload_words], - (CXP_SOP, 0xF), - (repeat_byte(0x01), 0x0), - (repeat_byte((stream_id + 1) & 0xFF), 0x0), - (repeat_byte((packet_tag + 1) & 0xFF), 0x0), - (repeat_byte((len(line_words) + 2) >> 8), 0x0), - (repeat_byte((len(line_words) + 2) & 0xFF), 0x0), - (CXP_MARKER, 0xF), - (repeat_byte(CXP_PKT_IMAGE_LINE), 0x0), - *[(word, 0x0) for word in line_words], + *_stream_packet_sequence( + stream_id=stream_id, + packet_tag=packet_tag, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_HEADER), 0x0), + *[(word, 0xF) for word in header_payload_words], + ], + ), + *_stream_packet_sequence( + stream_id=(stream_id + 1) & 0xFF, + packet_tag=(packet_tag + 1) & 0xFF, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_LINE), 0x0), + *[(word, 0x0) for word in line_words], + ], + ), ] @@ -354,27 +409,75 @@ async def _send_one_lane_frame( line_packet_tag: int = 0x55, ) -> None: sequence = [ - (CXP_SOP, 0xF), - (repeat_byte(0x01), 0x0), - (repeat_byte(header_stream_id), 0x0), - (repeat_byte(header_packet_tag), 0x0), - (repeat_byte(0x00), 0x0), - (repeat_byte(25), 0x0), - (CXP_MARKER, 0xF), - (repeat_byte(CXP_PKT_IMAGE_HEADER), 0x0), - *[(word, 0xF) for word in SINGLE_LINE_HEADER_WORDS], - (CXP_SOP, 0xF), - (repeat_byte(0x01), 0x0), - (repeat_byte(line_stream_id), 0x0), - (repeat_byte(line_packet_tag), 0x0), - (repeat_byte(0x00), 0x0), - (repeat_byte(3), 0x0), - (CXP_MARKER, 0xF), - (repeat_byte(CXP_PKT_IMAGE_LINE), 0x0), - (line_word, 0x0), + *_stream_packet_sequence( + stream_id=header_stream_id, + packet_tag=header_packet_tag, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_HEADER), 0x0), + *[(word, 0xF) for word in SINGLE_LINE_HEADER_WORDS], + ], + ), + *_stream_packet_sequence( + stream_id=line_stream_id, + packet_tag=line_packet_tag, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_LINE), 0x0), + (line_word, 0x0), + ], + ), + ] + for data, data_k in sequence: + await send_rx_word(dut, data=data, data_k=data_k, clk=dut.rxClk) + + +async def _send_one_lane_frame_and_capture( + dut, + *, + line_word: int, + data_beats: list[tuple[int, int, int, int]], + hdr_beats: list[tuple[int, int, int, int]], + header_stream_id: int = 0x22, + header_packet_tag: int = 0x33, + line_stream_id: int = 0x44, + line_packet_tag: int = 0x55, + start_cycle_index: int = 0, +) -> int: + sequence = [ + *_stream_packet_sequence( + stream_id=header_stream_id, + packet_tag=header_packet_tag, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_HEADER), 0x0), + *[(word, 0xF) for word in SINGLE_LINE_HEADER_WORDS], + ], + ), + *_stream_packet_sequence( + stream_id=line_stream_id, + packet_tag=line_packet_tag, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_LINE), 0x0), + (line_word, 0x0), + ], + ), ] + cycle_index = start_cycle_index for data, data_k in sequence: await send_rx_word(dut, data=data, data_k=data_k, clk=dut.rxClk) + _capture_outputs( + dut, + cfg_beats=[], + data_beats=data_beats, + hdr_beats=hdr_beats, + event_tags=[], + trig_ack_cycles=[], + cycle_index=cycle_index, + ) + cycle_index += 1 + return cycle_index async def _count_signal_high_cycles(signal, clk, stop_event: Event, counts: dict[str, int], key: str) -> None: @@ -453,6 +556,7 @@ async def coaxpress_rx_one_lane_integration_test(dut): "rxNumberOfLane": 0, "dataTReady": 1, "hdrTReady": 1, + "eventTReady": 1, }, ) await reset_signals( @@ -466,16 +570,14 @@ async def coaxpress_rx_one_lane_integration_test(dut): cfg_beats: list[tuple[int, int, int]] = [] data_beats: list[tuple[int, int, int, int]] = [] hdr_beats: list[tuple[int, int, int, int]] = [] + event_beats: list[tuple[int, int, int, int, int]] = [] event_tags: list[int] = [] trig_ack_cycles: list[int] = [] sequence = [ (CXP_SOP, 0xF), (repeat_byte(CXP_PKT_CTRL_ACK_NO_TAG), 0x0), - (repeat_byte(0x00), 0x0), - (0x04000000, 0x0), - (0x01234567, 0x0), - (0xCAFEBABE, 0x0), + *[(word, 0x0) for word in _control_ack_crc_words(ack_code=0x00, size_word=0x04000000, data_word=0x01234567)], (CXP_EOP, 0xF), (CXP_SOP, 0xF), (repeat_byte(CXP_PKT_EVENT), 0x0), @@ -490,28 +592,26 @@ async def coaxpress_rx_one_lane_integration_test(dut): (CXP_EOP, 0xF), (CXP_IO_ACK, 0xF), (repeat_byte(0x01), 0x0), - (CXP_SOP, 0xF), - (repeat_byte(0x01), 0x0), - (repeat_byte(0x22), 0x0), - (repeat_byte(0x33), 0x0), - (repeat_byte(0x00), 0x0), - (repeat_byte(25), 0x0), - (CXP_MARKER, 0xF), - (repeat_byte(CXP_PKT_IMAGE_HEADER), 0x0), - *[(word, 0xF) for word in HEADER_WORDS], - (CXP_SOP, 0xF), - (repeat_byte(0x01), 0x0), - (repeat_byte(0x44), 0x0), - (repeat_byte(0x55), 0x0), - (repeat_byte(0x00), 0x0), - (repeat_byte(5), 0x0), - (CXP_MARKER, 0xF), - (repeat_byte(CXP_PKT_IMAGE_LINE), 0x0), - (0x11111111, 0x0), - (0x22222222, 0x0), - (0x33333333, 0x0), - (0xBEEFBEEF, 0x0), - (CXP_EOP, 0xF), + *_stream_packet_sequence( + stream_id=0x22, + packet_tag=0x33, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_HEADER), 0x0), + *[(word, 0xF) for word in HEADER_WORDS], + ], + ), + *_stream_packet_sequence( + stream_id=0x44, + packet_tag=0x55, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_LINE), 0x0), + (0x11111111, 0x0), + (0x22222222, 0x0), + (0x33333333, 0x0), + ], + ), ] for cycle_index, (data, data_k) in enumerate(sequence): @@ -524,6 +624,7 @@ async def coaxpress_rx_one_lane_integration_test(dut): event_tags=event_tags, trig_ack_cycles=trig_ack_cycles, cycle_index=cycle_index, + event_beats=event_beats, ) for cycle_index in range(40): @@ -536,9 +637,11 @@ async def coaxpress_rx_one_lane_integration_test(dut): event_tags=event_tags, trig_ack_cycles=trig_ack_cycles, cycle_index=cycle_index + len(sequence), + event_beats=event_beats, ) assert cfg_beats == [(0x0123456700000000, 0xFF, 0)] + assert event_beats == [(0x11223344, 0xF, 1, 0x5A, 0x13121110)] assert event_tags == [0x5A] assert trig_ack_cycles assert [beat[:3] for beat in hdr_beats] == [(word, 0xF, 1 if index == len(EXPECTED_HDR_WORDS) - 1 else 0) for index, word in enumerate(EXPECTED_HDR_WORDS)] @@ -550,11 +653,9 @@ async def coaxpress_rx_one_lane_integration_test(dut): ] -@cocotb.test() -async def coaxpress_rx_two_lane_mux_rotation_test(dut): - if env_int("NUM_LANES_G", default=1) != 2: - return - +async def _drive_two_lane_mux_rotation( + dut, +) -> tuple[list[tuple[int, int, int, int]], list[tuple[int, int, int, int]], list[int]]: start_lockstep_clocks(dut.dataClk, dut.cfgClk, dut.txClk, dut.rxClk, period_ns=4.0) set_initial_values( dut, @@ -566,6 +667,7 @@ async def coaxpress_rx_two_lane_mux_rotation_test(dut): "rxNumberOfLane": 1, "dataTReady": 1, "hdrTReady": 1, + "eventTReady": 1, }, ) await reset_signals( @@ -578,6 +680,7 @@ async def coaxpress_rx_two_lane_mux_rotation_test(dut): data_beats: list[tuple[int, int, int, int]] = [] hdr_beats: list[tuple[int, int, int, int]] = [] + error_cycles: list[int] = [] async def capture(cycle_index: int) -> None: _capture_outputs( @@ -589,53 +692,55 @@ async def capture(cycle_index: int) -> None: trig_ack_cycles=[], cycle_index=cycle_index, ) - - lane0_sequence = [ - ([CXP_SOP, CXP_IDLE], [0xF, CXP_IDLE_K]), - ([repeat_byte(0x01), CXP_IDLE], [0x0, CXP_IDLE_K]), - ([repeat_byte(0x22), CXP_IDLE], [0x0, CXP_IDLE_K]), - ([repeat_byte(0x33), CXP_IDLE], [0x0, CXP_IDLE_K]), - ([repeat_byte(0x00), CXP_IDLE], [0x0, CXP_IDLE_K]), - ([repeat_byte(25), CXP_IDLE], [0x0, CXP_IDLE_K]), - ([CXP_MARKER, CXP_IDLE], [0xF, CXP_IDLE_K]), - ([repeat_byte(CXP_PKT_IMAGE_HEADER), CXP_IDLE], [0x0, CXP_IDLE_K]), - *[([word, CXP_IDLE], [0xF, CXP_IDLE_K]) for word in HEADER_WORDS], - ([CXP_SOP, CXP_IDLE], [0xF, CXP_IDLE_K]), - ([repeat_byte(0x01), CXP_IDLE], [0x0, CXP_IDLE_K]), - ([repeat_byte(0x44), CXP_IDLE], [0x0, CXP_IDLE_K]), - ([repeat_byte(0x55), CXP_IDLE], [0x0, CXP_IDLE_K]), - ([repeat_byte(0x00), CXP_IDLE], [0x0, CXP_IDLE_K]), - ([repeat_byte(5), CXP_IDLE], [0x0, CXP_IDLE_K]), - ([CXP_MARKER, CXP_IDLE], [0xF, CXP_IDLE_K]), - ([repeat_byte(CXP_PKT_IMAGE_LINE), CXP_IDLE], [0x0, CXP_IDLE_K]), - ([0x11111111, CXP_IDLE], [0x0, CXP_IDLE_K]), - ([0x22222222, CXP_IDLE], [0x0, CXP_IDLE_K]), - ([0x33333333, CXP_IDLE], [0x0, CXP_IDLE_K]), - ([CXP_EOP, CXP_IDLE], [0xF, CXP_IDLE_K]), + if int(dut.rxFsmError.value) == 1: + error_cycles.append(cycle_index) + + lane0_packets = [ + *_stream_packet_sequence( + stream_id=0x22, + packet_tag=0x33, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_HEADER), 0x0), + *[(word, 0xF) for word in HEADER_WORDS], + ], + ), + *_stream_packet_sequence( + stream_id=0x44, + packet_tag=0x55, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_LINE), 0x0), + (0x11111111, 0x0), + (0x22222222, 0x0), + (0x33333333, 0x0), + ], + ), ] - lane1_sequence = [ - ([CXP_IDLE, CXP_SOP], [CXP_IDLE_K, 0xF]), - ([CXP_IDLE, repeat_byte(0x01)], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, repeat_byte(0x22)], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, repeat_byte(0x33)], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, repeat_byte(0x00)], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, repeat_byte(25)], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, CXP_MARKER], [CXP_IDLE_K, 0xF]), - ([CXP_IDLE, repeat_byte(CXP_PKT_IMAGE_HEADER)], [CXP_IDLE_K, 0x0]), - *[([CXP_IDLE, word], [CXP_IDLE_K, 0xF]) for word in HEADER_WORDS], - ([CXP_IDLE, CXP_SOP], [CXP_IDLE_K, 0xF]), - ([CXP_IDLE, repeat_byte(0x01)], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, repeat_byte(0x44)], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, repeat_byte(0x55)], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, repeat_byte(0x00)], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, repeat_byte(5)], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, CXP_MARKER], [CXP_IDLE_K, 0xF]), - ([CXP_IDLE, repeat_byte(CXP_PKT_IMAGE_LINE)], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, 0x44444444], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, 0x55555555], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, 0x66666666], [CXP_IDLE_K, 0x0]), - ([CXP_IDLE, CXP_EOP], [CXP_IDLE_K, 0xF]), + lane1_packets = [ + *_stream_packet_sequence( + stream_id=0x22, + packet_tag=0x33, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_HEADER), 0x0), + *[(word, 0xF) for word in HEADER_WORDS], + ], + ), + *_stream_packet_sequence( + stream_id=0x44, + packet_tag=0x55, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_LINE), 0x0), + (0x44444444, 0x0), + (0x55555555, 0x0), + (0x66666666, 0x0), + ], + ), ] + lane0_sequence = [([data, CXP_IDLE], [data_k, CXP_IDLE_K]) for data, data_k in lane0_packets] + lane1_sequence = [([CXP_IDLE, data], [CXP_IDLE_K, data_k]) for data, data_k in lane1_packets] cycle_index = 0 for lane_words, lane_ks in lane0_sequence: @@ -658,19 +763,129 @@ async def capture(cycle_index: int) -> None: await capture(cycle_index) cycle_index += 1 + return hdr_beats, data_beats, error_cycles + + +@cocotb.test() +async def coaxpress_rx_two_lane_mux_rotation_test(dut): + if env_int("NUM_LANES_G", default=1) != 2: + return + + hdr_beats, data_beats, error_cycles = await _drive_two_lane_mux_rotation(dut) + + assert not error_cycles assert [beat[0] for beat in hdr_beats] == EXPECTED_HDR_WORDS * 2 - assert [beat[0] for beat in data_beats[:3]] == [0x11111111, 0x22222222, 0x33333333] - assert 0x44444444 in [beat[0] for beat in data_beats] - assert 0x55555555 in [beat[0] for beat in data_beats] - assert any(beat[2] == 1 for beat in data_beats) - - -# -# Opt-in investigation benches. These stay behind RUN_KNOWN_ISSUE_TESTS until -# the remaining 4-lane short-frame boundary issue in CoaXPressRxHsFsm is fixed. -# -@cocotb.test(skip=os.getenv("RUN_KNOWN_ISSUE_TESTS") != "1") -async def coaxpress_rx_four_lane_fsm_error_reset_recovery_known_issue_test(dut): + assert [beat[0] for beat in data_beats] == [ + 0x11111111, + 0x22222222, + 0x33333333, + 0x44444444, + 0x55555555, + 0x66666666, + ] + assert [beat[2] for beat in data_beats] == [0, 0, 1, 0, 0, 1] + + +@cocotb.test() +async def coaxpress_rx_lane_parser_error_status_recovery_test(dut): + if env_int("NUM_LANES_G", default=1) != 1: + return + + start_lockstep_clocks(dut.dataClk, dut.cfgClk, dut.txClk, dut.rxClk, period_ns=4.0) + set_initial_values( + dut, + { + "rxData": 0, + "rxDataK": 0, + "rxLinkUp": 1, + "rxFsmRst": 0, + "rxNumberOfLane": 0, + "dataTReady": 1, + "hdrTReady": 1, + "eventTReady": 1, + }, + ) + await reset_signals( + dut, + clk=dut.rxClk, + reset_names=("dataRst", "cfgRst", "txRst", "rxRst"), + assert_cycles=4, + release_cycles=4, + ) + + signal_counts = {"error_pulses": 0} + stop_event = Event() + monitor_task = cocotb.start_soon(_count_signal_high_cycles(dut.rxFsmError, dut.rxClk, stop_event, signal_counts, "error_pulses")) + + bad_line_packet = _stream_packet_sequence( + stream_id=0x44, + packet_tag=0x55, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_LINE), 0x0), + (0x11111111, 0x0), + ], + ) + bad_line_packet[-2] = (bad_line_packet[-2][0] ^ 0x00000001, bad_line_packet[-2][1]) + + bad_data_beats: list[tuple[int, int, int, int]] = [] + bad_hdr_beats: list[tuple[int, int, int, int]] = [] + cycle_index = 0 + + for data, data_k in [ + *_stream_packet_sequence( + stream_id=0x22, + packet_tag=0x33, + payload_items=[ + (CXP_MARKER, 0xF), + (repeat_byte(CXP_PKT_IMAGE_HEADER), 0x0), + *[(word, 0xF) for word in SINGLE_LINE_HEADER_WORDS], + ], + ), + *bad_line_packet, + ]: + await send_rx_word(dut, data=data, data_k=data_k, clk=dut.rxClk) + _capture_outputs( + dut, + cfg_beats=[], + data_beats=bad_data_beats, + hdr_beats=bad_hdr_beats, + event_tags=[], + trig_ack_cycles=[], + cycle_index=cycle_index, + ) + cycle_index += 1 + + await _drive_idle_and_capture( + dut, + cycles=32, + data_beats=bad_data_beats, + hdr_beats=bad_hdr_beats, + start_cycle_index=cycle_index, + ) + + data_beats: list[tuple[int, int, int, int]] = [] + hdr_beats: list[tuple[int, int, int, int]] = [] + cycle_index = await _send_one_lane_frame_and_capture( + dut, + line_word=0x22222222, + data_beats=data_beats, + hdr_beats=hdr_beats, + ) + await _drive_idle_and_capture(dut, cycles=64, data_beats=data_beats, hdr_beats=hdr_beats, start_cycle_index=cycle_index) + + stop_event.set() + await monitor_task + + assert signal_counts["error_pulses"] >= 1, signal_counts + assert bad_data_beats == [(0x11111111, 0xF, 1, 1)], bad_data_beats + assert [beat[0] for beat in data_beats] == [0x22222222], data_beats + assert [beat[2] for beat in data_beats] == [1], data_beats + assert [beat[3] for beat in data_beats] == [0], data_beats + + +@cocotb.test() +async def coaxpress_rx_four_lane_fsm_error_reset_recovery_test(dut): if env_int("NUM_LANES_G", default=1) != 4: return @@ -685,6 +900,7 @@ async def coaxpress_rx_four_lane_fsm_error_reset_recovery_known_issue_test(dut): "rxNumberOfLane": 3, "dataTReady": 1, "hdrTReady": 1, + "eventTReady": 1, }, ) await reset_signals( @@ -739,8 +955,8 @@ async def coaxpress_rx_four_lane_fsm_error_reset_recovery_known_issue_test(dut): assert observed_recovery_last == [0, 0, 1] * 4, observed_recovery_last -@cocotb.test(skip=os.getenv("RUN_KNOWN_ISSUE_TESTS") != "1") -async def coaxpress_rx_four_lane_clean_rotation_known_issue_test(dut): +@cocotb.test() +async def coaxpress_rx_four_lane_clean_rotation_test(dut): if env_int("NUM_LANES_G", default=1) != 4: return @@ -755,6 +971,7 @@ async def coaxpress_rx_four_lane_clean_rotation_known_issue_test(dut): "rxNumberOfLane": 3, "dataTReady": 1, "hdrTReady": 1, + "eventTReady": 1, }, ) await reset_signals( @@ -797,8 +1014,8 @@ async def coaxpress_rx_four_lane_clean_rotation_known_issue_test(dut): assert [beat[2] for beat in data_beats] == [0, 0, 1] * 4, data_beats -@cocotb.test(skip=os.getenv("RUN_KNOWN_ISSUE_TESTS") != "1") -async def coaxpress_rx_four_lane_fsm_error_recovery_known_issue_test(dut): +@cocotb.test() +async def coaxpress_rx_four_lane_fsm_error_recovery_test(dut): if env_int("NUM_LANES_G", default=1) != 4: return @@ -813,6 +1030,7 @@ async def coaxpress_rx_four_lane_fsm_error_recovery_known_issue_test(dut): "rxNumberOfLane": 3, "dataTReady": 1, "hdrTReady": 1, + "eventTReady": 1, }, ) await reset_signals( @@ -879,16 +1097,13 @@ async def coaxpress_rx_four_lane_fsm_error_recovery_known_issue_test(dut): assert signal_counts["error_pulses"] > 0 assert find_subsequence(observed_header_words, EXPECTED_HDR_WORDS) is not None, observed_header_words subseq_start = find_subsequence(observed_data_words, expected_recovery_words) - # Known issue under investigation: - # a malformed 4-lane header does raise rxFsmError, but the current RTL does - # not fully recover the expected post-error lane rotation and line payloads. assert subseq_start is not None, data_beats observed_recovery_last = [beat[2] for beat in data_beats[subseq_start : subseq_start + len(expected_recovery_words)]] assert observed_recovery_last == [0, 0, 1] * 4, observed_recovery_last -@cocotb.test(skip=os.getenv("RUN_KNOWN_ISSUE_TESTS") != "1") -async def coaxpress_rx_four_lane_overflow_reset_recovery_known_issue_test(dut): +@cocotb.test(skip=os.getenv("RUN_STRESS_TESTS") != "1") +async def coaxpress_rx_four_lane_overflow_reset_recovery_stress_test(dut): if env_int("NUM_LANES_G", default=1) != 4: return @@ -903,6 +1118,7 @@ async def coaxpress_rx_four_lane_overflow_reset_recovery_known_issue_test(dut): "rxNumberOfLane": 3, "dataTReady": 0, "hdrTReady": 1, + "eventTReady": 1, }, ) await reset_signals( @@ -982,8 +1198,8 @@ async def coaxpress_rx_four_lane_overflow_reset_recovery_known_issue_test(dut): assert observed_recovery_last == [0, 0, 1] * 4, (signal_counts, observed_recovery_last) -@cocotb.test(skip=os.getenv("RUN_KNOWN_ISSUE_TESTS") != "1") -async def coaxpress_rx_four_lane_overflow_recovery_known_issue_test(dut): +@cocotb.test(skip=os.getenv("RUN_STRESS_TESTS") != "1") +async def coaxpress_rx_four_lane_overflow_recovery_stress_test(dut): if env_int("NUM_LANES_G", default=1) != 4: return @@ -998,6 +1214,7 @@ async def coaxpress_rx_four_lane_overflow_recovery_known_issue_test(dut): "rxNumberOfLane": 3, "dataTReady": 0, "hdrTReady": 1, + "eventTReady": 1, }, ) await reset_signals( @@ -1066,10 +1283,6 @@ async def coaxpress_rx_four_lane_overflow_recovery_known_issue_test(dut): await task observed_data_words = [beat[0] for beat in data_beats] - # Known issue under investigation: - # with 4 bonded lanes, sustained sink backpressure can emit rxFsmError - # pulses before or alongside the expected overflow indication. The desired - # behavior is overflow-only, followed by clean post-stall recovery data. assert signal_counts["error_pulses"] == 0, signal_counts assert signal_counts["overflow_pulses"] > 0, signal_counts assert find_subsequence(observed_data_words, expected_recovery_words) is not None, observed_data_words[-64:] @@ -1079,8 +1292,8 @@ async def coaxpress_rx_four_lane_overflow_recovery_known_issue_test(dut): assert observed_recovery_last == [0, 0, 1] * 4, observed_recovery_last -@cocotb.test(skip=os.getenv("RUN_KNOWN_ISSUE_TESTS") != "1") -async def coaxpress_rx_repeated_single_line_frame_known_issue_test(dut): +@cocotb.test() +async def coaxpress_rx_repeated_single_line_frame_test(dut): if env_int("NUM_LANES_G", default=1) != 1: return @@ -1093,8 +1306,9 @@ async def coaxpress_rx_repeated_single_line_frame_known_issue_test(dut): "rxLinkUp": 1, "rxFsmRst": 0, "rxNumberOfLane": 0, - "dataTReady": env_int("CXP_RX_KNOWN_ISSUE_DATA_READY", default=0), + "dataTReady": 1, "hdrTReady": 1, + "eventTReady": 1, }, ) await reset_signals( @@ -1105,38 +1319,54 @@ async def coaxpress_rx_repeated_single_line_frame_known_issue_test(dut): release_cycles=4, ) - frame_count = env_int("CXP_RX_REPEATED_FRAME_COUNT", default=72) - vary_packet_fields = env_flag("CXP_RX_KNOWN_ISSUE_VARY_PACKET_FIELDS", default=False) + frame_count = env_int("CXP_RX_REPEATED_FRAME_COUNT", default=16) + vary_packet_fields = env_flag("CXP_RX_REPEATED_FRAME_VARY_PACKET_FIELDS", default=False) signal_counts = {"error_pulses": 0, "overflow_pulses": 0} + data_beats: list[tuple[int, int, int, int]] = [] + hdr_beats: list[tuple[int, int, int, int]] = [] stop_event = Event() monitor_tasks = [ cocotb.start_soon(_count_signal_high_cycles(dut.rxFsmError, dut.rxClk, stop_event, signal_counts, "error_pulses")), cocotb.start_soon(_count_signal_high_cycles(dut.rxOverflow, dut.rxClk, stop_event, signal_counts, "overflow_pulses")), ] + cycle_index = 0 for index in range(frame_count): header_stream_id = (0x50 + (2 * index)) & 0xFF if vary_packet_fields else 0x22 header_packet_tag = (0x70 + (2 * index)) & 0xFF if vary_packet_fields else 0x33 - await _send_one_lane_frame( + cycle_index = await _send_one_lane_frame_and_capture( dut, line_word=0xA0000000 + index, + data_beats=data_beats, + hdr_beats=hdr_beats, header_stream_id=header_stream_id, header_packet_tag=header_packet_tag, line_stream_id=(header_stream_id + 1) & 0xFF if vary_packet_fields else 0x44, line_packet_tag=(header_packet_tag + 1) & 0xFF if vary_packet_fields else 0x55, + start_cycle_index=cycle_index, ) - for _ in range(32): - await send_rx_word(dut, data=CXP_IDLE, data_k=CXP_IDLE_K, clk=dut.rxClk) + await _drive_idle_and_capture(dut, cycles=64, data_beats=data_beats, hdr_beats=hdr_beats, start_cycle_index=cycle_index) stop_event.set() for task in monitor_tasks: await task - assert signal_counts["overflow_pulses"] > 0, ( - f"overflow_pulses={signal_counts['overflow_pulses']} error_pulses={signal_counts['error_pulses']}" - ) + observed_words = [beat[0] for beat in data_beats] + expected_words = [0xA0000000 + index for index in range(frame_count)] assert signal_counts["error_pulses"] == 0, ( f"overflow_pulses={signal_counts['overflow_pulses']} error_pulses={signal_counts['error_pulses']}" ) + assert signal_counts["overflow_pulses"] == 0, signal_counts + assert observed_words == expected_words, data_beats + assert [beat[2] for beat in data_beats] == [1] * frame_count, data_beats + assert [beat[0] for beat in hdr_beats] == [ + 0x3456AA12, + 0x00000001, + 0x00000000, + 0x00000001, + 0x00000000, + 0x00000001, + 0x00200010, + ] * frame_count, hdr_beats PARAMETER_SWEEP = [ diff --git a/tests/protocols/coaxpress/test_CoaXPressRxHsFsm.py b/tests/protocols/coaxpress/test_CoaXPressRxHsFsm.py index 07d11be1c8..0adedfd93f 100644 --- a/tests/protocols/coaxpress/test_CoaXPressRxHsFsm.py +++ b/tests/protocols/coaxpress/test_CoaXPressRxHsFsm.py @@ -21,8 +21,6 @@ # - Timing: The source holds each beat until `sAxisTReady` rises so the checks # reflect the FSM's actual per-beat acceptance rather than idealized traffic. -import os - import cocotb import pytest from cocotb.triggers import RisingEdge, Timer @@ -42,6 +40,8 @@ start_clock, ) +CXP_RX_STREAM_TRAILER_USER = 1 << 4 + def _capture_outputs(dut, *, header_beats: list[dict[str, int]], data_beats: list[dict[str, int]]) -> None: if int(dut.hdrTValid.value) == 1: @@ -62,18 +62,24 @@ def _capture_outputs(dut, *, header_beats: list[dict[str, int]], data_beats: lis ) -async def _send_handshaked_beat(dut, *, data: int, keep: int, last: int = 0) -> None: +async def _send_handshaked_beat(dut, *, data: int, keep: int, last: int = 0, user: int = 0) -> None: dut.sAxisTValid.value = 1 dut.sAxisTData.value = data dut.sAxisTKeep.value = keep + dut.sAxisTUser.value = user dut.sAxisTLast.value = last await wait_sampled_ready(dut.sAxisTReady, clk=dut.rxClk) dut.sAxisTValid.value = 0 dut.sAxisTData.value = 0 dut.sAxisTKeep.value = 0 + dut.sAxisTUser.value = 0 dut.sAxisTLast.value = 0 +async def _send_trailer_marker(dut) -> None: + await _send_handshaked_beat(dut, data=0, keep=0xF, last=1, user=CXP_RX_STREAM_TRAILER_USER) + + def _beat_data(words: list[int], *, num_lanes: int) -> int: return pack_words(words + [0] * (num_lanes - len(words))) @@ -224,6 +230,7 @@ async def coaxpress_rx_hs_fsm_header_and_lines_test(dut): dut.sAxisTValid.setimmediatevalue(0) dut.sAxisTData.setimmediatevalue(0) dut.sAxisTKeep.setimmediatevalue(0) + dut.sAxisTUser.setimmediatevalue(0) dut.sAxisTLast.setimmediatevalue(0) await reset_dut(dut, reset_names=("rxRst",)) header_beats: list[dict[str, int]] = [] @@ -237,6 +244,8 @@ async def coaxpress_rx_hs_fsm_header_and_lines_test(dut): for word in _header_words(): await _send_handshaked_beat(dut, data=word, keep=0xF) _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) # Follow with two line packets whose final word should close the frame. for line_words in ([0x11111111, 0x22222222, 0x33333333], [0x44444444, 0x55555555, 0x66666666]): @@ -247,8 +256,10 @@ async def coaxpress_rx_hs_fsm_header_and_lines_test(dut): for word in line_words: await _send_handshaked_beat(dut, data=word, keep=0xF) _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) - for _ in range(6): + for _ in range(12): await RisingEdge(dut.rxClk) await Timer(1, unit="ns") _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) @@ -276,6 +287,7 @@ async def coaxpress_rx_hs_fsm_malformed_header_recovery_test(dut): dut.sAxisTValid.setimmediatevalue(0) dut.sAxisTData.setimmediatevalue(0) dut.sAxisTKeep.setimmediatevalue(0) + dut.sAxisTUser.setimmediatevalue(0) dut.sAxisTLast.setimmediatevalue(0) await reset_dut(dut, reset_names=("rxRst",)) header_beats: list[dict[str, int]] = [] @@ -296,6 +308,8 @@ async def coaxpress_rx_hs_fsm_malformed_header_recovery_test(dut): ) error_seen |= int(dut.rxFsmError.value) == 1 _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_trailer_marker(dut) + error_seen |= int(dut.rxFsmError.value) == 1 await cycle(dut.rxClk, 2) error_seen |= int(dut.rxFsmError.value) == 1 assert error_seen @@ -308,6 +322,8 @@ async def coaxpress_rx_hs_fsm_malformed_header_recovery_test(dut): for word in _header_words(): await _send_handshaked_beat(dut, data=word, keep=0xF) _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) await _send_handshaked_beat(dut, data=CXP_MARKER, keep=0xF) _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) await _send_handshaked_beat(dut, data=repeat_byte(CXP_PKT_IMAGE_LINE), keep=0xF) @@ -318,8 +334,10 @@ async def coaxpress_rx_hs_fsm_malformed_header_recovery_test(dut): _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) await _send_handshaked_beat(dut, data=0xABCDEF02, keep=0xF) _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) - for _ in range(6): + for _ in range(12): await RisingEdge(dut.rxClk) await Timer(1, unit="ns") _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) @@ -345,6 +363,7 @@ async def coaxpress_rx_hs_fsm_malformed_header_drops_following_line_test(dut): dut.sAxisTValid.setimmediatevalue(0) dut.sAxisTData.setimmediatevalue(0) dut.sAxisTKeep.setimmediatevalue(0) + dut.sAxisTUser.setimmediatevalue(0) dut.sAxisTLast.setimmediatevalue(0) await reset_dut(dut, reset_names=("rxRst",)) @@ -364,6 +383,8 @@ async def coaxpress_rx_hs_fsm_malformed_header_drops_following_line_test(dut): ) error_seen |= int(dut.rxFsmError.value) == 1 _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_trailer_marker(dut) + error_seen |= int(dut.rxFsmError.value) == 1 # A line packet arriving after a malformed header must be discarded until a # clean header has been accepted. @@ -377,6 +398,8 @@ async def coaxpress_rx_hs_fsm_malformed_header_drops_following_line_test(dut): await _send_handshaked_beat(dut, data=word, keep=0xF) error_seen |= int(dut.rxFsmError.value) == 1 _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_trailer_marker(dut) + error_seen |= int(dut.rxFsmError.value) == 1 await cycle(dut.rxClk, 2) error_seen |= int(dut.rxFsmError.value) == 1 @@ -391,6 +414,8 @@ async def coaxpress_rx_hs_fsm_malformed_header_drops_following_line_test(dut): for word in _header_words(): await _send_handshaked_beat(dut, data=word, keep=0xF) _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) await _send_handshaked_beat(dut, data=CXP_MARKER, keep=0xF) _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) await _send_handshaked_beat(dut, data=repeat_byte(CXP_PKT_IMAGE_LINE), keep=0xF) @@ -398,8 +423,10 @@ async def coaxpress_rx_hs_fsm_malformed_header_drops_following_line_test(dut): for word in (0xABCDEF00, 0xABCDEF01, 0xABCDEF02): await _send_handshaked_beat(dut, data=word, keep=0xF) _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) - for _ in range(6): + for _ in range(12): await RisingEdge(dut.rxClk) await Timer(1, unit="ns") _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) @@ -414,6 +441,120 @@ async def coaxpress_rx_hs_fsm_malformed_header_drops_following_line_test(dut): ] +@cocotb.test() +async def coaxpress_rx_hs_fsm_new_header_before_frame_complete_test(dut): + if env_int("NUM_LANES_G", default=1) != 1: + return + + start_clock(dut.rxClk) + dut.rxRst.setimmediatevalue(1) + dut.rxFsmRst.setimmediatevalue(0) + dut.sAxisTValid.setimmediatevalue(0) + dut.sAxisTData.setimmediatevalue(0) + dut.sAxisTKeep.setimmediatevalue(0) + dut.sAxisTUser.setimmediatevalue(0) + dut.sAxisTLast.setimmediatevalue(0) + await reset_dut(dut, reset_names=("rxRst",)) + + first_header = _image_header_words_from_fields( + stream_id=0x31, + source_tag=0x1020, + x_size=1, + x_offs=0, + y_size=2, + y_offs=0, + dsize_l=1, + pixel_f=0x0010, + tap_g=0x0020, + flags=0x01, + ) + second_header = _image_header_words_from_fields( + stream_id=0x32, + source_tag=0x3040, + x_size=1, + x_offs=0, + y_size=1, + y_offs=0, + dsize_l=1, + pixel_f=0x0011, + tap_g=0x0021, + flags=0x02, + ) + expected_first_header = _expected_header_data_from_fields( + stream_id=0x31, + source_tag=0x1020, + x_size=1, + x_offs=0, + y_size=2, + y_offs=0, + dsize_l=1, + pixel_f=0x0010, + tap_g=0x0020, + flags=0x01, + ) + expected_second_header = _expected_header_data_from_fields( + stream_id=0x32, + source_tag=0x3040, + x_size=1, + x_offs=0, + y_size=1, + y_offs=0, + dsize_l=1, + pixel_f=0x0011, + tap_g=0x0021, + flags=0x02, + ) + + header_beats: list[dict[str, int]] = [] + data_beats: list[dict[str, int]] = [] + error_seen = False + + async def send_and_capture(data: int) -> None: + nonlocal error_seen + await _send_handshaked_beat(dut, data=data, keep=0xF) + error_seen |= int(dut.rxFsmError.value) == 1 + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + + await send_and_capture(CXP_MARKER) + await send_and_capture(repeat_byte(CXP_PKT_IMAGE_HEADER)) + for word in first_header: + await send_and_capture(word) + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + + await send_and_capture(CXP_MARKER) + await send_and_capture(repeat_byte(CXP_PKT_IMAGE_LINE)) + await send_and_capture(0x11111111) + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + + # Starting a new rectangular image header before the declared two-line frame + # has completed is explicitly detected by the current RTL. + await send_and_capture(CXP_MARKER) + await send_and_capture(repeat_byte(CXP_PKT_IMAGE_HEADER)) + await cycle(dut.rxClk, 1) + error_seen |= int(dut.rxFsmError.value) == 1 + for word in second_header: + await send_and_capture(word) + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + + for _ in range(12): + await RisingEdge(dut.rxClk) + await Timer(1, unit="ns") + error_seen |= int(dut.rxFsmError.value) == 1 + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + + assert error_seen + assert header_beats == [ + {"hdrTData": expected_first_header, "hdrTLast": 1, "hdrTSof": 1}, + {"hdrTData": expected_second_header, "hdrTLast": 1, "hdrTSof": 1}, + ] + assert data_beats == [ + {"dataTData": 0x11111111, "dataTKeep": 0xF, "dataTLast": 0}, + ] + + @cocotb.test() async def coaxpress_rx_hs_fsm_two_lane_step_alignment_test(dut): if env_int("NUM_LANES_G", default=1) != 2: @@ -425,6 +566,7 @@ async def coaxpress_rx_hs_fsm_two_lane_step_alignment_test(dut): dut.sAxisTValid.setimmediatevalue(0) dut.sAxisTData.setimmediatevalue(0) dut.sAxisTKeep.setimmediatevalue(0) + dut.sAxisTUser.setimmediatevalue(0) dut.sAxisTLast.setimmediatevalue(0) await reset_dut(dut, reset_names=("rxRst",)) @@ -470,6 +612,8 @@ async def coaxpress_rx_hs_fsm_two_lane_step_alignment_test(dut): keep=lane_keep_mask(list(range(len(words)))), ) _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) await _send_handshaked_beat( dut, @@ -489,8 +633,10 @@ async def coaxpress_rx_hs_fsm_two_lane_step_alignment_test(dut): keep=lane_keep_mask([0, 1]), ) _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) - for _ in range(8): + for _ in range(12): await RisingEdge(dut.rxClk) await Timer(1, unit="ns") _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) @@ -537,6 +683,7 @@ async def coaxpress_rx_hs_fsm_quad_lane_tail_marker_type_same_beat_test(dut): dut.sAxisTValid.setimmediatevalue(0) dut.sAxisTData.setimmediatevalue(0) dut.sAxisTKeep.setimmediatevalue(0) + dut.sAxisTUser.setimmediatevalue(0) dut.sAxisTLast.setimmediatevalue(0) await reset_dut(dut, reset_names=("rxRst",)) @@ -600,6 +747,8 @@ async def coaxpress_rx_hs_fsm_quad_lane_tail_marker_type_same_beat_test(dut): keep=lane_keep_mask([0]), ) _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) await _send_handshaked_beat( dut, @@ -614,14 +763,16 @@ async def coaxpress_rx_hs_fsm_quad_lane_tail_marker_type_same_beat_test(dut): ) _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) - # Cover the merged corner case: the first line ends while the remaining - # words in the same 4-lane beat already contain the next line marker/type. + # Cover the partial-line packing corner case: the first line contributes + # only two words to the final 4-lane output beat, then its trailer verdict + # arrives before the next line completes the packed SSI frame. dut.sAxisTValid.value = 1 dut.sAxisTData.value = _beat_data( - [0x11111111, 0x22222222, CXP_MARKER, repeat_byte(CXP_PKT_IMAGE_LINE)], + [0x11111111, 0x22222222], num_lanes=4, ) - dut.sAxisTKeep.value = lane_keep_mask([0, 1, 2, 3]) + dut.sAxisTKeep.value = lane_keep_mask([0, 1]) + dut.sAxisTUser.value = 0 dut.sAxisTLast.value = 0 shared_beat_cycles = 0 while True: @@ -639,16 +790,29 @@ async def coaxpress_rx_hs_fsm_quad_lane_tail_marker_type_same_beat_test(dut): dut.sAxisTValid.value = 0 dut.sAxisTData.value = 0 dut.sAxisTKeep.value = 0 + dut.sAxisTUser.value = 0 dut.sAxisTLast.value = 0 + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + + await _send_handshaked_beat( + dut, + data=_beat_data([CXP_MARKER, repeat_byte(CXP_PKT_IMAGE_LINE)], num_lanes=4), + keep=lane_keep_mask([0, 1]), + ) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_handshaked_beat( dut, data=_beat_data([0x33333333, 0x44444444], num_lanes=4), keep=lane_keep_mask([0, 1]), ) _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) + await _send_trailer_marker(dut) + _capture_outputs(dut, header_beats=header_beats, data_beats=data_beats) - for _ in range(8): + for _ in range(20): await RisingEdge(dut.rxClk) await Timer(1, unit="ns") error_seen |= int(dut.rxFsmError.value) == 1 @@ -669,8 +833,8 @@ async def coaxpress_rx_hs_fsm_quad_lane_tail_marker_type_same_beat_test(dut): ], trace -@cocotb.test(skip=os.getenv("RUN_KNOWN_ISSUE_TESTS") != "1") -async def coaxpress_rx_hs_fsm_repeated_single_line_frame_known_issue_test(dut): +@cocotb.test() +async def coaxpress_rx_hs_fsm_repeated_single_line_frame_test(dut): if env_int("NUM_LANES_G", default=1) != 1: return @@ -680,6 +844,7 @@ async def coaxpress_rx_hs_fsm_repeated_single_line_frame_known_issue_test(dut): dut.sAxisTValid.setimmediatevalue(0) dut.sAxisTData.setimmediatevalue(0) dut.sAxisTKeep.setimmediatevalue(0) + dut.sAxisTUser.setimmediatevalue(0) dut.sAxisTLast.setimmediatevalue(0) await reset_dut(dut, reset_names=("rxRst",)) @@ -694,12 +859,16 @@ async def coaxpress_rx_hs_fsm_repeated_single_line_frame_known_issue_test(dut): for word in _single_line_header_words(): await _send_handshaked_beat(dut, data=word, keep=0xF) error_pulses += int(dut.rxFsmError.value) + await _send_trailer_marker(dut) + error_pulses += int(dut.rxFsmError.value) await _send_handshaked_beat(dut, data=CXP_MARKER, keep=0xF) error_pulses += int(dut.rxFsmError.value) await _send_handshaked_beat(dut, data=repeat_byte(CXP_PKT_IMAGE_LINE), keep=0xF) error_pulses += int(dut.rxFsmError.value) await _send_handshaked_beat(dut, data=0xA0000000 + index, keep=0xF) error_pulses += int(dut.rxFsmError.value) + await _send_trailer_marker(dut) + error_pulses += int(dut.rxFsmError.value) await cycle(dut.rxClk, 8) error_pulses += sum(int(dut.rxFsmError.value) for _ in range(1)) diff --git a/tests/protocols/coaxpress/test_CoaXPressRxLane.py b/tests/protocols/coaxpress/test_CoaXPressRxLane.py index 7925977217..87313d35b4 100644 --- a/tests/protocols/coaxpress/test_CoaXPressRxLane.py +++ b/tests/protocols/coaxpress/test_CoaXPressRxLane.py @@ -25,6 +25,8 @@ from tests.common.regression_utils import run_surf_vhdl_test from tests.protocols.coaxpress.coaxpress_test_utils import ( + CXP_ACK_SUCCESS, + CXP_ACK_SUCCESS_ALT, CXP_EOP, CXP_IDLE, CXP_IDLE_K, @@ -58,6 +60,48 @@ def _event_crc_words(*, event_bytes: tuple[int, int, int, int], packet_tag: int, ] +def _control_ack_crc_words( + *, + ack_code: int, + size_word: int, + data_word: int | None = None, + packet_tag: int | None = None, +) -> list[int]: + crc_inputs = [] + if packet_tag is not None: + crc_inputs.append(repeat_byte(packet_tag)) + crc_inputs.append(repeat_byte(ack_code)) + crc_inputs.append(size_word) + if data_word is not None: + crc_inputs.append(data_word) + return [ + *crc_inputs, + cxp_crc_word(crc_inputs), + ] + + +def _heartbeat_crc_words(payload_bytes: range) -> list[int]: + crc_inputs = [repeat_byte(byte) for byte in payload_bytes] + return [ + *crc_inputs, + cxp_crc_word(crc_inputs), + ] + + +def _stream_crc_words(*, stream_id: int, packet_tag: int, payload_words: list[int]) -> list[int]: + crc_inputs = [ + repeat_byte(stream_id), + repeat_byte(packet_tag), + repeat_byte((len(payload_words) >> 8) & 0xFF), + repeat_byte(len(payload_words) & 0xFF), + *payload_words, + ] + return [ + *crc_inputs, + cxp_crc_word(crc_inputs), + ] + + @cocotb.test() async def coaxpress_rx_lane_stream_and_io_ack_test(dut): start_clock(dut.rxClk) @@ -79,7 +123,7 @@ async def drive(data: int, data_k: int) -> None: clk=dut.rxClk, capture=data_beats, valid_name="dataTValid", - field_names=("dataTData", "dataTUser", "dataTLast"), + field_names=("dataTData", "dataTKeep", "dataTUser", "dataTLast"), ) io_ack_pulses += int(dut.ioAck.value) @@ -94,18 +138,23 @@ async def drive(data: int, data_k: int) -> None: await drive(repeat_byte(0x03), 0x0) await drive(CXP_IO_ACK, 0xF) await drive(repeat_byte(0x01), 0x0) - await drive(0x11223344, 0x0) - await drive(0x55667788, 0x5) - await drive(0x99AABBCC, 0x0) - await drive(0xDEADBEEF, 0x0) + stream_payload = [0x11223344, 0x55667788, 0x99AABBCC] + await drive(stream_payload[0], 0x0) + await drive(stream_payload[1], 0x5) + await drive(stream_payload[2], 0x0) + await drive( + cxp_crc_word([repeat_byte(0x22), repeat_byte(0x33), repeat_byte(0x00), repeat_byte(0x03), *stream_payload]), + 0x0, + ) await drive(CXP_EOP, 0xF) await drive(CXP_IDLE, CXP_IDLE_K) assert io_ack_pulses == 1 assert data_beats == [ - {"dataTData": 0x11223344, "dataTUser": 0x0, "dataTLast": 0}, - {"dataTData": 0x55667788, "dataTUser": 0x5, "dataTLast": 0}, - {"dataTData": 0x99AABBCC, "dataTUser": 0x0, "dataTLast": 1}, + {"dataTData": 0x11223344, "dataTKeep": 0xF, "dataTUser": 0x0, "dataTLast": 0}, + {"dataTData": 0x55667788, "dataTKeep": 0xF, "dataTUser": 0x5, "dataTLast": 0}, + {"dataTData": 0x99AABBCC, "dataTKeep": 0xF, "dataTUser": 0x0, "dataTLast": 1}, + {"dataTData": 0x00000000, "dataTKeep": 0xF, "dataTUser": 0x0, "dataTLast": 1}, ] @@ -120,6 +169,7 @@ async def coaxpress_rx_lane_spec_prefix_control_event_and_heartbeat_test(dut): cfg_beats: list[dict[str, int]] = [] heartbeat_beats: list[dict[str, int]] = [] + event_beats: list[dict[str, int]] = [] event_pulses: list[tuple[int, int]] = [] async def drive(data: int, data_k: int, *, link_up: int = 1) -> None: @@ -139,6 +189,15 @@ async def drive(data: int, data_k: int, *, link_up: int = 1) -> None: "heartbeatTLast": int(dut.heartbeatTLast.value), } ) + if int(dut.eventTValid.value) == 1: + event_beats.append( + { + "eventTData": int(dut.eventTData.value), + "eventTDest": int(dut.eventTDest.value), + "eventTUser": int(dut.eventTUser.value), + "eventTLast": int(dut.eventTLast.value), + } + ) if int(dut.eventAck.value) == 1: event_pulses.append((int(dut.eventAck.value), int(dut.eventTag.value))) @@ -146,40 +205,47 @@ async def drive(data: int, data_k: int, *, link_up: int = 1) -> None: # code 0x00, size=4 bytes, one reply-data word, CRC, EOP. await drive(CXP_SOP, 0xF) await drive(repeat_byte(CXP_PKT_CTRL_ACK_NO_TAG), 0x0) - await drive(repeat_byte(0x00), 0x0) - await drive(0x04000000, 0x0) - await drive(0x01234567, 0x0) - await drive(0xCAFEBABE, 0x0) + for word in _control_ack_crc_words(ack_code=0x00, size_word=0x04000000, data_word=0x01234567): + await drive(word, 0x0) await drive(CXP_EOP, 0xF) # Drive one alternate-success acknowledgment code. The current RTL maps # 0x04 to the same zero-success status word as 0x01. await drive(CXP_SOP, 0xF) await drive(repeat_byte(CXP_PKT_CTRL_ACK_NO_TAG), 0x0) - await drive(repeat_byte(0x04), 0x0) - await drive(0x04000000, 0x0) - await drive(0x76543210, 0x0) - await drive(0x0BADCAFE, 0x0) + for word in _control_ack_crc_words(ack_code=0x04, size_word=0x04000000, data_word=0x76543210): + await drive(word, 0x0) + await drive(CXP_EOP, 0xF) + + # Drive a write acknowledgment (size=0, no data word). This is the format + # cameras send for register writes per CXP-001-2021 Section 9.5.2.2. + await drive(CXP_SOP, 0xF) + await drive(repeat_byte(CXP_PKT_CTRL_ACK_NO_TAG), 0x0) + for word in _control_ack_crc_words(ack_code=CXP_ACK_SUCCESS, size_word=0x00000000): + await drive(word, 0x0) await drive(CXP_EOP, 0xF) - # Drive one spec-shaped tagged read acknowledgment. The current RTL skips - # the tag word, then forwards the first reply-data word with a zeroed - # success status in the low 32 bits. + # Drive a tagged write acknowledgment (size=0, no data word). await drive(CXP_SOP, 0xF) await drive(repeat_byte(CXP_PKT_CTRL_ACK_WITH_TAG), 0x0) - await drive(repeat_byte(0x55), 0x0) - await drive(repeat_byte(0x00), 0x0) - await drive(0x04000000, 0x0) - await drive(0x89ABCDEF, 0x0) - await drive(0xFEEDBEEF, 0x0) + for word in _control_ack_crc_words(ack_code=CXP_ACK_SUCCESS_ALT, size_word=0x00000000, packet_tag=0xAA): + await drive(word, 0x0) + await drive(CXP_EOP, 0xF) + + # Drive one spec-shaped tagged read acknowledgment. The RTL includes the tag + # in the CRC, then forwards the first reply-data word with a zeroed success + # status in the low 32 bits after the trailer passes. + await drive(CXP_SOP, 0xF) + await drive(repeat_byte(CXP_PKT_CTRL_ACK_WITH_TAG), 0x0) + for word in _control_ack_crc_words(ack_code=0x00, size_word=0x04000000, data_word=0x89ABCDEF, packet_tag=0x55): + await drive(word, 0x0) await drive(CXP_EOP, 0xF) # Heartbeat first keeps the on-wire ordering consistent before the event. await drive(CXP_SOP, 0xF) await drive(repeat_byte(CXP_PKT_HEARTBEAT), 0x0) - for word in range(0x20, 0x2C): - await drive(repeat_byte(word), 0x0) - await drive(0xB6B6B6B6, 0x0) + for word in _heartbeat_crc_words(range(0x20, 0x2C)): + await drive(word, 0x0) await drive(CXP_EOP, 0xF) # Drive a fuller event packet shape. The current RTL only consumes the @@ -202,9 +268,19 @@ async def drive(data: int, data_k: int, *, link_up: int = 1) -> None: assert cfg_beats == [ {"cfgTData": (0x01234567 << 32)}, {"cfgTData": (0x76543210 << 32)}, + {"cfgTData": (0xFFFFFFFF << 32)}, + {"cfgTData": (0xFFFFFFFF << 32)}, {"cfgTData": (0x89ABCDEF << 32)}, ] assert event_pulses == [(1, 0x5A)] + assert event_beats == [ + { + "eventTData": 0x11223344, + "eventTDest": 0x5A, + "eventTUser": 0x13121110, + "eventTLast": 1, + } + ] assert heartbeat_beats == [ { "heartbeatTData": sum((word << (8 * (word - 0x20))) for word in range(0x20, 0x2C)), @@ -225,9 +301,12 @@ async def coaxpress_rx_lane_event_payload_crc_guardrail_test(dut): cfg_beats: list[dict[str, int]] = [] data_beats: list[dict[str, int]] = [] heartbeat_beats: list[dict[str, int]] = [] + event_beats: list[dict[str, int]] = [] event_tags: list[int] = [] + error_pulses = 0 async def drive(data: int, data_k: int) -> None: + nonlocal error_pulses await send_rx_word(dut, data=data, data_k=data_k, clk=dut.rxClk) if int(dut.cfgTValid.value) == 1: cfg_beats.append({"cfgTData": int(dut.cfgTData.value)}) @@ -235,17 +314,28 @@ async def drive(data: int, data_k: int) -> None: data_beats.append( { "dataTData": int(dut.dataTData.value), + "dataTKeep": int(dut.dataTKeep.value), "dataTUser": int(dut.dataTUser.value), "dataTLast": int(dut.dataTLast.value), } ) if int(dut.heartbeatTValid.value) == 1: heartbeat_beats.append({"heartbeatTData": int(dut.heartbeatTData.value)}) + if int(dut.eventTValid.value) == 1: + event_beats.append( + { + "eventTData": int(dut.eventTData.value), + "eventTDest": int(dut.eventTDest.value), + "eventTUser": int(dut.eventTUser.value), + "eventTLast": int(dut.eventTLast.value), + } + ) if int(dut.eventAck.value) == 1: event_tags.append(int(dut.eventTag.value)) + error_pulses += int(dut.rxError.value) # The receive-lane RTL validates the event payload count, CRC, and EOP before - # acknowledging the tag. The payload is intentionally not forwarded anywhere. + # acknowledging the tag and releasing the payload stream. await drive(CXP_SOP, 0xF) await drive(repeat_byte(CXP_PKT_EVENT), 0x0) for word in _event_crc_words( @@ -256,6 +346,14 @@ async def drive(data: int, data_k: int) -> None: await drive(word, 0x0) await drive(CXP_EOP, 0xF) + # A bad heartbeat CRC must also suppress the heartbeat output. + await drive(CXP_SOP, 0xF) + await drive(repeat_byte(CXP_PKT_HEARTBEAT), 0x0) + bad_heartbeat_words = _heartbeat_crc_words(range(0x30, 0x3C)) + for word in [*bad_heartbeat_words[:-1], bad_heartbeat_words[-1] ^ 0x00000001]: + await drive(word, 0x0) + await drive(CXP_EOP, 0xF) + # A bad CRC must suppress the acknowledgment and still leave the parser ready # for a later clean event. await drive(CXP_SOP, 0xF) @@ -265,6 +363,19 @@ async def drive(data: int, data_k: int) -> None: await drive(word, 0x0) await drive(CXP_EOP, 0xF) + # Oversized event payloads exceed the bounded receive-side store. The lane + # must reject them before any payload word can leak out. + await drive(CXP_SOP, 0xF) + await drive(repeat_byte(CXP_PKT_EVENT), 0x0) + for word in [ + *[repeat_byte(byte) for byte in (0xC0, 0xC1, 0xC2, 0xC3)], + repeat_byte(0x4C), + repeat_byte(0x00), + repeat_byte(0x11), + ]: + await drive(word, 0x0) + await drive(CXP_IDLE, CXP_IDLE_K) + # A later zero-payload event must still be accepted, proving the ignored # bad-CRC packet did not leave stale parser state behind. await drive(CXP_SOP, 0xF) @@ -275,9 +386,189 @@ async def drive(data: int, data_k: int) -> None: await drive(CXP_IDLE, CXP_IDLE_K) assert event_tags == [0x6D, 0x7E] + assert event_beats == [ + { + "eventTData": 0x11223344, + "eventTDest": 0x6D, + "eventTUser": 0xA3A2A1A0, + "eventTLast": 0, + }, + { + "eventTData": 0x55667788, + "eventTDest": 0x6D, + "eventTUser": 0xA3A2A1A0, + "eventTLast": 1, + }, + ] assert cfg_beats == [] assert data_beats == [] assert heartbeat_beats == [] + assert error_pulses >= 2 + + +@cocotb.test() +async def coaxpress_rx_lane_control_ack_crc_eop_guardrail_test(dut): + start_clock(dut.rxClk) + dut.rxRst.setimmediatevalue(1) + dut.rxLinkUp.setimmediatevalue(1) + dut.rxData.setimmediatevalue(CXP_IDLE) + dut.rxDataK.setimmediatevalue(CXP_IDLE_K) + await reset_dut(dut) + + cfg_beats: list[dict[str, int]] = [] + + async def drive(data: int, data_k: int) -> None: + await send_rx_word( + dut, + data=data, + data_k=data_k, + clk=dut.rxClk, + capture=cfg_beats, + valid_name="cfgTValid", + field_names=("cfgTData",), + ) + + # A bad CRC must suppress the acknowledgment and still leave the parser ready + # for a later packet. + await drive(CXP_SOP, 0xF) + await drive(repeat_byte(CXP_PKT_CTRL_ACK_NO_TAG), 0x0) + bad_crc_words = _control_ack_crc_words(ack_code=CXP_ACK_SUCCESS, size_word=0x04000000, data_word=0x12345678) + for word in [*bad_crc_words[:-1], bad_crc_words[-1] ^ 0x00000001]: + await drive(word, 0x0) + await drive(CXP_EOP, 0xF) + + # A correct CRC followed by a malformed EOP must also suppress the response. + await drive(CXP_SOP, 0xF) + await drive(repeat_byte(CXP_PKT_CTRL_ACK_NO_TAG), 0x0) + for word in _control_ack_crc_words(ack_code=CXP_ACK_SUCCESS_ALT, size_word=0x04000000, data_word=0xDEADBEEF): + await drive(word, 0x0) + await drive(0x01020304, 0x0) + + # A later clean acknowledgment must still be decoded after the malformed + # trailer words. + await drive(CXP_SOP, 0xF) + await drive(repeat_byte(CXP_PKT_CTRL_ACK_NO_TAG), 0x0) + for word in _control_ack_crc_words(ack_code=CXP_ACK_SUCCESS_ALT, size_word=0x04000000, data_word=0x87654321): + await drive(word, 0x0) + await drive(CXP_EOP, 0xF) + await drive(CXP_IDLE, CXP_IDLE_K) + + assert cfg_beats == [ + {"cfgTData": (0x87654321 << 32)}, + ] + + +@cocotb.test() +async def coaxpress_rx_lane_control_ack_success_compatibility_test(dut): + start_clock(dut.rxClk) + dut.rxRst.setimmediatevalue(1) + dut.rxLinkUp.setimmediatevalue(1) + dut.rxData.setimmediatevalue(CXP_IDLE) + dut.rxDataK.setimmediatevalue(CXP_IDLE_K) + await reset_dut(dut) + + cfg_beats: list[dict[str, int]] = [] + + async def drive(data: int, data_k: int) -> None: + await send_rx_word( + dut, + data=data, + data_k=data_k, + clk=dut.rxClk, + capture=cfg_beats, + valid_name="cfgTValid", + field_names=("cfgTData",), + ) + + # Hardware seen in the lab can return the write-success code in P0 only and + # omit the explicit zero-size word. Keep that accepted while preserving the + # stricter CRC/EOP validation added for normal control acknowledgments. + low_byte_success = 0x00000001 + await drive(CXP_SOP, 0xF) + await drive(repeat_byte(CXP_PKT_CTRL_ACK_NO_TAG), 0x0) + await drive(low_byte_success, 0x0) + await drive(cxp_crc_word([low_byte_success]), 0x0) + await drive(CXP_EOP, 0xF) + + # Also accept the alternate success code in P0 with an explicit zero-size + # write-ACK word. + low_byte_success_alt = 0x00000004 + await drive(CXP_SOP, 0xF) + await drive(repeat_byte(CXP_PKT_CTRL_ACK_NO_TAG), 0x0) + await drive(low_byte_success_alt, 0x0) + await drive(0x00000000, 0x0) + await drive(cxp_crc_word([low_byte_success_alt, 0x00000000]), 0x0) + await drive(CXP_EOP, 0xF) + await drive(CXP_IDLE, CXP_IDLE_K) + + assert cfg_beats == [ + {"cfgTData": 0xFFFFFFFF00000000}, + {"cfgTData": 0xFFFFFFFF00000000}, + ] + + +@cocotb.test() +async def coaxpress_rx_lane_stream_crc_eop_guardrail_test(dut): + start_clock(dut.rxClk) + dut.rxRst.setimmediatevalue(1) + dut.rxLinkUp.setimmediatevalue(1) + dut.rxData.setimmediatevalue(CXP_IDLE) + dut.rxDataK.setimmediatevalue(CXP_IDLE_K) + await reset_dut(dut) + + observed: list[dict[str, int]] = [] + error_pulses = 0 + + async def drive(data: int, data_k: int) -> None: + nonlocal error_pulses + await send_rx_word( + dut, + data=data, + data_k=data_k, + clk=dut.rxClk, + capture=observed, + valid_name="dataTValid", + field_names=("dataTData", "dataTKeep", "dataTUser", "dataTLast"), + ) + error_pulses += int(dut.rxError.value) + + # A new SOP where the stream CRC belongs must not be accepted as a fresh + # packet. This locks down the receive-lane fix that keeps stream packets in + # the trailer parser after the declared payload word count has been emitted. + await drive(CXP_SOP, 0xF) + await drive(repeat_byte(CXP_PKT_STREAM_DATA), 0x0) + for word in _stream_crc_words(stream_id=0x10, packet_tag=0x11, payload_words=[0x11111111])[:-1]: + await drive(word, 0x0) + await drive(CXP_SOP, 0xF) + await drive(repeat_byte(CXP_PKT_STREAM_DATA), 0x0) + await drive(repeat_byte(0x20), 0x0) + await drive(repeat_byte(0x21), 0x0) + await drive(repeat_byte(0x00), 0x0) + await drive(repeat_byte(0x01), 0x0) + await drive(0x22222222, 0x0) + await drive( + cxp_crc_word([repeat_byte(0x20), repeat_byte(0x21), repeat_byte(0x00), repeat_byte(0x01), 0x22222222]), + 0x0, + ) + await drive(CXP_EOP, 0xF) + + # A later clean stream packet must still recover after the malformed + # trailer path returned to IDLE. + clean_payload = [0x33333333] + await drive(CXP_SOP, 0xF) + await drive(repeat_byte(CXP_PKT_STREAM_DATA), 0x0) + for word in _stream_crc_words(stream_id=0x30, packet_tag=0x31, payload_words=clean_payload): + await drive(word, 0x0) + await drive(CXP_EOP, 0xF) + await drive(CXP_IDLE, CXP_IDLE_K) + + assert observed == [ + {"dataTData": 0x11111111, "dataTKeep": 0xF, "dataTUser": 0x0, "dataTLast": 1}, + {"dataTData": 0x00000000, "dataTKeep": 0xF, "dataTUser": 0x1, "dataTLast": 1}, + {"dataTData": 0x33333333, "dataTKeep": 0xF, "dataTUser": 0x0, "dataTLast": 1}, + {"dataTData": 0x00000000, "dataTKeep": 0xF, "dataTUser": 0x0, "dataTLast": 1}, + ] + assert error_pulses == 1 @cocotb.test() @@ -306,11 +597,8 @@ async def coaxpress_rx_lane_error_recovery_test(dut): for data, data_k in ( (CXP_SOP, 0xF), (repeat_byte(CXP_PKT_STREAM_DATA), 0x0), - (repeat_byte(0xAA), 0x0), - (repeat_byte(0xBB), 0x0), - (repeat_byte(0x00), 0x0), - (repeat_byte(0x01), 0x0), - (0x55667788, 0x0), + *[(word, 0x0) for word in _stream_crc_words(stream_id=0xAA, packet_tag=0xBB, payload_words=[0x55667788])], + (CXP_EOP, 0xF), (CXP_IDLE, CXP_IDLE_K), ): await send_rx_word( @@ -320,10 +608,13 @@ async def coaxpress_rx_lane_error_recovery_test(dut): clk=dut.rxClk, capture=observed, valid_name="dataTValid", - field_names=("dataTData", "dataTLast"), + field_names=("dataTData", "dataTKeep", "dataTUser", "dataTLast"), ) - assert observed == [{"dataTData": 0x55667788, "dataTLast": 1}] + assert observed == [ + {"dataTData": 0x55667788, "dataTKeep": 0xF, "dataTUser": 0x0, "dataTLast": 1}, + {"dataTData": 0x00000000, "dataTKeep": 0xF, "dataTUser": 0x0, "dataTLast": 1}, + ] def test_CoaXPressRxLane(): diff --git a/tests/protocols/coaxpress/test_CoaXPressRxLaneMux.py b/tests/protocols/coaxpress/test_CoaXPressRxLaneMux.py index bba263f324..58009ce823 100644 --- a/tests/protocols/coaxpress/test_CoaXPressRxLaneMux.py +++ b/tests/protocols/coaxpress/test_CoaXPressRxLaneMux.py @@ -28,13 +28,17 @@ from tests.common.regression_utils import env_int, parameter_case, run_surf_vhdl_test from tests.protocols.coaxpress.coaxpress_test_utils import pack_words, reset_dut, start_clock +CXP_RX_STREAM_TRAILER_USER = 1 << 4 + def _set_lane_inputs(dut, lane_beats, *, num_lanes: int) -> None: lane_width = 32 * num_lanes keep_width = 4 * num_lanes + user_width = 8 valid = 0 data = 0 keep = 0 + user = 0 last = 0 for lane, beat in enumerate(lane_beats): if beat is None: @@ -42,10 +46,12 @@ def _set_lane_inputs(dut, lane_beats, *, num_lanes: int) -> None: valid |= 1 << lane data |= beat["data"] << (lane * lane_width) keep |= beat["keep"] << (lane * keep_width) + user |= beat.get("user", 0) << (lane * user_width) last |= beat["last"] << lane dut.sAxisTValid.value = valid dut.sAxisTData.value = data dut.sAxisTKeep.value = keep + dut.sAxisTUser.value = user dut.sAxisTLast.value = last @@ -60,6 +66,7 @@ async def coaxpress_rx_lane_mux_round_robin_test(dut): dut.sAxisTValid.setimmediatevalue(0) dut.sAxisTData.setimmediatevalue(0) dut.sAxisTKeep.setimmediatevalue(0) + dut.sAxisTUser.setimmediatevalue(0) dut.sAxisTLast.setimmediatevalue(0) await reset_dut(dut, reset_names=("rxRst",)) @@ -67,9 +74,16 @@ async def coaxpress_rx_lane_mux_round_robin_test(dut): [ {"data": pack_words([0x10, 0x11, 0x12]), "keep": 0x0FFF, "last": 0}, {"data": pack_words([0x13, 0x14, 0x15]), "keep": 0x0FFF, "last": 1}, + {"data": 0, "keep": 0x0FFF, "user": CXP_RX_STREAM_TRAILER_USER, "last": 1}, + ], + [ + {"data": pack_words([0x20, 0x21, 0x22]), "keep": 0x0FFF, "last": 1}, + {"data": 0, "keep": 0x0FFF, "user": CXP_RX_STREAM_TRAILER_USER, "last": 1}, + ], + [ + {"data": pack_words([0x30, 0x31, 0x32]), "keep": 0x0FFF, "last": 1}, + {"data": 0, "keep": 0x0FFF, "user": CXP_RX_STREAM_TRAILER_USER, "last": 1}, ], - [{"data": pack_words([0x20, 0x21, 0x22]), "keep": 0x0FFF, "last": 1}], - [{"data": pack_words([0x30, 0x31, 0x32]), "keep": 0x0FFF, "last": 1}], ] observed: list[tuple[int, int, int]] = [] @@ -105,8 +119,11 @@ async def coaxpress_rx_lane_mux_round_robin_test(dut): assert observed == [ (pack_words([0x10, 0x11, 0x12]), 0x0FFF, 0), (pack_words([0x13, 0x14, 0x15]), 0x0FFF, 1), + (0, 0x0FFF, 1), (pack_words([0x20, 0x21, 0x22]), 0x0FFF, 1), + (0, 0x0FFF, 1), (pack_words([0x30, 0x31, 0x32]), 0x0FFF, 1), + (0, 0x0FFF, 1), ] @@ -122,6 +139,7 @@ def test_CoaXPressRxLaneMux(parameters): extra_env=parameters, extra_vhdl_sources={ "surf": [ + "protocols/coaxpress/core/rtl/CoaXPressPkg.vhd", "protocols/coaxpress/core/rtl/CoaXPressRxLaneMux.vhd", "protocols/coaxpress/core/wrappers/CoaXPressRxLaneMuxWrapper.vhd", ] diff --git a/tests/protocols/coaxpress/test_CoaXPressRxWordPacker.py b/tests/protocols/coaxpress/test_CoaXPressRxWordPacker.py index b3e3e6d69f..301397dd55 100644 --- a/tests/protocols/coaxpress/test_CoaXPressRxWordPacker.py +++ b/tests/protocols/coaxpress/test_CoaXPressRxWordPacker.py @@ -410,6 +410,7 @@ def test_CoaXPressRxWordPacker(parameters): extra_env=parameters, extra_vhdl_sources={ "surf": [ + "protocols/coaxpress/core/rtl/CoaXPressPkg.vhd", "protocols/coaxpress/core/rtl/CoaXPressRxWordPacker.vhd", "protocols/coaxpress/core/wrappers/CoaXPressRxWordPackerWrapper.vhd", ]