From 4f34a7c6d0c905863cfcfd711b032793a0a492a2 Mon Sep 17 00:00:00 2001 From: morgan Date: Fri, 15 Mar 2024 12:11:15 +0800 Subject: [PATCH 1/7] flake: update artiq --- flake.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/flake.lock b/flake.lock index 7c3fdb5..073a657 100644 --- a/flake.lock +++ b/flake.lock @@ -11,11 +11,11 @@ "src-pythonparser": "src-pythonparser" }, "locked": { - "lastModified": 1706785107, - "narHash": "sha256-Uj72tqigiOCdewSSBBMg6zUpVKhwjAo1HeLJgvyZ3oc=", + "lastModified": 1710303235, + "narHash": "sha256-0rIfVoL8RInAQSDVfjpLdMqIYdnVsA8DdMk2+aqvwrM=", "ref": "refs/heads/master", - "rev": "3aaa7e04f26a495e8847e47424bfc16d76d82bf8", - "revCount": 8672, + "rev": "c4323e1179aa0b9c9b4c135f894f267715cf2391", + "revCount": 8727, "type": "git", "url": "https://github.com/m-labs/artiq.git" }, @@ -37,11 +37,11 @@ ] }, "locked": { - "lastModified": 1701573753, - "narHash": "sha256-vhEtXjb9AM6/HnsgfVmhJQeqQ9JqysUm7iWNzTIbexs=", + "lastModified": 1707216368, + "narHash": "sha256-ZXoqzG2QsVsybALLYXs473avXcyKSZNh2kIgcPo60XQ=", "owner": "m-labs", "repo": "artiq-comtools", - "rev": "199bdabf4de49cb7ada8a4ac7133008e0f8434b7", + "rev": "e5d0204490bccc07ef9141b0d7c405ab01cb8273", "type": "github" }, "original": { @@ -118,11 +118,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1706515015, - "narHash": "sha256-eFfY5A7wlYy3jD/75lx6IJRueg4noE+jowl0a8lIlVo=", + "lastModified": 1707347730, + "narHash": "sha256-0etC/exQIaqC9vliKhc3eZE2Mm2wgLa0tj93ZF/egvM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f4a8d6d5324c327dcc2d863eb7f3cc06ad630df4", + "rev": "6832d0d99649db3d65a0e15fa51471537b2c56a6", "type": "github" }, "original": { -- 2.42.0 From e4d8d44c7c54fb6985440a6ae0d43b1f8a951066 Mon Sep 17 00:00:00 2001 From: morgan Date: Thu, 4 Jan 2024 12:28:40 +0800 Subject: [PATCH 2/7] 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), + ] -- 2.42.0 From 7827c7b803e00e8444f003463817e694aee5703c Mon Sep 17 00:00:00 2001 From: morgan Date: Thu, 7 Mar 2024 13:11:41 +0800 Subject: [PATCH 3/7] Gateware: kasli_soc WRPLL setup kasli_soc: use enable_wrpll from json to switch from si5324 to si549 kasli_soc: add wrpll for all variants kasli_soc: add gtx & main tag nFIQ for all variants kasli_soc: add clk_synth_se for master & satellite kasli_soc: add wrpll_refclk for runtime kasli_soc: add skewtester for satman kasli_soc: add WRPLL_REF_CLK config for firmware --- src/gateware/kasli_soc.py | 83 ++++++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 19 deletions(-) diff --git a/src/gateware/kasli_soc.py b/src/gateware/kasli_soc.py index 25e9c04..bb12008 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 from config import write_csr_file, write_mem_file, write_rustc_cfg_file eem_iostandard_dict = { @@ -108,6 +109,7 @@ class GenericStandalone(SoCCore): def __init__(self, description, acpki=False): self.acpki = acpki clk_freq = description["rtio_frequency"] + with_wrpll = description["enable_wrpll"] platform = kasli_soc.Platform() platform.toolchain.bitstream_commands.extend([ @@ -119,13 +121,6 @@ class GenericStandalone(SoCCore): SoCCore.__init__(self, platform=platform, csr_data_width=32, ident=ident, ps_cd_sys=False) self.config["HW_REV"] = description["hw_rev"] - - - self.submodules += SMAClkinForward(self.platform) - - self.config["HAS_SI5324"] = None - self.config["SI5324_SOFT_RESET"] = None - clk_synth = platform.request("cdr_clk_clean_fabric") clk_synth_se = Signal() clk_synth_se_buf = Signal() @@ -149,6 +144,23 @@ class GenericStandalone(SoCCore): self.crg = self.ps7 # HACK for eem_7series to find the clock self.crg.cd_sys = self.sys_crg.cd_sys + if with_wrpll: + self.submodules.wrpll_refclk = wrpll.SMAFrequencyMultiplier(platform.request("sma_clkin")) + self.submodules.wrpll = wrpll.WRPLL( + platform=self.platform, + cd_ref=self.wrpll_refclk.cd_ref, + main_clk_se=clk_synth_se) + self.csr_devices.append("wrpll_refclk") + self.csr_devices.append("wrpll") + self.comb += self.ps7.core.core0.nfiq.eq(self.wrpll.ev.irq) + self.config["HAS_SI549"] = None + self.config["WRPLL_REF_CLK"] = "SMA_CLKIN" + else: + self.submodules += SMAClkinForward(self.platform) + self.config["HAS_SI5324"] = None + self.config["SI5324_SOFT_RESET"] = None + + self.rtio_channels = [] has_grabber = any(peripheral["type"] == "grabber" for peripheral in description["peripherals"]) if has_grabber: @@ -207,6 +219,7 @@ class GenericStandalone(SoCCore): class GenericMaster(SoCCore): def __init__(self, description, acpki=False): clk_freq = description["rtio_frequency"] + with_wrpll = description["enable_wrpll"] has_drtio_over_eem = any(peripheral["type"] == "shuttler" for peripheral in description["peripherals"]) self.acpki = acpki @@ -222,8 +235,6 @@ class GenericMaster(SoCCore): self.config["HW_REV"] = description["hw_rev"] - self.submodules += SMAClkinForward(self.platform) - data_pads = [platform.request("sfp", i) for i in range(4)] self.submodules.gt_drtio = gtx_7series.GTX( @@ -257,8 +268,25 @@ class GenericMaster(SoCCore): self.comb += ext_async_rst.eq(self.sys_crg.clk_sw_fsm.o_clk_sw & ~gtx0.tx_init.done) self.specials += MultiReg(self.sys_crg.clk_sw_fsm.o_clk_sw & self.sys_crg.mmcm_locked, self.gt_drtio.clk_path_ready, odomain="bootstrap") - self.config["HAS_SI5324"] = None - self.config["SI5324_SOFT_RESET"] = None + if with_wrpll: + clk_synth = platform.request("cdr_clk_clean_fabric") + clk_synth_se = Signal() + platform.add_period_constraint(clk_synth.p, 8.0) + self.specials += Instance("IBUFGDS", p_DIFF_TERM="TRUE", p_IBUF_LOW_PWR="FALSE", i_I=clk_synth.p, i_IB=clk_synth.n, o_O=clk_synth_se) + self.submodules.wrpll_refclk = wrpll.SMAFrequencyMultiplier(platform.request("sma_clkin")) + self.submodules.wrpll = wrpll.WRPLL( + platform=self.platform, + cd_ref=self.wrpll_refclk.cd_ref, + main_clk_se=clk_synth_se) + self.csr_devices.append("wrpll_refclk") + self.csr_devices.append("wrpll") + self.comb += self.ps7.core.core0.nfiq.eq(self.wrpll.ev.irq) + self.config["HAS_SI549"] = None + self.config["WRPLL_REF_CLK"] = "SMA_CLKIN" + else: + self.submodules += SMAClkinForward(self.platform) + self.config["HAS_SI5324"] = None + self.config["SI5324_SOFT_RESET"] = None self.rtio_channels = [] has_grabber = any(peripheral["type"] == "grabber" for peripheral in description["peripherals"]) @@ -400,6 +428,7 @@ class GenericMaster(SoCCore): class GenericSatellite(SoCCore): def __init__(self, description, acpki=False): clk_freq = description["rtio_frequency"] + with_wrpll = description["enable_wrpll"] self.acpki = acpki @@ -551,14 +580,30 @@ class GenericSatellite(SoCCore): self.config["RTIO_FREQUENCY"] = str(clk_freq/1e6) self.config["CLOCK_FREQUENCY"] = int(clk_freq) - self.submodules.siphaser = SiPhaser7Series( - si5324_clkin=platform.request("cdr_clk"), - rx_synchronizer=self.rx_synchronizer, - ultrascale=False, - rtio_clk_freq=self.gt_drtio.rtio_clk_freq) - self.csr_devices.append("siphaser") - self.config["HAS_SI5324"] = None - self.config["SI5324_SOFT_RESET"] = None + if with_wrpll: + clk_synth = platform.request("cdr_clk_clean_fabric") + clk_synth_se = Signal() + platform.add_period_constraint(clk_synth.p, 8.0) + self.specials += Instance("IBUFGDS", p_DIFF_TERM="TRUE", p_IBUF_LOW_PWR="FALSE", i_I=clk_synth.p, i_IB=clk_synth.n, o_O=clk_synth_se) + self.submodules.wrpll = wrpll.WRPLL( + platform=self.platform, + cd_ref=self.gt_drtio.cd_rtio_rx0, + main_clk_se=clk_synth_se) + self.submodules.wrpll_skewtester = wrpll.SkewTester(self.rx_synchronizer) + self.csr_devices.append("wrpll_skewtester") + self.csr_devices.append("wrpll") + self.comb += self.ps7.core.core0.nfiq.eq(self.wrpll.ev.irq) + self.config["HAS_SI549"] = None + self.config["WRPLL_REF_CLK"] = "GT_CDR" + else: + self.submodules.siphaser = SiPhaser7Series( + si5324_clkin=platform.request("cdr_clk"), + rx_synchronizer=self.rx_synchronizer, + ultrascale=False, + rtio_clk_freq=self.gt_drtio.rtio_clk_freq) + self.csr_devices.append("siphaser") + self.config["HAS_SI5324"] = None + self.config["SI5324_SOFT_RESET"] = None gtx0 = self.gt_drtio.gtxs[0] platform.add_false_path_constraints( -- 2.42.0 From a1d80fb93b413500d913ad3e88119b4e829e434a Mon Sep 17 00:00:00 2001 From: morgan Date: Thu, 4 Jan 2024 12:41:36 +0800 Subject: [PATCH 4/7] Firmware: Si549 and io_expander io_expander: set CLK_SEL pin to output when si549 is used io_expander: gate virtual leds for standalone si549: add bit bang i2c si549: add si549 programming si549: add main & helper setup --- src/libboard_artiq/src/io_expander.rs | 16 +- src/libboard_artiq/src/lib.rs | 3 +- src/libboard_artiq/src/si549.rs | 326 ++++++++++++++++++++++++++ 3 files changed, 340 insertions(+), 5 deletions(-) create mode 100644 src/libboard_artiq/src/si549.rs diff --git a/src/libboard_artiq/src/io_expander.rs b/src/libboard_artiq/src/io_expander.rs index 5157805..55a1694 100644 --- a/src/libboard_artiq/src/io_expander.rs +++ b/src/libboard_artiq/src/io_expander.rs @@ -1,6 +1,7 @@ use libboard_zynq::i2c; use log::info; +#[cfg(has_virtual_leds)] use crate::pl::csr; // Only the bare minimum registers. Bits/IO connections equivalent between IC types. @@ -19,11 +20,15 @@ const IODIR_OUT_SFP_LED: u8 = 0x40; const IODIR_OUT_SFP0_LED: u8 = 0x40; #[cfg(hw_rev = "v1.1")] const IODIR_OUT_SFP0_LED: u8 = 0x80; +#[cfg(has_si549)] +const IODIR_CLK_SEL: u8 = 0x80; // out +#[cfg(has_si5324)] +const IODIR_CLK_SEL: u8 = 0x00; // in //IO expander port direction const IODIR0: [u8; 2] = [ 0xFF & !IODIR_OUT_SFP_TX_DISABLE & !IODIR_OUT_SFP0_LED, - 0xFF & !IODIR_OUT_SFP_TX_DISABLE & !IODIR_OUT_SFP_LED, + 0xFF & !IODIR_OUT_SFP_TX_DISABLE & !IODIR_OUT_SFP_LED & !IODIR_CLK_SEL, ]; const IODIR1: [u8; 2] = [ @@ -33,6 +38,7 @@ const IODIR1: [u8; 2] = [ pub struct IoExpander { address: u8, + #[cfg(has_virtual_leds)] virtual_led_mapping: &'static [(u8, u8, u8)], iodir: [u8; 2], out_current: [u8; 2], @@ -42,17 +48,18 @@ pub struct IoExpander { impl IoExpander { pub fn new(i2c: &mut i2c::I2c, index: u8) -> Result { - #[cfg(hw_rev = "v1.0")] + #[cfg(all(hw_rev = "v1.0", has_virtual_leds))] const VIRTUAL_LED_MAPPING0: [(u8, u8, u8); 2] = [(0, 0, 6), (1, 1, 6)]; - #[cfg(hw_rev = "v1.1")] + #[cfg(all(hw_rev = "v1.1", has_virtual_leds))] const VIRTUAL_LED_MAPPING0: [(u8, u8, u8); 2] = [(0, 0, 7), (1, 1, 6)]; - + #[cfg(has_virtual_leds)] const VIRTUAL_LED_MAPPING1: [(u8, u8, u8); 2] = [(2, 0, 6), (3, 1, 6)]; // Both expanders on SHARED I2C bus let mut io_expander = match index { 0 => IoExpander { address: 0x40, + #[cfg(has_virtual_leds)] virtual_led_mapping: &VIRTUAL_LED_MAPPING0, iodir: IODIR0, out_current: [0; 2], @@ -66,6 +73,7 @@ impl IoExpander { }, 1 => IoExpander { address: 0x42, + #[cfg(has_virtual_leds)] virtual_led_mapping: &VIRTUAL_LED_MAPPING1, iodir: IODIR1, out_current: [0; 2], diff --git a/src/libboard_artiq/src/lib.rs b/src/libboard_artiq/src/lib.rs index 0a7c286..4ee5a3c 100644 --- a/src/libboard_artiq/src/lib.rs +++ b/src/libboard_artiq/src/lib.rs @@ -35,7 +35,8 @@ pub mod drtio_eem; pub mod grabber; #[cfg(has_si5324)] pub mod si5324; - +#[cfg(has_si549)] +pub mod si549; use core::{cmp, str}; pub fn identifier_read(buf: &mut [u8]) -> &str { diff --git a/src/libboard_artiq/src/si549.rs b/src/libboard_artiq/src/si549.rs new file mode 100644 index 0000000..ca11ccc --- /dev/null +++ b/src/libboard_artiq/src/si549.rs @@ -0,0 +1,326 @@ +use embedded_hal::prelude::_embedded_hal_blocking_delay_DelayUs; +use libboard_zynq::timer::GlobalTimer; +use log::info; + +use crate::pl::csr; + +#[cfg(feature = "target_kasli_soc")] +const ADDRESS: u8 = 0x67; + +const ADPLL_MAX: i32 = (950.0 / 0.0001164) as i32; + +pub struct DividerConfig { + pub hsdiv: u16, + pub lsdiv: u8, + pub fbdiv: u64, +} + +pub struct FrequencySetting { + pub main: DividerConfig, + pub helper: DividerConfig, +} + +mod i2c { + use super::*; + + #[derive(Clone, Copy)] + pub enum DCXO { + Main, + Helper, + } + + fn half_period(timer: &mut GlobalTimer) { + timer.delay_us(1) + } + + fn sda_i(dcxo: DCXO) -> bool { + match dcxo { + DCXO::Main => unsafe { csr::wrpll::main_dcxo_sda_in_read() == 1 }, + DCXO::Helper => unsafe { csr::wrpll::helper_dcxo_sda_in_read() == 1 }, + } + } + + fn sda_oe(dcxo: DCXO, oe: bool) { + let val = if oe { 1 } else { 0 }; + match dcxo { + DCXO::Main => unsafe { csr::wrpll::main_dcxo_sda_oe_write(val) }, + DCXO::Helper => unsafe { csr::wrpll::helper_dcxo_sda_oe_write(val) }, + }; + } + + fn sda_o(dcxo: DCXO, o: bool) { + let val = if o { 1 } else { 0 }; + match dcxo { + DCXO::Main => unsafe { csr::wrpll::main_dcxo_sda_out_write(val) }, + DCXO::Helper => unsafe { csr::wrpll::helper_dcxo_sda_out_write(val) }, + }; + } + + fn scl_oe(dcxo: DCXO, oe: bool) { + let val = if oe { 1 } else { 0 }; + match dcxo { + DCXO::Main => unsafe { csr::wrpll::main_dcxo_scl_oe_write(val) }, + DCXO::Helper => unsafe { csr::wrpll::helper_dcxo_scl_oe_write(val) }, + }; + } + + fn scl_o(dcxo: DCXO, o: bool) { + let val = if o { 1 } else { 0 }; + match dcxo { + DCXO::Main => unsafe { csr::wrpll::main_dcxo_scl_out_write(val) }, + DCXO::Helper => unsafe { csr::wrpll::helper_dcxo_scl_out_write(val) }, + }; + } + + pub fn init(dcxo: DCXO, timer: &mut GlobalTimer) -> Result<(), &'static str> { + // Set SCL as output, and high level + scl_o(dcxo, true); + scl_oe(dcxo, true); + // Prepare a zero level on SDA so that sda_oe pulls it down + sda_o(dcxo, false); + // Release SDA + sda_oe(dcxo, false); + + // Check the I2C bus is ready + half_period(timer); + half_period(timer); + if !sda_i(dcxo) { + // Try toggling SCL a few times + for _bit in 0..8 { + scl_o(dcxo, false); + half_period(timer); + scl_o(dcxo, true); + half_period(timer); + } + } + + if !sda_i(dcxo) { + return Err("SDA is stuck low and doesn't get unstuck"); + } + Ok(()) + } + + pub fn start(dcxo: DCXO, timer: &mut GlobalTimer) { + // Set SCL high then SDA low + scl_o(dcxo, true); + half_period(timer); + sda_oe(dcxo, true); + half_period(timer); + } + + pub fn stop(dcxo: DCXO, timer: &mut GlobalTimer) { + // First, make sure SCL is low, so that the target releases the SDA line + scl_o(dcxo, false); + half_period(timer); + // Set SCL high then SDA high + sda_oe(dcxo, true); + scl_o(dcxo, true); + half_period(timer); + sda_oe(dcxo, false); + half_period(timer); + } + + pub fn write(dcxo: DCXO, data: u8, timer: &mut GlobalTimer) -> bool { + // MSB first + for bit in (0..8).rev() { + // Set SCL low and set our bit on SDA + scl_o(dcxo, false); + sda_oe(dcxo, data & (1 << bit) == 0); + half_period(timer); + // Set SCL high ; data is shifted on the rising edge of SCL + scl_o(dcxo, true); + half_period(timer); + } + // Check ack + // Set SCL low, then release SDA so that the I2C target can respond + scl_o(dcxo, false); + half_period(timer); + sda_oe(dcxo, false); + // Set SCL high and check for ack + scl_o(dcxo, true); + half_period(timer); + // returns true if acked (I2C target pulled SDA low) + !sda_i(dcxo) + } + + pub fn read(dcxo: DCXO, ack: bool, timer: &mut GlobalTimer) -> u8 { + // Set SCL low first, otherwise setting SDA as input may cause a transition + // on SDA with SCL high which will be interpreted as START/STOP condition. + scl_o(dcxo, false); + half_period(timer); // make sure SCL has settled low + sda_oe(dcxo, false); + + let mut data: u8 = 0; + + // MSB first + for bit in (0..8).rev() { + scl_o(dcxo, false); + half_period(timer); + // Set SCL high and shift data + scl_o(dcxo, true); + half_period(timer); + if sda_i(dcxo) { + data |= 1 << bit + } + } + // Send ack + // Set SCL low and pull SDA low when acking + scl_o(dcxo, false); + if ack { + sda_oe(dcxo, true) + } + half_period(timer); + // then set SCL high + scl_o(dcxo, true); + half_period(timer); + + data + } +} + +fn write(dcxo: i2c::DCXO, reg: u8, val: u8, timer: &mut GlobalTimer) -> Result<(), &'static str> { + i2c::start(dcxo, timer); + if !i2c::write(dcxo, ADDRESS << 1, timer) { + return Err("Si549 failed to ack write address"); + } + if !i2c::write(dcxo, reg, timer) { + return Err("Si549 failed to ack register"); + } + if !i2c::write(dcxo, val, timer) { + return Err("Si549 failed to ack value"); + } + i2c::stop(dcxo, timer); + Ok(()) +} + +fn read(dcxo: i2c::DCXO, reg: u8, timer: &mut GlobalTimer) -> Result { + i2c::start(dcxo, timer); + if !i2c::write(dcxo, ADDRESS << 1, timer) { + return Err("Si549 failed to ack write address"); + } + if !i2c::write(dcxo, reg, timer) { + return Err("Si549 failed to ack register"); + } + i2c::stop(dcxo, timer); + + i2c::start(dcxo, timer); + if !i2c::write(dcxo, (ADDRESS << 1) | 1, timer) { + return Err("Si549 failed to ack read address"); + } + let val = i2c::read(dcxo, false, timer); + i2c::stop(dcxo, timer); + Ok(val) +} + +fn setup(dcxo: i2c::DCXO, config: &DividerConfig, timer: &mut GlobalTimer) -> Result<(), &'static str> { + i2c::init(dcxo, timer)?; + + write(dcxo, 255, 0x00, timer)?; // PAGE + write(dcxo, 69, 0x00, timer)?; // Disable FCAL override. + write(dcxo, 17, 0x00, timer)?; // Synchronously disable output + + // The Si549 has no ID register, so we check that it responds correctly + // by writing values to a RAM-like register and reading them back. + for test_value in 0..255 { + write(dcxo, 23, test_value, timer)?; + let readback = read(dcxo, 23, timer)?; + if readback != test_value { + return Err("Si549 detection failed"); + } + } + + write(dcxo, 23, config.hsdiv as u8, timer)?; + write(dcxo, 24, (config.hsdiv >> 8) as u8 | (config.lsdiv << 4), timer)?; + write(dcxo, 26, config.fbdiv as u8, timer)?; + write(dcxo, 27, (config.fbdiv >> 8) as u8, timer)?; + write(dcxo, 28, (config.fbdiv >> 16) as u8, timer)?; + write(dcxo, 29, (config.fbdiv >> 24) as u8, timer)?; + write(dcxo, 30, (config.fbdiv >> 32) as u8, timer)?; + write(dcxo, 31, (config.fbdiv >> 40) as u8, timer)?; + + write(dcxo, 7, 0x08, timer)?; // Start FCAL + timer.delay_us(30_000); // Internal FCAL VCO calibration + write(dcxo, 17, 0x01, timer)?; // Synchronously enable output + + Ok(()) +} + +pub fn main_setup(timer: &mut GlobalTimer, settings: &FrequencySetting) -> Result<(), &'static str> { + unsafe { + csr::wrpll::main_dcxo_bitbang_enable_write(1); + csr::wrpll::main_dcxo_i2c_address_write(ADDRESS); + } + + setup(i2c::DCXO::Main, &settings.main, timer)?; + + // Si549 maximum settling time for large frequency change. + timer.delay_us(40_000); + + unsafe { + csr::wrpll::main_dcxo_bitbang_enable_write(0); + } + + info!("Main Si549 started"); + Ok(()) +} + +pub fn helper_setup(timer: &mut GlobalTimer, settings: &FrequencySetting) -> Result<(), &'static str> { + unsafe { + csr::wrpll::helper_reset_write(1); + csr::wrpll::helper_dcxo_bitbang_enable_write(1); + csr::wrpll::helper_dcxo_i2c_address_write(ADDRESS); + } + + setup(i2c::DCXO::Helper, &settings.helper, timer)?; + + // Si549 maximum settling time for large frequency change. + timer.delay_us(40_000); + + unsafe { + csr::wrpll::helper_reset_write(0); + csr::wrpll::helper_dcxo_bitbang_enable_write(0); + } + info!("Helper Si549 started"); + Ok(()) +} + +fn set_adpll(dcxo: i2c::DCXO, adpll: i32) -> Result<(), &'static str> { + if adpll.abs() > ADPLL_MAX { + return Err("adpll is too large"); + } + + match dcxo { + i2c::DCXO::Main => unsafe { + if csr::wrpll::main_dcxo_bitbang_enable_read() == 1 { + return Err("Main si549 bitbang mode is active when using gateware i2c"); + } + + while csr::wrpll::main_dcxo_adpll_busy_read() == 1 {} + if csr::wrpll::main_dcxo_nack_read() == 1 { + return Err("Main si549 failed to ack adpll write"); + } + + csr::wrpll::main_dcxo_i2c_address_write(ADDRESS); + csr::wrpll::main_dcxo_adpll_write(adpll as u32); + + csr::wrpll::main_dcxo_adpll_stb_write(1); + }, + i2c::DCXO::Helper => unsafe { + if csr::wrpll::helper_dcxo_bitbang_enable_read() == 1 { + return Err("Helper si549 bitbang mode is active when using gateware i2c"); + } + + while csr::wrpll::helper_dcxo_adpll_busy_read() == 1 {} + if csr::wrpll::helper_dcxo_nack_read() == 1 { + return Err("Helper si549 failed to ack adpll write"); + } + + csr::wrpll::helper_dcxo_i2c_address_write(ADDRESS); + csr::wrpll::helper_dcxo_adpll_write(adpll as u32); + + csr::wrpll::helper_dcxo_adpll_stb_write(1); + }, + }; + + Ok(()) +} -- 2.42.0 From 291777f764539385a15a0cb258b8e34b642288a2 Mon Sep 17 00:00:00 2001 From: morgan Date: Tue, 9 Apr 2024 16:32:56 +0800 Subject: [PATCH 5/7] Firmware: Satman WRPLL satman: drive CLK_SEL to true when si549 is used satman : add main & helper si549 setup satman : add WRPLL select_recovered_clock si549: add tag collector to process gtx & main tags si549: add frequency counter to set BASE_ADPLL si549: add set_adpll for main & helper PLL si549: add main & helper PLL FIQ & si549: replace dummy with a custom handler for gtx & main tags ISR --- src/libboard_artiq/Cargo.toml.tpl | 2 +- src/libboard_artiq/src/fiq.rs | 22 +++ src/libboard_artiq/src/lib.rs | 3 + src/libboard_artiq/src/si549.rs | 216 ++++++++++++++++++++++++++++++ src/satman/src/main.rs | 46 +++++++ 5 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 src/libboard_artiq/src/fiq.rs diff --git a/src/libboard_artiq/Cargo.toml.tpl b/src/libboard_artiq/Cargo.toml.tpl index 5677984..9c1ad24 100644 --- a/src/libboard_artiq/Cargo.toml.tpl +++ b/src/libboard_artiq/Cargo.toml.tpl @@ -25,7 +25,7 @@ void = { version = "1", default-features = false } io = { path = "../libio", features = ["byteorder"] } libboard_zynq = { path = "@@ZYNQ_RS@@/libboard_zynq" } -libsupport_zynq = { path = "@@ZYNQ_RS@@/libsupport_zynq", default-features = false, features = ["alloc_core", "dummy_fiq_handler"] } +libsupport_zynq = { path = "@@ZYNQ_RS@@/libsupport_zynq", default-features = false, features = ["alloc_core"] } libregister = { path = "@@ZYNQ_RS@@/libregister" } libconfig = { path = "@@ZYNQ_RS@@/libconfig", features = ["fat_lfn"] } libcortex_a9 = { path = "@@ZYNQ_RS@@/libcortex_a9" } diff --git a/src/libboard_artiq/src/fiq.rs b/src/libboard_artiq/src/fiq.rs new file mode 100644 index 0000000..dc9c013 --- /dev/null +++ b/src/libboard_artiq/src/fiq.rs @@ -0,0 +1,22 @@ +use libboard_zynq::{println, stdio}; +use libcortex_a9::{interrupt_handler, regs::MPIDR}; +use libregister::RegisterR; + +#[cfg(has_si549)] +use crate::si549; + +interrupt_handler!(FIQ, fiq, __irq_stack0_start, __irq_stack1_start, { + match MPIDR.read().cpu_id() { + 0 => { + // nFIQ is driven directly and bypass GIC + #[cfg(has_si549)] + si549::wrpll::interrupt_handler(); + return; + } + _ => {} + }; + + stdio::drop_uart(); + println!("FIQ"); + loop {} +}); diff --git a/src/libboard_artiq/src/lib.rs b/src/libboard_artiq/src/lib.rs index 4ee5a3c..e9b9460 100644 --- a/src/libboard_artiq/src/lib.rs +++ b/src/libboard_artiq/src/lib.rs @@ -1,5 +1,7 @@ #![no_std] #![feature(never_type)] +#![feature(naked_functions)] +#![feature(asm)] extern crate core_io; extern crate crc; @@ -19,6 +21,7 @@ pub mod drtioaux; #[cfg(has_drtio)] pub mod drtioaux_async; pub mod drtioaux_proto; +pub mod fiq; #[cfg(all(feature = "target_kasli_soc", has_drtio))] pub mod io_expander; pub mod logger; diff --git a/src/libboard_artiq/src/si549.rs b/src/libboard_artiq/src/si549.rs index ca11ccc..1723cd3 100644 --- a/src/libboard_artiq/src/si549.rs +++ b/src/libboard_artiq/src/si549.rs @@ -324,3 +324,219 @@ fn set_adpll(dcxo: i2c::DCXO, adpll: i32) -> Result<(), &'static str> { Ok(()) } + +#[cfg(has_wrpll)] +pub mod wrpll { + + use super::*; + + const BEATING_PERIOD: i32 = 0x8000; + const BEATING_HALFPERIOD: i32 = 0x4000; + const COUNTER_WIDTH: u32 = 24; + const DIV_WIDTH: u32 = 2; + + const KP: i32 = 6; + const KI: i32 = 2; + // 4 ppm capture range + const ADPLL_LIM: i32 = (4.0 / 0.0001164) as i32; + + static mut BASE_ADPLL: i32 = 0; + static mut H_LAST_ADPLL: i32 = 0; + static mut LAST_PERIOD_ERR: i32 = 0; + static mut M_LAST_ADPLL: i32 = 0; + static mut LAST_PHASE_ERR: i32 = 0; + + #[derive(Clone, Copy)] + pub enum ISR { + RefTag, + MainTag, + } + + mod tag_collector { + use super::*; + + static mut REF_TAG: u32 = 0; + static mut REF_TAG_READY: bool = false; + static mut MAIN_TAG: u32 = 0; + static mut MAIN_TAG_READY: bool = false; + + pub fn reset() { + clear_phase_diff_ready(); + unsafe { + REF_TAG = 0; + MAIN_TAG = 0; + } + } + + pub fn clear_phase_diff_ready() { + unsafe { + REF_TAG_READY = false; + MAIN_TAG_READY = false; + } + } + + pub fn collect_tags(interrupt: ISR) { + match interrupt { + ISR::RefTag => unsafe { + REF_TAG = csr::wrpll::ref_tag_read(); + REF_TAG_READY = true; + }, + ISR::MainTag => unsafe { + MAIN_TAG = csr::wrpll::main_tag_read(); + MAIN_TAG_READY = true; + }, + } + } + + pub fn phase_diff_ready() -> bool { + unsafe { REF_TAG_READY && MAIN_TAG_READY } + } + + pub fn get_period_error() -> i32 { + // n * BEATING_PERIOD - REF_TAG(n) mod BEATING_PERIOD + let mut period_error = unsafe { REF_TAG.overflowing_neg().0.rem_euclid(BEATING_PERIOD as u32) as i32 }; + // mapping tags from [0, 2π] -> [-π, π] + if period_error > BEATING_HALFPERIOD { + period_error -= BEATING_PERIOD + } + period_error + } + + pub fn get_phase_error() -> i32 { + // MAIN_TAG(n) - REF_TAG(n) mod BEATING_PERIOD + let mut phase_error = + unsafe { MAIN_TAG.overflowing_sub(REF_TAG).0.rem_euclid(BEATING_PERIOD as u32) as i32 }; + + // mapping tags from [0, 2π] -> [-π, π] + if phase_error > BEATING_HALFPERIOD { + phase_error -= BEATING_PERIOD + } + phase_error + } + } + + fn set_isr(en: bool) { + let val = if en { 1 } else { 0 }; + unsafe { + csr::wrpll::ref_tag_ev_enable_write(val); + csr::wrpll::main_tag_ev_enable_write(val); + } + } + + fn set_base_adpll() -> Result<(), &'static str> { + let count2adpll = + |error: i32| ((error as f64 * 1e6) / (0.0001164 * (1 << (COUNTER_WIDTH - DIV_WIDTH)) as f64)) as i32; + + let (ref_count, main_count) = get_freq_counts(); + unsafe { + BASE_ADPLL = count2adpll(ref_count as i32 - main_count as i32); + set_adpll(i2c::DCXO::Main, BASE_ADPLL)?; + set_adpll(i2c::DCXO::Helper, BASE_ADPLL)?; + } + Ok(()) + } + + fn get_freq_counts() -> (u32, u32) { + unsafe { + csr::wrpll::frequency_counter_update_write(1); + while csr::wrpll::frequency_counter_busy_read() == 1 {} + #[cfg(wrpll_ref_clk = "GT_CDR")] + let ref_count = csr::wrpll::frequency_counter_counter_rtio_rx0_read(); + #[cfg(wrpll_ref_clk = "SMA_CLKIN")] + let ref_count = csr::wrpll::frequency_counter_counter_ref_read(); + let main_count = csr::wrpll::frequency_counter_counter_sys_read(); + + (ref_count, main_count) + } + } + + fn reset_plls(timer: &mut GlobalTimer) -> Result<(), &'static str> { + unsafe { + H_LAST_ADPLL = 0; + LAST_PERIOD_ERR = 0; + M_LAST_ADPLL = 0; + LAST_PHASE_ERR = 0; + } + set_adpll(i2c::DCXO::Main, 0)?; + set_adpll(i2c::DCXO::Helper, 0)?; + // wait for adpll to transfer and DCXO to settle + timer.delay_us(200); + Ok(()) + } + + fn clear_pending(interrupt: ISR) { + match interrupt { + ISR::RefTag => unsafe { csr::wrpll::ref_tag_ev_pending_write(1) }, + ISR::MainTag => unsafe { csr::wrpll::main_tag_ev_pending_write(1) }, + }; + } + + fn is_pending(interrupt: ISR) -> bool { + match interrupt { + ISR::RefTag => unsafe { csr::wrpll::ref_tag_ev_pending_read() == 1 }, + ISR::MainTag => unsafe { csr::wrpll::main_tag_ev_pending_read() == 1 }, + } + } + + pub fn interrupt_handler() { + if is_pending(ISR::RefTag) { + tag_collector::collect_tags(ISR::RefTag); + clear_pending(ISR::RefTag); + helper_pll().expect("failed to run helper DCXO PLL"); + } + + if is_pending(ISR::MainTag) { + tag_collector::collect_tags(ISR::MainTag); + clear_pending(ISR::MainTag); + } + + if tag_collector::phase_diff_ready() { + main_pll().expect("failed to run main DCXO PLL"); + tag_collector::clear_phase_diff_ready(); + } + } + + fn helper_pll() -> Result<(), &'static str> { + let period_err = tag_collector::get_period_error(); + unsafe { + // Based on https://hackmd.io/IACbwcOTSt6Adj3_F9bKuw?view#Integral-wind-up-and-output-limiting + let adpll = (H_LAST_ADPLL + (KP + KI) * period_err - KP * LAST_PERIOD_ERR).clamp(-ADPLL_LIM, ADPLL_LIM); + set_adpll(i2c::DCXO::Helper, BASE_ADPLL + adpll)?; + H_LAST_ADPLL = adpll; + LAST_PERIOD_ERR = period_err; + }; + Ok(()) + } + + fn main_pll() -> Result<(), &'static str> { + let phase_err = tag_collector::get_phase_error(); + unsafe { + // Based on https://hackmd.io/IACbwcOTSt6Adj3_F9bKuw?view#Integral-wind-up-and-output-limiting + let adpll = (M_LAST_ADPLL + (KP + KI) * phase_err - KP * LAST_PHASE_ERR).clamp(-ADPLL_LIM, ADPLL_LIM); + set_adpll(i2c::DCXO::Main, BASE_ADPLL + adpll)?; + M_LAST_ADPLL = adpll; + LAST_PHASE_ERR = phase_err; + }; + Ok(()) + } + + pub fn select_recovered_clock(rc: bool, timer: &mut GlobalTimer) { + set_isr(false); + + if rc { + tag_collector::reset(); + reset_plls(timer).expect("failed to reset main and helper PLL"); + + // get within capture range + set_base_adpll().expect("failed to set base adpll"); + + // clear gateware pending flag + clear_pending(ISR::RefTag); + clear_pending(ISR::MainTag); + + // use nFIQ to avoid IRQ being disabled by mutex lock and mess up PLL + set_isr(true); + info!("WRPLL interrupt enabled"); + } + } +} diff --git a/src/satman/src/main.rs b/src/satman/src/main.rs index 1ddea7b..2a588b9 100644 --- a/src/satman/src/main.rs +++ b/src/satman/src/main.rs @@ -29,6 +29,8 @@ use libboard_artiq::grabber; use libboard_artiq::io_expander; #[cfg(has_si5324)] use libboard_artiq::si5324; +#[cfg(has_si549)] +use libboard_artiq::si549; use libboard_artiq::{drtio_routing, drtioaux, drtioaux_proto::{MASTER_PAYLOAD_MAX_SIZE, SAT_PAYLOAD_MAX_SIZE}, identifier_read, logger, @@ -828,6 +830,36 @@ const SI5324_SETTINGS: si5324::FrequencySettings = si5324::FrequencySettings { crystal_as_ckin2: true, }; +#[cfg(all(has_si549, rtio_frequency = "125.0"))] +const SI549_SETTINGS: si549::FrequencySetting = si549::FrequencySetting { + main: si549::DividerConfig { + hsdiv: 0x058, + lsdiv: 0, + fbdiv: 0x04815791F25, + }, + helper: si549::DividerConfig { + // 125MHz*32767/32768 + hsdiv: 0x058, + lsdiv: 0, + fbdiv: 0x04814E8F442, + }, +}; + +#[cfg(all(has_si549, rtio_frequency = "100.0"))] +const SI549_SETTINGS: si549::FrequencySetting = si549::FrequencySetting { + main: si549::DividerConfig { + hsdiv: 0x06C, + lsdiv: 0, + fbdiv: 0x046C5F49797, + }, + helper: si549::DividerConfig { + // 100MHz*32767/32768 + hsdiv: 0x06C, + lsdiv: 0, + fbdiv: 0x046C5670BBD, + }, +}; + static mut LOG_BUFFER: [u8; 1 << 17] = [0; 1 << 17]; #[no_mangle] @@ -864,6 +896,11 @@ pub extern "C" fn main_core0() -> i32 { io_expander1 .init(&mut i2c) .expect("I2C I/O expander #1 initialization failed"); + + // Drive CLK_SEL to true + #[cfg(has_si549)] + io_expander0.set(1, 7, true); + // Drive TX_DISABLE to false on SFP0..3 io_expander0.set(0, 1, false); io_expander1.set(0, 1, false); @@ -875,6 +912,8 @@ pub extern "C" fn main_core0() -> i32 { #[cfg(has_si5324)] si5324::setup(&mut i2c, &SI5324_SETTINGS, si5324::Input::Ckin1, &mut timer).expect("cannot initialize Si5324"); + #[cfg(has_si549)] + si549::main_setup(&mut timer, &SI549_SETTINGS).expect("cannot initialize main Si549"); timer.delay_us(100_000); info!("Switching SYS clocks..."); @@ -892,6 +931,8 @@ pub extern "C" fn main_core0() -> i32 { unsafe { csr::gt_drtio::txenable_write(0xffffffffu32 as _); } + #[cfg(has_si549)] + si549::helper_setup(&mut timer, &SI549_SETTINGS).expect("cannot initialize helper Si549"); #[cfg(has_drtio_routing)] let mut repeaters = [repeater::Repeater::default(); csr::DRTIOREP.len()]; @@ -937,6 +978,9 @@ pub extern "C" fn main_core0() -> i32 { si5324::siphaser::calibrate_skew(&mut timer).expect("failed to calibrate skew"); } + #[cfg(has_wrpll)] + si549::wrpll::select_recovered_clock(true, &mut timer); + // Various managers created here, so when link is dropped, all DMA traces // are cleared out for a clean slate on subsequent connections, // without a manual intervention. @@ -1034,6 +1078,8 @@ pub extern "C" fn main_core0() -> i32 { info!("uplink is down, switching to local oscillator clock"); #[cfg(has_siphaser)] si5324::siphaser::select_recovered_clock(&mut i2c, false, &mut timer).expect("failed to switch clocks"); + #[cfg(has_wrpll)] + si549::wrpll::select_recovered_clock(false, &mut timer); } } -- 2.42.0 From b81323af306c9c9140866b918aee6554250bf0a0 Mon Sep 17 00:00:00 2001 From: morgan Date: Tue, 19 Mar 2024 10:36:47 +0800 Subject: [PATCH 6/7] Firmware: Satman skew calibration & tester cargo template: add calibrate_wrpll_skew feature tag collector: add TAG_OFFSET for Satman WRPLL tag collector: add TAG_OFFSET getter & setter for calibration wrpll: add skew tester and calibration wrpll: gate calibration behind calibrate_wrpll_skew feature --- src/libboard_artiq/Cargo.toml.tpl | 1 + src/libboard_artiq/src/si549.rs | 139 +++++++++++++++++++++++++++++- src/satman/Cargo.toml.tpl | 1 + 3 files changed, 138 insertions(+), 3 deletions(-) diff --git a/src/libboard_artiq/Cargo.toml.tpl b/src/libboard_artiq/Cargo.toml.tpl index 9c1ad24..995fc05 100644 --- a/src/libboard_artiq/Cargo.toml.tpl +++ b/src/libboard_artiq/Cargo.toml.tpl @@ -10,6 +10,7 @@ name = "libboard_artiq" [features] target_zc706 = ["libboard_zynq/target_zc706", "libconfig/target_zc706"] target_kasli_soc = ["libboard_zynq/target_kasli_soc", "libconfig/target_kasli_soc"] +calibrate_wrpll_skew = [] [build-dependencies] build_zynq = { path = "../libbuild_zynq" } diff --git a/src/libboard_artiq/src/si549.rs b/src/libboard_artiq/src/si549.rs index 1723cd3..f92f547 100644 --- a/src/libboard_artiq/src/si549.rs +++ b/src/libboard_artiq/src/si549.rs @@ -355,6 +355,10 @@ pub mod wrpll { mod tag_collector { use super::*; + #[cfg(wrpll_ref_clk = "GT_CDR")] + static mut TAG_OFFSET: u32 = 19050; + #[cfg(wrpll_ref_clk = "SMA_CLKIN")] + static mut TAG_OFFSET: u32 = 0; static mut REF_TAG: u32 = 0; static mut REF_TAG_READY: bool = false; static mut MAIN_TAG: u32 = 0; @@ -392,6 +396,18 @@ pub mod wrpll { unsafe { REF_TAG_READY && MAIN_TAG_READY } } + #[cfg(feature = "calibrate_wrpll_skew")] + pub fn set_tag_offset(offset: u32) { + unsafe { + TAG_OFFSET = offset; + } + } + + #[cfg(feature = "calibrate_wrpll_skew")] + pub fn get_tag_offset() -> u32 { + unsafe { TAG_OFFSET } + } + pub fn get_period_error() -> i32 { // n * BEATING_PERIOD - REF_TAG(n) mod BEATING_PERIOD let mut period_error = unsafe { REF_TAG.overflowing_neg().0.rem_euclid(BEATING_PERIOD as u32) as i32 }; @@ -403,9 +419,13 @@ pub mod wrpll { } pub fn get_phase_error() -> i32 { - // MAIN_TAG(n) - REF_TAG(n) mod BEATING_PERIOD - let mut phase_error = - unsafe { MAIN_TAG.overflowing_sub(REF_TAG).0.rem_euclid(BEATING_PERIOD as u32) as i32 }; + // MAIN_TAG(n) - REF_TAG(n) - TAG_OFFSET mod BEATING_PERIOD + let mut phase_error = unsafe { + MAIN_TAG + .overflowing_sub(REF_TAG + TAG_OFFSET) + .0 + .rem_euclid(BEATING_PERIOD as u32) as i32 + }; // mapping tags from [0, 2π] -> [-π, π] if phase_error > BEATING_HALFPERIOD { @@ -520,6 +540,113 @@ pub mod wrpll { Ok(()) } + #[cfg(wrpll_ref_clk = "GT_CDR")] + fn test_skew(timer: &mut GlobalTimer) -> Result<(), &'static str> { + // wait for PLL to stabilize + timer.delay_us(20_000); + + info!("testing the skew of SYS CLK..."); + if has_timing_error(timer) { + return Err("the skew cannot satisfy setup/hold time constraint of RX synchronizer"); + } + info!("the skew of SYS CLK met the timing constraint"); + Ok(()) + } + + #[cfg(wrpll_ref_clk = "GT_CDR")] + fn has_timing_error(timer: &mut GlobalTimer) -> bool { + unsafe { + csr::wrpll_skewtester::error_write(1); + } + timer.delay_us(5_000); + unsafe { csr::wrpll_skewtester::error_read() == 1 } + } + + #[cfg(feature = "calibrate_wrpll_skew")] + fn find_edge(target: bool, timer: &mut GlobalTimer) -> Result { + const STEP: u32 = 8; + const STABLE_THRESHOLD: u32 = 10; + + enum FSM { + Init, + WaitEdge, + GotEdge, + } + + let mut state: FSM = FSM::Init; + let mut offset: u32 = tag_collector::get_tag_offset(); + let mut median_edge: u32 = 0; + let mut stable_counter: u32 = 0; + + for _ in 0..(BEATING_PERIOD as u32 / STEP) as usize { + tag_collector::set_tag_offset(offset); + offset += STEP; + // wait for PLL to stabilize + timer.delay_us(20_000); + + let error = has_timing_error(timer); + // A median edge deglitcher + match state { + FSM::Init => { + if error != target { + stable_counter += 1; + } else { + stable_counter = 0; + } + + if stable_counter >= STABLE_THRESHOLD { + state = FSM::WaitEdge; + stable_counter = 0; + } + } + FSM::WaitEdge => { + if error == target { + state = FSM::GotEdge; + median_edge = offset; + } + } + FSM::GotEdge => { + if error != target { + median_edge += STEP; + stable_counter = 0; + } else { + stable_counter += 1; + } + + if stable_counter >= STABLE_THRESHOLD { + return Ok(median_edge); + } + } + } + } + return Err("failed to find timing error edge"); + } + + #[cfg(feature = "calibrate_wrpll_skew")] + fn calibrate_skew(timer: &mut GlobalTimer) -> Result<(), &'static str> { + info!("calibrating skew to meet timing constraint..."); + + // clear calibrated value + tag_collector::set_tag_offset(0); + let rising = find_edge(true, timer)? as i32; + let falling = find_edge(false, timer)? as i32; + + let width = BEATING_PERIOD - (falling - rising); + let result = falling + width / 2; + tag_collector::set_tag_offset(result as u32); + + info!( + "calibration successful, error zone: {} -> {}, width: {} ({}deg), middle of working region: {}", + rising, + falling, + width, + 360 * width / BEATING_PERIOD, + result, + ); + + Ok(()) + } + pub fn select_recovered_clock(rc: bool, timer: &mut GlobalTimer) { set_isr(false); @@ -537,6 +664,12 @@ pub mod wrpll { // use nFIQ to avoid IRQ being disabled by mutex lock and mess up PLL set_isr(true); info!("WRPLL interrupt enabled"); + + #[cfg(feature = "calibrate_wrpll_skew")] + calibrate_skew(timer).expect("failed to set the correct skew"); + + #[cfg(wrpll_ref_clk = "GT_CDR")] + test_skew(timer).expect("skew test failed"); } } } diff --git a/src/satman/Cargo.toml.tpl b/src/satman/Cargo.toml.tpl index d9cff61..d4fa63c 100644 --- a/src/satman/Cargo.toml.tpl +++ b/src/satman/Cargo.toml.tpl @@ -7,6 +7,7 @@ build = "build.rs" [features] target_zc706 = ["libboard_zynq/target_zc706", "libsupport_zynq/target_zc706", "libconfig/target_zc706", "libboard_artiq/target_zc706"] target_kasli_soc = ["libboard_zynq/target_kasli_soc", "libsupport_zynq/target_kasli_soc", "libconfig/target_kasli_soc", "libboard_artiq/target_kasli_soc"] +calibrate_wrpll_skew = ["libboard_artiq/calibrate_wrpll_skew"] default = ["target_zc706", ] [build-dependencies] -- 2.42.0 From 14fa038118884f807c4680c128a12b0148ca7fd5 Mon Sep 17 00:00:00 2001 From: morgan Date: Mon, 11 Mar 2024 14:46:38 +0800 Subject: [PATCH 7/7] Firmware: Runtime WRPLL runtime: drive CLK_SEL to true when si549 is used runtime & libboard_artiq: allow standalone to use io_expander si549: add bit bang mmcm dynamic configuration si549: add frequency counter for refclk rtio_clocking & si549: add 125Mhz wrpll refclk setup --- src/libboard_artiq/src/lib.rs | 2 +- src/libboard_artiq/src/si549.rs | 141 ++++++++++++++++++++++++++ src/runtime/src/main.rs | 14 ++- src/runtime/src/rtio_clocking.rs | 166 +++++++++++++++++++++++++++++++ 4 files changed, 318 insertions(+), 5 deletions(-) diff --git a/src/libboard_artiq/src/lib.rs b/src/libboard_artiq/src/lib.rs index e9b9460..70c4cb6 100644 --- a/src/libboard_artiq/src/lib.rs +++ b/src/libboard_artiq/src/lib.rs @@ -22,7 +22,7 @@ pub mod drtioaux; pub mod drtioaux_async; pub mod drtioaux_proto; pub mod fiq; -#[cfg(all(feature = "target_kasli_soc", has_drtio))] +#[cfg(feature = "target_kasli_soc")] pub mod io_expander; pub mod logger; #[cfg(has_drtio)] diff --git a/src/libboard_artiq/src/si549.rs b/src/libboard_artiq/src/si549.rs index f92f547..05f269f 100644 --- a/src/libboard_artiq/src/si549.rs +++ b/src/libboard_artiq/src/si549.rs @@ -673,3 +673,144 @@ pub mod wrpll { } } } + +#[cfg(has_wrpll_refclk)] +pub mod wrpll_refclk { + use super::*; + + pub struct MmcmSetting { + pub clkout0_reg1: u16, //0x08 + pub clkout0_reg2: u16, //0x09 + pub clkfbout_reg1: u16, //0x14 + pub clkfbout_reg2: u16, //0x15 + pub div_reg: u16, //0x16 + pub lock_reg1: u16, //0x18 + pub lock_reg2: u16, //0x19 + pub lock_reg3: u16, //0x1A + pub power_reg: u16, //0x28 + pub filt_reg1: u16, //0x4E + pub filt_reg2: u16, //0x4F + } + + fn one_clock_cycle() { + unsafe { + csr::wrpll_refclk::mmcm_dclk_write(1); + csr::wrpll_refclk::mmcm_dclk_write(0); + } + } + + fn set_addr(address: u8) { + unsafe { + csr::wrpll_refclk::mmcm_daddr_write(address); + } + } + + fn set_data(value: u16) { + unsafe { + csr::wrpll_refclk::mmcm_din_write(value); + } + } + + fn set_enable(en: bool) { + unsafe { + let val = if en { 1 } else { 0 }; + csr::wrpll_refclk::mmcm_den_write(val); + } + } + + fn set_write_enable(en: bool) { + unsafe { + let val = if en { 1 } else { 0 }; + csr::wrpll_refclk::mmcm_dwen_write(val); + } + } + + fn get_data() -> u16 { + unsafe { csr::wrpll_refclk::mmcm_dout_read() } + } + + fn drp_ready() -> bool { + unsafe { csr::wrpll_refclk::mmcm_dready_read() == 1 } + } + + #[allow(dead_code)] + fn read(address: u8) -> u16 { + set_addr(address); + set_enable(true); + // Set DADDR on the mmcm and assert DEN for one clock cycle + one_clock_cycle(); + + set_enable(false); + while !drp_ready() { + // keep the clock signal until data is ready + one_clock_cycle(); + } + get_data() + } + + fn write(address: u8, value: u16) { + set_addr(address); + set_data(value); + set_write_enable(true); + set_enable(true); + // Set DADDR, DI on the mmcm and assert DWE, DEN for one clock cycle + one_clock_cycle(); + + set_write_enable(false); + set_enable(false); + while !drp_ready() { + // keep the clock signal until write is finished + one_clock_cycle(); + } + } + + fn reset(rst: bool) { + unsafe { + let val = if rst { 1 } else { 0 }; + csr::wrpll_refclk::mmcm_reset_write(val) + } + } + + pub fn setup(timer: &mut GlobalTimer, settings: MmcmSetting, mmcm_bypass: bool) -> Result<(), &'static str> { + unsafe { + csr::wrpll_refclk::refclk_reset_write(1); + } + + if mmcm_bypass { + info!("Bypassing mmcm"); + unsafe { + csr::wrpll_refclk::mmcm_bypass_write(1); + } + } else { + // Based on "DRP State Machine" from XAPP888 + // hold reset HIGH during mmcm config + reset(true); + write(0x08, settings.clkout0_reg1); + write(0x09, settings.clkout0_reg2); + write(0x14, settings.clkfbout_reg1); + write(0x15, settings.clkfbout_reg2); + write(0x16, settings.div_reg); + write(0x18, settings.lock_reg1); + write(0x19, settings.lock_reg2); + write(0x1A, settings.lock_reg3); + write(0x28, settings.power_reg); + write(0x4E, settings.filt_reg1); + write(0x4F, settings.filt_reg2); + reset(false); + + // wait for the mmcm to lock + timer.delay_us(100); + + let locked = unsafe { csr::wrpll_refclk::mmcm_locked_read() == 1 }; + if !locked { + return Err("mmcm failed to generate 125MHz ref clock from SMA CLKIN"); + } + } + + unsafe { + csr::wrpll_refclk::refclk_reset_write(0); + } + + Ok(()) + } +} diff --git a/src/runtime/src/main.rs b/src/runtime/src/main.rs index e8b71a8..dd24e25 100644 --- a/src/runtime/src/main.rs +++ b/src/runtime/src/main.rs @@ -8,14 +8,14 @@ #[macro_use] extern crate alloc; -#[cfg(all(feature = "target_kasli_soc", has_drtio))] +#[cfg(all(feature = "target_kasli_soc", has_virtual_leds))] use core::cell::RefCell; use ksupport; use libasync::task; #[cfg(has_drtio_eem)] use libboard_artiq::drtio_eem; -#[cfg(all(feature = "target_kasli_soc", has_drtio))] +#[cfg(feature = "target_kasli_soc")] use libboard_artiq::io_expander; use libboard_artiq::{identifier_read, logger, pl}; use libboard_zynq::{gic, mpcore, timer::GlobalTimer}; @@ -43,7 +43,7 @@ extern "C" { static __exceptions_start: u32; } -#[cfg(all(feature = "target_kasli_soc", has_drtio))] +#[cfg(all(feature = "target_kasli_soc", has_virtual_leds))] async fn io_expanders_service( i2c_bus: RefCell<&mut libboard_zynq::i2c::I2c>, io_expander0: RefCell, @@ -101,7 +101,7 @@ pub fn main_core0() { info!("gateware ident: {}", identifier_read(&mut [0; 64])); ksupport::i2c::init(); - #[cfg(all(feature = "target_kasli_soc", has_drtio))] + #[cfg(feature = "target_kasli_soc")] { let i2c_bus = unsafe { (ksupport::i2c::I2C_BUS).as_mut().unwrap() }; let mut io_expander0 = io_expander::IoExpander::new(i2c_bus, 0).unwrap(); @@ -112,6 +112,11 @@ pub fn main_core0() { io_expander1 .init(i2c_bus) .expect("I2C I/O expander #1 initialization failed"); + + // Drive CLK_SEL to true + #[cfg(has_si549)] + io_expander0.set(1, 7, true); + // Drive TX_DISABLE to false on SFP0..3 io_expander0.set(0, 1, false); io_expander1.set(0, 1, false); @@ -119,6 +124,7 @@ pub fn main_core0() { io_expander1.set(1, 1, false); io_expander0.service(i2c_bus).unwrap(); io_expander1.service(i2c_bus).unwrap(); + #[cfg(has_virtual_leds)] task::spawn(io_expanders_service( RefCell::new(i2c_bus), RefCell::new(io_expander0), diff --git a/src/runtime/src/rtio_clocking.rs b/src/runtime/src/rtio_clocking.rs index e6cf632..fc906c1 100644 --- a/src/runtime/src/rtio_clocking.rs +++ b/src/runtime/src/rtio_clocking.rs @@ -4,6 +4,8 @@ use ksupport::i2c; use libboard_artiq::pl; #[cfg(has_si5324)] use libboard_artiq::si5324; +#[cfg(has_si549)] +use libboard_artiq::si549; #[cfg(has_si5324)] use libboard_zynq::i2c::I2c; use libboard_zynq::timer::GlobalTimer; @@ -260,6 +262,150 @@ fn setup_si5324(i2c: &mut I2c, timer: &mut GlobalTimer, clk: RtioClock) { si5324::setup(i2c, &si5324_settings, si5324_ref_input, timer).expect("cannot initialize Si5324"); } +#[cfg(all(has_si549, has_wrpll))] +fn wrpll_setup(timer: &mut GlobalTimer, clk: RtioClock, si549_settings: &si549::FrequencySetting) { + // register values are directly copied from preconfigured mmcm + let (mmcm_setting, mmcm_bypass) = match clk { + RtioClock::Ext0_Synth0_10to125 => ( + si549::wrpll_refclk::MmcmSetting { + // CLKFBOUT_MULT = 62.5, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 5 + clkout0_reg1: 0x1083, + clkout0_reg2: 0x0080, + clkfbout_reg1: 0x179e, + clkfbout_reg2: 0x4c00, + div_reg: 0x1041, + lock_reg1: 0x00fa, + lock_reg2: 0x7c01, + lock_reg3: 0xffe9, + power_reg: 0x9900, + filt_reg1: 0x0808, + filt_reg2: 0x0800, + }, + false, + ), + RtioClock::Ext0_Synth0_80to125 => ( + si549::wrpll_refclk::MmcmSetting { + // CLKFBOUT_MULT = 15.625, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 10 + clkout0_reg1: 0x1145, + clkout0_reg2: 0x0000, + clkfbout_reg1: 0x11c7, + clkfbout_reg2: 0x5880, + div_reg: 0x1041, + lock_reg1: 0x028a, + lock_reg2: 0x7c01, + lock_reg3: 0xffe9, + power_reg: 0x9900, + filt_reg1: 0x0808, + filt_reg2: 0x9800, + }, + false, + ), + RtioClock::Ext0_Synth0_100to125 => ( + si549::wrpll_refclk::MmcmSetting { + // CLKFBOUT_MULT = 12.5, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 10 + clkout0_reg1: 0x1145, + clkout0_reg2: 0x0000, + clkfbout_reg1: 0x1145, + clkfbout_reg2: 0x4c00, + div_reg: 0x1041, + lock_reg1: 0x0339, + lock_reg2: 0x7c01, + lock_reg3: 0xffe9, + power_reg: 0x9900, + filt_reg1: 0x0808, + filt_reg2: 0x9800, + }, + false, + ), + RtioClock::Ext0_Synth0_125to125 => ( + si549::wrpll_refclk::MmcmSetting { + // CLKFBOUT_MULT = 10, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 10 + clkout0_reg1: 0x1145, + clkout0_reg2: 0x0000, + clkfbout_reg1: 0x1145, + clkfbout_reg2: 0x0000, + div_reg: 0x1041, + lock_reg1: 0x03e8, + lock_reg2: 0x7001, + lock_reg3: 0xf3e9, + power_reg: 0x0100, + filt_reg1: 0x0808, + filt_reg2: 0x1100, + }, + true, + ), + _ => unreachable!(), + }; + + si549::helper_setup(timer, &si549_settings).expect("cannot initialize helper Si549"); + si549::wrpll_refclk::setup(timer, mmcm_setting, mmcm_bypass).expect("cannot initialize ref clk for wrpll"); + si549::wrpll::select_recovered_clock(true, timer); +} + +#[cfg(has_si549)] +fn get_si549_setting(clk: RtioClock) -> si549::FrequencySetting { + match clk { + RtioClock::Ext0_Synth0_10to125 => { + info!("using 10MHz reference to make 125MHz RTIO clock with WRPLL"); + } + RtioClock::Ext0_Synth0_80to125 => { + info!("using 80MHz reference to make 125MHz RTIO clock with WRPLL"); + } + RtioClock::Ext0_Synth0_100to125 => { + info!("using 100MHz reference to make 125MHz RTIO clock with WRPLL"); + } + RtioClock::Ext0_Synth0_125to125 => { + info!("using 125MHz reference to make 125MHz RTIO clock with WRPLL"); + } + RtioClock::Int_100 => { + info!("using internal 100MHz RTIO clock"); + } + RtioClock::Int_125 => { + info!("using internal 125MHz RTIO clock"); + } + _ => { + warn!( + "rtio_clock setting '{:?}' is unsupported. Falling back to default internal 125MHz RTIO clock.", + clk + ); + } + }; + + match clk { + RtioClock::Int_100 => { + si549::FrequencySetting { + main: si549::DividerConfig { + hsdiv: 0x06C, + lsdiv: 0, + fbdiv: 0x046C5F49797, + }, + helper: si549::DividerConfig { + // 100MHz*32767/32768 + hsdiv: 0x06C, + lsdiv: 0, + fbdiv: 0x046C5670BBD, + }, + } + } + _ => { + // Everything else use 125MHz + si549::FrequencySetting { + main: si549::DividerConfig { + hsdiv: 0x058, + lsdiv: 0, + fbdiv: 0x04815791F25, + }, + helper: si549::DividerConfig { + // 125MHz*32767/32768 + hsdiv: 0x058, + lsdiv: 0, + fbdiv: 0x04814E8F442, + }, + } + } + } +} + pub fn init(timer: &mut GlobalTimer, cfg: &Config) { let clk = get_rtio_clock_cfg(cfg); #[cfg(has_si5324)] @@ -274,9 +420,29 @@ pub fn init(timer: &mut GlobalTimer, cfg: &Config) { } } + #[cfg(has_si549)] + let si549_settings = get_si549_setting(clk); + + #[cfg(has_si549)] + si549::main_setup(timer, &si549_settings).expect("cannot initialize main Si549"); + #[cfg(has_drtio)] init_drtio(timer); #[cfg(not(has_drtio))] init_rtio(timer); + + #[cfg(all(has_si549, has_wrpll))] + { + // SYS CLK switch will reset CSRs that are used by WRPLL + match clk { + RtioClock::Ext0_Synth0_10to125 + | RtioClock::Ext0_Synth0_80to125 + | RtioClock::Ext0_Synth0_100to125 + | RtioClock::Ext0_Synth0_125to125 => { + wrpll_setup(timer, clk, &si549_settings); + } + _ => {} + } + } } -- 2.42.0