From e4d8d44c7c54fb6985440a6ae0d43b1f8a951066 Mon Sep 17 00:00:00 2001 From: morgan Date: Thu, 4 Jan 2024 12:28:40 +0800 Subject: [PATCH] Gateware: WRPLL ddmtd: add DDMTD and deglitcher wrpll: add helper clockdomain wrpll: add frequency counter wrpll: add skewtester wrpll: add gtx & main tag collection wrpll: add gtx & main tag eventmanager for shared peripheral interrupt wrpll: add SMA frequency multiplier to generate 125Mhz refclk si549: add i2c and adpll programmer --- src/gateware/ddmtd.py | 67 ++++++++++ src/gateware/si549.py | 277 ++++++++++++++++++++++++++++++++++++++++++ src/gateware/wrpll.py | 237 ++++++++++++++++++++++++++++++++++++ 3 files changed, 581 insertions(+) create mode 100644 src/gateware/ddmtd.py create mode 100644 src/gateware/si549.py create mode 100644 src/gateware/wrpll.py diff --git a/src/gateware/ddmtd.py b/src/gateware/ddmtd.py new file mode 100644 index 0000000..0073497 --- /dev/null +++ b/src/gateware/ddmtd.py @@ -0,0 +1,67 @@ +from migen import * +from migen.genlib.cdc import PulseSynchronizer, MultiReg +from misoc.interconnect.csr import * + + +class DDMTDSampler(Module): + def __init__(self, cd_ref, main_clk_se): + self.ref_beating = Signal() + self.main_beating = Signal() + + # # # + + ref_beating_FF = Signal() + main_beating_FF = Signal() + self.specials += [ + # Two back to back FFs are used to prevent metastability + Instance("FD", i_C=ClockSignal("helper"), + i_D=cd_ref.clk, o_Q=ref_beating_FF), + Instance("FD", i_C=ClockSignal("helper"), + i_D=ref_beating_FF, o_Q=self.ref_beating), + Instance("FD", i_C=ClockSignal("helper"), + i_D=main_clk_se, o_Q=main_beating_FF), + Instance("FD", i_C=ClockSignal("helper"), + i_D=main_beating_FF, o_Q=self.main_beating) + ] + + +class DDMTDDeglitcherFirstEdge(Module): + def __init__(self, input_signal, blind_period=400): + self.detect = Signal() + rising = Signal() + input_signal_r = Signal() + + # # # + + self.sync.helper += [ + input_signal_r.eq(input_signal), + rising.eq(input_signal & ~input_signal_r) + ] + + blind_counter = Signal(max=blind_period) + self.sync.helper += [ + If(blind_counter != 0, blind_counter.eq(blind_counter - 1)), + If(input_signal_r, blind_counter.eq(blind_period - 1)), + self.detect.eq(rising & (blind_counter == 0)) + ] + + +class DDMTD(Module): + def __init__(self, counter, input_signal): + + # in helper clock domain + self.h_tag = Signal(len(counter)) + self.h_tag_update = Signal() + + # # # + + deglitcher = DDMTDDeglitcherFirstEdge(input_signal) + self.submodules += deglitcher + + self.sync.helper += [ + self.h_tag_update.eq(0), + If(deglitcher.detect, + self.h_tag_update.eq(1), + self.h_tag.eq(counter) + ) + ] \ No newline at end of file diff --git a/src/gateware/si549.py b/src/gateware/si549.py new file mode 100644 index 0000000..3ae8948 --- /dev/null +++ b/src/gateware/si549.py @@ -0,0 +1,277 @@ +from migen import * +from migen.genlib.fsm import * + +from misoc.interconnect.csr import * + + +class I2CClockGen(Module): + def __init__(self, width): + self.load = Signal(width) + self.clk2x = Signal() + + cnt = Signal.like(self.load) + self.comb += [ + self.clk2x.eq(cnt == 0), + ] + self.sync += [ + If(self.clk2x, + cnt.eq(self.load), + ).Else( + cnt.eq(cnt - 1), + ) + ] + + +class I2CMasterMachine(Module): + def __init__(self, clock_width): + self.scl = Signal(reset=1) + self.sda_o = Signal(reset=1) + self.sda_i = Signal() + + self.submodules.cg = CEInserter()(I2CClockGen(clock_width)) + self.start = Signal() + self.stop = Signal() + self.write = Signal() + self.ack = Signal() + self.data = Signal(8) + self.ready = Signal() + + # # # + + bits = Signal(4) + data = Signal(8) + + fsm = CEInserter()(FSM("IDLE")) + self.submodules += fsm + + fsm.act("IDLE", + self.ready.eq(1), + If(self.start, + NextState("START0"), + ).Elif(self.stop, + NextState("STOP0"), + ).Elif(self.write, + NextValue(bits, 8), + NextValue(data, self.data), + NextState("WRITE0") + ) + ) + + fsm.act("START0", + NextValue(self.scl, 1), + NextState("START1") + ) + fsm.act("START1", + NextValue(self.sda_o, 0), + NextState("IDLE") + ) + + fsm.act("STOP0", + NextValue(self.scl, 0), + NextState("STOP1") + ) + fsm.act("STOP1", + NextValue(self.sda_o, 0), + NextState("STOP2") + ) + fsm.act("STOP2", + NextValue(self.scl, 1), + NextState("STOP3") + ) + fsm.act("STOP3", + NextValue(self.sda_o, 1), + NextState("IDLE") + ) + + fsm.act("WRITE0", + NextValue(self.scl, 0), + NextState("WRITE1") + ) + fsm.act("WRITE1", + If(bits == 0, + NextValue(self.sda_o, 1), + NextState("READACK0"), + ).Else( + NextValue(self.sda_o, data[7]), + NextState("WRITE2"), + ) + ) + fsm.act("WRITE2", + NextValue(self.scl, 1), + NextValue(data[1:], data[:-1]), + NextValue(bits, bits - 1), + NextState("WRITE0"), + ) + fsm.act("READACK0", + NextValue(self.scl, 1), + NextState("READACK1"), + ) + fsm.act("READACK1", + NextValue(self.ack, ~self.sda_i), + NextState("IDLE") + ) + + run = Signal() + idle = Signal() + self.comb += [ + run.eq((self.start | self.stop | self.write) & self.ready), + idle.eq(~run & fsm.ongoing("IDLE")), + self.cg.ce.eq(~idle), + fsm.ce.eq(run | self.cg.clk2x), + ] + + +class ADPLLProgrammer(Module): + def __init__(self): + self.i2c_divider = Signal(16) + self.i2c_address = Signal(7) + + self.adpll = Signal(24) + self.stb = Signal() + self.busy = Signal() + self.nack = Signal() + + self.scl = Signal() + self.sda_i = Signal() + self.sda_o = Signal() + + # # # + + master = I2CMasterMachine(16) + self.submodules += master + + self.comb += [ + master.cg.load.eq(self.i2c_divider), + self.scl.eq(master.scl), + master.sda_i.eq(self.sda_i), + self.sda_o.eq(master.sda_o) + ] + + fsm = FSM() + self.submodules += fsm + + fsm.act("IDLE", + If(self.stb, + NextValue(self.nack, 0), + NextState("START") + ) + ) + fsm.act("START", + master.start.eq(1), + If(master.ready, NextState("DEVADDRESS")) + ) + fsm.act("DEVADDRESS", + master.data.eq(self.i2c_address << 1), + master.write.eq(1), + If(master.ready, NextState("REGADRESS")) + ) + fsm.act("REGADRESS", + master.data.eq(231), + master.write.eq(1), + If(master.ready, + If(master.ack, + NextState("DATA0") + ).Else( + NextValue(self.nack, 1), + NextState("STOP") + ) + ) + ) + fsm.act("DATA0", + master.data.eq(self.adpll[0:8]), + master.write.eq(1), + If(master.ready, + If(master.ack, + NextState("DATA1") + ).Else( + NextValue(self.nack, 1), + NextState("STOP") + ) + ) + ) + fsm.act("DATA1", + master.data.eq(self.adpll[8:16]), + master.write.eq(1), + If(master.ready, + If(master.ack, + NextState("DATA2") + ).Else( + NextValue(self.nack, 1), + NextState("STOP") + ) + ) + ) + fsm.act("DATA2", + master.data.eq(self.adpll[16:24]), + master.write.eq(1), + If(master.ready, + If(~master.ack, NextValue(self.nack, 1)), + NextState("STOP") + ) + ) + fsm.act("STOP", + master.stop.eq(1), + If(master.ready, + If(~master.ack, NextValue(self.nack, 1)), + NextState("IDLE") + ) + ) + + self.comb += self.busy.eq(~fsm.ongoing("IDLE")) + + +class Si549(Module, AutoCSR): + def __init__(self, pads): + self.i2c_divider = CSRStorage(16, reset=75) + self.i2c_address = CSRStorage(7) + + self.adpll = CSRStorage(24) + self.adpll_stb = CSR() + self.adpll_busy = CSRStatus() + self.nack = CSRStatus() + + self.bitbang_enable = CSRStorage() + + self.sda_oe = CSRStorage() + self.sda_out = CSRStorage() + self.sda_in = CSRStatus() + self.scl_oe = CSRStorage() + self.scl_out = CSRStorage() + + # # # + + self.submodules.programmer = ADPLLProgrammer() + + self.sync += self.programmer.stb.eq(self.adpll_stb.re) + + self.comb += [ + self.programmer.i2c_divider.eq(self.i2c_divider.storage), + self.programmer.i2c_address.eq(self.i2c_address.storage), + self.programmer.adpll.eq(self.adpll.storage), + self.adpll_busy.status.eq(self.programmer.busy), + self.nack.status.eq(self.programmer.nack) + ] + + # I2C with bitbang/gateware mode select + sda_t = TSTriple(1) + scl_t = TSTriple(1) + self.specials += [ + sda_t.get_tristate(pads.sda), + scl_t.get_tristate(pads.scl) + ] + + self.comb += [ + If(self.bitbang_enable.storage, + sda_t.oe.eq(self.sda_oe.storage), + sda_t.o.eq(self.sda_out.storage), + self.sda_in.status.eq(sda_t.i), + scl_t.oe.eq(self.scl_oe.storage), + scl_t.o.eq(self.scl_out.storage) + ).Else( + sda_t.oe.eq(~self.programmer.sda_o), + sda_t.o.eq(0), + self.programmer.sda_i.eq(sda_t.i), + scl_t.oe.eq(~self.programmer.scl), + scl_t.o.eq(0), + ) + ] \ No newline at end of file diff --git a/src/gateware/wrpll.py b/src/gateware/wrpll.py new file mode 100644 index 0000000..a6b2b48 --- /dev/null +++ b/src/gateware/wrpll.py @@ -0,0 +1,237 @@ +from migen import * +from migen.genlib.cdc import MultiReg, AsyncResetSynchronizer, PulseSynchronizer +from misoc.interconnect.csr import * +from misoc.interconnect.csr_eventmanager import * + +from ddmtd import DDMTDSampler, DDMTD +from si549 import Si549 + +class FrequencyCounter(Module, AutoCSR): + def __init__(self, domains, counter_width=24): + self.update = CSR() + self.busy = CSRStatus() + + counter_reset = Signal() + counter_stb = Signal() + timer = Signal(counter_width) + + # # # + + fsm = FSM() + self.submodules += fsm + + fsm.act("IDLE", + counter_reset.eq(1), + If(self.update.re, + NextValue(timer, 2**counter_width - 1), + NextState("COUNTING") + ) + ) + fsm.act("COUNTING", + self.busy.status.eq(1), + If(timer != 0, + NextValue(timer, timer - 1) + ).Else( + counter_stb.eq(1), + NextState("IDLE") + ) + ) + + for domain in domains: + name = "counter_" + domain + counter_csr = CSRStatus(counter_width, name=name) + setattr(self, name, counter_csr) + + divider = Signal(2) + divided = Signal() + divided_sys = Signal() + divided_sys_r = Signal() + divided_tick = Signal() + counter = Signal(counter_width) + + # # # + + sync_domain = getattr(self.sync, domain) + sync_domain +=[ + divider.eq(divider + 1), + divided.eq(divider[-1]) + ] + self.specials += MultiReg(divided, divided_sys) + self.sync += divided_sys_r.eq(divided_sys) + self.comb += divided_tick.eq(divided_sys & ~divided_sys_r) + + self.sync += [ + If(counter_stb, counter_csr.status.eq(counter)), + If(divided_tick, counter.eq(counter + 1)), + If(counter_reset, counter.eq(0)) + ] + +class SkewTester(Module, AutoCSR): + def __init__(self, rx_synchronizer): + self.error = CSR() + + # # # + + # The RX synchronizer is tested for setup/hold violations by feeding it a + # toggling pattern and checking that the same toggling pattern comes out. + toggle_in = Signal() + self.sync.rtio_rx0 += toggle_in.eq(~toggle_in) + toggle_out = rx_synchronizer.resync(toggle_in) + + toggle_out_expected = Signal() + self.sync += toggle_out_expected.eq(~toggle_out) + + error = Signal() + self.sync += [ + If(toggle_out != toggle_out_expected, error.eq(1)), + If(self.error.re, error.eq(0)) + ] + self.specials += MultiReg(error, self.error.w) + + +class WRPLL(Module, AutoCSR): + def __init__(self, platform, cd_ref, main_clk_se, COUNTER_BIT=32): + self.helper_reset = CSRStorage(reset=1) + self.ref_tag = CSRStatus(COUNTER_BIT) + self.main_tag = CSRStatus(COUNTER_BIT) + + ddmtd_counter = Signal(COUNTER_BIT) + + ref_tag_sys = Signal(COUNTER_BIT) + main_tag_sys = Signal(COUNTER_BIT) + ref_tag_stb_sys = Signal() + main_tag_stb_sys = Signal() + + # # # + + self.submodules.main_dcxo = Si549(platform.request("ddmtd_main_dcxo_i2c")) + self.submodules.helper_dcxo = Si549(platform.request("ddmtd_helper_dcxo_i2c")) + + helper_dcxo_pads = platform.request("ddmtd_helper_clk") + self.clock_domains.cd_helper = ClockDomain() + self.specials += [ + Instance("IBUFGDS", + i_I=helper_dcxo_pads.p, i_IB=helper_dcxo_pads.n, + o_O=self.cd_helper.clk), + AsyncResetSynchronizer(self.cd_helper, self.helper_reset.storage) + ] + + self.submodules.frequency_counter = FrequencyCounter(["sys", cd_ref.name]) + + self.submodules.ddmtd_sampler = DDMTDSampler(cd_ref, main_clk_se) + + self.sync.helper += ddmtd_counter.eq(ddmtd_counter + 1) + self.submodules.ddmtd_ref = DDMTD(ddmtd_counter, self.ddmtd_sampler.ref_beating) + self.submodules.ddmtd_main = DDMTD(ddmtd_counter, self.ddmtd_sampler.main_beating) + + # DDMTD tags collection + + self.specials += [ + MultiReg(self.ddmtd_ref.h_tag, ref_tag_sys), + MultiReg(self.ddmtd_main.h_tag, main_tag_sys) + ] + + ref_tag_stb_ps = PulseSynchronizer("helper", "sys") + main_tag_stb_ps = PulseSynchronizer("helper", "sys") + self.submodules += [ + ref_tag_stb_ps, + main_tag_stb_ps + ] + self.sync.helper += [ + ref_tag_stb_ps.i.eq(self.ddmtd_ref.h_tag_update), + main_tag_stb_ps.i.eq(self.ddmtd_main.h_tag_update) + ] + self.sync += [ + ref_tag_stb_sys.eq(ref_tag_stb_ps.o), + main_tag_stb_sys.eq(main_tag_stb_ps.o) + ] + + self.sync += [ + If(ref_tag_stb_sys, + self.ref_tag.status.eq(ref_tag_sys), + ), + If(main_tag_stb_sys, + self.main_tag.status.eq(main_tag_sys) + ) + ] + + # EventMangers for firmware interrupt + + self.submodules.ref_tag_ev = EventManager() + self.ref_tag_ev.stb = EventSourcePulse() + self.ref_tag_ev.finalize() + + self.submodules.main_tag_ev = EventManager() + self.main_tag_ev.stb = EventSourcePulse() + self.main_tag_ev.finalize() + + self.sync += [ + self.ref_tag_ev.stb.trigger.eq(ref_tag_stb_sys), + self.main_tag_ev.stb.trigger.eq(main_tag_stb_sys) + ] + + self.submodules.ev = SharedIRQ(self.ref_tag_ev, self.main_tag_ev) + + +class SMAFrequencyMultiplier(Module, AutoCSR): + def __init__(self, sma_clkin): + sma_clkin_se = Signal() + mmcm_locked = Signal() + mmcm_fb_clk = Signal() + ref_clk = Signal() + self.clock_domains.cd_ref = ClockDomain() + self.refclk_reset = CSRStorage(reset=1) + + self.mmcm_bypass = CSRStorage() + self.mmcm_locked = CSRStatus() + self.mmcm_reset = CSRStorage(reset=1) + + self.mmcm_daddr = CSRStorage(7) + self.mmcm_din = CSRStorage(16) + self.mmcm_dwen = CSRStorage() + self.mmcm_den = CSRStorage() + self.mmcm_dclk = CSRStorage() + self.mmcm_dout = CSRStatus(16) + self.mmcm_dready = CSRStatus() + + # # # + + self.specials += [ + Instance("IBUFDS", + i_I=sma_clkin.p, i_IB=sma_clkin.n, + o_O=sma_clkin_se), + # MMCME2 is capable to accept 10MHz input while PLLE2 only support down to 19MHz input (DS191) + # The MMCME2 can be reconfiged during runtime using the Dynamic Reconfiguration Ports + Instance("MMCME2_ADV", + p_BANDWIDTH="LOW", # lower jitter + o_LOCKED=self.mmcm_locked.status, + i_RST=self.mmcm_reset.storage, + + p_CLKIN1_PERIOD=8, # ns + i_CLKIN1=sma_clkin_se, + i_CLKINSEL=1, # 1=CLKIN1 0=CLKIN2 + + # VCO @ 1.25GHz + p_CLKFBOUT_MULT_F=10, p_DIVCLK_DIVIDE=1, + i_CLKFBIN=mmcm_fb_clk, o_CLKFBOUT=mmcm_fb_clk, + + # 125MHz for WRPLL + p_CLKOUT0_DIVIDE_F=10, p_CLKOUT0_PHASE=0.0, o_CLKOUT0=ref_clk, + + # Dynamic Reconfiguration Ports + i_DADDR = self.mmcm_daddr.storage, + i_DI = self.mmcm_din.storage, + i_DWE = self.mmcm_dwen.storage, + i_DEN = self.mmcm_den.storage, + i_DCLK = self.mmcm_dclk.storage, + o_DO = self.mmcm_dout.status, + o_DRDY = self.mmcm_dready.status + ), + Instance("BUFGMUX", + i_I0=ref_clk, + i_I1=sma_clkin_se, + i_S=self.mmcm_bypass.storage, + o_O=self.cd_ref.clk + ), + AsyncResetSynchronizer(self.cd_ref, self.refclk_reset.storage), + ]