From 153d9724025353f8d4696b4182b86d99bba9ec81 Mon Sep 17 00:00:00 2001 From: Peter Drmota <49479443+pmldrmota@users.noreply.github.com> Date: Thu, 18 Nov 2021 23:42:07 +0000 Subject: [PATCH 1/4] Move cd_le from SR to Urukul module --- urukul.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/urukul.py b/urukul.py index f7edde2..a31d2da 100644 --- a/urukul.py +++ b/urukul.py @@ -36,14 +36,6 @@ def __init__(self, width): sr = Signal(width) - self.clock_domains.cd_le = ClockDomain("le", reset_less=True) - # clock the latch domain from selection deassertion but only after - # there was a serial clock edge with asserted select (i.e. ignore - # glitches). - self.specials += Instance("FDPE", p_INIT=1, - i_D=0, i_C=ClockSignal("sck1"), i_CE=self.sel, i_PRE=~self.sel, - o_Q=self.cd_le.clk) - self.sync.sck0 += [ If(self.sel, self.sdo.eq(sr[-1]), @@ -53,7 +45,7 @@ def __init__(self, width): self.sync.sck1 += [ If(self.sel, sr[0].eq(self.sdi), - If(self.cd_le.clk, + If(ClockSignal("le"), sr[1:].eq(self.do[:-1]) ).Else( sr[1:].eq(sr[:-1]) @@ -413,6 +405,7 @@ def __init__(self, platform): self.clock_domains.cd_sys = ClockDomain("sys", reset_less=True) self.clock_domains.cd_sck0 = ClockDomain("sck0", reset_less=True) self.clock_domains.cd_sck1 = ClockDomain("sck1", reset_less=True) + self.clock_domains.cd_le = ClockDomain("le", reset_less=True) platform.add_period_constraint(eem[0]._pin, 8.) platform.add_period_constraint(eem[2]._pin, 8.) @@ -455,6 +448,13 @@ def __init__(self, platform): i_D=0, i_C=ClockSignal("sck1"), i_CE=sel[2], i_PRE=~sel[2], o_Q=att.le) + # clock the latch domain from selection deassertion but only after + # there was a serial clock edge with asserted select (i.e. ignore + # glitches). + self.specials += Instance("FDPE", p_INIT=1, + i_D=0, i_C=ClockSignal("sck1"), i_CE=~sel[0], i_PRE=sel[0], + o_Q=self.cd_le.clk) + self.comb += [ cfg.en_9910.eq(en_9910), cs.eq(Cat(eem[3].i, eem[4].i, ~en_nu & eem[5].i)), From 7ed4a035259c2a937ae4be2ae7ce2e4b7b5ed4c4 Mon Sep 17 00:00:00 2001 From: Peter Drmota Date: Thu, 18 Nov 2021 23:44:22 +0000 Subject: [PATCH 2/4] Add half-duplex SPI readback option inside of NU mode. Due to hardware constraints, the MISO line is unavailable in NU mode. Therefore, an optional half-duplex SPI protocol is implemented using an additional 8-bit shift register. --- urukul.py | 120 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 106 insertions(+), 14 deletions(-) diff --git a/urukul.py b/urukul.py index a31d2da..4bf9c75 100644 --- a/urukul.py +++ b/urukul.py @@ -2,7 +2,7 @@ # increment this if the behavior (LEDs, registers, EEM pins) changes -__proto_rev__ = 8 +__proto_rev__ = 9 class SR(Module): @@ -57,6 +57,49 @@ def __init__(self, width): ] +class SISO(Module): + """ + Serial-in serial-out shift register, SPI slave + + * CPOL = 0 (clock idle low) + * CPHA = 0 (sample on first edge, shift on second) + * SPI mode 0 + * samples SDI on rising clock edges (SCK1 domain) + * shifts out SDO on falling clock edges (SCK0 domain) + * FIFO + * LOAD: parallel data do is loaded into the shift + register at the first rising SCK1 edge. In this + mode, the first (MSB) at the output is undefined. + """ + def __init__(self, width): + self.sdi = Signal() + self.sdo = Signal() + self.sel = Signal() + + self.load = Signal() + self.do = Signal(width - 1) + + # # # + + sr = Signal(width) + + self.sync.sck0 += [ + If(self.sel, + self.sdo.eq(sr[-1]) + ) + ] + self.sync.sck1 += [ + If(self.sel, + sr[0].eq(self.sdi), + If(self.load & ClockSignal("le"), + sr[1:].eq(self.do), + ).Else( + sr[1:].eq(sr[:-1]), + ) + ) + ] + + class CFG(Module): """Configuration register @@ -70,7 +113,7 @@ class CFG(Module): | RF_SW | 4 | Activates RF switch per channel | | LED | 4 | Activates the red LED per channel | | PROFILE | 3 | Controls DDS[0:3].PROFILE[0:2] | - | DUMMY | 1 | Reserved (used in a previous revision) | + | EN_RB | 1 | Enable read-back of 8 bits in EN_NU mode | | IO_UPDATE | 1 | Asserts DDS[0:3].IO_UPDATE where CFG.MASK_NU | | | | is high | | MASK_NU | 4 | Disables DDS from QSPI interface, disables | @@ -94,7 +137,7 @@ def __init__(self, platform, n=4): ("profile", 3), - ("dummy", 1), + ("en_rb", 1), ("io_update", 1), ("mask_nu", 4), @@ -269,6 +312,33 @@ class Urukul(Module): See :class:`Urukul` and :class:`SR` + SPI readback inside of EN_NU + --- + + With EN_NU, MISO is unavailable. To perform read transactions in this mode, + half-duplex SPI mode can be enabled by setting the EN_RB bit in CFG. + Once set, the CPLD expects an alternating sequence of two SPI transactions: + * RB_STATE = 0: One "read" transaction of any length from any device + (CS=1-3). The return data's least significant bits will be + stored in the SISO shift register. + + * RB_STATE = 1: One read transaction in half-duplex SPI mode shifting + out data from the SISO over MOSI. Depending on the chip selected, + data can be loaded into the SISO at the first SPI clock edge in this + state, overwriting the data that was previously inserted. + + | CS | data returned from SISO with EN_RB in RB_STATE = 1 | + |-----+--------------------------------------------------------| + | 1 | LSBs of PROTO_REV | + | 2 | LSBs of PLL_LOCK | + | 3 | LSBs of data that would have been returned during | + | | RB_STATE = 0 via MISO if the CPLD had not been in | + | | EN_NU mode. | + + To end this protocol, the RB_EN flag can be reset during RB_STATE = 0. + + See :class:`SISO` and :class:`CFG` + CFG --- @@ -417,13 +487,15 @@ def __init__(self, platform): en_9910 = Signal() # AD9910 populated (instead of AD9912) en_nu = Signal() # NU-Servo operation with quad SPI en_eem1 = Signal() # EEM1 connected and sync outputs used + en_rb = Signal() # Enable readback over MOSI line self.comb += [ fsen.eq(1), en_9910.eq(ifc_mode[0] | variant), en_nu.eq(ifc_mode[1]), en_eem1.eq(ifc_mode[2]), - [eem[i].oe.eq(0) for i in range(12) if i not in (2, 10)], + [eem[i].oe.eq(0) for i in range(12) if i not in (1, 2, 10)], + eem[1].oe.eq(en_rb), eem[2].oe.eq(~en_nu), eem[10].oe.eq(~en_nu & en_eem1), eem[10].o.eq(eem[6].i), @@ -435,18 +507,21 @@ def __init__(self, platform): cfg = CFG(platform) stat = Status(platform) sr = SR(24) + siso = SISO(8) assert len(cfg.data) <= len(sr.di) assert len(stat.data) <= len(sr.do) - self.submodules += cfg, stat, sr + self.submodules += cfg, stat, sr, siso + rb_state = Signal(reset_less=True) sel = Signal(8) cs = Signal(3) miso = Signal(8) mosi = eem[1].i + att_le = Signal() self.specials += Instance("FDPE", p_INIT=1, i_D=0, i_C=ClockSignal("sck1"), i_CE=sel[2], i_PRE=~sel[2], - o_Q=att.le) + o_Q=att_le) # clock the latch domain from selection deassertion but only after # there was a serial clock edge with asserted select (i.e. ignore @@ -455,20 +530,37 @@ def __init__(self, platform): i_D=0, i_C=ClockSignal("sck1"), i_CE=~sel[0], i_PRE=sel[0], o_Q=self.cd_le.clk) + self.sync.le += [ + If(cfg.data.en_rb & en_nu, + rb_state.eq(~rb_state) + ).Else( + rb_state.eq(0) + ) + ] + self.comb += [ cfg.en_9910.eq(en_9910), cs.eq(Cat(eem[3].i, eem[4].i, ~en_nu & eem[5].i)), Array(sel)[cs].eq(1), # one-hot eem[2].o.eq(Array(miso)[cs]), - miso[3].eq(miso[4]), # for all-DDS take DDS0:MISO - att.clk.eq(sel[2] & self.cd_sck1.clk), - att.s_in.eq(mosi), - miso[2].eq(att.s_out), + miso[3].eq(Mux(~en_nu, miso[4], + Mux(cfg.data.mask_nu[3], miso[7], + Mux(cfg.data.mask_nu[2], miso[6], + Mux(cfg.data.mask_nu[1], miso[5], miso[4]))))), + + att.clk.eq(sel[2] & ~en_rb & self.cd_sck1.clk), + att.le.eq(att_le & ~en_rb), + sr.sel.eq(sel[1] & ~en_rb), + + siso.sel.eq(~sel[0]), + siso.do.eq(Mux(sel[2], stat.data.pll_lock, stat.data.proto_rev)), + siso.load.eq((sel[1] | sel[2]) & en_rb), + en_rb.eq(cfg.data.en_rb & ~sel[0] & rb_state), - sr.sel.eq(sel[1]), - sr.sdi.eq(mosi), - miso[1].eq(sr.sdo), + Cat(siso.sdi, eem[1].o).eq(Cat(eem[2].o, siso.sdo)), + Cat(att.s_in, miso[2]).eq(Cat(mosi, att.s_out)), + Cat(sr.sdi, miso[1]).eq(Cat(mosi, sr.sdo)), cfg.data.raw_bits().eq(sr.di), sr.do.eq(stat.data.raw_bits()), @@ -485,7 +577,7 @@ def __init__(self, platform): sel_spi = Signal() sel_nu = Signal() self.comb += [ - sel_spi.eq(sel[i + 4] | (sel[3] & cfg.data.mask_nu[i])), + sel_spi.eq(sel[i + 4] | (sel[3] & ~en_rb & cfg.data.mask_nu[i])), sel_nu.eq(en_nu & ~cfg.data.mask_nu[i]), ddsi.cs_n.eq(~Mux(sel_nu, eem[5].i, sel_spi)), ddsi.sck.eq(Mux(sel_nu, eem[2].i, self.cd_sck1.clk)), From e93a5ac29a4fc82046bb0ad21b399fc30ed18b6b Mon Sep 17 00:00:00 2001 From: drmota Date: Thu, 18 Nov 2021 23:20:23 +0000 Subject: [PATCH 3/4] Disconnect test pins This is necessary to fit the design into XC2C128-6-TQ144 --- urukul.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/urukul.py b/urukul.py index 4bf9c75..f666498 100644 --- a/urukul.py +++ b/urukul.py @@ -586,10 +586,3 @@ def __init__(self, platform): ddsi.io_update.eq(Mux(cfg.data.mask_nu[i], cfg.data.io_update, eem[6].i)), ] - - tp = [platform.request("tp", i) for i in range(3)] - self.comb += [ - tp[0].eq(dds[0].cs_n), - tp[1].eq(dds[0].sck), - tp[2].eq(dds[0].sdi) - ] From 1d6a6b0376117f2c65b49bdc2a692a5e590cb71f Mon Sep 17 00:00:00 2001 From: Peter Drmota <49479443+pmldrmota@users.noreply.github.com> Date: Wed, 17 Nov 2021 13:39:19 +0100 Subject: [PATCH 4/4] keep proto_rev = 8 Avoids incompatibility with current ARTIQ coredevice driver. --- urukul.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urukul.py b/urukul.py index f666498..105ee56 100644 --- a/urukul.py +++ b/urukul.py @@ -2,7 +2,7 @@ # increment this if the behavior (LEDs, registers, EEM pins) changes -__proto_rev__ = 9 +__proto_rev__ = 8 class SR(Module):