Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 71 additions & 47 deletions urukul.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


# increment this if the behavior (LEDs, registers, EEM pins) changes
__proto_rev__ = 8
__proto_rev__ = 9


class SR(Module):
Expand All @@ -24,6 +24,7 @@ class SR(Module):
* following at least one rising clock edge, on the deassertion of SEL,
the shift register is loaded into the parallel data register DI
"""

def __init__(self, width):
self.sdi = Signal()
self.sdo = Signal()
Expand All @@ -47,7 +48,6 @@ def __init__(self, width):
self.sync.sck0 += [
If(self.sel,
self.sdo.eq(sr[-1]),

)
]
self.sync.sck1 += [
Expand Down Expand Up @@ -75,16 +75,18 @@ class CFG(Module):

| Name | Width | Function |
|-----------+-------+-------------------------------------------------|
| 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) |
| IO_UPDATE | 1 | Asserts DDS[0:3].IO_UPDATE where CFG.MASK_NU |
| | | is high |
| RF_SW | 4 | Activates RF switch (per channel) |
| LED | 4 | Activates the red LED (per channel) |
| PROFILE | 4 * 3 | Controls DDS.PROFILE[0:2] (per channel) |
| OSK | 4 | Asserts DDS.OSK (per channel) |
| DRCTL | 4 | Asserts DDS.DRCTL (per channel) |
| DRHOLD | 4 | Asserts DDS.DRHOLD (per channel) |
| IO_UPDATE | 4 | Asserts DDS.IO_UPDATE (per channel), where |
| | | CFG.MASK_NU is high |
| MASK_NU | 4 | Disables DDS from QSPI interface, disables |
| | | IO_UPDATE control through IO_UPDATE EEM signal, |
| | | enables access through CS=3, enables control of |
| | | IO_UPDATE through CFG.IO_UPDATE |
| | | IO_UPDATE through CFG.IO_UPDATE[0:3] |
| CLK_SEL0 | 1 | Selects CLK source: 0 MMCX/OSC, 1 SMA |
| SYNC_SEL | 1 | Selects SYNC source |
| RST | 1 | Asserts DDS[0:3].RESET, DDS[0:3].MASTER_RESET, |
Expand All @@ -94,27 +96,29 @@ class CFG(Module):
| DIV | 2 | Clock divider configuration: 0: default, |
| | | 1: divide-by-one, 2: divider-by-two, |
| | | 3: divide-by-four |
| ATT_EN | 4 | Enable ATT (per channel) |
"""
def __init__(self, platform, n=4):
self.data = Record([
("rf_sw", n),
("led", n),

("profile", 3),

("dummy", 1),
("io_update", 1),

("mask_nu", 4),

("clk_sel0", 1),
("sync_sel", 1),

("rst", 1),
("io_rst", 1),
("clk_sel1", 1),
("div", 2),
])
def __init__(self, platform, n=4):
self.data = Record(
[
("rf_sw", n),
("led", n),
("profile", 3 * n),
("osk", n),
("drctl", n),
("drhold", n),
("io_update", n),
("mask_nu", 4),
("clk_sel0", 1),
("sync_sel", 1),
("rst", 1),
("io_rst", 1),
("clk_sel1", 1),
("div", 2),
("att_en", n),
]
)
dds_common = platform.lookup_request("dds_common")
dds_sync = platform.lookup_request("dds_sync")
att = platform.lookup_request("att")
Expand All @@ -140,10 +144,10 @@ def __init__(self, platform, n=4):
dds.led[0].eq(dds.rf_sw), # green
dds.led[1].eq(self.data.led[i] | (self.en_9910 & (
dds.smp_err | ~dds.pll_lock))), # red
dds.profile.eq(self.data.profile),
dds.osk.eq(1),
dds.drhold.eq(0),
dds.drctl.eq(0),
dds.profile.eq(self.data.profile[3 * i : 3 * i + 3]),
dds.osk.eq(self.data.osk[i]),
dds.drhold.eq(self.data.drhold[i]),
dds.drctl.eq(self.data.drctl[i]),
]


Expand All @@ -158,21 +162,24 @@ class Status(Module):
|-----------+-------+-------------------------------------------|
| RF_SW | 4 | Actual RF switch and green LED activation |
| | | (including that by EEM1.SW[0:3]) |
| SMP_ERR | 4 | DDS[0:3].SMP_ERR |
| PLL_LOCK | 4 | DDS[0:3].PLL_LOCK |
| SMP_ERR | 4 | DDS.SMP_ERR per channel |
| PLL_LOCK | 4 | DDS.PLL_LOCK per channel |
| IFC_MODE | 4 | IFC_MODE[0:3] |
| PROTO_REV | 7 | Protocol revision (see __proto_rev__) |
| DUMMY | 1 | Not used, not usable, undefined |
| DROVER | 4 | DDS.DROVER per channel |
"""

def __init__(self, platform, n=4):
self.data = Record([
("rf_sw", n),
("smp_err", n),
("pll_lock", n),
("ifc_mode", 4),
("proto_rev", 7),
("dummy", 1)
])
self.data = Record(
[
("rf_sw", n),
("smp_err", n),
("pll_lock", n),
("ifc_mode", 4),
("proto_rev", 7),
("drover", n),
]
)
self.comb += [
self.data.ifc_mode.eq(platform.lookup_request("ifc_mode")),
self.data.proto_rev.eq(__proto_rev__),
Expand All @@ -184,6 +191,7 @@ def __init__(self, platform, n=4):
self.data.rf_sw[i].eq(dds.rf_sw),
self.data.smp_err[i].eq(dds.smp_err),
self.data.pll_lock[i].eq(dds.pll_lock),
self.data.drover[i].eq(dds.drover),
]


Expand Down Expand Up @@ -389,6 +397,7 @@ class Urukul(Module):
The test points expose miscellaneous signals for debugging and are not part
of the protocol revision.
"""

def __init__(self, platform):
clk = platform.request("clk")
dds_sync = platform.request("dds_sync")
Expand Down Expand Up @@ -445,7 +454,7 @@ def __init__(self, platform):

cfg = CFG(platform)
stat = Status(platform)
sr = SR(24)
sr = SR(52)
assert len(cfg.data) <= len(sr.di)
assert len(stat.data) <= len(sr.do)
self.submodules += cfg, stat, sr
Expand All @@ -456,12 +465,27 @@ def __init__(self, platform):
mosi = eem[1].i

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[i]) for i in range(4)]
i_D=0, i_C=ClockSignal("sck1"), i_CE=sel[2],
i_PRE=~(sel[2] & cfg.data.att_en[i]), o_Q=att.le[i]) for i in range(4)]

self.comb += [
cfg.en_9910.eq(en_9910),
# Important note regarding the chip-select (CS) signals (`eem[3].i`, `eem[4].i`, `eem[5].i`):
# These CS signals are generated by the Kasli SPI controller and used to select specific Urukul SPI targets.
# Some of these SPI targets rely on the CS signals to drive asynchronous inputs, meaning improper handling
# could introduce glitches.
#
# We are reasonably confident that the following combinatorial equation does not introduce glitches because:
#
# * The Kasli SPI controller drives the CS signals from DFFs on the same clock, ensuring synchronization.
# * The `en_nu` signal, controlled by a DIP switch, is a constant. As a result, `~en_nu & eem[5].i`
# should not create differences in gate propagation delays.
#
# NOTE: As long as the combinatorial logic below remains well-formed and does not introduce unintended
# asynchronous behavior, the signals should remain glitch-free. However, modifications to this logic
# should be made with caution to avoid potential hazards or race conditions that could lead to glitches.
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
Expand Down Expand Up @@ -493,7 +517,7 @@ def __init__(self, platform):
ddsi.sdi.eq(Mux(sel_nu, eem[i + 8].i, mosi)),
miso[i + 4].eq(ddsi.sdo),
ddsi.io_update.eq(Mux(cfg.data.mask_nu[i],
cfg.data.io_update, eem[6].i)),
cfg.data.io_update[i], eem[6].i)),
ddsi.reset.eq(cfg.data.rst | (~en_9910 & eem[7].i)),
]

Expand Down