Merge branch 'master' into new-py2llvm

This commit is contained in:
whitequark 2015-07-29 12:52:19 +03:00
commit fd46d8b11e
24 changed files with 857 additions and 104 deletions

View File

@ -177,7 +177,7 @@ class TTLInOut:
self._set_sensitivity(0)
@kernel
def gate_both_mu(self, duration):
def gate_both(self, duration):
"""Register both rising and falling edge events for the specified
duration (in seconds)."""
self._set_sensitivity(3)
@ -196,8 +196,8 @@ class TTLInOut:
@kernel
def timestamp_mu(self):
"""Poll the RTIO input and returns an event timestamp, according to
the gating.
"""Poll the RTIO input and returns an event timestamp (in machine
units), according to the gating.
If the gate is permanently closed, returns a negative value.
"""

View File

@ -118,8 +118,10 @@ class _OutputManager(Module):
sequence_error = Signal()
nop = Signal()
self.sync.rsys += [
replace.eq(self.ev.timestamp == buf.timestamp[fine_ts_width:]),
sequence_error.eq(self.ev.timestamp < buf.timestamp[fine_ts_width:])
replace.eq(self.ev.timestamp[fine_ts_width:] \
== buf.timestamp[fine_ts_width:]),
sequence_error.eq(self.ev.timestamp[fine_ts_width:] \
< buf.timestamp[fine_ts_width:])
]
if interface.suppress_nop:
# disable NOP at reset: do not suppress a first write with all 0s
@ -300,8 +302,7 @@ class _KernelCSRs(AutoCSR):
class RTIO(Module):
def __init__(self, channels, clk_freq, full_ts_width=63,
guard_io_cycles=20):
def __init__(self, channels, full_ts_width=63, guard_io_cycles=20):
data_width = max(rtlink.get_data_width(c.interface)
for c in channels)
address_width = max(rtlink.get_address_width(c.interface)
@ -329,11 +330,15 @@ class RTIO(Module):
self.cd_rsys.rst.eq(self.kcsrs.reset.storage)
]
self.comb += self.cd_rio.clk.eq(ClockSignal("rtio"))
self.specials += AsyncResetSynchronizer(self.cd_rio,
self.kcsrs.reset.storage)
self.specials += AsyncResetSynchronizer(
self.cd_rio,
self.kcsrs.reset.storage | ResetSignal("rtio",
allow_reset_less=True))
self.comb += self.cd_rio_phy.clk.eq(ClockSignal("rtio"))
self.specials += AsyncResetSynchronizer(self.cd_rio_phy,
self.kcsrs.reset_phy.storage)
self.specials += AsyncResetSynchronizer(
self.cd_rio_phy,
self.kcsrs.reset_phy.storage | ResetSignal("rtio",
allow_reset_less=True))
# Managers
self.submodules.counter = _RTIOCounter(full_ts_width - fine_ts_width)

View File

@ -0,0 +1,70 @@
from migen.fhdl.std import *
from artiq.gateware.rtio.phy import ttl_serdes_generic
class _OSERDESE2_8X(Module):
def __init__(self, pad):
self.o = Signal(8)
self.t_in = Signal()
self.t_out = Signal()
# # #
o = self.o
self.specials += Instance("OSERDESE2",
p_DATA_RATE_OQ="DDR", p_DATA_RATE_TQ="BUF",
p_DATA_WIDTH=8, p_TRISTATE_WIDTH=1,
o_OQ=pad, o_TQ=self.t_out,
i_CLK=ClockSignal("rtiox4"),
i_CLKDIV=ClockSignal("rio_phy"),
i_D1=o[0], i_D2=o[1], i_D3=o[2], i_D4=o[3],
i_D5=o[4], i_D6=o[5], i_D7=o[6], i_D8=o[7],
i_TCE=1, i_OCE=1, i_RST=0,
i_T1=self.t_in)
class _IOSERDESE2_8X(Module):
def __init__(self, pad):
self.o = Signal(8)
self.i = Signal(8)
self.oe = Signal()
# # #
pad_i = Signal()
pad_o = Signal()
i = self.i
self.specials += Instance("ISERDESE2", p_DATA_RATE="DDR",
p_DATA_WIDTH=8,
p_INTERFACE_TYPE="NETWORKING", p_NUM_CE=1,
o_Q1=i[7], o_Q2=i[6], o_Q3=i[5], o_Q4=i[4],
o_Q5=i[3], o_Q6=i[2], o_Q7=i[1], o_Q8=i[0],
i_D=pad_i,
i_CLK=ClockSignal("rtiox4"),
i_CLKB=~ClockSignal("rtiox4"),
i_CE1=1, i_RST=0,
i_CLKDIV=ClockSignal("rio_phy"))
oserdes = _OSERDESE2_8X(pad_o)
self.submodules += oserdes
self.specials += Instance("IOBUF",
i_I=pad_o, o_O=pad_i, i_T=oserdes.t_out,
io_IO=pad)
self.comb += [
oserdes.t_in.eq(~self.oe),
oserdes.o.eq(self.o)
]
class Output_8X(ttl_serdes_generic.Output):
def __init__(self, pad):
serdes = _OSERDESE2_8X(pad)
self.submodules += serdes
ttl_serdes_generic.Output.__init__(self, serdes)
class Inout_8X(ttl_serdes_generic.Inout):
def __init__(self, pad):
serdes = _IOSERDESE2_8X(pad)
self.submodules += serdes
ttl_serdes_generic.Inout.__init__(self, serdes)

View File

@ -0,0 +1,273 @@
from migen.fhdl.std import *
from migen.genlib.coding import PriorityEncoder
from artiq.gateware.rtio import rtlink
def _mk_edges(w, direction):
l = [(1 << i) - 1 for i in range(w)]
if direction == "rising":
l = [2**w - 1 ^ x for x in l]
elif direction == "falling":
pass
else:
raise ValueError
return l
class _SerdesDriver(Module):
def __init__(self, serdes_o, stb, data, fine_ts, override_en, override_o):
previous_data = Signal()
serdes_width = flen(serdes_o)
edges = Array(_mk_edges(serdes_width, "rising"))
edges_n = Array(_mk_edges(serdes_width, "falling"))
self.sync.rio_phy += [
If(stb, previous_data.eq(data)),
If(override_en,
serdes_o.eq(Replicate(override_o, serdes_width))
).Else(
If(stb & ~previous_data & data,
serdes_o.eq(edges[fine_ts]),
).Elif(stb & previous_data & ~data,
serdes_o.eq(edges_n[fine_ts]),
).Else(
serdes_o.eq(Replicate(previous_data, serdes_width)),
)
)
]
class Output(Module):
def __init__(self, serdes):
self.rtlink = rtlink.Interface(
rtlink.OInterface(1, fine_ts_width=log2_int(flen(serdes.o))))
self.probes = [serdes.o[-1]]
override_en = Signal()
override_o = Signal()
self.overrides = [override_en, override_o]
# # #
self.submodules += _SerdesDriver(
serdes.o,
self.rtlink.o.stb, self.rtlink.o.data, self.rtlink.o.fine_ts,
override_en, override_o)
class Inout(Module):
def __init__(self, serdes):
serdes_width = flen(serdes.o)
assert flen(serdes.i) == serdes_width
self.rtlink = rtlink.Interface(
rtlink.OInterface(2, 2, fine_ts_width=log2_int(serdes_width)),
rtlink.IInterface(1, fine_ts_width=log2_int(serdes_width)))
self.probes = [serdes.i[-1], serdes.oe]
override_en = Signal()
override_o = Signal()
override_oe = Signal()
self.overrides = [override_en, override_o, override_oe]
# # #
# Output
self.submodules += _SerdesDriver(
serdes_o=serdes.o,
stb=self.rtlink.o.stb & (self.rtlink.o.address == 0),
data=self.rtlink.o.data[0],
fine_ts=self.rtlink.o.fine_ts,
override_en=override_en, override_o=override_o)
oe_k = Signal()
self.sync.rio_phy += [
If(self.rtlink.o.stb & (self.rtlink.o.address == 1),
oe_k.eq(self.rtlink.o.data[0])),
If(override_en,
serdes.oe.eq(override_oe)
).Else(
serdes.oe.eq(oe_k)
)
]
# Input
sensitivity = Signal(2)
self.sync.rio += If(self.rtlink.o.stb & (self.rtlink.o.address == 2),
sensitivity.eq(self.rtlink.o.data))
i = serdes.i[-1]
i_d = Signal()
self.sync.rio_phy += [
i_d.eq(i),
self.rtlink.i.stb.eq(
(sensitivity[0] & ( i & ~i_d)) |
(sensitivity[1] & (~i & i_d))
),
self.rtlink.i.data.eq(i),
]
pe = PriorityEncoder(serdes_width)
self.submodules += pe
self.comb += pe.i.eq(serdes.i ^ Replicate(i_d, serdes_width))
self.sync.rio_phy += self.rtlink.i.fine_ts.eq(pe.o)
class _FakeSerdes(Module):
def __init__(self):
self.o = Signal(8)
self.i = Signal(8)
self.oe = Signal()
class _OutputTB(Module):
def __init__(self):
serdes = _FakeSerdes()
self.submodules.dut = RenameClockDomains(Output(serdes),
{"rio_phy": "sys"})
def gen_simulation(self, selfp):
selfp.dut.rtlink.o.data = 1
selfp.dut.rtlink.o.fine_ts = 1
selfp.dut.rtlink.o.stb = 1
yield
selfp.dut.rtlink.o.stb = 0
yield
selfp.dut.rtlink.o.data = 0
selfp.dut.rtlink.o.fine_ts = 2
selfp.dut.rtlink.o.stb = 1
yield
yield
selfp.dut.rtlink.o.data = 1
selfp.dut.rtlink.o.fine_ts = 7
selfp.dut.rtlink.o.stb = 1
for _ in range(6):
# note that stb stays active; output should not change
yield
class _InoutTB(Module):
def __init__(self):
self.serdes = _FakeSerdes()
self.submodules.dut = RenameClockDomains(Inout(self.serdes),
{"rio_phy": "sys",
"rio": "sys"})
def check_input(self, selfp, stb, fine_ts=None):
if stb != selfp.dut.rtlink.i.stb:
print("KO rtlink.i.stb should be {} but is {}"
.format(stb, selfp.dut.rtlink.i.stb))
elif fine_ts is not None and fine_ts != selfp.dut.rtlink.i.fine_ts:
print("KO rtlink.i.fine_ts should be {} but is {}"
.format(fine_ts, selfp.dut.rtlink.i.fine_ts))
else:
print("OK")
def check_output(self, selfp, data):
if selfp.serdes.o != data:
print("KO io.o should be {} but is {}".format(data, selfp.serdes.o))
else:
print("OK")
def check_output_enable(self, selfp, oe):
if selfp.serdes.oe != oe:
print("KO io.oe should be {} but is {}".format(oe, selfp.serdes.oe))
else:
print("OK")
def gen_simulation(self, selfp):
selfp.dut.rtlink.o.address = 2
selfp.dut.rtlink.o.data = 0b11
selfp.dut.rtlink.o.stb = 1 # set sensitivity to rising + falling
yield
selfp.dut.rtlink.o.stb = 0
self.check_output_enable(selfp, 0)
yield
selfp.serdes.i = 0b11111110 # rising edge at fine_ts = 1
yield
selfp.serdes.i = 0b11111111
yield
self.check_input(selfp, stb=1, fine_ts=1)
selfp.serdes.i = 0b01111111 # falling edge at fine_ts = 7
yield
selfp.serdes.i = 0b00000000
yield
self.check_input(selfp, stb=1, fine_ts=7)
selfp.serdes.i = 0b11000000 # rising edge at fine_ts = 6
yield
selfp.serdes.i = 0b11111111
yield
self.check_input(selfp, stb=1, fine_ts=6)
selfp.dut.rtlink.o.address = 2
selfp.dut.rtlink.o.data = 0b11
selfp.dut.rtlink.o.stb = 1 # set sensitivity to rising only
yield
selfp.dut.rtlink.o.stb = 0
yield
selfp.serdes.i = 0b00001111 # falling edge at fine_ts = 4
yield
self.check_input(selfp, stb=0) # no strobe, sensitivity is rising edge
selfp.serdes.i = 0b11110000 # rising edge at fine_ts = 4
yield
self.check_input(selfp, stb=1, fine_ts=4)
selfp.dut.rtlink.o.address = 1
selfp.dut.rtlink.o.data = 1
selfp.dut.rtlink.o.stb = 1 # set Output Enable to 1
yield
selfp.dut.rtlink.o.stb = 0
yield
yield
self.check_output_enable(selfp, 1)
selfp.dut.rtlink.o.address = 0
selfp.dut.rtlink.o.data = 1
selfp.dut.rtlink.o.fine_ts = 3
selfp.dut.rtlink.o.stb = 1 # rising edge at fine_ts = 3
yield
selfp.dut.rtlink.o.stb = 0
yield
self.check_output(selfp, data=0b11111000)
yield
self.check_output(selfp, data=0xFF) # stays at 1
selfp.dut.rtlink.o.data = 0
selfp.dut.rtlink.o.fine_ts = 0
selfp.dut.rtlink.o.stb = 1 # falling edge at fine_ts = 0
yield
selfp.dut.rtlink.o.stb = 0
yield
self.check_output(selfp, data=0)
yield
self.check_output(selfp, data=0)
selfp.dut.rtlink.o.data = 1
selfp.dut.rtlink.o.fine_ts = 7
selfp.dut.rtlink.o.stb = 1 # rising edge at fine_ts = 7
yield
selfp.dut.rtlink.o.stb = 0
yield
self.check_output(selfp, data=0b10000000)
if __name__ == "__main__":
import sys
from migen.sim.generic import Simulator, TopLevel
if len(sys.argv) != 2:
print("Incorrect command line")
sys.exit(1)
cls = {
"output": _OutputTB,
"inout": _InoutTB
}[sys.argv[1]]
with Simulator(cls(), TopLevel("top.vcd", clk_period=int(1/0.125))) as s:
s.run()

View File

@ -0,0 +1,154 @@
from migen.fhdl.std import *
from artiq.gateware.rtio.phy import ttl_serdes_generic
class _OSERDES2_8X(Module):
def __init__(self, pad, stb):
self.o = Signal(8)
self.t_in = Signal()
self.t_out = Signal()
# # #
cascade = Signal(4)
o = self.o
common = dict(p_DATA_RATE_OQ="SDR", p_DATA_RATE_OT="SDR",
p_DATA_WIDTH=8, p_OUTPUT_MODE="SINGLE_ENDED", i_TRAIN=0,
i_CLK0=ClockSignal("rtiox8"), i_CLK1=0,
i_CLKDIV=ClockSignal("rio_phy"),
i_IOCE=stb, i_OCE=1, i_TCE=1, i_RST=0,
i_T4=self.t_in, i_T3=self.t_in,
i_T2=self.t_in, i_T1=self.t_in)
self.specials += [
Instance("OSERDES2", p_SERDES_MODE="MASTER",
i_D4=o[7], i_D3=o[6], i_D2=o[5], i_D1=o[4],
i_SHIFTIN1=1, i_SHIFTIN2=1,
i_SHIFTIN3=cascade[2], i_SHIFTIN4=cascade[3],
o_SHIFTOUT1=cascade[0], o_SHIFTOUT2=cascade[1],
o_OQ=pad, o_TQ=self.t_out, **common),
Instance("OSERDES2", p_SERDES_MODE="SLAVE",
i_D4=o[3], i_D3=o[2], i_D2=o[1], i_D1=o[0],
i_SHIFTIN1=cascade[0], i_SHIFTIN2=cascade[1],
i_SHIFTIN3=1, i_SHIFTIN4=1,
o_SHIFTOUT3=cascade[2], o_SHIFTOUT4=cascade[3],
**common),
]
class _IOSERDES2_8X(Module):
def __init__(self, pad, stb):
self.o = Signal(8)
self.i = Signal(8)
self.oe = Signal()
# # #
pad_i = Signal()
pad_o = Signal()
cascade = Signal()
i = self.i
common = dict(p_BITSLIP_ENABLE="FALSE", p_DATA_RATE="SDR",
p_DATA_WIDTH=8, p_INTERFACE_TYPE="RETIMED",
i_BITSLIP=0, i_CE0=1, i_IOCE=stb,
i_RST=0, i_CLK0=ClockSignal("rtiox8"), i_CLK1=0,
i_CLKDIV=ClockSignal("rio_phy"))
self.specials += [
Instance("ISERDES2", p_SERDES_MODE="MASTER",
o_Q4=i[7], o_Q3=i[6], o_Q2=i[5], o_Q1=i[4],
o_SHIFTOUT=cascade, i_D=pad_i, i_SHIFTIN=0,
**common),
Instance("ISERDES2", p_SERDES_MODE="SLAVE",
o_Q4=i[3], o_Q3=i[2], o_Q2=i[1], o_Q1=i[0],
i_D=0, i_SHIFTIN=cascade, **common),
]
oserdes = _OSERDES2_8X(pad_o, stb)
self.submodules += oserdes
self.specials += Instance("IOBUF",
i_I=pad_o, o_O=pad_i, i_T=oserdes.t_out,
io_IO=pad)
self.comb += [
oserdes.t_in.eq(~self.oe),
oserdes.o.eq(self.o),
]
class Output_8X(ttl_serdes_generic.Output):
def __init__(self, pad, stb):
serdes = _OSERDES2_8X(pad, stb)
self.submodules += serdes
ttl_serdes_generic.Output.__init__(self, serdes)
class Inout_8X(ttl_serdes_generic.Inout):
def __init__(self, pad, stb):
serdes = _IOSERDES2_8X(pad, stb)
self.submodules += serdes
ttl_serdes_generic.Inout.__init__(self, serdes)
class _OSERDES2_4X(Module):
def __init__(self, pad, stb):
self.o = Signal(4)
self.t_in = Signal()
self.t_out = Signal()
# # #
o = self.o
self.specials += Instance("OSERDES2", p_SERDES_MODE="NONE",
p_DATA_RATE_OQ="SDR", p_DATA_RATE_OT="SDR",
p_DATA_WIDTH=4, p_OUTPUT_MODE="SINGLE_ENDED",
i_TRAIN=0, i_CLK0=ClockSignal("rtiox4"),
i_CLK1=0, i_CLKDIV=ClockSignal("rio_phy"),
i_IOCE=stb, i_OCE=1, i_TCE=1, i_RST=0,
i_T4=self.t_in, i_T3=self.t_in,
i_T2=self.t_in, i_T1=self.t_in,
i_D4=o[3], i_D3=o[2], i_D2=o[1], i_D1=o[0],
o_OQ=pad, o_TQ=self.t_out)
class _IOSERDES2_4X(Module):
def __init__(self, pad, stb):
self.o = Signal(4)
self.i = Signal(4)
self.oe = Signal()
# # #
pad_i = Signal()
pad_o = Signal()
i = self.i
self.specials += Instance("ISERDES2", p_SERDES_MODE="NONE",
p_BITSLIP_ENABLE="FALSE", p_DATA_RATE="SDR",
p_DATA_WIDTH=4, p_INTERFACE_TYPE="RETIMED",
i_BITSLIP=0, i_CE0=1, i_IOCE=stb,
i_RST=0, i_CLK0=ClockSignal("rtiox4"),
i_CLK1=0, i_CLKDIV=ClockSignal("rio_phy"),
o_Q4=i[3], o_Q3=i[2], o_Q2=i[1], o_Q1=i[0],
i_D=pad_i, i_SHIFTIN=0)
oserdes = _OSERDES2_4X(pad_o, stb)
self.submodules += oserdes
self.specials += Instance("IOBUF",
i_I=pad_o, o_O=pad_i, i_T=oserdes.t_out,
io_IO=pad)
self.comb += [
oserdes.t_in.eq(~self.oe),
oserdes.o.eq(self.o),
]
class Output_4X(ttl_serdes_generic.Output):
def __init__(self, pad, stb):
serdes = _OSERDES2_4X(pad, stb)
self.submodules += serdes
ttl_serdes_generic.Output.__init__(self, serdes)
class Inout_4X(ttl_serdes_generic.Inout):
def __init__(self, pad, stb):
serdes = _IOSERDES2_4X(pad, stb)
self.submodules += serdes
ttl_serdes_generic.Inout.__init__(self, serdes)

View File

@ -120,7 +120,7 @@ class LoopbackCount(EnvExperiment):
@kernel
def run(self):
self.ttl_inout.output()
delay(1*us)
delay(5*us)
with parallel:
self.ttl_inout.gate_rising(10*us)
with sequential:

View File

@ -147,7 +147,7 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC
::
$ cd ~/artiq-dev
$ svn co https://xc3sprog.svn.sourceforge.net/svnroot/xc3sprog/trunk xc3sprog
$ svn co http://svn.code.sf.net/p/xc3sprog/code/trunk xc3sprog
$ cd xc3sprog
$ cmake . && make
$ sudo make install
@ -299,16 +299,14 @@ Installing the host-side software
* Install the llvmlite Python bindings: ::
$ cd ~/artiq-dev
$ git clone https://github.com/numba/llvmlite
$ git clone https://github.com/m-labs/llvmlite
$ git checkout backport-3.5
$ cd llvmlite
$ patch -p1 < ~/artiq-dev/artiq/misc/llvmlite-add-all-targets.patch
$ patch -p1 < ~/artiq-dev/artiq/misc/llvmlite-rename.patch
$ patch -p1 < ~/artiq-dev/artiq/misc/llvmlite-build-as-debug-on-windows.patch
$ LLVM_CONFIG=/usr/local/llvm-or1k/bin/llvm-config python3 setup.py install --user
.. note::
llvmlite is in development and its API is not stable yet. Commit ID ``11a8303d02e3d6dd2d1e0e9065701795cd8a979f`` is known to work.
* Install ARTIQ: ::
$ cd ~/artiq-dev

View File

@ -9,7 +9,7 @@
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {}
"arguments": {"ref_period": 1e-9}
},
"pmt0": {
@ -133,6 +133,9 @@
"command": "lda_controller -p {port} --bind {bind}"
},
"ttl_inout": "pmt0",
"ttl_out": "ttl0",
"pmt": "pmt0",
"bd_dds": "dds0",
"bd_sw": "ttl0",

View File

@ -0,0 +1,71 @@
from artiq import *
class PulseNotReceivedError(Exception):
pass
class TDR(EnvExperiment):
"""Time domain reflectometer.
From ttl2 an impedance matched pulse is send onto a coax
cable with an open end. pmt0 (very short stub, high impedance) also
listens on the transmission line near ttl2.
When the forward propagating pulse passes pmt0, the voltage is half of the
logic voltage and does not register as a rising edge. Once the
rising edge is reflected at an open end (same sign) and passes by pmt0 on
its way back to ttl2, it is detected. Analogously, hysteresis leads to
detection of the falling edge once the reflection reaches pmt0 after
one round trip time.
This works marginally and is just a proof of principle: it relies on
hysteresis at FPGA inputs around half voltage and good impedance steps,
as well as reasonably low loss cable. It does not work well for longer
cables (>100 ns RTT). The default drive strength of 12 mA and 3.3 V would
be ~300 Ω but it seems 40 Ω series impedance at the output matches
the hysteresis of the input.
This is also equivalent to a loopback tester or a delay measurement.
"""
def build(self):
self.attr_device("core")
self.attr_device("pmt0")
self.attr_device("ttl2")
def run(self):
n = 1000 # repetitions
latency = 50e-9 # calibrated latency without a transmission line
pulse = 1e-6 # pulse length, larger than rtt
try:
self.many(n, seconds_to_mu(pulse, self.core))
except PulseNotReceivedError:
print("to few edges: cable too long or wiring bad")
else:
print(self.t)
t_rise = mu_to_seconds(self.t[0], self.core)/n - latency
t_fall = mu_to_seconds(self.t[1], self.core)/n - latency - pulse
print("round trip times:")
print("rising: {:5g} ns, falling {:5g} ns".format(
t_rise/1e-9, t_fall/1e-9))
@kernel
def many(self, n, p):
t = [0 for i in range(2)]
self.core.break_realtime()
for i in range(n):
self.one(t, p)
self.t = t
@kernel
def one(self, t, p):
t0 = now_mu()
with parallel:
self.pmt0.gate_both_mu(2*p)
self.ttl2.pulse_mu(p)
for i in range(len(t)):
ti = self.pmt0.timestamp_mu()
if ti <= 0:
raise PulseNotReceivedError
t[i] += ti - t0
self.pmt0.count() # flush

View File

@ -1,6 +1,6 @@
include $(MSCDIR)/software/common.mak
OBJECTS := isr.o flash_storage.o clock.o elf_loader.o services.o session.o log.o test_mode.o kloader.o bridge_ctl.o mailbox.o ksupport_data.o kserver.o moninj.o main.o
OBJECTS := isr.o flash_storage.o clock.o rtiocrg.o elf_loader.o services.o session.o log.o test_mode.o kloader.o bridge_ctl.o mailbox.o ksupport_data.o kserver.o moninj.o main.o
OBJECTS_KSUPPORT := ksupport.o exception_jmp.o exceptions.o mailbox.o bridge.o rtio.o ttl.o dds.o
CFLAGS += -Ilwip/src/include -Iliblwip

View File

@ -5,12 +5,14 @@
#include "dds.h"
#include "bridge.h"
#define TIME_BUFFER (8000 << RTIO_FINE_TS_WIDTH)
static void dds_write(int addr, int data)
{
rtio_chan_sel_write(RTIO_DDS_CHANNEL);
rtio_o_address_write(addr);
rtio_o_data_write(data);
rtio_o_timestamp_write(rtio_get_counter() + 8000);
rtio_o_timestamp_write(rtio_get_counter() + TIME_BUFFER);
rtio_o_we_write(1);
}
@ -46,7 +48,7 @@ void bridge_main(void)
struct msg_brg_ttl_out *msg;
msg = (struct msg_brg_ttl_out *)umsg;
ttl_set_oe(rtio_get_counter() + 8000, msg->channel, msg->value);
ttl_set_oe(rtio_get_counter() + TIME_BUFFER, msg->channel, msg->value);
mailbox_acknowledge();
break;
}
@ -54,7 +56,7 @@ void bridge_main(void)
struct msg_brg_ttl_out *msg;
msg = (struct msg_brg_ttl_out *)umsg;
ttl_set_o(rtio_get_counter() + 8000, msg->channel, msg->value);
ttl_set_o(rtio_get_counter() + TIME_BUFFER, msg->channel, msg->value);
mailbox_acknowledge();
break;
}

View File

@ -26,6 +26,16 @@ long long int clock_get_ms(void)
return clock_ms;
}
void busywait_us(long long int us)
{
long long int threshold;
timer0_update_value_write(1);
threshold = timer0_value_read() - us*(long long int)identifier_frequency_read()/1000000LL;
while(timer0_value_read() > threshold)
timer0_update_value_write(1);
}
struct watchdog {
int active;
long long int threshold;

View File

@ -3,6 +3,7 @@
void clock_init(void);
long long int clock_get_ms(void);
void busywait_us(long long us);
#define MAX_WATCHDOGS 16

View File

@ -2,7 +2,7 @@
#define __KLOADER_H
#define KERNELCPU_EXEC_ADDRESS 0x40400000
#define KERNELCPU_PAYLOAD_ADDRESS 0x40404000
#define KERNELCPU_PAYLOAD_ADDRESS 0x40408000
extern long long int now;

View File

@ -79,7 +79,7 @@ long long int now_init(void)
if(now < 0) {
rtio_init();
now = rtio_get_counter() + 125000;
now = rtio_get_counter() + (125000 << RTIO_FINE_TS_WIDTH);
}
return now;

View File

@ -4,10 +4,10 @@ ENTRY(_start)
INCLUDE generated/regions.ld
/* First 4M of main memory are reserved for runtime code/data
* then comes kernel memory. First 16K of kernel memory are for support code.
* then comes kernel memory. First 32K of kernel memory are for support code.
*/
MEMORY {
ksupport : ORIGIN = 0x40400000, LENGTH = 0x4000
ksupport : ORIGIN = 0x40400000, LENGTH = 0x8000
}
/* On AMP systems, kernel stack is at the end of main RAM,
@ -24,17 +24,6 @@ SECTIONS
_etext = .;
} > ksupport
.got :
{
_GLOBAL_OFFSET_TABLE_ = .;
*(.got)
} > ksupport
.got.plt :
{
*(.got.plt)
} > ksupport
.rodata :
{
. = ALIGN(4);

View File

@ -53,4 +53,3 @@ clean:
liblwip.a: $(LWIPOBJS)
$(AR) clr liblwip.a $(LWIPOBJS)
$(RANLIB) liblwip.a

View File

@ -26,17 +26,6 @@ SECTIONS
_etext = .;
} > runtime
.got :
{
_GLOBAL_OFFSET_TABLE_ = .;
*(.got)
} > runtime
.got.plt :
{
*(.got.plt)
} > runtime
.rodata :
{
. = ALIGN(4);

View File

@ -24,6 +24,7 @@
#include "kloader.h"
#include "flash_storage.h"
#include "clock.h"
#include "rtiocrg.h"
#include "test_mode.h"
#include "kserver.h"
#include "session.h"
@ -250,6 +251,7 @@ int main(void)
puts("ARTIQ runtime built "__DATE__" "__TIME__"\n");
clock_init();
rtiocrg_init();
puts("Press 't' to enter test mode...");
blink_led();

67
soc/runtime/rtiocrg.c Normal file
View File

@ -0,0 +1,67 @@
#include <stdio.h>
#include <generated/csr.h>
#include "clock.h"
#include "flash_storage.h"
#include "rtiocrg.h"
void rtiocrg_init(void)
{
char b;
int clk;
#ifdef CSR_RTIO_CRG_PLL_RESET_ADDR
rtio_crg_pll_reset_write(0);
#endif
b = 'i';
clk = 0;
fs_read("startup_clock", &b, 1, NULL);
if(b == 'i')
printf("Startup RTIO clock: internal\n");
else if(b == 'e') {
printf("Startup RTIO clock: external\n");
clk = 1;
} else
printf("WARNING: unknown startup_clock entry in flash storage\n");
if(!rtiocrg_switch_clock(clk)) {
printf("WARNING: startup RTIO clock failed\n");
printf("WARNING: this may cause the system initialization to fail\n");
printf("WARNING: fix clocking and reset the device\n");
}
}
int rtiocrg_check(void)
{
#ifdef CSR_RTIO_CRG_PLL_RESET_ADDR
return rtio_crg_pll_locked_read();
#else
return 1;
#endif
}
int rtiocrg_switch_clock(int clk)
{
int current_clk;
current_clk = rtio_crg_clock_sel_read();
if(clk == current_clk) {
#ifdef CSR_RTIO_CRG_PLL_RESET_ADDR
busywait_us(150);
if(!rtio_crg_pll_locked_read())
return 0;
#endif
return 1;
}
#ifdef CSR_RTIO_CRG_PLL_RESET_ADDR
rtio_crg_pll_reset_write(1);
#endif
rtio_crg_clock_sel_write(clk);
#ifdef CSR_RTIO_CRG_PLL_RESET_ADDR
rtio_crg_pll_reset_write(0);
busywait_us(150);
if(!rtio_crg_pll_locked_read())
return 0;
#endif
return 1;
}

8
soc/runtime/rtiocrg.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef __RTIOCRG_H
#define __RTIOCRG_H
void rtiocrg_init(void);
int rtiocrg_check(void);
int rtiocrg_switch_clock(int clk);
#endif /* __RTIOCRG_H */

View File

@ -12,6 +12,7 @@
#include "kloader.h"
#include "exceptions.h"
#include "flash_storage.h"
#include "rtiocrg.h"
#include "session.h"
#define BUFFER_IN_SIZE (1024*1024)
@ -154,8 +155,10 @@ static int process_input(void)
submit_output(9);
break;
}
rtio_crg_clock_sel_write(buffer_in[9]);
buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_COMPLETED;
if(rtiocrg_switch_clock(buffer_in[9]))
buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_COMPLETED;
else
buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_FAILED;
submit_output(9);
break;
case REMOTEMSG_TYPE_LOAD_OBJECT:
@ -527,10 +530,17 @@ void session_poll(void **data, int *len)
{
int l;
if((user_kernel_state == USER_KERNEL_RUNNING) && watchdog_expired()) {
log("Watchdog expired");
*len = -1;
return;
if(user_kernel_state == USER_KERNEL_RUNNING) {
if(watchdog_expired()) {
log("Watchdog expired");
*len = -1;
return;
}
if(!rtiocrg_check()) {
log("RTIO clock failure");
*len = -1;
return;
}
}
l = get_out_packet_len();

View File

@ -1,8 +1,11 @@
from migen.fhdl.std import *
from migen.genlib.resetsync import AsyncResetSynchronizer
from migen.genlib.cdc import MultiReg
from migen.bank.description import *
from migen.bank import wbgen
from mibuild.generic_platform import *
from mibuild.xilinx.vivado import XilinxVivadoToolchain
from mibuild.xilinx.ise import XilinxISEToolchain
from misoclib.com import gpio
from misoclib.soc import mem_decoder
@ -11,13 +14,22 @@ from targets.kc705 import MiniSoC
from artiq.gateware.soc import AMPSoC
from artiq.gateware import rtio, nist_qc1, nist_qc2
from artiq.gateware.rtio.phy import ttl_simple, dds
from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_7series, dds
class _RTIOCRG(Module, AutoCSR):
def __init__(self, platform, rtio_internal_clk):
self._clock_sel = CSRStorage()
self.clock_domains.cd_rtio = ClockDomain(reset_less=True)
self._pll_reset = CSRStorage(reset=1)
self._pll_locked = CSRStatus()
self.clock_domains.cd_rtio = ClockDomain()
self.clock_domains.cd_rtiox4 = ClockDomain(reset_less=True)
# 10 MHz when using 125MHz input
self.clock_domains.cd_ext_clkout = ClockDomain(reset_less=True)
ext_clkout = platform.request("user_sma_gpio_p")
self.sync.ext_clkout += ext_clkout.eq(~ext_clkout)
rtio_external_clk = Signal()
user_sma_clock = platform.request("user_sma_clock")
@ -25,11 +37,40 @@ class _RTIOCRG(Module, AutoCSR):
self.specials += Instance("IBUFDS",
i_I=user_sma_clock.p, i_IB=user_sma_clock.n,
o_O=rtio_external_clk)
self.specials += Instance("BUFGMUX",
i_I0=rtio_internal_clk,
i_I1=rtio_external_clk,
i_S=self._clock_sel.storage,
o_O=self.cd_rtio.clk)
pll_locked = Signal()
rtio_clk = Signal()
rtiox4_clk = Signal()
ext_clkout_clk = Signal()
self.specials += [
Instance("PLLE2_ADV",
p_STARTUP_WAIT="FALSE", o_LOCKED=pll_locked,
p_REF_JITTER1=0.01,
p_CLKIN1_PERIOD=8.0, p_CLKIN2_PERIOD=8.0,
i_CLKIN1=rtio_internal_clk, i_CLKIN2=rtio_external_clk,
# Warning: CLKINSEL=0 means CLKIN2 is selected
i_CLKINSEL=~self._clock_sel.storage,
# VCO @ 1GHz when using 125MHz input
p_CLKFBOUT_MULT=8, p_DIVCLK_DIVIDE=1,
i_CLKFBIN=self.cd_rtio.clk,
i_RST=self._pll_reset.storage,
o_CLKFBOUT=rtio_clk,
p_CLKOUT0_DIVIDE=2, p_CLKOUT0_PHASE=0.0,
o_CLKOUT0=rtiox4_clk,
p_CLKOUT1_DIVIDE=50, p_CLKOUT1_PHASE=0.0,
o_CLKOUT1=ext_clkout_clk),
Instance("BUFG", i_I=rtio_clk, o_O=self.cd_rtio.clk),
Instance("BUFG", i_I=rtiox4_clk, o_O=self.cd_rtiox4.clk),
Instance("BUFG", i_I=ext_clkout_clk, o_O=self.cd_ext_clkout.clk),
AsyncResetSynchronizer(self.cd_rtio, ~pll_locked),
MultiReg(pll_locked, self._pll_locked.status)
]
class _NIST_QCx(MiniSoC, AMPSoC):
@ -57,10 +98,10 @@ class _NIST_QCx(MiniSoC, AMPSoC):
platform.request("user_led", 1)))
def add_rtio(self, rtio_channels):
self.submodules.rtio_crg = _RTIOCRG(self.platform, self.crg.pll_sys)
self.submodules.rtio = rtio.RTIO(rtio_channels,
clk_freq=125000000)
self.submodules.rtio_crg = _RTIOCRG(self.platform, self.crg.cd_sys.clk)
self.submodules.rtio = rtio.RTIO(rtio_channels)
self.add_constant("RTIO_FINE_TS_WIDTH", self.rtio.fine_ts_width)
assert self.rtio.fine_ts_width <= 3
self.add_constant("DDS_RTIO_CLK_RATIO", 8 >> self.rtio.fine_ts_width)
self.submodules.rtio_moninj = rtio.MonInj(rtio_channels)
@ -71,6 +112,13 @@ create_clock -name rio_clk -period 8.0 [get_nets {rio_clk}]
set_false_path -from [get_clocks rsys_clk] -to [get_clocks rio_clk]
set_false_path -from [get_clocks rio_clk] -to [get_clocks rsys_clk]
""", rsys_clk=self.rtio.cd_rsys.clk, rio_clk=self.rtio.cd_rio.clk)
if isinstance(self.platform.toolchain, XilinxISEToolchain):
self.platform.add_platform_command("""
NET "sys_clk" TNM_NET = "GRPrsys_clk";
NET "{rio_clk}" TNM_NET = "GRPrio_clk";
TIMESPEC "TSfix_cdc1" = FROM "GRPrsys_clk" TO "GRPrio_clk" TIG;
TIMESPEC "TSfix_cdc2" = FROM "GRPrio_clk" TO "GRPrsys_clk" TIG;
""", rio_clk=self.rtio_crg.cd_rtio.clk)
rtio_csrs = self.rtio.get_csrs()
self.submodules.rtiowb = wbgen.Bank(rtio_csrs)
@ -92,11 +140,11 @@ class NIST_QC1(_NIST_QCx):
rtio_channels = []
for i in range(2):
phy = ttl_simple.Inout(platform.request("pmt", i))
phy = ttl_serdes_7series.Inout_8X(platform.request("pmt", i))
self.submodules += phy
rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=512))
for i in range(15):
phy = ttl_simple.Output(platform.request("ttl", i))
phy = ttl_serdes_7series.Output_8X(platform.request("ttl", i))
self.submodules += phy
rtio_channels.append(rtio.Channel.from_phy(phy))
@ -131,11 +179,11 @@ class NIST_QC2(_NIST_QCx):
# TTL14 is for the clock generator
continue
if i % 4 == 3:
phy = ttl_simple.Inout(platform.request("ttl", i))
phy = ttl_serdes_7series.Inout_8X(platform.request("ttl", i))
self.submodules += phy
rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=512))
else:
phy = ttl_simple.Output(platform.request("ttl", i))
phy = ttl_serdes_7series.Output_8X(platform.request("ttl", i))
self.submodules += phy
rtio_channels.append(rtio.Channel.from_phy(phy))

View File

@ -3,6 +3,8 @@ from fractions import Fraction
from migen.fhdl.std import *
from migen.bank.description import *
from migen.bank import wbgen
from migen.genlib.resetsync import AsyncResetSynchronizer
from migen.genlib.cdc import MultiReg
from misoclib.com import gpio
from misoclib.soc import mem_decoder
@ -11,46 +13,84 @@ from targets.pipistrello import BaseSoC
from artiq.gateware.soc import AMPSoC
from artiq.gateware import rtio, nist_qc1
from artiq.gateware.rtio.phy import ttl_simple, dds
from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_spartan6, dds
class _RTIOCRG(Module, AutoCSR):
def __init__(self, platform, clk_freq):
self._clock_sel = CSRStorage()
self.clock_domains.cd_rtio = ClockDomain(reset_less=True)
self._pll_reset = CSRStorage(reset=1)
self._pll_locked = CSRStatus()
f = Fraction(125*1000*1000, clk_freq)
self.clock_domains.cd_rtio = ClockDomain()
self.clock_domains.cd_rtiox4 = ClockDomain(reset_less=True)
self.clock_domains.cd_rtiox8 = ClockDomain(reset_less=True)
self.rtiox4_stb = Signal()
self.rtiox8_stb = Signal()
rtio_f = 125*1000*1000
f = Fraction(rtio_f, clk_freq)
rtio_internal_clk = Signal()
self.specials += Instance("DCM_CLKGEN",
p_CLKFXDV_DIVIDE=2,
p_CLKFX_DIVIDE=f.denominator,
p_CLKFX_MD_MAX=float(f),
p_CLKFX_MULTIPLY=f.numerator,
p_CLKIN_PERIOD=1e9/clk_freq,
p_SPREAD_SPECTRUM="NONE",
p_STARTUP_WAIT="FALSE",
i_CLKIN=ClockSignal(),
o_CLKFX=rtio_internal_clk,
i_FREEZEDCM=0,
i_RST=ResetSignal())
rtio_external_clk = platform.request("pmt", 2)
# ISE infers constraints for the internal clock
# and propagates them through the BUFGMUX. Adding this:
# platform.add_period_constraint(rtio_external_clk, 8.0)
# seems to confuse it
self.specials += Instance("BUFGMUX",
i_I0=rtio_internal_clk,
i_I1=rtio_external_clk,
i_S=self._clock_sel.storage,
o_O=self.cd_rtio.clk)
rtio_external_clk = Signal()
pmt2 = platform.request("pmt", 2)
dcm_locked = Signal()
rtio_clk = Signal()
pll_locked = Signal()
pll = Signal(3)
pll_fb = Signal()
self.specials += [
Instance("IBUFG", i_I=pmt2, o_O=rtio_external_clk),
Instance("DCM_CLKGEN", p_CLKFXDV_DIVIDE=2,
p_CLKFX_DIVIDE=f.denominator, p_CLKFX_MD_MAX=float(f),
p_CLKFX_MULTIPLY=f.numerator, p_CLKIN_PERIOD=1e9/clk_freq,
p_SPREAD_SPECTRUM="NONE", p_STARTUP_WAIT="FALSE",
i_CLKIN=ClockSignal(), o_CLKFX=rtio_internal_clk,
i_FREEZEDCM=0, i_RST=ResetSignal(), o_LOCKED=dcm_locked),
Instance("BUFGMUX",
i_I0=rtio_internal_clk, i_I1=rtio_external_clk,
i_S=self._clock_sel.storage, o_O=rtio_clk),
Instance("PLL_ADV", p_SIM_DEVICE="SPARTAN6",
p_BANDWIDTH="OPTIMIZED", p_COMPENSATION="INTERNAL",
p_REF_JITTER=.01, p_CLK_FEEDBACK="CLKFBOUT",
i_DADDR=0, i_DCLK=0, i_DEN=0, i_DI=0, i_DWE=0,
i_RST=self._pll_reset.storage | ~dcm_locked, i_REL=0,
p_DIVCLK_DIVIDE=1, p_CLKFBOUT_MULT=8,
p_CLKFBOUT_PHASE=0., i_CLKINSEL=1,
i_CLKIN1=rtio_clk, i_CLKIN2=0,
p_CLKIN1_PERIOD=1e9/rtio_f, p_CLKIN2_PERIOD=0.,
i_CLKFBIN=pll_fb, o_CLKFBOUT=pll_fb, o_LOCKED=pll_locked,
o_CLKOUT0=pll[0], p_CLKOUT0_DUTY_CYCLE=.5,
o_CLKOUT1=pll[1], p_CLKOUT1_DUTY_CYCLE=.5,
o_CLKOUT2=pll[2], p_CLKOUT2_DUTY_CYCLE=.5,
p_CLKOUT0_PHASE=0., p_CLKOUT0_DIVIDE=1,
p_CLKOUT1_PHASE=0., p_CLKOUT1_DIVIDE=2,
p_CLKOUT2_PHASE=0., p_CLKOUT2_DIVIDE=8),
Instance("BUFPLL", p_DIVIDE=8,
i_PLLIN=pll[0], i_GCLK=self.cd_rtio.clk,
i_LOCKED=pll_locked, o_IOCLK=self.cd_rtiox8.clk,
o_SERDESSTROBE=self.rtiox8_stb),
Instance("BUFPLL", p_DIVIDE=4,
i_PLLIN=pll[1], i_GCLK=self.cd_rtio.clk,
i_LOCKED=pll_locked, o_IOCLK=self.cd_rtiox4.clk,
o_SERDESSTROBE=self.rtiox4_stb),
Instance("BUFG", i_I=pll[2], o_O=self.cd_rtio.clk),
AsyncResetSynchronizer(self.cd_rtio, ~pll_locked),
MultiReg(pll_locked, self._pll_locked.status),
]
# ISE infers correct period constraints for cd_rtio.clk from
# the internal clock. The first two TIGs target just the BUFGMUX.
platform.add_platform_command("""
NET "{int_clk}" TNM_NET = "GRPint_clk";
NET "sys_clk" TNM_NET = "GRPsys_clk";
TIMESPEC "TSfix_ise1" = FROM "GRPint_clk" TO "GRPsys_clk" TIG;
NET "{ext_clk}" TNM_NET = "GRPext_clk";
TIMESPEC "TSfix_ise1" = FROM "GRPsys_clk" TO "GRPext_clk" TIG;
NET "{int_clk}" TNM_NET = "GRPint_clk";
TIMESPEC "TSfix_ise2" = FROM "GRPsys_clk" TO "GRPint_clk" TIG;
""", int_clk=rtio_internal_clk)
NET "{rtio_clk}" TNM_NET = "GRPrtio_clk";
TIMESPEC "TSfix_ise3" = FROM "GRPrtio_clk" TO "GRPsys_clk" TIG;
TIMESPEC "TSfix_ise4" = FROM "GRPsys_clk" TO "GRPrtio_clk" TIG;
""", ext_clk=rtio_external_clk, int_clk=rtio_internal_clk,
rtio_clk=self.cd_rtio.clk)
class NIST_QC1(BaseSoC, AMPSoC):
@ -73,6 +113,7 @@ class NIST_QC1(BaseSoC, AMPSoC):
sdram_controller_settings=MiniconSettings(l2_size=64*1024),
with_timer=False, **kwargs)
AMPSoC.__init__(self)
platform.toolchain.bitgen_opt = "-g Binary:Yes -w"
platform.toolchain.ise_commands += """
trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd {build_name}.pcf
"""
@ -90,16 +131,29 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd
platform.request("ttl_h_tx_en").eq(1)
]
self.submodules.rtio_crg = _RTIOCRG(platform, self.clk_freq)
# RTIO channels
rtio_channels = []
# pmt1 can run on a 8x serdes if pmt0 is not used
for i in range(2):
phy = ttl_simple.Inout(platform.request("pmt", i))
phy = ttl_serdes_spartan6.Inout_4X(platform.request("pmt", i),
self.rtio_crg.rtiox4_stb)
self.submodules += phy
rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=512,
ofifo_depth=4))
# ttl2 can run on a 8x serdes if xtrig is not used
for i in range(15):
phy = ttl_simple.Output(platform.request("ttl", i))
if i in (0, 1):
phy = ttl_serdes_spartan6.Output_4X(platform.request("ttl", i),
self.rtio_crg.rtiox4_stb)
elif i in (2,):
phy = ttl_serdes_spartan6.Output_8X(platform.request("ttl", i),
self.rtio_crg.rtiox8_stb)
else:
phy = ttl_simple.Output(platform.request("ttl", i))
self.submodules += phy
rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=256))
@ -120,16 +174,16 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd
self.add_constant("RTIO_DDS_CHANNEL", len(rtio_channels))
self.add_constant("DDS_CHANNEL_COUNT", 8)
self.add_constant("DDS_AD9858")
phy = dds.AD9858(platform.request("dds"), 8)
dds_pins = platform.request("dds")
self.comb += dds_pins.p.eq(0)
phy = dds.AD9858(dds_pins, 8)
self.submodules += phy
rtio_channels.append(rtio.Channel.from_phy(phy,
ofifo_depth=512,
ififo_depth=4))
# RTIO core
self.submodules.rtio_crg = _RTIOCRG(platform, self.clk_freq)
self.submodules.rtio = rtio.RTIO(rtio_channels,
clk_freq=125000000)
self.submodules.rtio = rtio.RTIO(rtio_channels)
self.add_constant("RTIO_FINE_TS_WIDTH", self.rtio.fine_ts_width)
self.add_constant("DDS_RTIO_CLK_RATIO", 8 >> self.rtio.fine_ts_width)
self.submodules.rtio_moninj = rtio.MonInj(rtio_channels)