mirror of https://github.com/m-labs/artiq.git
Merge branch 'master' into nac3
This commit is contained in:
commit
826281a529
|
@ -3,13 +3,15 @@
|
||||||
Release notes
|
Release notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Unreleased
|
ARTIQ-8 (Unreleased)
|
||||||
----------
|
--------------------
|
||||||
|
|
||||||
Highlights:
|
Highlights:
|
||||||
|
|
||||||
* Implemented Phaser-servo. This requires recent gateware on Phaser.
|
* Implemented Phaser-servo. This requires recent gateware on Phaser.
|
||||||
|
* Implemented Phaser-MIQRO support. This requires the Phaser MIQRO gateware
|
||||||
|
variant.
|
||||||
|
* MSYS2 packaging for Windows.
|
||||||
|
|
||||||
ARTIQ-7
|
ARTIQ-7
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -800,8 +800,14 @@ class CommKernel:
|
||||||
else:
|
else:
|
||||||
python_exn_type = embedding_map.retrieve_object(core_exn.id)
|
python_exn_type = embedding_map.retrieve_object(core_exn.id)
|
||||||
|
|
||||||
|
try:
|
||||||
python_exn = python_exn_type(
|
python_exn = python_exn_type(
|
||||||
nested_exceptions[-1][1].format(*nested_exceptions[0][2]))
|
nested_exceptions[-1][1].format(*nested_exceptions[0][2]))
|
||||||
|
except Exception as ex:
|
||||||
|
python_exn = RuntimeError(
|
||||||
|
f"Exception type={python_exn_type}, which couldn't be "
|
||||||
|
f"reconstructed ({ex})"
|
||||||
|
)
|
||||||
python_exn.artiq_core_exception = core_exn
|
python_exn.artiq_core_exception = core_exn
|
||||||
raise python_exn
|
raise python_exn
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,10 @@ from artiq.coredevice.trf372017 import TRF372017
|
||||||
|
|
||||||
|
|
||||||
PHASER_BOARD_ID = 19
|
PHASER_BOARD_ID = 19
|
||||||
|
|
||||||
|
PHASER_GW_BASE = 1
|
||||||
|
PHASER_GW_MIQRO = 2
|
||||||
|
|
||||||
PHASER_ADDR_BOARD_ID = 0x00
|
PHASER_ADDR_BOARD_ID = 0x00
|
||||||
PHASER_ADDR_HW_REV = 0x01
|
PHASER_ADDR_HW_REV = 0x01
|
||||||
PHASER_ADDR_GW_REV = 0x02
|
PHASER_ADDR_GW_REV = 0x02
|
||||||
|
@ -48,6 +52,12 @@ PHASER_ADDR_SERVO_CFG1 = 0x31
|
||||||
# 0x32 - 0x71 servo coefficients + offset data
|
# 0x32 - 0x71 servo coefficients + offset data
|
||||||
PHASER_ADDR_SERVO_DATA_BASE = 0x32
|
PHASER_ADDR_SERVO_DATA_BASE = 0x32
|
||||||
|
|
||||||
|
# 0x72 - 0x78 Miqro channel profile/window memories
|
||||||
|
PHASER_ADDR_MIQRO_MEM_ADDR = 0x72
|
||||||
|
PHASER_ADDR_MIQRO_MEM_DATA = 0x74
|
||||||
|
|
||||||
|
# Miqro profile memory select
|
||||||
|
PHASER_MIQRO_SEL_PROFILE = 1 << 14
|
||||||
|
|
||||||
PHASER_SEL_DAC = 1 << 0
|
PHASER_SEL_DAC = 1 << 0
|
||||||
PHASER_SEL_TRF0 = 1 << 1
|
PHASER_SEL_TRF0 = 1 << 1
|
||||||
|
@ -80,6 +90,26 @@ class Phaser:
|
||||||
Phaser contains a 4 channel, 1 GS/s DAC chip with integrated upconversion,
|
Phaser contains a 4 channel, 1 GS/s DAC chip with integrated upconversion,
|
||||||
quadrature modulation compensation and interpolation features.
|
quadrature modulation compensation and interpolation features.
|
||||||
|
|
||||||
|
The coredevice RTIO PHY and the Phaser gateware come in different modes
|
||||||
|
that have different features. Phaser mode and coredevice PHY mode are both
|
||||||
|
selected at their respective gateware compile-time and need to match.
|
||||||
|
|
||||||
|
=============== ============== ===================================
|
||||||
|
Phaser gateware Coredevice PHY Features per :class:`PhaserChannel`
|
||||||
|
=============== ============== ===================================
|
||||||
|
Base <= v0.5 Base Base (5 :class:`PhaserOscillator`)
|
||||||
|
Base >= v0.6 Base Base + Servo
|
||||||
|
Miqro >= v0.6 Miqro :class:`Miqro`
|
||||||
|
=============== ============== ===================================
|
||||||
|
|
||||||
|
The coredevice driver (this class and :class:`PhaserChannel`) exposes
|
||||||
|
the superset of all functionality regardless of the Coredevice RTIO PHY
|
||||||
|
or Phaser gateware modes. This is to evade type unification limitations.
|
||||||
|
Features absent in Coredevice PHY/Phaser gateware will not work and
|
||||||
|
should not be accessed.
|
||||||
|
|
||||||
|
**Base mode**
|
||||||
|
|
||||||
The coredevice produces 2 IQ (in-phase and quadrature) data streams with 25
|
The coredevice produces 2 IQ (in-phase and quadrature) data streams with 25
|
||||||
MS/s and 14 bit per quadrature. Each data stream supports 5 independent
|
MS/s and 14 bit per quadrature. Each data stream supports 5 independent
|
||||||
numerically controlled IQ oscillators (NCOs, DDSs with 32 bit frequency, 16
|
numerically controlled IQ oscillators (NCOs, DDSs with 32 bit frequency, 16
|
||||||
|
@ -110,6 +140,14 @@ class Phaser:
|
||||||
absolute phase with respect to other RTIO input and output events
|
absolute phase with respect to other RTIO input and output events
|
||||||
(see `get_next_frame_mu()`).
|
(see `get_next_frame_mu()`).
|
||||||
|
|
||||||
|
**Miqro mode**
|
||||||
|
|
||||||
|
See :class:`Miqro`
|
||||||
|
|
||||||
|
Here the DAC operates in 4x interpolation.
|
||||||
|
|
||||||
|
**Analog flow**
|
||||||
|
|
||||||
The four analog DAC outputs are passed through anti-aliasing filters.
|
The four analog DAC outputs are passed through anti-aliasing filters.
|
||||||
|
|
||||||
In the baseband variant, the even/in-phase DAC channels feed 31.5 dB range
|
In the baseband variant, the even/in-phase DAC channels feed 31.5 dB range
|
||||||
|
@ -127,6 +165,8 @@ class Phaser:
|
||||||
configured through a shared SPI bus that is accessed and controlled via
|
configured through a shared SPI bus that is accessed and controlled via
|
||||||
FPGA registers.
|
FPGA registers.
|
||||||
|
|
||||||
|
**Servo**
|
||||||
|
|
||||||
Each phaser output channel features a servo to control the RF output amplitude
|
Each phaser output channel features a servo to control the RF output amplitude
|
||||||
using feedback from an ADC. The servo consists of a first order IIR (infinite
|
using feedback from an ADC. The servo consists of a first order IIR (infinite
|
||||||
impulse response) filter fed by the ADC and a multiplier that scales the I
|
impulse response) filter fed by the ADC and a multiplier that scales the I
|
||||||
|
@ -196,6 +236,7 @@ class Phaser:
|
||||||
clk_sel: Kernel[bool]
|
clk_sel: Kernel[bool]
|
||||||
tune_fifo_offset: Kernel[bool]
|
tune_fifo_offset: Kernel[bool]
|
||||||
sync_dly: Kernel[int32]
|
sync_dly: Kernel[int32]
|
||||||
|
gw_rev: Kernel[int32]
|
||||||
dac_mmap: KernelInvariant[list[int32]]
|
dac_mmap: KernelInvariant[list[int32]]
|
||||||
channel: Kernel[list[PhaserChannel]]
|
channel: Kernel[list[PhaserChannel]]
|
||||||
|
|
||||||
|
@ -214,6 +255,7 @@ class Phaser:
|
||||||
self.clk_sel = clk_sel
|
self.clk_sel = clk_sel
|
||||||
self.tune_fifo_offset = tune_fifo_offset
|
self.tune_fifo_offset = tune_fifo_offset
|
||||||
self.sync_dly = sync_dly
|
self.sync_dly = sync_dly
|
||||||
|
self.gw_rev = -1 # discovered in init()
|
||||||
|
|
||||||
self.dac_mmap = DAC34H84(dac).get_mmap()
|
self.dac_mmap = DAC34H84(dac).get_mmap()
|
||||||
self.dac_mmap = [int32(uint32(x)) for x in self.dac_mmap] # NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/14
|
self.dac_mmap = [int32(uint32(x)) for x in self.dac_mmap] # NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/14
|
||||||
|
@ -238,9 +280,9 @@ class Phaser:
|
||||||
self.core.delay(.1*ms) # slack
|
self.core.delay(.1*ms) # slack
|
||||||
is_baseband = hw_rev & PHASER_HW_REV_VARIANT != 0
|
is_baseband = hw_rev & PHASER_HW_REV_VARIANT != 0
|
||||||
|
|
||||||
gw_rev = self.read8(PHASER_ADDR_GW_REV)
|
self.gw_rev = self.read8(PHASER_ADDR_GW_REV)
|
||||||
if debug:
|
if debug:
|
||||||
print_rpc(("gw_rev:", gw_rev))
|
print_rpc(("gw_rev:", self.gw_rev))
|
||||||
self.core.break_realtime()
|
self.core.break_realtime()
|
||||||
self.core.delay(.1*ms) # slack
|
self.core.delay(.1*ms) # slack
|
||||||
|
|
||||||
|
@ -287,7 +329,7 @@ class Phaser:
|
||||||
|
|
||||||
for data in self.dac_mmap:
|
for data in self.dac_mmap:
|
||||||
self.dac_write(data >> 16, data)
|
self.dac_write(data >> 16, data)
|
||||||
self.core.delay(40.*us)
|
self.core.delay(120.*us)
|
||||||
self.dac_sync()
|
self.dac_sync()
|
||||||
self.core.delay(40.*us)
|
self.core.delay(40.*us)
|
||||||
|
|
||||||
|
@ -358,10 +400,11 @@ class Phaser:
|
||||||
if channel.get_att_mu() != 0x5a:
|
if channel.get_att_mu() != 0x5a:
|
||||||
raise ValueError("attenuator test failed")
|
raise ValueError("attenuator test failed")
|
||||||
self.core.delay(.1*ms)
|
self.core.delay(.1*ms)
|
||||||
channel.set_att_mu(0x00) # minimum attenuation
|
channel.set_att_mu(0x00) # maximum attenuation
|
||||||
|
|
||||||
channel.set_servo(profile=0, enable=False, hold=True)
|
channel.set_servo(profile=0, enable=False, hold=True)
|
||||||
|
|
||||||
|
if self.gw_rev == PHASER_GW_BASE:
|
||||||
# test oscillators and DUC
|
# test oscillators and DUC
|
||||||
for i in range(len(channel.oscillator)):
|
for i in range(len(channel.oscillator)):
|
||||||
oscillator = channel.oscillator[i]
|
oscillator = channel.oscillator[i]
|
||||||
|
@ -389,6 +432,9 @@ class Phaser:
|
||||||
abs(data_i - data_q) > 2):
|
abs(data_i - data_q) > 2):
|
||||||
raise ValueError("DUC+oscillator phase/amplitude test failed")
|
raise ValueError("DUC+oscillator phase/amplitude test failed")
|
||||||
|
|
||||||
|
if self.gw_rev == PHASER_GW_MIQRO:
|
||||||
|
channel.miqro.reset()
|
||||||
|
|
||||||
if is_baseband:
|
if is_baseband:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -674,7 +720,7 @@ class Phaser:
|
||||||
.. note:: Synchronising the NCO clears the phase-accumulator
|
.. note:: Synchronising the NCO clears the phase-accumulator
|
||||||
"""
|
"""
|
||||||
config1f = self.dac_read(0x1f)
|
config1f = self.dac_read(0x1f)
|
||||||
self.core.delay(.1*ms)
|
self.core.delay(.4*ms)
|
||||||
self.dac_write(0x1f, config1f & ~int32(1 << 1))
|
self.dac_write(0x1f, config1f & ~int32(1 << 1))
|
||||||
self.dac_write(0x1f, config1f | (1 << 1))
|
self.dac_write(0x1f, config1f | (1 << 1))
|
||||||
|
|
||||||
|
@ -794,6 +840,8 @@ class Phaser:
|
||||||
if good & (1 << o) != 0:
|
if good & (1 << o) != 0:
|
||||||
sum += o
|
sum += o
|
||||||
count += 1
|
count += 1
|
||||||
|
if count == 0:
|
||||||
|
raise ValueError("no good fifo offset")
|
||||||
best = ((sum // count) + offset) % 8
|
best = ((sum // count) + offset) % 8
|
||||||
self.dac_write(0x09, (config9 & 0x1fff) | (best << 13))
|
self.dac_write(0x09, (config9 & 0x1fff) | (best << 13))
|
||||||
return best
|
return best
|
||||||
|
@ -805,8 +853,9 @@ class PhaserChannel:
|
||||||
|
|
||||||
A Phaser channel contains:
|
A Phaser channel contains:
|
||||||
|
|
||||||
* multiple oscillators (in the coredevice phy),
|
* multiple :class:`PhaserOscillator` (in the coredevice phy),
|
||||||
* an interpolation chain and digital upconverter (DUC) on Phaser,
|
* an interpolation chain and digital upconverter (DUC) on Phaser,
|
||||||
|
* a :class:`Miqro` instance on Phaser,
|
||||||
* several channel-specific settings in the DAC:
|
* several channel-specific settings in the DAC:
|
||||||
|
|
||||||
* quadrature modulation compensation QMC
|
* quadrature modulation compensation QMC
|
||||||
|
@ -818,6 +867,7 @@ class PhaserChannel:
|
||||||
Attributes:
|
Attributes:
|
||||||
|
|
||||||
* :attr:`oscillator`: List of five :class:`PhaserOscillator`.
|
* :attr:`oscillator`: List of five :class:`PhaserOscillator`.
|
||||||
|
* :attr:`miqro`: A :class:`Miqro`.
|
||||||
|
|
||||||
.. note:: The amplitude sum of the oscillators must be less than one to
|
.. note:: The amplitude sum of the oscillators must be less than one to
|
||||||
avoid clipping or overflow. If any of the DDS or DUC frequencies are
|
avoid clipping or overflow. If any of the DDS or DUC frequencies are
|
||||||
|
@ -830,6 +880,8 @@ class PhaserChannel:
|
||||||
changes in oscillator parameters, the overshoot can lead to clipping
|
changes in oscillator parameters, the overshoot can lead to clipping
|
||||||
or overflow after the interpolation. Either band-limit any changes
|
or overflow after the interpolation. Either band-limit any changes
|
||||||
in the oscillator parameters or back off the amplitude sufficiently.
|
in the oscillator parameters or back off the amplitude sufficiently.
|
||||||
|
Miqro is not affected by this. But both the oscillators and Miqro can
|
||||||
|
be affected by intrinsic overshoot of the interpolator on the DAC.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
core: KernelInvariant[Core]
|
core: KernelInvariant[Core]
|
||||||
|
@ -837,6 +889,7 @@ class PhaserChannel:
|
||||||
index: KernelInvariant[int32]
|
index: KernelInvariant[int32]
|
||||||
trf_mmap: KernelInvariant[list[int32]]
|
trf_mmap: KernelInvariant[list[int32]]
|
||||||
oscillator: Kernel[list[PhaserOscillator]]
|
oscillator: Kernel[list[PhaserOscillator]]
|
||||||
|
miqro: KernelInvariant[Miqro]
|
||||||
|
|
||||||
def __init__(self, phaser, index, trf):
|
def __init__(self, phaser, index, trf):
|
||||||
self.core = phaser.core
|
self.core = phaser.core
|
||||||
|
@ -846,6 +899,7 @@ class PhaserChannel:
|
||||||
self.trf_mmap = [int32(uint32(x)) for x in self.trf_mmap] # NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/14
|
self.trf_mmap = [int32(uint32(x)) for x in self.trf_mmap] # NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/14
|
||||||
|
|
||||||
self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)]
|
self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)]
|
||||||
|
self.miqro = Miqro(self)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def get_dac_data(self) -> int32:
|
def get_dac_data(self) -> int32:
|
||||||
|
@ -1169,8 +1223,8 @@ class PhaserChannel:
|
||||||
Gains are given in units of output full per scale per input full scale.
|
Gains are given in units of output full per scale per input full scale.
|
||||||
|
|
||||||
.. note:: Due to inherent constraints of the fixed point datatypes and IIR
|
.. note:: Due to inherent constraints of the fixed point datatypes and IIR
|
||||||
filters, the ``x_offset`` (setpoint) resolution depends on the selected gains.
|
filters, the ``x_offset`` (setpoint) resolution depends on the selected
|
||||||
Low ``ki`` gains will lead to a low ``x_offset`` resolution.
|
gains. Low ``ki`` gains will lead to a low ``x_offset`` resolution.
|
||||||
|
|
||||||
The transfer function is (up to time discretization and
|
The transfer function is (up to time discretization and
|
||||||
coefficient quantization errors):
|
coefficient quantization errors):
|
||||||
|
@ -1294,3 +1348,311 @@ class PhaserOscillator:
|
||||||
raise ValueError("amplitude out of bounds")
|
raise ValueError("amplitude out of bounds")
|
||||||
pow = round(phase*float(1 << 16))
|
pow = round(phase*float(1 << 16))
|
||||||
self.set_amplitude_phase_mu(asf, pow, clr)
|
self.set_amplitude_phase_mu(asf, pow, clr)
|
||||||
|
|
||||||
|
|
||||||
|
@nac3
|
||||||
|
class Miqro:
|
||||||
|
"""
|
||||||
|
Miqro pulse generator.
|
||||||
|
|
||||||
|
A Miqro instance represents one RF output. The DSP components are fully
|
||||||
|
contained in the Phaser gateware. The output is generated by with
|
||||||
|
the following data flow:
|
||||||
|
|
||||||
|
**Oscillators**
|
||||||
|
|
||||||
|
* There are n_osc = 16 oscillators with oscillator IDs 0..n_osc-1.
|
||||||
|
* Each oscillator outputs one tone at any given time
|
||||||
|
|
||||||
|
* I/Q (quadrature, a.k.a. complex) 2x16 bit signed data
|
||||||
|
at tau = 4 ns sample intervals, 250 MS/s, Nyquist 125 MHz, bandwidth 200 MHz
|
||||||
|
(from f = -100..+100 MHz, taking into account the interpolation anti-aliasing
|
||||||
|
filters in subsequent interpolators),
|
||||||
|
* 32 bit frequency (f) resolution (~ 1/16 Hz),
|
||||||
|
* 16 bit unsigned amplitude (a) resolution
|
||||||
|
* 16 bit phase offset (p) resolution
|
||||||
|
|
||||||
|
* The output phase p' of each oscillator at time t (boot/reset/initialization of the
|
||||||
|
device at t=0) is then p' = f*t + p (mod 1 turn) where f and p are the (currently
|
||||||
|
active) profile frequency and phase offset.
|
||||||
|
* Note: The terms "phase coherent" and "phase tracking" are defined to refer to this
|
||||||
|
choice of oscillator output phase p'. Note that the phase offset p is not relative to
|
||||||
|
(on top of previous phase/profiles/oscillator history).
|
||||||
|
It is "absolute" in the sense that frequency f and phase offset p fully determine
|
||||||
|
oscillator output phase p' at time t. This is unlike typical DDS behavior.
|
||||||
|
* Frequency, phase, and amplitude of each oscillator are configurable by selecting one of
|
||||||
|
n_profile = 32 profiles 0..n_profile-1. This selection is fast and can be done for
|
||||||
|
each pulse. The phase coherence defined above is guaranteed for each
|
||||||
|
profile individually.
|
||||||
|
* Note: one profile per oscillator (usually profile index 0) should be reserved
|
||||||
|
for the NOP (no operation, identity) profile, usually with zero amplitude.
|
||||||
|
* Data for each profile for each oscillator can be configured
|
||||||
|
individually. Storing profile data should be considered "expensive".
|
||||||
|
* Note: The annotation that some operation is "expensive" does not mean it is
|
||||||
|
impossible, just that it may take a significant amount of time and
|
||||||
|
resources to execute such that it may be impractical when used often or
|
||||||
|
during fast pulse sequences. They are intended for use in calibration and
|
||||||
|
initialization.
|
||||||
|
|
||||||
|
**Summation**
|
||||||
|
|
||||||
|
* The oscillator outputs are added together (wrapping addition).
|
||||||
|
* The user must ensure that the sum of oscillators outputs does not exceed the
|
||||||
|
data range. In general that means that the sum of the amplitudes must not
|
||||||
|
exceed one.
|
||||||
|
|
||||||
|
**Shaper**
|
||||||
|
|
||||||
|
* The summed complex output stream is then multiplied with a the complex-valued
|
||||||
|
output of a triggerable shaper.
|
||||||
|
* Triggering the shaper corresponds to passing a pulse from all oscillators to
|
||||||
|
the RF output.
|
||||||
|
* Selected profiles become active simultaneously (on the same output sample) when
|
||||||
|
triggering the shaper with the first shaper output sample.
|
||||||
|
* The shaper reads (replays) window samples from a memory of size n_window = 1 << 10.
|
||||||
|
* The window memory can be segmented by choosing different start indices
|
||||||
|
to support different windows.
|
||||||
|
* Each window memory segment starts with a header determining segment
|
||||||
|
length and interpolation parameters.
|
||||||
|
* The window samples are interpolated by a factor (rate change) between 1 and
|
||||||
|
r = 1 << 12.
|
||||||
|
* The interpolation order is constant, linear, quadratic, or cubic. This
|
||||||
|
corresponds to interpolation modes from rectangular window (1st order CIC)
|
||||||
|
or zero order hold) to Parzen window (4th order CIC or cubic spline).
|
||||||
|
* This results in support for single shot pulse lengths (envelope support) between
|
||||||
|
tau and a bit more than r * n_window * tau = (1 << 12 + 10) tau ~ 17 ms.
|
||||||
|
* Windows can be configured to be head-less and/or tail-less, meaning, they
|
||||||
|
do not feed zero-amplitude samples into the shaper before and after
|
||||||
|
each window respectively. This is used to implement pulses with arbitrary
|
||||||
|
length or CW output.
|
||||||
|
|
||||||
|
**Overall properties**
|
||||||
|
|
||||||
|
* The DAC may upconvert the signal by applying a frequency offset f1 with
|
||||||
|
phase p1.
|
||||||
|
* In the Upconverter Phaser variant, the analog quadrature upconverter
|
||||||
|
applies another frequency of f2 and phase p2.
|
||||||
|
* The resulting phase of the signal from one oscillator at the SMA output is
|
||||||
|
(f + f1 + f2)*t + p + s(t - t0) + p1 + p2 (mod 1 turn)
|
||||||
|
where s(t - t0) is the phase of the interpolated
|
||||||
|
shaper output, and t0 is the trigger time (fiducial of the shaper).
|
||||||
|
Unsurprisingly the frequency is the derivative of the phase.
|
||||||
|
* Group delays between pulse parameter updates are matched across oscillators,
|
||||||
|
shapers, and channels.
|
||||||
|
* The minimum time to change profiles and phase offsets is ~128 ns (estimate, TBC).
|
||||||
|
This is the minimum pulse interval.
|
||||||
|
The sustained pulse rate of the RTIO PHY/Fastlink is one pulse per Fastlink frame
|
||||||
|
(may be increased, TBC).
|
||||||
|
"""
|
||||||
|
|
||||||
|
core: KernelInvariant[Core]
|
||||||
|
channel: KernelInvariant[PhaserChannel]
|
||||||
|
base_addr: KernelInvariant[int32]
|
||||||
|
|
||||||
|
def __init__(self, channel):
|
||||||
|
self.core = channel.core
|
||||||
|
self.channel = channel
|
||||||
|
self.base_addr = (self.channel.phaser.channel_base + 1 +
|
||||||
|
self.channel.index) << 8
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def reset(self):
|
||||||
|
"""Establish no-output profiles and no-output window and execute them.
|
||||||
|
|
||||||
|
This establishes the first profile (index 0) on all oscillators as zero
|
||||||
|
amplitude, creates a trivial window (one sample with zero amplitude,
|
||||||
|
minimal interpolation), and executes a corresponding pulse.
|
||||||
|
"""
|
||||||
|
for osc in range(16):
|
||||||
|
self.set_profile_mu(osc, profile=0, ftw=0, asf=0)
|
||||||
|
self.core.delay(20.*us)
|
||||||
|
self.set_window_mu(start=0, iq=[0], order=0)
|
||||||
|
self.pulse(window=0, profiles=[0])
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_profile_mu(self, oscillator: int32, profile: int32, ftw: int32, asf: int32, pow_: int32 = 0):
|
||||||
|
"""Store an oscillator profile (machine units).
|
||||||
|
|
||||||
|
:param oscillator: Oscillator index (0 to 15)
|
||||||
|
:param profile: Profile index (0 to 31)
|
||||||
|
:param ftw: Frequency tuning word (32 bit signed integer on a 250 MHz clock)
|
||||||
|
:param asf: Amplitude scale factor (16 bit unsigned integer)
|
||||||
|
:param pow_: Phase offset word (16 bit integer)
|
||||||
|
"""
|
||||||
|
if oscillator >= 16:
|
||||||
|
raise ValueError("invalid oscillator index")
|
||||||
|
if profile >= 32:
|
||||||
|
raise ValueError("invalid profile index")
|
||||||
|
self.channel.phaser.write16(PHASER_ADDR_MIQRO_MEM_ADDR,
|
||||||
|
(self.channel.index << 15) | PHASER_MIQRO_SEL_PROFILE |
|
||||||
|
(oscillator << 6) | (profile << 1))
|
||||||
|
self.channel.phaser.write32(PHASER_ADDR_MIQRO_MEM_DATA, ftw)
|
||||||
|
self.channel.phaser.write32(PHASER_ADDR_MIQRO_MEM_DATA,
|
||||||
|
(asf & 0xffff) | (pow_ << 16))
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_profile(self, oscillator: int32, profile: int32, frequency: float, amplitude: float, phase: float = 0.) -> int32:
|
||||||
|
"""Store an oscillator profile.
|
||||||
|
|
||||||
|
:param oscillator: Oscillator index (0 to 15)
|
||||||
|
:param profile: Profile index (0 to 31)
|
||||||
|
:param frequency: Frequency in Hz (passband -100 to 100 MHz).
|
||||||
|
Interpreted in the Nyquist sense, i.e. aliased.
|
||||||
|
:param amplitude: Amplitude in units of full scale (0. to 1.)
|
||||||
|
:param phase: Phase in turns. See :class:`Miqro` for a definition of
|
||||||
|
phase in this context.
|
||||||
|
:return: The quantized 32 bit frequency tuning word
|
||||||
|
"""
|
||||||
|
ftw = round(frequency*(float(1 << 30)/(62.5*MHz)))
|
||||||
|
asf = round(amplitude*float(0xffff))
|
||||||
|
if asf < 0 or asf > 0xffff:
|
||||||
|
raise ValueError("amplitude out of bounds")
|
||||||
|
pow_ = round(phase*float(1 << 16))
|
||||||
|
self.set_profile_mu(oscillator, profile, ftw, asf, pow_)
|
||||||
|
return ftw
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_window_mu(self, start: int32, iq: list[int32], rate: int32 = 1, shift: int32 = 0, order: int32 = 3, head: bool = True, tail: bool = True) -> int32:
|
||||||
|
"""Store a window segment (machine units)
|
||||||
|
|
||||||
|
:param start: Window start address (0 to 0x3ff)
|
||||||
|
:param iq: List of IQ window samples. Each window sample is an integer
|
||||||
|
containing the signed I part in the 16 LSB and the signed Q part in
|
||||||
|
the 16 MSB. The maximum window length is 0x3fe. The user must
|
||||||
|
ensure that this window does not overlap with other windows in the
|
||||||
|
memory.
|
||||||
|
:param rate: Interpolation rate change (1 to 1 << 12)
|
||||||
|
:param shift: Interpolator amplitude gain compensation in powers of 2 (0 to 63)
|
||||||
|
:param order: Interpolation order from 0 (corresponding to
|
||||||
|
constant/rectangular window/zero-order-hold/1st order CIC interpolation)
|
||||||
|
to 3 (corresponding to cubic/Parzen window/4th order CIC interpolation)
|
||||||
|
:param head: Update the interpolator settings and clear its state at the start
|
||||||
|
of the window. This also implies starting the envelope from zero.
|
||||||
|
:param tail: Feed zeros into the interpolator after the window samples.
|
||||||
|
In the absence of further pulses this will return the output envelope
|
||||||
|
to zero with the chosen interpolation.
|
||||||
|
:return: Next available window memory address after this segment.
|
||||||
|
"""
|
||||||
|
if start >= 1 << 10:
|
||||||
|
raise ValueError("start out of bounds")
|
||||||
|
if len(iq) >= 1 << 10:
|
||||||
|
raise ValueError("window length out of bounds")
|
||||||
|
if rate < 1 or rate > 1 << 12:
|
||||||
|
raise ValueError("rate out of bounds")
|
||||||
|
if shift > 0x3f:
|
||||||
|
raise ValueError("shift out of bounds")
|
||||||
|
if order > 3:
|
||||||
|
raise ValueError("order out of bounds")
|
||||||
|
self.channel.phaser.write16(PHASER_ADDR_MIQRO_MEM_ADDR,
|
||||||
|
(self.channel.index << 15) | start)
|
||||||
|
self.channel.phaser.write32(PHASER_ADDR_MIQRO_MEM_DATA,
|
||||||
|
(len(iq) & 0x3ff) |
|
||||||
|
((rate - 1) << 10) |
|
||||||
|
(shift << 22) |
|
||||||
|
(order << 28) |
|
||||||
|
(int32(head) << 30) |
|
||||||
|
(int32(tail) << 31)
|
||||||
|
)
|
||||||
|
for iqi in iq:
|
||||||
|
self.channel.phaser.write32(PHASER_ADDR_MIQRO_MEM_DATA, iqi)
|
||||||
|
self.core.delay(20.*us) # slack for long windows
|
||||||
|
return (start + 1 + len(iq)) & 0x3ff
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_window(self, start: int32, iq: list[tuple[float, float]], period: float = 4e-9, order: int32 = 3, head: bool = True, tail: bool = True) -> float:
|
||||||
|
"""Store a window segment
|
||||||
|
|
||||||
|
:param start: Window start address (0 to 0x3ff)
|
||||||
|
:param iq: List of IQ window samples. Each window sample is a pair of
|
||||||
|
two float numbers -1 to 1, one for each I and Q in units of full scale.
|
||||||
|
The maximum window length is 0x3fe. The user must ensure that this window
|
||||||
|
does not overlap with other windows in the memory.
|
||||||
|
:param period: Desired window sample period in SI units (4*ns to (4 << 12)*ns).
|
||||||
|
:param order: Interpolation order from 0 (corresponding to
|
||||||
|
constant/zero-order-hold/1st order CIC interpolation) to 3 (corresponding
|
||||||
|
to cubic/Parzen/4th order CIC interpolation)
|
||||||
|
:param head: Update the interpolator settings and clear its state at the start
|
||||||
|
of the window. This also implies starting the envelope from zero.
|
||||||
|
:param tail: Feed zeros into the interpolator after the window samples.
|
||||||
|
In the absence of further pulses this will return the output envelope
|
||||||
|
to zero with the chosen interpolation.
|
||||||
|
:return: Actual sample period in SI units
|
||||||
|
"""
|
||||||
|
rate = round(period/(4.*ns))
|
||||||
|
gain = 1.
|
||||||
|
for _ in range(order):
|
||||||
|
gain *= float(rate)
|
||||||
|
shift = 0
|
||||||
|
while gain >= 2.:
|
||||||
|
shift += 1
|
||||||
|
gain *= .5
|
||||||
|
scale = float((1 << 15) - 1)/gain
|
||||||
|
iq_mu = [
|
||||||
|
(int32(round(iqi[0]*scale)) & 0xffff) |
|
||||||
|
(int32(round(iqi[1]*scale)) << 16)
|
||||||
|
for iqi in iq
|
||||||
|
]
|
||||||
|
self.set_window_mu(start, iq_mu, rate, shift, order, head, tail)
|
||||||
|
return float((len(iq) + order)*rate)*4.*ns
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def encode(self, window: int32, profiles: list[int32], data: list[int32]) -> int32:
|
||||||
|
"""Encode window and profile selection
|
||||||
|
|
||||||
|
:param window: Window start address (0 to 0x3ff)
|
||||||
|
:param profiles: List of profile indices for the oscillators. Maximum
|
||||||
|
length 16. Unused oscillators will be set to profile 0.
|
||||||
|
:param data: List of integers to store the encoded data words into.
|
||||||
|
Unused entries will remain untouched. Must contain at least three
|
||||||
|
lements if all oscillators are used and should be initialized to
|
||||||
|
zeros.
|
||||||
|
:return: Number of words from `data` used.
|
||||||
|
"""
|
||||||
|
if len(profiles) > 16:
|
||||||
|
raise ValueError("too many oscillators")
|
||||||
|
if window > 0x3ff:
|
||||||
|
raise ValueError("window start out of bounds")
|
||||||
|
data[0] = window
|
||||||
|
word = 0
|
||||||
|
idx = 10
|
||||||
|
for profile in profiles:
|
||||||
|
if profile > 0x1f:
|
||||||
|
raise ValueError("profile out of bounds")
|
||||||
|
if idx > 32 - 5:
|
||||||
|
word += 1
|
||||||
|
idx = 0
|
||||||
|
data[word] |= profile << idx
|
||||||
|
idx += 5
|
||||||
|
return word + 1
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def pulse_mu(self, data: list[int32]):
|
||||||
|
"""Emit a pulse (encoded)
|
||||||
|
|
||||||
|
The pulse fiducial timing resolution is 4 ns.
|
||||||
|
|
||||||
|
:param data: List of up to 3 words containing an encoded MIQRO pulse as
|
||||||
|
returned by :meth:`encode`.
|
||||||
|
"""
|
||||||
|
word = len(data)
|
||||||
|
delay_mu(int64(-8*word)) # back shift to align
|
||||||
|
while word > 0:
|
||||||
|
word -= 1
|
||||||
|
delay_mu(int64(8))
|
||||||
|
# final write sets pulse stb
|
||||||
|
rtio_output(self.base_addr + word, data[word])
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def pulse(self, window: int32, profiles: list[int32]):
|
||||||
|
"""Emit a pulse
|
||||||
|
|
||||||
|
This encodes the window and profiles (see :meth:`encode`) and emits them
|
||||||
|
(see :meth:`pulse_mu`).
|
||||||
|
|
||||||
|
:param window: Window start address (0 to 0x3ff)
|
||||||
|
:param profiles: List of profile indices for the oscillators. Maximum
|
||||||
|
length 16. Unused oscillators will select profile 0.
|
||||||
|
"""
|
||||||
|
data = [0, 0, 0]
|
||||||
|
words = self.encode(window, profiles, data)
|
||||||
|
self.pulse_mu(data[:words])
|
||||||
|
|
|
@ -8,8 +8,11 @@ from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
from sipyco.sync_struct import Subscriber
|
from sipyco.sync_struct import Subscriber
|
||||||
|
|
||||||
from artiq.coredevice.comm_moninj import *
|
from artiq.coredevice.comm_moninj import *
|
||||||
from artiq.coredevice.ad9910 import _AD9910_REG_PROFILE0, _AD9910_REG_PROFILE7, _AD9910_REG_FTW
|
from artiq.coredevice.ad9910 import (
|
||||||
from artiq.coredevice.ad9912_reg import AD9912_POW1
|
_AD9910_REG_PROFILE0, _AD9910_REG_PROFILE7,
|
||||||
|
_AD9910_REG_FTW, _AD9910_REG_CFR1
|
||||||
|
)
|
||||||
|
from artiq.coredevice.ad9912_reg import AD9912_POW1, AD9912_SER_CONF
|
||||||
from artiq.gui.tools import LayoutWidget
|
from artiq.gui.tools import LayoutWidget
|
||||||
from artiq.gui.flowlayout import FlowLayout
|
from artiq.gui.flowlayout import FlowLayout
|
||||||
|
|
||||||
|
@ -311,6 +314,10 @@ class _DDSWidget(QtWidgets.QFrame):
|
||||||
apply.clicked.connect(self.apply_changes)
|
apply.clicked.connect(self.apply_changes)
|
||||||
if self.dds_model.is_urukul:
|
if self.dds_model.is_urukul:
|
||||||
off_btn.clicked.connect(self.off_clicked)
|
off_btn.clicked.connect(self.off_clicked)
|
||||||
|
off_btn.setToolTip(textwrap.dedent(
|
||||||
|
"""Note: If TTL RTIO sw for the channel is switched high,
|
||||||
|
this button will not disable the channel.
|
||||||
|
Use the TTL override instead."""))
|
||||||
self.value_edit.returnPressed.connect(lambda: self.apply_changes(None))
|
self.value_edit.returnPressed.connect(lambda: self.apply_changes(None))
|
||||||
self.value_edit.escapePressedConnect(self.cancel_changes)
|
self.value_edit.escapePressedConnect(self.cancel_changes)
|
||||||
cancel.clicked.connect(self.cancel_changes)
|
cancel.clicked.connect(self.cancel_changes)
|
||||||
|
@ -549,33 +556,55 @@ class _DeviceManager:
|
||||||
scheduling["flush"])
|
scheduling["flush"])
|
||||||
logger.info("Submitted '%s', RID is %d", title, rid)
|
logger.info("Submitted '%s', RID is %d", title, rid)
|
||||||
|
|
||||||
def dds_set_frequency(self, dds_channel, dds_model, freq):
|
def _dds_faux_injection(self, dds_channel, dds_model, action, title, log_msg):
|
||||||
# create kernel and fill it in and send-by-content
|
# create kernel and fill it in and send-by-content
|
||||||
|
|
||||||
|
# initialize CPLD (if applicable)
|
||||||
if dds_model.is_urukul:
|
if dds_model.is_urukul:
|
||||||
# urukuls need CPLD init and switch to on
|
# urukuls need CPLD init and switch to on
|
||||||
# keep previous config if it was set already
|
|
||||||
cpld_dev = """self.setattr_device("core_cache")
|
cpld_dev = """self.setattr_device("core_cache")
|
||||||
self.setattr_device("{}")""".format(dds_model.cpld)
|
self.setattr_device("{}")""".format(dds_model.cpld)
|
||||||
cpld_init = """cfg = self.core_cache.get("_{cpld}_cfg")
|
|
||||||
if len(cfg) > 0:
|
# `sta`/`rf_sw`` variables are guaranteed for urukuls
|
||||||
self.{cpld}.cfg_reg = cfg[0]
|
# so {action} can use it
|
||||||
else:
|
# if there's no RF enabled, CPLD may have not been initialized
|
||||||
|
# but if there is, it has been initialised - no need to do again
|
||||||
|
cpld_init = """delay(15*ms)
|
||||||
|
was_init = self.core_cache.get("_{cpld}_init")
|
||||||
|
sta = self.{cpld}.sta_read()
|
||||||
|
rf_sw = urukul_sta_rf_sw(sta)
|
||||||
|
if rf_sw == 0 and len(was_init) == 0:
|
||||||
delay(15*ms)
|
delay(15*ms)
|
||||||
self.{cpld}.init()
|
self.{cpld}.init()
|
||||||
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
|
self.core_cache.put("_{cpld}_init", [1])
|
||||||
cfg = self.core_cache.get("_{cpld}_cfg")
|
|
||||||
""".format(cpld=dds_model.cpld)
|
""".format(cpld=dds_model.cpld)
|
||||||
cfg_sw = """self.{}.cfg_sw(True)
|
|
||||||
cfg[0] = self.{}.cfg_reg
|
|
||||||
""".format(dds_channel, dds_model.cpld)
|
|
||||||
else:
|
else:
|
||||||
cpld_dev = ""
|
cpld_dev = ""
|
||||||
cpld_init = ""
|
cpld_init = ""
|
||||||
cfg_sw = ""
|
|
||||||
|
# AD9912/9910: init channel (if uninitialized)
|
||||||
|
if dds_model.dds_type == "AD9912":
|
||||||
|
# 0xFF before init, 0x99 after
|
||||||
|
channel_init = """
|
||||||
|
if self.{dds_channel}.read({cfgreg}, length=1) == 0xFF:
|
||||||
|
delay(10*ms)
|
||||||
|
self.{dds_channel}.init()
|
||||||
|
""".format(dds_channel=dds_channel, cfgreg=AD9912_SER_CONF)
|
||||||
|
elif dds_model.dds_type == "AD9910":
|
||||||
|
# -1 before init, 2 after
|
||||||
|
channel_init = """
|
||||||
|
if self.{dds_channel}.read32({cfgreg}) == -1:
|
||||||
|
delay(10*ms)
|
||||||
|
self.{dds_channel}.init()
|
||||||
|
""".format(dds_channel=dds_channel, cfgreg=AD9912_SER_CONF)
|
||||||
|
else:
|
||||||
|
channel_init = "self.{dds_channel}.init()".format(dds_channel=dds_channel)
|
||||||
|
|
||||||
dds_exp = textwrap.dedent("""
|
dds_exp = textwrap.dedent("""
|
||||||
from artiq.experiment import *
|
from artiq.experiment import *
|
||||||
|
from artiq.coredevice.urukul import *
|
||||||
|
|
||||||
class SetDDS(EnvExperiment):
|
class {title}(EnvExperiment):
|
||||||
def build(self):
|
def build(self):
|
||||||
self.setattr_device("core")
|
self.setattr_device("core")
|
||||||
self.setattr_device("{dds_channel}")
|
self.setattr_device("{dds_channel}")
|
||||||
|
@ -585,53 +614,55 @@ class _DeviceManager:
|
||||||
def run(self):
|
def run(self):
|
||||||
self.core.break_realtime()
|
self.core.break_realtime()
|
||||||
{cpld_init}
|
{cpld_init}
|
||||||
delay(5*ms)
|
delay(10*ms)
|
||||||
self.{dds_channel}.init()
|
{channel_init}
|
||||||
self.{dds_channel}.set({freq})
|
delay(15*ms)
|
||||||
{cfg_sw}
|
{action}
|
||||||
""".format(dds_channel=dds_channel, freq=freq,
|
""".format(title=title, action=action,
|
||||||
|
dds_channel=dds_channel,
|
||||||
cpld_dev=cpld_dev, cpld_init=cpld_init,
|
cpld_dev=cpld_dev, cpld_init=cpld_init,
|
||||||
cfg_sw=cfg_sw))
|
channel_init=channel_init))
|
||||||
asyncio.ensure_future(
|
asyncio.ensure_future(
|
||||||
self._submit_by_content(
|
self._submit_by_content(
|
||||||
dds_exp,
|
dds_exp,
|
||||||
|
title,
|
||||||
|
log_msg))
|
||||||
|
|
||||||
|
def dds_set_frequency(self, dds_channel, dds_model, freq):
|
||||||
|
action = "self.{ch}.set({freq})".format(
|
||||||
|
freq=freq, ch=dds_channel)
|
||||||
|
if dds_model.is_urukul:
|
||||||
|
action += """
|
||||||
|
ch_no = self.{ch}.chip_select - 4
|
||||||
|
self.{cpld}.cfg_switches(rf_sw | 1 << ch_no)
|
||||||
|
""".format(ch=dds_channel, cpld=dds_model.cpld)
|
||||||
|
self._dds_faux_injection(
|
||||||
|
dds_channel,
|
||||||
|
dds_model,
|
||||||
|
action,
|
||||||
"SetDDS",
|
"SetDDS",
|
||||||
"Set DDS {} {}MHz".format(dds_channel, freq/1e6)))
|
"Set DDS {} {}MHz".format(dds_channel, freq/1e6))
|
||||||
|
|
||||||
def dds_channel_toggle(self, dds_channel, dds_model, sw=True):
|
def dds_channel_toggle(self, dds_channel, dds_model, sw=True):
|
||||||
# urukul only
|
# urukul only
|
||||||
toggle_exp = textwrap.dedent("""
|
if sw:
|
||||||
from artiq.experiment import *
|
switch = "| 1 << ch_no"
|
||||||
|
|
||||||
class ToggleDDS(EnvExperiment):
|
|
||||||
def build(self):
|
|
||||||
self.setattr_device("core")
|
|
||||||
self.setattr_device("{ch}")
|
|
||||||
self.setattr_device("core_cache")
|
|
||||||
self.setattr_device("{cpld}")
|
|
||||||
|
|
||||||
@kernel
|
|
||||||
def run(self):
|
|
||||||
self.core.break_realtime()
|
|
||||||
cfg = self.core_cache.get("_{cpld}_cfg")
|
|
||||||
if len(cfg) > 0:
|
|
||||||
self.{cpld}.cfg_reg = cfg[0]
|
|
||||||
else:
|
else:
|
||||||
delay(15*ms)
|
switch = "& ~(1 << ch_no)"
|
||||||
self.{cpld}.init()
|
action = """
|
||||||
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
|
ch_no = self.{dds_channel}.chip_select - 4
|
||||||
cfg = self.core_cache.get("_{cpld}_cfg")
|
self.{cpld}.cfg_switches(rf_sw {switch})
|
||||||
delay(5*ms)
|
""".format(
|
||||||
self.{ch}.init()
|
dds_channel=dds_channel,
|
||||||
self.{ch}.cfg_sw({sw})
|
cpld=dds_model.cpld,
|
||||||
cfg[0] = self.{cpld}.cfg_reg
|
switch=switch
|
||||||
""".format(ch=dds_channel, cpld=dds_model.cpld, sw=sw))
|
)
|
||||||
asyncio.ensure_future(
|
self._dds_faux_injection(
|
||||||
self._submit_by_content(
|
dds_channel,
|
||||||
toggle_exp,
|
dds_model,
|
||||||
|
action,
|
||||||
"ToggleDDS",
|
"ToggleDDS",
|
||||||
"Toggle DDS {} {}".format(dds_channel, "on" if sw else "off"))
|
"Toggle DDS {} {}".format(dds_channel, "on" if sw else "off"))
|
||||||
)
|
|
||||||
|
|
||||||
def setup_ttl_monitoring(self, enable, channel):
|
def setup_ttl_monitoring(self, enable, channel):
|
||||||
if self.mi_connection is not None:
|
if self.mi_connection is not None:
|
||||||
|
|
|
@ -60,6 +60,11 @@ SECTIONS
|
||||||
KEEP(*(.eh_frame_hdr))
|
KEEP(*(.eh_frame_hdr))
|
||||||
} > ksupport :text :eh_frame
|
} > ksupport :text :eh_frame
|
||||||
|
|
||||||
|
.gcc_except_table :
|
||||||
|
{
|
||||||
|
*(.gcc_except_table)
|
||||||
|
} > ksupport
|
||||||
|
|
||||||
.data :
|
.data :
|
||||||
{
|
{
|
||||||
*(.data .data.*)
|
*(.data .data.*)
|
||||||
|
|
|
@ -156,6 +156,21 @@ extern fn rpc_send_async(service: u32, tag: &CSlice<u8>, data: *const *const ())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Receives the result from an RPC call into the given memory buffer.
|
||||||
|
///
|
||||||
|
/// To handle aggregate objects with an a priori unknown size and number of
|
||||||
|
/// sub-allocations (e.g. a list of list of lists, where, at each level, the number of
|
||||||
|
/// elements is not statically known), this function needs to be called in a loop:
|
||||||
|
///
|
||||||
|
/// On the first call, `slot` should be a buffer of suitable size and alignment for
|
||||||
|
/// the top-level return value (e.g. in the case of a list, the pointer/length pair).
|
||||||
|
/// A return value of zero indicates that the value has been completely received.
|
||||||
|
/// As long as the return value is positive, another allocation with the given number of
|
||||||
|
/// bytes is needed, so the function should be called again with such a buffer (aligned
|
||||||
|
/// to the maximum required for any of the possible types according to the target ABI).
|
||||||
|
///
|
||||||
|
/// If the RPC call resulted in an exception, it is reconstructed and raised.
|
||||||
#[unwind(allowed)]
|
#[unwind(allowed)]
|
||||||
extern fn rpc_recv(slot: *mut ()) -> usize {
|
extern fn rpc_recv(slot: *mut ()) -> usize {
|
||||||
send(&RpcRecvRequest(slot));
|
send(&RpcRecvRequest(slot));
|
||||||
|
|
|
@ -6,22 +6,81 @@ use io::{ProtoRead, Read, Write, ProtoWrite, Error};
|
||||||
use self::tag::{Tag, TagIterator, split_tag};
|
use self::tag::{Tag, TagIterator, split_tag};
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn alignment_offset(alignment: isize, ptr: isize) -> isize {
|
fn round_up(val: usize, power_of_two: usize) -> usize {
|
||||||
(-ptr).rem_euclid(alignment)
|
assert!(power_of_two.is_power_of_two());
|
||||||
|
let max_rem = power_of_two - 1;
|
||||||
|
(val + max_rem) & (!max_rem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn round_up_mut<T>(ptr: *mut T, power_of_two: usize) -> *mut T {
|
||||||
|
round_up(ptr as usize, power_of_two) as *mut T
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn round_up_const<T>(ptr: *const T, power_of_two: usize) -> *const T {
|
||||||
|
round_up(ptr as usize, power_of_two) as *const T
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
unsafe fn align_ptr<T>(ptr: *const ()) -> *const T {
|
unsafe fn align_ptr<T>(ptr: *const ()) -> *const T {
|
||||||
let alignment = core::mem::align_of::<T>() as isize;
|
round_up_const(ptr, core::mem::align_of::<T>()) as *const T
|
||||||
let fix = alignment_offset(alignment as isize, ptr as isize);
|
|
||||||
((ptr as isize) + fix) as *const T
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
unsafe fn align_ptr_mut<T>(ptr: *mut ()) -> *mut T {
|
unsafe fn align_ptr_mut<T>(ptr: *mut ()) -> *mut T {
|
||||||
let alignment = core::mem::align_of::<T>() as isize;
|
round_up_mut(ptr, core::mem::align_of::<T>()) as *mut T
|
||||||
let fix = alignment_offset(alignment as isize, ptr as isize);
|
|
||||||
((ptr as isize) + fix) as *mut T
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads (deserializes) `length` array or list elements of type `tag` from `reader`,
|
||||||
|
/// writing them into the buffer given by `storage`.
|
||||||
|
///
|
||||||
|
/// `alloc` is used for nested allocations (if elements themselves contain
|
||||||
|
/// lists/arrays), see [recv_value].
|
||||||
|
unsafe fn recv_elements<R, E>(
|
||||||
|
reader: &mut R,
|
||||||
|
tag: Tag,
|
||||||
|
length: usize,
|
||||||
|
storage: *mut (),
|
||||||
|
alloc: &dyn Fn(usize) -> Result<*mut (), E>,
|
||||||
|
) -> Result<(), E>
|
||||||
|
where
|
||||||
|
R: Read + ?Sized,
|
||||||
|
E: From<Error<R::ReadError>>,
|
||||||
|
{
|
||||||
|
// List of simple types are special-cased in the protocol for performance.
|
||||||
|
match tag {
|
||||||
|
Tag::Bool => {
|
||||||
|
let dest = slice::from_raw_parts_mut(storage as *mut u8, length);
|
||||||
|
reader.read_exact(dest)?;
|
||||||
|
},
|
||||||
|
Tag::Int32 => {
|
||||||
|
let dest = slice::from_raw_parts_mut(storage as *mut u8, length * 4);
|
||||||
|
reader.read_exact(dest)?;
|
||||||
|
let dest = slice::from_raw_parts_mut(storage as *mut i32, length);
|
||||||
|
NativeEndian::from_slice_i32(dest);
|
||||||
|
},
|
||||||
|
Tag::Int64 | Tag::Float64 => {
|
||||||
|
let dest = slice::from_raw_parts_mut(storage as *mut u8, length * 8);
|
||||||
|
reader.read_exact(dest)?;
|
||||||
|
let dest = slice::from_raw_parts_mut(storage as *mut i64, length);
|
||||||
|
NativeEndian::from_slice_i64(dest);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let mut data = storage;
|
||||||
|
for _ in 0..length {
|
||||||
|
recv_value(reader, tag, &mut data, alloc)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads (deserializes) a value of type `tag` from `reader`, writing the results to
|
||||||
|
/// the kernel-side buffer `data` (the passed pointer to which is incremented to point
|
||||||
|
/// past the just-received data). For nested allocations (lists/arrays), `alloc` is
|
||||||
|
/// invoked any number of times with the size of the required allocation as a parameter
|
||||||
|
/// (which is assumed to be correctly aligned for all payload types).
|
||||||
unsafe fn recv_value<R, E>(reader: &mut R, tag: Tag, data: &mut *mut (),
|
unsafe fn recv_value<R, E>(reader: &mut R, tag: Tag, data: &mut *mut (),
|
||||||
alloc: &dyn Fn(usize) -> Result<*mut (), E>)
|
alloc: &dyn Fn(usize) -> Result<*mut (), E>)
|
||||||
-> Result<(), E>
|
-> Result<(), E>
|
||||||
|
@ -59,99 +118,63 @@ unsafe fn recv_value<R, E>(reader: &mut R, tag: Tag, data: &mut *mut (),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Tag::Tuple(it, arity) => {
|
Tag::Tuple(it, arity) => {
|
||||||
*data = data.offset(alignment_offset(tag.alignment() as isize, *data as isize));
|
let alignment = tag.alignment();
|
||||||
|
*data = round_up_mut(*data, alignment);
|
||||||
let mut it = it.clone();
|
let mut it = it.clone();
|
||||||
for _ in 0..arity {
|
for _ in 0..arity {
|
||||||
let tag = it.next().expect("truncated tag");
|
let tag = it.next().expect("truncated tag");
|
||||||
recv_value(reader, tag, data, alloc)?
|
recv_value(reader, tag, data, alloc)?
|
||||||
}
|
}
|
||||||
|
// Take into account any tail padding (if element(s) with largest alignment
|
||||||
|
// are not at the end).
|
||||||
|
*data = round_up_mut(*data, alignment);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Tag::List(it) => {
|
Tag::List(it) => {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct List { elements: *mut (), length: u32 }
|
struct List { elements: *mut (), length: usize }
|
||||||
consume_value!(*mut List, |ptr| {
|
consume_value!(*mut List, |ptr_to_list| {
|
||||||
let tag = it.clone().next().expect("truncated tag");
|
let tag = it.clone().next().expect("truncated tag");
|
||||||
let padding = if let Tag::Int64 | Tag::Float64 = tag { 4 } else { 0 };
|
|
||||||
|
|
||||||
let length = reader.read_u32()? as usize;
|
let length = reader.read_u32()? as usize;
|
||||||
let data = alloc(tag.size() * length + padding + 8)? as *mut u8;
|
|
||||||
*ptr = data as *mut List;
|
|
||||||
let ptr = data as *mut List;
|
|
||||||
let mut data = data.offset(8 + alignment_offset(tag.alignment() as isize, data as isize)) as *mut ();
|
|
||||||
|
|
||||||
(*ptr).length = length as u32;
|
// To avoid multiple kernel CPU roundtrips, use a single allocation for
|
||||||
(*ptr).elements = data;
|
// both the pointer/length List (slice) and the backing storage for the
|
||||||
match tag {
|
// elements. We can assume that alloc() is aligned suitably, so just
|
||||||
Tag::Bool => {
|
// need to take into account any extra padding required.
|
||||||
let dest = slice::from_raw_parts_mut(data as *mut u8, length);
|
// (Note: On RISC-V, there will never actually be any types with
|
||||||
reader.read_exact(dest)?;
|
// alignment larger than 8 bytes, so storage_offset == 0 always.)
|
||||||
},
|
let list_size = 4 + 4;
|
||||||
Tag::Int32 => {
|
let storage_offset = round_up(list_size, tag.alignment());
|
||||||
let dest = slice::from_raw_parts_mut(data as *mut u8, length * 4);
|
let storage_size = tag.size() * length;
|
||||||
reader.read_exact(dest)?;
|
|
||||||
let dest = slice::from_raw_parts_mut(data as *mut i32, length);
|
let allocation = alloc(storage_offset + storage_size)? as *mut u8;
|
||||||
NativeEndian::from_slice_i32(dest);
|
*ptr_to_list = allocation as *mut List;
|
||||||
},
|
let storage = allocation.offset(storage_offset as isize) as *mut ();
|
||||||
Tag::Int64 | Tag::Float64 => {
|
|
||||||
let dest = slice::from_raw_parts_mut(data as *mut u8, length * 8);
|
(**ptr_to_list).length = length;
|
||||||
reader.read_exact(dest)?;
|
(**ptr_to_list).elements = storage;
|
||||||
let dest = slice::from_raw_parts_mut(data as *mut i64, length);
|
recv_elements(reader, tag, length, storage, alloc)
|
||||||
NativeEndian::from_slice_i64(dest);
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
for _ in 0..length {
|
|
||||||
recv_value(reader, tag, &mut data, alloc)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Tag::Array(it, num_dims) => {
|
Tag::Array(it, num_dims) => {
|
||||||
consume_value!(*mut (), |buffer| {
|
consume_value!(*mut (), |buffer| {
|
||||||
let mut total_len: u32 = 1;
|
// Deserialize length along each dimension and compute total number of
|
||||||
|
// elements.
|
||||||
|
let mut total_len: usize = 1;
|
||||||
for _ in 0..num_dims {
|
for _ in 0..num_dims {
|
||||||
let len = reader.read_u32()?;
|
let len = reader.read_u32()? as usize;
|
||||||
total_len *= len;
|
total_len *= len;
|
||||||
consume_value!(u32, |ptr| *ptr = len )
|
consume_value!(usize, |ptr| *ptr = len )
|
||||||
}
|
}
|
||||||
let length = total_len as usize;
|
|
||||||
|
|
||||||
|
// Allocate backing storage for elements; deserialize them.
|
||||||
let elt_tag = it.clone().next().expect("truncated tag");
|
let elt_tag = it.clone().next().expect("truncated tag");
|
||||||
let padding = if let Tag::Int64 | Tag::Float64 = tag { 4 } else { 0 };
|
*buffer = alloc(elt_tag.size() * total_len)?;
|
||||||
let mut data = alloc(elt_tag.size() * length + padding)?;
|
recv_elements(reader, elt_tag, total_len, *buffer, alloc)
|
||||||
data = data.offset(alignment_offset(tag.alignment() as isize, data as isize));
|
|
||||||
|
|
||||||
*buffer = data;
|
|
||||||
match elt_tag {
|
|
||||||
Tag::Bool => {
|
|
||||||
let dest = slice::from_raw_parts_mut(data as *mut u8, length);
|
|
||||||
reader.read_exact(dest)?;
|
|
||||||
},
|
|
||||||
Tag::Int32 => {
|
|
||||||
let dest = slice::from_raw_parts_mut(data as *mut u8, length * 4);
|
|
||||||
reader.read_exact(dest)?;
|
|
||||||
let dest = slice::from_raw_parts_mut(data as *mut i32, length);
|
|
||||||
NativeEndian::from_slice_i32(dest);
|
|
||||||
},
|
|
||||||
Tag::Int64 | Tag::Float64 => {
|
|
||||||
let dest = slice::from_raw_parts_mut(data as *mut u8, length * 8);
|
|
||||||
reader.read_exact(dest)?;
|
|
||||||
let dest = slice::from_raw_parts_mut(data as *mut i64, length);
|
|
||||||
NativeEndian::from_slice_i64(dest);
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
for _ in 0..length {
|
|
||||||
recv_value(reader, elt_tag, &mut data, alloc)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Tag::Range(it) => {
|
Tag::Range(it) => {
|
||||||
*data = data.offset(alignment_offset(tag.alignment() as isize, *data as isize));
|
*data = round_up_mut(*data, tag.alignment());
|
||||||
let tag = it.clone().next().expect("truncated tag");
|
let tag = it.clone().next().expect("truncated tag");
|
||||||
recv_value(reader, tag, data, alloc)?;
|
recv_value(reader, tag, data, alloc)?;
|
||||||
recv_value(reader, tag, data, alloc)?;
|
recv_value(reader, tag, data, alloc)?;
|
||||||
|
@ -180,6 +203,36 @@ pub fn recv_return<R, E>(reader: &mut R, tag_bytes: &[u8], data: *mut (),
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe fn send_elements<W>(writer: &mut W, elt_tag: Tag, length: usize, data: *const ())
|
||||||
|
-> Result<(), Error<W::WriteError>>
|
||||||
|
where W: Write + ?Sized
|
||||||
|
{
|
||||||
|
writer.write_u8(elt_tag.as_u8())?;
|
||||||
|
match elt_tag {
|
||||||
|
// we cannot use NativeEndian::from_slice_i32 as the data is not mutable,
|
||||||
|
// and that is not needed as the data is already in native endian
|
||||||
|
Tag::Bool => {
|
||||||
|
let slice = slice::from_raw_parts(data as *const u8, length);
|
||||||
|
writer.write_all(slice)?;
|
||||||
|
},
|
||||||
|
Tag::Int32 => {
|
||||||
|
let slice = slice::from_raw_parts(data as *const u8, length * 4);
|
||||||
|
writer.write_all(slice)?;
|
||||||
|
},
|
||||||
|
Tag::Int64 | Tag::Float64 => {
|
||||||
|
let slice = slice::from_raw_parts(data as *const u8, length * 8);
|
||||||
|
writer.write_all(slice)?;
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let mut data = data;
|
||||||
|
for _ in 0..length {
|
||||||
|
send_value(writer, elt_tag, &mut data)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn send_value<W>(writer: &mut W, tag: Tag, data: &mut *const ())
|
unsafe fn send_value<W>(writer: &mut W, tag: Tag, data: &mut *const ())
|
||||||
-> Result<(), Error<W::WriteError>>
|
-> Result<(), Error<W::WriteError>>
|
||||||
where W: Write + ?Sized
|
where W: Write + ?Sized
|
||||||
|
@ -213,10 +266,13 @@ unsafe fn send_value<W>(writer: &mut W, tag: Tag, data: &mut *const ())
|
||||||
Tag::Tuple(it, arity) => {
|
Tag::Tuple(it, arity) => {
|
||||||
let mut it = it.clone();
|
let mut it = it.clone();
|
||||||
writer.write_u8(arity)?;
|
writer.write_u8(arity)?;
|
||||||
|
let mut max_alignment = 0;
|
||||||
for _ in 0..arity {
|
for _ in 0..arity {
|
||||||
let tag = it.next().expect("truncated tag");
|
let tag = it.next().expect("truncated tag");
|
||||||
|
max_alignment = core::cmp::max(max_alignment, tag.alignment());
|
||||||
send_value(writer, tag, data)?
|
send_value(writer, tag, data)?
|
||||||
}
|
}
|
||||||
|
*data = round_up_const(*data, max_alignment);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Tag::List(it) => {
|
Tag::List(it) => {
|
||||||
|
@ -226,30 +282,7 @@ unsafe fn send_value<W>(writer: &mut W, tag: Tag, data: &mut *const ())
|
||||||
let length = (**ptr).length as usize;
|
let length = (**ptr).length as usize;
|
||||||
writer.write_u32((**ptr).length)?;
|
writer.write_u32((**ptr).length)?;
|
||||||
let tag = it.clone().next().expect("truncated tag");
|
let tag = it.clone().next().expect("truncated tag");
|
||||||
let mut data = (**ptr).elements;
|
send_elements(writer, tag, length, (**ptr).elements)
|
||||||
writer.write_u8(tag.as_u8())?;
|
|
||||||
match tag {
|
|
||||||
// we cannot use NativeEndian::from_slice_i32 as the data is not mutable,
|
|
||||||
// and that is not needed as the data is already in native endian
|
|
||||||
Tag::Bool => {
|
|
||||||
let slice = slice::from_raw_parts(data as *const u8, length);
|
|
||||||
writer.write_all(slice)?;
|
|
||||||
},
|
|
||||||
Tag::Int32 => {
|
|
||||||
let slice = slice::from_raw_parts(data as *const u8, length * 4);
|
|
||||||
writer.write_all(slice)?;
|
|
||||||
},
|
|
||||||
Tag::Int64 | Tag::Float64 => {
|
|
||||||
let slice = slice::from_raw_parts(data as *const u8, length * 8);
|
|
||||||
writer.write_all(slice)?;
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
for _ in 0..length {
|
|
||||||
send_value(writer, tag, &mut data)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Tag::Array(it, num_dims) => {
|
Tag::Array(it, num_dims) => {
|
||||||
|
@ -265,30 +298,7 @@ unsafe fn send_value<W>(writer: &mut W, tag: Tag, data: &mut *const ())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
let length = total_len as usize;
|
let length = total_len as usize;
|
||||||
let mut data = *buffer;
|
send_elements(writer, elt_tag, length, *buffer)
|
||||||
writer.write_u8(elt_tag.as_u8())?;
|
|
||||||
match elt_tag {
|
|
||||||
// we cannot use NativeEndian::from_slice_i32 as the data is not mutable,
|
|
||||||
// and that is not needed as the data is already in native endian
|
|
||||||
Tag::Bool => {
|
|
||||||
let slice = slice::from_raw_parts(data as *const u8, length);
|
|
||||||
writer.write_all(slice)?;
|
|
||||||
},
|
|
||||||
Tag::Int32 => {
|
|
||||||
let slice = slice::from_raw_parts(data as *const u8, length * 4);
|
|
||||||
writer.write_all(slice)?;
|
|
||||||
},
|
|
||||||
Tag::Int64 | Tag::Float64 => {
|
|
||||||
let slice = slice::from_raw_parts(data as *const u8, length * 8);
|
|
||||||
writer.write_all(slice)?;
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
for _ in 0..length {
|
|
||||||
send_value(writer, elt_tag, &mut data)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Tag::Range(it) => {
|
Tag::Range(it) => {
|
||||||
|
@ -349,7 +359,7 @@ pub fn send_args<W>(writer: &mut W, service: u32, tag_bytes: &[u8], data: *const
|
||||||
|
|
||||||
mod tag {
|
mod tag {
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use super::alignment_offset;
|
use super::round_up;
|
||||||
|
|
||||||
pub fn split_tag(tag_bytes: &[u8]) -> (&[u8], &[u8]) {
|
pub fn split_tag(tag_bytes: &[u8]) -> (&[u8], &[u8]) {
|
||||||
let tag_separator =
|
let tag_separator =
|
||||||
|
@ -416,16 +426,18 @@ mod tag {
|
||||||
let it = it.clone();
|
let it = it.clone();
|
||||||
it.take(3).map(|t| t.alignment()).max().unwrap()
|
it.take(3).map(|t| t.alignment()).max().unwrap()
|
||||||
}
|
}
|
||||||
// CSlice basically
|
// the ptr/length(s) pair is basically CSlice
|
||||||
Tag::Bytes | Tag::String | Tag::ByteArray | Tag::List(_) =>
|
Tag::Bytes | Tag::String | Tag::ByteArray | Tag::List(_) | Tag::Array(_, _) =>
|
||||||
core::mem::align_of::<CSlice<()>>(),
|
core::mem::align_of::<CSlice<()>>(),
|
||||||
// array buffer is allocated, so no need for alignment first
|
|
||||||
Tag::Array(_, _) => 1,
|
|
||||||
// will not be sent from the host
|
// will not be sent from the host
|
||||||
_ => unreachable!("unexpected tag from host")
|
_ => unreachable!("unexpected tag from host")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the "alignment size" of a value with the type described by the tag
|
||||||
|
/// (in bytes), i.e. the stride between successive elements in a list/array of
|
||||||
|
/// the given type, or the offset from a struct element of this type to the
|
||||||
|
/// next field.
|
||||||
pub fn size(self) -> usize {
|
pub fn size(self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Tag::None => 0,
|
Tag::None => 0,
|
||||||
|
@ -438,12 +450,18 @@ mod tag {
|
||||||
Tag::ByteArray => 8,
|
Tag::ByteArray => 8,
|
||||||
Tag::Tuple(it, arity) => {
|
Tag::Tuple(it, arity) => {
|
||||||
let mut size = 0;
|
let mut size = 0;
|
||||||
|
let mut max_alignment = 0;
|
||||||
let mut it = it.clone();
|
let mut it = it.clone();
|
||||||
for _ in 0..arity {
|
for _ in 0..arity {
|
||||||
let tag = it.next().expect("truncated tag");
|
let tag = it.next().expect("truncated tag");
|
||||||
|
let alignment = tag.alignment();
|
||||||
|
max_alignment = core::cmp::max(max_alignment, alignment);
|
||||||
|
size = round_up(size, alignment);
|
||||||
size += tag.size();
|
size += tag.size();
|
||||||
size += alignment_offset(tag.alignment() as isize, size as isize) as usize;
|
|
||||||
}
|
}
|
||||||
|
// Take into account any tail padding (if element(s) with largest
|
||||||
|
// alignment are not at the end).
|
||||||
|
size = round_up(size, max_alignment);
|
||||||
size
|
size
|
||||||
}
|
}
|
||||||
Tag::List(_) => 8,
|
Tag::List(_) => 8,
|
||||||
|
|
|
@ -29,7 +29,19 @@ def get_artiq_rev():
|
||||||
import artiq
|
import artiq
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return None
|
return None
|
||||||
return artiq._version.get_rev()
|
rev = artiq._version.get_rev()
|
||||||
|
if rev == "unknown":
|
||||||
|
return None
|
||||||
|
return rev
|
||||||
|
|
||||||
|
|
||||||
|
def get_artiq_major_version():
|
||||||
|
try:
|
||||||
|
import artiq
|
||||||
|
except ImportError:
|
||||||
|
return None
|
||||||
|
version = artiq._version.get_version()
|
||||||
|
return version.split(".")[0]
|
||||||
|
|
||||||
|
|
||||||
def zip_unarchive(data, directory):
|
def zip_unarchive(data, directory):
|
||||||
|
@ -70,17 +82,18 @@ class Client:
|
||||||
self.send_command("LOGIN", username, password)
|
self.send_command("LOGIN", username, password)
|
||||||
return self.read_reply() == ["HELLO"]
|
return self.read_reply() == ["HELLO"]
|
||||||
|
|
||||||
def build(self, rev, variant, log):
|
def build(self, major_ver, rev, variant, log, experimental_features):
|
||||||
if not variant:
|
if not variant:
|
||||||
variants = self.get_variants()
|
variant = self.get_single_variant(error_msg="User can build more than 1 variant - need to specify")
|
||||||
if len(variants) != 1:
|
|
||||||
raise ValueError("User can build more than 1 variant - need to specify")
|
|
||||||
variant = variants[0][0]
|
|
||||||
print("Building variant: {}".format(variant))
|
print("Building variant: {}".format(variant))
|
||||||
if log:
|
build_args = (
|
||||||
self.send_command("BUILD", rev, variant, "LOG_ENABLE")
|
rev,
|
||||||
else:
|
variant,
|
||||||
self.send_command("BUILD", rev, variant)
|
"LOG_ENABLE" if log else "LOG_DISABLE",
|
||||||
|
major_ver,
|
||||||
|
*experimental_features,
|
||||||
|
)
|
||||||
|
self.send_command("BUILD", *build_args)
|
||||||
reply = self.read_reply()[0]
|
reply = self.read_reply()[0]
|
||||||
if reply != "BUILDING":
|
if reply != "BUILDING":
|
||||||
return reply, None
|
return reply, None
|
||||||
|
@ -124,6 +137,26 @@ class Client:
|
||||||
raise ValueError("Unexpected server reply: expected 'OK', got '{}'".format(reply))
|
raise ValueError("Unexpected server reply: expected 'OK', got '{}'".format(reply))
|
||||||
return self.read_json()
|
return self.read_json()
|
||||||
|
|
||||||
|
def get_single_variant(self, error_msg):
|
||||||
|
variants = self.get_variants()
|
||||||
|
if len(variants) != 1:
|
||||||
|
print(error_msg)
|
||||||
|
table = PrettyTable()
|
||||||
|
table.field_names = ["Variant", "Expiry date"]
|
||||||
|
table.add_rows(variants)
|
||||||
|
print(table)
|
||||||
|
sys.exit(1)
|
||||||
|
return variants[0][0]
|
||||||
|
|
||||||
|
def get_json(self, variant):
|
||||||
|
self.send_command("GET_JSON", variant)
|
||||||
|
reply = self.read_reply()
|
||||||
|
if reply[0] != "OK":
|
||||||
|
return reply[0], None
|
||||||
|
length = int(reply[1])
|
||||||
|
json_str = self.fsocket.read(length).decode("ascii")
|
||||||
|
return "OK", json_str
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
|
@ -134,12 +167,18 @@ def main():
|
||||||
action = parser.add_subparsers(dest="action")
|
action = parser.add_subparsers(dest="action")
|
||||||
action.required = True
|
action.required = True
|
||||||
act_build = action.add_parser("build", help="build and download firmware")
|
act_build = action.add_parser("build", help="build and download firmware")
|
||||||
|
act_build.add_argument("--major-ver", default=None, help="ARTIQ major version")
|
||||||
act_build.add_argument("--rev", default=None, help="revision to build (default: currently installed ARTIQ revision)")
|
act_build.add_argument("--rev", default=None, help="revision to build (default: currently installed ARTIQ revision)")
|
||||||
act_build.add_argument("--log", action="store_true", help="Display the build log")
|
act_build.add_argument("--log", action="store_true", help="Display the build log")
|
||||||
|
act_build.add_argument("--experimental", action="append", default=[], help="enable an experimental feature (can be repeatedly specified to enable multiple features)")
|
||||||
act_build.add_argument("directory", help="output directory")
|
act_build.add_argument("directory", help="output directory")
|
||||||
act_build.add_argument("variant", nargs="?", default=None, help="variant to build (can be omitted if user is authorised to build only one)")
|
act_build.add_argument("variant", nargs="?", default=None, help="variant to build (can be omitted if user is authorised to build only one)")
|
||||||
act_passwd = action.add_parser("passwd", help="change password")
|
act_passwd = action.add_parser("passwd", help="change password")
|
||||||
act_get_variants = action.add_parser("get_variants", help="get available variants and expiry dates")
|
act_get_variants = action.add_parser("get_variants", help="get available variants and expiry dates")
|
||||||
|
act_get_json = action.add_parser("get_json", help="get JSON description file of variant")
|
||||||
|
act_get_json.add_argument("variant", nargs="?", default=None, help="variant to get (can be omitted if user is authorised to build only one)")
|
||||||
|
act_get_json.add_argument("-o", "--out", default=None, help="output JSON file")
|
||||||
|
act_get_json.add_argument("-f", "--force", action="store_true", help="overwrite file if it already exists")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
cert = args.cert
|
cert = args.cert
|
||||||
|
@ -182,13 +221,19 @@ def main():
|
||||||
except NotADirectoryError:
|
except NotADirectoryError:
|
||||||
print("A file with the same name as the output directory already exists. Please remove it and try again.")
|
print("A file with the same name as the output directory already exists. Please remove it and try again.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
major_ver = args.major_ver
|
||||||
|
if major_ver is None:
|
||||||
|
major_ver = get_artiq_major_version()
|
||||||
|
if major_ver is None:
|
||||||
|
print("Unable to determine currently installed ARTIQ major version. Specify manually using --major-ver.")
|
||||||
|
sys.exit(1)
|
||||||
rev = args.rev
|
rev = args.rev
|
||||||
if rev is None:
|
if rev is None:
|
||||||
rev = get_artiq_rev()
|
rev = get_artiq_rev()
|
||||||
if rev is None:
|
if rev is None:
|
||||||
print("Unable to determine currently installed ARTIQ revision. Specify manually using --rev.")
|
print("Unable to determine currently installed ARTIQ revision. Specify manually using --rev.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
result, contents = client.build(rev, args.variant, args.log)
|
result, contents = client.build(major_ver, rev, args.variant, args.log, args.experimental)
|
||||||
if result != "OK":
|
if result != "OK":
|
||||||
if result == "UNAUTHORIZED":
|
if result == "UNAUTHORIZED":
|
||||||
print("You are not authorized to build this variant. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.")
|
print("You are not authorized to build this variant. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.")
|
||||||
|
@ -204,6 +249,24 @@ def main():
|
||||||
table.field_names = ["Variant", "Expiry date"]
|
table.field_names = ["Variant", "Expiry date"]
|
||||||
table.add_rows(data)
|
table.add_rows(data)
|
||||||
print(table)
|
print(table)
|
||||||
|
elif args.action == "get_json":
|
||||||
|
if args.variant:
|
||||||
|
variant = args.variant
|
||||||
|
else:
|
||||||
|
variant = client.get_single_variant(error_msg="User can get JSON of more than 1 variant - need to specify")
|
||||||
|
result, json_str = client.get_json(variant)
|
||||||
|
if result != "OK":
|
||||||
|
if result == "UNAUTHORIZED":
|
||||||
|
print(f"You are not authorized to get JSON of variant {variant}. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.")
|
||||||
|
sys.exit(1)
|
||||||
|
if args.out:
|
||||||
|
if not args.force and os.path.exists(args.out):
|
||||||
|
print(f"File {args.out} already exists. You can use -f to overwrite the existing file.")
|
||||||
|
sys.exit(1)
|
||||||
|
with open(args.out, "w") as f:
|
||||||
|
f.write(json_str)
|
||||||
|
else:
|
||||||
|
print(json_str)
|
||||||
else:
|
else:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
finally:
|
finally:
|
||||||
|
|
|
@ -153,7 +153,7 @@ def main():
|
||||||
|
|
||||||
browser = Browser(smgr, datasets_sub, args.browse_root,
|
browser = Browser(smgr, datasets_sub, args.browse_root,
|
||||||
args.server, args.port)
|
args.server, args.port)
|
||||||
widget_log_handler.callback = browser.log.append_message
|
widget_log_handler.callback = browser.log.model.append
|
||||||
|
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
# HACK: show the main window before creating applets.
|
# HACK: show the main window before creating applets.
|
||||||
|
|
|
@ -248,9 +248,8 @@ def main():
|
||||||
server_description = server_name + " ({})".format(args.server)
|
server_description = server_name + " ({})".format(args.server)
|
||||||
else:
|
else:
|
||||||
server_description = args.server
|
server_description = args.server
|
||||||
logging.info("ARTIQ dashboard version: %s",
|
logging.info("ARTIQ dashboard %s connected to master %s",
|
||||||
artiq_version)
|
artiq_version, server_description)
|
||||||
logging.info("ARTIQ dashboard connected to moninj_proxy (%s)", server_description)
|
|
||||||
# run
|
# run
|
||||||
main_window.show()
|
main_window.show()
|
||||||
loop.run_until_complete(main_window.exit_request.wait())
|
loop.run_until_complete(main_window.exit_request.wait())
|
||||||
|
|
|
@ -524,13 +524,21 @@ class PeripheralManager:
|
||||||
"type": "local",
|
"type": "local",
|
||||||
"module": "artiq.coredevice.fastino",
|
"module": "artiq.coredevice.fastino",
|
||||||
"class": "Fastino",
|
"class": "Fastino",
|
||||||
"arguments": {{"channel": 0x{channel:06x}}}
|
"arguments": {{"channel": 0x{channel:06x}, "log2_width": {log2_width}}}
|
||||||
}}""",
|
}}""",
|
||||||
name=self.get_name("fastino"),
|
name=self.get_name("fastino"),
|
||||||
channel=rtio_offset)
|
channel=rtio_offset,
|
||||||
|
log2_width=peripheral["log2_width"])
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def process_phaser(self, rtio_offset, peripheral):
|
def process_phaser(self, rtio_offset, peripheral):
|
||||||
|
mode = peripheral.get("mode", "base")
|
||||||
|
if mode == "miqro":
|
||||||
|
dac = ', "dac": {"pll_m": 16, "pll_n": 3, "interpolation": 2}'
|
||||||
|
n_channels = 3
|
||||||
|
else:
|
||||||
|
dac = ""
|
||||||
|
n_channels = 5
|
||||||
self.gen("""
|
self.gen("""
|
||||||
device_db["{name}"] = {{
|
device_db["{name}"] = {{
|
||||||
"type": "local",
|
"type": "local",
|
||||||
|
@ -538,12 +546,13 @@ class PeripheralManager:
|
||||||
"class": "Phaser",
|
"class": "Phaser",
|
||||||
"arguments": {{
|
"arguments": {{
|
||||||
"channel_base": 0x{channel:06x},
|
"channel_base": 0x{channel:06x},
|
||||||
"miso_delay": 1,
|
"miso_delay": 1{dac}
|
||||||
}}
|
}}
|
||||||
}}""",
|
}}""",
|
||||||
name=self.get_name("phaser"),
|
name=self.get_name("phaser"),
|
||||||
|
dac=dac,
|
||||||
channel=rtio_offset)
|
channel=rtio_offset)
|
||||||
return 5
|
return n_channels
|
||||||
|
|
||||||
def process_hvamp(self, rtio_offset, peripheral):
|
def process_hvamp(self, rtio_offset, peripheral):
|
||||||
hvamp_name = self.get_name("hvamp")
|
hvamp_name = self.get_name("hvamp")
|
||||||
|
|
|
@ -18,9 +18,10 @@ from artiq.coredevice.adf5356 import ADF5356
|
||||||
from artiq.coredevice.sampler import Sampler
|
from artiq.coredevice.sampler import Sampler
|
||||||
from artiq.coredevice.zotino import Zotino
|
from artiq.coredevice.zotino import Zotino
|
||||||
from artiq.coredevice.fastino import Fastino
|
from artiq.coredevice.fastino import Fastino
|
||||||
from artiq.coredevice.phaser import Phaser
|
from artiq.coredevice.phaser import Phaser, PHASER_GW_BASE, PHASER_GW_MIQRO
|
||||||
from artiq.coredevice.grabber import Grabber
|
from artiq.coredevice.grabber import Grabber
|
||||||
from artiq.coredevice.suservo import SUServo, Channel as SUServoChannel
|
from artiq.coredevice.suservo import SUServo, Channel as SUServoChannel
|
||||||
|
|
||||||
from artiq.master.databases import DeviceDB
|
from artiq.master.databases import DeviceDB
|
||||||
from artiq.master.worker_db import DeviceManager
|
from artiq.master.worker_db import DeviceManager
|
||||||
|
|
||||||
|
@ -590,12 +591,13 @@ class SinaraTester(EnvExperiment):
|
||||||
self.core.break_realtime()
|
self.core.break_realtime()
|
||||||
phaser.init()
|
phaser.init()
|
||||||
self.core.delay(1.*ms)
|
self.core.delay(1.*ms)
|
||||||
|
if phaser.gw_rev == PHASER_GW_BASE:
|
||||||
phaser.channel[0].set_duc_frequency(duc)
|
phaser.channel[0].set_duc_frequency(duc)
|
||||||
phaser.channel[0].set_duc_cfg()
|
phaser.channel[0].set_duc_cfg()
|
||||||
phaser.channel[0].set_att(6.*dB)
|
phaser.channel[0].set_att(6*dB)
|
||||||
phaser.channel[1].set_duc_frequency(-duc)
|
phaser.channel[1].set_duc_frequency(-duc)
|
||||||
phaser.channel[1].set_duc_cfg()
|
phaser.channel[1].set_duc_cfg()
|
||||||
phaser.channel[1].set_att(6.*dB)
|
phaser.channel[1].set_att(6*dB)
|
||||||
phaser.duc_stb()
|
phaser.duc_stb()
|
||||||
self.core.delay(1.*ms)
|
self.core.delay(1.*ms)
|
||||||
for i in range(len(osc)):
|
for i in range(len(osc)):
|
||||||
|
@ -604,6 +606,22 @@ class SinaraTester(EnvExperiment):
|
||||||
phaser.channel[1].oscillator[i].set_frequency(-osc[i])
|
phaser.channel[1].oscillator[i].set_frequency(-osc[i])
|
||||||
phaser.channel[1].oscillator[i].set_amplitude_phase(.2)
|
phaser.channel[1].oscillator[i].set_amplitude_phase(.2)
|
||||||
self.core.delay(1.*ms)
|
self.core.delay(1.*ms)
|
||||||
|
elif phaser.gw_rev == PHASER_GW_MIQRO:
|
||||||
|
for ch in range(2):
|
||||||
|
phaser.channel[ch].set_att(6*dB)
|
||||||
|
phaser.channel[ch].set_duc_cfg()
|
||||||
|
sign = 1. - 2.*ch
|
||||||
|
for i in range(len(osc)):
|
||||||
|
phaser.channel[ch].miqro.set_profile(i, profile=1,
|
||||||
|
frequency=sign*(duc + osc[i]), amplitude=1./len(osc))
|
||||||
|
self.core.delay(100.*us)
|
||||||
|
phaser.channel[ch].miqro.set_window(
|
||||||
|
start=0x000, iq=[[1., 0.]], order=0, tail=0)
|
||||||
|
phaser.channel[ch].miqro.pulse(
|
||||||
|
window=0x000, profiles=[1 for _ in range(len(osc))])
|
||||||
|
self.core.delay(1.*ms)
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def phaser_led_wave(self, phasers: list[Phaser]):
|
def phaser_led_wave(self, phasers: list[Phaser]):
|
||||||
|
|
|
@ -662,10 +662,11 @@ class Phaser(_EEM):
|
||||||
) for pol in "pn"]
|
) for pol in "pn"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_std(cls, target, eem, iostandard=default_iostandard):
|
def add_std(cls, target, eem, mode="base", iostandard=default_iostandard):
|
||||||
cls.add_extension(target, eem, iostandard=iostandard)
|
cls.add_extension(target, eem, iostandard=iostandard)
|
||||||
|
|
||||||
phy = phaser.Phaser(
|
if mode == "base":
|
||||||
|
phy = phaser.Base(
|
||||||
target.platform.request("phaser{}_ser_p".format(eem)),
|
target.platform.request("phaser{}_ser_p".format(eem)),
|
||||||
target.platform.request("phaser{}_ser_n".format(eem)))
|
target.platform.request("phaser{}_ser_n".format(eem)))
|
||||||
target.submodules += phy
|
target.submodules += phy
|
||||||
|
@ -676,6 +677,18 @@ class Phaser(_EEM):
|
||||||
rtio.Channel.from_phy(phy.ch1.frequency),
|
rtio.Channel.from_phy(phy.ch1.frequency),
|
||||||
rtio.Channel.from_phy(phy.ch1.phase_amplitude),
|
rtio.Channel.from_phy(phy.ch1.phase_amplitude),
|
||||||
])
|
])
|
||||||
|
elif mode == "miqro":
|
||||||
|
phy = phaser.Miqro(
|
||||||
|
target.platform.request("phaser{}_ser_p".format(eem)),
|
||||||
|
target.platform.request("phaser{}_ser_n".format(eem)))
|
||||||
|
target.submodules += phy
|
||||||
|
target.rtio_channels.extend([
|
||||||
|
rtio.Channel.from_phy(phy, ififo_depth=4),
|
||||||
|
rtio.Channel.from_phy(phy.ch0),
|
||||||
|
rtio.Channel.from_phy(phy.ch1),
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
raise ValueError("invalid mode", mode)
|
||||||
|
|
||||||
|
|
||||||
class HVAmp(_EEM):
|
class HVAmp(_EEM):
|
||||||
|
|
|
@ -116,7 +116,8 @@ def peripheral_fastino(module, peripheral, **kwargs):
|
||||||
def peripheral_phaser(module, peripheral, **kwargs):
|
def peripheral_phaser(module, peripheral, **kwargs):
|
||||||
if len(peripheral["ports"]) != 1:
|
if len(peripheral["ports"]) != 1:
|
||||||
raise ValueError("wrong number of ports")
|
raise ValueError("wrong number of ports")
|
||||||
eem.Phaser.add_std(module, peripheral["ports"][0], **kwargs)
|
eem.Phaser.add_std(module, peripheral["ports"][0],
|
||||||
|
peripheral.get("mode", "base"), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def peripheral_hvamp(module, peripheral, **kwargs):
|
def peripheral_hvamp(module, peripheral, **kwargs):
|
||||||
|
|
|
@ -27,7 +27,7 @@ class DDSChannel(Module):
|
||||||
[Cat(i.a, i.clr, i.p) for i in self.dds.i])
|
[Cat(i.a, i.clr, i.p) for i in self.dds.i])
|
||||||
|
|
||||||
|
|
||||||
class Phaser(Module):
|
class Base(Module):
|
||||||
def __init__(self, pins, pins_n):
|
def __init__(self, pins, pins_n):
|
||||||
self.rtlink = rtlink.Interface(
|
self.rtlink = rtlink.Interface(
|
||||||
rtlink.OInterface(data_width=8, address_width=8,
|
rtlink.OInterface(data_width=8, address_width=8,
|
||||||
|
@ -87,3 +87,93 @@ class Phaser(Module):
|
||||||
self.rtlink.i.stb.eq(re_dly[0] & self.serializer.stb),
|
self.rtlink.i.stb.eq(re_dly[0] & self.serializer.stb),
|
||||||
self.rtlink.i.data.eq(self.serializer.readback),
|
self.rtlink.i.data.eq(self.serializer.readback),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class MiqroChannel(Module):
|
||||||
|
def __init__(self):
|
||||||
|
self.rtlink = rtlink.Interface(
|
||||||
|
rtlink.OInterface(data_width=30, address_width=2, fine_ts_width=1,
|
||||||
|
enable_replace=False))
|
||||||
|
self.pulse = Signal(128)
|
||||||
|
self.ack = Signal()
|
||||||
|
regs = [Signal(30, reset_less=True) for _ in range(3)]
|
||||||
|
dt = Signal(7, reset_less=True)
|
||||||
|
stb = Signal()
|
||||||
|
pulse = Cat(stb, dt, regs)
|
||||||
|
assert len(self.pulse) >= len(pulse)
|
||||||
|
self.comb += [
|
||||||
|
self.pulse.eq(pulse),
|
||||||
|
self.rtlink.o.busy.eq(stb & ~self.ack),
|
||||||
|
]
|
||||||
|
self.sync.rtio += [
|
||||||
|
If(~stb,
|
||||||
|
dt.eq(dt + 2),
|
||||||
|
),
|
||||||
|
If(self.ack,
|
||||||
|
dt[1:].eq(0),
|
||||||
|
stb.eq(0),
|
||||||
|
If(stb,
|
||||||
|
[r.eq(0) for r in regs],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
If(self.rtlink.o.stb,
|
||||||
|
Array(regs)[self.rtlink.o.address].eq(self.rtlink.o.data),
|
||||||
|
If(self.rtlink.o.address == 0,
|
||||||
|
dt[0].eq(self.rtlink.o.fine_ts),
|
||||||
|
stb.eq(1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Miqro(Module):
|
||||||
|
def __init__(self, pins, pins_n):
|
||||||
|
self.rtlink = rtlink.Interface(
|
||||||
|
rtlink.OInterface(data_width=8, address_width=8,
|
||||||
|
enable_replace=False),
|
||||||
|
rtlink.IInterface(data_width=10))
|
||||||
|
|
||||||
|
self.submodules.ch0 = MiqroChannel()
|
||||||
|
self.submodules.ch1 = MiqroChannel()
|
||||||
|
|
||||||
|
self.submodules.serializer = SerDes(
|
||||||
|
n_data=8, t_clk=8, d_clk=0b00001111,
|
||||||
|
n_frame=10, n_crc=6, poly=0x2f)
|
||||||
|
self.submodules.intf = SerInterface(pins, pins_n)
|
||||||
|
self.comb += [
|
||||||
|
Cat(self.intf.data[:-1]).eq(Cat(self.serializer.data[:-1])),
|
||||||
|
self.serializer.data[-1].eq(self.intf.data[-1]),
|
||||||
|
]
|
||||||
|
|
||||||
|
header = Record([
|
||||||
|
("we", 1),
|
||||||
|
("addr", 7),
|
||||||
|
("data", 8),
|
||||||
|
("type", 4)
|
||||||
|
])
|
||||||
|
self.comb += [
|
||||||
|
self.serializer.payload.eq(Cat(
|
||||||
|
header.raw_bits(),
|
||||||
|
self.ch0.pulse,
|
||||||
|
self.ch1.pulse,
|
||||||
|
)),
|
||||||
|
self.ch0.ack.eq(self.serializer.stb),
|
||||||
|
self.ch1.ack.eq(self.serializer.stb),
|
||||||
|
]
|
||||||
|
|
||||||
|
re_dly = Signal(3) # stage, send, respond
|
||||||
|
self.sync.rtio += [
|
||||||
|
header.type.eq(3), # body type is miqro pulse data
|
||||||
|
If(self.serializer.stb,
|
||||||
|
header.we.eq(0),
|
||||||
|
re_dly.eq(re_dly[1:]),
|
||||||
|
),
|
||||||
|
If(self.rtlink.o.stb,
|
||||||
|
re_dly[-1].eq(~self.rtlink.o.address[-1]),
|
||||||
|
header.we.eq(self.rtlink.o.address[-1]),
|
||||||
|
header.addr.eq(self.rtlink.o.address),
|
||||||
|
header.data.eq(self.rtlink.o.data),
|
||||||
|
),
|
||||||
|
self.rtlink.i.stb.eq(re_dly[0] & self.serializer.stb),
|
||||||
|
self.rtlink.i.data.eq(self.serializer.readback),
|
||||||
|
]
|
||||||
|
|
|
@ -170,9 +170,11 @@ class Tester(StandaloneBase):
|
||||||
"""
|
"""
|
||||||
Configuration for CI tests. Contains the maximum number of different EEMs.
|
Configuration for CI tests. Contains the maximum number of different EEMs.
|
||||||
"""
|
"""
|
||||||
def __init__(self, hw_rev=None, **kwargs):
|
def __init__(self, hw_rev=None, dds=None, **kwargs):
|
||||||
if hw_rev is None:
|
if hw_rev is None:
|
||||||
hw_rev = "v2.0"
|
hw_rev = "v2.0"
|
||||||
|
if dds is None:
|
||||||
|
dds = "ad9910"
|
||||||
StandaloneBase.__init__(self, hw_rev=hw_rev, **kwargs)
|
StandaloneBase.__init__(self, hw_rev=hw_rev, **kwargs)
|
||||||
|
|
||||||
self.config["SI5324_AS_SYNTHESIZER"] = None
|
self.config["SI5324_AS_SYNTHESIZER"] = None
|
||||||
|
@ -186,7 +188,7 @@ class Tester(StandaloneBase):
|
||||||
eem.DIO.add_std(self, 5,
|
eem.DIO.add_std(self, 5,
|
||||||
ttl_serdes_7series.InOut_8X, ttl_serdes_7series.Output_8X,
|
ttl_serdes_7series.InOut_8X, ttl_serdes_7series.Output_8X,
|
||||||
edge_counter_cls=edge_counter.SimpleEdgeCounter)
|
edge_counter_cls=edge_counter.SimpleEdgeCounter)
|
||||||
eem.Urukul.add_std(self, 0, 1, ttl_serdes_7series.Output_8X,
|
eem.Urukul.add_std(self, 0, 1, ttl_serdes_7series.Output_8X, dds,
|
||||||
ttl_simple.ClockGen)
|
ttl_simple.ClockGen)
|
||||||
eem.Sampler.add_std(self, 3, 2, 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.Zotino.add_std(self, 4, ttl_serdes_7series.Output_8X)
|
||||||
|
@ -686,6 +688,9 @@ def main():
|
||||||
help="variant: {} (default: %(default)s)".format(
|
help="variant: {} (default: %(default)s)".format(
|
||||||
"/".join(sorted(VARIANTS.keys()))))
|
"/".join(sorted(VARIANTS.keys()))))
|
||||||
parser.add_argument("--with-wrpll", default=False, action="store_true")
|
parser.add_argument("--with-wrpll", default=False, action="store_true")
|
||||||
|
parser.add_argument("--tester-dds", default=None,
|
||||||
|
help="Tester variant DDS type: ad9910/ad9912 "
|
||||||
|
"(default: ad9910)")
|
||||||
parser.add_argument("--gateware-identifier-str", default=None,
|
parser.add_argument("--gateware-identifier-str", default=None,
|
||||||
help="Override ROM identifier")
|
help="Override ROM identifier")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
@ -694,6 +699,7 @@ def main():
|
||||||
if args.with_wrpll:
|
if args.with_wrpll:
|
||||||
argdict["with_wrpll"] = True
|
argdict["with_wrpll"] = True
|
||||||
argdict["gateware_identifier_str"] = args.gateware_identifier_str
|
argdict["gateware_identifier_str"] = args.gateware_identifier_str
|
||||||
|
argdict["dds"] = args.tester_dds
|
||||||
|
|
||||||
variant = args.variant.lower()
|
variant = args.variant.lower()
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -18,6 +18,32 @@ class _ModelItem:
|
||||||
self.children_by_row = []
|
self.children_by_row = []
|
||||||
|
|
||||||
|
|
||||||
|
class _LogFilterProxyModel(QtCore.QSortFilterProxyModel):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||||
|
self.setRecursiveFilteringEnabled(True)
|
||||||
|
self.filter_level = 0
|
||||||
|
|
||||||
|
def filterAcceptsRow(self, source_row, source_parent):
|
||||||
|
source = self.sourceModel()
|
||||||
|
index0 = source.index(source_row, 0, source_parent)
|
||||||
|
index1 = source.index(source_row, 1, source_parent)
|
||||||
|
level = source.data(index0, QtCore.Qt.UserRole)
|
||||||
|
|
||||||
|
if level >= self.filter_level:
|
||||||
|
regex = self.filterRegExp()
|
||||||
|
index0_text = source.data(index0, QtCore.Qt.DisplayRole)
|
||||||
|
msg_text = source.data(index1, QtCore.Qt.DisplayRole)
|
||||||
|
return (regex.indexIn(index0_text) != -1 or regex.indexIn(msg_text) != -1)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def apply_filter_level(self, filter_level):
|
||||||
|
self.filter_level = getattr(logging, filter_level)
|
||||||
|
self.invalidateFilter()
|
||||||
|
|
||||||
|
|
||||||
class _Model(QtCore.QAbstractItemModel):
|
class _Model(QtCore.QAbstractItemModel):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QtCore.QAbstractTableModel.__init__(self)
|
QtCore.QAbstractTableModel.__init__(self)
|
||||||
|
@ -168,6 +194,8 @@ class _Model(QtCore.QAbstractItemModel):
|
||||||
return (log_level_to_name(v[0]) + ", " +
|
return (log_level_to_name(v[0]) + ", " +
|
||||||
time.strftime("%m/%d %H:%M:%S", time.localtime(v[2])) +
|
time.strftime("%m/%d %H:%M:%S", time.localtime(v[2])) +
|
||||||
"\n" + v[3][lineno])
|
"\n" + v[3][lineno])
|
||||||
|
elif role == QtCore.Qt.UserRole:
|
||||||
|
return self.entries[msgnum][0]
|
||||||
|
|
||||||
|
|
||||||
class LogDock(QDockWidgetCloseDetect):
|
class LogDock(QDockWidgetCloseDetect):
|
||||||
|
@ -181,11 +209,11 @@ class LogDock(QDockWidgetCloseDetect):
|
||||||
grid.addWidget(QtWidgets.QLabel("Minimum level: "), 0, 0)
|
grid.addWidget(QtWidgets.QLabel("Minimum level: "), 0, 0)
|
||||||
self.filter_level = QtWidgets.QComboBox()
|
self.filter_level = QtWidgets.QComboBox()
|
||||||
self.filter_level.addItems(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"])
|
self.filter_level.addItems(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"])
|
||||||
self.filter_level.setToolTip("Receive entries at or above this level")
|
self.filter_level.setToolTip("Filter entries at or above this level")
|
||||||
grid.addWidget(self.filter_level, 0, 1)
|
grid.addWidget(self.filter_level, 0, 1)
|
||||||
self.filter_freetext = QtWidgets.QLineEdit()
|
self.filter_freetext = QtWidgets.QLineEdit()
|
||||||
self.filter_freetext.setPlaceholderText("freetext filter...")
|
self.filter_freetext.setPlaceholderText("freetext filter...")
|
||||||
self.filter_freetext.setToolTip("Receive entries containing this text")
|
self.filter_freetext.setToolTip("Filter entries containing this text")
|
||||||
grid.addWidget(self.filter_freetext, 0, 2)
|
grid.addWidget(self.filter_freetext, 0, 2)
|
||||||
|
|
||||||
scrollbottom = QtWidgets.QToolButton()
|
scrollbottom = QtWidgets.QToolButton()
|
||||||
|
@ -240,27 +268,22 @@ class LogDock(QDockWidgetCloseDetect):
|
||||||
self.log.header().resizeSection(0, 26*cw)
|
self.log.header().resizeSection(0, 26*cw)
|
||||||
|
|
||||||
self.model = _Model()
|
self.model = _Model()
|
||||||
self.log.setModel(self.model)
|
self.proxy_model = _LogFilterProxyModel()
|
||||||
|
self.proxy_model.setSourceModel(self.model)
|
||||||
|
self.log.setModel(self.proxy_model)
|
||||||
|
|
||||||
self.model.rowsAboutToBeInserted.connect(self.rows_inserted_before)
|
self.model.rowsAboutToBeInserted.connect(self.rows_inserted_before)
|
||||||
self.model.rowsInserted.connect(self.rows_inserted_after)
|
self.model.rowsInserted.connect(self.rows_inserted_after)
|
||||||
self.model.rowsRemoved.connect(self.rows_removed)
|
self.model.rowsRemoved.connect(self.rows_removed)
|
||||||
|
|
||||||
def append_message(self, msg):
|
self.filter_freetext.returnPressed.connect(self.apply_text_filter)
|
||||||
min_level = getattr(logging, self.filter_level.currentText())
|
self.filter_level.currentIndexChanged.connect(self.apply_level_filter)
|
||||||
freetext = self.filter_freetext.text()
|
|
||||||
|
|
||||||
accepted_level = msg[0] >= min_level
|
def apply_text_filter(self):
|
||||||
|
self.proxy_model.setFilterRegExp(self.filter_freetext.text())
|
||||||
|
|
||||||
if freetext:
|
def apply_level_filter(self):
|
||||||
data_source = msg[1]
|
self.proxy_model.apply_filter_level(self.filter_level.currentText())
|
||||||
data_message = msg[3]
|
|
||||||
accepted_freetext = (freetext in data_source
|
|
||||||
or any(freetext in m for m in data_message))
|
|
||||||
else:
|
|
||||||
accepted_freetext = True
|
|
||||||
|
|
||||||
if accepted_level and accepted_freetext:
|
|
||||||
self.model.append(msg)
|
|
||||||
|
|
||||||
def scroll_to_bottom(self):
|
def scroll_to_bottom(self):
|
||||||
self.log.scrollToBottom()
|
self.log.scrollToBottom()
|
||||||
|
@ -291,7 +314,8 @@ class LogDock(QDockWidgetCloseDetect):
|
||||||
def copy_to_clipboard(self):
|
def copy_to_clipboard(self):
|
||||||
idx = self.log.selectedIndexes()
|
idx = self.log.selectedIndexes()
|
||||||
if idx:
|
if idx:
|
||||||
entry = "\n".join(self.model.full_entry(idx[0]))
|
source_idx = self.proxy_model.mapToSource(idx[0])
|
||||||
|
entry = "\n".join(self.model.full_entry(source_idx))
|
||||||
QtWidgets.QApplication.clipboard().setText(entry)
|
QtWidgets.QApplication.clipboard().setText(entry)
|
||||||
|
|
||||||
def save_state(self):
|
def save_state(self):
|
||||||
|
@ -331,7 +355,7 @@ class LogDockManager:
|
||||||
|
|
||||||
def append_message(self, msg):
|
def append_message(self, msg):
|
||||||
for dock in self.docks.values():
|
for dock in self.docks.values():
|
||||||
dock.append_message(msg)
|
dock.model.append(msg)
|
||||||
|
|
||||||
def create_new_dock(self, add_to_area=True):
|
def create_new_dock(self, add_to_area=True):
|
||||||
n = 0
|
n = 0
|
||||||
|
|
|
@ -79,7 +79,10 @@ class RoundtripTest(ExperimentCase):
|
||||||
self.assertRoundtrip(([1, 2], [3, 4]))
|
self.assertRoundtrip(([1, 2], [3, 4]))
|
||||||
|
|
||||||
def test_list_mixed_tuple(self):
|
def test_list_mixed_tuple(self):
|
||||||
self.assertRoundtrip([(0x12345678, [("foo", [0.0, 1.0], [0, 1])])])
|
self.assertRoundtrip([
|
||||||
|
(0x12345678, [("foo", [0.0, 1.0], [0, 1])]),
|
||||||
|
(0x23456789, [("bar", [2.0, 3.0], [2, 3])])])
|
||||||
|
self.assertRoundtrip([(0, 1.0, 0), (1, 1.5, 2), (2, 1.9, 4)])
|
||||||
|
|
||||||
def test_array_1d(self):
|
def test_array_1d(self):
|
||||||
self.assertArrayRoundtrip(numpy.array([True, False]))
|
self.assertArrayRoundtrip(numpy.array([True, False]))
|
||||||
|
@ -521,19 +524,32 @@ class NumpyBoolTest(ExperimentCase):
|
||||||
class _Alignment(EnvExperiment):
|
class _Alignment(EnvExperiment):
|
||||||
def build(self):
|
def build(self):
|
||||||
self.setattr_device("core")
|
self.setattr_device("core")
|
||||||
|
self.a = False
|
||||||
|
self.b = 1234.5678
|
||||||
|
self.c = True
|
||||||
|
self.d = True
|
||||||
|
self.e = 2345.6789
|
||||||
|
self.f = False
|
||||||
|
|
||||||
@rpc
|
@rpc
|
||||||
def a_tuple(self) -> list[tuple[bool, float, bool]]:
|
def get_tuples(self) -> list[tuple[bool, float, bool]]:
|
||||||
return [(True, 1234.5678, True)]
|
return [(self.a, self.b, self.c), (self.d, self.e, self.f)]
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def run(self):
|
def run(self):
|
||||||
a, b, c = self.a_tuple()[0]
|
# Run two RPCs before checking to catch any obvious allocation size calculation
|
||||||
d, e, f = self.a_tuple()[0]
|
# issues (i.e. use of uninitialised stack memory).
|
||||||
assert a == d
|
tuples0 = self.get_tuples()
|
||||||
assert b == e
|
tuples1 = self.get_tuples()
|
||||||
assert c == f
|
for tuples in [tuples0, tuples1]:
|
||||||
return 0
|
a, b, c = tuples[0]
|
||||||
|
d, e, f = tuples[1]
|
||||||
|
assert a == self.a
|
||||||
|
assert b == self.b
|
||||||
|
assert c == self.c
|
||||||
|
assert d == self.d
|
||||||
|
assert e == self.e
|
||||||
|
assert f == self.f
|
||||||
|
|
||||||
|
|
||||||
class AlignmentTest(ExperimentCase):
|
class AlignmentTest(ExperimentCase):
|
||||||
|
|
|
@ -16,7 +16,7 @@ ARTIQ itself does not depend on Nix, and it is also possible to compile everythi
|
||||||
* Enable flakes in Nix by e.g. adding ``experimental-features = nix-command flakes`` to ``nix.conf`` (for example ``~/.config/nix/nix.conf``).
|
* Enable flakes in Nix by e.g. adding ``experimental-features = nix-command flakes`` to ``nix.conf`` (for example ``~/.config/nix/nix.conf``).
|
||||||
* Enter the development shell by running ``nix develop git+https://github.com/m-labs/artiq.git``, or alternatively by cloning the ARTIQ Git repository and running ``nix develop`` at the root (where ``flake.nix`` is).
|
* Enter the development shell by running ``nix develop git+https://github.com/m-labs/artiq.git``, or alternatively by cloning the ARTIQ Git repository and running ``nix develop`` at the root (where ``flake.nix`` is).
|
||||||
* You can then build the firmware and gateware with a command such as ``$ python -m artiq.gateware.targets.kasli``. If you are using a JSON system description file, use ``$ python -m artiq.gateware.targets.kasli_generic file.json``.
|
* You can then build the firmware and gateware with a command such as ``$ python -m artiq.gateware.targets.kasli``. If you are using a JSON system description file, use ``$ python -m artiq.gateware.targets.kasli_generic file.json``.
|
||||||
* Flash the binaries into the FPGA board with a command such as ``$ artiq_flash --srcbuild -d artiq_kasli -V <your_variant>``. You need to configure OpenOCD as explained :ref:`in the user section <configuring-openocd>`. OpenOCD is already part of the flake's development environment.
|
* Flash the binaries into the FPGA board with a command such as ``$ artiq_flash --srcbuild -d artiq_kasli/<your_variant>``. You need to configure OpenOCD as explained :ref:`in the user section <configuring-openocd>`. OpenOCD is already part of the flake's development environment.
|
||||||
* Check that the board boots and examine the UART messages by running a serial terminal program, e.g. ``$ flterm /dev/ttyUSB1`` (``flterm`` is part of MiSoC and installed in the flake's development environment). Leave the terminal running while you are flashing the board, so that you see the startup messages when the board boots immediately after flashing. You can also restart the board (without reflashing it) with ``$ artiq_flash start``.
|
* Check that the board boots and examine the UART messages by running a serial terminal program, e.g. ``$ flterm /dev/ttyUSB1`` (``flterm`` is part of MiSoC and installed in the flake's development environment). Leave the terminal running while you are flashing the board, so that you see the startup messages when the board boots immediately after flashing. You can also restart the board (without reflashing it) with ``$ artiq_flash start``.
|
||||||
* The communication parameters are 115200 8-N-1. Ensure that your user has access to the serial device (e.g. by adding the user account to the ``dialout`` group).
|
* The communication parameters are 115200 8-N-1. Ensure that your user has access to the serial device (e.g. by adding the user account to the ``dialout`` group).
|
||||||
|
|
||||||
|
|
|
@ -84,25 +84,25 @@ You can create directories containing each a ``flake.nix`` that correspond to di
|
||||||
|
|
||||||
If your favorite package is not available with Nix, contact us using the helpdesk@ email.
|
If your favorite package is not available with Nix, contact us using the helpdesk@ email.
|
||||||
|
|
||||||
Installing with MSYS2 (Windows)
|
Installing via MSYS2 (Windows)
|
||||||
-------------------------------
|
------------------------------
|
||||||
|
|
||||||
Install `MSYS2 <https://www.msys2.org>`, and open "MSYS2 MinGW x64". Edit ``/etc/pacman.conf`` to add:
|
Install `MSYS2 <https://msys2.org>`_, then edit ``C:\MINGW64\etc\pacman.conf`` and add at the end: ::
|
||||||
```
|
|
||||||
[artiq]
|
|
||||||
SigLevel = Optional TrustAll
|
|
||||||
Server = https://lab.m-labs.hk/msys2
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run the following commands:
|
[artiq]
|
||||||
```
|
SigLevel = Optional TrustAll
|
||||||
pacman -Syu
|
Server = https://msys2.m-labs.hk/artiq-nac3
|
||||||
pacman -S mingw-w64-x86_64-artiq
|
|
||||||
```
|
Launch ``MSYS2 MINGW64`` from the Windows Start menu to open the MSYS2 shell, and enter the following commands: ::
|
||||||
|
|
||||||
|
pacman -Syy
|
||||||
|
pacman -S mingw-w64-x86_64-artiq
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Some ARTIQ examples also require matplotlib and numba, and they must be installed manually for running those examples. They are available in MSYS2.
|
Some ARTIQ examples also require matplotlib and numba, and they must be installed manually for running those examples. They are available in MSYS2.
|
||||||
|
|
||||||
|
If your favorite package is not available with MSYS2, contact us using the helpdesk@ email.
|
||||||
|
|
||||||
Upgrading ARTIQ (with Nix)
|
Upgrading ARTIQ (with Nix)
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
|
@ -115,9 +115,7 @@ You may need to reflash the gateware and firmware of the core device to keep it
|
||||||
Upgrading ARTIQ (with MSYS2)
|
Upgrading ARTIQ (with MSYS2)
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
Run this command to update the entire MSYS2 environment including ARTIQ: ::
|
Run ``pacman -Syu`` to update all MSYS2 packages including ARTIQ. If you get a message telling you that the shell session must be restarted after a partial update, open the shell again after the partial update and repeat the command. See the MSYS2 and Pacman manual for information on how to update individual packages if required.
|
||||||
|
|
||||||
$ pacman -Syu
|
|
||||||
|
|
||||||
You may need to reflash the gateware and firmware of the core device to keep it synchronized with the software.
|
You may need to reflash the gateware and firmware of the core device to keep it synchronized with the software.
|
||||||
|
|
||||||
|
@ -189,7 +187,7 @@ If you have an active firmware subscription with M-Labs or QUARTIQ, you can obta
|
||||||
|
|
||||||
Run the command::
|
Run the command::
|
||||||
|
|
||||||
$ afws_client [username] build [variant] [afws_directory]
|
$ afws_client [username] build [afws_directory] [variant]
|
||||||
|
|
||||||
Replace ``[username]`` with the login name that was given to you with the subscription, ``[variant]`` with the name of your system variant, and ``[afws_directory]`` with the name of an empty directory, which will be created by the command if it does not exist. Enter your password when prompted and wait for the build (if applicable) and download to finish. If you experience issues with the AFWS client, write to the helpdesk@ email.
|
Replace ``[username]`` with the login name that was given to you with the subscription, ``[variant]`` with the name of your system variant, and ``[afws_directory]`` with the name of an empty directory, which will be created by the command if it does not exist. Enter your password when prompted and wait for the build (if applicable) and download to finish. If you experience issues with the AFWS client, write to the helpdesk@ email.
|
||||||
|
|
||||||
|
|
|
@ -3,28 +3,30 @@ List of available NDSPs
|
||||||
|
|
||||||
The following network device support packages are available for ARTIQ. If you would like to add yours to this list, just send us an email or a pull request.
|
The following network device support packages are available for ARTIQ. If you would like to add yours to this list, just send us an email or a pull request.
|
||||||
|
|
||||||
+---------------------------------+--------------------------------------------------------+
|
+---------------------------------+-----------------------------------+----------------------------------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------+
|
||||||
| Equipment | URL |
|
| Equipment | Nix package | MSYS2 package | Documentation | URL |
|
||||||
+=================================+========================================================+
|
+=================================+===================================+==================================+=====================================================================================================+========================================================+
|
||||||
| PDQ2 | https://github.com/m-labs/pdq |
|
| PDQ2 | Not available | Not available | `HTML <https://pdq.readthedocs.io>`_ | https://github.com/m-labs/pdq |
|
||||||
+---------------------------------+--------------------------------------------------------+
|
+---------------------------------+-----------------------------------+----------------------------------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------+
|
||||||
| Lab Brick Digital Attenuator | https://github.com/m-labs/lda |
|
| Lab Brick Digital Attenuator | ``lda`` | ``lda`` | `HTML <https://nixbld.m-labs.hk/job/artiq/full/lda-manual-html/latest/download/1>`_ | https://github.com/m-labs/lda |
|
||||||
+---------------------------------+--------------------------------------------------------+
|
+---------------------------------+-----------------------------------+----------------------------------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------+
|
||||||
| Novatech 409B | https://github.com/m-labs/novatech409b |
|
| Novatech 409B | ``novatech409b`` | ``novatech409b`` | `HTML <https://nixbld.m-labs.hk/job/artiq/full/novatech409b-manual-html/latest/download/1>`_ | https://github.com/m-labs/novatech409b |
|
||||||
+---------------------------------+--------------------------------------------------------+
|
+---------------------------------+-----------------------------------+----------------------------------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------+
|
||||||
| Thorlabs T-Cubes | https://github.com/m-labs/thorlabs_tcube |
|
| Thorlabs T-Cubes | ``thorlabs_tcube`` | ``thorlabs_tcube`` | `HTML <https://nixbld.m-labs.hk/job/artiq/full/thorlabs_tcube-manual-html/latest/download/1>`_ | https://github.com/m-labs/thorlabs_tcube |
|
||||||
+---------------------------------+--------------------------------------------------------+
|
+---------------------------------+-----------------------------------+----------------------------------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------+
|
||||||
| Korad KA3005P | https://github.com/m-labs/korad_ka3005p |
|
| Korad KA3005P | ``korad_ka3005p`` | ``korad_ka3005p`` | `HTML <https://nixbld.m-labs.hk/job/artiq/full/korad_ka3005p-manual-html/latest/download/1>`_ | https://github.com/m-labs/korad_ka3005p |
|
||||||
+---------------------------------+--------------------------------------------------------+
|
+---------------------------------+-----------------------------------+----------------------------------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------+
|
||||||
| Newfocus 8742 | https://github.com/quartiq/newfocus8742 |
|
| Newfocus 8742 | ``newfocus8742`` | ``newfocus8742`` | `HTML <https://nixbld.m-labs.hk/job/artiq/full/newfocus8742-manual-html/latest/download/1>`_ | https://github.com/quartiq/newfocus8742 |
|
||||||
+---------------------------------+--------------------------------------------------------+
|
+---------------------------------+-----------------------------------+----------------------------------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------+
|
||||||
| Princeton Instruments PICam | https://github.com/quartiq/picam |
|
| Princeton Instruments PICam | Not available | Not available | Not available | https://github.com/quartiq/picam |
|
||||||
+---------------------------------+--------------------------------------------------------+
|
+---------------------------------+-----------------------------------+----------------------------------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------+
|
||||||
| Anel HUT2 power distribution | https://github.com/quartiq/hut2 |
|
| Anel HUT2 power distribution | ``hut2`` | Not available | `HTML <https://nixbld.m-labs.hk/job/artiq/full/hut2-manual-html/latest/download/1>`_ | https://github.com/quartiq/hut2 |
|
||||||
+---------------------------------+--------------------------------------------------------+
|
+---------------------------------+-----------------------------------+----------------------------------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------+
|
||||||
| TOPTICA lasers | https://github.com/quartiq/lasersdk-artiq |
|
| TOPTICA lasers | ``toptica-lasersdk-artiq`` | Not available | Not available | https://github.com/quartiq/lasersdk-artiq |
|
||||||
+---------------------------------+--------------------------------------------------------+
|
+---------------------------------+-----------------------------------+----------------------------------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------+
|
||||||
| HighFinesse wavemeters | https://github.com/quartiq/highfinesse-net |
|
| HighFinesse wavemeters | ``highfinesse-net`` | Not available | `HTML <https://nixbld.m-labs.hk/job/artiq/full/highfinesse-net-manual-html/latest/download/1>`_ | https://github.com/quartiq/highfinesse-net |
|
||||||
+---------------------------------+--------------------------------------------------------+
|
+---------------------------------+-----------------------------------+----------------------------------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------+
|
||||||
| InfluxDB database | https://gitlab.com/charlesbaynham/artiq_influx_generic |
|
| InfluxDB database | Not available | Not available | `HTML <https://gitlab.com/charlesbaynham/artiq_influx_generic>`_ | https://gitlab.com/charlesbaynham/artiq_influx_generic |
|
||||||
+---------------------------------+--------------------------------------------------------+
|
+---------------------------------+-----------------------------------+----------------------------------+-----------------------------------------------------------------------------------------------------+--------------------------------------------------------+
|
||||||
|
|
||||||
|
MSYS2 packages all start with the ``mingw-w64-x86_64-`` prefix.
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,771 @@
|
||||||
|
diff --git a/artiq/coredevice/suservo.py b/artiq/coredevice/suservo.py
|
||||||
|
index 1d0a72dad..a89cdcca4 100644
|
||||||
|
--- a/artiq/coredevice/suservo.py
|
||||||
|
+++ b/artiq/coredevice/suservo.py
|
||||||
|
@@ -3,17 +3,14 @@
|
||||||
|
from artiq.coredevice.rtio import rtio_output, rtio_input_data
|
||||||
|
from artiq.coredevice import spi2 as spi
|
||||||
|
from artiq.coredevice import urukul, sampler
|
||||||
|
+from math import ceil, log2
|
||||||
|
|
||||||
|
|
||||||
|
-COEFF_WIDTH = 18
|
||||||
|
+COEFF_WIDTH = 18 # Must match gateware IIRWidths.coeff
|
||||||
|
Y_FULL_SCALE_MU = (1 << (COEFF_WIDTH - 1)) - 1
|
||||||
|
-COEFF_DEPTH = 10 + 1
|
||||||
|
-WE = 1 << COEFF_DEPTH + 1
|
||||||
|
-STATE_SEL = 1 << COEFF_DEPTH
|
||||||
|
-CONFIG_SEL = 1 << COEFF_DEPTH - 1
|
||||||
|
-CONFIG_ADDR = CONFIG_SEL | STATE_SEL
|
||||||
|
T_CYCLE = (2*(8 + 64) + 2)*8*ns # Must match gateware Servo.t_cycle.
|
||||||
|
-COEFF_SHIFT = 11
|
||||||
|
+COEFF_SHIFT = 11 # Must match gateware IIRWidths.shift
|
||||||
|
+PROFILE_WIDTH = 5 # Must match gateware IIRWidths.profile
|
||||||
|
|
||||||
|
|
||||||
|
@portable
|
||||||
|
@@ -35,8 +32,8 @@ class SUServo:
|
||||||
|
"""Sampler-Urukul Servo parent and configuration device.
|
||||||
|
|
||||||
|
Sampler-Urukul Servo is a integrated device controlling one
|
||||||
|
- 8-channel ADC (Sampler) and two 4-channel DDS (Urukuls) with a DSP engine
|
||||||
|
- connecting the ADC data and the DDS output amplitudes to enable
|
||||||
|
+ 8-channel ADC (Sampler) and any number of 4-channel DDS (Urukuls) with a
|
||||||
|
+ DSP engine connecting the ADC data and the DDS output amplitudes to enable
|
||||||
|
feedback. SU Servo can for example be used to implement intensity
|
||||||
|
stabilization of laser beams with an amplifier and AOM driven by Urukul
|
||||||
|
and a photodetector connected to Sampler.
|
||||||
|
@@ -49,7 +46,7 @@ class SUServo:
|
||||||
|
* See the SU Servo variant of the Kasli target for an example of how to
|
||||||
|
connect the gateware and the devices. Sampler and each Urukul need
|
||||||
|
two EEM connections.
|
||||||
|
- * Ensure that both Urukuls are AD9910 variants and have the on-board
|
||||||
|
+ * Ensure that all Urukuls are AD9910 variants and have the on-board
|
||||||
|
dip switches set to 1100 (first two on, last two off).
|
||||||
|
* Refer to the Sampler and Urukul documentation and the SU Servo
|
||||||
|
example device database for runtime configuration of the devices
|
||||||
|
@@ -65,7 +62,8 @@ class SUServo:
|
||||||
|
:param core_device: Core device name
|
||||||
|
"""
|
||||||
|
kernel_invariants = {"channel", "core", "pgia", "cplds", "ddses",
|
||||||
|
- "ref_period_mu"}
|
||||||
|
+ "ref_period_mu", "num_channels", "coeff_sel",
|
||||||
|
+ "state_sel", "config_addr", "write_enable"}
|
||||||
|
|
||||||
|
def __init__(self, dmgr, channel, pgia_device,
|
||||||
|
cpld_devices, dds_devices,
|
||||||
|
@@ -83,9 +81,19 @@ def __init__(self, dmgr, channel, pgia_device,
|
||||||
|
self.core.coarse_ref_period)
|
||||||
|
assert self.ref_period_mu == self.core.ref_multiplier
|
||||||
|
|
||||||
|
+ # The width of parts of the servo memory address depends on the number
|
||||||
|
+ # of channels.
|
||||||
|
+ self.num_channels = 4 * len(dds_devices)
|
||||||
|
+ channel_width = ceil(log2(self.num_channels))
|
||||||
|
+ coeff_depth = PROFILE_WIDTH + channel_width + 3
|
||||||
|
+ self.state_sel = 2 << (coeff_depth - 2)
|
||||||
|
+ self.config_addr = 3 << (coeff_depth - 2)
|
||||||
|
+ self.coeff_sel = 1 << coeff_depth
|
||||||
|
+ self.write_enable = 1 << (coeff_depth + 1)
|
||||||
|
+
|
||||||
|
@kernel
|
||||||
|
def init(self):
|
||||||
|
- """Initialize the servo, Sampler and both Urukuls.
|
||||||
|
+ """Initialize the servo, Sampler and all Urukuls.
|
||||||
|
|
||||||
|
Leaves the servo disabled (see :meth:`set_config`), resets and
|
||||||
|
configures all DDS.
|
||||||
|
@@ -122,7 +130,7 @@ def write(self, addr, value):
|
||||||
|
:param addr: Memory location address.
|
||||||
|
:param value: Data to be written.
|
||||||
|
"""
|
||||||
|
- addr |= WE
|
||||||
|
+ addr |= self.write_enable
|
||||||
|
value &= (1 << COEFF_WIDTH) - 1
|
||||||
|
value |= (addr >> 8) << COEFF_WIDTH
|
||||||
|
addr = addr & 0xff
|
||||||
|
@@ -158,7 +166,7 @@ def set_config(self, enable):
|
||||||
|
Disabling takes up to two servo cycles (~2.3 µs) to clear the
|
||||||
|
processing pipeline.
|
||||||
|
"""
|
||||||
|
- self.write(CONFIG_ADDR, enable)
|
||||||
|
+ self.write(self.config_addr, enable)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def get_status(self):
|
||||||
|
@@ -179,7 +187,7 @@ def get_status(self):
|
||||||
|
:return: Status. Bit 0: enabled, bit 1: done,
|
||||||
|
bits 8-15: channel clip indicators.
|
||||||
|
"""
|
||||||
|
- return self.read(CONFIG_ADDR)
|
||||||
|
+ return self.read(self.config_addr)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def get_adc_mu(self, adc):
|
||||||
|
@@ -197,7 +205,8 @@ def get_adc_mu(self, adc):
|
||||||
|
# State memory entries are 25 bits. Due to the pre-adder dynamic
|
||||||
|
# range, X0/X1/OFFSET are only 24 bits. Finally, the RTIO interface
|
||||||
|
# only returns the 18 MSBs (the width of the coefficient memory).
|
||||||
|
- return self.read(STATE_SEL | (adc << 1) | (1 << 8))
|
||||||
|
+ return self.read(self.state_sel |
|
||||||
|
+ (2 * adc + (1 << PROFILE_WIDTH) * self.num_channels))
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_pgia_mu(self, channel, gain):
|
||||||
|
@@ -285,10 +294,11 @@ def set_dds_mu(self, profile, ftw, offs, pow_=0):
|
||||||
|
:param offs: IIR offset (17 bit signed)
|
||||||
|
:param pow_: Phase offset word (16 bit)
|
||||||
|
"""
|
||||||
|
- base = (self.servo_channel << 8) | (profile << 3)
|
||||||
|
+ base = self.servo.coeff_sel | (self.servo_channel <<
|
||||||
|
+ (3 + PROFILE_WIDTH)) | (profile << 3)
|
||||||
|
self.servo.write(base + 0, ftw >> 16)
|
||||||
|
self.servo.write(base + 6, (ftw & 0xffff))
|
||||||
|
- self.set_dds_offset_mu(profile, offs)
|
||||||
|
+ self.servo.write(base + 4, offs)
|
||||||
|
self.servo.write(base + 2, pow_)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
@@ -319,7 +329,8 @@ def set_dds_offset_mu(self, profile, offs):
|
||||||
|
:param profile: Profile number (0-31)
|
||||||
|
:param offs: IIR offset (17 bit signed)
|
||||||
|
"""
|
||||||
|
- base = (self.servo_channel << 8) | (profile << 3)
|
||||||
|
+ base = self.servo.coeff_sel | (self.servo_channel <<
|
||||||
|
+ (3 + PROFILE_WIDTH)) | (profile << 3)
|
||||||
|
self.servo.write(base + 4, offs)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
@@ -344,6 +355,30 @@ def dds_offset_to_mu(self, offset):
|
||||||
|
"""
|
||||||
|
return int(round(offset * (1 << COEFF_WIDTH - 1)))
|
||||||
|
|
||||||
|
+ @kernel
|
||||||
|
+ def set_dds_phase_mu(self, profile, pow_):
|
||||||
|
+ """Set only POW in profile DDS coefficients.
|
||||||
|
+
|
||||||
|
+ See :meth:`set_dds_mu` for setting the complete DDS profile.
|
||||||
|
+
|
||||||
|
+ :param profile: Profile number (0-31)
|
||||||
|
+ :param pow_: Phase offset word (16 bit)
|
||||||
|
+ """
|
||||||
|
+ base = self.servo.coeff_sel | (self.servo_channel <<
|
||||||
|
+ (3 + PROFILE_WIDTH)) | (profile << 3)
|
||||||
|
+ self.servo.write(base + 2, pow_)
|
||||||
|
+
|
||||||
|
+ @kernel
|
||||||
|
+ def set_dds_phase(self, profile, phase):
|
||||||
|
+ """Set only phase in profile DDS coefficients.
|
||||||
|
+
|
||||||
|
+ See :meth:`set_dds` for setting the complete DDS profile.
|
||||||
|
+
|
||||||
|
+ :param profile: Profile number (0-31)
|
||||||
|
+ :param phase: DDS phase in turns
|
||||||
|
+ """
|
||||||
|
+ self.set_dds_phase_mu(profile, self.dds.turns_to_pow(phase))
|
||||||
|
+
|
||||||
|
@kernel
|
||||||
|
def set_iir_mu(self, profile, adc, a1, b0, b1, dly=0):
|
||||||
|
"""Set profile IIR coefficients in machine units.
|
||||||
|
@@ -378,7 +413,8 @@ def set_iir_mu(self, profile, adc, a1, b0, b1, dly=0):
|
||||||
|
:param dly: IIR update suppression time. In units of IIR cycles
|
||||||
|
(~1.2 µs, 0-255).
|
||||||
|
"""
|
||||||
|
- base = (self.servo_channel << 8) | (profile << 3)
|
||||||
|
+ base = self.servo.coeff_sel | (self.servo_channel <<
|
||||||
|
+ (3 + PROFILE_WIDTH)) | (profile << 3)
|
||||||
|
self.servo.write(base + 3, adc | (dly << 8))
|
||||||
|
self.servo.write(base + 1, b1)
|
||||||
|
self.servo.write(base + 5, a1)
|
||||||
|
@@ -470,7 +506,9 @@ def get_profile_mu(self, profile, data):
|
||||||
|
:param profile: Profile number (0-31)
|
||||||
|
:param data: List of 8 integers to write the profile data into
|
||||||
|
"""
|
||||||
|
- base = (self.servo_channel << 8) | (profile << 3)
|
||||||
|
+ assert len(data) == 8
|
||||||
|
+ base = self.servo.coeff_sel | (self.servo_channel <<
|
||||||
|
+ (3 + PROFILE_WIDTH)) | (profile << 3)
|
||||||
|
for i in range(len(data)):
|
||||||
|
data[i] = self.servo.read(base + i)
|
||||||
|
delay(4*us)
|
||||||
|
@@ -491,7 +529,8 @@ def get_y_mu(self, profile):
|
||||||
|
:param profile: Profile number (0-31)
|
||||||
|
:return: 17 bit unsigned Y0
|
||||||
|
"""
|
||||||
|
- return self.servo.read(STATE_SEL | (self.servo_channel << 5) | profile)
|
||||||
|
+ return self.servo.read(self.servo.state_sel | (
|
||||||
|
+ self.servo_channel << PROFILE_WIDTH) | profile)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def get_y(self, profile):
|
||||||
|
@@ -529,7 +568,8 @@ def set_y_mu(self, profile, y):
|
||||||
|
"""
|
||||||
|
# State memory is 25 bits wide and signed.
|
||||||
|
# Reads interact with the 18 MSBs (coefficient memory width)
|
||||||
|
- self.servo.write(STATE_SEL | (self.servo_channel << 5) | profile, y)
|
||||||
|
+ self.servo.write(self.servo.state_sel | (
|
||||||
|
+ self.servo_channel << PROFILE_WIDTH) | profile, y)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_y(self, profile, y):
|
||||||
|
diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py
|
||||||
|
index 7f5fe3fdf..fbfdafe7d 100644
|
||||||
|
--- a/artiq/gateware/eem.py
|
||||||
|
+++ b/artiq/gateware/eem.py
|
||||||
|
@@ -473,11 +473,10 @@ def add_std(cls, target, eem, eem_aux=None, eem_aux2=None, ttl_out_cls=None,
|
||||||
|
class SUServo(_EEM):
|
||||||
|
@staticmethod
|
||||||
|
def io(*eems, iostandard):
|
||||||
|
- assert len(eems) in (4, 6)
|
||||||
|
- io = (Sampler.io(*eems[0:2], iostandard=iostandard)
|
||||||
|
- + Urukul.io_qspi(*eems[2:4], iostandard=iostandard))
|
||||||
|
- if len(eems) == 6: # two Urukuls
|
||||||
|
- io += Urukul.io_qspi(*eems[4:6], iostandard=iostandard)
|
||||||
|
+ assert len(eems) >= 4 and len(eems) % 2 == 0
|
||||||
|
+ io = Sampler.io(*eems[0:2], iostandard=iostandard)
|
||||||
|
+ for i in range(len(eems) // 2 - 1):
|
||||||
|
+ io += Urukul.io_qspi(*eems[(2 * i + 2):(2 * i + 4)], iostandard=iostandard)
|
||||||
|
return io
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@@ -516,10 +515,9 @@ def add_std(cls, target, eems_sampler, eems_urukul,
|
||||||
|
# difference (4 cycles measured)
|
||||||
|
t_conv=57 - 4, t_rtt=t_rtt + 4)
|
||||||
|
iir_p = servo.IIRWidths(state=25, coeff=18, adc=16, asf=14, word=16,
|
||||||
|
- accu=48, shift=shift, channel=3,
|
||||||
|
- profile=profile, dly=8)
|
||||||
|
+ accu=48, shift=shift, profile=profile, dly=8)
|
||||||
|
dds_p = servo.DDSParams(width=8 + 32 + 16 + 16,
|
||||||
|
- channels=adc_p.channels, clk=clk)
|
||||||
|
+ channels=4 * len(eem_urukul), clk=clk)
|
||||||
|
su = servo.Servo(sampler_pads, urukul_pads, adc_p, iir_p, dds_p)
|
||||||
|
su = ClockDomainsRenamer("rio_phy")(su)
|
||||||
|
# explicitly name the servo submodule to enable the migen namer to derive
|
||||||
|
@@ -540,27 +538,23 @@ def add_std(cls, target, eems_sampler, eems_urukul,
|
||||||
|
target.submodules += phy
|
||||||
|
target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4))
|
||||||
|
|
||||||
|
- for i in range(2):
|
||||||
|
- if len(eem_urukul) > i:
|
||||||
|
- spi_p, spi_n = (
|
||||||
|
- target.platform.request("{}_spi_p".format(eem_urukul[i])),
|
||||||
|
- target.platform.request("{}_spi_n".format(eem_urukul[i])))
|
||||||
|
- else: # create a dummy bus
|
||||||
|
- spi_p = Record([("clk", 1), ("cs_n", 1)]) # mosi, cs_n
|
||||||
|
- spi_n = None
|
||||||
|
-
|
||||||
|
+ dds_sync = Signal(reset=0)
|
||||||
|
+ for j, eem_urukuli in enumerate(eem_urukul):
|
||||||
|
+ # connect quad-SPI
|
||||||
|
+ spi_p, spi_n = (
|
||||||
|
+ target.platform.request("{}_spi_p".format(eem_urukuli)),
|
||||||
|
+ target.platform.request("{}_spi_n".format(eem_urukuli)))
|
||||||
|
phy = spi2.SPIMaster(spi_p, spi_n)
|
||||||
|
target.submodules += phy
|
||||||
|
target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4))
|
||||||
|
-
|
||||||
|
- for j, eem_urukuli in enumerate(eem_urukul):
|
||||||
|
+ # connect `reset_sync_in`
|
||||||
|
pads = target.platform.request("{}_dds_reset_sync_in".format(eem_urukuli))
|
||||||
|
- target.specials += DifferentialOutput(0, pads.p, pads.n)
|
||||||
|
-
|
||||||
|
+ target.specials += DifferentialOutput(dds_sync, pads.p, pads.n)
|
||||||
|
+ # connect RF switches
|
||||||
|
for i, signal in enumerate("sw0 sw1 sw2 sw3".split()):
|
||||||
|
pads = target.platform.request("{}_{}".format(eem_urukuli, signal))
|
||||||
|
target.specials += DifferentialOutput(
|
||||||
|
- su.iir.ctrl[j*4 + i].en_out, pads.p, pads.n)
|
||||||
|
+ su.iir.ctrl[j * 4 + i].en_out, pads.p, pads.n)
|
||||||
|
|
||||||
|
|
||||||
|
class Mirny(_EEM):
|
||||||
|
diff --git a/artiq/gateware/rtio/phy/servo.py b/artiq/gateware/rtio/phy/servo.py
|
||||||
|
index 9fa634521..379e7ba32 100644
|
||||||
|
--- a/artiq/gateware/rtio/phy/servo.py
|
||||||
|
+++ b/artiq/gateware/rtio/phy/servo.py
|
||||||
|
@@ -34,28 +34,38 @@ class RTServoMem(Module):
|
||||||
|
"""All-channel all-profile coefficient and state RTIO control
|
||||||
|
interface.
|
||||||
|
|
||||||
|
+ The real-time interface exposes the following functions:
|
||||||
|
+ 1. enable/disable servo iterations
|
||||||
|
+ 2. read the servo status (including state of clip register)
|
||||||
|
+ 3. access the IIR coefficient memory (set PI loop gains etc.)
|
||||||
|
+ 4. access the IIR state memory (set offset and read ADC data)
|
||||||
|
+
|
||||||
|
+ The bit assignments for the servo address space are (from MSB):
|
||||||
|
+ * write-enable (1 bit)
|
||||||
|
+ * sel_coeff (1 bit)
|
||||||
|
+ If selected, the coefficient memory location is
|
||||||
|
+ addressed by all the lower bits excluding the LSB (high_coeff).
|
||||||
|
+ - high_coeff (1 bit) selects between the upper and lower halves of that
|
||||||
|
+ memory location.
|
||||||
|
+ Else (if ~sel_coeff), the following bits are:
|
||||||
|
+ - sel (2 bits) selects between the following memory locations:
|
||||||
|
+
|
||||||
|
+ destination | sel | sel_coeff |
|
||||||
|
+ ----------------|-------|--------------|
|
||||||
|
+ IIR coeff mem | - | 1 |
|
||||||
|
+ Reserved | 1 | 0 |
|
||||||
|
+ IIR state mem | 2 | 0 |
|
||||||
|
+ config (write) | 3 | 0 |
|
||||||
|
+ status (read) | 3 | 0 |
|
||||||
|
+
|
||||||
|
+ - IIR state memory address
|
||||||
|
+
|
||||||
|
Servo internal addresses are internal_address_width wide, which is
|
||||||
|
typically longer than the 8-bit RIO address space. We pack the overflow
|
||||||
|
onto the RTIO data word after the data.
|
||||||
|
|
||||||
|
- Servo address space (from LSB):
|
||||||
|
- - IIR coefficient/state memory address, (w.profile + w.channel + 2) bits.
|
||||||
|
- If the state memory is selected, the lower bits are used directly as
|
||||||
|
- the memory address. If the coefficient memory is selected, the LSB
|
||||||
|
- (high_coeff) selects between the upper and lower halves of the memory
|
||||||
|
- location, which is two coefficients wide, with the remaining bits used
|
||||||
|
- as the memory address.
|
||||||
|
- - config_sel (1 bit)
|
||||||
|
- - state_sel (1 bit)
|
||||||
|
- - we (1 bit)
|
||||||
|
-
|
||||||
|
- destination | config_sel | state_sel
|
||||||
|
- ----------------|------------|----------
|
||||||
|
- IIR coeff mem | 0 | 0
|
||||||
|
- IIR coeff mem | 1 | 0
|
||||||
|
- IIR state mem | 0 | 1
|
||||||
|
- config (write) | 1 | 1
|
||||||
|
- status (read) | 1 | 1
|
||||||
|
+ The address layout reflects the fact that typically, the coefficient memory
|
||||||
|
+ address is 2 bits wider than the state memory address.
|
||||||
|
|
||||||
|
Values returned to the user on the Python side of the RTIO interface are
|
||||||
|
32 bit, so we sign-extend all values from w.coeff to that width. This works
|
||||||
|
@@ -71,6 +81,7 @@ def __init__(self, w, servo):
|
||||||
|
# mode=READ_FIRST,
|
||||||
|
clock_domain="rio")
|
||||||
|
self.specials += m_state, m_coeff
|
||||||
|
+ w_channel = bits_for(len(servo.iir.dds) - 1)
|
||||||
|
|
||||||
|
# just expose the w.coeff (18) MSBs of state
|
||||||
|
assert w.state >= w.coeff
|
||||||
|
@@ -83,7 +94,7 @@ def __init__(self, w, servo):
|
||||||
|
assert 8 + w.dly < w.coeff
|
||||||
|
|
||||||
|
# coeff, profile, channel, 2 mems, rw
|
||||||
|
- internal_address_width = 3 + w.profile + w.channel + 1 + 1
|
||||||
|
+ internal_address_width = 3 + w.profile + w_channel + 1 + 1
|
||||||
|
rtlink_address_width = min(8, internal_address_width)
|
||||||
|
overflow_address_width = internal_address_width - rtlink_address_width
|
||||||
|
self.rtlink = rtlink.Interface(
|
||||||
|
@@ -99,8 +110,9 @@ def __init__(self, w, servo):
|
||||||
|
# # #
|
||||||
|
|
||||||
|
config = Signal(w.coeff, reset=0)
|
||||||
|
- status = Signal(w.coeff)
|
||||||
|
+ status = Signal(8 + len(servo.iir.ctrl))
|
||||||
|
pad = Signal(6)
|
||||||
|
+ assert len(status) <= len(self.rtlink.i.data)
|
||||||
|
self.comb += [
|
||||||
|
Cat(servo.start).eq(config),
|
||||||
|
status.eq(Cat(servo.start, servo.done, pad,
|
||||||
|
@@ -109,15 +121,19 @@ def __init__(self, w, servo):
|
||||||
|
|
||||||
|
assert len(self.rtlink.o.address) + len(self.rtlink.o.data) - w.coeff == (
|
||||||
|
1 + # we
|
||||||
|
- 1 + # state_sel
|
||||||
|
+ 1 + # sel_coeff
|
||||||
|
1 + # high_coeff
|
||||||
|
len(m_coeff.adr))
|
||||||
|
# ensure that we can fit config/status into the state address space
|
||||||
|
assert len(self.rtlink.o.address) + len(self.rtlink.o.data) - w.coeff >= (
|
||||||
|
1 + # we
|
||||||
|
- 1 + # state_sel
|
||||||
|
- 1 + # config_sel
|
||||||
|
+ 1 + # sel_coeff
|
||||||
|
+ 2 + # sel
|
||||||
|
len(m_state.adr))
|
||||||
|
+ # ensure that IIR state mem addresses are at least 2 bits less wide than
|
||||||
|
+ # IIR coeff mem addresses to ensure we can fit SEL after the state mem
|
||||||
|
+ # address and before the SEL_COEFF bit.
|
||||||
|
+ assert w.profile + w_channel >= 4
|
||||||
|
|
||||||
|
internal_address = Signal(internal_address_width)
|
||||||
|
self.comb += internal_address.eq(Cat(self.rtlink.o.address,
|
||||||
|
@@ -127,52 +143,51 @@ def __init__(self, w, servo):
|
||||||
|
self.comb += coeff_data.eq(self.rtlink.o.data[:w.coeff])
|
||||||
|
|
||||||
|
we = internal_address[-1]
|
||||||
|
- state_sel = internal_address[-2]
|
||||||
|
- config_sel = internal_address[-3]
|
||||||
|
+ sel_coeff = internal_address[-2]
|
||||||
|
+ sel1 = internal_address[-3]
|
||||||
|
+ sel0 = internal_address[-4]
|
||||||
|
high_coeff = internal_address[0]
|
||||||
|
+ sel = Signal(2)
|
||||||
|
self.comb += [
|
||||||
|
self.rtlink.o.busy.eq(0),
|
||||||
|
+ sel.eq(Mux(sel_coeff, 0, Cat(sel0, sel1))),
|
||||||
|
m_coeff.adr.eq(internal_address[1:]),
|
||||||
|
m_coeff.dat_w.eq(Cat(coeff_data, coeff_data)),
|
||||||
|
- m_coeff.we[0].eq(self.rtlink.o.stb & ~high_coeff &
|
||||||
|
- we & ~state_sel),
|
||||||
|
- m_coeff.we[1].eq(self.rtlink.o.stb & high_coeff &
|
||||||
|
- we & ~state_sel),
|
||||||
|
+ m_coeff.we[0].eq(self.rtlink.o.stb & ~high_coeff & we & sel_coeff),
|
||||||
|
+ m_coeff.we[1].eq(self.rtlink.o.stb & high_coeff & we & sel_coeff),
|
||||||
|
m_state.adr.eq(internal_address),
|
||||||
|
m_state.dat_w[w.state - w.coeff:].eq(self.rtlink.o.data),
|
||||||
|
- m_state.we.eq(self.rtlink.o.stb & we & state_sel & ~config_sel),
|
||||||
|
+ m_state.we.eq(self.rtlink.o.stb & we & (sel == 2)),
|
||||||
|
]
|
||||||
|
read = Signal()
|
||||||
|
- read_state = Signal()
|
||||||
|
read_high = Signal()
|
||||||
|
- read_config = Signal()
|
||||||
|
+ read_sel = Signal(2)
|
||||||
|
self.sync.rio += [
|
||||||
|
If(read,
|
||||||
|
read.eq(0)
|
||||||
|
),
|
||||||
|
If(self.rtlink.o.stb,
|
||||||
|
read.eq(~we),
|
||||||
|
- read_state.eq(state_sel),
|
||||||
|
+ read_sel.eq(sel),
|
||||||
|
read_high.eq(high_coeff),
|
||||||
|
- read_config.eq(config_sel),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
self.sync.rio_phy += [
|
||||||
|
- If(self.rtlink.o.stb & we & state_sel & config_sel,
|
||||||
|
+ If(self.rtlink.o.stb & we & (sel == 3),
|
||||||
|
config.eq(self.rtlink.o.data)
|
||||||
|
),
|
||||||
|
- If(read & read_config & read_state,
|
||||||
|
+ If(read & (read_sel == 3),
|
||||||
|
[_.clip.eq(0) for _ in servo.iir.ctrl]
|
||||||
|
- )
|
||||||
|
+ ),
|
||||||
|
]
|
||||||
|
+ # read return value by destination
|
||||||
|
+ read_acts = Array([
|
||||||
|
+ Mux(read_high, m_coeff.dat_r[w.coeff:], m_coeff.dat_r[:w.coeff]),
|
||||||
|
+ 0,
|
||||||
|
+ m_state.dat_r[w.state - w.coeff:],
|
||||||
|
+ status
|
||||||
|
+ ])
|
||||||
|
self.comb += [
|
||||||
|
self.rtlink.i.stb.eq(read),
|
||||||
|
- _eq_sign_extend(self.rtlink.i.data,
|
||||||
|
- Mux(read_state,
|
||||||
|
- Mux(read_config,
|
||||||
|
- status,
|
||||||
|
- m_state.dat_r[w.state - w.coeff:]),
|
||||||
|
- Mux(read_high,
|
||||||
|
- m_coeff.dat_r[w.coeff:],
|
||||||
|
- m_coeff.dat_r[:w.coeff])))
|
||||||
|
+ _eq_sign_extend(self.rtlink.i.data, read_acts[read_sel]),
|
||||||
|
]
|
||||||
|
diff --git a/artiq/gateware/suservo/iir.py b/artiq/gateware/suservo/iir.py
|
||||||
|
index 0ec9bfa09..6b975b753 100644
|
||||||
|
--- a/artiq/gateware/suservo/iir.py
|
||||||
|
+++ b/artiq/gateware/suservo/iir.py
|
||||||
|
@@ -16,7 +16,6 @@
|
||||||
|
"word", # "word" size to break up DDS profile data (16)
|
||||||
|
"asf", # unsigned amplitude scale factor for DDS (14)
|
||||||
|
"shift", # fixed point scaling coefficient for a1, b0, b1 (log2!) (11)
|
||||||
|
- "channel", # channels (log2!) (3)
|
||||||
|
"profile", # profiles per channel (log2!) (5)
|
||||||
|
"dly", # the activation delay
|
||||||
|
])
|
||||||
|
@@ -213,10 +212,10 @@ class IIR(Module):
|
||||||
|
--/--: signal with a given bit width always includes a sign bit
|
||||||
|
-->--: flow is to the right and down unless otherwise indicated
|
||||||
|
"""
|
||||||
|
- def __init__(self, w):
|
||||||
|
- self.widths = w
|
||||||
|
- for i, j in enumerate(w):
|
||||||
|
- assert j > 0, (i, j, w)
|
||||||
|
+ def __init__(self, w, w_i, w_o):
|
||||||
|
+ for v in (w, w_i, w_o):
|
||||||
|
+ for i, j in enumerate(v):
|
||||||
|
+ assert j > 0, (i, j, v)
|
||||||
|
assert w.word <= w.coeff # same memory
|
||||||
|
assert w.state + w.coeff + 3 <= w.accu
|
||||||
|
|
||||||
|
@@ -224,13 +223,13 @@ def __init__(self, w):
|
||||||
|
# ~processing
|
||||||
|
self.specials.m_coeff = Memory(
|
||||||
|
width=2*w.coeff, # Cat(pow/ftw/offset, cfg/a/b)
|
||||||
|
- depth=4 << w.profile + w.channel)
|
||||||
|
+ depth=(4 << w.profile) * w_o.channels)
|
||||||
|
# m_state[x] should only be read externally during ~(shifting | loading)
|
||||||
|
# m_state[y] of active profiles should only be read externally during
|
||||||
|
# ~processing
|
||||||
|
self.specials.m_state = Memory(
|
||||||
|
width=w.state, # y1,x0,x1
|
||||||
|
- depth=(1 << w.profile + w.channel) + (2 << w.channel))
|
||||||
|
+ depth=(1 << w.profile) * w_o.channels + 2 * w_i.channels)
|
||||||
|
# ctrl should only be updated synchronously
|
||||||
|
self.ctrl = [Record([
|
||||||
|
("profile", w.profile),
|
||||||
|
@@ -238,14 +237,14 @@ def __init__(self, w):
|
||||||
|
("en_iir", 1),
|
||||||
|
("clip", 1),
|
||||||
|
("stb", 1)])
|
||||||
|
- for i in range(1 << w.channel)]
|
||||||
|
+ for i in range(w_o.channels)]
|
||||||
|
# only update during ~loading
|
||||||
|
self.adc = [Signal((w.adc, True), reset_less=True)
|
||||||
|
- for i in range(1 << w.channel)]
|
||||||
|
+ for i in range(w_i.channels)]
|
||||||
|
# Cat(ftw0, ftw1, pow, asf)
|
||||||
|
# only read externally during ~processing
|
||||||
|
- self.dds = [Signal(4*w.word, reset_less=True)
|
||||||
|
- for i in range(1 << w.channel)]
|
||||||
|
+ self.dds = [Signal(4 * w.word, reset_less=True)
|
||||||
|
+ for i in range(w_o.channels)]
|
||||||
|
# perform one IIR iteration, start with loading,
|
||||||
|
# then processing, then shifting, end with done
|
||||||
|
self.start = Signal()
|
||||||
|
@@ -281,7 +280,7 @@ def __init__(self, w):
|
||||||
|
# using the (MSBs of) t_current_step, and, after all channels have been
|
||||||
|
# covered, proceed once the pipeline has completely drained.
|
||||||
|
self.submodules.fsm = fsm = FSM("IDLE")
|
||||||
|
- t_current_step = Signal(w.channel + 2)
|
||||||
|
+ t_current_step = Signal(max=max(4 * (w_o.channels + 2), 2 * w_i.channels))
|
||||||
|
t_current_step_clr = Signal()
|
||||||
|
|
||||||
|
# pipeline group activity flags (SR)
|
||||||
|
@@ -298,7 +297,7 @@ def __init__(self, w):
|
||||||
|
)
|
||||||
|
fsm.act("LOAD",
|
||||||
|
self.loading.eq(1),
|
||||||
|
- If(t_current_step == (1 << w.channel) - 1,
|
||||||
|
+ If(t_current_step == w_i.channels - 1,
|
||||||
|
t_current_step_clr.eq(1),
|
||||||
|
NextValue(stages_active[0], 1),
|
||||||
|
NextState("PROCESS")
|
||||||
|
@@ -315,7 +314,7 @@ def __init__(self, w):
|
||||||
|
)
|
||||||
|
fsm.act("SHIFT",
|
||||||
|
self.shifting.eq(1),
|
||||||
|
- If(t_current_step == (2 << w.channel) - 1,
|
||||||
|
+ If(t_current_step == 2 * w_i.channels - 1,
|
||||||
|
NextState("IDLE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@@ -333,13 +332,13 @@ def __init__(self, w):
|
||||||
|
# pipeline group channel pointer (SR)
|
||||||
|
# for each pipeline stage, this is the channel currently being
|
||||||
|
# processed
|
||||||
|
- channel = [Signal(w.channel, reset_less=True) for i in range(3)]
|
||||||
|
+ channel = [Signal(max=w_o.channels, reset_less=True) for i in range(3)]
|
||||||
|
self.comb += Cat(pipeline_phase, channel[0]).eq(t_current_step)
|
||||||
|
self.sync += [
|
||||||
|
If(pipeline_phase == 3,
|
||||||
|
Cat(channel[1:]).eq(Cat(channel[:-1])),
|
||||||
|
stages_active[1:].eq(stages_active[:-1]),
|
||||||
|
- If(channel[0] == (1 << w.channel) - 1,
|
||||||
|
+ If(channel[0] == w_o.channels - 1,
|
||||||
|
stages_active[0].eq(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@@ -393,13 +392,13 @@ def __init__(self, w):
|
||||||
|
|
||||||
|
# selected adc and profile delay (combinatorial from dat_r)
|
||||||
|
# both share the same coeff word (sel in the lower 8 bits)
|
||||||
|
- sel_profile = Signal(w.channel)
|
||||||
|
+ sel_profile = Signal(max=w_i.channels)
|
||||||
|
dly_profile = Signal(w.dly)
|
||||||
|
- assert w.channel <= 8
|
||||||
|
+ assert w_o.channels < (1 << 8)
|
||||||
|
assert 8 + w.dly <= w.coeff
|
||||||
|
|
||||||
|
# latched adc selection
|
||||||
|
- sel = Signal(w.channel, reset_less=True)
|
||||||
|
+ sel = Signal(max=w_i.channels, reset_less=True)
|
||||||
|
# iir enable SR
|
||||||
|
en = Signal(2, reset_less=True)
|
||||||
|
|
||||||
|
@@ -407,12 +406,12 @@ def __init__(self, w):
|
||||||
|
sel_profile.eq(m_coeff.dat_r[w.coeff:]),
|
||||||
|
dly_profile.eq(m_coeff.dat_r[w.coeff + 8:]),
|
||||||
|
If(self.shifting,
|
||||||
|
- m_state.adr.eq(t_current_step | (1 << w.profile + w.channel)),
|
||||||
|
+ m_state.adr.eq(t_current_step + (1 << w.profile) * w_o.channels),
|
||||||
|
m_state.dat_w.eq(m_state.dat_r),
|
||||||
|
m_state.we.eq(t_current_step[0])
|
||||||
|
),
|
||||||
|
If(self.loading,
|
||||||
|
- m_state.adr.eq((t_current_step << 1) | (1 << w.profile + w.channel)),
|
||||||
|
+ m_state.adr.eq((t_current_step << 1) + (1 << w.profile) * w_o.channels),
|
||||||
|
m_state.dat_w[-w.adc - 1:-1].eq(Array(self.adc)[t_current_step]),
|
||||||
|
m_state.dat_w[-1].eq(m_state.dat_w[-2]),
|
||||||
|
m_state.we.eq(1)
|
||||||
|
@@ -424,9 +423,9 @@ def __init__(self, w):
|
||||||
|
# read old y
|
||||||
|
Cat(profile[0], channel[0]),
|
||||||
|
# read x0 (recent)
|
||||||
|
- 0 | (sel_profile << 1) | (1 << w.profile + w.channel),
|
||||||
|
+ 0 | (sel_profile << 1) + (1 << w.profile) * w_o.channels,
|
||||||
|
# read x1 (old)
|
||||||
|
- 1 | (sel << 1) | (1 << w.profile + w.channel),
|
||||||
|
+ 1 | (sel << 1) + (1 << w.profile) * w_o.channels,
|
||||||
|
])[pipeline_phase]),
|
||||||
|
m_state.dat_w.eq(dsp.output),
|
||||||
|
m_state.we.eq((pipeline_phase == 0) & stages_active[2] & en[1]),
|
||||||
|
@@ -438,11 +437,9 @@ def __init__(self, w):
|
||||||
|
#
|
||||||
|
|
||||||
|
# internal channel delay counters
|
||||||
|
- dlys = Array([Signal(w.dly)
|
||||||
|
- for i in range(1 << w.channel)])
|
||||||
|
- self._dlys = dlys # expose for debugging only
|
||||||
|
+ dlys = Array([Signal(w.dly) for i in range(w_o.channels)])
|
||||||
|
|
||||||
|
- for i in range(1 << w.channel):
|
||||||
|
+ for i in range(w_o.channels):
|
||||||
|
self.sync += [
|
||||||
|
# (profile != profile_old) | ~en_out
|
||||||
|
If(self.ctrl[i].stb,
|
||||||
|
@@ -517,6 +514,12 @@ def __init__(self, w):
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
+ # expose for simulation and debugging only
|
||||||
|
+ self.widths = w
|
||||||
|
+ self.widths_adc = w_i
|
||||||
|
+ self.widths_dds = w_o
|
||||||
|
+ self._dlys = dlys
|
||||||
|
+
|
||||||
|
def _coeff(self, channel, profile, coeff):
|
||||||
|
"""Return ``high_word``, ``address`` and bit ``mask`` for the
|
||||||
|
storage of coefficient name ``coeff`` in profile ``profile``
|
||||||
|
@@ -564,31 +567,33 @@ def get_coeff(self, channel, profile, coeff):
|
||||||
|
def set_state(self, channel, val, profile=None, coeff="y1"):
|
||||||
|
"""Set a state value."""
|
||||||
|
w = self.widths
|
||||||
|
+ w_o = self.widths_dds
|
||||||
|
if coeff == "y1":
|
||||||
|
assert profile is not None
|
||||||
|
yield self.m_state[profile | (channel << w.profile)].eq(val)
|
||||||
|
elif coeff == "x0":
|
||||||
|
assert profile is None
|
||||||
|
- yield self.m_state[(channel << 1) |
|
||||||
|
- (1 << w.profile + w.channel)].eq(val)
|
||||||
|
+ yield self.m_state[(channel << 1) +
|
||||||
|
+ (1 << w.profile) * w_o.channels].eq(val)
|
||||||
|
elif coeff == "x1":
|
||||||
|
assert profile is None
|
||||||
|
- yield self.m_state[1 | (channel << 1) |
|
||||||
|
- (1 << w.profile + w.channel)].eq(val)
|
||||||
|
+ yield self.m_state[1 | (channel << 1) +
|
||||||
|
+ (1 << w.profile) * w_o.channels].eq(val)
|
||||||
|
else:
|
||||||
|
raise ValueError("no such state", coeff)
|
||||||
|
|
||||||
|
def get_state(self, channel, profile=None, coeff="y1"):
|
||||||
|
"""Get a state value."""
|
||||||
|
w = self.widths
|
||||||
|
+ w_o = self.widths_dds
|
||||||
|
if coeff == "y1":
|
||||||
|
val = yield self.m_state[profile | (channel << w.profile)]
|
||||||
|
elif coeff == "x0":
|
||||||
|
- val = yield self.m_state[(channel << 1) |
|
||||||
|
- (1 << w.profile + w.channel)]
|
||||||
|
+ val = yield self.m_state[(channel << 1) +
|
||||||
|
+ (1 << w.profile) * w_o.channels]
|
||||||
|
elif coeff == "x1":
|
||||||
|
- val = yield self.m_state[1 | (channel << 1) |
|
||||||
|
- (1 << w.profile + w.channel)]
|
||||||
|
+ val = yield self.m_state[1 | (channel << 1) +
|
||||||
|
+ (1 << w.profile) * w_o.channels]
|
||||||
|
else:
|
||||||
|
raise ValueError("no such state", coeff)
|
||||||
|
return signed(val, w.state)
|
||||||
|
@@ -607,6 +612,8 @@ def check_iter(self):
|
||||||
|
"""Perform a single processing iteration while verifying
|
||||||
|
the behavior."""
|
||||||
|
w = self.widths
|
||||||
|
+ w_i = self.widths_adc
|
||||||
|
+ w_o = self.widths_dds
|
||||||
|
|
||||||
|
while not (yield self.done):
|
||||||
|
yield
|
||||||
|
@@ -622,7 +629,7 @@ def check_iter(self):
|
||||||
|
|
||||||
|
x0s = []
|
||||||
|
# check adc loading
|
||||||
|
- for i in range(1 << w.channel):
|
||||||
|
+ for i in range(w_i.channels):
|
||||||
|
v_adc = signed((yield self.adc[i]), w.adc)
|
||||||
|
x0 = yield from self.get_state(i, coeff="x0")
|
||||||
|
x0s.append(x0)
|
||||||
|
@@ -631,7 +638,7 @@ def check_iter(self):
|
||||||
|
|
||||||
|
data = []
|
||||||
|
# predict output
|
||||||
|
- for i in range(1 << w.channel):
|
||||||
|
+ for i in range(w_o.channels):
|
||||||
|
j = yield self.ctrl[i].profile
|
||||||
|
en_iir = yield self.ctrl[i].en_iir
|
||||||
|
en_out = yield self.ctrl[i].en_out
|
||||||
|
@@ -640,7 +647,7 @@ def check_iter(self):
|
||||||
|
i, j, en_iir, en_out, dly_i)
|
||||||
|
|
||||||
|
cfg = yield from self.get_coeff(i, j, "cfg")
|
||||||
|
- k_j = cfg & ((1 << w.channel) - 1)
|
||||||
|
+ k_j = cfg & ((1 << bits_for(w_i.channels - 1)) - 1)
|
||||||
|
dly_j = (cfg >> 8) & 0xff
|
||||||
|
logger.debug("cfg[%d,%d] sel=%d dly=%d", i, j, k_j, dly_j)
|
||||||
|
|
||||||
|
@@ -694,7 +701,7 @@ def check_iter(self):
|
||||||
|
logger.debug("adc[%d] x0=%x x1=%x", i, x0, x1)
|
||||||
|
|
||||||
|
# check new state
|
||||||
|
- for i in range(1 << w.channel):
|
||||||
|
+ for i in range(w_o.channels):
|
||||||
|
j = yield self.ctrl[i].profile
|
||||||
|
logger.debug("ch[%d] profile=%d", i, j)
|
||||||
|
y1 = yield from self.get_state(i, j, "y1")
|
||||||
|
@@ -702,7 +709,7 @@ def check_iter(self):
|
||||||
|
assert y1 == y0, (hex(y1), hex(y0))
|
||||||
|
|
||||||
|
# check dds output
|
||||||
|
- for i in range(1 << w.channel):
|
||||||
|
+ for i in range(w_o.channels):
|
||||||
|
ftw0, ftw1, pow, y0, x1, x0 = data[i]
|
||||||
|
asf = y0 >> (w.state - w.asf - 1)
|
||||||
|
dds = (ftw0 | (ftw1 << w.word) |
|
||||||
|
diff --git a/artiq/gateware/suservo/pads.py b/artiq/gateware/suservo/pads.py
|
||||||
|
index 0ab7d352f..778f05d01 100644
|
||||||
|
--- a/artiq/gateware/suservo/pads.py
|
||||||
|
+++ b/artiq/gateware/suservo/pads.py
|
||||||
|
@@ -72,12 +72,11 @@ def __init__(self, platform, *eems):
|
||||||
|
DifferentialOutput(self.clk, spip[i].clk, spin[i].clk),
|
||||||
|
DifferentialOutput(self.io_update, ioup[i].p, ioup[i].n))
|
||||||
|
for i in range(len(eems))]
|
||||||
|
- for i in range(8):
|
||||||
|
+ for i in range(4 * len(eems)):
|
||||||
|
mosi = Signal()
|
||||||
|
setattr(self, "mosi{}".format(i), mosi)
|
||||||
|
- for i in range(4*len(eems)):
|
||||||
|
self.specials += [
|
||||||
|
- DifferentialOutput(getattr(self, "mosi{}".format(i)),
|
||||||
|
+ DifferentialOutput(mosi,
|
||||||
|
getattr(spip[i // 4], "mosi{}".format(i % 4)),
|
||||||
|
getattr(spin[i // 4], "mosi{}".format(i % 4)))
|
||||||
|
]
|
||||||
|
diff --git a/artiq/gateware/suservo/servo.py b/artiq/gateware/suservo/servo.py
|
||||||
|
index 1aec95f02..59529320c 100644
|
||||||
|
--- a/artiq/gateware/suservo/servo.py
|
||||||
|
+++ b/artiq/gateware/suservo/servo.py
|
||||||
|
@@ -42,7 +42,7 @@ def __init__(self, adc_pads, dds_pads, adc_p, iir_p, dds_p):
|
||||||
|
assert t_iir + 2*adc_p.channels < t_cycle, "need shifting time"
|
||||||
|
|
||||||
|
self.submodules.adc = ADC(adc_pads, adc_p)
|
||||||
|
- self.submodules.iir = IIR(iir_p)
|
||||||
|
+ self.submodules.iir = IIR(iir_p, adc_p, dds_p)
|
||||||
|
self.submodules.dds = DDS(dds_pads, dds_p)
|
||||||
|
|
||||||
|
# adc channels are reversed on Sampler
|
62
flake.lock
62
flake.lock
|
@ -2,6 +2,7 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"artiq-comtools": {
|
"artiq-comtools": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nac3",
|
"nac3",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
|
@ -11,11 +12,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1654007592,
|
"lastModified": 1664405593,
|
||||||
"narHash": "sha256-vaDFhE1ItjqtIcinC/6RAJGbj44pxxMUEeQUa3FtgEE=",
|
"narHash": "sha256-yP441NerlLGig7n+9xHsx8yCtZ+Ggd0VqfBSzc20E04=",
|
||||||
"owner": "m-labs",
|
"owner": "m-labs",
|
||||||
"repo": "artiq-comtools",
|
"repo": "artiq-comtools",
|
||||||
"rev": "cb73281154656ee8f74db1866859e31bf42755cd",
|
"rev": "15ddac62813ef623a076ccf982b3bc63d314e651",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -24,14 +25,29 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1659877975,
|
||||||
|
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"mozilla-overlay": {
|
"mozilla-overlay": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1657214286,
|
"lastModified": 1664789696,
|
||||||
"narHash": "sha256-rO/4oymKXU09wG2bcTt4uthPCp1XsBZjxuCJo3yVXNs=",
|
"narHash": "sha256-UGWJHQShiwLCr4/DysMVFrYdYYHcOqAOVsWNUu+l6YU=",
|
||||||
"owner": "mozilla",
|
"owner": "mozilla",
|
||||||
"repo": "nixpkgs-mozilla",
|
"repo": "nixpkgs-mozilla",
|
||||||
"rev": "0508a66e28a5792fdfb126bbf4dec1029c2509e0",
|
"rev": "80627b282705101e7b38e19ca6e8df105031b072",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -45,11 +61,11 @@
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1659710552,
|
"lastModified": 1668759346,
|
||||||
"narHash": "sha256-BFM0wnEaWFBHwHNET2KZBB0LxKGA44asnDBnMXQ6KMA=",
|
"narHash": "sha256-Bp12lH4VUZfdeHXAtBEQf0mJVCPbpFUsBv7mi1bl8Lc=",
|
||||||
"ref": "master",
|
"ref": "master",
|
||||||
"rev": "813bfa92a7b56fa5fe8d11bac4c224b84903e7c0",
|
"rev": "085c6ee738c5971c57375e9055321823e2592eec",
|
||||||
"revCount": 798,
|
"revCount": 800,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.m-labs.hk/m-labs/nac3.git"
|
"url": "https://git.m-labs.hk/m-labs/nac3.git"
|
||||||
},
|
},
|
||||||
|
@ -60,11 +76,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1659689094,
|
"lastModified": 1668759079,
|
||||||
"narHash": "sha256-cXrWxpPYpV1PeEhtpQf9W++8aCgwzxpx2PzfszPofJE=",
|
"narHash": "sha256-WWi1+WXgppqLp9V/P8n2tBgrVL1a8d4FnbWbL+nF/Jw=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "697fc6ae98d077f6448cada3ecd63465c48c6af5",
|
"rev": "bcb2de4443268e009b82f22570567b2b013d5523",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -92,11 +108,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1654830914,
|
"lastModified": 1664319253,
|
||||||
"narHash": "sha256-tratXcWu6Dgzd0Qd9V6EMjuNlE9qDN1pKFhP+Gt0b64=",
|
"narHash": "sha256-hycJAgy+NFF9f5I6++7yo8KdhMSyKCPKJazRPxeedI4=",
|
||||||
"owner": "m-labs",
|
"owner": "m-labs",
|
||||||
"repo": "sipyco",
|
"repo": "sipyco",
|
||||||
"rev": "58b0935f7ae47659abee5b5792fa594153328d6f",
|
"rev": "d58ded7280e0f020be2446d4fee70f4393e6045f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -108,11 +124,11 @@
|
||||||
"src-migen": {
|
"src-migen": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1656649178,
|
"lastModified": 1662111470,
|
||||||
"narHash": "sha256-A91sZRrprEuPOtIUVxm6wX5djac9wnNZQ4+cU1nvJPc=",
|
"narHash": "sha256-IPyhoFZLhY8d3jHB8jyvGdbey7V+X5eCzBZYSrJ18ec=",
|
||||||
"owner": "m-labs",
|
"owner": "m-labs",
|
||||||
"repo": "migen",
|
"repo": "migen",
|
||||||
"rev": "0fb91737090fe45fd764ea3f71257a4c53c7a4ae",
|
"rev": "639e66f4f453438e83d86dc13491b9403bbd8ec6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -124,11 +140,11 @@
|
||||||
"src-misoc": {
|
"src-misoc": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1649324486,
|
"lastModified": 1665395741,
|
||||||
"narHash": "sha256-Mw/fQS3lHFvCm7L1k63joRkz5uyijQfywcOq+X2+o2s=",
|
"narHash": "sha256-7ULMGBPPn5NxZX6rdxU5GheoSNBiJklHQEVf04jU9tI=",
|
||||||
"ref": "master",
|
"ref": "master",
|
||||||
"rev": "f1dc58d2b8c222ba41c25cee4301626625f46e43",
|
"rev": "4fb0730db4c5de7e86f82fa3bd204e6c4608af85",
|
||||||
"revCount": 2420,
|
"revCount": 2427,
|
||||||
"submodules": true,
|
"submodules": true,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/m-labs/misoc.git"
|
"url": "https://github.com/m-labs/misoc.git"
|
||||||
|
|
18
flake.nix
18
flake.nix
|
@ -73,7 +73,7 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
artiq = pkgs.python3Packages.buildPythonPackage rec {
|
artiq-upstream = pkgs.python3Packages.buildPythonPackage rec {
|
||||||
pname = "artiq";
|
pname = "artiq";
|
||||||
version = artiqVersion;
|
version = artiqVersion;
|
||||||
src = self;
|
src = self;
|
||||||
|
@ -85,6 +85,7 @@
|
||||||
'';
|
'';
|
||||||
|
|
||||||
nativeBuildInputs = [ pkgs.qt5.wrapQtAppsHook ];
|
nativeBuildInputs = [ pkgs.qt5.wrapQtAppsHook ];
|
||||||
|
|
||||||
# keep llvm_x in sync with nac3
|
# keep llvm_x in sync with nac3
|
||||||
propagatedBuildInputs = [ pkgs.llvm_14 nac3.packages.x86_64-linux.nac3artiq-pgo sipyco.packages.x86_64-linux.sipyco artiq-comtools.packages.x86_64-linux.artiq-comtools ]
|
propagatedBuildInputs = [ pkgs.llvm_14 nac3.packages.x86_64-linux.nac3artiq-pgo sipyco.packages.x86_64-linux.sipyco artiq-comtools.packages.x86_64-linux.artiq-comtools ]
|
||||||
++ (with pkgs.python3Packages; [ pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial h5py pyqt5 qasync ]);
|
++ (with pkgs.python3Packages; [ pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial h5py pyqt5 qasync ]);
|
||||||
|
@ -110,6 +111,11 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
artiq = artiq-upstream // {
|
||||||
|
withExperimentalFeatures = features: artiq-upstream.overrideAttrs(oa:
|
||||||
|
{ patches = map (f: ./experimental-features/${f}.diff) features; });
|
||||||
|
};
|
||||||
|
|
||||||
migen = pkgs.python3Packages.buildPythonPackage rec {
|
migen = pkgs.python3Packages.buildPythonPackage rec {
|
||||||
name = "migen";
|
name = "migen";
|
||||||
src = src-migen;
|
src = src-migen;
|
||||||
|
@ -131,7 +137,6 @@
|
||||||
misoc = pkgs.python3Packages.buildPythonPackage {
|
misoc = pkgs.python3Packages.buildPythonPackage {
|
||||||
name = "misoc";
|
name = "misoc";
|
||||||
src = src-misoc;
|
src = src-misoc;
|
||||||
doCheck = false; # TODO: fix misoc bitrot and re-enable tests
|
|
||||||
propagatedBuildInputs = with pkgs.python3Packages; [ jinja2 numpy migen pyserial asyncserial ];
|
propagatedBuildInputs = with pkgs.python3Packages; [ jinja2 numpy migen pyserial asyncserial ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -173,7 +178,7 @@
|
||||||
runScript = "vivado";
|
runScript = "vivado";
|
||||||
};
|
};
|
||||||
|
|
||||||
makeArtiqBoardPackage = { target, variant, buildCommand ? "python -m artiq.gateware.targets.${target} -V ${variant}" }:
|
makeArtiqBoardPackage = { target, variant, buildCommand ? "python -m artiq.gateware.targets.${target} -V ${variant}", experimentalFeatures ? [] }:
|
||||||
pkgs.stdenv.mkDerivation {
|
pkgs.stdenv.mkDerivation {
|
||||||
name = "artiq-board-${target}-${variant}";
|
name = "artiq-board-${target}-${variant}";
|
||||||
phases = [ "buildPhase" "checkPhase" "installPhase" ];
|
phases = [ "buildPhase" "checkPhase" "installPhase" ];
|
||||||
|
@ -184,7 +189,7 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
(pkgs.python3.withPackages(ps: [ ps.jsonschema migen misoc artiq]))
|
(pkgs.python3.withPackages(ps: [ ps.jsonschema migen misoc (artiq.withExperimentalFeatures experimentalFeatures) ]))
|
||||||
rustPlatform.rust.rustc
|
rustPlatform.rust.rustc
|
||||||
rustPlatform.rust.cargo
|
rustPlatform.rust.cargo
|
||||||
pkgs.llvmPackages_14.clang-unwrapped
|
pkgs.llvmPackages_14.clang-unwrapped
|
||||||
|
@ -377,6 +382,11 @@
|
||||||
pkgs.python3Packages.sphinx pkgs.python3Packages.sphinx_rtd_theme
|
pkgs.python3Packages.sphinx pkgs.python3Packages.sphinx_rtd_theme
|
||||||
sphinx-argparse sphinxcontrib-wavedrom latex-artiq-manual
|
sphinx-argparse sphinxcontrib-wavedrom latex-artiq-manual
|
||||||
];
|
];
|
||||||
|
shellHook = ''
|
||||||
|
export LIBARTIQ_SUPPORT=`libartiq-support`
|
||||||
|
export QT_PLUGIN_PATH=${pkgs.qt5.qtbase}/${pkgs.qt5.qtbase.dev.qtPluginPrefix}
|
||||||
|
export QML2_IMPORT_PATH=${pkgs.qt5.qtbase}/${pkgs.qt5.qtbase.dev.qtQmlPrefix}
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
packages.aarch64-linux = {
|
packages.aarch64-linux = {
|
||||||
|
|
Loading…
Reference in New Issue