From 6ae29ba6b42e0651779a6a6794bad012cfe8f34c Mon Sep 17 00:00:00 2001 From: morgan Date: Mon, 17 Jun 2024 13:19:37 +0800 Subject: [PATCH] cxp upconn gw: add low speed serial PHY testing: add debug fifo output b4 encoder cxp upconn: add low speed serial cxp upconn: add reset, tx_busy, tx_enable cxp upconn: add clockgen module for 20.83Mbps & 41.66Mbps using counters cxp upconn: add oserdes using CEInserter cxp upconn: add packet scheduler with CEInserter scheduler: send priority packet during word/char boundary scheduler: send IDLE every 10000 words scheduler: encode packet before sending to oserdes --- src/gateware/cxp_upconn.py | 374 +++++++++++++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 src/gateware/cxp_upconn.py diff --git a/src/gateware/cxp_upconn.py b/src/gateware/cxp_upconn.py new file mode 100644 index 0000000..922533f --- /dev/null +++ b/src/gateware/cxp_upconn.py @@ -0,0 +1,374 @@ +from math import ceil + +from migen import * +from migen.genlib.coding import PriorityEncoder + +from misoc.cores.code_8b10b import SingleEncoder +from misoc.interconnect import stream +from misoc.interconnect.csr import * + +from cxp_pipeline import upconn_layout + +IDLE_CHARS = Array([ + #[char, k] + [0xBC, 1], #K28.5 + [0x3C, 1], #K28.1 + [0x3C, 1], #K28.1 + [0xB5, 0], #D21.5 +]) + +@ResetInserter() +class UpConn_ClockGen(Module): + def __init__(self, sys_clk_freq): + self.clk = Signal() + self.clk_10x = Signal() # 20.83MHz 48ns or 41.66MHz 24ns + + self.freq2x_enable = Signal() + # # # + + period = 1e9/sys_clk_freq + max_count = ceil(48/period) + counter = Signal(max=max_count, reset=max_count-1) + + clk_div = Signal(max=10, reset=9) + + self.sync += [ + self.clk.eq(0), + self.clk_10x.eq(0), + + If(counter == 0, + self.clk_10x.eq(1), + If(self.freq2x_enable, + counter.eq(int(max_count/2)-1), + ).Else( + counter.eq(counter.reset), + ), + ).Else( + counter.eq(counter-1), + ), + + If(counter == 0, + If(clk_div == 0, + self.clk.eq(1), + clk_div.eq(clk_div.reset), + ).Else( + clk_div.eq(clk_div-1), + ) + ) + + ] + +@ResetInserter() +@CEInserter() +class SERDES_10bits(Module): + def __init__(self, pad): + self.oe = Signal() + self.d = Signal(10) + + # # # + + o = Signal() + tx_bitcount = Signal(max=10) + tx_reg = Signal(10) + + # DEBUG: + self.o = Signal() + self.comb += self.o.eq(o) + + self.specials += Instance("OBUF", i_I=o, o_O=pad), + + self.sync += [ + If(self.oe, + # send LSB first + o.eq(tx_reg[0]), + tx_reg.eq(Cat(tx_reg[1:], 0)), + tx_bitcount.eq(tx_bitcount + 1), + + If(tx_bitcount == 9, + tx_bitcount.eq(0), + tx_reg.eq(self.d), + ), + ).Else( + o.eq(0), + tx_bitcount.eq(0), + ) + ] + +@ResetInserter() +class Transmit_Scheduler(Module): + def __init__(self, interface, debug_buf): + self.tx_enable = Signal() + + self.oe = Signal() + self.ce = Signal() + + # # # + + self.submodules.startup_fsm = startup_fsm = CEInserter()(FSM(reset_state="WAIT_TX_ENABLE")) + self.submodules.encoder = encoder = CEInserter()(SingleEncoder(True)) + self.comb += [ + startup_fsm.ce.eq(self.ce), + encoder.ce.eq(self.ce), + ] + + tx_charcount = Signal(max=4) + tx_wordcount = Signal(max=10000) + + idling = Signal() + priorities = Signal.like(interface.pe.o) + + # DEBUG: + self.idling = Signal() + self.tx_charcount = Signal(max=4) + self.comb += [ + self.idling.eq(idling), + self.tx_charcount.eq(tx_charcount), + ] + + startup_fsm.act("WAIT_TX_ENABLE", + If(self.tx_enable, + NextValue(idling, 1), + NextValue(tx_charcount, 0), + NextValue(encoder.d, IDLE_CHARS[0][0]), + NextValue(encoder.k, IDLE_CHARS[0][1]), + NextState("START_TX"), + + # DEBUG: + If(debug_buf.sink_ack, + NextValue(debug_buf.sink_stb, 1), + NextValue(debug_buf.sink_data, IDLE_CHARS[0][0]), + NextValue(debug_buf.sink_k, IDLE_CHARS[0][1]), + ) + ) + ) + + startup_fsm.act("START_TX", + self.oe.eq(1), + If((~self.tx_enable) & (tx_charcount == 3), + NextState("WAIT_TX_ENABLE") + ) + ) + + # hold ack for only one sys clk cycle to prevent data loss + for ack in interface.sink_ack: + self.sync += ack.eq(0) + + self.sync += [ + debug_buf.sink_stb.eq(0), + If(self.oe & self.ce, + encoder.disp_in.eq(encoder.disp_out), + If((~interface.pe.n) & (interface.pe.o == 0), + # trigger packets are inserted at char boundary and don't contribute to word count + interface.sink_ack[0].eq(1), + encoder.d.eq(interface.sink_data[0]), + encoder.k.eq(interface.sink_k[0]), + + # DEBUG: + If(debug_buf.sink_ack, + debug_buf.sink_stb.eq(1), + debug_buf.sink_data.eq(interface.sink_data[0]), + debug_buf.sink_k.eq(interface.sink_k[0]), + ) + ).Else( + If(tx_charcount == 3, + tx_charcount.eq(0), + + # Section 9.2.4 (CXP-001-2021) + # other priorities packets are inserted at word boundary + If((~interface.pe.n) & (tx_wordcount != 9999), + idling.eq(0), + priorities.eq(interface.pe.o), + tx_wordcount.eq(tx_wordcount + 1), + + interface.sink_ack[interface.pe.o].eq(1), + encoder.d.eq(interface.sink_data[interface.pe.o]), + encoder.k.eq(interface.sink_k[interface.pe.o]), + + # DEBUG: + If(debug_buf.sink_ack, + debug_buf.sink_stb.eq(1), + debug_buf.sink_data.eq(interface.sink_data[interface.pe.o]), + debug_buf.sink_k.eq(interface.sink_k[interface.pe.o]), + ) + ).Else( + # Section 9.2.5.1 (CXP-001-2021) + # IDLE word shall be transmitted at least once every 10,000 words, but should not be inserted into trigger packet + idling.eq(1), + tx_wordcount.eq(0), + + encoder.d.eq(IDLE_CHARS[0][0]), + encoder.k.eq(IDLE_CHARS[0][1]), + + # DEBUG: + If(debug_buf.sink_ack, + debug_buf.sink_stb.eq(1), + debug_buf.sink_data.eq(IDLE_CHARS[0][0]), + debug_buf.sink_k.eq(IDLE_CHARS[0][1]), + ) + ) + ).Else( + tx_charcount.eq(tx_charcount + 1), + If(~idling, + tx_wordcount.eq(tx_wordcount + 1), + interface.sink_ack[priorities].eq(1), + encoder.d.eq(interface.sink_data[priorities]), + encoder.k.eq(interface.sink_k[priorities]), + + # DEBUG: + If(debug_buf.sink_ack, + debug_buf.sink_stb.eq(1), + debug_buf.sink_data.eq(interface.sink_data[priorities]), + debug_buf.sink_k.eq(interface.sink_k[priorities]), + ) + ).Else( + encoder.d.eq(IDLE_CHARS[tx_charcount + 1][0]), + encoder.k.eq(IDLE_CHARS[tx_charcount + 1][1]), + + # DEBUG: + If(debug_buf.sink_ack, + debug_buf.sink_stb.eq(1), + debug_buf.sink_data.eq(IDLE_CHARS[tx_charcount + 1][0]), + debug_buf.sink_k.eq(IDLE_CHARS[tx_charcount + 1][1]), + ) + ) + ), + ), + ) + ] + +class PHY_Interface(Module): + def __init__(self, layout, nsink): + sink_stb = Signal(nsink) + self.sink_ack = Array(Signal() for _ in range(nsink)) + self.sink_data = Array(Signal(8) for _ in range(nsink)) + self.sink_k = Array(Signal() for _ in range(nsink)) + + # # # + + self.sinks = [] + for i in range(nsink): + sink = stream.Endpoint(layout) + self.sinks += [sink] + + self.comb += [ + sink.ack.eq(self.sink_ack[i]), + sink_stb[i].eq(sink.stb), + self.sink_data[i].eq(sink.data), + self.sink_k[i].eq(sink.k), + ] + + # FIFOs transmission priority + self.submodules.pe = PriorityEncoder(nsink) + self.comb += self.pe.i.eq(sink_stb) + +class Debug_buffer(Module,AutoCSR): + def __init__(self, layout): + self.sink_stb = Signal() + self.sink_ack = Signal() + self.sink_data = Signal(8) + self.sink_k = Signal() + + # # # + + self.submodules.buf_out = buf_out = stream.SyncFIFO(layout, 128) + + self.sync += [ + buf_out.sink.stb.eq(self.sink_stb), + self.sink_ack.eq(buf_out.sink.ack), + buf_out.sink.data.eq(self.sink_data), + buf_out.sink.k.eq(self.sink_k), + ] + + self.inc = CSR() + self.dout_pak = CSRStatus(8) + self.kout_pak = CSRStatus() + self.dout_valid = CSRStatus() + + self.sync += [ + # output + buf_out.source.ack.eq(self.inc.re), + self.dout_pak.status.eq(buf_out.source.data), + self.kout_pak.status.eq(buf_out.source.k), + self.dout_valid.status.eq(buf_out.source.stb), + ] + + +class CXP_UpConn_PHY(Module, AutoCSR): + def __init__(self, pad, sys_clk_freq, debug_sma, pmod_pads, nsink=3): + self.bitrate2x_enable = Signal() + self.clk_reset = Signal() + + self.tx_enable = Signal() + self.tx_busy = Signal() + + # # # + + self.submodules.cg = cg = UpConn_ClockGen(sys_clk_freq) + self.submodules.interface = interface = PHY_Interface(upconn_layout, nsink) + + self.sinks = interface.sinks + + # DEBUG: + self.submodules.debug_buf = debug_buf = Debug_buffer(upconn_layout) + + self.submodules.scheduler = scheduler = Transmit_Scheduler(interface, debug_buf) + self.submodules.serdes = serdes = SERDES_10bits(pad) + + self.comb += [ + self.tx_busy.eq(~interface.pe.n), + + cg.reset.eq(self.clk_reset), + cg.freq2x_enable.eq(self.bitrate2x_enable), + + scheduler.reset.eq(self.clk_reset), + scheduler.ce.eq(cg.clk), + scheduler.tx_enable.eq(self.tx_enable), + + serdes.reset.eq(self.clk_reset), + serdes.ce.eq(cg.clk_10x), + serdes.d.eq(scheduler.encoder.output), + serdes.oe.eq(scheduler.oe), + ] + + # DEBUG: remove pads + + prioity_0 = Signal() + word_bound = Signal() + + p0 = Signal() + p3 = Signal() + self.comb += [ + prioity_0.eq((~interface.pe.n) & (interface.pe.o == 0)), + word_bound.eq(scheduler.tx_charcount == 3), + + # because of clk delay + p0.eq(scheduler.tx_charcount == 2), + p3.eq(scheduler.tx_charcount == 1), + + ] + self.specials += [ + # # debug sma + Instance("OBUF", i_I=serdes.o, o_O=debug_sma.p_tx), + Instance("OBUF", i_I=cg.clk_10x, o_O=debug_sma.n_rx), + + + + # # pmod 0-7 pin + # Instance("OBUF", i_I=serdes.o, o_O=pmod_pads[0]), + # Instance("OBUF", i_I=cg.clk_10x, o_O=pmod_pads[1]), + # Instance("OBUF", i_I=~tx_fifos.pe.n, o_O=pmod_pads[2]), + # Instance("OBUF", i_I=prioity_0, o_O=pmod_pads[3]), + # Instance("OBUF", i_I=word_bound, o_O=pmod_pads[4]), + # Instance("OBUF", i_I=debug_buf.buf_out.sink.stb, o_O=pmod_pads[4]), + # Instance("OBUF", i_I=debug_buf.buf_out.sink.ack, o_O=pmod_pads[5]), + # Instance("OBUF", i_I=debug_buf.buf_out.source.stb, o_O=pmod_pads[6]), + # Instance("OBUF", i_I=debug_buf.buf_out.source.ack, o_O=pmod_pads[7]), + + # Instance("OBUF", i_I=scheduler.idling, o_O=pmod_pads[5]), + # # Instance("OBUF", i_I=tx_fifos.source_ack[0], o_O=pmod[6]), + # # Instance("OBUF", i_I=tx_fifos.source_ack[2], o_O=pmod[6]), + # # Instance("OBUF", i_I=tx_fifos.source_ack[1], o_O=pmod[7]), + # Instance("OBUF", i_I=p0, o_O=pmod_pads[6]), + # Instance("OBUF", i_I=p3, o_O=pmod_pads[7]), + ] +