diff --git a/artiq/gateware/wrpll/__init__.py b/artiq/gateware/wrpll/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/artiq/gateware/wrpll/ddmtd.py b/artiq/gateware/wrpll/ddmtd.py new file mode 100644 index 000000000..2d0380810 --- /dev/null +++ b/artiq/gateware/wrpll/ddmtd.py @@ -0,0 +1,119 @@ +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_clk = Signal() + self.specials +=[ + # ISERDESE2 can only be driven from fabric via IDELAYE2 (see UG471) + Instance("IDELAYE2", + p_DELAY_SRC="DATAIN", + p_HIGH_PERFORMANCE_MODE="TRUE", + p_REFCLK_FREQUENCY=208.3, # REFCLK frequency from IDELAYCTRL + p_IDELAY_VALUE=0, + + i_DATAIN=cd_ref.clk, + + o_DATAOUT=ref_clk + ), + Instance("ISERDESE2", + p_IOBDELAY="IFD", # use DDLY as input + p_DATA_RATE="SDR", + p_DATA_WIDTH=2, # min is 2 + p_NUM_CE=1, + + i_DDLY=ref_clk, + i_CE1=1, + i_CLK=ClockSignal("helper"), + i_CLKDIV=ClockSignal("helper"), + + o_Q1=self.ref_beating + ), + Instance("ISERDESE2", + p_DATA_RATE="SDR", + p_DATA_WIDTH=2, # min is 2 + p_NUM_CE=1, + + i_D=main_clk_se, + i_CE1=1, + i_CLK=ClockSignal("helper"), + i_CLKDIV=ClockSignal("helper"), + + o_Q1=self.main_beating, + ), + ] + + +class DDMTDDeglitcherMedianEdge(Module): + def __init__(self, counter, input_signal, stable_0_period=100, stable_1_period=100): + self.tag = Signal(len(counter)) + self.detect = Signal() + + stable_0_counter = Signal(reset=stable_0_period - 1, max=stable_0_period) + stable_1_counter = Signal(reset=stable_1_period - 1, max=stable_1_period) + + # # # + + # Based on CERN's median edge deglitcher FSM + # https://white-rabbit.web.cern.ch/documents/Precise_time_and_frequency_transfer_in_a_White_Rabbit_network.pdf (p.72) + fsm = ClockDomainsRenamer("helper")(FSM(reset_state="WAIT_STABLE_0")) + self.submodules += fsm + + fsm.act("WAIT_STABLE_0", + If(stable_0_counter != 0, + NextValue(stable_0_counter, stable_0_counter - 1) + ).Else( + NextValue(stable_0_counter, stable_0_period - 1), + NextState("WAIT_EDGE") + ), + If(input_signal, + NextValue(stable_0_counter, stable_0_period - 1) + ), + ) + fsm.act("WAIT_EDGE", + If(input_signal, + NextValue(self.tag, counter), + NextState("GOT_EDGE") + ) + ) + fsm.act("GOT_EDGE", + If(stable_1_counter != 0, + NextValue(stable_1_counter, stable_1_counter - 1) + ).Else( + NextValue(stable_1_counter, stable_1_period - 1), + self.detect.eq(1), + NextState("WAIT_STABLE_0") + ), + If(~input_signal, + NextValue(self.tag, self.tag + 1), + NextValue(stable_1_counter, stable_1_period - 1) + ), + ) + + +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 = DDMTDDeglitcherMedianEdge(counter, 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(deglitcher.tag) + ) + ] \ No newline at end of file diff --git a/artiq/gateware/wrpll/si549.py b/artiq/gateware/wrpll/si549.py new file mode 100644 index 000000000..3ae894832 --- /dev/null +++ b/artiq/gateware/wrpll/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/artiq/gateware/wrpll/wrpll.py b/artiq/gateware/wrpll/wrpll.py new file mode 100644 index 000000000..ef5a15290 --- /dev/null +++ b/artiq/gateware/wrpll/wrpll.py @@ -0,0 +1,173 @@ +from migen import * +from migen.genlib.cdc import MultiReg, AsyncResetSynchronizer, PulseSynchronizer +from misoc.interconnect.csr import * +from misoc.interconnect.csr_eventmanager import * + +from artiq.gateware.wrpll.ddmtd import DDMTDSampler, DDMTD +from artiq.gateware.wrpll.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)