From 7eeceb1dfada5176785a7881e2e48c2c6e9ce475 Mon Sep 17 00:00:00 2001 From: morgan Date: Thu, 26 Sep 2024 13:23:12 +0800 Subject: [PATCH] cxp GW: use memory buffer tx command cxp GW: add 32bit test packet cxp GW: add 32bit trig ack to pipeline cxp GW: restore rx loopback test cxp GW: add idle word inserter cxp GW: add phy to pipeline cxp GW: add trig to pipeline cxp GW: add tx docs --- src/gateware/cxp.py | 157 ++++++++++++++++++-------------------------- 1 file changed, 65 insertions(+), 92 deletions(-) diff --git a/src/gateware/cxp.py b/src/gateware/cxp.py index e6d54fe..583ba30 100644 --- a/src/gateware/cxp.py +++ b/src/gateware/cxp.py @@ -5,7 +5,6 @@ from cxp_downconn import CXP_DownConn_PHY from cxp_upconn import CXP_UpConn_PHY from cxp_pipeline import * -buffer_depth = 128 @FullMemoryWE() class CXP(Module, AutoCSR): @@ -15,74 +14,17 @@ class CXP(Module, AutoCSR): self.submodules.downconn = DownConn_Interface(refclk, downconn_pads, sys_clk_freq, debug_sma, pmod_pads) # TODO: support the option high speed upconn - self.submodules.transmitter = Transmitter() - # TODO: add link layer def get_tx_port(self): - return self.transmitter.mem.get_port(write_capable=True) + return self.upconn.command.mem.get_port(write_capable=True) + + def get_loopback_tx_port(self): + return self.downconn.command.mem.get_port(write_capable=True) def get_mem_size(self): return buffer_depth*downconn_dw -@FullMemoryWE() -class Transmitter(Module, AutoCSR): - def __init__(self): - self.cxp_tx_word_len = CSRStorage(bits_for(buffer_depth)) - self.cxp_tx = CSR() - - # # # - - self.specials.mem = mem = Memory(downconn_dw, buffer_depth) - self.specials.mem_port = mem_port = mem.get_port() - self.source = stream.Endpoint(downconn_layout) - - - tx_done = Signal() - addr_next = Signal(bits_for(buffer_depth)) - addr = Signal.like(addr_next) - addr_rst = Signal() - addr_inc = Signal() - - # increment addr in the same cycle the moment addr_inc is rise - # since memory takes one cycle to shift to the correct addr - self.sync += [ - addr.eq(addr_next), - If(self.cxp_tx.re, self.cxp_tx.w.eq(1)), - If(tx_done, self.cxp_tx.w.eq(0)), - ] - - self.comb += [ - addr_next.eq(addr), - If(addr_rst, - addr_next.eq(addr_next.reset), - ).Elif(addr_inc, - addr_next.eq(addr + 1), - ), - mem_port.adr.eq(addr_next), - self.source.data.eq(mem_port.dat_r) - ] - - self.submodules.fsm = fsm = FSM(reset_state="IDLE") - - fsm.act("IDLE", - addr_rst.eq(1), - If(self.cxp_tx.re, NextState("TRANSMIT")) - ) - fsm.act("TRANSMIT", - self.source.stb.eq(1), - If(self.source.ack, - addr_inc.eq(1), - ), - If(addr_next == self.cxp_tx_word_len.storage, - tx_done.eq(1), - NextState("IDLE") - ) - ) - - self.submodules.debug_out = debug_out = RX_Debug_Buffer() - self.comb += self.source.connect(debug_out.sink) - class DownConn_Interface(Module, AutoCSR): def __init__(self, refclk, downconn_pads, sys_clk_freq, debug_sma, pmod_pads): # # # @@ -91,24 +33,23 @@ class DownConn_Interface(Module, AutoCSR): self.gtxs = phy.gtxs # DEBUG: TX pipeline - self.submodules.debug_src = debug_src = TX_Command_Packet() - self.submodules.trig_ack = trig_ack = Trigger_ACK() + self.submodules.command = command = TX_Command_Packet() self.submodules.testseq = testseq = TX_Test_Packet() - self.submodules.mux = mux = stream.Multiplexer(upconn_layout, 3) - self.submodules.conv = conv = stream.StrideConverter(upconn_layout, downconn_layout) + self.submodules.mux = mux = stream.Multiplexer(word_layout, 2) + self.submodules.pak_wrp = pak_wrp = Packet_Wrapper() + self.submodules.trig_ack = trig_ack = Trigger_ACK_Inserter() self.ack = CSR() - self.mux_sel = CSRStorage(4) - self.sync += trig_ack.ack.eq(self.ack.re), + self.mux_sel = CSRStorage() + self.sync += trig_ack.stb.eq(self.ack.re), self.comb += [ - debug_src.source.connect(mux.sink0), - trig_ack.source.connect(mux.sink1), - testseq.source.connect(mux.sink2), - mux.sel.eq(self.mux_sel.storage) + command.source.connect(mux.sink0), + testseq.source.connect(mux.sink1), + mux.sel.eq(self.mux_sel.storage), ] - tx_pipeline = [mux , conv, phy.sinks[0]] + tx_pipeline = [mux , pak_wrp, trig_ack, phy.sinks[0]] for s, d in zip(tx_pipeline, tx_pipeline[1:]): self.comb += s.source.connect(d.sink) @@ -162,6 +103,8 @@ class UpConn_Interface(Module, AutoCSR): self.clk_reset = CSRStorage(reset=1) self.bitrate2x_enable = CSRStorage() self.tx_enable = CSRStorage() + + # TODO: add busy condition self.tx_busy = CSRStatus() self.tx_testmode_en = CSRStorage() @@ -174,57 +117,87 @@ class UpConn_Interface(Module, AutoCSR): phy.bitrate2x_enable.eq(self.bitrate2x_enable.storage), phy.tx_enable.eq(self.tx_enable.storage), phy.clk_reset.eq(self.clk_reset.re), - self.tx_busy.status.eq(phy.tx_busy), ] - # TODO: rewrite the transmite path into pipeline - # + # Transmission Pipeline + # # test pak ----+ - # from gw | 32 32 8 - # |---/---> mux -----> trig ack -----> idle word ---/--> conv ---/---> trig -----> PHY - # | inserter inserter inserter + # from gw | 32 32 8 + # |---/---> mux -----> packet -----> idle word -----> trigger ack ---/--> conv ---/---> trigger -----> PHY + # | wrapper inserter inserter inserter # data pak ----+ # from fw + # + # Equivalent transmission priority: + # trigger > trigger ack > idle > test/data packet + # To maintain the trigger performance, idle word should not be inserted into trigger or trigger ack. + # + # In low speed serial, the higher priority packet can be inserted in two types of boundary + # Insertion @ char boundary: Trigger packets + # Insertion @ word boundary: Trigger ack & IDLE packets + # The 32 bit part of the pipeline handles the word boundary insertion while the 8 bit part handles the char boundary insertion + + # Packet FIFOs with transmission priority # 0: Trigger packet self.submodules.trig = trig = TX_Trigger() - self.comb += trig.source.connect(phy.sinks[0]) - # DEBUG: INPUT + # # DEBUG: INPUT self.trig_stb = CSR() self.trig_delay = CSRStorage(8) self.linktrigger = CSRStorage(2) self.sync += [ - trig.trig_stb.eq(self.trig_stb.re), + trig.stb.eq(self.trig_stb.re), trig.delay.eq(self.trig_delay.storage), trig.linktrig_mode.eq(self.linktrigger.storage), ] # 1: IO acknowledgment for trigger packet - self.submodules.trig_ack = trig_ack = Trigger_ACK() - self.comb += trig_ack.source.connect(phy.sinks[1]) + self.submodules.trig_ack = trig_ack = Trigger_ACK_Inserter() # DEBUG: INPUT self.ack = CSR() - self.sync += trig_ack.ack.eq(self.ack.re), + self.sync += trig_ack.stb.eq(self.ack.re), - # 2: All other packets - # Control is not timing dependent, all the link layer is done in firmware + # 2: All other packets (data & test packet) + # Control is not timing dependent, all the data packets are handled in firmware + self.submodules.command = command = TX_Command_Packet() self.submodules.testseq = testseq = TX_Test_Packet() - - - self.submodules.mux = mux = stream.Multiplexer(upconn_layout, 2) + self.submodules.mux = mux = stream.Multiplexer(word_layout, 2) self.comb += [ command.source.connect(mux.sink0), testseq.source.connect(mux.sink1), mux.sel.eq(self.tx_testmode_en.storage), - - mux.source.connect(phy.sinks[2]) ] + + self.submodules.pak_wrp = pak_wrp = Packet_Wrapper() + + # IDLE Word + self.submodules.idle = idle = Idle_Word_Inserter() + + # Section 9.2.5.1 (CXP-001-2021) + # IDLE should be transmitter every 10000 words + cnt = Signal(max=10000) + + self.sync += [ + idle.stb.eq(0), + If((~idle.sink.stb) | (cnt == 9999), + idle.stb.eq(1), + cnt.eq(cnt.reset), + ).Else( + cnt.eq(cnt + 1), + ), + ] + + self.submodules.converter = converter = stream.StrideConverter(word_layout, char_layout) + + tx_pipeline = [mux, pak_wrp, idle, trig_ack, converter, trig, phy] + for s, d in zip(tx_pipeline, tx_pipeline[1:]): + self.comb += s.source.connect(d.sink)