diff --git a/src/gateware/ddmtd.py b/src/gateware/ddmtd.py new file mode 100644 index 0000000..adeb217 --- /dev/null +++ b/src/gateware/ddmtd.py @@ -0,0 +1,156 @@ +from migen import * +from migen.genlib.cdc import PulseSynchronizer, MultiReg +from migen.genlib.fsm import FSM +from misoc.interconnect.csr import * + + +class DDMTDSamplerGTX(Module): + def __init__(self, gtx, main_xo_pads): + self.gtx_beating = Signal() + self.main_beating = Signal() + + # # # + + main_clk_se = Signal() + gtx_beating_FF = Signal() + main_beating_FF = Signal() + self.specials += [ + Instance("IBUFDS", + i_I=main_xo_pads.p, i_IB=main_xo_pads.n, + o_O=main_clk_se), + # Two back to back FFs are used to prevent metastability + Instance("FD", i_C=ClockSignal("helper"), + i_D=gtx.cd_rtio_rx0.clk, o_Q=gtx_beating_FF), + Instance("FD", i_C=ClockSignal("helper"), + i_D=gtx_beating_FF, o_Q=self.gtx_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=128): + 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) + ) + ] + + +class Collector(Module): + """ + A phase and period collector for DDMTD + """ + + def __init__(self, N): + + self.gtx_stb = Signal() + self.main_stb = Signal() + self.tag_gtx = Signal(N) + self.tag_main = Signal(N) + + self.out_period_stb = Signal() + self.out_beating_period = Signal(N) + last_gtx_tag = Signal(N) + beating_period_r = Signal(N) + + self.out_phase_stb = Signal() + self.out_tag_gtx = Signal(N) + self.out_tag_main = Signal(N) + tag_gtx_r = Signal(N) + tag_main_r = Signal(N) + + # # # + + # Period collector + # collect the difference between each gtx tags + + self.submodules.period_colr_fsm = period_colr_fsm = FSM(reset_state="IDLE") + + period_colr_fsm.act("IDLE", + NextValue(self.out_period_stb, 0), + If(self.gtx_stb, + NextValue(beating_period_r, self.tag_gtx - last_gtx_tag), + NextValue(last_gtx_tag, self.tag_gtx), + NextState("OUTPUT") + ) + ) + period_colr_fsm.act("OUTPUT", + NextValue(self.out_beating_period, beating_period_r), + NextValue(self.out_period_stb, 1), + NextState("IDLE") + ) + + + # Phase collector + # collect main and gtx tag + + self.submodules.phase_colr_fsm = phase_colr_fsm = FSM(reset_state="IDLE") + + phase_colr_fsm.act("IDLE", + NextValue(self.out_phase_stb, 0), + If(self.gtx_stb & self.main_stb, + NextValue(tag_gtx_r, self.tag_gtx), + NextValue(tag_main_r, self.tag_main), + NextState("OUTPUT") + ).Elif(self.gtx_stb, + NextValue(tag_gtx_r, self.tag_gtx), + NextState("WAITMAIN") + ).Elif(self.main_stb, + NextValue(tag_main_r, self.tag_main), + NextState("WAITGTX") + ) + ) + phase_colr_fsm.act("WAITGTX", + If(self.gtx_stb, + NextValue(tag_gtx_r, self.tag_gtx), + NextState("OUTPUT") + ) + ) + phase_colr_fsm.act("WAITMAIN", + If(self.main_stb, + NextValue(tag_main_r, self.tag_main), + NextState("OUTPUT") + ) + ) + phase_colr_fsm.act("OUTPUT", + NextValue(self.out_tag_gtx, tag_gtx_r), + NextValue(self.out_tag_main, tag_main_r), + NextValue(self.out_phase_stb, 1), + NextState("IDLE") + ) \ No newline at end of file diff --git a/src/gateware/kasli_soc.py b/src/gateware/kasli_soc.py index 2b34c0c..1eb000e 100755 --- a/src/gateware/kasli_soc.py +++ b/src/gateware/kasli_soc.py @@ -26,6 +26,7 @@ import analyzer import acpki import drtio_aux_controller import zynq_clocking +import wrpll import si549 from config import write_csr_file, write_mem_file, write_rustc_cfg_file @@ -564,6 +565,17 @@ class GenericSatellite(SoCCore): else: self.submodules.main_dcxo = si549.Si549(platform.request("ddmtd_main_dcxo_i2c")) self.submodules.helper_dcxo = si549.Si549(platform.request("ddmtd_helper_dcxo_i2c")) + self.submodules.wrpll = wrpll.WRPLL( + gtx=self.gt_drtio, + main_dcxo_pads=platform.request("cdr_clk_clean_fabric"), + helper_dcxo_pads=platform.request("ddmtd_helper_clk")) + self.csr_devices.append("main_dcxo") + self.csr_devices.append("helper_dcxo") + self.csr_devices.append("wrpll") + self.comb += [ + self.ps7.interrupt[0].eq(self.wrpll.period_ev.irq), # IRQ_ID = 61 + self.ps7.interrupt[1].eq(self.wrpll.phase_ev.irq) # IRQ_ID = 62 + ] self.config["HAS_SI549"] = None gtx0 = self.gt_drtio.gtxs[0] diff --git a/src/gateware/wrpll.py b/src/gateware/wrpll.py new file mode 100644 index 0000000..307cbe4 --- /dev/null +++ b/src/gateware/wrpll.py @@ -0,0 +1,140 @@ +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 DDMTDSamplerGTX, DDMTD, Collector + + +class FrequencyCounter(Module, AutoCSR): + def __init__(self, counter_width=24, domains=["gtx0_rtio_rx", "sys", "helper"]): + for domain in domains: + name = "counter_" + domain + counter = CSRStatus(counter_width, name=name) + setattr(self, name, counter) + self.update_en = CSRStorage() + + timer = Signal(counter_width) + timer_tick = Signal() + self.sync += Cat(timer, timer_tick).eq(timer + 1) + + for domain in domains: + sync_domain = getattr(self.sync, domain) + divider = Signal(2) + sync_domain += divider.eq(divider + 1) + + divided = Signal() + divided.attr.add("no_retiming") + sync_domain += divided.eq(divider[-1]) + divided_sys = Signal() + self.specials += MultiReg(divided, divided_sys) + + divided_sys_r = Signal() + divided_tick = Signal() + self.sync += divided_sys_r.eq(divided_sys) + self.comb += divided_tick.eq(divided_sys & ~divided_sys_r) + + counter = Signal(counter_width) + counter_csr = getattr(self, "counter_" + domain) + self.sync += [ + If(timer_tick, + If(self.update_en.storage, counter_csr.status.eq(counter)), + counter.eq(0), + ).Else( + If(divided_tick, counter.eq(counter + 1)) + ) + ] + + +class WRPLL(Module, AutoCSR): + def __init__(self, gtx, main_dcxo_pads, helper_dcxo_pads, COUNTER_BIT=32): + + self.gtx_period = CSRStatus(COUNTER_BIT) + self.gtx_tag = CSRStatus(COUNTER_BIT) + self.main_tag = CSRStatus(COUNTER_BIT) + + ddmtd_counter = Signal(COUNTER_BIT) + + gtx_period_sys = Signal(COUNTER_BIT) + gtx_tag_sys = Signal(COUNTER_BIT) + main_tag_sys = Signal(COUNTER_BIT) + + period_colr_stb_sys = Signal() + phase_colr_stb_sys = Signal() + + # # # + + self.helper_reset = CSRStorage(reset=1) + self.clock_domains.cd_helper = ClockDomain() + self.helper_reset.storage.attr.add("no_retiming") + 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() + + self.submodules.ddmtd_sampler = DDMTDSamplerGTX(gtx, main_dcxo_pads) + + self.sync.helper += ddmtd_counter.eq(ddmtd_counter + 1) + self.submodules.ddmtd_gtx = DDMTD(ddmtd_counter, self.ddmtd_sampler.gtx_beating) + self.submodules.ddmtd_main = DDMTD(ddmtd_counter, self.ddmtd_sampler.main_beating) + + # DDMTD tags collection + + self.submodules.collector = ClockDomainsRenamer("helper")(Collector(COUNTER_BIT)) + + self.comb += [ + self.collector.gtx_stb.eq(self.ddmtd_gtx.h_tag_update), + self.collector.main_stb.eq(self.ddmtd_main.h_tag_update), + self.collector.tag_gtx.eq(self.ddmtd_gtx.h_tag), + self.collector.tag_main.eq(self.ddmtd_main.h_tag) + ] + + period_colr_stb_ps = PulseSynchronizer("helper", "sys") + phase_colr_stb_ps = PulseSynchronizer("helper", "sys") + self.submodules += [ + period_colr_stb_ps, + phase_colr_stb_ps + ] + self.sync.helper += [ + period_colr_stb_ps.i.eq(self.collector.out_period_stb), + phase_colr_stb_ps.i.eq(self.collector.out_phase_stb) + ] + self.sync += [ + period_colr_stb_sys.eq(period_colr_stb_ps.o), + phase_colr_stb_sys.eq(phase_colr_stb_ps.o) + ] + + self.specials += [ + MultiReg(self.collector.out_beating_period, gtx_period_sys), + MultiReg(self.collector.out_tag_gtx, gtx_tag_sys), + MultiReg(self.collector.out_tag_main, main_tag_sys) + ] + + self.sync += [ + If(period_colr_stb_sys, + self.gtx_period.status.eq(gtx_period_sys), + ), + If(phase_colr_stb_sys, + self.gtx_tag.status.eq(gtx_tag_sys), + self.main_tag.status.eq(main_tag_sys) + ) + ] + + # PL-PS shared peripheral interrupt (SPI) + + self.submodules.period_ev = EventManager() + self.period_ev.stb = EventSourcePulse() + self.period_ev.finalize() + + self.submodules.phase_ev = EventManager() + self.phase_ev.stb = EventSourcePulse() + self.phase_ev.finalize() + + self.sync += [ + self.period_ev.stb.trigger.eq(period_colr_stb_sys), + self.phase_ev.stb.trigger.eq(phase_colr_stb_sys) + ]