forked from M-Labs/artiq
1
0
Fork 0

Merge branch 'master' into switching125

This commit is contained in:
Sebastien Bourdeauducq 2018-10-04 10:08:42 +08:00
commit 969a305c5a
33 changed files with 946 additions and 149 deletions

View File

@ -162,16 +162,16 @@ class AD9914:
def set_phase_mode(self, phase_mode):
"""Sets the phase mode of the DDS channel. Supported phase modes are:
* ``PHASE_MODE_CONTINUOUS``: the phase accumulator is unchanged when
* :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged when
switching frequencies. The DDS phase is the sum of the phase
accumulator and the phase offset. The only discrete jumps in the
DDS output phase come from changes to the phase offset.
* ``PHASE_MODE_ABSOLUTE``: the phase accumulator is reset when
* :const:`PHASE_MODE_ABSOLUTE`: the phase accumulator is reset when
switching frequencies. Thus, the phase of the DDS at the time of
the frequency change is equal to the phase offset.
* ``PHASE_MODE_TRACKING``: when switching frequencies, the phase
* :const:`PHASE_MODE_TRACKING`: when switching frequencies, the phase
accumulator is set to the value it would have if the DDS had been
running at the specified frequency since the start of the
experiment.
@ -193,7 +193,7 @@ class AD9914:
:param ftw: frequency to generate.
:param pow: adds an offset to the phase.
:param phase_mode: if specified, overrides the default phase mode set
by ``set_phase_mode`` for this call.
by :meth:`set_phase_mode` for this call.
:param ref_time: reference time used to compute phase. Specifying this
makes it easier to have a well-defined phase relationship between
DDSes on the same bus that are updated at a similar time.
@ -270,7 +270,7 @@ class AD9914:
@kernel
def set(self, frequency, phase=0.0, phase_mode=_PHASE_MODE_DEFAULT,
amplitude=1.0):
"""Like ``set_mu``, but uses Hz and turns."""
"""Like :meth:`set_mu`, but uses Hz and turns."""
self.set_mu(self.frequency_to_ftw(frequency),
self.turns_to_pow(phase), phase_mode,
self.amplitude_to_asf(amplitude))
@ -323,7 +323,7 @@ class AD9914:
@kernel
def set_x(self, frequency, amplitude=1.0):
"""Like ``set_x_mu``, but uses Hz and turns.
"""Like :meth:`set_x_mu`, but uses Hz and turns.
Note that the precision of ``float`` is less than the precision
of the extended frequency tuning word.

View File

@ -34,7 +34,7 @@ def dma_playback(timestamp: TInt64, ptr: TInt32) -> TNone:
class DMARecordContextManager:
"""Context manager returned by ``CoreDMA.record()``.
"""Context manager returned by :meth:`CoreDMA.record()`.
Upon entering, starts recording a DMA trace. All RTIO operations are
redirected to a newly created DMA buffer after this call, and ``now``

View File

@ -184,7 +184,7 @@ class SPIMaster:
experiments and are known.
This method is portable and can also be called from e.g.
``__init__``.
:meth:`__init__`.
:param div: SPI clock divider (see: :meth:`set_config_mu`)
:param length: SPI transfer length (see: :meth:`set_config_mu`)

View File

@ -253,7 +253,7 @@ class Channel:
This method does not advance the timeline. Output RF switch setting
takes effect immediately and is independent of any other activity
(profile settings, other channels). The RF switch behaves like
``TTLOut``. RTIO event replacement is supported. IIR updates take place
:class:`artiq.coredevice.ttl.TTLOut`. RTIO event replacement is supported. IIR updates take place
once the RF switch has been enabled for the configured delay and the
profile setting has been stable. Profile changes take between one and
two servo cycles to reach the DDS.

View File

@ -2,8 +2,8 @@
Drivers for TTL signals on RTIO.
TTL channels (including the clock generator) all support output event
replacement. For example, pulses of "zero" length (e.g. ``on()``
immediately followed by ``off()``, without a delay) are suppressed.
replacement. For example, pulses of "zero" length (e.g. :meth:`TTLInOut.on`
immediately followed by :meth:`TTLInOut.off`, without a delay) are suppressed.
"""
import numpy
@ -107,8 +107,8 @@ class TTLInOut:
This should be used with bidirectional channels.
Note that the channel is in input mode by default. If you need to drive a
signal, you must call ``output``. If the channel is in output mode most of
the time in your setup, it is a good idea to call ``output`` in the
signal, you must call :meth:`output`. If the channel is in output mode most of
the time in your setup, it is a good idea to call :meth:`output` in the
startup kernel.
There are three input APIs: gating, sampling and watching. When one
@ -301,10 +301,10 @@ class TTLInOut:
@kernel
def sample_get(self):
"""Returns the value of a sample previously obtained with
``sample_input``.
:meth:`sample_input`.
Multiple samples may be queued (using multiple calls to
``sample_input``) into the RTIO FIFOs and subsequently read out using
:meth:`sample_input`) into the RTIO FIFOs and subsequently read out using
multiple calls to this function.
This function does not interact with the time cursor."""
@ -324,11 +324,11 @@ class TTLInOut:
@kernel
def watch_stay_on(self):
"""Checks that the input is at a high level at the position
of the time cursor and keep checking until ``watch_done``
of the time cursor and keep checking until :meth:`watch_done`
is called.
Returns ``True`` if the input is high. A call to this function
must always be followed by an eventual call to ``watch_done``
must always be followed by an eventual call to :meth:`watch_done`
(use e.g. a try/finally construct to ensure this).
The time cursor is not modified by this function.
@ -338,7 +338,7 @@ class TTLInOut:
@kernel
def watch_stay_off(self):
"""Like ``watch_stay_on``, but for low levels."""
"""Like :meth:`watch_stay_on`, but for low levels."""
rtio_output(now_mu(), self.channel, 3, 1) # gate rising
return rtio_input_data(self.channel) == 0
@ -419,7 +419,7 @@ class TTLClockGen:
@kernel
def set(self, frequency):
"""Like ``set_mu``, but using Hz."""
"""Like :meth:`set_mu`, but using Hz."""
self.set_mu(self.frequency_to_ftw(frequency))
@kernel

View File

@ -74,6 +74,7 @@ class _TTLWidget(QtWidgets.QFrame):
self.cur_level = False
self.cur_oe = False
self.cur_override = False
self.cur_override_level = False
self.refresh_display()
def enterEvent(self, event):
@ -106,7 +107,9 @@ class _TTLWidget(QtWidgets.QFrame):
self.set_mode(self.channel, "0")
def refresh_display(self):
value_s = "1" if self.cur_level else "0"
level = self.cur_override_level if self.cur_override else self.cur_level
value_s = "1" if level else "0"
if self.cur_override:
value_s = "<b>" + value_s + "</b>"
color = " color=\"red\""
@ -377,7 +380,7 @@ class _DeviceManager:
if override == TTLOverride.en.value:
widget.cur_override = bool(value)
if override == TTLOverride.level.value:
widget.cur_level = bool(value)
widget.cur_override_level = bool(value)
widget.refresh_display()
async def core_connector(self):

View File

@ -139,6 +139,13 @@ for i in range(2):
}
}
device_db["grabber0"] = {
"type": "local",
"module": "artiq.coredevice.grabber",
"class": "Grabber",
"arguments": {"channel_base": 20}
}
device_db.update(
led0={
"type": "local",

View File

@ -0,0 +1,225 @@
core_addr = "kasli-1.lab.m-labs.hk"
device_db = {
"core": {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {"host": core_addr, "ref_period": 1e-9}
},
"core_log": {
"type": "controller",
"host": "::1",
"port": 1068,
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",
"class": "CoreCache"
},
"core_dma": {
"type": "local",
"module": "artiq.coredevice.dma",
"class": "CoreDMA"
},
"i2c_switch0": {
"type": "local",
"module": "artiq.coredevice.i2c",
"class": "PCA9548",
"arguments": {"address": 0xe0}
},
"i2c_switch1": {
"type": "local",
"module": "artiq.coredevice.i2c",
"class": "PCA9548",
"arguments": {"address": 0xe2}
},
}
for i in range(8):
device_db["ttl" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLInOut" if i < 4 else "TTLOut",
"arguments": {"channel": i},
}
device_db.update(
spi_urukul0={
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 8}
},
ttl_urukul0_io_update={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 9}
},
ttl_urukul0_sw0={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 10}
},
ttl_urukul0_sw1={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 11}
},
ttl_urukul0_sw2={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 12}
},
ttl_urukul0_sw3={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 13}
},
urukul0_cpld={
"type": "local",
"module": "artiq.coredevice.urukul",
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul0",
"io_update_device": "ttl_urukul0_io_update",
"refclk": 125e6,
"clk_sel": 0
}
}
)
for i in range(4):
device_db["urukul0_ch" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ad9910",
"class": "AD9910",
"arguments": {
"pll_n": 32,
"chip_select": 4 + i,
"cpld_device": "urukul0_cpld",
"sw_device": "ttl_urukul0_sw" + str(i)
}
}
device_db.update(
spi_urukul1={
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 14}
},
ttl_urukul1_io_update={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 15}
},
ttl_urukul1_sw0={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 16}
},
ttl_urukul1_sw1={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 17}
},
ttl_urukul1_sw2={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 18}
},
ttl_urukul1_sw3={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 19}
},
urukul1_cpld={
"type": "local",
"module": "artiq.coredevice.urukul",
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul1",
"io_update_device": "ttl_urukul1_io_update",
"refclk": 125e6,
"clk_sel": 0
}
}
)
for i in range(4):
device_db["urukul1_ch" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ad9910",
"class": "AD9910",
"arguments": {
"pll_n": 32,
"chip_select": 4 + i,
"cpld_device": "urukul1_cpld",
"sw_device": "ttl_urukul1_sw" + str(i)
}
}
for i in range(2):
device_db["spi_zotino{}".format(i)] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 20+3*i+0}
}
device_db["ttl_zotino{}_ldac".format(i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 20+3*i+1}
}
device_db["ttl_zotino{}_clr".format(i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 20+3*i+2}
}
device_db["zotino{}".format(i)] = {
"type": "local",
"module": "artiq.coredevice.zotino",
"class": "Zotino",
"arguments": {
"spi_device": "spi_zotino{}".format(i),
"ldac_device": "ttl_zotino{}_ldac".format(i),
"clr_device": "ttl_zotino{}_clr".format(i)
}
}
device_db["grabber0"] = {
"type": "local",
"module": "artiq.coredevice.grabber",
"class": "Grabber",
"arguments": {"channel_base": 26}
}
device_db.update(
led0={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 28}
},
led1={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 29}
},
)

View File

@ -171,6 +171,13 @@ device_db["zotino0"] = {
}
}
device_db["grabber0"] = {
"type": "local",
"module": "artiq.coredevice.grabber",
"class": "Grabber",
"arguments": {"channel_base": 20}
}
device_db.update(
led0={
"type": "local",

View File

@ -173,6 +173,13 @@ for i in range(4):
}
}
device_db["grabber0"] = {
"type": "local",
"module": "artiq.coredevice.grabber",
"class": "Grabber",
"arguments": {"channel_base": 36}
}
device_db.update(
led0={
"type": "local",

View File

@ -156,7 +156,9 @@ class KasliTester(EnvExperiment):
def test_ttl_ins(self):
print("*** Testing TTL inputs.")
if not self.ttl_outs:
print("No TTL output channel available to use as stimulus.")
return
ttl_out_name, ttl_out_dev = next(iter(self.ttl_outs))
for ttl_in_name, ttl_in_dev in self.ttl_ins:
print("Connect {} to {}. Press ENTER when done."
@ -277,23 +279,10 @@ class KasliTester(EnvExperiment):
print("Press ENTER when done.")
input()
def test_grabbers(self):
rois = [[0, 0, 0, 2, 2], [1, 0, 0, 2048, 2048]]
print("*** Testing Grabber Frame Grabbers.")
print("ROIs: %s".format(rois))
print("Press ENTER, activate the camera's frame grabber output, "
"and trigger it once. Type 's' to skip the test.")
if input().strip().lower() == "s":
print("skipping...")
return
for card_n, (card_name, card_dev) in enumerate(self.grabbers):
print(card_name)
self.grabber_capture(card_dev, rois)
@kernel
def grabber_capture(self, card_dev, rois):
self.core.break_realtime()
delay(10*us)
delay(100*us)
mask = 0
for i in range(len(rois)):
i = rois[i][0]
@ -308,8 +297,21 @@ class KasliTester(EnvExperiment):
card_dev.input_mu(n)
self.core.break_realtime()
card_dev.gate_roi(0)
print("ROI sums:")
print(n)
print("ROI sums: {}".format(n))
def test_grabbers(self):
print("*** Testing Grabber Frame Grabbers.")
print("Activate the camera's frame grabber output, type 'g', press "
"ENTER, and trigger the camera.")
print("Just press ENTER to skip the test.")
if input().strip().lower() != "g":
print("skipping...")
return
rois = [[0, 0, 0, 2, 2], [1, 0, 0, 2048, 2048]]
print("ROIs: {}".format(rois))
for card_n, (card_name, card_dev) in enumerate(self.grabbers):
print(card_name)
self.grabber_capture(card_dev, rois)
def run(self):
print("****** Kasli system tester ******")

View File

@ -39,6 +39,7 @@ device_db = {
}
# DIO (EEM5) starting at RTIO channel 0
for i in range(8):
device_db["ttl" + str(i)] = {
"type": "local",
@ -48,6 +49,7 @@ for i in range(8):
}
# Urukul (EEM1) starting at RTIO channel 8
device_db.update(
spi_urukul0={
"type": "local",
@ -112,6 +114,7 @@ for i in range(4):
}
# Sampler (EEM3) starting at RTIO channel 14
device_db["spi_sampler0_adc"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
@ -142,6 +145,7 @@ device_db["sampler0"] = {
}
# Zotino (EEM4) starting at RTIO channel 17
device_db["spi_zotino0"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
@ -171,18 +175,150 @@ device_db["zotino0"] = {
}
}
# Grabber (EEM6) starting at RTIO channel 20
device_db["grabber0"] = {
"type": "local",
"module": "artiq.coredevice.grabber",
"class": "Grabber",
"arguments": {"channel_base": 20}
}
# Urukul (EEM7) starting at RTIO channel 22
device_db.update(
spi_urukul1={
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 22}
},
ttl_urukul1_io_update={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 23}
},
urukul1_cpld={
"type": "local",
"module": "artiq.coredevice.urukul",
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul1",
"io_update_device": "ttl_urukul1_io_update",
"refclk": 100e6,
"clk_sel": 1
}
}
)
for i in range(4):
device_db["urukul1_ch" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ad9912",
"class": "AD9912",
"arguments": {
"pll_n": 10,
"chip_select": 4 + i,
"cpld_device": "urukul1_cpld"
}
}
# DIO (EEM8) starting at RTIO channel 24
for i in range(8):
device_db["ttl" + str(8 + i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 24 + i},
}
# DIO (EEM9) starting at RTIO channel 32
for i in range(8):
device_db["ttl" + str(16 + i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 32 + i},
}
# Sampler (EEM10) starting at RTIO channel 40
device_db["spi_sampler1_adc"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 40}
}
device_db["spi_sampler1_pgia"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 41}
}
device_db["spi_sampler1_cnv"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 42},
}
device_db["sampler1"] = {
"type": "local",
"module": "artiq.coredevice.sampler",
"class": "Sampler",
"arguments": {
"spi_adc_device": "spi_sampler1_adc",
"spi_pgia_device": "spi_sampler1_pgia",
"cnv_device": "spi_sampler1_cnv"
}
}
# Zotino (EEM11) starting at RTIO channel 43
device_db["spi_zotino1"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 43}
}
device_db["ttl_zotino1_ldac"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 44}
}
device_db["ttl_zotino1_clr"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 45}
}
device_db["zotino1"] = {
"type": "local",
"module": "artiq.coredevice.zotino",
"class": "Zotino",
"arguments": {
"spi_device": "spi_zotino1",
"ldac_device": "ttl_zotino1_ldac",
"clr_device": "ttl_zotino1_clr"
}
}
device_db.update(
led0={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 20}
"arguments": {"channel": 46}
},
led1={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 21}
"arguments": {"channel": 47}
},
)

View File

@ -134,8 +134,12 @@ pub fn tick() {
if clock_pattern_ok(g) {
let last_xy = get_last_pixels(g);
if last_xy != unsafe { INFO[g].frame_size } {
// x capture is on ~LVAL which is after
// the last increment on DVAL
// y capture is on ~FVAL which coincides with the
// last increment on ~LVAL
info!("grabber{} frame size: {}x{}",
g, last_xy.0 + 1, last_xy.1 + 1);
g, last_xy.0, last_xy.1 + 1);
unsafe { INFO[g].frame_size = last_xy }
}
State::Watch

View File

@ -272,9 +272,8 @@ fn startup_ethernet() {
smoltcp::phy::EthernetTracer::new(net_device, net_trace_fn)
};
let mut neighbor_map = [None; 8];
let neighbor_cache =
smoltcp::iface::NeighborCache::new(&mut neighbor_map[..]);
smoltcp::iface::NeighborCache::new(alloc::btree_map::BTreeMap::new());
let mut interface =
smoltcp::iface::EthernetInterfaceBuilder::new(net_device)
.neighbor_cache(neighbor_cache)

View File

@ -381,6 +381,19 @@ const SI5324_SETTINGS: si5324::FrequencySettings
crystal_ref: true
};
#[cfg(rtio_frequency = "125.0")]
const SI5324_SETTINGS: si5324::FrequencySettings
= si5324::FrequencySettings {
n1_hs : 5,
nc1_ls : 8,
n2_hs : 7,
n2_ls : 360,
n31 : 63,
n32 : 63,
bwsel : 4,
crystal_ref: true
};
const SIPHASER_PHASE: u16 = 32;
#[no_mangle]

View File

@ -344,7 +344,7 @@ def main():
storage_img = args.storage
programmer.write_binary(*config["storage"], storage_img)
elif action == "firmware":
if variant == "satellite":
if variant.endswith("satellite"):
firmware = "satman"
else:
firmware = "runtime"

View File

@ -4,17 +4,20 @@ from migen.genlib.cdc import MultiReg
from misoc.interconnect.csr import *
# This code assumes 125/62.5MHz reference clock and 150MHz RTIO frequency.
# This code assumes 125/62.5MHz reference clock and 125MHz or 150MHz RTIO
# frequency.
class SiPhaser7Series(Module, AutoCSR):
def __init__(self, si5324_clkin, si5324_clkout_fabric,
ref_clk=None, ref_div2=False):
ref_clk=None, ref_div2=False, rtio_clk_freq=150e6):
self.switch_clocks = CSRStorage()
self.phase_shift = CSR()
self.phase_shift_done = CSRStatus(reset=1)
self.sample_result = CSRStatus()
# 125MHz/62.5MHz reference clock to 150MHz. VCO @ 750MHz.
assert rtio_clk_freq in (125e6, 150e6)
# 125MHz/62.5MHz reference clock to 125MHz/150MHz. VCO @ 750MHz.
# Used to provide a startup clock to the transceiver through the Si,
# we do not use the crystal reference so that the PFD (f3) frequency
# can be high.
@ -31,10 +34,12 @@ class SiPhaser7Series(Module, AutoCSR):
o_CLKFBOUT=mmcm_freerun_fb, i_CLKFBIN=mmcm_freerun_fb,
p_CLKOUT0_DIVIDE_F=5.0, o_CLKOUT0=mmcm_freerun_output,
p_CLKOUT0_DIVIDE_F=750e6/rtio_clk_freq,
o_CLKOUT0=mmcm_freerun_output,
)
# 150MHz to 150MHz with controllable phase shift, VCO @ 1200MHz.
# 125MHz/150MHz to 125MHz/150MHz with controllable phase shift,
# VCO @ 1000MHz/1200MHz.
# Inserted between CDR and output to Si, used to correct
# non-determinstic skew of Si5324.
mmcm_ps_fb = Signal()
@ -42,7 +47,7 @@ class SiPhaser7Series(Module, AutoCSR):
mmcm_ps_psdone = Signal()
self.specials += \
Instance("MMCME2_ADV",
p_CLKIN1_PERIOD=1e9/150e6,
p_CLKIN1_PERIOD=1e9/rtio_clk_freq,
i_CLKIN1=ClockSignal("rtio_rx0"),
i_RST=ResetSignal("rtio_rx0"),
i_CLKINSEL=1, # yes, 1=CLKIN1 0=CLKIN2

View File

@ -85,16 +85,20 @@ class Parser(Module, AutoCSR):
self.sync.cl += [
last_lval.eq(lval),
last_fval.eq(fval),
If(dval,
pix.x.eq(pix.x + 1),
),
If(~lval,
pix.x.eq(0),
If(last_lval, last_x.eq(pix.x)),
If(last_fval & last_lval,
If(last_lval,
last_x.eq(pix.x),
pix.y.eq(pix.y + 1)
)
),
pix.x.eq(0)
),
If(~fval,
If(last_fval, last_y.eq(pix.y)),
If(last_fval,
last_y.eq(pix.y)
),
pix.y.eq(0)
)
]

View File

@ -104,7 +104,7 @@ class IIR(Module):
will be abbreviated W here.
It reads 1 << W.channels input channels (typically from an ADC)
and on each iteration processes the data on using a first-order IIR filter.
and on each iteration processes the data using a first-order IIR filter.
At the end of the cycle each the output of the filter together with
additional data (typically frequency tunning word and phase offset word
for a DDS) are presented at the 1 << W.channels outputs of the module.

View File

@ -154,7 +154,7 @@ class Opticlock(_StandaloneBase):
_StandaloneBase.__init__(self, hw_rev=hw_rev, **kwargs)
self.config["SI5324_AS_SYNTHESIZER"] = None
# self.config["SI5324_EXT_REF"] = None
self.config["SI5324_EXT_REF"] = None
self.config["RTIO_FREQUENCY"] = "125.0"
if hw_rev == "v1.0":
# EEM clock fan-out from Si5324, not MMCX
@ -306,6 +306,45 @@ class MITLL(_StandaloneBase):
self.rtio_crg.cd_rtio.clk, self.grabber0.deserializer.cd_cl.clk)
class MITLL2(_StandaloneBase):
def __init__(self, hw_rev=None, **kwargs):
if hw_rev is None:
hw_rev = "v1.1"
_StandaloneBase.__init__(self, hw_rev=hw_rev, **kwargs)
self.config["SI5324_AS_SYNTHESIZER"] = None
self.config["RTIO_FREQUENCY"] = "125.0"
if hw_rev == "v1.0":
# EEM clock fan-out from Si5324, not MMCX
self.comb += self.platform.request("clk_sel").eq(1)
self.rtio_channels = []
self.grabber_csr_group = []
eem.DIO.add_std(self, 5,
ttl_serdes_7series.InOut_8X, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 2, 1, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 4, 3, ttl_serdes_7series.Output_8X)
eem.Zotino.add_std(self, 6, ttl_serdes_7series.Output_8X)
eem.Zotino.add_std(self, 7, ttl_serdes_7series.Output_8X)
eem.Grabber.add_std(self, 0)
for i in (1, 2):
sfp_ctl = self.platform.request("sfp_ctl", i)
phy = ttl_simple.Output(sfp_ctl.led)
self.submodules += phy
self.rtio_channels.append(rtio.Channel.from_phy(phy))
self.config["HAS_RTIO_LOG"] = None
self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels)
self.rtio_channels.append(rtio.LogChannel())
self.add_rtio(self.rtio_channels)
self.config["HAS_GRABBER"] = None
self.add_csr_group("grabber", self.grabber_csr_group)
self.platform.add_false_path_constraints(
self.rtio_crg.cd_rtio.clk, self.grabber0.deserializer.cd_cl.clk)
class USTC(_StandaloneBase):
def __init__(self, hw_rev=None, **kwargs):
if hw_rev is None:
@ -573,11 +612,20 @@ class Tester(_StandaloneBase):
self.comb += self.platform.request("clk_sel").eq(1)
self.rtio_channels = []
self.grabber_csr_group = []
eem.DIO.add_std(self, 5,
ttl_serdes_7series.InOut_8X, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 1, 0, ttl_serdes_7series.Output_8X)
eem.Sampler.add_std(self, 3, 2, ttl_serdes_7series.Output_8X)
eem.Zotino.add_std(self, 4, ttl_serdes_7series.Output_8X)
eem.Grabber.add_std(self, 6)
eem.Urukul.add_std(self, 7, None, ttl_serdes_7series.Output_8X)
eem.DIO.add_std(self, 8,
ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X)
eem.DIO.add_std(self, 9,
ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X)
eem.Sampler.add_std(self, 10, None, ttl_serdes_7series.Output_8X)
eem.Zotino.add_std(self, 11, ttl_serdes_7series.Output_8X)
for i in (1, 2):
sfp_ctl = self.platform.request("sfp_ctl", i)
@ -588,9 +636,13 @@ class Tester(_StandaloneBase):
self.config["HAS_RTIO_LOG"] = None
self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels)
self.rtio_channels.append(rtio.LogChannel())
self.add_rtio(self.rtio_channels)
self.config["HAS_GRABBER"] = None
self.add_csr_group("grabber", self.grabber_csr_group)
self.platform.add_false_path_constraints(
self.rtio_crg.cd_rtio.clk, self.grabber0.deserializer.cd_cl.clk)
class _RTIOClockMultiplier(Module, AutoCSR):
def __init__(self, rtio_clk_freq):
@ -633,7 +685,7 @@ class _MasterBase(MiniSoC, AMPSoC):
}
mem_map.update(MiniSoC.mem_map)
def __init__(self, **kwargs):
def __init__(self, rtio_clk_freq=150e6, **kwargs):
MiniSoC.__init__(self,
cpu_type="or1k",
sdram_controller_type="minicon",
@ -645,7 +697,6 @@ class _MasterBase(MiniSoC, AMPSoC):
add_identifier(self)
platform = self.platform
rtio_clk_freq = 150e6
i2c = self.platform.request("i2c")
self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda])
@ -783,7 +834,7 @@ class _SatelliteBase(BaseSoC):
}
mem_map.update(BaseSoC.mem_map)
def __init__(self, **kwargs):
def __init__(self, rtio_clk_freq=150e6, **kwargs):
BaseSoC.__init__(self,
cpu_type="or1k",
sdram_controller_type="minicon",
@ -792,7 +843,6 @@ class _SatelliteBase(BaseSoC):
add_identifier(self)
platform = self.platform
rtio_clk_freq = 150e6
disable_si5324_ibuf = Signal(reset=1)
disable_si5324_ibuf.attr.add("no_retiming")
@ -872,7 +922,8 @@ class _SatelliteBase(BaseSoC):
self.submodules.siphaser = SiPhaser7Series(
si5324_clkin=platform.request("si5324_clkin"),
si5324_clkout_fabric=platform.request("si5324_clkout_fabric"),
ref_clk=self.crg.clk125_div2, ref_div2=True)
ref_clk=self.crg.clk125_div2, ref_div2=True,
rtio_clk_freq=rtio_clk_freq)
platform.add_false_path_constraints(
self.crg.cd_sys.clk, self.siphaser.mmcm_freerun_output)
self.csr_devices.append("siphaser")
@ -950,6 +1001,66 @@ class Satellite(_SatelliteBase):
self.add_rtio(self.rtio_channels)
class VLBAIMaster(_MasterBase):
def __init__(self, hw_rev=None, *args, **kwargs):
if hw_rev is None:
hw_rev = "v1.1"
_MasterBase.__init__(self, rtio_clk_freq=125e6, hw_rev=hw_rev, *args,
**kwargs)
self.rtio_channels = []
eem.DIO.add_std(self, 0,
ttl_serdes_7series.InOut_8X, ttl_serdes_7series.Output_8X)
eem.DIO.add_std(self, 1,
ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X)
eem.DIO.add_std(self, 2,
ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X)
eem.Sampler.add_std(self, 3, None, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 5, 4, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X)
for i in (0, 1):
phy = ttl_simple.Output(self.platform.request("user_led", i))
self.submodules += phy
self.rtio_channels.append(rtio.Channel.from_phy(phy))
eem.Zotino.add_std(self, 7, ttl_serdes_7series.Output_8X)
self.config["HAS_RTIO_LOG"] = None
self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels)
self.rtio_channels.append(rtio.LogChannel())
self.add_rtio(self.rtio_channels)
class VLBAISatellite(_SatelliteBase):
def __init__(self, hw_rev=None, *args, **kwargs):
if hw_rev is None:
hw_rev = "v1.1"
_SatelliteBase.__init__(self, rtio_clk_freq=125e6, hw_rev=hw_rev,
*args, **kwargs)
self.rtio_channels = []
eem.DIO.add_std(self, 0,
ttl_serdes_7series.InOut_8X, ttl_serdes_7series.Output_8X)
eem.DIO.add_std(self, 1,
ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X)
eem.DIO.add_std(self, 2,
ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X)
eem.Sampler.add_std(self, 3, None, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 5, 4, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X)
for i in (0, 1):
phy = ttl_simple.Output(self.platform.request("user_led", i))
self.submodules += phy
self.rtio_channels.append(rtio.Channel.from_phy(phy))
eem.Zotino.add_std(self, 7, ttl_serdes_7series.Output_8X)
self.add_rtio(self.rtio_channels)
def main():
parser = argparse.ArgumentParser(
description="ARTIQ device binary builder for Kasli systems")
@ -957,8 +1068,8 @@ def main():
soc_kasli_args(parser)
parser.set_defaults(output_dir="artiq_kasli")
variants = {cls.__name__.lower(): cls for cls in [
Opticlock, SUServo, SYSU, MITLL, USTC, Tsinghua, WIPM, PTB, HUB, LUH,
Tester, Master, Satellite]}
Opticlock, SUServo, SYSU, MITLL, MITLL2, USTC, Tsinghua, WIPM, PTB, HUB, LUH,
VLBAIMaster, VLBAISatellite, Tester, Master, Satellite]}
parser.add_argument("-V", "--variant", default="opticlock",
help="variant: {} (default: %(default)s)".format(
"/".join(sorted(variants.keys()))))

View File

@ -28,19 +28,20 @@ def kernel(arg=None, flags={}):
This decorator marks an object's method for execution on the core
device.
When a decorated method is called from the Python interpreter, the ``core``
When a decorated method is called from the Python interpreter, the :attr:`core`
attribute of the object is retrieved and used as core device driver. The
core device driver will typically compile, transfer and run the method
(kernel) on the device.
When kernels call another method:
- if the method is a kernel for the same core device, is it compiled
- if the method is a kernel for the same core device, it is compiled
and sent in the same binary. Calls between kernels happen entirely on
the device.
- if the method is a regular Python method (not a kernel), it generates
a remote procedure call (RPC) for execution on the host.
The decorator takes an optional parameter that defaults to ``core`` and
The decorator takes an optional parameter that defaults to :attr`core` and
specifies the name of the attribute to use as core device driver.
This decorator must be present in the global namespace of all modules using

View File

@ -230,7 +230,7 @@ class HasEnvironment:
instead: when the repository is scanned to build the list of
available experiments and when the dataset browser ``artiq_browser``
is used to open or run the analysis stage of an experiment. Do not
rely on being able to operate on devices or arguments in ``build()``.
rely on being able to operate on devices or arguments in :meth:`build`.
Datasets are read-only in this method.
@ -328,8 +328,10 @@ class HasEnvironment:
By default, datasets obtained by this method are archived into the output
HDF5 file of the experiment. If an archived dataset is requested more
than one time (and therefore its value has potentially changed) or is
modified, a warning is emitted. Archival can be turned off by setting
the ``archive`` argument to ``False``.
modified, a warning is emitted.
:param archive: Set to ``False`` to prevent archival together with the run's results.
Default is ``True``
"""
try:
return self.__dataset_mgr.get(key, archive)
@ -355,7 +357,7 @@ class Experiment:
"""Entry point for pre-computing data necessary for running the
experiment.
Doing such computations outside of ``run`` enables more efficient
Doing such computations outside of :meth:`run` enables more efficient
scheduling of multiple experiments that need to access the shared
hardware during part of their execution.
@ -371,8 +373,8 @@ class Experiment:
This method may interact with the hardware.
The experiment may call the scheduler's ``pause`` method while in
``run``.
The experiment may call the scheduler's :meth:`pause` method while in
:meth:`run`.
"""
raise NotImplementedError
@ -382,7 +384,7 @@ class Experiment:
This method may be overloaded by the user to implement the analysis
phase of the experiment, for example fitting curves.
Splitting this phase from ``run`` enables tweaking the analysis
Splitting this phase from :meth:`run` enables tweaking the analysis
algorithm on pre-existing data, and CPU-bound analyses to be run
overlapped with the next experiment in a pipelined manner.
@ -392,13 +394,14 @@ class Experiment:
class EnvExperiment(Experiment, HasEnvironment):
"""Base class for top-level experiments that use the ``HasEnvironment``
environment manager.
"""Base class for top-level experiments that use the
:class:`~artiq.language.environment.HasEnvironment` environment manager.
Most experiments should derive from this class."""
def prepare(self):
"""The default prepare method calls prepare for all children, in the
order of instantiation, if the child has a prepare method."""
"""This default prepare method calls :meth:`~artiq.language.environment.Experiment.prepare`
for all children, in the order of instantiation, if the child has a
:meth:`~artiq.language.environment.Experiment.prepare` method."""
for child in self.children:
if hasattr(child, "prepare"):
child.prepare()

View File

@ -6,7 +6,8 @@ class AsyncioServer:
"""Generic TCP server based on asyncio.
Users of this class must derive from it and define the
``_handle_connection_cr`` method and coroutine.
:meth:`~artiq.protocols.asyncio_server.AsyncioServer._handle_connection_cr`
method/coroutine.
"""
def __init__(self):
self._client_tasks = set()
@ -14,10 +15,10 @@ class AsyncioServer:
async def start(self, host, port):
"""Starts the server.
The user must call ``stop`` to free resources properly after this
method completes successfully.
The user must call :meth:`stop`
to free resources properly after this method completes successfully.
This method is a `coroutine`.
This method is a *coroutine*.
:param host: Bind address of the server (see ``asyncio.start_server``
from the Python standard library).
@ -48,3 +49,6 @@ class AsyncioServer:
task = asyncio.ensure_future(self._handle_connection_cr(reader, writer))
self._client_tasks.add(task)
task.add_done_callback(self._client_done)
async def _handle_connection_cr(self, reader, writer):
raise NotImplementedError

View File

@ -1,7 +1,7 @@
"""
This module provides a remote procedure call (RPC) mechanism over sockets
between conventional computers (PCs) running Python. It strives to be
transparent and uses ``artiq.protocols.pyon`` internally so that e.g. Numpy
transparent and uses :mod:`artiq.protocols.pyon` internally so that e.g. Numpy
arrays can be easily used.
Note that the server operates on copies of objects provided by the client,
@ -61,18 +61,18 @@ class Client:
can be used as if they were local methods.
For example, if the server provides method ``foo``, and ``c`` is a local
``Client`` object, then the method can be called as: ::
:class:`.Client` object, then the method can be called as: ::
result = c.foo(param1, param2)
The parameters and the result are automatically transferred with the
The parameters and the result are automatically transferred from the
server.
Only methods are supported. Attributes must be accessed by providing and
using "get" and/or "set" methods on the server side.
At object initialization, the connection to the remote server is
automatically attempted. The user must call ``close_rpc`` to
automatically attempted. The user must call :meth:`~artiq.protocols.pc_rpc.Client.close_rpc` to
free resources properly after initialization completes successfully.
:param host: Identifier of the server. The string can represent a
@ -81,11 +81,11 @@ class Client:
:param port: TCP port to use.
:param target_name: Target name to select. ``IncompatibleServer`` is
raised if the target does not exist.
Use ``AutoTarget`` for automatic selection if the server has only one
Use :class:`.AutoTarget` for automatic selection if the server has only one
target.
Use ``None`` to skip selecting a target. The list of targets can then
be retrieved using ``get_rpc_id`` and then one can be selected later
using ``select_rpc_target``.
be retrieved using :meth:`~artiq.protocols.pc_rpc.Client.get_rpc_id`
and then one can be selected later using :meth:`~artiq.protocols.pc_rpc.Client.select_rpc_target`.
:param timeout: Socket operation timeout. Use ``None`` for blocking
(default), ``0`` for non-blocking, and a finite value to raise
``socket.timeout`` if an operation does not complete within the
@ -198,7 +198,7 @@ class AsyncioClient:
async def connect_rpc(self, host, port, target_name):
"""Connects to the server. This cannot be done in __init__ because
this method is a coroutine. See ``Client`` for a description of the
this method is a coroutine. See :class:`artiq.protocols.pc_rpc.Client` for a description of the
parameters."""
self.__reader, self.__writer = \
await asyncio.open_connection(host, port, limit=100*1024*1024)
@ -447,7 +447,8 @@ class _PrettyPrintCall:
class Server(_AsyncioServer):
"""This class creates a TCP server that handles requests coming from
``Client`` objects.
*Client* objects (whether :class:`.Client`, :class:`.BestEffortClient`,
or :class:`.AsyncioClient`).
The server is designed using ``asyncio`` so that it can easily support
multiple connections without the locking issues that arise in
@ -591,7 +592,7 @@ def simple_server_loop(targets, host, port, description=None):
"""Runs a server until an exception is raised (e.g. the user hits Ctrl-C)
or termination is requested by a client.
See ``Server`` for a description of the parameters.
See :class:`artiq.protocols.pc_rpc.Server` for a description of the parameters.
"""
loop = asyncio.get_event_loop()
try:

View File

@ -7,10 +7,10 @@ large amounts of data with it, and only exchange higher-level, processed data
with the experiment (and over the network).
Controllers with support for remote execution contain an additional target
that gives RPC access to instances of ``RemoteExecServer``. One such instance
that gives RPC access to instances of :class:`.RemoteExecServer`. One such instance
is created per client (experiment) connection and manages one Python namespace
in which the experiment can execute arbitrary code by calling the methods of
``RemoteExecServer``.
:class:`.RemoteExecServer`.
The namespaces are initialized with the following global values:

View File

@ -117,10 +117,10 @@ class Subscriber:
class Notifier:
"""Encapsulates a structure whose changes need to be published.
All mutations to the structure must be made through the ``Notifier``. The
All mutations to the structure must be made through the :class:`.Notifier`. The
original structure must only be accessed for reads.
In addition to the list methods below, the ``Notifier`` supports the index
In addition to the list methods below, the :class:`.Notifier` supports the index
syntax for modification and deletion of elements. Modification of nested
structures can be also done using the index syntax, for example:
@ -131,11 +131,11 @@ class Notifier:
[[42]]
This class does not perform any network I/O and is meant to be used with
e.g. the ``Publisher`` for this purpose. Only one publisher at most can be
associated with a ``Notifier``.
e.g. the :class:`.Publisher` for this purpose. Only one publisher at most can be
associated with a :class:`.Notifier`.
:param backing_struct: Structure to encapsulate. For convenience, it
also becomes available as the ``read`` property of the ``Notifier``.
also becomes available as the ``read`` property of the :class:`.Notifier`.
"""
def __init__(self, backing_struct, root=None, path=[]):
self.read = backing_struct
@ -168,7 +168,7 @@ class Notifier:
def pop(self, i=-1):
"""Pop an element from a list. The returned element is not
encapsulated in a ``Notifier`` and its mutations are no longer
encapsulated in a :class:`.Notifier` and its mutations are no longer
tracked."""
r = self._backing_struct.pop(i)
if self.root.publish is not None:
@ -199,11 +199,11 @@ class Notifier:
class Publisher(AsyncioServer):
"""A network server that publish changes to structures encapsulated in
``Notifiers``.
a :class:`.Notifier`.
:param notifiers: A dictionary containing the notifiers to associate with
the ``Publisher``. The keys of the dictionary are the names of the
notifiers to be used with ``Subscriber``.
the :class:`.Publisher`. The keys of the dictionary are the names of the
notifiers to be used with :class:`.Subscriber`.
"""
def __init__(self, notifiers):
AsyncioServer.__init__(self)
@ -245,7 +245,7 @@ class Publisher(AsyncioServer):
finally:
self._recipients[notifier_name].remove(queue)
except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError):
# subscribers disconnecting are a normal occurence
# subscribers disconnecting are a normal occurrence
pass
finally:
writer.close()

View File

@ -1,7 +1,7 @@
Core language reference
=======================
The most commonly used features from the ARTIQ language modules and from the core device modules are bundled together in ``artiq.experiment`` and can be imported with ``from artiq.experiment import *``.
The most commonly used features from the ARTIQ language modules and from the core device modules are bundled together in :mod:`artiq.experiment` and can be imported with ``from artiq.experiment import *``.
:mod:`artiq.language.core` module
---------------------------------

View File

@ -95,9 +95,9 @@ and the ARTIQ kernels.
* Install LLVM and Clang: ::
$ cd ~/artiq-dev
$ git clone -b artiq-4.0 https://github.com/m-labs/llvm-or1k
$ git clone -b artiq-6.0 https://github.com/m-labs/llvm-or1k
$ cd llvm-or1k
$ git clone -b artiq-4.0 https://github.com/m-labs/clang-or1k tools/clang
$ git clone -b artiq-6.0 https://github.com/m-labs/clang-or1k tools/clang
$ mkdir build
$ cd build
@ -108,7 +108,7 @@ and the ARTIQ kernels.
* Install Rust: ::
$ cd ~/artiq-dev
$ git clone -b artiq-1.23.0 https://github.com/m-labs/rust
$ git clone -b artiq-1.28.0 https://github.com/m-labs/rust
$ cd rust
$ git submodule update --init --recursive
$ mkdir build
@ -118,18 +118,18 @@ and the ARTIQ kernels.
$ sudo chown $USER.$USER /usr/local/rust-or1k
$ make install
$ libs="core std_unicode alloc"
$ rustc="/usr/local/rust-or1k/bin/rustc --target or1k-unknown-none -C target-feature=+mul,+div,+ffl1,+cmov,+addc -C opt-level=s -g --crate-type rlib -L ."
$ destdir="/usr/local/rust-or1k/lib/rustlib/or1k-unknown-none/lib/"
$ mkdir ../build-or1k
$ cd ../build-or1k
$ for lib in ${libs}; do ${rustc} --crate-name ${lib} ../src/lib${lib}/lib.rs; done
$ ${rustc} --crate-name libc ../src/liblibc_mini/lib.rs
$ ${rustc} --crate-name unwind ../src/libunwind/lib.rs
$ ${rustc} -Cpanic=abort --crate-name panic_abort ../src/libpanic_abort/lib.rs
$ ${rustc} -Cpanic=unwind --crate-name panic_unwind ../src/libpanic_unwind/lib.rs --cfg llvm_libunwind
$ rustc="rustc --out-dir ${destdir} -L ${destdir} --target or1k-unknown-none -g -C target-feature=+mul,+div,+ffl1,+cmov,+addc -C opt-level=s --crate-type rlib"
$ mkdir -p ${destdir}
$ cp *.rlib ${destdir}
$ ${rustc} --crate-name core src/libcore/lib.rs
$ ${rustc} --crate-name compiler_builtins src/libcompiler_builtins/src/lib.rs --cfg $ 'feature="compiler-builtins"' --cfg 'feature="mem"'
$ ${rustc} --crate-name std_unicode src/libstd_unicode/lib.rs
$ ${rustc} --crate-name alloc src/liballoc/lib.rs
$ ${rustc} --crate-name libc src/liblibc_mini/lib.rs
$ ${rustc} --crate-name unwind src/libunwind/lib.rs
$ ${rustc} -Cpanic=abort --crate-name panic_abort src/libpanic_abort/lib.rs
$ ${rustc} -Cpanic=unwind --crate-name panic_unwind src/libpanic_unwind/lib.rs \
--cfg llvm_libunwind
.. note::
Compilation of LLVM can take more than 30 min on some machines. Compilation of Rust can take more than two hours.

View File

@ -1,5 +1,5 @@
Developing a network device support package
===========================================
Developing a Network Device Support Package (NDSP)
==================================================
Most ARTIQ devices are interfaced through "controllers" that expose RPC interfaces to the network (based on :class:`artiq.protocols.pc_rpc`). The master never does direct I/O to the devices, but issues RPCs to the controllers when needed. As opposed to running everything on the master, this architecture has those main advantages:

View File

@ -10,7 +10,7 @@ The device database
The device database contains information about the devices available in a ARTIQ installation, what drivers to use, what controllers to use and on what machine, and where the devices are connected.
The master (or ``artiq_run``) instantiates the device drivers (and the RPC clients in the case of controllers) for the experiments based on the contents of the device database.
The master (or :mod:`~artiq.frontend.artiq_run`) instantiates the device drivers (and the RPC clients in the case of controllers) for the experiments based on the contents of the device database.
The device database is stored in the memory of the master and is generated by a Python script typically called ``device_db.py``. That script must define a global variable ``device_db`` with the contents of the database. The device database is a Python dictionary whose keys are the device names, and values can have several types.

View File

@ -21,7 +21,7 @@ As a very first step, we will turn on a LED on the core device. Create a file ``
self.core.reset()
self.led.on()
The central part of our code is our ``LED`` class, that derives from :class:`artiq.language.environment.EnvExperiment`. Among other features, ``EnvExperiment`` calls our ``build`` method and provides the ``setattr_device`` method that interfaces to the device database to create the appropriate device drivers and make those drivers accessible as ``self.core`` and ``self.led``. The ``@kernel`` decorator tells the system that the ``run`` method must be compiled for and executed on the core device (instead of being interpreted and executed as regular Python code on the host). The decorator uses ``self.core`` internally, which is why we request the core device using ``setattr_device`` like any other.
The central part of our code is our ``LED`` class, which derives from :class:`artiq.language.environment.EnvExperiment`. Among other features, :class:`~artiq.language.environment.EnvExperiment` calls our :meth:`~artiq.language.environment.Experiment.build` method and provides the :meth:`~artiq.language.environment.HasEnvironment.setattr_device` method that interfaces to the device database to create the appropriate device drivers and make those drivers accessible as ``self.core`` and ``self.led``. The :func:`~artiq.language.core.kernel` decorator (``@kernel``) tells the system that the :meth:`~artiq.language.environment.Experiment.run` method must be compiled for and executed on the core device (instead of being interpreted and executed as regular Python code on the host). The decorator uses ``self.core`` internally, which is why we request the core device using :meth:`~artiq.language.environment.HasEnvironment.setattr_device` like any other.
Copy the file ``device_db.py`` (containing the device database) from the ``examples/master`` folder of ARTIQ into the same directory as ``led.py`` (alternatively, you can use the ``--device-db`` option of ``artiq_run``). You will probably want to set the IP address of the core device in ``device_db.py`` so that the computer can connect to it (it is the ``host`` parameter of the ``comm`` entry). See :ref:`device-db` for more information. The example device database is designed for the ``nist_clock`` hardware adapter on the KC705; see :ref:`board-ports` for RTIO channel assignments if you need to adapt the device database to a different hardware platform.
@ -69,18 +69,18 @@ You can then turn the LED off and on by entering 0 or 1 at the prompt that appea
$ artiq_run led.py
Enter desired LED state: 0
What happens is the ARTIQ compiler notices that the ``input_led_state`` function does not have a ``@kernel`` decorator and thus must be executed on the host. When the core device calls it, it sends a request to the host to execute it. The host displays the prompt, collects user input, and sends the result back to the core device, which sets the LED state accordingly.
What happens is the ARTIQ compiler notices that the :meth:`input_led_state` function does not have a ``@kernel`` decorator (:func:`~artiq.language.core.kernel`) and thus must be executed on the host. When the core device calls it, it sends a request to the host to execute it. The host displays the prompt, collects user input, and sends the result back to the core device, which sets the LED state accordingly.
RPC functions must always return a value of the same type. When they return a value that is not ``None``, the compiler should be informed in advance of the type of the value, which is what the ``-> TBool`` annotation is for.
Without the ``break_realtime()`` call, the RTIO events emitted by ``self.led.on()`` or ``self.led.off()`` would be scheduled at a fixed and very short delay after entering ``run()``.
These events would fail because the RPC to ``input_led_state()`` can take an arbitrary amount of time and therefore the deadline for submission of RTIO events would have long passed when ``self.led.on()`` or ``self.led.off()`` are called.
The ``break_realtime()`` call is necessary to waive the real-time requirements of the LED state change.
Without the :meth:`~artiq.coredevice.core.Core.break_realtime` call, the RTIO events emitted by :func:`self.led.on()` or :func:`self.led.off()` would be scheduled at a fixed and very short delay after entering :meth:`~artiq.language.environment.Experiment.run()`.
These events would fail because the RPC to :meth:`input_led_state()` can take an arbitrary amount of time and therefore the deadline for submission of RTIO events would have long passed when :func:`self.led.on()` or :func:`self.led.off()` are called.
The :meth:`~artiq.coredevice.core.Core.break_realtime` call is necessary to waive the real-time requirements of the LED state change.
It advances the timeline far enough to ensure that events can meet the submission deadline.
Real-time I/O (RTIO)
--------------------
Real-time Input/Output (RTIO)
-----------------------------
The point of running code on the core device is the ability to meet demanding real-time constraints. In particular, the core device can respond to an incoming stimulus or the result of a measurement with a low and predictable latency. We will see how to use inputs later; first, we must familiarize ourselves with how time is managed in kernels.
@ -102,11 +102,11 @@ Create a new file ``rtio.py`` containing the following: ::
delay(2*us)
self.ttl0.pulse(2*us)
In its ``build()`` method, the experiment obtains the core device and a TTL device called ``ttl0`` as defined in the device database.
In its :meth:`~artiq.language.environment.Experiment.build` method, the experiment obtains the core device and a TTL device called ``ttl0`` as defined in the device database.
In ARTIQ, TTL is used roughly synonymous with "a single generic digital signal" and does not refer to a specific signaling standard or voltage/current levels.
When ``run()``, the experiment first ensures that ``ttl0`` is in output mode and actively driving the device it is connected to.
Bidirectional TTL channels (i.e. ``TTLInOut``) are in input (high impedance) mode by default, output-only TTL channels (``TTLOut``) are always in output mode.
When :meth:`~artiq.language.environment.Experiment.run`, the experiment first ensures that ``ttl0`` is in output mode and actively driving the device it is connected to.
Bidirectional TTL channels (i.e. :class:`~artiq.devices.ttl.TTLInOut`) are in input (high impedance) mode by default, output-only TTL channels (:class:`~artiq.devices.ttl.TTLOut`) are always in output mode.
There are no input-only TTL channels.
The experiment then drives one million 2 µs long pulses separated by 2 µs each.
@ -118,7 +118,7 @@ Any asymmetry in the overhead would manifest itself in a distorted and variable
Instead, inside the core device, output timing is generated by the gateware and the CPU only programs switching commands with certain timestamps that the CPU computes.
This guarantees precise timing as long as the CPU can keep generating timestamps that are increasing fast enough. In case it fails to do that (and attempts to program an event with a timestamp smaller than the current RTIO clock timestamp), a :class:`artiq.coredevice.exceptions.RTIOUnderflow` exception is raised. The kernel causing it may catch it (using a regular ``try... except...`` construct), or it will be propagated to the host.
This guarantees precise timing as long as the CPU can keep generating timestamps that are increasing fast enough. In case it fails to do that (and attempts to program an event with a timestamp smaller than the current RTIO clock timestamp), a :exc:`~artiq.coredevice.exceptions.RTIOUnderflow` exception is raised. The kernel causing it may catch it (using a regular ``try... except...`` construct), or it will be propagated to the host.
Try reducing the period of the generated waveform until the CPU cannot keep up with the generation of switching events and the underflow exception is raised. Then try catching it: ::
@ -161,7 +161,7 @@ ARTIQ can implement ``with parallel`` blocks without having to resort to any of
It simply remembers the position on the timeline when entering the ``parallel`` block and then seeks back to that position after submitting the events generated by each statement.
In other words, the statements in the ``parallel`` block are actually executed sequentially, only the RTIO events generated by them are scheduled to be executed in parallel.
Note that if a statement takes a lot of CPU time to execute (this different from the events scheduled by a statement taking a long time), it may cause a subsequent statement to miss the deadline for timely submission of its events.
This then causes a ``RTIOUnderflow`` exception to be raised.
This then causes a :exc:`~artiq.coredevice.exceptions.RTIOUnderflow` exception to be raised.
Within a parallel block, some statements can be made sequential again using a ``with sequential`` construct. Observe the pulses generated by this code: ::
@ -199,8 +199,8 @@ The core device records the real-time I/O waveforms into a circular buffer. It i
Afterwards, the recorded data can be extracted and written to a VCD file using ``artiq_coreanalyzer -w rtio.vcd`` (see: :ref:`core-device-rtio-analyzer-tool`). VCD files can be viewed using third-party tools such as GtkWave.
DMA
---
Direct Memory Access (DMA)
--------------------------
DMA allows you to store fixed sequences of pulses in system memory, and have the DMA core in the FPGA play them back at high speed. Pulse sequences that are too fast for the CPU (i.e. would cause RTIO underflows) can still be generated using DMA. The only modification of the sequence that the DMA core supports is shifting it in time (so it can be played back at any position of the timeline), everything else is fixed at the time of recording the sequence.

263
doc/manual/make.bat Normal file
View File

@ -0,0 +1,263 @@
@ECHO OFF
REM Command file for Sphinx documentation
REM TODO: missing latexpdf and latexpdfja cmds of ./Makefile
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. epub3 to make an epub3
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. xml to make Docutils-native XML files
echo. pseudoxml to make pseudoxml-XML files for display purposes
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
REM Check if sphinx-build is available and fallback to Python version if any
%SPHINXBUILD% 1>NUL 2>NUL
if errorlevel 9009 goto sphinx_python
goto sphinx_ok
:sphinx_python
set SPHINXBUILD=python -m sphinx.__init__
%SPHINXBUILD% 2> nul
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
:sphinx_ok
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
REM echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ARTIQ.qhcp
REM echo.To view the help file:
REM echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ARTIQ.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "epub3" (
%SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdf" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdfja" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf-ja
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
if "%1" == "xml" (
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The XML files are in %BUILDDIR%/xml.
goto end
)
if "%1" == "pseudoxml" (
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
goto end
)
:end

View File

@ -1,7 +1,7 @@
Management system
=================
The management system described below is optional: experiments can be run one by one using ``artiq_run``, and the controllers can run stand-alone (without a controller manager). For their very first steps with ARTIQ or in simple or particular cases, users do not need to deploy the management system.
The management system described below is optional: experiments can be run one by one using :mod:`~artiq.frontend.artiq_run`, and the controllers can run stand-alone (without a controller manager). For their very first steps with ARTIQ or in simple or particular cases, users do not need to deploy the management system.
Components
**********
@ -42,13 +42,15 @@ Basics
To use hardware resources more efficiently, potentially compute-intensive pre-computation and analysis phases of other experiments is executed in parallel with the body of the current experiment that accesses the hardware.
.. seealso:: These steps are implemented in :class:`~artiq.language.environment.Experiment`. However, user-written experiments should usually derive from (sub-class) :class:`artiq.language.environment.EnvExperiment`.
Experiments are divided into three phases that are programmed by the user:
1. The preparation stage, that pre-fetches and pre-computes any data that necessary to run the experiment. Users may implement this stage by overloading the ``prepare`` method. It is not permitted to access hardware in this stage, as doing so may conflict with other experiments using the same devices.
2. The running stage, that corresponds to the body of the experiment, and typically accesses hardware. Users must implement this stage and overload the ``run`` method.
3. The analysis stage, where raw results collected in the running stage are post-processed and may lead to updates of the parameter database. This stage may be implemented by overloading the ``analyze`` method.
1. The **preparation** stage, that pre-fetches and pre-computes any data that necessary to run the experiment. Users may implement this stage by overloading the :meth:`~artiq.language.environment.Experiment.prepare` method. It is not permitted to access hardware in this stage, as doing so may conflict with other experiments using the same devices.
2. The **running** stage, that corresponds to the body of the experiment, and typically accesses hardware. Users must implement this stage and overload the :meth:`~artiq.language.environment.Experiment.run` method.
3. The **analysis** stage, where raw results collected in the running stage are post-processed and may lead to updates of the parameter database. This stage may be implemented by overloading the :meth:`~artiq.language.environment.Experiment.analyze` method.
.. note:: Only the ``run`` method implementation is mandatory; if the experiment does not fit into the pipelined scheduling model, it can leave one or both of the other methods empty (which is the default).
.. note:: Only the :meth:`~artiq.language.environment.Experiment.run` method implementation is mandatory; if the experiment does not fit into the pipelined scheduling model, it can leave one or both of the other methods empty (which is the default).
The three phases of several experiments are then executed in a pipelined manner by the scheduler in the ARTIQ master: experiment A executes its preparation stage, then experiment A executes its running stage while experiment B executes its preparation stage, and so on.
@ -70,11 +72,11 @@ If there are other experiments with higher priority (e.g. a high-priority timed
Otherwise, ``pause()`` returns immediately.
To check whether ``pause()`` would in fact *not* return immediately, use :meth:`artiq.master.scheduler.Scheduler.check_pause`.
The experiment must place the hardware in a safe state and disconnect from the core device (typically, by using ``self.core.comm.close()``) before calling ``pause``.
The experiment must place the hardware in a safe state and disconnect from the core device (typically, by calling ``self.coredevice.comm.close()`` from the kernel, which is equivalent to :meth:`artiq.coredevice.core.Core.close`) before calling ``pause()``.
Accessing the ``pause`` and ``check_pause`` methods is done through a virtual device called ``scheduler`` that is accessible to all experiments. The scheduler virtual device is requested like regular devices using ``get_device`` or ``attr_device``.
Accessing the ``pause()`` and :meth:`~artiq.master.scheduler.Scheduler.check_pause` methods is done through a virtual device called ``scheduler`` that is accessible to all experiments. The scheduler virtual device is requested like regular devices using :meth:`~artiq.language.environment.HasEnvironment.get_device` (``self.get_device()``) or :meth:`~artiq.language.environment.HasEnvironment.setattr_device` (``self.setattr_device()``).
``check_pause`` can be called (via RPC) from a kernel, but ``pause`` must not.
:meth:`~artiq.master.scheduler.Scheduler.check_pause` can be called (via RPC) from a kernel, but ``pause()`` must not.
Multiple pipelines
------------------