From 7f23de0ce81670d06408ba238a94a9e93943a863 Mon Sep 17 00:00:00 2001 From: occheung Date: Sun, 23 Apr 2023 11:42:18 +0800 Subject: [PATCH] init --- .gitignore | 6 + bscan_spi_xc7a100t.bit | Bin 0 -> 404986 bytes buffer.py | 26 +++ comm.py | 22 +++ default.nix | 55 ++++++ eem_helpers.py | 29 +++ io_loopback.py | 41 ++++ kasli_crg.py | 125 ++++++++++++ loopback.py | 44 +++++ serdes.py | 122 ++++++++++++ serdes_loopback.py | 102 ++++++++++ single_serdes_loopback.py | 164 ++++++++++++++++ sync_serdes.py | 387 ++++++++++++++++++++++++++++++++++++++ test_aligner.py | 198 +++++++++++++++++++ test_buffer.py | 46 +++++ uart.py | 105 +++++++++++ util.py | 11 ++ 17 files changed, 1483 insertions(+) create mode 100644 .gitignore create mode 100644 bscan_spi_xc7a100t.bit create mode 100644 buffer.py create mode 100644 comm.py create mode 100644 default.nix create mode 100644 eem_helpers.py create mode 100644 io_loopback.py create mode 100644 kasli_crg.py create mode 100644 loopback.py create mode 100644 serdes.py create mode 100644 serdes_loopback.py create mode 100644 single_serdes_loopback.py create mode 100644 sync_serdes.py create mode 100644 test_aligner.py create mode 100644 test_buffer.py create mode 100644 uart.py create mode 100644 util.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff57468 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build +__pycache__ +test +*.vcd +*.cfg +*.txt \ No newline at end of file diff --git a/bscan_spi_xc7a100t.bit b/bscan_spi_xc7a100t.bit new file mode 100644 index 0000000000000000000000000000000000000000..c456de32cb21c76e6f31514a10fb311cc66b39a3 GIT binary patch literal 404986 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 K1OJp?1OErD&Q`Sm literal 0 HcmV?d00001 diff --git a/buffer.py b/buffer.py new file mode 100644 index 0000000..046b569 --- /dev/null +++ b/buffer.py @@ -0,0 +1,26 @@ +from migen import * +from migen.genlib.fifo import SyncFIFO + + +class Buffer(Module): + def __init__(self, width=8, depth=128): + self.din = Signal(width) + self.i_stb = Signal() + + self.dout = Signal(width) + self.o_stb = Signal() + self.o_ack = Signal() + + # Underlying data structure + self.submodules.fifo = SyncFIFO(width, depth) + + self.comb += [ + # TX path + self.o_stb.eq(self.fifo.readable), + self.dout.eq(self.fifo.dout), + self.fifo.re.eq(self.o_ack), + + # RX path + self.fifo.we.eq(self.i_stb), + self.fifo.din.eq(self.din), + ] diff --git a/comm.py b/comm.py new file mode 100644 index 0000000..03f72e7 --- /dev/null +++ b/comm.py @@ -0,0 +1,22 @@ +import serial + + +def main(): + comm = serial.Serial("/dev/ttyUSB3", 115200) + # comm.write(b"Hello World!") + + # for _ in range(32): + while True: + byte = comm.read(2) + print(f'{byte[0]:0>8b}' + f'{byte[1]:0>8b}') + byte = comm.read(1) + print(f'{byte[0]:0>8b}') + # cached_byte = None + # while True: + # byte = comm.read(1) + # if byte != cached_byte: + # cached_byte = byte + # print(f'{byte[0]:0>8b}') + +if __name__ == "__main__": + main() diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..8253755 --- /dev/null +++ b/default.nix @@ -0,0 +1,55 @@ +{ pkgs ? import {} }: + +let + migen = pkgs.python3Packages.buildPythonPackage rec { + name = "migen"; + + src = pkgs.fetchFromGitHub { + owner = "m-labs"; + repo = "migen"; + rev = "7bc4eb1387b39159a74c1dbd1b820728e0bfbbaa"; + sha256 = "039jk8y7f0vhr32svg3nd23i88c0bhws8ngxwk9bdznfxvhiy1h6"; + fetchSubmodules = true; + }; + + propagatedBuildInputs = with pkgs.python3Packages; [ colorama ]; + }; + + vivadoDeps = pkgs: with pkgs; [ + libxcrypt + ncurses5 + zlib + libuuid + xorg.libSM + xorg.libICE + xorg.libXrender + xorg.libX11 + xorg.libXext + xorg.libXtst + xorg.libXi + freetype + fontconfig + ]; + + vivadoEnv = pkgs.buildFHSUserEnv { + name = "vivado-env"; + targetPkgs = vivadoDeps; + }; + + vivado = pkgs.buildFHSUserEnv { + name = "vivado"; + targetPkgs = vivadoDeps; + profile = "set -e; source /opt/Xilinx/Vivado/2022.2/settings64.sh"; + runScript = "vivado"; + }; + +in pkgs.mkShell { + name = "UART-Testing"; + buildInputs = [ + migen + pkgs.python3Packages.pyserial + vivado + vivadoEnv + pkgs.python3Packages.numpy + ]; +} diff --git a/eem_helpers.py b/eem_helpers.py new file mode 100644 index 0000000..91b7bfb --- /dev/null +++ b/eem_helpers.py @@ -0,0 +1,29 @@ +from migen import * +from migen.build.generic_platform import * + + +def _eem_signal(i): + n = "d{}".format(i) + if i == 0: + n += "_cc" + return n + + +def _eem_pin(eem, i, pol): + return "eem{}:{}_{}".format(eem, _eem_signal(i), pol) + + +def default_iostandard(eem): + return IOStandard("LVDS_25") + + +def diff_io(eem, iostandard=default_iostandard): + return [("dio{}".format(eem), i, + Subsignal("p", Pins(_eem_pin(eem, i, "p"))), + Subsignal("n", Pins(_eem_pin(eem, i, "n"))), + iostandard(eem)) + for i in range(8)] + + +def generate_pads(platform, eem): + platform.add_extension(diff_io(eem)) diff --git a/io_loopback.py b/io_loopback.py new file mode 100644 index 0000000..bbb7df4 --- /dev/null +++ b/io_loopback.py @@ -0,0 +1,41 @@ +from migen import * + + +class IOLoopBack(Module): + def __init__(self, pads): + self.o = Signal(4) + self.i = Signal(4) + self.t = Signal(4) + + for i in range(4): + self.specials += Instance("IOBUFDS", + o_O=self.o[i], + io_IO=pads[i].p, + io_IOB=pads[i].n, + i_I=self.i[i], + # Always enable input buffer, so it is actually a loop back + i_T=self.t[i], + ) + + +class SingleIOLoopback(Module): + def __init__(self, pad): + self.o = Signal() + self.i = Signal() + self.t = Signal() + + self.specials += Instance( + # "IOBUFDS_DCIEN", + "IOBUFDS", + # p_DIFF_TERM="TRUE", + # p_IBUF_LOW_PWR="TRUE", + # p_USE_IBUFDISABLE="TRUE", + o_O=self.o, + io_IO=pad.p, + io_IOB=pad.n, + i_I=self.i, + # Always enable input buffer, so it is actually a loop back + i_T=self.t, + # i_IBUFDISABLE=~self.t, + # i_DCITERMDISABLE=~self.t, + ) diff --git a/kasli_crg.py b/kasli_crg.py new file mode 100644 index 0000000..50407bf --- /dev/null +++ b/kasli_crg.py @@ -0,0 +1,125 @@ +from migen import * +from migen.build.platforms.sinara import kasli +from migen.genlib.resetsync import AsyncResetSynchronizer + + +class KasliCRG(Module): + def __init__(self, platform): + self.platform = platform + + # Generated clock domains + self.clock_domains.cd_sys = ClockDomain() + self.clock_domains.cd_sys5x = ClockDomain() + self.clock_domains.cd_rx_sys = ClockDomain() + self.clock_domains.cd_rx_sys5x = ClockDomain() + self.clock_domains.cd_clk200 = ClockDomain() + + # Configure system clock using GTP ports + self.sys_clk_freq = 125e6 + clk125 = self.platform.request("clk125_gtp") + clk125_buf = Signal() + clk125_div2 = Signal() + + self.specials += Instance("IBUFDS_GTE2", + i_CEB=0, + i_I=clk125.p, i_IB=clk125.n, + o_O=clk125_buf, + o_ODIV2=clk125_div2) + + # MMCM to generate different frequencies + mmcm_fb = Signal() + mmcm_locked = Signal() + mmcm_sys = Signal() + mmcm_sys5x = Signal() + mmcm_rx_sys = Signal() + mmcm_rx_sys5x = Signal() + + # PLL to IDELAYCTRL clock + pll_locked = Signal() + pll_fb = Signal() + pll_clk200 = Signal() + + # Actual MMCM/PLL instances + self.specials += [ + Instance("MMCME2_BASE", + p_CLKIN1_PERIOD=16.0, + i_CLKIN1=clk125_div2, + + i_CLKFBIN=mmcm_fb, + o_CLKFBOUT=mmcm_fb, + o_LOCKED=mmcm_locked, + + # VCO @ 1.25GHz with MULT=20 + p_CLKFBOUT_MULT_F=20.0, p_DIVCLK_DIVIDE=1, + + # ~125MHz + p_CLKOUT0_DIVIDE_F=10.0, p_CLKOUT0_PHASE=0.0, o_CLKOUT0=mmcm_sys, + + # ~625MHz + p_CLKOUT1_DIVIDE=2, p_CLKOUT1_PHASE=0.0, o_CLKOUT1=mmcm_sys5x, + + # ~125MHz separated from sysclk, for RX + p_CLKOUT2_DIVIDE=10, p_CLKOUT2_PHASE=0.0, o_CLKOUT2=mmcm_rx_sys, + + # ~625MHz separated from sysclk, for RX + p_CLKOUT3_DIVIDE=2, p_CLKOUT3_PHASE=0.0, o_CLKOUT3=mmcm_rx_sys5x, + + # Leftovers... + # p_CLKOUT2_DIVIDE=2, p_CLKOUT2_PHASE=90.0, o_CLKOUT2=mmcm_sys4x_dqs, + ), + Instance("PLLE2_BASE", + p_CLKIN1_PERIOD=16.0, + i_CLKIN1=clk125_div2, + + i_CLKFBIN=pll_fb, + o_CLKFBOUT=pll_fb, + o_LOCKED=pll_locked, + + # VCO @ 1GHz + p_CLKFBOUT_MULT=16, p_DIVCLK_DIVIDE=1, + + # 200MHz for IDELAYCTRL + p_CLKOUT0_DIVIDE=5, p_CLKOUT0_PHASE=0.0, o_CLKOUT0=pll_clk200, + ), + ] + + self.specials += [ + Instance("BUFG", i_I=mmcm_sys, o_O=self.cd_sys.clk), + Instance("BUFG", i_I=mmcm_sys5x, o_O=self.cd_sys5x.clk), + Instance("BUFG", i_I=mmcm_rx_sys, o_O=self.cd_rx_sys.clk), + Instance("BUFG", i_I=mmcm_rx_sys5x, o_O=self.cd_rx_sys5x.clk), + Instance("BUFG", i_I=pll_clk200, o_O=self.cd_clk200.clk), + AsyncResetSynchronizer(self.cd_clk200, ~pll_locked), + ] + + reset_counter = Signal(4, reset=15) + ic_reset = Signal(reset=1) + self.sync.clk200 += \ + If(reset_counter != 0, + reset_counter.eq(reset_counter - 1) + ).Else( + ic_reset.eq(0) + ) + self.specials += Instance("IDELAYCTRL", i_REFCLK=ClockSignal("clk200"), i_RST=ic_reset) + + # Add clock costraints for all clock signals + platform.add_period_constraint(self.cd_sys.clk, 8.) + platform.add_period_constraint(self.cd_sys5x.clk, 1.6) + platform.add_period_constraint(self.cd_rx_sys.clk, 8.) + platform.add_period_constraint(self.cd_rx_sys5x.clk, 1.6) + platform.add_period_constraint(self.cd_clk200.clk, 5.) + + platform.add_platform_command( + "set_false_path -quiet " + "-through [get_pins -filter {{REF_PIN_NAME == OQ || REF_PIN_NAME == TQ}} " + "-of [get_cells -filter {{REF_NAME == OSERDESE2}}]] " + "-to [get_pins -filter {{REF_PIN_NAME == D}} " + "-of [get_cells -filter {{REF_NAME == ISERDESE2}}]]" + ) + platform.add_platform_command( + "set_false_path -quiet " + "-through [get_pins -filter {{REF_PIN_NAME == OQ || REF_PIN_NAME == TQ}} " + "-of [get_cells -filter {{REF_NAME == OSERDESE2}}]] " + "-to [get_pins -filter {{REF_PIN_NAME == DDLY}} " + "-of [get_cells -filter {{REF_NAME == ISERDESE2}}]]" + ) diff --git a/loopback.py b/loopback.py new file mode 100644 index 0000000..fc9a856 --- /dev/null +++ b/loopback.py @@ -0,0 +1,44 @@ +from migen import * +from migen.build.platforms.sinara import kasli +from migen.genlib.fifo import SyncFIFO +from uart import UART +from kasli_crg import KasliCRG + + +class UARTLoopBack(Module): + def __init__(self, sys_clk_freq): + self.uart_rx = Signal() + self.uart_tx = Signal() + + self.submodules.uart = UART(round((115200/sys_clk_freq)*2**32)) + self.comb += [ + self.uart.phy_rx.eq(self.uart_rx), + self.uart_tx.eq(self.uart.phy_tx), + ] + + # Attach buffer between UART RX --> TX + # This constitutes the loopback channel + self.submodules.buffer = SyncFIFO(8, 64) + self.comb += [ + self.buffer.din.eq(self.uart.rx_data), + self.buffer.we.eq(self.uart.rx_stb), + self.uart.tx_data.eq(self.buffer.dout), + self.uart.tx_stb.eq(self.buffer.readable), + self.buffer.re.eq(self.uart.tx_ack), + ] + + +if __name__ == "__main__": + platform = kasli.Platform(hw_rev="v2.0") + crg = KasliCRG(platform) + top = UARTLoopBack(crg.sys_clk_freq) + + # Wire up UART core to the pads + uart_pads = platform.request("serial") + top.comb += [ + top.uart_rx.eq(uart_pads.rx), + uart_pads.tx.eq(top.uart_tx), + ] + + top.submodules += crg + platform.build(top) diff --git a/serdes.py b/serdes.py new file mode 100644 index 0000000..f6a55e1 --- /dev/null +++ b/serdes.py @@ -0,0 +1,122 @@ +from migen import * + + +class SerTX(Module): + def __init__(self): + self.txdata = Signal(20) + self.ser_out = Signal(4) + self.t_out = Signal(4) + # TODO: Create T pins + + # Transmitter PHY: 4-wire + for i in range(4): + # Serialize 5 bits into each channel + + # TX SERDES + self.specials += Instance("OSERDESE2", + p_DATA_RATE_OQ="SDR", p_DATA_RATE_TQ="BUF", + p_DATA_WIDTH=5, p_TRISTATE_WIDTH=1, + p_INIT_OQ=0b00000, + o_OQ=self.ser_out[i], o_TQ=self.t_out[i], + i_RST=ResetSignal(), + i_CLK=ClockSignal("sys5x"), + i_CLKDIV=ClockSignal(), + i_D1=self.txdata[i*5 + 0], + i_D2=self.txdata[i*5 + 1], + i_D3=self.txdata[i*5 + 2], + i_D4=self.txdata[i*5 + 3], + i_D5=self.txdata[i*5 + 4], + i_TCE=1, i_OCE=1, + # TODO: Hardcode t_in? Output disable is always unnecessary? + i_T1=0) + + +class DesRX(Module): + def __init__(self): + self.rxdata = Signal(20) + self.ser_in = Signal(4) + + for i in range(4): + # Deserialize 5 bits from each channel + # RX SERDES + self.specials += [ + Instance("ISERDESE2", + p_DATA_RATE="SDR", + p_DATA_WIDTH=5, + p_INTERFACE_TYPE="NETWORKING", p_NUM_CE=1, + o_Q1=self.rxdata[i*5 + 4], + o_Q2=self.rxdata[i*5 + 3], + o_Q3=self.rxdata[i*5 + 2], + o_Q4=self.rxdata[i*5 + 1], + o_Q5=self.rxdata[i*5 + 0], + i_D=self.ser_in[i], + i_CLK=ClockSignal("rx_sys5x"), + i_CLKB=~ClockSignal("rx_sys5x"), + i_CE1=1, + i_RST=ResetSignal("rx_sys"), + i_CLKDIV=ClockSignal("rx_sys")), + + # # Tunable delay + # Instance("IDELAYE2", + # p_DELAY_SRC="IDATAIN", p_SIGNAL_PATTERN="DATA", + # p_CINVCTRL_SEL="FALSE", p_HIGH_PERFORMANCE_MODE="TRUE", p_REFCLK_FREQUENCY=200.0, + # p_PIPE_SEL="FALSE", p_IDELAY_TYPE="VARIABLE", p_IDELAY_VALUE=0, + + # i_C=ClockSignal(), + # i_LD=self._dly_sel.storage[i//8] & self._rdly_dq_rst.re, + # i_CE=self._dly_sel.storage[i//8] & self._rdly_dq_inc.re, + # i_LDPIPEEN=0, i_INC=1, + + # i_IDATAIN=dq_i_nodelay, o_DATAOUT=dq_i_delayed + # ) + ] + + +# class DoubleDesRX(Module): +# def __init__(self): +# self.rxdata = Signal(20) + +# self.rx_first_edge = Signal() +# rx_raw = Array(Signal(20) for _ in range(2)) +# self.comb += [ +# rxdata.eq(rx_raw[self.rx_first_edge]) +# ] + +# # Receiver PHY: 4-wire +# for i in range(4): +# # Deserialize 5 bits from each channel +# # With 2x oversampling + +# # RX SERDES +# self.specials += Instance("ISERDESE2", p_DATA_RATE="DDR", +# p_DATA_WIDTH=10, +# p_INTERFACE_TYPE="NETWORKING", p_NUM_CE=1, +# p_SERDES_MODE="MASTER", +# o_Q1=rx_raw[1][i*5 + 4], o_Q2=rx_raw[0][i*5 + 4], +# o_Q3=rx_raw[1][i*5 + 3], o_Q4=rx_raw[0][i*5 + 3], +# o_Q5=rx_raw[1][i*5 + 2], o_Q6=rx_raw[0][i*5 + 2], +# o_Q7=rx_raw[1][i*5 + 1], o_Q8=rx_raw[0][i*5 + 1], +# i_D=self.ser_in[i], +# # We are using 5x for SDR +# i_CLK=ClockSignal("sys5x"), +# i_CLKB=~ClockSignal("sys5x"), +# i_CE1=1, +# i_RST=ResetSignal(), +# i_CLKDIV=ClockSignal(), +# o_SHIFTOUT1=serdes_link1, +# o_SHIFTOUT2=serdes_link2) + +# self.specials += Instance("ISERDESE2", p_DATA_RATE="DDR", +# p_DATA_WIDTH=10, +# p_INTERFACE_TYPE="NETWORKING", p_NUM_CE=1, +# p_SERDES_MODE="SLAVE", +# o_Q1=rx_raw[1][i*5], o_Q2=rx_raw[0][i*5], +# i_D=self.ser_in[i], +# # We are using 5x for SDR +# i_CLK=ClockSignal("sys5x"), +# i_CLKB=~ClockSignal("sys5x"), +# i_CE1=1, +# i_RST=ResetSignal(), +# i_CLKDIV=ClockSignal(), +# i_SHIFTIN1=serdes_link1, +# i_SHIFTIN2=serdes_link2) diff --git a/serdes_loopback.py b/serdes_loopback.py new file mode 100644 index 0000000..de54de4 --- /dev/null +++ b/serdes_loopback.py @@ -0,0 +1,102 @@ +from migen import * +from serdes import * +from migen.genlib.fifo import SyncFIFO +from migen.build.platforms.sinara import kasli +from migen.genlib.misc import WaitTimer +from kasli_crg import KasliCRG +from eem_helpers import generate_pads +from uart import UART +from io_loopback import IOLoopBack + + +SEPARATOR = Constant(0b0101) + +class SerDesLoopBack(Module): + def __init__(self, io_pads, sys_clk_freq): + self.uart_rx = Signal() + self.uart_tx = Signal() + + self.submodules.uart = UART(round((115200/sys_clk_freq)*2**32)) + self.comb += [ + self.uart.phy_rx.eq(self.uart_rx), + self.uart_tx.eq(self.uart.phy_tx), + ] + + self.submodules.tx = SerTX() + self.submodules.rx = DesRX() + + # The actual channel + self.submodules.channel = IOLoopBack(io_pads) + + # # Additional timer: Only permit UART transmission when timer is not up + # self.submodules.wait_timer = WaitTimer(10) + + # Memoize the previous rxdata + self.rxdata_r = Signal(8) + + # Attach FIFO to UART TX, send rate is too slow w.r.t sysclk + self.submodules.tx_fifo = SyncFIFO(8, 64) + + self.comb += [ + # RX path: From UART to channel + # self.rx_buffer.din.eq(self.uart.rx_data), + # self.rx_buffer.we.eq(self.uart.rx_stb), + self.tx.txdata[:8].eq(Mux(self.uart.rx_stb, self.uart.rx_data, 0)), + self.tx.txdata[8:12].eq(Mux(SEPARATOR, self.uart.rx_data, 0)), + self.tx.txdata[12:].eq(Mux(self.uart.rx_stb, self.uart.rx_data, 0)), + + # Loopback channel + self.channel.i.eq(self.tx.ser_out), + self.rx.ser_in.eq(self.channel.o), + self.channel.t.eq(self.tx.t_out), + # self.rx.ser_in.eq(self.tx.ser_out), + # self.rx.ser_in[3].eq(self.tx.ser_out[3]), + + # TX path + # self.uart.tx_data.eq(self.tx_buffer.dout), + # self.uart.tx_stb.eq(self.tx_buffer.readable), + # self.tx_buffer.re.eq(self.uart.tx_ack), + self.uart.tx_data.eq(self.tx_fifo.dout), + self.uart.tx_stb.eq(self.tx_fifo.readable), + self.tx_fifo.re.eq(self.uart.tx_ack), + ] + + # Timer control + self.sync += [ + # Send data to FIFO if not repeated + If(self.rxdata_r != self.rx.rxdata[:8], + self.rxdata_r.eq(self.rx.rxdata), + self.tx_fifo.din.eq(self.rx.rxdata), + self.tx_fifo.we.eq(1) + ).Else( + self.tx_fifo.we.eq(0) + ) + ] + + # self.sync.sys5x += [ + # self.rx.ser_in.eq(self.tx.ser_out), + # ] + + +if __name__ == "__main__": + platform = kasli.Platform(hw_rev="v2.0") + + # Generate pads for the I/O blocks + eem = 0 + generate_pads(platform, eem) + pads = [ + platform.request("dio{}".format(eem), i) for i in range(4) + ] + + crg = KasliCRG(platform) + top = SerDesLoopBack(pads, crg.sys_clk_freq) + + # Wire up UART core to the pads + uart_pads = platform.request("serial") + top.comb += [ + top.uart_rx.eq(uart_pads.rx), + uart_pads.tx.eq(top.uart_tx), + ] + + top.submodules += crg + platform.build(top) diff --git a/single_serdes_loopback.py b/single_serdes_loopback.py new file mode 100644 index 0000000..f38bb2e --- /dev/null +++ b/single_serdes_loopback.py @@ -0,0 +1,164 @@ +from migen import * +from sync_serdes import * +from migen.genlib.fifo import SyncFIFO +from migen.build.platforms.sinara import kasli +from migen.genlib.misc import WaitTimer +from kasli_crg import KasliCRG +from eem_helpers import generate_pads +from uart import UART +from io_loopback import SingleIOLoopback + + +SEPARATOR = Constant(0b0101) + +class SingleSerDesLoopBack(Module): + def __init__(self, io_pad, sys_clk_freq): + self.uart_rx = Signal() + self.uart_tx = Signal() + + self.submodules.uart = UART(round((115200/sys_clk_freq)*2**32)) + self.comb += [ + self.uart.phy_rx.eq(self.uart_rx), + self.uart_tx.eq(self.uart.phy_tx), + ] + + self.submodules.tx = SingleLineTX() + self.submodules.rx = SingleLineRX() + self.submodules.phase_reader = PhaseReader() + # self.submodules.delay_optimizer = DelayOptimizer() + + # The actual channel + self.submodules.channel = SingleIOLoopback(io_pad) + + # Attach FIFO to UART TX, send rate is too slow w.r.t sysclk + self.submodules.tx_fifo = SyncFIFO(8, 64) + + self.comb += [ + # Repetitively send 0b00100 + self.tx.txdata.eq(0b00100), + + # Loopback channel + self.channel.i.eq(self.tx.ser_out), + self.rx.ser_in_no_dly.eq(self.channel.o), + self.channel.t.eq(self.tx.t_out), + + # TX path + self.uart.tx_data.eq(self.tx_fifo.dout), + self.uart.tx_stb.eq(self.tx_fifo.readable), + self.tx_fifo.re.eq(self.uart.tx_ack), + ] + + # Route deserializer to phase_reader & the delay tap optimizer + self.comb += [ + # Start the reader initially + self.phase_reader.start.eq(1), + # Delay tap optimizer will start after the reader is done + # self.delay_optimizer.start.eq(0), + + # RXDATA for both reader and optimzer + self.phase_reader.loopback_rxdata.eq(self.rx.rxdata), + # TODO: Reconnet + # self.delay_optimizer.loopback_rxdata.eq(self.rx.rxdata), + + # Delay tap value + self.phase_reader.delay_tap.eq(self.rx.cnt_out), + # TODO: Reconnet + # self.delay_optimizer.delay_tap.eq(self.rx.cnt_out), + + # Increment control enable, such that phase_reader can + # increment tap value after delay measurement + # Re-assign the incremnet control to the optimizer after the optimizer has started + # If(self.delay_optimizer.start, + # self.rx.ce.eq(self.delay_optimizer.inc_en), + # ).Else( + # self.rx.ce.eq(self.phase_reader.inc_en), + # ) + self.rx.ce.eq(self.phase_reader.inc_en), + ] + + # Show measured result on UART + delay_tap = Signal(6) + + fsm = FSM(reset_state="WAIT_DONE") + self.submodules += fsm + + fsm.act("WAIT_DONE", + If(self.phase_reader.done, + NextState("WRITE_UPPER"), + ), + ) + + fsm.act("WRITE_UPPER", + # Exist state if all results are sent + If(delay_tap == 32, + NextState("TERMINATE"), + ).Elif(self.tx_fifo.writable, + self.tx_fifo.we.eq(1), + self.tx_fifo.din.eq(self.phase_reader.data_result[delay_tap][8:]), + NextState("WRITE_LOWER"), + ), + ) + + fsm.act("WRITE_LOWER", + self.tx_fifo.we.eq(1), + self.tx_fifo.din.eq(self.phase_reader.data_result[delay_tap][:8]), + NextValue(delay_tap, delay_tap + 1), + NextState("WRITE_UPPER"), + ) + + # fsm.act("FIND_OPT_DELAY", + # self.delay_optimizer.start.eq(1), + # self.rx.ce.eq(self.delay_optimizer.inc_en), + # If(self.delay_optimizer.done, + # NextState("WRITE_OPT"), + # ).Else( + # NextState("FIND_OPT_DELAY") + # ) + # ) + + # fsm.act("WRITE_OPT", + # self.tx_fifo.we.eq(1), + # self.tx_fifo.din.eq(self.delay_optimizer.opt_delay_tap), + # NextState("TERMINATE") + # ) + + fsm.act("TERMINATE", + NextState("TERMINATE"), + ) + + # # Output control + # self.sync += [ + # # Send data to FIFO if not repeated + # If(self.rxdata_r[:5] != self.rx.rxdata, + # self.rxdata_r.eq(self.rx.rxdata), + # self.tx_fifo.din.eq(self.rx.rxdata), + # self.tx_fifo.we.eq(1) + # ).Else( + # self.tx_fifo.we.eq(0) + # ) + # ] + + +if __name__ == "__main__": + platform = kasli.Platform(hw_rev="v2.0") + + # Generate pads for the I/O blocks + eem = 3 + generate_pads(platform, eem) + # pads = [ + # platform.request("dio{}".format(eem), i) for i in range(4) + # ] + pad = platform.request("dio{}".format(eem), 0) + + crg = KasliCRG(platform) + top = SingleSerDesLoopBack(pad, crg.sys_clk_freq) + + # Wire up UART core to the pads + uart_pads = platform.request("serial") + top.comb += [ + top.uart_rx.eq(uart_pads.rx), + uart_pads.tx.eq(top.uart_tx), + ] + + top.submodules += crg + platform.build(top) diff --git a/sync_serdes.py b/sync_serdes.py new file mode 100644 index 0000000..7245026 --- /dev/null +++ b/sync_serdes.py @@ -0,0 +1,387 @@ +from migen import * +from migen.genlib.misc import WaitTimer +from util import PriorityEncoderMSB + + +class SingleLineTX(Module): + def __init__(self): + self.txdata = Signal(5) + self.ser_out = Signal() + self.t_out = Signal() + + # TX SERDES + self.specials += Instance("OSERDESE2", + p_DATA_RATE_OQ="SDR", p_DATA_RATE_TQ="BUF", + p_DATA_WIDTH=5, p_TRISTATE_WIDTH=1, + p_INIT_OQ=0b00000, + o_OQ=self.ser_out, o_TQ=self.t_out, + i_RST=ResetSignal(), + i_CLK=ClockSignal("sys5x"), + i_CLKDIV=ClockSignal(), + i_D1=self.txdata[0], + i_D2=self.txdata[1], + i_D3=self.txdata[2], + i_D4=self.txdata[3], + i_D5=self.txdata[4], + i_TCE=1, i_OCE=1, + # TODO: Hardcode t_in? Output disable is always unnecessary? + i_T1=0) + + +class SingleLineRX(Module): + def __init__(self): + self.rxdata = Signal(10) + self.ser_in_no_dly = Signal() + self.ce = Signal() + self.cnt_out = Signal(5) + self.opt_delay = Signal(5) + + ser_in = Signal() + shifts = Signal(2) + + self.specials += [ + # Master deserializer + Instance("ISERDESE2", + p_DATA_RATE="DDR", + p_DATA_WIDTH=10, + p_INTERFACE_TYPE="NETWORKING", + p_NUM_CE=1, + p_SERDES_MODE="MASTER", + p_IOBDELAY="IFD", + o_Q1=self.rxdata[9], + o_Q2=self.rxdata[8], + o_Q3=self.rxdata[7], + o_Q4=self.rxdata[6], + o_Q5=self.rxdata[5], + o_Q6=self.rxdata[4], + o_Q7=self.rxdata[3], + o_Q8=self.rxdata[2], + o_SHIFTOUT1=shifts[0], + o_SHIFTOUT2=shifts[1], + i_DDLY=ser_in, + i_BITSLIP=0, + i_CLK=ClockSignal("rx_sys5x"), + i_CLKB=~ClockSignal("rx_sys5x"), + i_CE1=1, + i_RST=ResetSignal("rx_sys"), + i_CLKDIV=ClockSignal("rx_sys")), + + # Slave deserializer + Instance("ISERDESE2", + p_DATA_RATE="DDR", + p_DATA_WIDTH=10, + p_INTERFACE_TYPE="NETWORKING", + p_NUM_CE=1, + p_SERDES_MODE="SLAVE", + p_IOBDELAY="IFD", + o_Q3=self.rxdata[1], + o_Q4=self.rxdata[0], + # i_DDLY=ser_in, + i_BITSLIP=0, + i_CLK=ClockSignal("rx_sys5x"), + i_CLKB=~ClockSignal("rx_sys5x"), + i_CE1=1, + i_RST=ResetSignal("rx_sys"), + i_CLKDIV=ClockSignal("rx_sys"), + i_SHIFTIN1=shifts[0], + i_SHIFTIN2=shifts[1]), + + # Tunable delay + Instance("IDELAYE2", + p_DELAY_SRC="IDATAIN", + p_SIGNAL_PATTERN="DATA", + p_CINVCTRL_SEL="FALSE", + p_HIGH_PERFORMANCE_MODE="TRUE", + # REFCLK refers to the clock source of IDELAYCTRL + p_REFCLK_FREQUENCY=200.0, + p_PIPE_SEL="FALSE", + p_IDELAY_TYPE="VARIABLE", + p_IDELAY_VALUE=0, + + i_C=ClockSignal("rx_sys"), + # i_LD=self._dly_sel.storage[i//8] & self._rdly_dq_rst.re, + # i_CE=self._dly_sel.storage[i//8] & self._rdly_dq_inc.re, + i_LD=0, + i_CE=self.ce, # TODO: Port output + i_LDPIPEEN=0, + i_INC=1, # Always increment + + # Allow aligner to check delay tap value + o_CNTVALUEOUT=self.cnt_out, + + i_IDATAIN=self.ser_in_no_dly, o_DATAOUT=ser_in + ), + + # IDELAYCTRL is with the clocking + ] + + +class BitSlipReader(Module): + def __init__(self): + # IN + self.loopback_rxdata = Signal(10) + self.start = Signal() + + # Wait for stabilization after bitslip + self.submodules.stab_timer = WaitTimer(511) + + # OUT + self.done = Signal() + self.bitslip = Signal() + self.data_result = Array(Signal(10) for _ in range(5)) + + self.slip_count = Signal(3) + + fsm = FSM(reset_state="WAIT_START") + self.submodules += fsm + + fsm.act("WAIT_START", + If(self.start, + NextState("WAIT_TIMER"), + ).Else( + NextState("WAIT_START"), + ) + ) + + fsm.act("WAIT_TIMER", + self.stab_timer.wait.eq(1), + If(self.stab_timer.done, + NextState("SAMPLE"), + ) + ) + + fsm.act("SAMPLE", + # Wait is reset now + # Explicit assignment is unnecessary, as combinatorial statement + # falls back to he default value when not driven + + # Keep result alive until reset + NextValue(self.data_result[self.slip_count], self.loopback_rxdata), + NextValue(self.slip_count, self.slip_count + 1), + NextState("HIGH_BITSLIP_FIRST"), + ) + + # Pulsing BITSLIP alternate between 1 right shift and 3 left shifts + # We are trying to figure out which 2-bits are the slave copying from + # Hence, we only want shifts by 2. Pulsing twice does exactly that. + fsm.act("HIGH_BITSLIP_FIRST", + self.bitslip.eq(1), + NextState("LOW_BITSLIP"), + ) + + fsm.act("LOW_BITSLIP", + # bitslip signal is auto-reset + NextState("HIGH_BITSLIP_SECOND"), + ) + + fsm.act("HIGH_BITSLIP_SECOND", + self.bitslip.eq(1), + If(self.slip_count == 5, + NextState("TERMINATE"), + ).Else( + NextState("WAIT_TIMER"), + ) + ) + + fsm.act("TERMINATE", + self.done.eq(1), + NextState("TERMINATE"), + ) + + + +class PhaseReader(Module): + def __init__(self): + # Drive IDELAYE2 CE pin to increment delay + # The signal should only last for 1 cycle + self.inc_en = Signal() + self.loopback_rxdata = Signal(10) + self.delay_tap = Signal(5) + + # Pull up to start the phase reader + self.start = Signal() + + self.data_result = Array(Signal(10) for _ in range(32)) + self.done = Signal() + + # Wait for stabilization after increment + self.submodules.stab_timer = WaitTimer(511) + + fsm = FSM(reset_state="WAIT_START") + self.submodules += fsm + + fsm.act("WAIT_START", + If(self.start, + NextState("WAIT_TIMER"), + ).Else( + NextState("WAIT_START"), + ) + ) + + fsm.act("WAIT_TIMER", + self.stab_timer.wait.eq(1), + If(self.stab_timer.done, + NextState("SAMPLE"), + ) + ) + + fsm.act("SAMPLE", + # Wait is reset now + # Explicit assignment is unnecessary, as combinatorial statement + # falls back to he default value when not driven + + # Keep result alive until reset + NextValue(self.data_result[self.delay_tap], self.loopback_rxdata), + NextState("HIGH_CE"), + ) + + fsm.act("HIGH_CE", + self.inc_en.eq(1), + NextState("LOW_CE"), + ) + + fsm.act("LOW_CE", + # TAP OUT is available 1 cycle after the pulse + # Explicit signal reset is unnecessary, as signal assigned by + # combinatorial logic in FSM is after leaving the setting block + NextState("READ_TAP"), + ) + + fsm.act("READ_TAP", + If(self.delay_tap != 0, + NextState("WAIT_TIMER"), + ).Else( + NextState("PROBE_FIN"), + ) + ) + + fsm.act("PROBE_FIN", + self.done.eq(1), + NextState("PROBE_FIN"), + ) + + +class DelayOptimizer(Module): + def __init__(self): + # IN + # Signals from the channel + self.loopback_rxdata = Signal(10) + self.delay_tap = Signal(5) + + # IN + # Signal to start the calculation + self.start = Signal() + + # OUT + # Signal for controlling the channel delay tap + self.inc_en = Signal() + + # OUT + # The optimal delay + self.opt_delay_tap = Signal(5) + + # OUT + # Optimal delay is calculated + self.done = Signal() + + # Priority encoder for finding the pulse location + self.submodules.pulse_encoder = PriorityEncoderMSB(10) + + # Wait for stabilization after increment + self.submodules.stab_timer = WaitTimer(511) + + # Intermediate signals + self.expected_pulse = Signal(max=9) + self.min_delay = Signal(5) + self.max_offset = Signal(5) + + # Translate rxdata into array to allow indexing + self.rxdata_array = Array(Signal() for _ in range(10)) + self.comb += [ self.rxdata_array[i].eq(self.loopback_rxdata[i]) for i in range(10) ] + + fsm = FSM(reset_state="WAIT_START") + self.submodules += fsm + + fsm.act("WAIT_START", + If(self.start, + NextState("WAIT_ZERO"), + ).Else( + NextState("WAIT_START"), + ) + ) + + fsm.act("WAIT_ZERO", + self.stab_timer.wait.eq(1), + If(self.stab_timer.done, + NextState("SAMPLE_ZERO"), + ) + ) + + fsm.act("SAMPLE_ZERO", + # Oversampling should guarantee the detection + # However, priority encoder itself does not wraparound + # So, we need to avoid passing wrapped around pulse signal into + # the priority encoder. + If(self.loopback_rxdata[0] & self.loopback_rxdata[-1], + NextValue(self.expected_pulse, 1), + ).Else( + self.pulse_encoder.i.eq(self.loopback_rxdata), + If(self.pulse_encoder.o == 9, + NextValue(self.expected_pulse, 0), + ).Else( + NextValue(self.expected_pulse, self.pulse_encoder.o + 1), + ) + ), + # Goto the next delay tap and wait for the pulse. + NextState("INC_PULSE_DELAY_IN"), + ) + + fsm.act("WAIT_PULSE_IN", + self.stab_timer.wait.eq(1), + If(self.stab_timer.done, + NextState("SAMPLE_PULSE_IN"), + ) + ) + + fsm.act("SAMPLE_PULSE_IN", + If(self.rxdata_array[self.expected_pulse], + NextValue(self.min_delay, self.delay_tap), + NextState("INC_PULSE_DELAY_OUT"), + ).Else( + NextState("INC_PULSE_DELAY_IN"), + ) + ) + + fsm.act("INC_PULSE_DELAY_IN", + # This signal is automatically deasserted after this state + self.inc_en.eq(1), + NextState("WAIT_PULSE_IN"), + ) + + fsm.act("WAIT_PULSE_OUT", + self.stab_timer.wait.eq(1), + If(self.stab_timer.done, + NextState("SAMPLE_PULSE_OUT"), + ) + ) + + fsm.act("SAMPLE_PULSE_OUT", + If(~self.rxdata_array[self.expected_pulse], + NextValue(self.opt_delay_tap, self.min_delay + (self.max_offset >> 1)), + NextState("TERMINATE"), + ).Else( + NextValue(self.max_offset, self.max_offset + 1), + NextState("INC_PULSE_DELAY_OUT"), + ) + ) + + fsm.act("INC_PULSE_DELAY_OUT", + # This signal is automatically deasserted after this state + self.inc_en.eq(1), + NextState("WAIT_PULSE_OUT"), + ) + + fsm.act("TERMINATE", + self.done.eq(1), + NextState("TERMINATE"), + ) diff --git a/test_aligner.py b/test_aligner.py new file mode 100644 index 0000000..8393756 --- /dev/null +++ b/test_aligner.py @@ -0,0 +1,198 @@ +from migen import * +from sync_serdes import PhaseReader, DelayOptimizer, BitSlipReader +import random + + +def reader_testbench(dut, rxdata_list): + yield dut.delay_tap.eq(0) + yield dut.start.eq(1) + assert (yield dut.stab_timer.wait) == 0 + + for i in range(32): + yield dut.loopback_rxdata.eq(rxdata_list[i]) + yield + yield + assert (yield dut.stab_timer.wait) == 1 + + # Keep yielding until the DUT gives CE signal + while (yield dut.inc_en) == 0: + yield + + # Check that inc_en is deassrted after 1 clock cycle + yield + assert (yield dut.inc_en) == 0 + + # Load a new tap value + yield dut.delay_tap.eq(i + 1) + yield + # Nothing to check in the READ_TAP state + yield + + assert(yield dut.done) == 1 + + for i in range(32): + signal = yield dut.data_result[i] + expected = rxdata_list[i] + assert signal == expected + + for i in range(200): + assert (yield dut.inc_en) == 0 + yield + + # Untouched delay: Record should be invariant + for i in range(32): + signal = yield dut.data_result[i] + expected = rxdata_list[i] + assert signal == expected + + +def optimal_delay_testbench(dut, pulse_list, cycles, pulse_index, min_delay, max_offset, opt_delay_tap): + # Start the module + yield dut.delay_tap.eq(0) + yield dut.start.eq(1) + assert (yield dut.stab_timer.wait) == 0 + + for i in range(cycles): + # Pass in a new rxdata for sampling + # The stab_timer should start waiting after + yield dut.loopback_rxdata.eq(pulse_list[i]) + yield + yield + assert (yield dut.stab_timer.wait) == 1 + + # Eventually, the wait will end + # Either it triggers an increment or a finished signal + # And we will get the expected pulse location + # inc_en is pulsed after this is found + if i == (cycles - 1): + while (yield dut.done) == 0: + yield + break + else: + while (yield dut.inc_en) == 0: + yield + + # Then we increment the rxdata index + yield dut.delay_tap.eq(i + 1) + yield + + # Fast-forward to the result + # while (yield dut.done) == 0: + # yield + + assert (yield dut.done) == 1 + assert (yield dut.expected_pulse) == pulse_index + assert (yield dut.min_delay) == min_delay + assert (yield dut.max_offset) == max_offset + assert (yield dut.opt_delay_tap) == opt_delay_tap + + for _ in range(100): + yield + + # Invariant test: Everything is frozen after done + assert (yield dut.done) == 1 + assert (yield dut.expected_pulse) == pulse_index + assert (yield dut.min_delay) == min_delay + assert (yield dut.max_offset) == max_offset + assert (yield dut.opt_delay_tap) == opt_delay_tap + + +def bitslip_reader_tb(dut, rxdata_list): + # Start the module + yield dut.start.eq(1) + assert (yield dut.stab_timer.wait) == 0 + + for i in range(5): + yield dut.loopback_rxdata.eq(rxdata_list[i]) + yield + yield + assert (yield dut.stab_timer.wait) == 1 + + # Keep yielding until the DUT gives BITSLIP signal + while (yield dut.bitslip) == 0: + yield + + # There will be 2 BITSLIP pulses + # Both BITSLIP pulses should last for 1 cycle + assert (yield dut.bitslip) == 1 + yield + assert (yield dut.bitslip) == 0 + yield + assert (yield dut.bitslip) == 1 + yield + assert (yield dut.bitslip) == 0 + + assert (yield dut.done) == 1 + # The result in the module should contain all rxdata + for i, rxdata in enumerate(rxdata_list): + assert (yield dut.data_result[i]) == rxdata + + yield + yield + + +# # Random testing for delay reader +# for _ in range(32): +# rxdata_list = [ random.getrandbits(10) for _ in range(32) ] +# dut = PhaseReader() +# run_simulation(dut, reader_testbench(dut, rxdata_list), vcd_name="phase_reader.vcd") + +# # Random testing for optimal delay calculation +# # Generate a delay list +# start = random.randint(0, 9) +# start_length = random.randint(1, 10) +# offset = random.randint(4, 5) + +# current_index = start +# remaining_length = start_length +# single_pulse_list = [] + +# expected_index = (current_index + 1) % 10 +# expected_length = 10 + +# for tap in range(32 + offset): +# single_pulse_list.append(1 << current_index) +# remaining_length -= 1 + +# if remaining_length == 0: +# current_index = (current_index + 1) % 10 +# remaining_length = 10 + +# pulse_list = list(single_pulse_list) +# for i in range(offset, 32): +# pulse_list[i] |= single_pulse_list[i - offset] + +# found_start_edge = False +# max_offset = 0 + +# # Calculate min_delay +# for i, pulse in enumerate(pulse_list): +# if (pulse & (1 << expected_index)) != 0: +# if not found_start_edge: +# min_delay = i +# found_start_edge = True +# else: +# max_offset += 1 +# if (pulse & (1 << expected_index)) == 0 and found_start_edge: +# cycles = i + 1 +# break + +# print(min_delay) +# print(max_offset) +# print(cycles) +# opt_delay = int(min_delay + (max_offset / 2)) +# print(opt_delay) + +# # Simulate +# dut = DelayOptimizer() +# run_simulation(dut, optimal_delay_testbench( +# dut, pulse_list, cycles, expected_index, +# min_delay, max_offset, opt_delay), +# vcd_name="delay_opt.vcd" +# ) + +# Random test for bitslip reader +for _ in range(32): + rxdata_list = [ random.getrandbits(10) for _ in range(5) ] + dut = BitSlipReader() + run_simulation(dut, bitslip_reader_tb(dut, rxdata_list), vcd_name="bitslip_reader.vcd") diff --git a/test_buffer.py b/test_buffer.py new file mode 100644 index 0000000..2144968 --- /dev/null +++ b/test_buffer.py @@ -0,0 +1,46 @@ +from migen import * +from buffer import Buffer + + +BUFFER_DEPTH=8 +full_test_bytes = [0xDE, 0xAD, 0xBE, 0xEF, 0xBA, 0xD0, 0xCA, 0xFE, 0x15, 0x77] + + +def testbench(dut, length=10): + # dut = Buffer(8, BUFFER_DEPTH) + # full_test_bytes = [0xDE, 0xAD, 0xBE, 0xEF, 0xBA, 0xD0, 0xCA, 0xFE, 0x15, 0x77] + + test_data = full_test_bytes[:min(BUFFER_DEPTH, length)] + + # Initial condition, stb low + yield dut.i_stb.eq(0) + yield + + # Append bytes one-by-one + for number in test_data: + yield dut.din.eq(number) + yield dut.i_stb.eq(1) + yield + + # Deassert input strobe to buffers + yield dut.i_stb.eq(0) + yield + + # Receive bytes on-by-one + for number in test_data: + assert (yield dut.o_stb) == 1 + assert (yield dut.dout) == number + yield dut.o_ack.eq(1) + yield + yield dut.o_ack.eq(0) + yield + yield + + yield + yield + assert (yield dut.o_stb) == 0 + + +for buffer_len in range(len(full_test_bytes)): + dut = Buffer(8, BUFFER_DEPTH) + run_simulation(dut, testbench(dut, length=buffer_len), vcd_name="buffer.vcd") diff --git a/uart.py b/uart.py new file mode 100644 index 0000000..ddde0c9 --- /dev/null +++ b/uart.py @@ -0,0 +1,105 @@ +from migen import * +from migen.genlib.cdc import MultiReg + + +class UART(Module): + def __init__(self, tuning_word): + self.phy_rx = Signal() + self.phy_tx = Signal() + + self.rx_data = Signal(8) + self.rx_stb = Signal() + + self.tx_data = Signal(8) + self.tx_stb = Signal() + self.tx_ack = Signal() + + # # # + + # + # RX + # + + uart_clk_rxen = Signal() + phase_accumulator_rx = Signal(32) + + rx = Signal() + self.specials += MultiReg(self.phy_rx, rx) + rx_r = Signal() + rx_reg = Signal(8) + rx_bitcount = Signal(4) + rx_busy = Signal() + rx_done = self.rx_stb + rx_data = self.rx_data + self.sync += [ + rx_done.eq(0), + rx_r.eq(rx), + If(~rx_busy, + If(~rx & rx_r, # look for start bit + rx_busy.eq(1), + rx_bitcount.eq(0), + ) + ).Else( + If(uart_clk_rxen, + rx_bitcount.eq(rx_bitcount + 1), + If(rx_bitcount == 0, + If(rx, # verify start bit + rx_busy.eq(0) + ) + ).Elif(rx_bitcount == 9, + rx_busy.eq(0), + If(rx, # verify stop bit + rx_data.eq(rx_reg), + rx_done.eq(1) + ) + ).Else( + rx_reg.eq(Cat(rx_reg[1:], rx)) + ) + ) + ) + ] + self.sync += \ + If(rx_busy, + Cat(phase_accumulator_rx, uart_clk_rxen).eq(phase_accumulator_rx + tuning_word) + ).Else( + Cat(phase_accumulator_rx, uart_clk_rxen).eq(2**31) + ) + + # + # TX + # + uart_clk_txen = Signal() + phase_accumulator_tx = Signal(32) + + self.phy_tx.reset = 1 + + tx_reg = Signal(8) + tx_bitcount = Signal(4) + tx_busy = Signal() + self.sync += [ + self.tx_ack.eq(0), + If(self.tx_stb & ~tx_busy & ~self.tx_ack, + tx_reg.eq(self.tx_data), + tx_bitcount.eq(0), + tx_busy.eq(1), + self.phy_tx.eq(0) + ).Elif(uart_clk_txen & tx_busy, + tx_bitcount.eq(tx_bitcount + 1), + If(tx_bitcount == 8, + self.phy_tx.eq(1) + ).Elif(tx_bitcount == 9, + self.phy_tx.eq(1), + tx_busy.eq(0), + self.tx_ack.eq(1), + ).Else( + self.phy_tx.eq(tx_reg[0]), + tx_reg.eq(Cat(tx_reg[1:], 0)) + ) + ) + ] + self.sync += \ + If(tx_busy, + Cat(phase_accumulator_tx, uart_clk_txen).eq(phase_accumulator_tx + tuning_word) + ).Else( + Cat(phase_accumulator_tx, uart_clk_txen).eq(0) + ) diff --git a/util.py b/util.py new file mode 100644 index 0000000..27283b2 --- /dev/null +++ b/util.py @@ -0,0 +1,11 @@ +from migen import * + + +class PriorityEncoderMSB(Module): + def __init__(self, width): + self.i = Signal(width) # one-hot, msb has priority + self.o = Signal(max=max(2, width)) # binary + self.n = Signal() # none + for j in range(width): # first has priority + self.comb += If(self.i[j], self.o.eq(j)) + self.comb += self.n.eq(self.i == 0)