diff --git a/protocols/rssi/v1/rtl/RssiAxiLiteRegItf.vhd b/protocols/rssi/v1/rtl/RssiAxiLiteRegItf.vhd index 6cd31dcd97..185dbcd605 100644 --- a/protocols/rssi/v1/rtl/RssiAxiLiteRegItf.vhd +++ b/protocols/rssi/v1/rtl/RssiAxiLiteRegItf.vhd @@ -17,6 +17,7 @@ -- 0x02 (RW)- Version register [3:0](Default x"1") -- 0x03 (RW)- Maximum out standing segments [7:0](Default "008"): -- Defines the max number of segments in the RSSI receiver buffer +-- [31:24] (RO): MAX_NUM_OUTS_SEG_G build-time capability (0 in older firmware) -- 0x04 (RW)- Maximum segment size [15:0](Default x"0400") -- Defines the size of segment buffer! Number of bytes! -- 0x05 (RW)- Retransmission timeout [15:0](Default 50) @@ -33,8 +34,9 @@ -- When more than maxCumAck are received and not acknowledged the -- ACK packet will be sent to acknowledge the received packets. Even though the -- cumulative acknowledgment timeout has not been reached yet! --- 0x0A (RW)- Max out of sequence segments (EACK) [7:0](Default x"03") --- Currently not used TBD +-- 0x0A (RW)- Reserved max out-of-sequence/EACK field [7:0](Default x"03") +-- EACK is not implemented in RSSI v1. +-- [31:24] (RO): SEGMENT_ADDR_SIZE_G build-time capability (0 in older firmware) -- 0x0B (RW)- Connection ID [31:0](Default x"12345678") -- Every connection should have unique connection ID. -- Statuses @@ -246,6 +248,11 @@ begin v.appRssiParam.version := axilWriteMaster.wdata(3 downto 0); when 16#03# => -- ADDR (12) v.appRssiParam.maxOutsSeg := axilWriteMaster.wdata(7 downto 0); + if (v.appRssiParam.maxOutsSeg = 0) then + v.appRssiParam.maxOutsSeg := toSlv(1, v.appRssiParam.maxOutsSeg'length); + elsif (unsigned(v.appRssiParam.maxOutsSeg) > MAX_NUM_OUTS_SEG_G) then + v.appRssiParam.maxOutsSeg := toSlv(MAX_NUM_OUTS_SEG_G, v.appRssiParam.maxOutsSeg'length); + end if; when 16#04# => -- ADDR (16) v.appRssiParam.maxSegSize := axilWriteMaster.wdata(15 downto 0); if (unsigned(v.appRssiParam.maxSegSize) < 8) then @@ -255,10 +262,19 @@ begin end if; when 16#05# => v.appRssiParam.retransTout := axilWriteMaster.wdata(15 downto 0); + if (v.appRssiParam.retransTout = 0) then + v.appRssiParam.retransTout := toSlv(1, v.appRssiParam.retransTout'length); + end if; when 16#06# => v.appRssiParam.cumulAckTout := axilWriteMaster.wdata(15 downto 0); + if (v.appRssiParam.cumulAckTout = 0) then + v.appRssiParam.cumulAckTout := toSlv(1, v.appRssiParam.cumulAckTout'length); + end if; when 16#07# => v.appRssiParam.nullSegTout := axilWriteMaster.wdata(15 downto 0); + if (v.appRssiParam.nullSegTout = 0) then + v.appRssiParam.nullSegTout := toSlv(1, v.appRssiParam.nullSegTout'length); + end if; when 16#08# => v.appRssiParam.maxRetrans := axilWriteMaster.wdata(7 downto 0); when 16#09# => @@ -286,6 +302,9 @@ begin when 16#03# => -- ADDR (12) v.axilReadSlave.rdata(7 downto 0) := r.appRssiParam.maxOutsSeg; v.axilReadSlave.rdata(23 downto 16) := negRssiParam.maxOutsSeg; + -- Build-time capability advertisement (software auto-discovery). + -- Older firmware reads 0 here, signalling "fall back to defaults". + v.axilReadSlave.rdata(31 downto 24) := toSlv(MAX_NUM_OUTS_SEG_G, 8); when 16#04# => -- ADDR (16) v.axilReadSlave.rdata(15 downto 0) := r.appRssiParam.maxSegSize; v.axilReadSlave.rdata(31 downto 16) := negRssiParam.maxSegSize; @@ -307,6 +326,9 @@ begin when 16#0A# => -- ADDR (40) v.axilReadSlave.rdata(7 downto 0) := r.appRssiParam.maxOutofseq; v.axilReadSlave.rdata(23 downto 16) := negRssiParam.maxOutofseq; + -- Build-time capability advertisement (software auto-discovery). + -- Older firmware reads 0 here, signalling "fall back to defaults". + v.axilReadSlave.rdata(31 downto 24) := toSlv(SEGMENT_ADDR_SIZE_G, 8); when 16#0B# => -- ADDR (44) v.axilReadSlave.rdata(31 downto 0) := r.appRssiParam.connectionId; when 16#0C# => -- ADDR (48) diff --git a/python/surf/protocols/rssi/_RssiCore.py b/python/surf/protocols/rssi/_RssiCore.py index 300b7ab650..b023f3060f 100644 --- a/python/surf/protocols/rssi/_RssiCore.py +++ b/python/surf/protocols/rssi/_RssiCore.py @@ -18,9 +18,21 @@ import pyrogue as pr class RssiCore(pr.Device): + + # Defaults used when older firmware does not advertise its build-time + # capability through the upper byte of registers 0x0C and 0x28. + DEFAULT_MAX_NUM_OUTS_SEG = 8 + DEFAULT_SEGMENT_ADDR_SIZE = 7 + def __init__(self, **kwargs): super().__init__(**kwargs) + # Range bounds are tightened in _start() once firmware capability + # registers can be read. Seed with the legacy defaults so the rogue + # tree is well-formed before the link comes up. + maxOutsSeg = min(self.DEFAULT_MAX_NUM_OUTS_SEG, 0xFF) + maxSegSize = (2**self.DEFAULT_SEGMENT_ADDR_SIZE)*8 + ############################## # Variables ############################## @@ -101,6 +113,10 @@ def __init__(self, **kwargs): varPrefix = ['loc','cur'] for i in range(2): + mode = ('RW' if i==0 else 'RO') + maxOutsSegRange = ({'minimum': 1, 'maximum': maxOutsSeg} if i==0 else {}) + maxSegSizeRange = ({'minimum': 8, 'maximum': maxSegSize} if i==0 else {}) + timeoutRange = ({'minimum': 1} if i==0 else {}) self.add(pr.RemoteVariable( name = (varPrefix[i]+'MaxOutsSeg'), @@ -108,8 +124,9 @@ def __init__(self, **kwargs): offset = 0x0C, bitSize = 8, bitOffset = i*16, - mode = ('RW' if i==0 else 'RO'), + mode = mode, disp = '{:d}', + **maxOutsSegRange, )) self.add(pr.RemoteVariable( @@ -118,8 +135,9 @@ def __init__(self, **kwargs): offset = 0x10, bitSize = 16, bitOffset = i*16, - mode = ('RW' if i==0 else 'RO'), + mode = mode, disp = '{:d}', + **maxSegSizeRange, )) self.add(pr.RemoteVariable( @@ -128,8 +146,9 @@ def __init__(self, **kwargs): offset = 0x14, bitSize = 16, bitOffset = i*16, - mode = ('RW' if i==0 else 'RO'), + mode = mode, disp = '{:d}', + **timeoutRange, )) self.add(pr.RemoteVariable( @@ -138,8 +157,9 @@ def __init__(self, **kwargs): offset = 0x18, bitSize = 16, bitOffset = i*16, - mode = ('RW' if i==0 else 'RO'), + mode = mode, disp = '{:d}', + **timeoutRange, )) self.add(pr.RemoteVariable( @@ -148,8 +168,9 @@ def __init__(self, **kwargs): offset = 0x1C, bitSize = 16, bitOffset = i*16, - mode = ('RW' if i==0 else 'RO'), + mode = mode, disp = '{:d}', + **timeoutRange, )) self.add(pr.RemoteVariable( @@ -158,7 +179,7 @@ def __init__(self, **kwargs): offset = 0x20, bitSize = 8, bitOffset = i*16, - mode = ('RW' if i==0 else 'RO'), + mode = mode, disp = '{:d}', )) @@ -168,13 +189,13 @@ def __init__(self, **kwargs): offset = 0x24, bitSize = 8, bitOffset = i*16, - mode = ('RW' if i==0 else 'RO'), + mode = mode, disp = '{:d}', )) # self.add(pr.RemoteVariable( # name = (varPrefix[i]+'MaxOutOfSeq'), - # description = 'Max out of sequence segments (EACK) [7:0]', + # description = 'Reserved: out-of-sequence/EACK is not implemented in RSSI v1 [7:0]', # offset = 0x28, # bitSize = 8, # bitOffset = i*16, @@ -182,6 +203,31 @@ def __init__(self, **kwargs): # disp = '{:d}', # )) + # Build-time capability advertisements injected into the upper byte of + # registers 0x0C and 0x28. Older firmware reads 0 here, signalling + # that the legacy defaults should be used (see _discoverCapabilities). + self.add(pr.RemoteVariable( + name = 'FwMaxNumOutsSeg', + description = 'Firmware MAX_NUM_OUTS_SEG_G build-time generic (0 = legacy firmware)', + offset = 0x0C, + bitSize = 8, + bitOffset = 24, + mode = 'RO', + hidden = True, + disp = '{:d}', + )) + + self.add(pr.RemoteVariable( + name = 'FwSegmentAddrSize', + description = 'Firmware SEGMENT_ADDR_SIZE_G build-time generic (0 = legacy firmware)', + offset = 0x28, + bitSize = 8, + bitOffset = 24, + mode = 'RO', + hidden = True, + disp = '{:d}', + )) + self.add(pr.RemoteVariable( name = 'ConnectionActive', description = 'Connection Active', @@ -402,7 +448,7 @@ def __init__(self, **kwargs): self.add(pr.RemoteVariable( name = 'TxAckState', - description = 'TX Acknowledge FSM state', + description = 'TX Acknowledge FSM state; EACK state is reserved/unused in RSSI v1', offset = 0x6C, bitSize = 4, bitOffset = 12, @@ -537,3 +583,28 @@ def C_RestartConn(): def C_InjectFault(): self.InjectFault.set(1) self.InjectFault.set(0) + + def _start(self): + super()._start() + self._discoverCapabilities() + + def _discoverCapabilities(self): + # Firmware advertises MAX_NUM_OUTS_SEG_G in 0x0C[31:24] and + # SEGMENT_ADDR_SIZE_G in 0x28[31:24]. Older firmware reads 0 there; + # in that case fall back to the legacy compile-time defaults. + try: + maxNumOutsSeg = self.FwMaxNumOutsSeg.get() + segmentAddrSize = self.FwSegmentAddrSize.get() + except Exception: + return + + if maxNumOutsSeg == 0: + maxNumOutsSeg = self.DEFAULT_MAX_NUM_OUTS_SEG + if segmentAddrSize == 0: + segmentAddrSize = self.DEFAULT_SEGMENT_ADDR_SIZE + + # Range bounds are advisory metadata in rogue (the firmware itself + # clamps writes via the AXI-Lite handler). Update the private state + # directly so GUIs and EPICS records see the discovered limits. + self.locMaxOutsSeg._maximum = min(maxNumOutsSeg, 0xFF) + self.locMaxSegSize._maximum = (2**segmentAddrSize)*8 diff --git a/tests/protocols/rssi/test_RssiAxiLiteRegItf.py b/tests/protocols/rssi/test_RssiAxiLiteRegItf.py index 298f232f57..fa9d7179e8 100644 --- a/tests/protocols/rssi/test_RssiAxiLiteRegItf.py +++ b/tests/protocols/rssi/test_RssiAxiLiteRegItf.py @@ -41,7 +41,7 @@ from cocotbext.axi import AxiLiteBus, AxiLiteMaster, AxiResp from tests.axi.utils import axil_read_u32, axil_write_u32 -from tests.common.regression_utils import env_flag, run_surf_vhdl_test +from tests.common.regression_utils import run_surf_vhdl_test REG_CONTROL = 0x00 @@ -323,10 +323,7 @@ async def build_time_capability_advertised_in_upper_byte_test(dut): PARAMETER_SWEEP = [pytest.param({}, id="axi_lite")] -KNOWN_ISSUE_REASON = "set RUN_RSSI_KNOWN_ISSUE_TESTS=1 to run RSSI cases that require follow-up RTL fixes" - -@pytest.mark.skipif(not env_flag("RUN_RSSI_KNOWN_ISSUE_TESTS", default=False), reason=KNOWN_ISSUE_REASON) @pytest.mark.parametrize("parameters", PARAMETER_SWEEP) def test_RssiAxiLiteRegItf(parameters): run_surf_vhdl_test(