forked from M-Labs/artiq-zynq
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
This commit is contained in:
parent
eed2a4fee9
commit
6ae29ba6b4
|
@ -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]),
|
||||
]
|
||||
|
Loading…
Reference in New Issue