1
0
forked from M-Labs/artiq

Compare commits

...

11 Commits
nac3 ... master

Author SHA1 Message Date
63db4af1fc doc: Fix example flake 2025-01-09 11:16:25 +08:00
626c709b4e doc: Warning on Urukul cycle alignment in DMA 2025-01-08 10:30:33 +08:00
8ff433596b flake: update dependencies 2025-01-08 10:27:57 +08:00
dc21f0b6dd update copyright year 2025-01-04 10:45:34 +08:00
087eb514c1 flake: update dependencies 2024-12-30 19:24:25 +08:00
newell
7534a8fe04
Update AD9834 coredevice driver 2024-12-30 13:40:13 +08:00
7669cfce3d doc: Clarify Nix installation 2024-12-30 13:35:17 +08:00
99fe642cab Fix sloppy mistake in PR 2061 2024-12-30 13:34:07 +08:00
d8184cfb56
do not fail on exception message formatting, add tests 2024-12-30 13:16:16 +08:00
5b52f187d0 analyzer: increase thread stack size 2024-12-25 09:48:23 +08:00
9c99d116bb Fix StoppedMessage has no attribute "channel" error for DRTIO setups
Since DRTIO setups now have sequences from multiple cores, it can have more than one StoppedMessage.

Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-12-21 09:43:31 +08:00
16 changed files with 627 additions and 358 deletions

View File

@ -25,7 +25,7 @@ Core technologies employed include `Python <https://www.python.org/>`_, `Migen <
License
=======
Copyright (C) 2014-2024 M-Labs Limited.
Copyright (C) 2014-2025 M-Labs Limited.
ARTIQ is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by

View File

@ -5,6 +5,8 @@ RTIO Driver for the Analog Devices AD9834 DDS via 3-wire SPI interface.
# https://www.analog.com/media/en/technical-documentation/data-sheets/AD9834.pdf
# https://www.analog.com/media/en/technical-documentation/app-notes/an-1070.pdf
from numpy import int32
from artiq.coredevice import spi2 as spi
from artiq.experiment import *
from artiq.language.core import *
@ -64,167 +66,21 @@ class AD9834:
self.ctrl_reg = 0x0000 # Reset control register
@kernel
def init(self):
def write(self, data: TInt32):
"""
Initialize the AD9834: configure the SPI bus and reset the DDS.
Write a 16-bit word to the AD9834.
This method performs the necessary setup for the AD9834 device, including:
- Configuring the SPI bus parameters (clock polarity, data width, and frequency).
- Putting the AD9834 into a reset state to ensure proper initialization.
This method sends a 16-bit data word to the AD9834 via the SPI bus. The input
data is left-shifted by 16 bits to ensure proper alignment for the SPI controller,
allowing for accurate processing of the command by the AD9834.
The SPI bus is configured to use 16 bits of data width with the clock frequency
provided as a parameter when creating the AD9834 instance. After configuring
the SPI bus, the method invokes :meth:`enable_reset()` to reset the AD9834.
This is an essential step to prepare the device for subsequent configuration
of frequency and phase.
This method is used internally by other methods to update the control registers
and frequency settings of the AD9834. It should not be called directly unless
low-level register manipulation is required.
This method should be called before any other operations are performed
on the AD9834 to ensure that the device is in a known state.
:param data: The 16-bit word to be sent to the AD9834.
"""
self.bus.set_config(spi.SPI_CLK_POLARITY | spi.SPI_END, 16, self.spi_freq, 1)
self.enable_reset()
@kernel
def set_frequency_reg(self, freq_reg, frequency: TFloat):
"""
Set the frequency for the specified frequency register.
This method calculates the frequency word based on the provided frequency in Hz
and writes it to the specified frequency register.
:param freq_reg: The frequency register to write to, must be one of
:const:`AD9834_FREQ_REG_0` or :const:`AD9834_FREQ_REG_1`.
:param frequency: The desired frequency in Hz, which will be converted to a
frequency word suitable for the AD9834.
The frequency word is calculated using the formula:
``freq_word = (frequency * (1 << 28)) / clk_freq``
The result is limited to the lower 28 bits for compatibility with the AD9834.
The method first sets the control register to enable the appropriate settings,
then sends the fourteen least significant bits LSBs and fourteen most significant
bits MSBs in two consecutive writes to the specified frequency register.
"""
if freq_reg not in FREQ_REGS:
raise ValueError("Invalid frequency register")
assert frequency <= 37.5 * MHz, "Frequency exceeds maximum value of 37.5 MHz"
freq_word = int((frequency * (1 << 28)) / self.clk_freq) & 0x0FFFFFFF
self.ctrl_reg |= AD9834_B28
self.write(self.ctrl_reg)
lsb = freq_word & 0x3FFF
msb = (freq_word >> 14) & 0x3FFF
self.write(freq_reg | lsb)
self.write(freq_reg | msb)
@kernel
def set_frequency_reg_msb(self, freq_reg, word: TInt32):
"""
Set the fourteen most significant bits MSBs of the specified frequency register.
This method updates the specified frequency register with the provided MSB value.
It configures the control register to indicate that the MSB is being set.
:param freq_reg: The frequency register to update, must be one of
:const:`AD9834_FREQ_REG_0` or :const:`AD9834_FREQ_REG_1`.
:param word: The value to be written to the fourteen MSBs of the frequency register.
The method first clears the appropriate control bits, sets :const:`AD9834_HLB` to
indicate that the MSB is being sent, and then writes the updated control register
followed by the MSB value to the specified frequency register.
"""
if freq_reg not in FREQ_REGS:
raise ValueError("Invalid frequency register")
self.ctrl_reg &= ~AD9834_B28
self.ctrl_reg |= AD9834_HLB
self.write(self.ctrl_reg)
self.write(freq_reg | (word & 0x3FFF))
@kernel
def set_frequency_reg_lsb(self, freq_reg, word: TInt32):
"""
Set the fourteen least significant bits LSBs of the specified frequency register.
This method updates the specified frequency register with the provided LSB value.
It configures the control register to indicate that the LSB is being set.
:param freq_reg: The frequency register to update, must be one of
:const:`AD9834_FREQ_REG_0` or :const:`AD9834_FREQ_REG_1`.
:param word: The value to be written to the fourteen LSBs of the frequency register.
The method first clears the appropriate control bits and writes the updated control
register followed by the LSB value to the specified frequency register.
"""
if freq_reg not in FREQ_REGS:
raise ValueError("Invalid frequency register")
self.ctrl_reg &= ~AD9834_B28
self.ctrl_reg &= ~AD9834_HLB
self.write(self.ctrl_reg)
self.write(freq_reg | (word & 0x3FFF))
@kernel
def select_frequency_reg(self, freq_reg):
"""
Select the active frequency register for the phase accumulator.
This method chooses between the two available frequency registers in the AD9834 to
control the frequency of the output waveform. The control register is updated
to reflect the selected frequency register.
:param freq_reg: The frequency register to select. Must be one of
:const:`AD9834_FREQ_REG_0` or :const:`AD9834_FREQ_REG_1`.
"""
if freq_reg not in FREQ_REGS:
raise ValueError("Invalid frequency register")
if freq_reg == FREQ_REGS[0]:
self.ctrl_reg &= ~AD9834_FSEL
else:
self.ctrl_reg |= AD9834_FSEL
self.ctrl_reg &= ~AD9834_PIN_SW
self.write(self.ctrl_reg)
@kernel
def set_phase_reg(self, phase_reg, phase: TInt32):
"""
Set the phase for the specified phase register.
This method updates the specified phase register with the provided phase value.
:param phase_reg: The phase register to update, must be one of
:const:`AD9834_PHASE_REG_0` or :const:`AD9834_PHASE_REG_1`.
:param phase: The value to be written to the phase register.
The method masks the phase value to ensure it fits within the 12-bit limit
and writes it to the specified phase register.
"""
if phase_reg not in PHASE_REGS:
raise ValueError("Invalid phase register")
phase_word = phase & 0x0FFF
self.write(phase_reg | phase_word)
@kernel
def select_phase_reg(self, phase_reg):
"""
Select the active phase register for the phase accumulator.
This method chooses between the two available phase registers in the AD9834 to
control the phase of the output waveform. The control register is updated
to reflect the selected phase register.
:param phase_reg: The phase register to select. Must be one of
:const:`AD9834_PHASE_REG_0` or :const:`AD9834_PHASE_REG_1`.
"""
if phase_reg not in PHASE_REGS:
raise ValueError("Invalid phase register")
if phase_reg == PHASE_REGS[0]:
self.ctrl_reg &= ~AD9834_PSEL
else:
self.ctrl_reg |= AD9834_PSEL
self.ctrl_reg &= ~AD9834_PIN_SW
self.write(self.ctrl_reg)
self.bus.write(data << 16)
@kernel
def enable_reset(self):
@ -255,6 +111,158 @@ class AD9834:
self.ctrl_reg &= ~AD9834_RESET
self.write(self.ctrl_reg)
@kernel
def init(self):
"""
Initialize the AD9834: configure the SPI bus and reset the DDS.
This method performs the necessary setup for the AD9834 device, including:
- Configuring the SPI bus parameters (clock polarity, data width, and frequency).
- Putting the AD9834 into a reset state to ensure proper initialization.
The SPI bus is configured to use 16 bits of data width with the clock frequency
provided as a parameter when creating the AD9834 instance. After configuring
the SPI bus, the method invokes :meth:`enable_reset()` to reset the AD9834.
This is an essential step to prepare the device for subsequent configuration
of frequency and phase.
This method should be called before any other operations are performed
on the AD9834 to ensure that the device is in a known state.
"""
self.bus.set_config(spi.SPI_CLK_POLARITY | spi.SPI_END, 16, self.spi_freq, 1)
self.enable_reset()
@kernel
def set_frequency_reg_msb(self, freq_reg: TInt32, word: TInt32):
"""
Set the fourteen most significant bits MSBs of the specified frequency register.
This method updates the specified frequency register with the provided MSB value.
It configures the control register to indicate that the MSB is being set.
:param freq_reg: The frequency register to write to (0-1).
:param word: The value to be written to the fourteen MSBs of the frequency register.
The method first clears the appropriate control bits, sets :const:`AD9834_HLB` to
indicate that the MSB is being sent, and then writes the updated control register
followed by the MSB value to the specified frequency register.
"""
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
self.ctrl_reg &= ~AD9834_B28
self.ctrl_reg |= AD9834_HLB
self.write(self.ctrl_reg)
self.write(FREQ_REGS[freq_reg] | (word & 0x3FFF))
@kernel
def set_frequency_reg_lsb(self, freq_reg: TInt32, word: TInt32):
"""
Set the fourteen least significant bits LSBs of the specified frequency register.
This method updates the specified frequency register with the provided LSB value.
It configures the control register to indicate that the LSB is being set.
:param freq_reg: The frequency register to write to (0-1).
:param word: The value to be written to the fourteen LSBs of the frequency register.
The method first clears the appropriate control bits and writes the updated control
register followed by the LSB value to the specified frequency register.
"""
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
self.ctrl_reg &= ~AD9834_B28
self.ctrl_reg &= ~AD9834_HLB
self.write(self.ctrl_reg)
self.write(FREQ_REGS[freq_reg] | (word & 0x3FFF))
@kernel
def set_frequency_reg(self, freq_reg: TInt32, freq_word: TInt32):
"""
Set the frequency for the specified frequency register using a precomputed frequency word.
This writes to the 28-bit frequency register in one transfer.
:param freq_reg: The frequency register to write to (0-1).
:param freq_word: The precomputed frequency word.
"""
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
self.ctrl_reg |= AD9834_B28
self.write(self.ctrl_reg)
lsb = freq_word & 0x3FFF
msb = (freq_word >> 14) & 0x3FFF
self.write(FREQ_REGS[freq_reg] | lsb)
self.write(FREQ_REGS[freq_reg] | msb)
@portable(flags={"fast-math"})
def frequency_to_ftw(self, frequency: TFloat) -> TInt32:
"""Return the 28-bit frequency tuning word corresponding to the given
frequency.
"""
assert frequency <= 37.5 * MHz, "Frequency exceeds maximum value of 37.5 MHz"
return int((frequency * (1 << 28)) / self.clk_freq) & 0x0FFFFFFF
@portable(flags={"fast-math"})
def turns_to_pow(self, turns: TFloat) -> TInt32:
"""Return the 12-bit phase offset word corresponding to the given phase
in turns."""
assert 0.0 <= turns <= 1.0, "Turns exceeds range 0.0 - 1.0"
return int32(round(turns * 0x1000)) & int32(0x0FFF)
@kernel
def select_frequency_reg(self, freq_reg: TInt32):
"""
Select the active frequency register for the phase accumulator.
This method chooses between the two available frequency registers in the AD9834 to
control the frequency of the output waveform. The control register is updated
to reflect the selected frequency register.
:param freq_reg: The frequency register to write to (0-1).
"""
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
if freq_reg:
self.ctrl_reg |= AD9834_FSEL
else:
self.ctrl_reg &= ~AD9834_FSEL
self.ctrl_reg &= ~AD9834_PIN_SW
self.write(self.ctrl_reg)
@kernel
def set_phase_reg(self, phase_reg: TInt32, phase: TInt32):
"""
Set the phase for the specified phase register.
This method updates the specified phase register with the provided phase value.
:param phase_reg: The phase register to write to (0-1).
:param phase: The value to be written to the phase register.
The method masks the phase value to ensure it fits within the 12-bit limit
and writes it to the specified phase register.
"""
assert 0 <= phase_reg <= 1, "Invalid phase register index"
phase_word = phase & 0x0FFF
self.write(PHASE_REGS[phase_reg] | phase_word)
@kernel
def select_phase_reg(self, phase_reg: TInt32):
"""
Select the active phase register for the phase accumulator.
This method chooses between the two available phase registers in the AD9834 to
control the phase of the output waveform. The control register is updated
to reflect the selected phase register.
:param phase_reg: The phase register to write to (0-1).
"""
assert 0 <= phase_reg <= 1, "Invalid phase register index"
if phase_reg:
self.ctrl_reg |= AD9834_PSEL
else:
self.ctrl_reg &= ~AD9834_PSEL
self.ctrl_reg &= ~AD9834_PIN_SW
self.write(self.ctrl_reg)
@kernel
def sleep(self, dac_pd: bool = False, clk_dis: bool = False):
"""
@ -329,19 +337,13 @@ class AD9834:
self.ctrl_reg &= ~AD9834_OPBITEN
elif msb_2:
self.ctrl_reg |= AD9834_OPBITEN
self.ctrl_reg &= ~AD9834_MODE
self.ctrl_reg &= ~AD9834_SIGN_PIB
self.ctrl_reg &= ~AD9834_DIV2
self.ctrl_reg &= ~(AD9834_MODE | AD9834_SIGN_PIB | AD9834_DIV2)
elif msb:
self.ctrl_reg |= AD9834_OPBITEN
self.ctrl_reg &= ~AD9834_MODE
self.ctrl_reg &= ~AD9834_SIGN_PIB
self.ctrl_reg |= AD9834_DIV2
self.ctrl_reg |= AD9834_OPBITEN | AD9834_DIV2
self.ctrl_reg &= ~(AD9834_MODE | AD9834_SIGN_PIB)
elif comp_out:
self.ctrl_reg |= AD9834_OPBITEN
self.ctrl_reg |= AD9834_OPBITEN | AD9834_SIGN_PIB | AD9834_DIV2
self.ctrl_reg &= ~AD9834_MODE
self.ctrl_reg |= AD9834_SIGN_PIB
self.ctrl_reg |= AD9834_DIV2
else:
self.ctrl_reg &= ~AD9834_OPBITEN
@ -380,18 +382,56 @@ class AD9834:
self.write(self.ctrl_reg)
@kernel
def write(self, data: TInt32):
def set_mu(
self,
freq_word: TInt32 = 0,
phase_word: TInt32 = 0,
freq_reg: TInt32 = 0,
phase_reg: TInt32 = 0,
):
"""
Write a 16-bit word to the AD9834.
Set DDS frequency and phase in machine units.
This method sends a 16-bit data word to the AD9834 via the SPI bus. The input
data is left-shifted by 16 bits to ensure proper alignment for the SPI controller,
allowing for accurate processing of the command by the AD9834.
This method updates the specified frequency and phase registers with the provided
machine units, selects the corresponding registers, and enables the output.
This method is used internally by other methods to update the control registers
and frequency settings of the AD9834. It should not be called directly unless
low-level register manipulation is required.
:param data: The 16-bit word to be sent to the AD9834.
:param freq_word: Frequency tuning word (28-bit).
:param phase_word: Phase tuning word (12-bit).
:param freq_reg: Frequency register to write to (0 or 1).
:param phase_reg: Phase register to write to (0 or 1).
"""
self.bus.write(data << 16)
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
assert 0 <= phase_reg <= 1, "Invalid phase register index"
self.set_frequency_reg_lsb(freq_reg, freq_word & 0x3FFF)
self.set_frequency_reg_msb(freq_reg, (freq_word >> 14) & 0x3FFF)
self.set_phase_reg(phase_reg, phase_word)
self.select_frequency_reg(freq_reg)
self.select_phase_reg(phase_reg)
self.output_enable()
@kernel
def set(
self,
frequency: TFloat = 0.0,
phase: TFloat = 0.0,
freq_reg: TInt32 = 0,
phase_reg: TInt32 = 0,
):
"""
Set DDS frequency in Hz and phase using fractional turns.
This method converts the specified frequency and phase to their corresponding
machine units, updates the selected registers, and enables the output.
:param frequency: Frequency in Hz.
:param phase: Phase in fractional turns (e.g., 0.5 for 180 degrees).
:param freq_reg: Frequency register to write to (0 or 1).
:param phase_reg: Phase register to write to (0 or 1).
"""
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
assert 0 <= phase_reg <= 1, "Invalid phase register index"
freq_word = self.frequency_to_ftw(frequency)
phase_word = self.turns_to_pow(phase)
self.set_mu(freq_word, phase_word, freq_reg, phase_reg)

View File

@ -534,9 +534,17 @@ class AD9910:
After the SPI transfer, the shared IO update pin is pulsed to
activate the data.
.. seealso: :meth:`AD9910.set_phase_mode` for a definition of the different
.. seealso:: :meth:`AD9910.set_phase_mode` for a definition of the different
phase modes.
.. warning::
Deterministic phase control depends on correct alignment of operations
to a 4ns grid (``SYNC_CLK``). This function uses :meth:`~artiq.language.core.now_mu()`
to ensure such alignment automatically. When replayed over DMA, however, the ensuing
event sequence *must* be started at the same offset relative to ``SYNC_CLK``, or
unstable ``SYNC_CLK`` cycle assignment (i.e. inconsistent delays of exactly 4ns) will
result.
:param ftw: Frequency tuning word: 32-bit.
:param pow_: Phase tuning word: 16-bit unsigned.
:param asf: Amplitude scale factor: 14-bit unsigned.

View File

@ -728,15 +728,7 @@ def decoded_dump_to_target(manager, devices, dump, uniform_interval):
logger.warning("unable to determine DDS sysclk")
dds_sysclk = 3e9 # guess
if isinstance(dump.messages[-1], StoppedMessage):
m = dump.messages[-1]
end_time = get_message_time(m)
manager.set_end_time(end_time)
messages = dump.messages[:-1]
else:
logger.warning("StoppedMessage missing")
messages = dump.messages
messages = sorted(messages, key=get_message_time)
messages = sorted(dump.messages, key=get_message_time)
channel_handlers = create_channel_handlers(
manager, devices, ref_period,
@ -752,6 +744,8 @@ def decoded_dump_to_target(manager, devices, dump, uniform_interval):
interval = manager.get_channel("interval", 64, ty=WaveformType.ANALOG)
slack = manager.get_channel("rtio_slack", 64, ty=WaveformType.ANALOG)
stopped_messages = []
manager.set_time(0)
start_time = 0
for m in messages:
@ -762,7 +756,10 @@ def decoded_dump_to_target(manager, devices, dump, uniform_interval):
manager.set_start_time(start_time)
t0 = start_time
for i, message in enumerate(messages):
if message.channel in channel_handlers:
if isinstance(message, StoppedMessage):
stopped_messages.append(message)
logger.debug(f"StoppedMessage at {get_message_time(message)}")
elif message.channel in channel_handlers:
t = get_message_time(message)
if t >= 0:
if uniform_interval:
@ -776,3 +773,9 @@ def decoded_dump_to_target(manager, devices, dump, uniform_interval):
if isinstance(message, OutputMessage):
slack.set_value_double(
(message.timestamp - message.rtio_counter)*ref_period)
if not stopped_messages:
logger.warning("StoppedMessage missing")
else:
end_time = get_message_time(stopped_messages[-1])
manager.set_end_time(end_time)

View File

@ -705,8 +705,13 @@ class CommKernel:
python_exn_type = embedding_map.retrieve_object(core_exn.id)
try:
python_exn = python_exn_type(
nested_exceptions[-1][1].format(*nested_exceptions[0][2]))
message = nested_exceptions[0][1].format(*nested_exceptions[0][2])
except:
message = nested_exceptions[0][1]
logger.error("Couldn't format exception message", exc_info=True)
try:
python_exn = python_exn_type(message)
except Exception as ex:
python_exn = RuntimeError(
f"Exception type={python_exn_type}, which couldn't be "

View File

@ -30,7 +30,12 @@ OSError = builtins.OSError
class CoreException:
"""Information about an exception raised or passed through the core device."""
"""Information about an exception raised or passed through the core device.
If the exception message contains positional format arguments, it
will attempt to substitute them with the provided parameters.
If the substitution fails, the original message will remain unchanged.
"""
def __init__(self, exceptions, exception_info, traceback, stack_pointers):
self.exceptions = exceptions
self.exception_info = exception_info
@ -92,7 +97,10 @@ class CoreException:
exn_id = int(exn_id)
else:
exn_id = 0
try:
lines.append("{}({}): {}".format(name, exn_id, message.format(*params)))
except:
lines.append("{}({}): {}".format(name, exn_id, message))
zipped.append(((exception[3], exception[4], exception[5], exception[6],
None, []), None))

View File

@ -500,7 +500,7 @@ pub extern fn main() -> i32 {
println!(r"|_| |_|_|____/ \___/ \____|");
println!("");
println!("MiSoC Bootloader");
println!("Copyright (c) 2017-2024 M-Labs Limited");
println!("Copyright (c) 2017-2025 M-Labs Limited");
println!("");
#[cfg(has_ethmac)]

View File

@ -240,7 +240,7 @@ fn startup() {
let subkernel_mutex = subkernel_mutex.clone();
let drtio_routing_table = drtio_routing_table.clone();
let up_destinations = up_destinations.clone();
io.spawn(8192, move |io| { analyzer::thread(io, &aux_mutex, &ddma_mutex, &subkernel_mutex, &drtio_routing_table, &up_destinations) });
io.spawn(16384, move |io| { analyzer::thread(io, &aux_mutex, &ddma_mutex, &subkernel_mutex, &drtio_routing_table, &up_destinations) });
}
#[cfg(has_grabber)]

View File

@ -11,8 +11,6 @@ from artiq.coredevice.ad9834 import (
AD9834_SIGN_PIB,
AD9834_SLEEP1,
AD9834_SLEEP12,
FREQ_REGS,
PHASE_REGS,
)
from artiq.experiment import *
from artiq.language.units import MHz
@ -34,165 +32,251 @@ class AD9834Exp(EnvExperiment):
@kernel
def init(self):
self.core.break_realtime()
self.core.reset()
self.dev.init()
self.set_dataset("spi_freq", self.dev.spi_freq)
self.set_dataset("clk_freq", self.dev.clk_freq)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def set_frequency_reg_fail1(self):
self.core.break_realtime()
frequency = 10 * MHz
self.dev.set_frequency_reg(19, frequency)
def frequency_to_ftw_fail(self):
self.core.reset()
self.dev.init()
self.dev.frequency_to_ftw(37.6 * MHz)
@kernel
def set_frequency_reg_fail2(self):
self.core.break_realtime()
self.dev.set_frequency_reg(FREQ_REGS[0], 37.6 * MHz)
def turns_to_phase_fail(self):
self.core.reset()
self.dev.init()
self.dev.turns_to_phase(1.1)
@kernel
def set_frequency_reg_fail(self):
self.core.reset()
self.dev.init()
self.dev.set_frequency_reg(19, self.dev.frequency_to_ftw(10 * MHz))
@kernel
def set_frequency_reg(self):
self.core.break_realtime()
self.core.reset()
self.dev.init()
self.dev.set_frequency_reg(FREQ_REGS[1], 19 * MHz)
self.dev.set_frequency_reg(1, self.dev.frequency_to_ftw(19 * MHz))
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def set_frequency_reg_msb(self):
self.core.break_realtime()
self.core.reset()
self.dev.init()
self.dev.ctrl_reg |= AD9834_B28
self.dev.set_frequency_reg_msb(FREQ_REGS[0], 0x1111)
self.dev.set_frequency_reg_msb(0, 0x1111)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def set_frequency_reg_lsb(self):
self.core.break_realtime()
self.core.reset()
self.dev.init()
self.dev.ctrl_reg |= AD9834_B28 | AD9834_HLB
self.dev.set_frequency_reg_lsb(FREQ_REGS[1], 0xFFFF)
self.dev.set_frequency_reg_lsb(1, 0xFFFF)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def select_frequency_reg_0(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_FSEL | AD9834_PIN_SW
self.dev.select_frequency_reg(FREQ_REGS[0])
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
self.core.reset()
self.dev.init()
self.dev.select_frequency_reg(0)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg & ~(AD9834_FSEL | AD9834_PIN_SW))
@kernel
def select_frequency_reg_1(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_PIN_SW
self.dev.select_frequency_reg(FREQ_REGS[1])
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
self.core.reset()
self.dev.init()
self.dev.select_frequency_reg(1)
self.set_dataset("ctrl_reg", (self.dev.ctrl_reg | AD9834_FSEL) & ~AD9834_PIN_SW)
@kernel
def set_phase_reg_fail(self):
self.core.break_realtime()
self.core.reset()
self.dev.init()
self.dev.set_phase_reg(19, 0x123)
@kernel
def set_phase_reg(self):
self.core.break_realtime()
self.core.reset()
self.dev.init()
self.dev.set_phase_reg(PHASE_REGS[0], 0x123)
self.dev.set_phase_reg(0, 0x123)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def select_phase_reg_0(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_PSEL | AD9834_PIN_SW
self.dev.select_phase_reg(PHASE_REGS[0])
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
self.core.reset()
self.dev.init()
self.dev.select_phase_reg(0)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg & ~(AD9834_PSEL | AD9834_PIN_SW))
@kernel
def select_phase_reg_1(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_PIN_SW
self.dev.select_phase_reg(PHASE_REGS[1])
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def enable_reset(self):
self.core.break_realtime()
self.dev.enable_reset()
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def output_enable(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_RESET
self.dev.output_enable()
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
self.core.reset()
self.dev.init()
self.dev.select_phase_reg(1)
self.set_dataset("ctrl_reg", (self.dev.ctrl_reg | AD9834_PSEL) & ~AD9834_PIN_SW)
@kernel
def sleep_dac_powerdown(self):
self.core.break_realtime()
self.core.reset()
self.dev.init()
self.dev.sleep(dac_pd=True)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def sleep_internal_clk_disable(self):
self.core.break_realtime()
self.core.reset()
self.dev.init()
self.dev.sleep(clk_dis=True)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def sleep(self):
self.core.break_realtime()
self.core.reset()
self.dev.init()
self.dev.sleep(dac_pd=True, clk_dis=True)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def awake(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_SLEEP1 | AD9834_SLEEP12
self.dev.sleep()
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
self.core.reset()
self.dev.init()
self.dev.awake()
self.set_dataset(
"ctrl_reg", self.dev.ctrl_reg & ~(AD9834_SLEEP1 | AD9834_SLEEP12)
)
@kernel
def sign_bit_high_z(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_OPBITEN
self.core.reset()
self.dev.init()
self.dev.config_sign_bit_out()
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg & ~AD9834_OPBITEN)
@kernel
def sign_bit_msb_2(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_MODE | AD9834_SIGN_PIB | AD9834_DIV2
self.core.reset()
self.dev.init()
self.dev.config_sign_bit_out(msb_2=True)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
self.set_dataset(
"ctrl_reg",
(self.dev.ctrl_reg | AD9834_OPBITEN)
& ~(AD9834_MODE | AD9834_SIGN_PIB | AD9834_DIV2),
)
@kernel
def sign_bit_msb(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_MODE | AD9834_SIGN_PIB
self.core.reset()
self.dev.init()
self.dev.config_sign_bit_out(msb=True)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
self.set_dataset(
"ctrl_reg",
(self.dev.ctrl_reg | AD9834_MODE | AD9834_SIGN_PIB)
& ~(AD9834_MODE | AD9834_SIGN_PIB),
)
@kernel
def sign_bit_comp_out(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_MODE
self.core.reset()
self.dev.init()
self.dev.config_sign_bit_out(comp_out=True)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
self.set_dataset(
"ctrl_reg",
(self.dev.ctrl_reg | AD9834_OPBITEN | AD9834_SIGN_PIB | AD9834_DIV2)
& ~AD9834_MODE,
)
@kernel
def enable_triangular_waveform(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_OPBITEN
self.core.reset()
self.dev.init()
self.dev.enable_triangular_waveform()
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg | AD9834_MODE)
@kernel
def disable_triangular_waveform(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_MODE
self.core.reset()
self.dev.init()
self.dev.disable_triangular_waveform()
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg & ~AD9834_MODE)
## The following tests should be hooked up to an oscilloscope
## to monitor the waveforms
@kernel
def single_tone(self):
self.core.reset()
self.dev.init()
self.dev.set_frequency_reg(0, self.dev.frequency_to_ftw(1 * MHz))
self.dev.select_frequency_reg(0)
self.dev.output_enable()
delay(5 * s)
self.dev.enable_reset()
self.core.wait_until_mu(now_mu())
@kernel
def toggle_frequency(self):
self.core.reset()
self.dev.init()
self.dev.set_frequency_reg(0, self.dev.frequency_to_ftw(1 * MHz))
self.dev.set_frequency_reg(1, self.dev.frequency_to_ftw(2 * MHz))
self.dev.select_frequency_reg(0)
self.dev.output_enable()
for _ in range(6):
self.dev.select_frequency_reg(0)
delay(1 * s)
self.dev.select_frequency_reg(1)
delay(1 * s)
self.dev.enable_reset()
self.core.wait_until_mu(now_mu())
@kernel
def toggle_phase(self):
self.core.reset()
self.dev.init()
self.dev.set_frequency_reg(0, self.dev.frequency_to_ftw(1 * MHz))
self.dev.select_frequency_reg(0)
self.dev.set_phase_reg(0, 0x0)
self.dev.set_phase_reg(1, 0x7FF)
self.dev.output_enable()
for _ in range(300000):
self.dev.select_phase_reg(0)
delay(10 * us)
self.dev.select_phase_reg(1)
delay(10 * us)
self.dev.enable_reset()
self.core.wait_until_mu(now_mu())
@kernel
def set_mu(self):
self.core.reset()
self.dev.init()
freq_word = self.dev.frequency_to_ftw(1 * MHz)
phase_word = self.dev.turns_to_phase(0.5)
self.dev.set_mu(freq_word, phase_word, 0, 1)
delay(5 * s)
self.dev.enable_reset()
self.core.wait_until_mu(now_mu())
@kernel
def set(self):
self.core.reset()
self.dev.init()
self.dev.set(2 * MHz, 0.5, 1, 0)
delay(5 * s)
self.dev.enable_reset()
self.core.wait_until_mu(now_mu())
class AD9834Test(ExperimentCase):
@ -208,21 +292,27 @@ class AD9834Test(ExperimentCase):
self.assertEqual(clk_freq, 75 * MHz)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
def test_set_frequency_reg_fail(self):
with self.assertRaises(ValueError):
self.execute(AD9834Exp, "set_frequency_reg_fail1")
def test_frequency_to_ftw_fail(self):
with self.assertRaises(AssertionError):
self.execute(AD9834Exp, "set_frequency_reg_fail2")
self.execute(AD9834Exp, "frequency_to_ftw_fail")
def test_turns_to_phase_fail(self):
with self.assertRaises(AssertionError):
self.execute(AD9834Exp, "turns_to_phase_fail")
def test_set_frequency_reg_fail(self):
with self.assertRaises(AssertionError):
self.execute(AD9834Exp, "set_frequency_reg_fail")
def test_set_frequency_reg(self):
self.execute(AD9834Exp, "set_frequency_reg")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET | AD9834_B28)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_B28 | AD9834_RESET)
def test_set_frequency_reg_msb(self):
self.execute(AD9834Exp, "set_frequency_reg_msb")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET | AD9834_HLB)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_HLB | AD9834_RESET)
def test_set_frequency_reg_lsb(self):
self.execute(AD9834Exp, "set_frequency_reg_lsb")
@ -232,15 +322,15 @@ class AD9834Test(ExperimentCase):
def test_select_frequency_reg_0(self):
self.execute(AD9834Exp, "select_frequency_reg_0")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
def test_select_frequency_reg_1(self):
self.execute(AD9834Exp, "select_frequency_reg_1")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000 | AD9834_FSEL)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_FSEL | AD9834_RESET)
def test_set_phase_reg_fail(self):
with self.assertRaises(ValueError):
with self.assertRaises(AssertionError):
self.execute(AD9834Exp, "set_phase_reg_fail")
def test_set_phase_reg(self):
@ -251,71 +341,85 @@ class AD9834Test(ExperimentCase):
def test_select_phase_reg_0(self):
self.execute(AD9834Exp, "select_phase_reg_0")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
def test_select_phase_reg_1(self):
self.execute(AD9834Exp, "select_phase_reg_1")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000 | AD9834_PSEL)
def test_enable_reset(self):
self.execute(AD9834Exp, "enable_reset")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
def test_output_enable(self):
self.execute(AD9834Exp, "output_enable")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_PSEL | AD9834_RESET)
def test_sleep_dac_powerdown(self):
self.execute(AD9834Exp, "sleep_dac_powerdown")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000 | AD9834_SLEEP12)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_SLEEP12 | AD9834_RESET)
def test_sleep_internal_clk_disable(self):
self.execute(AD9834Exp, "sleep_internal_clk_disable")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000 | AD9834_SLEEP1)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_SLEEP1 | AD9834_RESET)
def test_sleep(self):
self.execute(AD9834Exp, "sleep")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000 | AD9834_SLEEP1 | AD9834_SLEEP12)
self.assertEqual(
ctrl_reg, 0x0000 | AD9834_SLEEP1 | AD9834_SLEEP12 | AD9834_RESET
)
def test_awake(self):
self.execute(AD9834Exp, "awake")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
def test_sign_bit_high_z(self):
self.execute(AD9834Exp, "sign_bit_high_z")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
def test_sign_bit_msb_2(self):
self.execute(AD9834Exp, "sign_bit_msb_2")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000 | AD9834_OPBITEN)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_OPBITEN | AD9834_RESET)
def test_sign_bit_msb(self):
self.execute(AD9834Exp, "sign_bit_msb")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000 | AD9834_OPBITEN | AD9834_DIV2)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_OPBITEN | AD9834_DIV2 | AD9834_RESET)
def test_sign_bit_comp_out(self):
self.execute(AD9834Exp, "sign_bit_comp_out")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(
ctrl_reg, 0x0000 | AD9834_OPBITEN | AD9834_SIGN_PIB | AD9834_DIV2
ctrl_reg,
0x0000 | AD9834_OPBITEN | AD9834_SIGN_PIB | AD9834_DIV2 | AD9834_RESET,
)
def test_enble_triangular_waveform(self):
self.execute(AD9834Exp, "enable_triangular_waveform")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000 | AD9834_MODE)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_MODE | AD9834_RESET)
def test_disble_triangular_waveform(self):
self.execute(AD9834Exp, "disable_triangular_waveform")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000)
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
## Waveform Tests
def test_single_tone(self):
print("Running waveform test:", self._testMethodName)
self.execute(AD9834Exp, "single_tone")
def test_toggle_frequency(self):
print("Running waveform test:", self._testMethodName)
self.execute(AD9834Exp, "toggle_frequency")
def test_toggle_phase(self):
print("Running waveform test:", self._testMethodName)
self.execute(AD9834Exp, "toggle_phase")
def test_set_mu(self):
print("Running waveform test:", self._testMethodName)
self.execute(AD9834Exp, "set_mu")
def test_set(self):
print("Running waveform test:", self._testMethodName)
self.execute(AD9834Exp, "set")

View File

@ -1,10 +1,94 @@
import unittest
import artiq.coredevice.exceptions as exceptions
import re
from artiq.experiment import *
from artiq.master.worker_db import DeviceError
from artiq.test.hardware_testbench import ExperimentCase
from artiq.compiler.embedding import EmbeddingMap
from artiq.coredevice.core import test_exception_id_sync
import artiq.coredevice.exceptions as exceptions
class CustomException(Exception):
pass
class KernelFmtException(EnvExperiment):
def build(self):
self.setattr_device("core")
@kernel
def run(self):
self.throw()
def throw(self):
raise CustomException("{foo}")
class KernelNestedFmtException(EnvExperiment):
def build(self):
self.setattr_device("core")
@kernel
def run(self):
try:
self.throw_foo()
except:
try:
raise RTIOUnderflow("{bar}")
except:
try:
raise RTIOOverflow("{bizz}")
except:
self.throw_buzz()
def throw_foo(self):
raise CustomException("{foo}")
def throw_buzz(self):
raise RTIOUnderflow("{buzz}")
class KernelRTIOUnderflow(EnvExperiment):
def build(self):
self.setattr_device("core")
try:
self.setattr_device("led")
except DeviceError:
self.led = self.get_device("led0")
@kernel
def run(self):
self.core.reset()
at_mu(self.core.get_rtio_counter_mu() - 1000); self.led.on()
class ExceptionFormatTest(ExperimentCase):
def test_custom_formatted_kernel_exception(self):
with self.assertLogs() as captured:
with self.assertRaisesRegex(CustomException, r"CustomException\(\d+\): \{foo\}"):
self.execute(KernelFmtException)
captured_lines = captured.output[0].split('\n')
self.assertEqual([captured_lines[0], captured_lines[-1]],
["ERROR:artiq.coredevice.comm_kernel:Couldn't format exception message", "KeyError: 'foo'"])
def test_nested_formatted_kernel_exception(self):
with self.assertLogs() as captured:
with self.assertRaisesRegex(CustomException,
re.compile(
r"CustomException\(\d+\): \{foo\}.*?RTIOUnderflow\(\d+\): \{bar\}.*?RTIOOverflow\(\d+\): \{bizz\}.*?RTIOUnderflow\(\d+\): \{buzz\}",
re.DOTALL)):
self.execute(KernelNestedFmtException)
captured_lines = captured.output[0].split('\n')
self.assertEqual([captured_lines[0], captured_lines[-1]],
["ERROR:artiq.coredevice.comm_kernel:Couldn't format exception message", "KeyError: 'foo'"])
def test_rtio_underflow(self):
with self.assertRaisesRegex(RTIOUnderflow,
re.compile(
r"RTIO underflow at channel 0x[0-9a-fA-F]*?:led\d*?, \d+? mu, slack -\d+? mu.*?RTIOUnderflow\(\d+\): RTIO underflow at channel 0x([0-9a-fA-F]+?):led\d*?, \d+? mu, slack -\d+? mu",
re.DOTALL)):
self.execute(KernelRTIOUnderflow)
"""
Test sync in exceptions raised between host and kernel
@ -38,7 +122,7 @@ class _TestExceptionSync(EnvExperiment):
test_exception_id_sync(id)
class ExceptionTest(ExperimentCase):
class ExceptionSyncTest(ExperimentCase):
def test_raise_exceptions_kernel(self):
exp = self.create(_TestExceptionSync)
@ -56,4 +140,3 @@ class ExceptionTest(ExperimentCase):
name = name.split('.')[-1].split(':')[-1]
with self.assertRaises(getattr(exceptions, name)) as ctx:
exp.raise_exception_host(id)

View File

@ -86,9 +86,7 @@ Nix development environment
- Enable flakes, for example by adding ``experimental-features = nix-command flakes`` to ``nix.conf``. See also the `NixOS Wiki on flakes <https://nixos.wiki/wiki/flakes>`_.
- Add ``/opt`` (or your Vivado location) as an Nix sandbox, for example by adding ``extra-sandbox-paths = /opt`` to ``nix.conf``.
- Create a file called ``trusted-settings.json`` in ``~/.local/share/nix/``, if it doesn't exist already. Make sure it contains the following:
::
- Make sure that you have accepted and marked as permanent the additional settings described in :ref:`installing-details`. You can check on this manually by ensuring the file ``trusted-settings.json`` in ``~/.local/share/nix/`` exists and contains the following: ::
{
"extra-sandbox-paths":{
@ -102,7 +100,7 @@ Nix development environment
}
}
- If using NixOS, instead make the equivalent changes to your ``configuration.nix``.
- If using NixOS, make the equivalent changes to your ``configuration.nix`` instead.
* Clone `the ARTIQ Git repository <https://github.com/m-labs/artiq>`_, or `the ARTIQ-Zynq repository <https://git.m-labs.hk/M-Labs/artiq-zynq>`__ for :ref:`Zynq devices <devices-table>` (Kasli-SoC, ZC706, or EBAZ4205). By default, you are working with the ``master`` branch, which represents the beta version and is not stable (see :doc:`releases`). Checkout the most recent release (``git checkout release-[number]``) for a stable version.
* If your Vivado installation is not in its default location ``/opt``, open ``flake.nix`` and edit it accordingly (note that the edits must be made in the main ARTIQ flake, even if you are working with Zynq, see also tip below).
@ -120,7 +118,7 @@ Once you have run ``nix develop`` you are in the ARTIQ development environment.
$ export PYTHONPATH=/absolute/path/to/your/artiq:$PYTHONPATH
Note that this only applies for incremental builds. If you want to use ``nix build``, or make changes to the dependencies, look into changing the inputs of the ``flake.nix`` instead. You can do this by replacing the URL of the GitHub ARTIQ repository with ``path:/absolute/path/to/your/artiq``; remember that Nix caches dependencies, so to incorporate new changes you will need to exit the development shell, update the Nix cache with ``nix flake update``, and re-run ``nix develop``.
Note that this only applies for incremental builds. If you want to use ``nix build``, or make changes to the dependencies, look into changing the inputs of the ``flake.nix`` instead. You can do this by replacing the URL of the GitHub ARTIQ repository with ``path:/absolute/path/to/your/artiq``; remember that Nix pins dependencies, so to incorporate new changes you will need to exit the development shell, update the environment with ``nix flake update``, and re-run ``nix develop``.
Building only standard binaries
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -138,7 +136,7 @@ The parallel command does exist for ARTIQ-Zynq: ::
$ nix develop git+https://git.m-labs.hk/m-labs/artiq-zynq\?ref=release-[number]
but if you are building ARTIQ-Zynq without intention to change the source, it is not actually necessary to enter the development environment at all; Nix is capable of accessing the official flake remotely for the build itself, eliminating the requirement for any particular environment.
but if you are building ARTIQ-Zynq without intention to change the source, it is not actually necessary to enter the development environment at all; Nix is capable of accessing the official flake directly to set up the build, eliminating the requirement for any particular environment.
This is equally possible for original ARTIQ, but not as useful, as the development environment (specifically the ``#boards`` shell) is still the easiest way to access the necessary tools for flashing the board. On the other hand, Zynq boards can also be flashed by writing to the SD card directly, which requires no further special tools. As long as you have a functioning Nix/Vivado installation with flakes enabled, you can progress directly to the building instructions below.

View File

@ -90,7 +90,7 @@ master_doc = 'index'
# General information about the project.
project = 'ARTIQ'
copyright = '2014-2024, M-Labs Limited'
copyright = '2014-2025, M-Labs Limited'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the

View File

@ -1,31 +1,35 @@
Installing ARTIQ
================
ARTIQ can be installed using the Nix (on Linux) or MSYS2 (on Windows) package managers. Using Conda is also possible on both platforms but not recommended.
M-Labs recommends installing ARTIQ through Nix (on Linux) or MSYS2 (on Windows). It is also possible to use Conda (on either platform), but this is not preferred, and likely to become unsupported in the near future.
Installing via Nix (Linux)
--------------------------
First install the Nix package manager. Some distributions provide a package for it; otherwise, it can be installed via the script on the `Nix website <http://nixos.org/nix/>`_. Make sure you get Nix version 2.4 or higher. Prefer a single-user installation for simplicity.
First, install the Nix package manager. Some distributions provide a package for it. Otherwise, use the official install script, as described on the `Nix website <https://nixos.org/download/>`_, e.g.: ::
Once Nix is installed, enable flakes, for example by running: ::
$ sh <(curl -L https://nixos.org/nix/install) --no-daemon
Prefer the single-user installation for simplicity. Enable `Nix flakes <https://nixos.wiki/wiki/flakes>`_, for example by running: ::
$ mkdir -p ~/.config/nix
$ echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
See also the different options for enabling flakes on `the NixOS wiki <https://nixos.wiki/wiki/flakes>`_.
User environment installation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The easiest way to obtain ARTIQ is to install it into the user environment with ::
There are few options for accessing ARTIQ through Nix. The easiest way is to install it into the user environment: ::
$ nix profile install git+https://github.com/m-labs/artiq.git
Answer "Yes" to the questions about setting Nix configuration options (for more details see 'Troubleshooting' below.) You should now have a minimal installation of ARTIQ, where the usual front-end commands (:mod:`~artiq.frontend.artiq_run`, :mod:`~artiq.frontend.artiq_master`, :mod:`~artiq.frontend.artiq_dashboard`, etc.) are all available to you.
Answer "Yes" to the questions about setting Nix configuration options (for more details see :ref:`installing-details` below.) You should now have a minimal installation of ARTIQ, where the usual front-end commands (:mod:`~artiq.frontend.artiq_run`, :mod:`~artiq.frontend.artiq_master`, :mod:`~artiq.frontend.artiq_dashboard`, etc.) are all available to you.
This installation is however quite limited, as Nix creates a dedicated Python environment for the ARTIQ commands alone. This means that other useful Python packages, which ARTIQ is not dependent on but which you may want to use in your experiments (pandas, matplotlib...), are not available.
This installation is however relatively limited. Without further instructions, Nix takes its cues from the main ARTIQ flake (the ``flake.nix`` file at the root of the repository linked in the command) and creates a dedicated Python environment for the ARTIQ commands alone. This means that other useful Python packages, which are not necessary to run ARTIQ but which you might want to use in experiments (pandas, matplotlib...), are not available.
Installing multiple packages and making them visible to the ARTIQ commands requires using the Nix language. Create an empty directory with a file ``flake.nix`` with the following contents:
Flake custom environments
^^^^^^^^^^^^^^^^^^^^^^^^^
::
Modifying the environment and making additional packages visible to the ARTIQ commands requires using the Nix language and writing your own flake. Create an empty directory with a file ``flake.nix`` containing the following: ::
{
inputs.extrapkg.url = "git+https://git.m-labs.hk/M-Labs/artiq-extrapkg.git";
@ -34,11 +38,12 @@ Installing multiple packages and making them visible to the ARTIQ commands requi
pkgs = extrapkg.pkgs;
artiq = extrapkg.packages.x86_64-linux;
in {
defaultPackage.x86_64-linux = pkgs.buildEnv {
# This section defines the new environment
packages.x86_64-linux.default = pkgs.buildEnv {
name = "artiq-env";
paths = [
# ========================================
# EDIT BELOW
# ADD PACKAGES BELOW
# ========================================
(pkgs.python3.withPackages(ps : [
# List desired Python packages here.
@ -60,35 +65,42 @@ Installing multiple packages and making them visible to the ARTIQ commands requi
#ps.qiskit
# Note that NixOS also provides packages ps.numpy and ps.scipy, but it is
# not necessary to explicitly add these, since they are dependencies of
# ARTIQ and available with an ARTIQ install anyway.
# ARTIQ and incorporated with an ARTIQ install anyway.
]))
# List desired non-Python packages here
# Additional NDSPs can be included:
#artiq.korad_ka3005p
#artiq.novatech409b
# List desired non-Python packages here
# Other potentially interesting non-Python packages from the NixOS package collection:
#pkgs.gtkwave
#pkgs.spyder
#pkgs.R
#pkgs.julia
# ========================================
# EDIT ABOVE
# ADD PACKAGES ABOVE
# ========================================
];
};
};
# This section configures additional settings to be able to use M-Labs binary caches
nixConfig = { # work around https://github.com/NixOS/nix/issues/6771
extra-trusted-public-keys = "nixbld.m-labs.hk-1:5aSRVA5b320xbNvu30tqxVPXpld73bhtOeH6uAjRyHc=";
extra-substituters = "https://nixbld.m-labs.hk";
};
}
You can now spawn a shell containing these packages by running ``$ nix shell`` in the directory containing the ``flake.nix``. This should make both the ARTIQ commands and all the additional packages available to you. You can exit the shell with Control+D or with the command ``exit``. A first execution of ``$ nix shell`` may take some time, but for any future repetitions Nix will use cached packages and startup should be much faster.
To spawn a shell in this environment, navigate to the directory containing the ``flake.nix`` and run: ::
You might be interested in creating multiple directories containing different ``flake.nix`` files which represent different sets of packages for different purposes. If you are familiar with Conda, using Nix in this way is similar to having multiple Conda environments.
$ nix shell
The resulting shell will have access to ARTIQ as well as any additional packages you may have added. You can exit this shell at any time with CTRL+D or with the command ``exit``. Note that a first execution of ``nix shell`` on a given flake may take some time; repetitions of the same command will use stored versions of packages and run much more quickly.
You might be interested in creating multiple directories containing separate ``flake.nix`` files to represent different sets of packages for different purposes. If you are familiar with Conda, using Nix in this way is similar to having multiple Conda environments.
To find more packages you can browse the `Nix package search <https://search.nixos.org/packages>`_ website. If your favorite package is not available with Nix, contact M-Labs using the helpdesk@ email.
.. note::
If you find you prefer using flakes to your original ``nix profile`` installation, you can remove it from your system by running: ::
$ nix profile list
@ -97,35 +109,39 @@ To find more packages you can browse the `Nix package search <https://search.nix
$ nix profile remove [index]
While using flakes, ARTIQ is not 'installed' as such in any permanent way. However, Nix will preserve independent cached packages in ``/nix/store`` for each flake, which over time or with many different flakes and versions can take up large amounts of storage space. To clear this cache, run ``$ nix-collect-garbage``.
While using flakes, ARTIQ is not strictly 'installed' in a permanent way. However, Nix will keep collected packages in ``/nix/store`` for each flake, which over time or with many different flakes and versions can take up large amounts of storage space. To clear this cache, run ``nix-collect-garbage``. (After a garbage collection, ``nix shell`` will require some time again when first used).
.. _installing-troubleshooting:
.. _installing-details:
Troubleshooting
^^^^^^^^^^^^^^^
Installation details
^^^^^^^^^^^^^^^^^^^^
"Do you want to allow configuration setting... (y/N)?"
""""""""""""""""""""""""""""""""""""""""""""""""""""""
When installing and initializing ARTIQ using commands like ``nix shell``, ``nix develop``, or ``nix profile install``, you may encounter prompts to modify certain configuration settings. These settings correspond to the ``nixConfig`` flag within the ARTIQ flake: ::
When installing and initializing ARTIQ using commands like ``nix shell``, ``nix develop``, or ``nix profile install``, you may encounter prompts to modify certain configuration settings. These settings correspond to the ``nixConfig`` section in the ARTIQ flake: ::
do you want to allow configuration setting 'extra-sandbox-paths' to be set to '/opt' (y/N)?
do you want to allow configuration setting 'extra-substituters' to be set to 'https://nixbld.m-labs.hk' (y/N)?
do you want to allow configuration setting 'extra-trusted-public-keys' to be set to 'nixbld.m-labs.hk-1:5aSRVA5b320xbNvu30tqxVPXpld73bhtOeH6uAjRyHc=' (y/N)?
We recommend accepting these settings by responding with ``y``. If asked to permanently mark these values as trusted, choose ``y`` again. This action saves the configuration to ``~/.local/share/nix/trusted-settings.json``, allowing future prompts to be bypassed.
.. note::
The first is necessary in order to be able to use Vivado to build ARTIQ gateware (e.g. :doc:`building_developing`). The latter two are necessary in order to use the M-Labs nixbld server as a binary cache; refusing these will result in Nix attempting to build these binaries from source, which is possible to do, but requires a considerable amount of time (on the order of hours) on most machines.
Alternatively, you can also use the option `accept-flake-config <https://nix.dev/manual/nix/stable/command-ref/conf-file#conf-accept-flake-config>`_ by appending ``--accept-flake-config`` to your nix command, for example: ::
It is recommended to accept all three settings by responding with ``y``. If asked to permanently mark these values as trusted, choose ``y`` again. This action saves the configuration to ``~/.local/share/nix/trusted-settings.json``, allowing future prompts to be bypassed.
nix develop --accept-flake-config
Alternatively, you can also use the option `accept-flake-config <https://nix.dev/manual/nix/stable/command-ref/conf-file#conf-accept-flake-config>`_ on a per-command basis by appending ``--accept-flake-config``, for example: ::
Or add the option to ``~/.config/nix/nix.conf`` to make the setting more permanent: ::
nix shell --accept-flake-config
Or add the option to ``~/.config/nix/nix.conf`` to make the setting apply to all commands by default: ::
extra-experimental-features = flakes
accept-flake-config = true
.. note::
Should you wish to revert to the default settings, you can do so by editing the appropriate options in the aforementioned configuration files.
Should you wish to revert to the default settings, you can do so at any time by editing the appropriate options in the aforementioned configuration files.
"Ignoring untrusted substituter, you are not a trusted user"
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
@ -135,7 +151,7 @@ If the following message displays when running ``nix shell`` or ``nix develop``
warning: ignoring untrusted substituter 'https://nixbld.m-labs.hk', you are not a trusted user.
Run `man nix.conf` for more information on the `substituters` configuration option.
and Nix proceeds to build some packages from source, this means that you are using `multi-user mode <https://nix.dev/manual/nix/stable/installation/multi-user>`_ in Nix, which may be the case for example when Nix is installed via ``pacman`` in Arch Linux. By default, users accessing Nix in multi-user mode are "unprivileged" and cannot use untrusted substituters. To change this, edit ``/etc/nix/nix.conf`` and add the following line (or append to the key if the key already exists): ::
and Nix tries to build some packages from source, this means that you are using `multi-user mode <https://nix.dev/manual/nix/stable/installation/multi-user>`_ in Nix, which may be the case for example when Nix is installed via ``pacman`` in Arch Linux. By default, users accessing Nix in multi-user mode are "unprivileged" and cannot use untrusted substituters. To change this, edit ``/etc/nix/nix.conf`` and add the following line (or append to the key if the key already exists): ::
trusted-substituters = https://nixbld.m-labs.hk
@ -143,9 +159,9 @@ This will add the substituter as a trusted substituter for all users using Nix.
Alternatively, add the following line: ::
trusted-users = <username> # Replace <username> with the user invoking `nix`
trusted-users = <username> # Replace <username> with your username
This will set your user as a trusted user, allowing the use of any untrusted substituters.
This will set your user as a trusted user, allowing you to specify untrusted substituters.
.. warning::
@ -216,9 +232,9 @@ Upgrading ARTIQ
Upgrading with Nix
^^^^^^^^^^^^^^^^^^
Run ``$ nix profile upgrade`` if you installed ARTIQ into your user profile. If you used a ``flake.nix`` shell environment, make a back-up copy of the ``flake.lock`` file to enable rollback, then run ``$ nix flake update`` and re-enter the environment with ``$ nix shell``.
Run ``$ nix profile upgrade`` if you installed ARTIQ into your user profile. If you use a ``flake.nix`` shell environment, make a back-up copy of the ``flake.lock`` file to enable rollback, then run ``$ nix flake update`` and re-enter the environment with ``$ nix shell``. If you use multiple flakes, each has its own ``flake.lock`` and can be updated or rolled back separately.
To rollback to the previous version, respectively use ``$ nix profile rollback`` or restore the backed-up version of the ``flake.lock`` file.
To rollback to the previous version, respectively use ``$ nix profile rollback`` or restore the backed-up versions of the ``flake.lock`` files.
Upgrading with MSYS2
^^^^^^^^^^^^^^^^^^^^

View File

@ -23,4 +23,4 @@ Core technologies employed include `Python <https://www.python.org/>`_, `Migen <
`Cite ARTIQ <http://dx.doi.org/10.5281/zenodo.51303>`_ as ``Bourdeauducq, Sébastien et al. (2016). ARTIQ 1.0. Zenodo. 10.5281/zenodo.51303``.
Copyright (C) 2014-2024 M-Labs Limited. Licensed under GNU LGPL version 3+.
Copyright (C) 2014-2025 M-Labs Limited. Licensed under GNU LGPL version 3+.

26
flake.lock generated
View File

@ -11,11 +11,11 @@
]
},
"locked": {
"lastModified": 1720768567,
"narHash": "sha256-3VoK7o5MtHtbHLrc6Pv+eQWFtaz5Gd/YWyV5TD3c5Ss=",
"lastModified": 1734270714,
"narHash": "sha256-7bzGn/hXLIsLQHGQsvo+uoIFUrw9DjXSlMC449BY4ME=",
"owner": "m-labs",
"repo": "artiq-comtools",
"rev": "f93570d8f2ed5a3cfb3e1c16ab00f2540551e994",
"rev": "7e3152314af8f5987370e33b347b2ec2697567ed",
"type": "github"
},
"original": {
@ -44,11 +44,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1733940404,
"narHash": "sha256-Pj39hSoUA86ZePPF/UXiYHHM7hMIkios8TYG29kQT4g=",
"lastModified": 1736012469,
"narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5d67ea6b4b63378b9c13be21e2ec9d1afc921713",
"rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d",
"type": "github"
},
"original": {
@ -113,11 +113,11 @@
"src-migen": {
"flake": false,
"locked": {
"lastModified": 1727677091,
"narHash": "sha256-Zg3SQnTwMM/VkOGKogbPyuCC2NhLy8HB2SPEUWWNgCU=",
"lastModified": 1735131698,
"narHash": "sha256-P4vaF+9iVekRAC2/mc9G7IwI6baBpPAxiDQ8uye4sAs=",
"owner": "m-labs",
"repo": "migen",
"rev": "c19ae9f8ae162ffe2d310a92bfce53ac2a821bc8",
"rev": "4c2ae8dfeea37f235b52acb8166f12acaaae4f7c",
"type": "github"
},
"original": {
@ -129,11 +129,11 @@
"src-misoc": {
"flake": false,
"locked": {
"lastModified": 1729234629,
"narHash": "sha256-TLsTCXV5AC2xh+bS7EhBVBKqdqIU3eKrnlWcFF9LtAM=",
"lastModified": 1736302987,
"narHash": "sha256-DMbaAxjtyZDlA20FTaObalH6ov2roE5Gsh6lVdnzPVY=",
"ref": "refs/heads/master",
"rev": "6085a312bca26adeca6584e37d08c8ba2e1d6e38",
"revCount": 2460,
"rev": "adb3f111750cb458f7e390bd242deb1d86ad69cb",
"revCount": 2463,
"submodules": true,
"type": "git",
"url": "https://github.com/m-labs/misoc.git"

View File

@ -604,11 +604,15 @@
# Read "Ok" line when remote successfully locked
read LOCK_OK
export ARTIQ_ROOT=`python -c "import artiq; print(artiq.__path__[0])"`/examples/kc705_nist_clock
export ARTIQ_LOW_LATENCY=1
artiq_rtiomap --device-db $ARTIQ_ROOT/device_db.py device_map.bin
artiq_mkfs -s ip `python -c "import artiq.examples.kc705_nist_clock.device_db as ddb; print(ddb.core_addr)"`/24 -f device_map device_map.bin kc705_nist_clock.config
artiq_flash -t kc705 -H rpi-1 storage -f kc705_nist_clock.config
artiq_flash -t kc705 -H rpi-1 -d ${packages.x86_64-linux.artiq-board-kc705-nist_clock}
sleep 30
export ARTIQ_ROOT=`python -c "import artiq; print(artiq.__path__[0])"`/examples/kc705_nist_clock
export ARTIQ_LOW_LATENCY=1
python -m unittest discover -v artiq.test.coredevice
)