1
0
forked from M-Labs/artiq

Compare commits

..

87 Commits

Author SHA1 Message Date
Pachigulla Ramtej
180a0fface Merge remote-tracking branch 'upstream/nac3' into nac3 2025-04-11 13:10:24 +08:00
fb08762a30 FFI: pass string directly 2025-04-01 16:34:35 +08:00
d4f54f0b68 flake: update dependencies 2025-04-01 16:34:24 +08:00
e0310caa2f test_embedding: _BoolListType nac3 fixes 2025-03-17 23:19:31 +08:00
a4e262bec6 ad9834: remove 2025-03-17 21:57:48 +08:00
14b62dc2f1 ksupport: invalidate I-cache after rebind
Rebinding modifies instructions directly, if binary is linked using nac3ld
2025-03-17 21:56:12 +08:00
e186a983d5 flake: update misoc 2025-03-14 14:58:50 +08:00
ed24bcfb69 Merge branch 'master' into nac3 2025-03-14 14:50:02 +08:00
3a9a10a41c exceptions: remove unused InternalError 2025-03-14 14:49:33 +08:00
49ed898900 ad9834: nac3 fixes 2025-03-14 14:40:50 +08:00
a264fa3b70 flake: update dependencies 2025-03-14 14:33:42 +08:00
adbe8c1ec0 Merge branch 'master' into nac3 2025-03-14 14:33:18 +08:00
72095fa2f6 flake: update dependencies 2025-03-14 12:15:12 +08:00
cdacbf71c5 flake: update dependencies 2025-03-14 12:11:24 +08:00
1533aba3ea cxp_grabber driver: add ROIViewer driver
cxp grabber: add ROIViewer startup support
cxp grabber: add ROIViewer frame download as PGM file
2025-03-14 12:10:04 +08:00
bd1759dbf9 cxp_grabber: add ROIViewer gateware support
core: add ROICropper to crop the ROI for downstream consumption
core: add ROIViewer to buffer the cropped pixels
rtio: connect pixel source to ROIViewer
2025-03-14 12:10:04 +08:00
7e1da9da72 RELEASE_NOTES: update 2025-03-13 14:17:47 +08:00
5e651308bb converter_spi: impl Error to str conversion 2025-03-13 13:56:37 +08:00
f213b8e1ca si5324: allow nack when soft reset
See 0d2f89db.
2025-03-13 13:56:37 +08:00
9fe9abd7fb satman: accept shuttler on satellite kasli
revert #2593 and update remote coremgmt drop link process
2025-03-11 11:45:05 +08:00
90eb59c54d i2c: make write nack an error, force to handle nacks 2025-03-11 11:42:43 +08:00
9a80818e97 doc: cxpgrabber docstring spellchecks 2025-03-11 11:41:34 +08:00
9f1a8e7d4f doc: Remove deprecated conf line 2025-03-11 11:41:34 +08:00
7f007b41cb flake: update nac3 2025-03-04 20:55:01 +08:00
9d2897de5f cxp_grabber driver doc: fix "note" formatting 2025-03-04 15:32:36 +08:00
1794c939b2 RELEASE_NOTES: update 2025-03-04 15:17:05 +08:00
519fdfa864 cxp grabber driver: flake8 fmt 2025-03-04 14:47:28 +08:00
c5473a8a02 cxp_grabber: driver allocate buffer in kernel 2025-03-04 14:47:28 +08:00
9c94ba7f89 doc: add cxp_grabber 2025-03-04 14:47:28 +08:00
76de902f2c RELEASE_NOTES: update 2025-03-04 14:47:28 +08:00
99bf703851 coredevice: add cxp grabber driver
cxp grabber: add sending cxp linktrigger
cxp grabber: add setting and gating ROIs
cxp grabber: add read/write 32bit register support
cxp grabber: add camera setting xml file download support
2025-03-04 14:47:28 +08:00
4b60cd86c5 artiq error: add cxp error 2025-03-04 14:47:28 +08:00
466f8943a4 flake: update dependencies 2025-03-03 19:48:26 +08:00
f98ce34480 flake: update misoc 2025-03-03 19:34:22 +08:00
8bd03f5bb4 dma: report last write to FIFO 2025-03-03 15:55:16 +08:00
06a2660f9c analyzer: revert lookahead buffer
CTI can be inferred by the last signal.
2025-03-03 15:55:16 +08:00
d0a2519b98 test_rtio: resume dma playback test for Kasli 2025-03-03 15:55:16 +08:00
690292f467 analyzer: align to sdram page 2025-03-03 15:55:16 +08:00
d80901d216 analyzer: perform burst write to main memory 2025-03-03 15:55:16 +08:00
3d22c50837 dma: perform burst read from main memory
with an additional FIFO to regulate data flow
2025-03-03 15:55:16 +08:00
6c5ff1e64f flake: update dependencies 2025-03-03 15:55:00 +08:00
5379bd9d38 test_rtio: close CommMgmt socket on test skip
Signed-off-by: Simon Renblad <srenblad@m-labs.hk>
2025-03-03 15:08:12 +08:00
66cec318a5 cxp test: add pixel parser unittest
test: use roi result to test pixel parser
2025-03-03 13:34:06 +08:00
2a0c064de7 cxp test: init and add mono pixel packet generator 2025-03-03 13:34:06 +08:00
a0886b602e rtio: add cxp grabber
cxp grabber: add and connect cxp phys, host core, stream decoder
cxp grabber: add and connect ROIs to stream decoder
cxp grabber: add rtio for line trigger, roi cfg and count gating
2025-03-03 13:34:06 +08:00
c0f5d8ba06 cxp grabber: add stream decoder, roi & host core 2025-03-03 13:34:06 +08:00
16d98279d5 cxp grabber: init and add frame handling modules
cxp grabber: add frame header reader and end of line marker
cxp grabber: add pixel unpacker & coordinate tracker
cxp grabber: add pixel parser that support mono8,10,12,14,16
2025-03-03 13:34:06 +08:00
bc94614395 flake: update dependencies 2025-03-03 13:34:06 +08:00
a0fff20855 flake: update nac3 2025-02-25 20:43:46 +08:00
6bce611ef8 tools: replace deprecated np.unicode_ 2025-02-19 22:32:21 +08:00
994f60edd5 gateware: remove redundant gtx_7series_init.py 2025-02-14 11:00:15 +08:00
47a13e4ede gtx: use gtx_7series_init.py from misoc 2025-02-14 11:00:15 +08:00
bb6ff80777 flake: update dependencies 2025-02-14 11:00:15 +08:00
7702987436 efc: reorder ADC pins on AFE if using standardized control
AFE control signals are first standardized in revision v1.3.
2025-02-13 16:39:51 +08:00
a2383def27 efc: allow specifying hw_rev for AFE individually 2025-02-13 16:39:51 +08:00
c9e2ba1536 flake: update dependencies 2025-02-13 14:28:12 +08:00
c1d3314ca1 kc705: define cdr clk 2025-02-13 14:26:57 +08:00
d1177836f2 gui/experiments: fix custom color application to preserve default Qt styling 2025-02-07 21:28:26 +08:00
d9cc35c724 gui/entries: split widget styling into separate color setting methods 2025-02-07 21:28:26 +08:00
c8d0ab9afe
Avoid quadratic behaviour in _unify_attribute (#2673)
ARTIQ maintains a list of all known instances of a particular class, and
after each attribute access, `_unify_attribute` is called to check all
these instances have this attribute (and it's the right type).

While the attribute type computation is cached, this is still O(nm)
(with n being the number of instances, and m the number of attribute
lookups). For something like ndscan's `FloatParamHandle.get`, you can
have a lot of both.

This commit changes `_unify_attribute` to only check the attributes of
new instances, effectively lowering the complexity to O(n). This
provides a small (5%) boost to compile times.

Signed-off-by: Jonathan Coates <jonathan.coates@oxionics.com>
2025-02-07 08:33:08 +08:00
ae12270363 Fix exceptions test for zc706
Signed-off-by: Egor Savkin <es@m-labs.hk>
2025-02-06 20:14:27 +08:00
a360628e95 core1: use repr C in attribute writeback 2025-02-06 20:14:07 +08:00
9558dd20e1 tests: Adjust regex to match Kasli-SoC exceptions as well
Signed-off-by: Egor Savkin <es@m-labs.hk>
2025-02-05 16:52:57 +08:00
d4bd4e5879 Update error_with_rpc to reflect changes in error message 2025-01-31 15:08:25 +00:00
c50f3a1c12
compiler: Report non-kernel __enter__/__exit__ methods
Signed-off-by: Jonathan Coates <jonathan.coates@oxionics.com>
2025-01-30 20:10:14 +00:00
0f0f030267 gtxinit: fix attributeerror for rx multilane 2025-01-27 20:38:28 +08:00
c1f2ff3717 experiments: implement LRU-based management for state cleanup
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2025-01-23 11:54:57 +08:00
7619dff08d experiments: fix color dictionary handling
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2025-01-23 11:54:36 +08:00
415895413c test_embedding: add bool list reg test 2025-01-21 08:59:13 +08:00
0da4dbf2f2 Update to llvmlite v0.44 as in MSYS2 packages
Signed-off-by: Egor Savkin <es@m-labs.hk>
2025-01-21 08:58:48 +08:00
ebb4058d09 compiler: fix bool list inferred as int list 2025-01-17 16:59:26 +08:00
occheung
2b48822fe3
targets: configure L2 line size (#2647) 2025-01-16 13:05:39 +08:00
8ce6048c96 flake: update dependencies 2025-01-16 13:03:05 +08:00
33c91d73bb drtio: fix RTIO channel name resolution for remote channels 2025-01-15 21:12:24 +08:00
d49ef555d1 flake: switch to Vivado 2024.2 2025-01-15 11:53:47 +08:00
0bd9bcf28c flake: update dependencies 2025-01-15 11:53:29 +08:00
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
50 changed files with 1986 additions and 1319 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

@ -6,24 +6,30 @@ Release notes
ARTIQ-9 (Unreleased)
--------------------
* Hardware support:
- CoaXPress grabber support on ZC706 with Hello-FPGA CXP 4R FMC card.
- Improved SDRAM memory controller and DMA cores puts Kasli DMA performance on par with
other platforms.
- DRTIO repeater support across GT/EEM. This enables Shuttler support on DRTIO satellites.
- Core device reflashing over the network through the new ``flash`` tool in ``artiq_coremgmt``.
It also supports configuring and reflashing DRTIO satellites over the DRTIO link.
- Fastino monitoring with Moninj.
- Zotino monitoring now displays the values in volts.
- Support for the ultra-low-cost EBAZ4205 Zynq-7000 control card, with core device driver
for the AD9834 DDS, tested with the ZonRi Technology Co., Ltd. AD9834-Module.
* Dashboard:
- Experiment windows can have different colors, selected by the user.
- Zotino monitoring now displays the values in volts.
- Schedule display columns can now be reordered and shown/hidden using the table
header context menu.
- State files are now automatically backed up upon successful loading.
* afws_client now uses the "happy eyeballs" algorithm (RFC 6555) for a faster and more
* ``afws_client`` now uses the "happy eyeballs" algorithm (RFC 6555) for a faster and more
reliable connection to the server.
* The Zadig driver installer was added to the MSYS2 offline installer.
* Fastino monitoring with Moninj is now supported.
* Qt6 support.
* Python 3.12 support.
* Compiler can now give automatic suggestions for ``kernel_invariants``.
* Idle kernels now restart when written with ``artiq_coremgmt`` and stop when erased/removed from config.
* New support for the EBAZ4205 Zynq-SoC control card.
* New core device driver for the AD9834 DDS, tested with the ZonRi Technology Co., Ltd. AD9834-Module.
* Support for coredevice reflashing through the new ``flash`` tool in ``artiq_coremgmt``.
* ``artiq_coremgmt`` now supports configuring satellites.
* Updated Rust support for Zynq-7000 firmware.
* Qt6 support.
* Python 3.12 support.
* The Zadig driver installer was added to the MSYS2 offline installer.
* ``artiq.coredevice.fmcdio_vhdci_eem`` has been removed.
ARTIQ-8

View File

@ -1,410 +0,0 @@
"""
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.core import Core
from artiq.coredevice.spi2 import *
from artiq.experiment import *
from artiq.language.core import *
from artiq.language.units import *
AD9834_B28 = 1 << 13
AD9834_HLB = 1 << 12
AD9834_FSEL = 1 << 11
AD9834_PSEL = 1 << 10
AD9834_PIN_SW = 1 << 9
AD9834_RESET = 1 << 8
AD9834_SLEEP1 = 1 << 7
AD9834_SLEEP12 = 1 << 6
AD9834_OPBITEN = 1 << 5
AD9834_SIGN_PIB = 1 << 4
AD9834_DIV2 = 1 << 3
AD9834_MODE = 1 << 1
AD9834_FREQ_REG_0 = 0b01 << 14
AD9834_FREQ_REG_1 = 0b10 << 14
FREQ_REGS = [AD9834_FREQ_REG_0, AD9834_FREQ_REG_1]
AD9834_PHASE_REG = 0b11 << 14
AD9834_PHASE_REG_0 = AD9834_PHASE_REG | (0 << 13)
AD9834_PHASE_REG_1 = AD9834_PHASE_REG | (1 << 13)
PHASE_REGS = [AD9834_PHASE_REG_0, AD9834_PHASE_REG_1]
@nac3
class AD9834:
"""
AD9834 DDS driver.
This class provides control for the DDS AD9834.
The driver utilizes bit-controlled :const:`AD9834_FSEL`, :const:`AD9834_PSEL`, and
:const:`AD9834_RESET`. To pin control ``FSELECT``, ``PSELECT``, and ``RESET`` set
:const:`AD9834_PIN_SW`. The ``ctrl_reg`` attribute is used to maintain the state of
the control register, enabling persistent management of various configurations.
:param spi_device: SPI bus device name.
:param spi_freq: SPI bus clock frequency (default: 10 MHz, max: 40 MHz).
:param clk_freq: DDS clock frequency (default: 75 MHz).
:param core_device: Core device name (default: "core").
"""
core: KernelInvariant[Core]
bus: KernelInvariant[SPIMaster]
spi_freq: KernelInvariant[float]
clk_freq: KernelInvariant[float]
ctrl_reg: Kernel[int32]
def __init__(
self, dmgr, spi_device, spi_freq=10. * MHz, clk_freq=75. * MHz, core_device="core"
):
self.core = dmgr.get(core_device)
self.bus = dmgr.get(spi_device)
assert spi_freq <= 40. * MHz, "SPI frequency exceeds maximum value of 40 MHz"
self.spi_freq = spi_freq
self.clk_freq = clk_freq
self.ctrl_reg = 0x0000 # Reset control register
@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_CLK_POLARITY | SPI_END, 16, self.spi_freq, 1)
self.enable_reset()
@kernel
def set_frequency_reg(self, freq_reg: int32, frequency: float):
"""
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.
"""
# NAC3TODO
#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 = int32((frequency * float(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: int32, word: int32):
"""
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.
"""
# NAC3TODO
#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: int32, word: int32):
"""
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.
"""
# NAC3TODO
#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: int32):
"""
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`.
"""
# NAC3TODO
#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: int32, phase: int32):
"""
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.
"""
# NAC3TODO
#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: int32):
"""
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`.
"""
# NAC3TODO
#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)
@kernel
def enable_reset(self):
"""
Enable the DDS reset.
This method sets :const:`AD9834_RESET`, putting the AD9834 into a reset state.
While in this state, the digital-to-analog converter (DAC) is not operational.
This method should be called during initialization or when a reset is required
to reinitialize the device and ensure proper operation.
"""
self.ctrl_reg |= AD9834_RESET
self.write(self.ctrl_reg)
@kernel
def output_enable(self):
"""
Disable the DDS reset and start signal generation.
This method clears :const:`AD9834_RESET`, allowing the AD9834 to begin generating
signals. Once this method is called, the device will resume normal operation and
output the generated waveform.
This method should be called after configuration of the frequency and phase
settings to activate the output.
"""
self.ctrl_reg &= ~AD9834_RESET
self.write(self.ctrl_reg)
@kernel
def sleep(self, dac_pd: bool = False, clk_dis: bool = False):
"""
Put the AD9834 into sleep mode by selectively powering down the DAC and/or disabling
the internal clock.
This method controls the sleep mode behavior of the AD9834 by setting or clearing the
corresponding bits in the control register. Two independent options can be specified:
:param dac_pd: Set to ``True`` to power down the DAC (:const:`AD9834_SLEEP12` is set).
``False`` will leave the DAC active.
:param clk_dis: Set to ``True`` to disable the internal clock (:const:`AD9834_SLEEP1` is set).
``False`` will keep the clock running.
Both options can be enabled independently, allowing the DAC and/or clock to be powered down as needed.
The method updates the control register and writes the changes to the AD9834 device.
"""
if dac_pd:
self.ctrl_reg |= AD9834_SLEEP12
else:
self.ctrl_reg &= ~AD9834_SLEEP12
if clk_dis:
self.ctrl_reg |= AD9834_SLEEP1
else:
self.ctrl_reg &= ~AD9834_SLEEP1
self.write(self.ctrl_reg)
@kernel
def awake(self):
"""
Exit sleep mode and restore normal operation.
This method brings the AD9834 out of sleep mode by clearing any DAC power-down or
internal clock disable settings. It calls :meth:`sleep()` with no arguments,
effectively setting both ``dac_powerdown`` and ``internal_clk_disable`` to ``False``.
The device will resume generating output based on the current frequency and phase
settings.
"""
self.sleep()
@kernel
def config_sign_bit_out(
self,
high_z: bool = False,
msb_2: bool = False,
msb: bool = False,
comp_out: bool = False,
):
"""
Configure the ``SIGN BIT OUT`` pin for various output modes.
This method sets the output mode for the ``SIGN BIT OUT`` pin of the AD9834 based on the provided flags.
The user can enable one of several modes, including high impedance, MSB/2 output, MSB output,
or comparator output. These modes are mutually exclusive, and passing ``True`` to one flag will
configure the corresponding mode, while other flags should be left as ``False``.
:param high_z: Set to ``True`` to place the ``SIGN BIT OUT`` pin in high impedance (disabled) mode.
:param msb_2: Set to ``True`` to output DAC Data MSB divided by 2 on the ``SIGN BIT OUT`` pin.
:param msb: Set to ``True`` to output DAC Data MSB on the ``SIGN BIT OUT`` pin.
:param comp_out: Set to ``True`` to output the comparator signal on the ``SIGN BIT OUT`` pin.
Only one flag should be set to ``True`` at a time. If no valid mode is selected, the ``SIGN BIT OUT``
pin will default to high impedance mode.
The method updates the control register with the appropriate configuration and writes it to the AD9834.
"""
if high_z:
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
elif msb:
self.ctrl_reg |= AD9834_OPBITEN
self.ctrl_reg &= ~AD9834_MODE
self.ctrl_reg &= ~AD9834_SIGN_PIB
self.ctrl_reg |= AD9834_DIV2
elif comp_out:
self.ctrl_reg |= AD9834_OPBITEN
self.ctrl_reg &= ~AD9834_MODE
self.ctrl_reg |= AD9834_SIGN_PIB
self.ctrl_reg |= AD9834_DIV2
else:
self.ctrl_reg &= ~AD9834_OPBITEN
self.write(self.ctrl_reg)
@kernel
def enable_triangular_waveform(self):
"""
Enable triangular waveform generation.
This method configures the AD9834 to output a triangular waveform. It does so
by clearing :const:`AD9834_OPBITEN` in the control register and setting :const:`AD9834_MODE`.
Once this method is called, the AD9834 will begin generating a triangular waveform
at the frequency set for the selected frequency register.
This method should be called when a triangular waveform is desired for signal
generation. Ensure that the frequency is set appropriately before invoking this method.
"""
self.ctrl_reg &= ~AD9834_OPBITEN
self.ctrl_reg |= AD9834_MODE
self.write(self.ctrl_reg)
@kernel
def disable_triangular_waveform(self):
"""
Disable triangular waveform generation.
This method disables the triangular waveform output by clearing :const:`AD9834_MODE`.
After invoking this method, the AD9834 will cease generating a triangular waveform.
The device can then be configured to output other waveform types if needed.
This method should be called when switching to a different waveform type or
when the triangular waveform is no longer required.
"""
self.ctrl_reg &= ~AD9834_MODE
self.write(self.ctrl_reg)
@kernel
def write(self, data: int32):
"""
Write a 16-bit word to the AD9834.
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 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.
"""
self.bus.write(data << 16)

View File

@ -559,9 +559,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

@ -824,8 +824,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

@ -0,0 +1,299 @@
from numpy import array, int32, int64, uint8, uint16, iinfo
from artiq.language.core import syscall, kernel
from artiq.language.types import TInt32, TNone, TList
from artiq.coredevice.rtio import rtio_output, rtio_input_timestamped_data
from artiq.coredevice.grabber import OutOfSyncException, GrabberTimeoutException
from artiq.experiment import *
@syscall(flags={"nounwind"})
def cxp_download_xml_file(buffer: TList(TInt32)) -> TInt32:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind"})
def cxp_read32(addr: TInt32) -> TInt32:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind"})
def cxp_write32(addr: TInt32, val: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind"})
def cxp_start_roi_viewer(x0: TInt32, x1: TInt32, y0: TInt32, y1: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind"})
def cxp_download_roi_viewer_frame(
buffer: TList(TInt64),
) -> TTuple([TInt32, TInt32, TInt32]):
raise NotImplementedError("syscall not simulated")
class CXPGrabber:
"""Driver for the CoaXPress Grabber camera interface."""
kernel_invariants = {
"core",
"channel",
"trigger_ch",
"roi_config_ch",
"roi_gating_ch",
"sentinel",
}
def __init__(self, dmgr, channel, core_device="core", count_width=31):
self.core = dmgr.get(core_device)
self.channel = channel
self.trigger_ch = channel
self.roi_config_ch = channel + 1
self.roi_gating_ch = channel + 2
# This value is inserted by the gateware to mark the start of a series of
# ROI engine outputs for one video frame.
self.sentinel = int32(int64(2**count_width))
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [
(channel, "Trigger"),
(channel + 1, "ROI coordinates"),
(channel + 2, "ROI mask"),
]
@kernel
def send_cxp_linktrigger(self, linktrigger, extra_linktrigger=False):
"""
Send CoaXpress fixed-latency linktrigger to camera
:param linktrigger: Set linktrigger type:
0-1 is available, when extra_linktrigger is False
0-3 is available, when extra_linktrigger is True
In CXP v1.x, linktrigger0 was called `rising edge` and linktrigger1 `falling edge`
:param extra_linktrigger: Boolean, set to True when ExtraLsTriggerEnable is set to 1 on camera
"""
extra_linktrigger_mask = 1 if extra_linktrigger else 0
rtio_output(self.trigger_ch << 8, linktrigger << 1 | extra_linktrigger_mask)
@kernel
def setup_roi(self, n, x0, y0, x1, y1):
"""
Defines the coordinates of a ROI.
The coordinates are set around the current position of the RTIO time
cursor.
The user must keep the ROI engine disabled for a duration of more
than one video frame after calling this function, as the output
generated for that video frame is undefined.
Advances the timeline by 4 coarse RTIO cycles.
"""
c = int64(self.core.ref_multiplier)
rtio_output(self.roi_config_ch << 8 | (4 * n + 0), x0)
delay_mu(c)
rtio_output(self.roi_config_ch << 8 | (4 * n + 1), y0)
delay_mu(c)
rtio_output(self.roi_config_ch << 8 | (4 * n + 2), x1)
delay_mu(c)
rtio_output(self.roi_config_ch << 8 | (4 * n + 3), y1)
delay_mu(c)
@kernel
def gate_roi(self, mask):
"""
Defines which ROI engines produce input events.
At the end of each video frame, the output from each ROI engine that
has been enabled by the mask is enqueued into the RTIO input FIFO.
This function sets the mask at the current position of the RTIO time
cursor.
Setting the mask using this function is atomic; in other words,
if the system is in the middle of processing a frame and the mask
is changed, the processing will complete using the value of the mask
that it started with.
:param mask: bitmask enabling or disabling each ROI engine.
"""
rtio_output(self.roi_gating_ch << 8, mask)
@kernel
def gate_roi_pulse(self, mask, dt):
"""
Sets a temporary mask for the specified duration (in seconds), then
disables all ROI engines.
"""
self.gate_roi(mask)
delay(dt)
self.gate_roi(0)
@kernel
def input_mu(self, data, timeout_mu=-1):
"""
Retrieves the accumulated values for one frame from the ROI engines.
Blocks until values are available or timeout is reached.
The input list must be a list of integers of the same length as there
are enabled ROI engines. This method replaces the elements of the
input list with the outputs of the enabled ROI engines, sorted by
number.
If the number of elements in the list does not match the number of
ROI engines that produced output, an exception will be raised during
this call or the next.
If the timeout is reached before data is available, the exception
:exc:`artiq.coredevice.grabber.GrabberTimeoutException` is raised.
:param timeout_mu: Timestamp at which a timeout will occur. Set to -1
(default) to disable timeout.
"""
timestamp, sentinel = rtio_input_timestamped_data(
timeout_mu, self.roi_gating_ch
)
if timestamp == -1:
raise GrabberTimeoutException("Timeout before Grabber frame available")
if sentinel != self.sentinel:
raise OutOfSyncException
for i in range(len(data)):
timestamp, roi_output = rtio_input_timestamped_data(
timeout_mu, self.roi_gating_ch
)
if roi_output == self.sentinel:
raise OutOfSyncException
if timestamp == -1:
raise GrabberTimeoutException(
"Timeout retrieving ROIs (attempting to read more ROIs than enabled?)"
)
data[i] = roi_output
@kernel
def read32(self, address: TInt32) -> TInt32:
"""
Read a 32-bit value from camera register
.. warning:: This is NOT a real-time operation.
:param address: 32-bit register address to read from
:returns: 32-bit value from register
"""
return cxp_read32(address)
@kernel
def write32(self, address: TInt32, value: TInt32):
"""
Write a 32-bit value to camera register
.. warning:: This is NOT a real-time operation.
:param address: 32-bit register address to write to
:param value: 32-bit value to be written
"""
cxp_write32(address, value)
@kernel
def download_local_xml(self, file_path, buffer_size=102400):
"""
Downloads the XML setting file to PC from the camera if available.
The file format may be .zip or .xml depending on the camera model.
.. warning:: This is NOT a real-time operation.
:param file_path: a relative path on PC
:param buffer_size: size of read buffer expressed in bytes; must be a multiple of 4
"""
buffer = [0] * (buffer_size // 4)
size_read = cxp_download_xml_file(buffer)
self._write_file(buffer[:size_read], file_path)
@rpc
def _write_file(self, data, file_path):
"""
Write big endian encoded data into a file
:param data: a list of 32-bit integers
:param file_path: a relative path on PC
"""
byte_arr = bytearray()
for d in data:
byte_arr += d.to_bytes(4, "big", signed=True)
with open(file_path, "wb") as binary_file:
binary_file.write(byte_arr)
@kernel
def start_roi_viewer(self, x0, x1, y0, y1):
"""
Defines the coordinates of ROI viewer and start the capture.
Unlike :exc:`setup_roi`, ROI viewer has a maximum size limit of 4096 pixels.
.. warning:: This is NOT a real-time operation.
"""
cxp_start_roi_viewer(x0, x1, y0, y1)
@kernel
def download_roi_viewer_frame(self, file_path):
"""
Downloads the ROI viewer frame as a PGM file to PC.
The user must :exc:`start_roi_viewer` and trigger the camera before the frame is avaiable.
.. warning:: This is NOT a real-time operation.
:param file_path: a relative path on PC
"""
buffer = [0] * 1024
width, height, pixel_width = cxp_download_roi_viewer_frame(buffer)
self._write_pgm(buffer, width, height, pixel_width, file_path)
@rpc
def _write_pgm(self, data, width, height, pixel_width, file_path):
"""
Write pixel data into a PGM file.
:param data: a list of 64-bit integers
:param file_path: a relative path on PC
"""
if ".pgm" not in file_path.lower():
raise ValueError("The file extension must be .pgm")
pixels = []
width_aligned = (width + 3) & (~3)
for d in data[: width_aligned * height // 4]:
pixels += [
d & 0xFFFF,
(d >> 16) & 0xFFFF,
(d >> 32) & 0xFFFF,
(d >> 48) & 0xFFFF,
]
if pixel_width == 8:
dtype = uint8
else:
dtype = uint16
# pad to 16-bit for compatibility, as most software can only read 8, 16-bit PGM
pixels = [p << (16 - pixel_width) for p in pixels]
# trim the frame if the width is not multiple of 4
frame = array([pixels], dtype).reshape((height, width_aligned))[:, :width]
# save as PGM binary variant
# https://en.wikipedia.org/wiki/Netpbm#Description
with open(file_path, "wb") as file:
file.write(f"P5\n{width} {height}\n{iinfo(dtype).max}\n".encode("ASCII"))
if dtype == uint8:
file.write(frame.tobytes())
else:
# PGM use big endian
file.write(frame.astype(">u2").tobytes())

View File

@ -2,6 +2,7 @@ import builtins
from numpy.linalg import LinAlgError
from artiq.language.core import nac3, UnwrapNoneError
"""
This file provides class definition for all the exceptions declared in `EmbeddingMap` in `artiq.language.embedding_map`
@ -9,6 +10,7 @@ For Python builtin exceptions, use the `builtins` module
For ARTIQ specific exceptions, inherit from `Exception` class
"""
AssertionError = builtins.AssertionError
AttributeError = builtins.AttributeError
IndexError = builtins.IndexError
@ -23,6 +25,7 @@ ValueError = builtins.ValueError
ZeroDivisionError = builtins.ZeroDivisionError
OSError = builtins.OSError
@nac3
class RTIOUnderflow(Exception):
"""Raised when the CPU or DMA core fails to submit a RTIO event early
@ -78,6 +81,7 @@ class ClockFailure(Exception):
"""Raised when RTIO PLL has lost lock."""
artiq_builtin = True
@nac3
class I2CError(Exception):
"""Raised when a I2C transaction fails."""
@ -90,7 +94,13 @@ class SPIError(Exception):
artiq_builtin = True
@nac3
class UnwrapNoneError(Exception):
"""Raised when unwrapping a none Option."""
artiq_builtin = True
@nac3
class CXPError(Exception):
"""Raised when CXP transaction fails."""
artiq_builtin = True

View File

@ -79,18 +79,12 @@ class _ArgumentEditor(EntryTreeWidget):
argument["desc"] = procdesc
argument["state"] = state
self.update_argument(name, argument)
self.dock.apply_colors()
self.dock.apply_window_color()
def apply_color(self, palette, color):
self.setPalette(palette)
for child in self.findChildren(QtWidgets.QWidget):
child.setPalette(palette)
child.setAutoFillBackground(True)
items = self.findItems("*",
QtCore.Qt.MatchFlag.MatchWildcard | QtCore.Qt.MatchFlag.MatchRecursive)
for item in items:
for column in range(item.columnCount()):
item.setBackground(column, QtGui.QColor(color))
def apply_color(self, color):
self.set_background_color(color)
self.set_gradient_color(color)
self.set_group_color(color)
# Hooks that allow user-supplied argument editors to react to imminent user
# actions. Here, we always keep the manager-stored submission arguments
@ -329,7 +323,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
self.argeditor = editor_class(self.manager, self, self.expurl)
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
self.argeditor.restore_state(argeditor_state)
self.apply_colors()
self.apply_window_color()
def contextMenuEvent(self, event):
menu = QtWidgets.QMenu(self)
@ -353,22 +347,25 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
title=f"Select {key.replace('_', ' ').title()} color")
if color.isValid():
self.manager.set_color(self.expurl, key, color.name())
self.apply_colors()
if key == "title_bar":
self.apply_title_bar_color()
else:
self.apply_window_color()
def apply_colors(self):
def apply_title_bar_color(self):
colors = self.manager.get_colors(self.expurl)
if colors is None:
palette = QtWidgets.QApplication.palette()
colors = {
"window": palette.color(QtGui.QPalette.ColorRole.Window).name(),
"title_bar": palette.color(QtGui.QPalette.ColorRole.Highlight).name(),
}
self.manager.colors[self.expurl] = colors
if not colors:
return
self.setStyle(_ColoredTitleBar(colors["title_bar"]))
def apply_window_color(self):
colors = self.manager.get_colors(self.expurl)
if not colors:
return
colors["window_text"] = "#000000" if QtGui.QColor(
colors["window"]).lightness() > 128 else "#FFFFFF"
self.modify_palette(colors)
self.setStyle(_ColoredTitleBar(colors["title_bar"]))
self.argeditor.apply_color(self.palette(), (colors["window"]))
self.argeditor.apply_color(colors["window"])
def modify_palette(self, colors):
palette = self.palette()
@ -381,8 +378,13 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
self.setPalette(palette)
def reset_colors(self):
colors = self.manager.get_colors(self.expurl)
if not colors:
return
self.manager.reset_colors(self.expurl)
self.apply_colors()
self.setStyle(None)
self.setPalette(QtWidgets.QApplication.palette())
self.argeditor.apply_color(None)
async def _recompute_sched_options_task(self):
try:
@ -511,6 +513,24 @@ class _QuickOpenDialog(QtWidgets.QDialog):
self.close()
class _LRUDict(OrderedDict):
def __init__(self, size_limit=1000):
super().__init__()
self.size_limit = size_limit
def __setitem__(self, key, value):
if key in self:
self.pop(key)
super().__setitem__(key, value)
if len(self) > self.size_limit:
self.popitem(last=False)
def __getitem__(self, key):
value = super().__getitem__(key)
self.move_to_end(key)
return value
class ExperimentManager:
#: Global registry for custom argument editor classes, indexed by the experiment
#: `argument_ui` string; can be populated by dashboard plugins such as ndscan.
@ -525,12 +545,12 @@ class ExperimentManager:
self.schedule_ctl = schedule_ctl
self.experiment_db_ctl = experiment_db_ctl
self.dock_states = dict()
self.submission_scheduling = dict()
self.submission_options = dict()
self.submission_arguments = dict()
self.argument_ui_names = dict()
self.colors = dict()
self.dock_states = _LRUDict()
self.submission_scheduling = _LRUDict()
self.submission_options = _LRUDict()
self.submission_arguments = _LRUDict()
self.argument_ui_names = _LRUDict()
self.colors = _LRUDict()
self.datasets = dict()
dataset_sub.add_setmodel_callback(self.set_dataset_model)
@ -563,7 +583,7 @@ class ExperimentManager:
self.colors[expurl][key] = value
def get_colors(self, expurl):
return self.colors.get(expurl)
return self.colors.get(expurl, {})
def reset_colors(self, expurl):
if expurl in self.colors:
@ -678,7 +698,12 @@ class ExperimentManager:
self.open_experiments[expurl] = dock
dock.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
self.main_window.centralWidget().addSubWindow(dock)
dock.apply_colors()
colors = self.get_colors(expurl)
if colors:
if "title_bar" in colors:
dock.apply_title_bar_color()
if "window" in colors:
dock.apply_window_color()
dock.show()
dock.sigClosed.connect(partial(self.on_dock_closed, expurl))
if expurl in self.dock_states:
@ -790,24 +815,24 @@ class ExperimentManager:
for expurl, dock in self.open_experiments.items():
self.dock_states[expurl] = dock.save_state()
return {
"scheduling": self.submission_scheduling,
"options": self.submission_options,
"arguments": self.submission_arguments,
"docks": self.dock_states,
"argument_uis": self.argument_ui_names,
"scheduling": dict(self.submission_scheduling),
"options": dict(self.submission_options),
"arguments": dict(self.submission_arguments),
"docks": dict(self.dock_states),
"argument_uis": dict(self.argument_ui_names),
"open_docks": set(self.open_experiments.keys()),
"colors": self.colors
"colors": dict(self.colors)
}
def restore_state(self, state):
if self.open_experiments:
raise NotImplementedError
self.dock_states = state["docks"]
self.submission_scheduling = state["scheduling"]
self.submission_options = state["options"]
self.submission_arguments = state["arguments"]
self.argument_ui_names = state.get("argument_uis", {})
self.colors = state.get("colors", {})
self.dock_states.update(state["docks"])
self.submission_scheduling.update(state["scheduling"])
self.submission_options.update(state["options"])
self.submission_arguments.update(state["arguments"])
self.argument_ui_names.update(state.get("argument_uis", {}))
self.colors.update(state.get("colors", {}))
for expurl in state["open_docks"]:
self.open_experiment(expurl)

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

@ -329,7 +329,7 @@ extern fn stop_fn(_version: c_int,
}
// Must be kept in sync with `nac3artiq::Nac3::new`
static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [
static EXCEPTION_ID_LOOKUP: [(&str, u32); 23] = [
("RTIOUnderflow", 0),
("RTIOOverflow", 1),
("RTIODestinationUnreachable", 2),
@ -352,6 +352,7 @@ static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [
("ZeroDivisionError", 19),
("LinAlgError", 20),
("UnwrapNoneError", 21),
("CXPError", 22)
];
pub fn get_exception_id(name: &str) -> u32 {

View File

@ -209,7 +209,7 @@ fn terminate(exceptions: &'static [Option<eh_artiq::Exception<'static>>],
loop {}
}
extern fn cache_get<'a>(key: &CSlice<u8>) -> *const CSlice<'a, i32> {
extern fn cache_get<'a>(key: CSlice<u8>) -> *const CSlice<'a, i32> {
send(&CacheGetRequest {
key: str::from_utf8(key.as_ref()).unwrap()
});
@ -218,7 +218,7 @@ extern fn cache_get<'a>(key: &CSlice<u8>) -> *const CSlice<'a, i32> {
})
}
extern "C-unwind" fn cache_put(key: &CSlice<u8>, list: &CSlice<i32>) {
extern "C-unwind" fn cache_put(key: CSlice<u8>, list: &CSlice<i32>) {
send(&CachePutRequest {
key: str::from_utf8(key.as_ref()).unwrap(),
value: list.as_ref()
@ -251,7 +251,7 @@ fn dma_record_flush() {
}
}
extern "C-unwind" fn dma_record_start(name: &CSlice<u8>) {
extern "C-unwind" fn dma_record_start(name: CSlice<u8>) {
let name = str::from_utf8(name.as_ref()).unwrap();
unsafe {
@ -264,6 +264,7 @@ extern "C-unwind" fn dma_record_start(name: &CSlice<u8>) {
dma_record_output as *const () as u32).unwrap();
library.rebind(b"rtio_output_wide",
dma_record_output_wide as *const () as u32).unwrap();
board_misoc::cache::flush_cpu_icache();
DMA_RECORDER.active = true;
send(&DmaRecordStart(name));
@ -283,6 +284,7 @@ extern "C-unwind" fn dma_record_stop(duration: i64, enable_ddma: bool) {
rtio::output as *const () as u32).unwrap();
library.rebind(b"rtio_output_wide",
rtio::output_wide as *const () as u32).unwrap();
board_misoc::cache::flush_cpu_icache();
DMA_RECORDER.active = false;
send(&DmaRecordStop {
@ -359,7 +361,7 @@ extern fn dma_record_output_wide(target: i32, words: &CSlice<i32>) {
}
}
extern fn dma_erase(name: &CSlice<u8>) {
extern fn dma_erase(name: CSlice<u8>) {
let name = str::from_utf8(name.as_ref()).unwrap();
send(&DmaEraseRequest { name: name });
@ -372,7 +374,7 @@ struct DmaTrace {
uses_ddma: bool,
}
extern "C-unwind" fn dma_retrieve(name: &CSlice<u8>) -> DmaTrace {
extern "C-unwind" fn dma_retrieve(name: CSlice<u8>) -> DmaTrace {
let name = str::from_utf8(name.as_ref()).unwrap();
send(&DmaRetrieveRequest { name: name });
@ -562,12 +564,14 @@ extern "C-unwind" fn subkernel_await_message(id: i32, timeout: i64, tags: &CSlic
}
unsafe fn attribute_writeback(typeinfo: *const ()) {
#[repr(C)]
struct Attr {
offset: usize,
tag: CSlice<'static, u8>,
name: CSlice<'static, u8>
}
#[repr(C)]
struct Type {
attributes: *const *const Attr,
objects: *const *const ()

View File

@ -90,15 +90,24 @@ fn map_frequency_settings(settings: &FrequencySettings) -> Result<FrequencySetti
fn write(reg: u8, val: u8) -> Result<()> {
i2c::start(BUSNO).unwrap();
if !i2c::write(BUSNO, ADDRESS << 1).unwrap() {
return Err("Si5324 failed to ack write address")
}
if !i2c::write(BUSNO, reg).unwrap() {
return Err("Si5324 failed to ack register")
}
if !i2c::write(BUSNO, val).unwrap() {
return Err("Si5324 failed to ack value")
}
i2c::write(BUSNO, ADDRESS << 1).map_err(|err|
match err {
i2c::Error::Nack => "Si5324 failed to ack write address",
err => err.into()
}
)?;
i2c::write(BUSNO, reg).map_err(|err|
match err {
i2c::Error::Nack => "Si5324 failed to ack register",
err => err.into()
}
)?;
i2c::write(BUSNO, val).map_err(|err|
match err {
i2c::Error::Nack => "Si5324 failed to ack value",
err => err.into()
}
)?;
i2c::stop(BUSNO).unwrap();
Ok(())
}
@ -106,29 +115,47 @@ fn write(reg: u8, val: u8) -> Result<()> {
#[cfg(si5324_soft_reset)]
fn write_no_ack_value(reg: u8, val: u8) -> Result<()> {
i2c::start(BUSNO).unwrap();
if !i2c::write(BUSNO, ADDRESS << 1).unwrap() {
return Err("Si5324 failed to ack write address")
}
if !i2c::write(BUSNO, reg).unwrap() {
return Err("Si5324 failed to ack register")
}
i2c::write(BUSNO, val).unwrap();
i2c::write(BUSNO, ADDRESS << 1).map_err(|err|
match err {
i2c::Error::Nack => "Si5324 failed to ack write address",
err => err.into()
}
)?;
i2c::write(BUSNO, reg).map_err(|err|
match err {
i2c::Error::Nack => "Si5324 failed to ack register",
err => err.into()
}
)?;
match i2c::write(BUSNO, val) {
Ok(()) | Err(i2c::Error::Nack) => Ok(()),
err => err
}?;
i2c::stop(BUSNO).unwrap();
Ok(())
}
fn read(reg: u8) -> Result<u8> {
i2c::start(BUSNO).unwrap();
if !i2c::write(BUSNO, ADDRESS << 1).unwrap() {
return Err("Si5324 failed to ack write address")
}
if !i2c::write(BUSNO, reg).unwrap() {
return Err("Si5324 failed to ack register")
}
i2c::write(BUSNO, ADDRESS << 1).map_err(|err|
match err {
i2c::Error::Nack => "Si5324 failed to ack write address",
err => err.into()
}
)?;
i2c::write(BUSNO, reg).map_err(|err|
match err {
i2c::Error::Nack => "Si5324 failed to ack register",
err => err.into()
}
)?;
i2c::restart(BUSNO).unwrap();
if !i2c::write(BUSNO, (ADDRESS << 1) | 1).unwrap() {
return Err("Si5324 failed to ack read address")
}
i2c::write(BUSNO, (ADDRESS << 1) | 1).map_err(|err|
match err {
i2c::Error::Nack => "Si5324 failed to ack read address",
err => err.into()
}
)?;
let val = i2c::read(BUSNO, false).unwrap();
i2c::stop(BUSNO).unwrap();
Ok(val)

View File

@ -1,12 +1,27 @@
pub enum Error {
NoSPI,
InvalidBus,
OtherError,
}
impl From<Error> for &str {
fn from(err: Error) -> &'static str {
match err {
Error::NoSPI => "SPI not supported",
Error::InvalidBus => "Invalid SPI bus",
Error::OtherError => "other error",
}
}
}
#[cfg(has_converter_spi)]
mod imp {
use board_misoc::csr;
use super::Error;
const INVALID_BUS: &'static str = "Invalid SPI bus";
pub fn set_config(busno: u8, flags: u8, length: u8, div: u8, cs: u8) -> Result<(), &'static str> {
pub fn set_config(busno: u8, flags: u8, length: u8, div: u8, cs: u8) -> Result<(), Error> {
if busno != 0 {
return Err(INVALID_BUS)
return Err(Error::InvalidBus)
}
unsafe {
while csr::converter_spi::writable_read() == 0 {}
@ -33,9 +48,9 @@ mod imp {
Ok(())
}
pub fn write(busno: u8, data: u32) -> Result<(), &'static str> {
pub fn write(busno: u8, data: u32) -> Result<(), Error> {
if busno != 0 {
return Err(INVALID_BUS)
return Err(Error::InvalidBus)
}
unsafe {
while csr::converter_spi::writable_read() == 0 {}
@ -44,9 +59,9 @@ mod imp {
Ok(())
}
pub fn read(busno: u8) -> Result<u32, &'static str> {
pub fn read(busno: u8) -> Result<u32, Error> {
if busno != 0 {
return Err(INVALID_BUS)
return Err(Error::InvalidBus)
}
Ok(unsafe {
while csr::converter_spi::writable_read() == 0 {}
@ -57,9 +72,11 @@ mod imp {
#[cfg(not(has_converter_spi))]
mod imp {
pub fn set_config(_busno: u8, _flags: u8, _length: u8, _div: u8, _cs: u8) -> Result<(), ()> { Err(()) }
pub fn write(_busno: u8,_data: u32) -> Result<(), ()> { Err(()) }
pub fn read(_busno: u8,) -> Result<u32, ()> { Err(()) }
use super::Error;
pub fn set_config(_busno: u8, _flags: u8, _length: u8, _div: u8, _cs: u8) -> Result<(), Error> { Err(Error::NoSPI) }
pub fn write(_busno: u8,_data: u32) -> Result<(), Error> { Err(Error::NoSPI) }
pub fn read(_busno: u8,) -> Result<u32, Error> { Err(Error::NoSPI) }
}
pub use self::imp::*;

View File

@ -1,8 +1,34 @@
#[derive(Debug)]
pub enum Error {
NoI2C,
InvalidBus,
Nack,
SCLLow,
SDALow,
ArbitrationLost,
IOExpanderError,
OtherError,
}
impl From<Error> for &str {
fn from(err: Error) -> &'static str {
match err {
Error::NoI2C => "I2C not supported",
Error::InvalidBus => "Invalid I2C bus",
Error::Nack => "I2C write was not ACKed",
Error::SCLLow => "SCL stuck low",
Error::SDALow => "SDA stuck low",
Error::ArbitrationLost => "SDA arbitration lost",
Error::IOExpanderError => "I2C IO Expander error",
Error::OtherError => "other error",
}
}
}
#[cfg(has_i2c)]
mod imp {
use super::super::{csr, clock};
const INVALID_BUS: &'static str = "Invalid I2C bus";
use super::Error;
fn half_period() { clock::spin_us(100) }
fn sda_bit(busno: u8) -> u8 { 1 << (2 * busno + 1) }
@ -52,7 +78,7 @@ mod imp {
}
}
pub fn init() -> Result<(), &'static str> {
pub fn init() -> Result<(), Error> {
for busno in 0..csr::CONFIG_I2C_BUS_COUNT {
let busno = busno as u8;
scl_oe(busno, false);
@ -74,26 +100,26 @@ mod imp {
}
if !sda_i(busno) {
return Err("SDA is stuck low and doesn't get unstuck");
return Err(Error::SDALow);
}
if !scl_i(busno) {
return Err("SCL is stuck low and doesn't get unstuck");
return Err(Error::SCLLow);
}
// postcondition: SCL and SDA high
}
Ok(())
}
pub fn start(busno: u8) -> Result<(), &'static str> {
pub fn start(busno: u8) -> Result<(), Error> {
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
return Err(INVALID_BUS)
return Err(Error::InvalidBus)
}
// precondition: SCL and SDA high
if !scl_i(busno) {
return Err("SCL is stuck low and doesn't get unstuck");
return Err(Error::SCLLow);
}
if !sda_i(busno) {
return Err("SDA arbitration lost");
return Err(Error::ArbitrationLost);
}
sda_oe(busno, true);
half_period();
@ -102,9 +128,9 @@ mod imp {
Ok(())
}
pub fn restart(busno: u8) -> Result<(), &'static str> {
pub fn restart(busno: u8) -> Result<(), Error> {
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
return Err(INVALID_BUS)
return Err(Error::InvalidBus)
}
// precondition SCL and SDA low
sda_oe(busno, false);
@ -116,9 +142,9 @@ mod imp {
Ok(())
}
pub fn stop(busno: u8) -> Result<(), &'static str> {
pub fn stop(busno: u8) -> Result<(), Error> {
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
return Err(INVALID_BUS)
return Err(Error::InvalidBus)
}
// precondition: SCL and SDA low
half_period();
@ -127,15 +153,15 @@ mod imp {
sda_oe(busno, false);
half_period();
if !sda_i(busno) {
return Err("SDA arbitration lost");
return Err(Error::ArbitrationLost);
}
// postcondition: SCL and SDA high
Ok(())
}
pub fn write(busno: u8, data: u8) -> Result<bool, &'static str> {
pub fn write(busno: u8, data: u8) -> Result<(), Error> {
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
return Err(INVALID_BUS)
return Err(Error::InvalidBus)
}
// precondition: SCL and SDA low
// MSB first
@ -156,12 +182,16 @@ mod imp {
sda_oe(busno, true);
// postcondition: SCL and SDA low
Ok(ack)
if !ack {
return Err(Error::Nack)
}
Ok(())
}
pub fn read(busno: u8, ack: bool) -> Result<u8, &'static str> {
pub fn read(busno: u8, ack: bool) -> Result<u8, Error> {
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
return Err(INVALID_BUS)
return Err(Error::InvalidBus)
}
// precondition: SCL and SDA low
sda_oe(busno, false);
@ -188,17 +218,13 @@ mod imp {
Ok(data)
}
pub fn switch_select(busno: u8, address: u8, mask: u8) -> Result<(), &'static str> {
pub fn switch_select(busno: u8, address: u8, mask: u8) -> Result<(), Error> {
// address in 7-bit form
// mask in format of 1 << channel (or 0 for disabling output)
// PCA9548 support only for now
start(busno)?;
if !write(busno, address << 1)? {
return Err("PCA9548 failed to ack write address")
}
if !write(busno, mask)? {
return Err("PCA9548 failed to ack control word")
}
write(busno, address << 1)?;
write(busno, mask)?;
stop(busno)?;
Ok(())
}
@ -206,14 +232,15 @@ mod imp {
#[cfg(not(has_i2c))]
mod imp {
const NO_I2C: &'static str = "No I2C support on this platform";
pub fn init() -> Result<(), &'static str> { Err(NO_I2C) }
pub fn start(_busno: u8) -> Result<(), &'static str> { Err(NO_I2C) }
pub fn restart(_busno: u8) -> Result<(), &'static str> { Err(NO_I2C) }
pub fn stop(_busno: u8) -> Result<(), &'static str> { Err(NO_I2C) }
pub fn write(_busno: u8, _data: u8) -> Result<bool, &'static str> { Err(NO_I2C) }
pub fn read(_busno: u8, _ack: bool) -> Result<u8, &'static str> { Err(NO_I2C) }
pub fn switch_select(_busno: u8, _address: u8, _mask: u8) -> Result<(), &'static str> { Err(NO_I2C) }
use super::Error;
pub fn init() -> Result<(), Error> { Err(Error::NoI2C) }
pub fn start(_busno: u8) -> Result<(), Error> { Err(Error::NoI2C) }
pub fn restart(_busno: u8) -> Result<(), Error> { Err(Error::NoI2C) }
pub fn stop(_busno: u8) -> Result<(), Error> { Err(Error::NoI2C) }
pub fn write(_busno: u8, _data: u8) -> Result<bool, Error> { Err(Error::NoI2C) }
pub fn read(_busno: u8, _ack: bool) -> Result<u8, Error> { Err(Error::NoI2C) }
pub fn switch_select(_busno: u8, _address: u8, _mask: u8) -> Result<(), Error> { Err(Error::NoI2C) }
}
pub use self::imp::*;

View File

@ -29,14 +29,14 @@ impl EEPROM {
}
#[cfg(soc_platform = "kasli")]
fn select(&self) -> Result<(), &'static str> {
fn select(&self) -> Result<(), i2c::Error> {
let mask: u16 = 1 << self.port;
i2c::switch_select(self.busno, 0x70, mask as u8)?;
i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8)?;
Ok(())
}
pub fn read<'a>(&self, addr: u8, buf: &'a mut [u8]) -> Result<(), &'static str> {
pub fn read<'a>(&self, addr: u8, buf: &'a mut [u8]) -> Result<(), i2c::Error> {
self.select()?;
i2c::start(self.busno)?;
@ -58,7 +58,7 @@ impl EEPROM {
/// > The 24AA02XEXX is programmed at the factory with a
/// > globally unique node address stored in the upper half
/// > of the array and permanently write-protected.
pub fn read_eui48<'a>(&self) -> Result<[u8; 6], &'static str> {
pub fn read_eui48<'a>(&self) -> Result<[u8; 6], i2c::Error> {
let mut buffer = [0u8; 6];
self.read(0xFA, &mut buffer)?;
Ok(buffer)

View File

@ -23,7 +23,7 @@ pub struct IoExpander {
impl IoExpander {
#[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))]
pub fn new(index: u8) -> Result<Self, &'static str> {
pub fn new(index: u8) -> Result<Self, i2c::Error> {
const VIRTUAL_LED_MAPPING0: [(u8, u8, u8); 2] = [(0, 0, 6), (1, 1, 6)];
const VIRTUAL_LED_MAPPING1: [(u8, u8, u8); 2] = [(2, 0, 6), (3, 1, 6)];
@ -79,7 +79,11 @@ impl IoExpander {
gpiob: 0x13,
},
},
_ => return Err("incorrect I/O expander index"),
_ => {
#[cfg(feature = "log")]
log::error!("incorrect I/O expander index");
return Err(i2c::Error::IOExpanderError)
}
};
if !io_expander.check_ack()? {
#[cfg(feature = "log")]
@ -95,14 +99,16 @@ impl IoExpander {
gpiob: 0x03,
};
if !io_expander.check_ack()? {
return Err("Neither MCP23017 nor PCA9539 io expander found.");
#[cfg(feature = "log")]
log::error!("Neither MCP23017 nor PCA9539 io expander found.");
return Err(i2c::Error::IOExpanderError);
};
}
Ok(io_expander)
}
#[cfg(soc_platform = "efc")]
pub fn new() -> Result<Self, &'static str> {
pub fn new() -> Result<Self, i2c::Error> {
const VIRTUAL_LED_MAPPING: [(u8, u8, u8); 2] = [(0, 0, 5), (1, 0, 6)];
let io_expander = IoExpander {
@ -121,13 +127,15 @@ impl IoExpander {
},
};
if !io_expander.check_ack()? {
return Err("MCP23017 io expander not found.");
#[cfg(feature = "log")]
log::error!("MCP23017 io expander not found.");
return Err(i2c::Error::IOExpanderError);
};
Ok(io_expander)
}
#[cfg(soc_platform = "kasli")]
fn select(&self) -> Result<(), &'static str> {
fn select(&self) -> Result<(), i2c::Error> {
let mask: u16 = 1 << self.port;
i2c::switch_select(self.busno, 0x70, mask as u8)?;
i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8)?;
@ -135,13 +143,13 @@ impl IoExpander {
}
#[cfg(soc_platform = "efc")]
fn select(&self) -> Result<(), &'static str> {
fn select(&self) -> Result<(), i2c::Error> {
let mask: u16 = 1 << self.port;
i2c::switch_select(self.busno, 0x70, mask as u8)?;
Ok(())
}
fn write(&self, addr: u8, value: u8) -> Result<(), &'static str> {
fn write(&self, addr: u8, value: u8) -> Result<(), i2c::Error> {
i2c::start(self.busno)?;
i2c::write(self.busno, self.address)?;
i2c::write(self.busno, addr)?;
@ -150,22 +158,26 @@ impl IoExpander {
Ok(())
}
fn check_ack(&self) -> Result<bool, &'static str> {
fn check_ack(&self) -> Result<bool, i2c::Error> {
// Check for ack from io expander
self.select()?;
i2c::start(self.busno)?;
let ack = i2c::write(self.busno, self.address)?;
let ack = match i2c::write(self.busno, self.address) {
Ok(()) => true,
Err(i2c::Error::Nack) => false,
Err(err) => return Err(err)
};
i2c::stop(self.busno)?;
Ok(ack)
}
fn update_iodir(&self) -> Result<(), &'static str> {
fn update_iodir(&self) -> Result<(), i2c::Error> {
self.write(self.registers.iodira, self.iodir[0])?;
self.write(self.registers.iodirb, self.iodir[1])?;
Ok(())
}
pub fn init(&mut self) -> Result<(), &'static str> {
pub fn init(&mut self) -> Result<(), i2c::Error> {
self.select()?;
for (_led, port, bit) in self.virtual_led_mapping.iter() {
@ -180,7 +192,7 @@ impl IoExpander {
Ok(())
}
pub fn set_oe(&mut self, port: u8, outputs: u8) -> Result<(), &'static str> {
pub fn set_oe(&mut self, port: u8, outputs: u8) -> Result<(), i2c::Error> {
self.iodir[port as usize] &= !outputs;
self.update_iodir()?;
Ok(())
@ -194,7 +206,7 @@ impl IoExpander {
}
}
pub fn service(&mut self) -> Result<(), &'static str> {
pub fn service(&mut self) -> Result<(), i2c::Error> {
for (led, port, bit) in self.virtual_led_mapping.iter() {
let level = unsafe { (csr::virtual_leds::status_read() >> led) & 1 };
self.set(*port, *bit, level != 0);

View File

@ -10,7 +10,7 @@ use core::cell::RefCell;
const BUFFER_SIZE: usize = 512 * 1024;
#[repr(align(64))]
#[repr(align(2048))]
struct Buffer {
data: [u8; BUFFER_SIZE],
}

View File

@ -14,11 +14,12 @@ mod remote_i2c {
use drtio_routing;
use rtio_mgt::drtio;
use sched::{Io, Mutex};
use super::local_i2c;
pub fn start(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &drtio_routing::RoutingTable,
linkno: u8, destination: u8, busno: u8
) -> Result<(), &'static str> {
) -> Result<(), local_i2c::Error> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&drtioaux::Packet::I2cStartRequest {
destination: destination,
@ -26,15 +27,15 @@ mod remote_i2c {
});
match reply {
Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => {
if succeeded { Ok(()) } else { Err("i2c basic reply error") }
if succeeded { Ok(()) } else { Err(local_i2c::Error::OtherError) }
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Err("received unexpected aux packet")
Err(local_i2c::Error::OtherError)
}
Err(e) => {
error!("aux packet error ({})", e);
Err("aux packet error")
Err(local_i2c::Error::OtherError)
}
}
}
@ -42,7 +43,7 @@ mod remote_i2c {
pub fn restart(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &drtio_routing::RoutingTable,
linkno: u8, destination: u8, busno: u8
) -> Result<(), &'static str> {
) -> Result<(), local_i2c::Error> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&drtioaux::Packet::I2cRestartRequest {
destination: destination,
@ -50,15 +51,15 @@ mod remote_i2c {
});
match reply {
Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => {
if succeeded { Ok(()) } else { Err("i2c basic reply error") }
if succeeded { Ok(()) } else { Err(local_i2c::Error::OtherError) }
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Err("received unexpected aux packet")
Err(local_i2c::Error::OtherError)
}
Err(e) => {
error!("aux packet error ({})", e);
Err("aux packet error")
Err(local_i2c::Error::OtherError)
}
}
}
@ -66,7 +67,7 @@ mod remote_i2c {
pub fn stop(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &drtio_routing::RoutingTable,
linkno: u8, destination: u8, busno: u8
) -> Result<(), &'static str> {
) -> Result<(), local_i2c::Error> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&drtioaux::Packet::I2cStopRequest {
destination: destination,
@ -74,15 +75,15 @@ mod remote_i2c {
});
match reply {
Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => {
if succeeded { Ok(()) } else { Err("i2c basic reply error") }
if succeeded { Ok(()) } else { Err(local_i2c::Error::OtherError) }
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Err("received unexpected aux packet")
Err(local_i2c::Error::OtherError)
}
Err(e) => {
error!("aux packet error ({})", e);
Err("aux packet error")
Err(local_i2c::Error::OtherError)
}
}
}
@ -90,7 +91,7 @@ mod remote_i2c {
pub fn write(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &drtio_routing::RoutingTable,
linkno: u8, destination: u8, busno: u8, data: u8
) -> Result<bool, &'static str> {
) -> Result<(), local_i2c::Error> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&drtioaux::Packet::I2cWriteRequest {
destination: destination,
@ -99,15 +100,21 @@ mod remote_i2c {
});
match reply {
Ok(drtioaux::Packet::I2cWriteReply { succeeded, ack }) => {
if succeeded { Ok(ack) } else { Err("i2c write reply error") }
if succeeded && ack {
Ok(())
} else if !ack {
Err(local_i2c::Error::Nack)
} else {
Err(local_i2c::Error::OtherError)
}
}
Ok(_) => {
error!("received unexpected aux packet");
Err("received unexpected aux packet")
Err(local_i2c::Error::OtherError)
}
Err(e) => {
error!("aux packet error ({})", e);
Err("aux packet error")
Err(local_i2c::Error::OtherError)
}
}
}
@ -115,7 +122,7 @@ mod remote_i2c {
pub fn read(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &drtio_routing::RoutingTable,
linkno: u8, destination: u8, busno: u8, ack: bool
) -> Result<u8, &'static str> {
) -> Result<u8, local_i2c::Error> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&drtioaux::Packet::I2cReadRequest {
destination: destination,
@ -124,15 +131,15 @@ mod remote_i2c {
});
match reply {
Ok(drtioaux::Packet::I2cReadReply { succeeded, data }) => {
if succeeded { Ok(data) } else { Err("i2c read reply error") }
if succeeded { Ok(data) } else { Err(local_i2c::Error::OtherError) }
}
Ok(_) => {
error!("received unexpected aux packet");
Err("received unexpected aux packet")
Err(local_i2c::Error::OtherError)
}
Err(e) => {
error!("aux packet error ({})", e);
Err("aux packet error")
Err(local_i2c::Error::OtherError)
}
}
}
@ -140,7 +147,7 @@ mod remote_i2c {
pub fn switch_select(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &drtio_routing::RoutingTable,
linkno: u8, destination: u8, busno: u8, address: u8, mask: u8
) -> Result<(), &'static str> {
) -> Result<(), local_i2c::Error> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&drtioaux::Packet::I2cSwitchSelectRequest {
destination: destination,
@ -150,15 +157,15 @@ mod remote_i2c {
});
match reply {
Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => {
if succeeded { Ok(()) } else { Err("i2c basic reply error") }
if succeeded { Ok(()) } else { Err(local_i2c::Error::OtherError) }
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Err("received unexpected aux packet")
Err(local_i2c::Error::OtherError)
}
Err(e) => {
error!("aux packet error ({})", e);
Err("aux packet error")
Err(local_i2c::Error::OtherError)
}
}
}
@ -170,11 +177,12 @@ mod remote_spi {
use drtio_routing;
use rtio_mgt::drtio;
use sched::{Io, Mutex};
use super::local_spi;
pub fn set_config(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &drtio_routing::RoutingTable,
linkno: u8, destination: u8, busno: u8, flags: u8, length: u8, div: u8, cs: u8
) -> Result<(), ()> {
) -> Result<(), local_spi::Error> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, &drtioaux::Packet::SpiSetConfigRequest {
destination: destination,
busno: busno,
@ -185,15 +193,15 @@ mod remote_spi {
});
match reply {
Ok(drtioaux::Packet::SpiBasicReply { succeeded }) => {
if succeeded { Ok(()) } else { Err(()) }
if succeeded { Ok(()) } else { Err(local_spi::Error::OtherError) }
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Err(())
Err(local_spi::Error::OtherError)
}
Err(e) => {
error!("aux packet error ({})", e);
Err(())
Err(local_spi::Error::OtherError)
}
}
}
@ -201,7 +209,7 @@ mod remote_spi {
pub fn write(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &drtio_routing::RoutingTable,
linkno: u8, destination: u8, busno: u8, data: u32
) -> Result<(), ()> {
) -> Result<(), local_spi::Error> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, &drtioaux::Packet::SpiWriteRequest {
destination: destination,
busno: busno,
@ -209,22 +217,22 @@ mod remote_spi {
});
match reply {
Ok(drtioaux::Packet::SpiBasicReply { succeeded }) => {
if succeeded { Ok(()) } else { Err(()) }
if succeeded { Ok(()) } else { Err(local_spi::Error::OtherError) }
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Err(())
Err(local_spi::Error::OtherError)
}
Err(e) => {
error!("aux packet error ({})", e);
Err(())
Err(local_spi::Error::OtherError)
}
}
}
pub fn read(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &drtio_routing::RoutingTable, linkno: u8, destination: u8, busno: u8
) -> Result<u32, ()> {
) -> Result<u32, local_spi::Error> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&drtioaux::Packet::SpiReadRequest {
destination: destination,
@ -232,15 +240,15 @@ mod remote_spi {
});
match reply {
Ok(drtioaux::Packet::SpiReadReply { succeeded, data }) => {
if succeeded { Ok(data) } else { Err(()) }
if succeeded { Ok(data) } else { Err(local_spi::Error::OtherError) }
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Err(())
Err(local_spi::Error::OtherError)
}
Err(e) => {
error!("aux packet error ({})", e);
Err(())
Err(local_spi::Error::OtherError)
}
}
}
@ -306,7 +314,8 @@ pub fn process_kern_hwreq(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subker
}
&kern::I2cWriteRequest { busno, data } => {
match dispatch!(io, aux_mutex, ddma_mutex, subkernel_mutex, local_i2c, remote_i2c, routing_table, busno, write, data) {
Ok(ack) => kern_send(io, &kern::I2cWriteReply { succeeded: true, ack: ack }),
Ok(()) => kern_send(io, &kern::I2cWriteReply { succeeded: true, ack: true }),
Err(local_i2c::Error::Nack) => kern_send(io, &kern::I2cWriteReply { succeeded: true, ack: false }),
Err(_) => kern_send(io, &kern::I2cWriteReply { succeeded: false, ack: false })
}
}

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

@ -369,15 +369,18 @@ pub mod drtio {
}
drtioaux::Packet::DestinationOkReply => (),
drtioaux::Packet::DestinationSequenceErrorReply { channel } => {
error!("[DEST#{}] RTIO sequence error involving channel 0x{:04x}:{}", destination, channel, resolve_channel_name(channel as u32));
let global_ch = ((destination as u32) << 16) | channel as u32;
error!("[DEST#{}] RTIO sequence error involving channel 0x{:04x}:{}", destination, channel, resolve_channel_name(global_ch));
unsafe { SEEN_ASYNC_ERRORS |= ASYNC_ERROR_SEQUENCE_ERROR };
}
drtioaux::Packet::DestinationCollisionReply { channel } => {
error!("[DEST#{}] RTIO collision involving channel 0x{:04x}:{}", destination, channel, resolve_channel_name(channel as u32));
let global_ch = ((destination as u32) << 16) | channel as u32;
error!("[DEST#{}] RTIO collision involving channel 0x{:04x}:{}", destination, channel, resolve_channel_name(global_ch));
unsafe { SEEN_ASYNC_ERRORS |= ASYNC_ERROR_COLLISION };
}
drtioaux::Packet::DestinationBusyReply { channel } => {
error!("[DEST#{}] RTIO busy error involving channel 0x{:04x}:{}", destination, channel, resolve_channel_name(channel as u32));
let global_ch = ((destination as u32) << 16) | channel as u32;
error!("[DEST#{}] RTIO busy error involving channel 0x{:04x}:{}", destination, channel, resolve_channel_name(global_ch));
unsafe { SEEN_ASYNC_ERRORS |= ASYNC_ERROR_BUSY };
}
packet => error!("[DEST#{}] received unexpected aux packet: {:?}", destination, packet),

View File

@ -1050,8 +1050,10 @@ fn process_kern_hwreq(request: &kern::Message, self_destination: u8) -> Result<b
}
&kern::I2cWriteRequest { busno, data } => {
match i2c::write(busno as u8, data) {
Ok(ack) => kern_send(
&kern::I2cWriteReply { succeeded: true, ack: ack }),
Ok(()) => kern_send(
&kern::I2cWriteReply { succeeded: true, ack: true }),
Err(i2c::Error::Nack) => kern_send(
&kern::I2cWriteReply { succeeded: true, ack: false }),
Err(_) => kern_send(
&kern::I2cWriteReply { succeeded: false, ack: false })
}

View File

@ -321,8 +321,10 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
drtioaux::Packet::I2cWriteRequest { destination: _destination, busno, data } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
match i2c::write(busno, data) {
Ok(ack) => drtioaux::send(0,
&drtioaux::Packet::I2cWriteReply { succeeded: true, ack: ack }),
Ok(()) => drtioaux::send(0,
&drtioaux::Packet::I2cWriteReply { succeeded: true, ack: true }),
Err(i2c::Error::Nack) => drtioaux::send(0,
&drtioaux::Packet::I2cWriteReply { succeeded: true, ack: false }),
Err(_) => drtioaux::send(0,
&drtioaux::Packet::I2cWriteReply { succeeded: false, ack: false })
}
@ -647,7 +649,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
drtioaux::Packet::CoreMgmtDropLinkAck { destination: _destination } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
#[cfg(not(has_drtio_eem))]
#[cfg(not(soc_platform = "efc"))]
unsafe {
csr::gt_drtio::txenable_write(0);
}
@ -949,7 +951,7 @@ fn startup() {
io_expander.service().unwrap();
}
#[cfg(not(has_drtio_eem))]
#[cfg(not(soc_platform = "efc"))]
unsafe {
csr::gt_drtio::txenable_write(0xffffffffu32 as _);
}

View File

View File

@ -0,0 +1,352 @@
from migen import *
from migen.genlib.cdc import MultiReg, PulseSynchronizer
from misoc.interconnect.csr import *
from misoc.interconnect.stream import AsyncFIFO, Buffer, Endpoint, SyncFIFO
from misoc.cores.coaxpress.common import char_width, word_layout_dchar, word_width
from misoc.cores.coaxpress.core import HostTXCore, HostRXCore
from misoc.cores.coaxpress.core.packet import StreamPacketArbiter
from misoc.cores.coaxpress.core.crc import CXPCRC32Checker
from artiq.gateware.cxp_grabber.frame import (
EndOfLineMarker,
FrameHeaderReader,
PixelParser,
pixelword_layout,
)
from operator import or_, add
class CXPHostCore(Module, AutoCSR):
def __init__(self, tx_phy, rx_phy, clk_freq, command_buffer_depth=32, nrxslot=4):
# control buffer is only 32 words (128 bytes) wide for compatibility with CXP 1.x compliant devices
# Section 12.1.6 (CXP-001-2021)
self.buffer_depth, self.nslots = command_buffer_depth, nrxslot
self.submodules.tx = HostTXCore(tx_phy, command_buffer_depth, clk_freq, False)
self.submodules.rx = HostRXCore(rx_phy, command_buffer_depth, nrxslot, False)
def get_tx_port(self):
return self.tx.writer.mem.get_port(write_capable=True)
def get_rx_port(self):
return self.rx.command_reader.mem.get_port(write_capable=False)
def get_mem_size(self):
return word_width * self.buffer_depth * self.nslots // 8
class StreamDecoder(Module, AutoCSR):
"""
Convert the raw frame data into pixel data
Currently only support:
- Pixel format: mono8, mono10, mono12, mono14, mono16
- Tap geometry: 1X-1Y
- Scanning mode: area scanning
"""
def __init__(self, res_width):
self.crc_error = CSR()
self.stream_type_error = CSR()
self.new_frame = CSR()
self.x_size = CSRStatus(3*char_width)
self.y_size = CSRStatus(3*char_width)
self.pixel_format_code = CSRStatus(2*char_width)
# # #
cdr = ClockDomainsRenamer("cxp_gt_rx")
#
# 32+8(dchar) 32
# sink ────/────> stream ────> buffer ────> crc checker ─────> frame header ───/───> end of line ─────> skid buffer ─────> pixel parser ─────> 4x pixel with
# arbiter reader reader marker xy coordinate
#
# Drops the packet header & K29.7 and mark eop on the crc word
self.submodules.arbiter = arbiter = cdr(StreamPacketArbiter())
# Buffer to improve timing
self.submodules.buffer = buffer = cdr(Buffer(word_layout_dchar))
# CRC
self.submodules.crc_checker = crc_checker = cdr(CXPCRC32Checker())
self.submodules.crc_error_ps = crc_error_ps = PulseSynchronizer("cxp_gt_rx", "sys")
self.sync.cxp_gt_rx += crc_error_ps.i.eq(crc_checker.error)
self.sync += [
If(crc_error_ps.o,
self.crc_error.w.eq(1),
).Elif(self.crc_error.re,
self.crc_error.w.eq(0),
),
]
# Frame header extraction
self.submodules.header_reader = header_reader = cdr(FrameHeaderReader())
# New frame and stream type error notification
self.submodules.new_frame_ps = new_frame_ps = PulseSynchronizer("cxp_gt_rx", "sys")
self.submodules.stream_type_err_ps = stream_type_err_ps = PulseSynchronizer("cxp_gt_rx", "sys")
self.sync.cxp_gt_rx += [
new_frame_ps.i.eq(header_reader.new_frame),
stream_type_err_ps.i.eq(header_reader.decode_err),
]
self.sync += [
If(new_frame_ps.o,
self.new_frame.w.eq(1),
).Elif(self.new_frame.re,
self.new_frame.w.eq(0),
),
If(stream_type_err_ps.o,
self.stream_type_error.w.eq(1),
).Elif(self.stream_type_error.re,
self.stream_type_error.w.eq(0),
)
]
frame_header = header_reader.header
self.specials += [
MultiReg(frame_header.Xsize, self.x_size.status),
MultiReg(frame_header.Ysize, self.y_size.status),
MultiReg(frame_header.PixelF, self.pixel_format_code.status),
]
# Mark end of line for pixel parser
self.submodules.eol_marker = eol_marker = cdr(EndOfLineMarker())
self.sync.cxp_gt_rx += eol_marker.words_per_img_line.eq(frame_header.DsizeL)
# Skid buffer to prevent pipeline stalling
# At each linebreak, `Pixel_Parser.sink.ack` will fall for 1-2 cycle.
# Without the skid buffer , the whole pipleline will stall during that 1-2 cycle.
#
# Due to the backpressure, 2 words line marker (4x K28.3 + 4x 0x02) will arrive as the linebreak indicator and will be consumed by `frame_header_reader`
# Thus, the buffer won't experience any data buildup.
self.submodules.skid_buf = skid_buf = cdr(SyncFIFO(pixelword_layout, 2))
self.submodules.parser = parser = cdr(PixelParser(res_width))
self.sync.cxp_gt_rx += [
parser.x_size.eq(frame_header.Xsize),
parser.y_size.eq(frame_header.Ysize),
parser.pixel_format_code.eq(frame_header.PixelF),
]
# Connecting the pipeline
self.sink = arbiter.sink
self.comb += arbiter.sources[0].connect(buffer.sink)
self.pipeline = [buffer, crc_checker, header_reader, eol_marker, skid_buf, parser]
for s, d in zip(self.pipeline, self.pipeline[1:]):
self.comb += s.source.connect(d.sink)
# For downstream ROI engine
self.source_pixel4x = parser.source_pixel4x
class ROI(Module):
"""
ROI Engine that accept 4 pixels each cycle. For each frame, accumulates pixels values within a
rectangular region of interest, and reports the total.
"""
def __init__(self, pixel_4x, count_width):
assert len(pixel_4x) == 4
self.cfg = Record([
("x0", len(pixel_4x[0].x)),
("y0", len(pixel_4x[0].y)),
("x1", len(pixel_4x[0].x)),
("y1", len(pixel_4x[0].y)),
])
self.out = Record([
("update", 1),
# registered output - can be used as CDC input
("count", count_width),
])
# # #
roi_4x = [
Record([
("x_good", 1),
("y_good", 1),
("gray", len(pixel_4x[0].gray)),
("stb", 1),
("count", count_width),
]) for _ in range(4)
]
for pix, roi in zip(pixel_4x, roi_4x):
self.sync += [
# stage 1 - generate "good" (in-ROI) signals
roi.x_good.eq(0),
If((self.cfg.x0 <= pix.x) & (pix.x < self.cfg.x1),
roi.x_good.eq(1)
),
# the 4 pixels are on the same y level, no need for extra calculation
If(pix.y == self.cfg.y0,
roi.y_good.eq(1)
),
If(pix.y == self.cfg.y1,
roi.y_good.eq(0)
),
If(pix.eof,
roi.x_good.eq(0),
roi.y_good.eq(0)
),
roi.gray.eq(pix.gray),
roi.stb.eq(pix.stb),
# stage 2 - accumulate
If((roi.stb & roi.x_good & roi.y_good),
roi.count.eq(roi.count + roi.gray)
)
]
eof = Signal()
eof_buf = Signal()
count_buf = [Signal(count_width), Signal(count_width)]
# stage 3 - update
self.sync += [
eof.eq(reduce(or_, [pix.eof for pix in pixel_4x])),
eof_buf.eq(eof),
count_buf[0].eq(roi_4x[0].count + roi_4x[1].count),
count_buf[1].eq(roi_4x[2].count + roi_4x[3].count),
self.out.update.eq(0),
If(eof_buf,
[roi.count.eq(0) for roi in roi_4x],
self.out.update.eq(1),
self.out.count.eq(reduce(add, count_buf))
),
]
class ROICropper(Module):
def __init__(self, pixel_4x):
self.enable = Signal()
self.cfg = Record([
("x0", len(pixel_4x[0].x)),
("y0", len(pixel_4x[0].y)),
("x1", len(pixel_4x[0].x)),
("y1", len(pixel_4x[0].y)),
])
max_pixel_width = len(pixel_4x[0].gray)
self.source = Endpoint([("data", 4 * max_pixel_width)])
# # #
roi_4x = [
Record([
("x_good", 1),
("y_good", 1),
("gray", max_pixel_width),
("stb", 1),
("eof", 1),
]) for _ in range(4)
]
for i, (pix, roi) in enumerate(zip(pixel_4x, roi_4x)):
self.sync += [
roi.x_good.eq(0),
If((self.cfg.x0 <= pix.x) & (pix.x < self.cfg.x1),
roi.x_good.eq(1)
),
# the 4 pixels are on the same y level, no need for extra calculation
If(pix.y == self.cfg.y0,
roi.y_good.eq(1)
),
If(pix.y == self.cfg.y1,
roi.y_good.eq(0)
),
If(pix.eof,
roi.x_good.eq(0),
roi.y_good.eq(0)
),
roi.gray.eq(pix.gray),
roi.stb.eq(pix.stb),
roi.eof.eq(pix.eof),
self.source.data[i * max_pixel_width: (i + 1) * max_pixel_width].eq(0),
If((self.enable & roi.stb & roi.x_good & roi.y_good),
self.source.data[i * max_pixel_width: (i + 1) * max_pixel_width].eq(roi.gray)
),
]
# use the first roi for flow control as the first pixel is always available
if i == 0:
self.sync += [
self.source.stb.eq(0),
self.source.eop.eq(0),
If((self.enable & roi.stb & roi.x_good & roi.y_good),
self.source.stb.eq(roi.stb),
self.source.eop.eq(roi.eof),
),
]
class ROIViewer(Module, AutoCSR):
def __init__(self, pixel_4x, fifo_depth=1024):
self.arm = CSR()
self.ready = CSR()
self.x0 = CSRStorage(len(pixel_4x[0].x))
self.x1 = CSRStorage(len(pixel_4x[0].y))
self.y0 = CSRStorage(len(pixel_4x[0].x))
self.y1 = CSRStorage(len(pixel_4x[0].y))
max_pixel_width = len(pixel_4x[0].gray)
self.fifo_ack = CSR()
self.fifo_data = CSRStatus(4 * max_pixel_width)
self.fifo_stb = CSRStatus()
# # #
cdr = ClockDomainsRenamer("cxp_gt_rx")
self.submodules.cropper = cropper = cdr(ROICropper(pixel_4x))
self.submodules.arm_ps = arm_ps = PulseSynchronizer("sys", "cxp_gt_rx")
self.submodules.ready_ps = ready_ps = PulseSynchronizer("cxp_gt_rx", "sys")
self.sync += [
arm_ps.i.eq(self.arm.re),
If(ready_ps.o,
self.ready.w.eq(1),
).Elif(self.ready.re,
self.ready.w.eq(0),
),
]
self.sync.cxp_gt_rx += [
If(arm_ps.o,
cropper.enable.eq(1),
).Elif(pixel_4x[0].eof,
cropper.enable.eq(0),
),
ready_ps.i.eq(pixel_4x[0].eof),
]
self.specials += [
MultiReg(self.x0.storage, cropper.cfg.x0, "cxp_gt_rx"),
MultiReg(self.x1.storage, cropper.cfg.x1, "cxp_gt_rx"),
MultiReg(self.y0.storage, cropper.cfg.y0, "cxp_gt_rx"),
MultiReg(self.y1.storage, cropper.cfg.y1, "cxp_gt_rx"),
]
self.submodules.buffer = buffer = cdr(Buffer([("data", 4 * max_pixel_width)]))
self.submodules.fifo = fifo = ClockDomainsRenamer(
{"write": "cxp_gt_rx", "read": "sys"}
)(AsyncFIFO([("data", 4 * max_pixel_width)], fifo_depth))
pipeline = [cropper, buffer, fifo]
for s, d in zip(pipeline, pipeline[1:]):
self.comb += s.source.connect(d.sink)
self.sync += [
fifo.source.ack.eq(self.fifo_ack.re),
self.fifo_data.status.eq(fifo.source.data),
self.fifo_stb.status.eq(fifo.source.stb),
]

View File

@ -0,0 +1,472 @@
from migen import *
from misoc.interconnect.stream import Endpoint
from misoc.cores.coaxpress.common import (
char_width,
KCode,
switch_endianness,
word_layout_dchar,
word_width,
)
from math import lcm
from types import SimpleNamespace
max_pixel_width = 16
# the pixel data don't include any K code nor duplicate char
pixelword_layout = [("data", word_width)]
class FrameHeaderReader(Module):
def __init__(self):
self.decode_err = Signal()
self.new_frame = Signal()
# # #
# Table 47 (CXP-001-2021)
n_header_chars = 23
img_header_layout = [
("StreamID", char_width),
("SourceTag", 2 * char_width),
("Xsize", 3 * char_width),
("Xoffs", 3 * char_width), # horizontal offset in pixels
("Ysize", 3 * char_width),
("Yoffs", 3 * char_width), # vertical offset in pixels
("DsizeL", 3 * char_width), # number of data words per image line
("PixelF", 2 * char_width),
("TapG", 2 * char_width), # tap geometry
("Flags", char_width),
]
assert layout_len(img_header_layout) == n_header_chars * char_width
self.sink = Endpoint(word_layout_dchar)
self.source = Endpoint(pixelword_layout)
# # #
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
fsm.act("IDLE",
self.sink.ack.eq(1),
If((self.sink.stb & (self.sink.dchar == KCode["stream_marker"]) & (self.sink.dchar_k == 1)),
NextState("DECODE"),
)
)
fsm.act("COPY",
# until for new line or new frame
If((self.sink.stb & (self.sink.dchar == KCode["stream_marker"]) & (self.sink.dchar_k == 1)),
self.sink.ack.eq(1),
NextState("DECODE"),
).Else(
self.sink.connect(self.source, omit={"k", "dchar", "dchar_k"}),
)
)
type = {
"new_frame": 0x01,
"line_break": 0x02,
}
cnt = Signal(max=n_header_chars)
fsm.act("DECODE",
self.sink.ack.eq(1),
If(self.sink.stb,
Case(self.sink.dchar, {
type["new_frame"]: [
NextValue(cnt, cnt.reset),
NextState("GET_FRAME_DATA"),
],
type["line_break"]: [
NextState("COPY"),
],
"default": [
self.decode_err.eq(1),
# discard all data until valid frame header
NextState("IDLE"),
],
}),
)
)
packet_buffer = Signal(layout_len(img_header_layout))
case = dict(
(i, NextValue(packet_buffer[8*i:8*(i+1)], self.sink.dchar))
for i in range(n_header_chars)
)
fsm.act("GET_FRAME_DATA",
self.sink.ack.eq(1),
If(self.sink.stb,
Case(cnt, case),
If(cnt == n_header_chars - 1,
self.new_frame.eq(1),
NextState("COPY"),
NextValue(cnt, cnt.reset),
).Else(
NextValue(cnt, cnt + 1),
),
),
)
# dissect packet
self.header = SimpleNamespace()
idx = 0
for name, size in img_header_layout:
# CXP also use MSB when sending duplicate chars in sequence
setattr(self.header, name, switch_endianness(packet_buffer[idx:idx+size]))
idx += size
class EndOfLineMarker(Module):
def __init__(self):
# Assume words_per_img_line arrive at least one cycle before pixel data
self.words_per_img_line = Signal(3*char_width)
self.sink = Endpoint(pixelword_layout)
self.source = Endpoint(pixelword_layout)
# # #
cnt = Signal.like(self.words_per_img_line, reset=1)
self.sync += [
If(self.source.ack,
self.sink.connect(self.source, omit={"ack", "eop"}),
If(self.sink.stb,
If(cnt == 1,
cnt.eq(self.words_per_img_line)
).Else(
cnt.eq(cnt - 1),
)
),
),
]
self.comb += [
self.sink.ack.eq(self.source.ack),
# repurpose eop as end of line
self.source.eop.eq(cnt == 1),
]
class PixelUnpacker(Module):
"""
Unpack 32 bits words into 4x pixel
Assume:
- x_size arrive at least one cycle before any pixel data
- the last pixel word is marked with eop
Only support:
- Pixel format: mono8, mono10, mono12, mono14, mono16
"""
def __init__(self, size):
assert size <= max_pixel_width
assert size in [8, 10, 12, 14, 16]
self.x_size = Signal(3*char_width)
self.sink = Endpoint(pixelword_layout)
self.source = Endpoint(
[
("data", max_pixel_width * 4),
("valid", 4),
]
)
# # #
sink_dw, source_dw = layout_len(pixelword_layout), size*4
ring_buf_size = lcm(sink_dw, source_dw)
# ensure the shift register is at least twice the size of sink/source dw
if (ring_buf_size//sink_dw) < 2:
ring_buf_size = ring_buf_size * 2
if (ring_buf_size//source_dw) < 2:
ring_buf_size = ring_buf_size * 2
# Control interface
reset_reg = Signal()
we = Signal()
re = Signal()
level = Signal(max=ring_buf_size)
w_cnt = Signal(max=ring_buf_size//sink_dw)
r_cnt = Signal(max=ring_buf_size//source_dw)
self.sync += [
If(reset_reg,
level.eq(level.reset),
).Else(
If(we & ~re, level.eq(level + sink_dw)),
If(~we & re, level.eq(level - source_dw)),
If(we & re, level.eq(level + sink_dw - source_dw)),
),
If(reset_reg,
w_cnt.eq(w_cnt.reset),
r_cnt.eq(r_cnt.reset),
).Else(
If(we,
If(w_cnt == ((ring_buf_size//sink_dw) - 1),
w_cnt.eq(w_cnt.reset),
).Else(
w_cnt.eq(w_cnt + 1),
)
),
If(re,
If(r_cnt == ((ring_buf_size//source_dw) - 1),
r_cnt.eq(r_cnt.reset),
).Else(
r_cnt.eq(r_cnt + 1),
)
),
)
]
extra_eol_handling = size in [10, 12, 14]
if extra_eol_handling:
# the source need to be stb twice
# (one for level >= source_dw and the other for the remaining pixels)
# when last word of each line packet satisfied the following condition:
#
# if there exist an integers j such that
# sink_dw * i > size * j > source_dw * k
# where i,k are postive integers and source_dw * k - sink_dw * (i-1) > 0
#
stb_aligned = Signal()
match size:
case 10:
# For example size == 10
# 32 * 2 > 10 * (5) > 40 * 1
# 32 * 2 > 10 * (6) > 40 * 1
# 32 * 3 > 10 * (9) > 40 * 2
# ...
#
# the packing pattern for size == 10 repeat every 16 pixels
# the remaining special case can be taken care off using modulo operation
stb_cases = {
5: stb_aligned.eq(1),
6: stb_aligned.eq(1),
9: stb_aligned.eq(1),
}
self.sync += Case(self.x_size[:4], stb_cases) # mod 16
case 12:
stb_cases = {
5: stb_aligned.eq(1),
}
self.sync += Case(self.x_size[:3], stb_cases) # mod 8
case 14:
stb_cases = {
9: stb_aligned.eq(1),
13: stb_aligned.eq(1),
}
self.sync += Case(self.x_size[:4], stb_cases) # mod 16
self.submodules.fsm = fsm = FSM(reset_state="SHIFTING")
fsm.act(
"SHIFTING",
self.sink.ack.eq(1),
self.source.stb.eq(level >= source_dw),
we.eq(self.sink.stb),
re.eq((self.source.stb & self.source.ack)),
If(self.sink.stb & self.sink.eop,
(If(stb_aligned,
NextState("MOVE_ALIGNED_PIX"),
).Else(
NextState("MOVE_REMAINING_PIX"),
) if extra_eol_handling else
NextState("MOVE_REMAINING_PIX"),
)
),
)
if extra_eol_handling:
fsm.act(
"MOVE_ALIGNED_PIX",
self.source.stb.eq(1),
re.eq((self.source.stb & self.source.ack)),
NextState("MOVE_REMAINING_PIX"),
)
stb_remaining_pix = Signal()
fsm.act(
"MOVE_REMAINING_PIX",
reset_reg.eq(1),
self.source.stb.eq(1),
stb_remaining_pix.eq(1),
NextState("SHIFTING"),
)
# Data path
ring_buf = Signal(ring_buf_size, reset_less=True)
sink_cases = {}
for i in range(ring_buf_size//sink_dw):
sink_cases[i] = [
ring_buf[sink_dw*i:sink_dw*(i+1)].eq(self.sink.data),
]
self.sync += If(self.sink.stb, Case(w_cnt, sink_cases))
source_cases = {}
for i in range(ring_buf_size//source_dw):
source_cases[i] = []
for j in range(4):
source_cases[i].append(
self.source.data[max_pixel_width * j : max_pixel_width * (j + 1)].eq(
ring_buf[(source_dw * i) + (size * j) : (source_dw * i) + (size * (j + 1))]
)
)
# calcule which last pixels are valid
valid = Signal(4)
bit_cases = {
0: valid.eq(0b1111),
1: valid.eq(0b0001),
2: valid.eq(0b0011),
3: valid.eq(0b0111),
}
self.sync += Case(self.x_size[:2], bit_cases)
self.comb += [
Case(r_cnt, source_cases),
If(stb_remaining_pix,
self.source.valid.eq(valid),
self.source.eop.eq(1),
).Else(
self.source.valid.eq(0b1111),
),
]
class PixelCoordinateTracker(Module):
"""
Track and append 4x pixel with xy coordinates
Assume:
- y_size arrive at least one cycle before any pixel data
- camera is in area scan mode
- 1X-1Y Tap geometry
"""
def __init__(self, res_width):
# largest x/y pixel size supported by frame header are 24 bits
assert res_width <= 3*char_width
# line scanning frame will have y_size = 0 and won't trigger the end of frame bit
self.y_size = Signal(3*char_width)
self.sink = Endpoint(
[
("data", max_pixel_width * 4),
("valid", 4),
]
)
# # #
self.pixel4x = []
for _ in range(4):
self.pixel4x.append(Record([
("x", res_width),
("y", res_width),
("gray", max_pixel_width),
("stb", 1),
("eof", 1), # end of frame
]))
x_4x = [Signal(len(self.pixel4x[0].x), reset=i) for i in range(4)]
y_r = Signal(len(self.pixel4x[0].y))
y_max = Signal.like(self.y_size)
self.sync += [
self.sink.ack.eq(1),
y_max.eq(self.y_size - 1),
]
for i, (x_r, pix) in enumerate(zip(x_4x, self.pixel4x)):
self.sync += [
pix.stb.eq(0),
pix.eof.eq(0),
If(self.sink.stb,
If(self.sink.eop,
# new line
x_r.eq(x_r.reset),
If(y_r == y_max,
pix.eof.eq(1),
y_r.eq(y_r.reset),
).Else(
y_r.eq(y_r + 1),
)
).Else(
x_r.eq(x_r + 4),
),
pix.stb.eq(self.sink.valid[i]),
pix.x.eq(x_r),
pix.y.eq(y_r),
pix.gray.eq(self.sink.data[max_pixel_width*i:max_pixel_width*(i+1)]),
)
]
class PixelParser(Module):
"""
Prase 32 bit pixel word into 4x pixel with xy coordinate
Only support:
- Pixel format: mono8, mono10, mono12, mono14, mono16
- Tap geometry: 1X-1Y
- Scanning mode: area scanning
"""
def __init__(self, res_width):
self.x_size = Signal(3 * char_width)
self.y_size = Signal(3 * char_width)
self.pixel_format_code = Signal(2 * char_width)
self.sink = Endpoint(pixelword_layout)
# # #
#
# 32 4x pixel
# sink ───/───┬──> 8 bits ──┬───/───> pixel coordinate ─────> 4x pixel with
# ├──> 10 bits ──┤ tracker xy coordinate
# ├──> 12 bits ──┤
# ├──> 14 bits ──┤
# └──> 16 bits ──┘
# pixel unpacker
#
# From Table 34 (CXP-001-2021)
pixel_formats = {
"mono8": 0x0101,
"mono10": 0x0102,
"mono12": 0x0103,
"mono14": 0x0104,
"mono16": 0x0105,
}
unpackers = {}
for s in [8, 10, 12, 14, 16]:
unpacker = PixelUnpacker(s)
unpackers["mono"+str(s)] = unpacker
self.submodules += unpacker
self.sync += unpacker.x_size.eq(self.x_size),
self.submodules.tracker = tracker = PixelCoordinateTracker(res_width)
self.sync += tracker.y_size.eq(self.y_size)
# discard unknown pixel format
mux_cases = {"default": [self.sink.ack.eq(1)]}
for fmt, code in pixel_formats.items():
mux_cases[code] = [
self.sink.connect(unpackers[fmt].sink),
unpackers[fmt].source.connect(tracker.sink),
]
self.comb += Case(self.pixel_format_code, mux_cases)
self.source_pixel4x = tracker.pixel4x

View File

@ -3,11 +3,11 @@ from migen.genlib.resetsync import AsyncResetSynchronizer
from migen.genlib.cdc import MultiReg
from misoc.cores.code_8b10b import Encoder, Decoder
from misoc.cores.gtx_7series_init import *
from misoc.interconnect.csr import *
from artiq.gateware.drtio.core import TransceiverInterface, ChannelInterface
from artiq.gateware.drtio.transceiver.clock_aligner import BruteforceClockAligner
from artiq.gateware.drtio.transceiver.gtx_7series_init import *
class GTX_20X(Module):

View File

@ -1,257 +0,0 @@
from math import ceil
from migen import *
from migen.genlib.cdc import MultiReg
from migen.genlib.misc import WaitTimer
from migen.genlib.fsm import FSM
class GTXInit(Module):
# Based on LiteSATA by Enjoy-Digital
# Choose between Auto Mode and Manual Mode for TX/RX phase alignment with buffer bypassed:
# * Auto Mode: When only single lane is involved, as suggested by Xilinx (AR59612)
# * Manual Mode: When only multi-lane is involved, as suggested by Xilinx (AR59612)
def __init__(self, clk_freq, rx, mode="single"):
assert isinstance(rx, bool)
assert mode in ["single", "master", "slave"]
self.mode = mode
self.clk_path_ready = Signal()
self.done = Signal()
self.restart = Signal()
# GTX signals
self.cplllock = Signal()
self.cpllreset = Signal()
self.gtXxreset = Signal()
self.Xxresetdone = Signal()
self.Xxdlysreset = Signal()
self.Xxdlysresetdone = Signal()
self.Xxphaligndone = Signal()
self.Xxuserrdy = Signal()
# GTX signals exclusive to multi-lane
if mode != "single":
self.Xxphalign = Signal()
self.Xxdlyen = Signal()
# TX only:
if not rx:
self.txphinit = Signal()
self.txphinitdone = Signal()
# Strobe from master channel to initialize TX/RX phase alignment on slaves
self.master_phaligndone = Signal()
# Strobe from slave channels to re-enable TX/RX delay alignment on master;
# To be combinatorially AND-ed from all slave's `done`
if mode == "master":
self.slaves_phaligndone = Signal()
# # #
# Double-latch transceiver asynch outputs
cplllock = Signal()
Xxresetdone = Signal()
Xxdlysresetdone = Signal()
Xxphaligndone = Signal()
self.specials += [
MultiReg(self.cplllock, cplllock),
MultiReg(self.Xxresetdone, Xxresetdone),
MultiReg(self.Xxdlysresetdone, Xxdlysresetdone),
MultiReg(self.Xxphaligndone, Xxphaligndone),
]
if mode != "single":
txphinitdone = Signal()
self.specials += MultiReg(self.txphinitdone, txphinitdone)
# Deglitch FSM outputs driving transceiver asynch inputs
gtXxreset = Signal()
Xxdlysreset = Signal()
Xxuserrdy = Signal()
self.sync += [
self.gtXxreset.eq(gtXxreset),
self.Xxdlysreset.eq(Xxdlysreset),
self.Xxuserrdy.eq(Xxuserrdy)
]
if mode != "single":
Xxphalign = Signal()
Xxdlyen = Signal()
self.sync += [
self.Xxphalign.eq(Xxphalign),
self.Xxdlyen.eq(Xxdlyen)
]
if not rx:
txphinit = Signal()
self.sync += self.txphinit.eq(txphinit)
# After configuration, transceiver resets have to stay low for
# at least 500ns (see AR43482)
startup_cycles = ceil(500*clk_freq/1000000000)
startup_timer = WaitTimer(startup_cycles)
self.submodules += startup_timer
# PLL reset should be 1 period of refclk
# (i.e. 1/(125MHz) for the case of RTIO @ 125MHz)
pll_reset_cycles = ceil(clk_freq/125e6)
pll_reset_timer = WaitTimer(pll_reset_cycles)
self.submodules += pll_reset_timer
startup_fsm = FSM(reset_state="INITIAL")
self.submodules += startup_fsm
if rx:
cdr_stable_timer = WaitTimer(1024)
self.submodules += cdr_stable_timer
# Rising edge detection for phase alignment "done"
Xxphaligndone_r = Signal(reset=1)
Xxphaligndone_rising = Signal()
self.sync += Xxphaligndone_r.eq(Xxphaligndone)
self.comb += Xxphaligndone_rising.eq(Xxphaligndone & ~Xxphaligndone_r)
startup_fsm.act("INITIAL",
startup_timer.wait.eq(1),
If(startup_timer.done & self.clk_path_ready, NextState("RESET_PLL"))
)
startup_fsm.act("RESET_PLL",
gtXxreset.eq(1),
self.cpllreset.eq(1),
pll_reset_timer.wait.eq(1),
If(pll_reset_timer.done, NextState("RELEASE_PLL_RESET"))
)
startup_fsm.act("RELEASE_PLL_RESET",
gtXxreset.eq(1),
If(cplllock, NextState("RESET_GTX"))
)
startup_fsm.act("RESET_GTX",
gtXxreset.eq(1),
pll_reset_timer.wait.eq(1),
If(pll_reset_timer.done, NextState("RELEASE_GTX_RESET"))
)
# Release GTX reset and wait for GTX resetdone
# (from UG476, GTX is reset on falling edge
# of gttxreset)
if rx:
startup_fsm.act("RELEASE_GTX_RESET",
Xxuserrdy.eq(1),
cdr_stable_timer.wait.eq(1),
If(Xxresetdone & cdr_stable_timer.done, NextState("DELAY_ALIGN"))
)
else:
startup_fsm.act("RELEASE_GTX_RESET",
Xxuserrdy.eq(1),
If(Xxresetdone, NextState("DELAY_ALIGN"))
)
# States exclusive to Auto Mode:
if mode == "single":
# Start delay alignment (pulse)
startup_fsm.act("DELAY_ALIGN",
Xxuserrdy.eq(1),
Xxdlysreset.eq(1),
NextState("WAIT_DELAY_ALIGN")
)
# Wait for delay alignment
startup_fsm.act("WAIT_DELAY_ALIGN",
Xxuserrdy.eq(1),
If(Xxdlysresetdone, NextState("WAIT_FIRST_PHASE_ALIGN_DONE"))
)
# Wait 2 rising edges of rxphaligndone
# (from UG476 in buffer bypass config)
startup_fsm.act("WAIT_FIRST_PHASE_ALIGN_DONE",
Xxuserrdy.eq(1),
If(Xxphaligndone_rising, NextState("WAIT_SECOND_PHASE_ALIGN_DONE"))
)
startup_fsm.act("WAIT_SECOND_PHASE_ALIGN_DONE",
Xxuserrdy.eq(1),
If(Xxphaligndone_rising, NextState("READY"))
)
# States exclusive to Manual Mode:
else:
# Start delay alignment (hold)
startup_fsm.act("DELAY_ALIGN",
Xxuserrdy.eq(1),
Xxdlysreset.eq(1),
If(Xxdlysresetdone,
# TX master: proceed to initialize phase alignment manually
(NextState("PHASE_ALIGN_INIT") if not rx else
# RX master: proceed to start phase alignment manually
NextState("PHASE_ALIGN")) if mode == "master" else
# TX/RX slave: wait for phase alignment "done" on master
NextState("WAIT_MASTER")
)
)
if mode == "slave":
# TX slave: Wait for phase alignment "done" on master
startup_fsm.act("WAIT_MASTER",
Xxuserrdy.eq(1),
If(self.master_phaligndone,
# TX slave: proceed to initialize phase alignment manually
NextState("PHASE_ALIGN_INIT") if not rx else
# RX slave: proceed to start phase alignment manually
NextState("PHASE_ALIGN")
)
)
if not rx:
# TX master/slave: Initialize phase alignment, wait rising edge on "done"
startup_fsm.act("PHASE_ALIGN_INIT",
Xxuserrdy.eq(1),
txphinit.eq(1),
If(txphinitdone, NextState("PHASE_ALIGN"))
)
# Do phase ealignment, wait rising edge on "done"
startup_fsm.act("PHASE_ALIGN",
Xxuserrdy.eq(1),
Xxphalign.eq(1),
If(Xxphaligndone_rising,
# TX/RX master: proceed to set T/RXDLYEN
NextState("FIRST_DLYEN") if mode == "master" else
# TX/RX slave: proceed to signal master
NextState("READY")
)
)
if mode == "master":
# Enable delay alignment in manual mode, wait rising edge on phase alignment "done"
startup_fsm.act("FIRST_DLYEN",
Xxuserrdy.eq(1),
Xxdlyen.eq(1),
If(Xxphaligndone_rising, NextState("WAIT_SLAVES"))
)
# Wait for phase alignment "done" on all slaves
startup_fsm.act("WAIT_SLAVES",
Xxuserrdy.eq(1),
self.master_phaligndone.eq(1),
If(self.slaves_phaligndone, NextState("SECOND_DLYEN"))
)
# Re-enable delay alignment in manual mode, wait rising edge on phase alignment "done"
startup_fsm.act("SECOND_DLYEN",
Xxuserrdy.eq(1),
Xxdlyen.eq(1),
If(Xxphaligndone_rising, NextState("READY"))
)
# Transceiver is ready, alignment can be restarted
startup_fsm.act("READY",
Xxuserrdy.eq(1),
self.done.eq(1),
If(self.restart, NextState("RESET_GTX"))
)
class GTXInitPhaseAlignment(Module):
# Interconnect of phase alignment "done" signals for Manual Mode multi-lane
def __init__(self, gtx_inits):
master_phaligndone = Signal() # Fan-out to `slave.master_phaligndone`s
slaves_phaligndone = Signal(reset=1) # ANDed from `slave.done`s
# Slave channels
for gtx_init in gtx_inits:
if gtx_init.mode == "slave":
self.comb += gtx_init.master_phaligndone.eq(master_phaligndone)
slaves_phaligndone = slaves_phaligndone & gtx_init.done
# Master channels
for gtx_init in gtx_inits:
if gtx_init.mode == "master":
self.comb += [
master_phaligndone.eq(gtx_init.master_phaligndone),
gtx_init.slaves_phaligndone.eq(slaves_phaligndone)
]

View File

@ -160,10 +160,12 @@ class DMAWriter(Module, AutoCSR):
self.comb += [
membus.cyc.eq(self.sink.stb),
membus.stb.eq(self.sink.stb),
membus.cti.eq(Mux(self.sink.last, 0b111, 0b010)),
self.sink.ack.eq(membus.ack),
membus.we.eq(1),
membus.dat_w.eq(dma.convert_signal(self.sink.data, cpu_dw//8))
]
if messages_per_dw > 1:
for i in range(dw//8):
self.comb += membus.sel[i].eq(
@ -201,8 +203,9 @@ class Analyzer(Module, AutoCSR):
self.submodules.message_encoder = MessageEncoder(
tsc, cri, self.enable.storage)
hi_wm = 64 if fifo_depth > 64 else None
self.submodules.fifo = stream.SyncFIFO(
[("data", message_len)], fifo_depth, True)
[("data", message_len)], fifo_depth, True, hi_wm=hi_wm)
self.submodules.converter = stream.Converter(
message_len, len(membus.dat_w), reverse=True,
report_valid_token_count=True)

View File

@ -35,23 +35,39 @@ class WishboneReader(Module):
# # #
bus_stb = Signal()
data_reg_loaded = Signal()
transfer_cyc = Signal(max=64, reset=64-1)
transfer_cyc_ce = Signal()
transfer_cyc_rst = Signal()
self.sync += [
If(transfer_cyc_rst,
transfer_cyc.eq(transfer_cyc.reset),
).Elif(transfer_cyc_ce,
transfer_cyc.eq(transfer_cyc - 1),
)
]
last = Signal()
self.comb += [
bus_stb.eq(self.sink.stb & (~data_reg_loaded | self.source.ack)),
# source ack (from FIFO) signals FIFO space availability
bus_stb.eq(self.sink.stb & self.source.ack),
last.eq(transfer_cyc == 0),
transfer_cyc_rst.eq(self.source.stb & self.source.ack & (self.sink.eop | last)),
transfer_cyc_ce.eq(self.source.stb & self.source.ack),
bus.cyc.eq(bus_stb),
bus.stb.eq(bus_stb),
bus.cti.eq(Mux((self.sink.eop | last), 0b111, 0b010)),
bus.adr.eq(self.sink.address),
self.sink.ack.eq(bus.ack),
self.source.stb.eq(data_reg_loaded),
]
self.sync += [
If(self.source.ack, data_reg_loaded.eq(0)),
If(bus.ack,
data_reg_loaded.eq(1),
self.source.data.eq(convert_signal(bus.dat_r, cpu_dw//8)),
self.source.eop.eq(self.sink.eop)
)
self.source.stb.eq(bus.ack),
self.source.data.eq(convert_signal(bus.dat_r, cpu_dw//8)),
self.source.last.eq(self.sink.eop | last),
self.source.eop.eq(self.sink.eop),
]
@ -341,13 +357,16 @@ class DMA(Module):
flow_enable = Signal()
self.submodules.dma = DMAReader(membus, flow_enable, cpu_dw)
self.submodules.fifo = stream.SyncFIFO(
[("data", len(membus.dat_w))], 128, True, lo_wm=64)
self.submodules.slicer = RecordSlicer(len(membus.dat_w))
self.submodules.time_offset = TimeOffset()
self.submodules.cri_master = CRIMaster()
self.cri = self.cri_master.cri
self.comb += [
self.dma.source.connect(self.slicer.sink),
self.dma.source.connect(self.fifo.sink),
self.fifo.source.connect(self.slicer.sink),
self.slicer.source.connect(self.time_offset.sink),
self.time_offset.source.connect(self.cri_master.sink)
]

View File

@ -0,0 +1,82 @@
from migen import *
from migen.genlib.cdc import MultiReg
from misoc.interconnect.csr import *
from misoc.cores.coaxpress.phy.high_speed_gtx import HostRXPHYs
from misoc.cores.coaxpress.phy.low_speed_serdes import HostTXPHYs
from artiq.gateware.rtio import rtlink
from artiq.gateware.rtio.phy.grabber import Serializer, Synchronizer
from artiq.gateware.cxp_grabber.core import CXPHostCore, ROI, ROIViewer, StreamDecoder
class CXPGrabber(Module, AutoCSR):
def __init__(
self,
refclk,
tx_pads,
rx_pads,
sys_clk_freq,
roi_engine_count=8,
res_width=16,
count_width=31,
):
assert count_width <= 31
# Trigger rtio
nbit_extra_linktrig = 1
nbit_linktrig = 2
self.trigger = rtlink.Interface(rtlink.OInterface(nbit_extra_linktrig + nbit_linktrig))
# ROI rtio
# 4 configs (x0, y0, x1, y1) per roi_engine
self.config = rtlink.Interface(rtlink.OInterface(res_width, bits_for(4*roi_engine_count-1)))
# select which roi engine can output rtio_input signal
self.gate_data = rtlink.Interface(
rtlink.OInterface(roi_engine_count),
# the extra MSB bits is for sentinel
rtlink.IInterface(count_width + 1, timestamped=False),
)
# # #
self.submodules.phy_tx = tx = HostTXPHYs(tx_pads, sys_clk_freq)
self.submodules.phy_rx = rx = HostRXPHYs(refclk, rx_pads, sys_clk_freq)
self.submodules.core = core = CXPHostCore(tx.phys[0], rx.phys[0], sys_clk_freq)
self.sync.rio += [
If(self.trigger.o.stb,
core.tx.trig_extra_linktrig.eq(self.trigger.o.data[:nbit_extra_linktrig]),
core.tx.trig_linktrig_mode.eq(self.trigger.o.data[nbit_extra_linktrig:]),
),
core.tx.trig_stb.eq(self.trigger.o.stb),
]
self.submodules.stream_decoder = stream_decoder = StreamDecoder(res_width)
self.comb += core.rx.source.connect(stream_decoder.sink)
# ROI Viewer
self.submodules.roi_viewer = ROIViewer(stream_decoder.source_pixel4x)
# ROI engines configuration and count gating
cdr = ClockDomainsRenamer("cxp_gt_rx")
roi_engines = [
cdr(ROI(stream_decoder.source_pixel4x, count_width))
for _ in range(roi_engine_count)
]
self.submodules += roi_engines
for n, roi in enumerate(roi_engines):
cfg = roi.cfg
for offset, target in enumerate([cfg.x0, cfg.y0, cfg.x1, cfg.y1]):
roi_boundary = Signal.like(target)
self.sync.rio += If(self.config.o.stb & (self.config.o.address == 4*n+offset),
roi_boundary.eq(self.config.o.data))
self.specials += MultiReg(roi_boundary, target, "cxp_gt_rx")
self.submodules.synchronizer = synchronizer = ClockDomainsRenamer({"cl" : "cxp_gt_rx"})(Synchronizer(roi_engines))
self.submodules.serializer = serializer = Serializer(synchronizer.update, synchronizer.counts, self.gate_data.i)
self.sync.rio += If(self.gate_data.o.stb, serializer.gate.eq(self.gate_data.o.data))

View File

@ -29,13 +29,14 @@ class Satellite(BaseSoC, AMPSoC):
}
mem_map.update(BaseSoC.mem_map)
def __init__(self, gateware_identifier_str=None, hw_rev="v1.1", **kwargs):
def __init__(self, gateware_identifier_str=None, efc_hw_rev="v1.1", afe_hw_rev="v1.3", **kwargs):
BaseSoC.__init__(self,
cpu_type="vexriscv",
hw_rev=hw_rev,
hw_rev=efc_hw_rev,
cpu_bus_width=64,
sdram_controller_type="minicon",
l2_size=128*1024,
l2_line_size=64,
clk_freq=125e6,
**kwargs)
AMPSoC.__init__(self)
@ -155,15 +156,27 @@ class Satellite(BaseSoC, AMPSoC):
Subsignal('mosi', Pins('fmc0:LA00_CC_N')),
Subsignal('cs_n', Pins('fmc0:LA02_P fmc0:LA01_CC_N')),
IOStandard("LVCMOS18")),
('afe_adc_spi', 0,
Subsignal('clk', Pins('fmc0:LA29_P')),
Subsignal('mosi', Pins('fmc0:LA29_N')),
Subsignal('miso', Pins('fmc0:LA30_N')),
Subsignal('cs_n', Pins('fmc0:LA28_P')),
IOStandard("LVCMOS18")),
('afe_adc_error_n', 0, Pins('fmc0:LA28_N'), IOStandard("LVCMOS18")),
]
if afe_hw_rev in ("v1.0", "v1.1", "v1.2"):
afe_adc_io = ('afe_adc_spi', 0,
Subsignal('clk', Pins('fmc0:LA29_P')),
Subsignal('mosi', Pins('fmc0:LA29_N')),
Subsignal('miso', Pins('fmc0:LA30_N')),
Subsignal('cs_n', Pins('fmc0:LA28_P')),
IOStandard("LVCMOS18"))
elif afe_hw_rev == "v1.3":
afe_adc_io = ('afe_adc_spi', 0,
Subsignal('clk', Pins('fmc0:LA29_N')),
Subsignal('mosi', Pins('fmc0:LA28_P')),
Subsignal('miso', Pins('fmc0:LA30_N')),
Subsignal('cs_n', Pins('fmc0:LA29_P')),
IOStandard("LVCMOS18"))
else:
raise ValueError("Unknown AFE hardware revision", afe_hw_rev)
shuttler_io.append(afe_adc_io)
platform.add_extension(shuttler_io)
self.submodules.converter_spi = spi2.SPIMaster(spi2.SPIInterface(self.platform.request("dac_spi", 0)))
@ -247,15 +260,18 @@ def main():
builder_args(parser)
parser.set_defaults(output_dir="artiq_efc")
parser.add_argument("-V", "--variant", default="shuttler")
parser.add_argument("--hw-rev", choices=["v1.0", "v1.1"], default="v1.1",
help="Hardware revision")
parser.add_argument("--efc-hw-rev", choices=["v1.0", "v1.1"], default="v1.1",
help="EFC hardware revision")
parser.add_argument("--afe-hw-rev", choices=["v1.0", "v1.1", "v1.2", "v1.3"],
default="v1.3", help="AFE hardware revision")
parser.add_argument("--gateware-identifier-str", default=None,
help="Override ROM identifier")
args = parser.parse_args()
argdict = dict()
argdict["gateware_identifier_str"] = args.gateware_identifier_str
argdict["hw_rev"] = args.hw_rev
argdict["efc_hw_rev"] = args.efc_hw_rev
argdict["afe_hw_rev"] = args.afe_hw_rev
soc = Satellite(**argdict)
build_artiq_soc(soc, builder_argdict(args))

View File

@ -68,6 +68,7 @@ class StandaloneBase(MiniSoC, AMPSoC):
cpu_bus_width=cpu_bus_width,
sdram_controller_type="minicon",
l2_size=128*1024,
l2_line_size=64,
integrated_sram_size=8192,
ethmac_nrxslots=4,
ethmac_ntxslots=4,
@ -176,6 +177,7 @@ class MasterBase(MiniSoC, AMPSoC):
cpu_bus_width=cpu_bus_width,
sdram_controller_type="minicon",
l2_size=128*1024,
l2_line_size=64,
integrated_sram_size=8192,
ethmac_nrxslots=4,
ethmac_ntxslots=4,
@ -427,6 +429,7 @@ class SatelliteBase(BaseSoC, AMPSoC):
cpu_bus_width=cpu_bus_width,
sdram_controller_type="minicon",
l2_size=128*1024,
l2_line_size=64,
clk_freq=rtio_clk_freq,
rtio_sys_merge=True,
**kwargs)

View File

@ -90,6 +90,7 @@ class _StandaloneBase(MiniSoC, AMPSoC):
cpu_bus_width=64,
sdram_controller_type="minicon",
l2_size=128*1024,
l2_line_size=64,
integrated_sram_size=8192,
ethmac_nrxslots=4,
ethmac_ntxslots=4,
@ -127,6 +128,7 @@ class _StandaloneBase(MiniSoC, AMPSoC):
Instance("BUFG", i_I=cdr_clk, o_O=cdr_clk_buf)
]
self.platform.add_period_constraint(cdr_clk_out, 8.)
self.crg.configure(cdr_clk_buf)
self.submodules += SMAClkinForward(self.platform)
@ -187,6 +189,7 @@ class _MasterBase(MiniSoC, AMPSoC):
cpu_bus_width=64,
sdram_controller_type="minicon",
l2_size=128*1024,
l2_line_size=64,
integrated_sram_size=8192,
ethmac_nrxslots=4,
ethmac_ntxslots=4,
@ -335,6 +338,7 @@ class _SatelliteBase(BaseSoC, AMPSoC):
cpu_bus_width=64,
sdram_controller_type="minicon",
l2_size=128*1024,
l2_line_size=64,
integrated_sram_size=8192,
clk_freq=clk_freq,
rtio_sys_merge=True,

View File

@ -0,0 +1,51 @@
from migen import *
from misoc.cores.coaxpress.common import char_width, KCode, word_width
from math import ceil
from collections import namedtuple
_WORDLAYOUT = namedtuple("WordLayout", ["data", "k", "stb", "eop"])
def MonoPixelPacketGenerator(
x_size,
y_size,
pixel_width,
with_eol_marked=False,
stb_line_marker=False,
):
words_per_image_line = ceil(x_size * pixel_width / word_width)
packet = []
for _ in range(y_size):
packed = 0
for x in range(x_size):
# full white pixel
gray = (2**pixel_width) - 1
packed += gray << x * pixel_width
# Line marker
packet += [
_WORDLAYOUT(
data=Replicate(KCode["stream_marker"], 4),
k=Replicate(1, 4),
stb=1 if stb_line_marker else 0,
eop=0,
),
_WORDLAYOUT(
data=Replicate(C(0x02, char_width), 4),
k=Replicate(0, 4),
stb=1 if stb_line_marker else 0,
eop=0,
),
]
for i in range(words_per_image_line):
serialized = (packed & (0xFFFF_FFFF << i * word_width)) >> i * word_width
eop = 1 if ((i == words_per_image_line - 1) and with_eol_marked) else 0
packet.append(
_WORDLAYOUT(
data=C(serialized, word_width), k=Replicate(0, 4), stb=1, eop=eop
),
)
return packet

View File

@ -0,0 +1,100 @@
from migen import *
from artiq.gateware.cxp_grabber.frame import PixelParser
from artiq.gateware.cxp_grabber.core import ROI
from artiq.gateware.test.cxp_grabber.packet_generator import MonoPixelPacketGenerator
import unittest
class DUT(Module):
def __init__(self, res_width, count_width):
self.parser = PixelParser(res_width)
self.roi = ROI(self.parser.source_pixel4x, count_width)
self.submodules += self.parser, self.roi
class Testbench:
def __init__(self, res_width, count_width):
self.dut = DUT(res_width, count_width)
self.fragment = self.dut.get_fragment()
def write_frame_info(self, x_size, y_size, pixel_code):
yield self.dut.parser.x_size.eq(x_size)
yield self.dut.parser.y_size.eq(y_size)
yield self.dut.parser.pixel_format_code.eq(pixel_code)
yield
def write_frame(self, packet):
for i, word in enumerate(packet):
yield self.dut.parser.sink.data.eq(word.data)
yield self.dut.parser.sink.stb.eq(word.stb)
yield self.dut.parser.sink.eop.eq(word.eop)
yield
yield self.dut.parser.sink.stb.eq(0) # prevent accidental stb
def write_roi_cofig(self, x0, y0, x1, y1):
yield self.dut.roi.cfg.x0.eq(x0)
yield self.dut.roi.cfg.y0.eq(y0)
yield self.dut.roi.cfg.x1.eq(x1)
yield self.dut.roi.cfg.y1.eq(y1)
yield
def fetch_roi_output(self):
return (yield self.dut.roi.out.count)
def delay(self, cycle):
for _ in range(cycle):
yield
def run(self, gen):
run_simulation(self.fragment, gen)
class TestPixelParser(unittest.TestCase):
def test_run(self):
tb = Testbench(16, 31)
def gen(x_size, y_size, pixel_width, x0, y0, x1, y1):
pixel_code = {
8: 0x0101,
10: 0x0102,
12: 0x0103,
14: 0x0104,
16: 0x0105,
}
expected_count = (x1 - x0) * (y1 - y0) * ((2**pixel_width) - 1)
yield from tb.write_roi_cofig(x0, y0, x1, y1)
packet = MonoPixelPacketGenerator(
x_size, y_size, pixel_width, with_eol_marked=True
)
yield from tb.write_frame_info(x_size, y_size, pixel_code[pixel_width])
yield from tb.write_frame(packet)
# there is a 6 cycle delay between stbing the last pixel word and roi update is ready
for _ in range(6):
yield
# verify the pixel parser using the roi result
self.assertEqual((yield from tb.fetch_roi_output()), expected_count)
for pixel_width, pattern_cnt in [[8, 4], [10, 16], [12, 8], [14, 16], [16, 2]]:
# start from pattern_cnt to ensure ROI got some pixels to work with
for res_size in range(pattern_cnt * 1, pattern_cnt * 2):
tb.run(
gen(
res_size,
res_size,
pixel_width,
0,
0,
res_size - 1,
res_size - 1,
)
)
if __name__ == "__main__":
unittest.main()

View File

@ -31,23 +31,53 @@ class EntryTreeWidget(QtWidgets.QTreeWidget):
self.setHorizontalScrollMode(self.ScrollMode.ScrollPerPixel)
self.setVerticalScrollMode(self.ScrollMode.ScrollPerPixel)
self.setStyleSheet("QTreeWidget {background: " +
self.palette().midlight().color().name() + " ;}")
self.viewport().installEventFilter(WheelFilter(self.viewport(), True))
self._groups = dict()
self._arg_to_widgets = dict()
self._arguments = dict()
self.set_background_color()
self.gradient = QtGui.QLinearGradient(
0, 0, 0, QtGui.QFontMetrics(self.font()).lineSpacing() * 2.5)
self.gradient.setColorAt(0, self.palette().base().color())
self.gradient.setColorAt(1, self.palette().midlight().color())
self.set_gradient_color()
self.set_group_color()
self.bottom_item = QtWidgets.QTreeWidgetItem()
self.addTopLevelItem(self.bottom_item)
def set_background_color(self, color=None):
if color is None:
base_color = self.palette().base().color()
else:
base_color = QtGui.QColor(color)
self.palette().setColor(QtGui.QPalette.ColorRole.Base, base_color)
def set_gradient_color(self, color=None):
if color is None:
start_color = self.palette().base().color()
end_color = self.palette().midlight().color()
else:
start_color = QtGui.QColor(color)
end_color = start_color.toHsv()
end_color.setHsv(end_color.hue(),
int(end_color.saturation() * 0.8),
min(255, int(end_color.value() * 1.2)))
self.gradient.setColorAt(0, start_color)
self.gradient.setColorAt(1, end_color)
for widgets in self._arg_to_widgets.values():
for col in range(3):
widgets["widget_item"].setBackground(col, self.gradient)
def set_group_color(self, color=None):
if color is None:
group_color = self.palette().mid().color()
else:
group_color = QtGui.QColor(color)
for group in self._groups.values():
for col in range(3):
group.setBackground(col, group_color)
def set_argument(self, key, argument):
self._arguments[key] = argument
widgets = dict()
@ -63,8 +93,7 @@ class EntryTreeWidget(QtWidgets.QTreeWidget):
widgets["entry"] = entry
widgets["widget_item"] = widget_item
for col in range(3):
widget_item.setBackground(col, self.gradient)
self.set_gradient_color()
font = widget_item.font(0)
font.setBold(True)
widget_item.setFont(0, font)
@ -108,12 +137,12 @@ class EntryTreeWidget(QtWidgets.QTreeWidget):
return self._groups[key]
group = QtWidgets.QTreeWidgetItem([key])
for col in range(3):
group.setBackground(col, self.palette().mid())
font = group.font(col)
font.setBold(True)
group.setFont(col, font)
self.insertTopLevelItem(self.indexFromItem(self.bottom_item).row(), group)
self._groups[key] = group
self.set_group_color()
return group
def _disable_other_scans(self, current_key):

View File

@ -1,321 +0,0 @@
from artiq.coredevice.ad9834 import (
AD9834_B28,
AD9834_DIV2,
AD9834_FSEL,
AD9834_HLB,
AD9834_MODE,
AD9834_OPBITEN,
AD9834_PIN_SW,
AD9834_PSEL,
AD9834_RESET,
AD9834_SIGN_PIB,
AD9834_SLEEP1,
AD9834_SLEEP12,
FREQ_REGS,
PHASE_REGS,
)
from artiq.experiment import *
from artiq.language.units import MHz
from artiq.test.hardware_testbench import ExperimentCase
class AD9834Exp(EnvExperiment):
def build(self, runner):
self.setattr_device("core")
self.dev = self.get_device("dds0")
self.runner = runner
def run(self):
getattr(self, self.runner)()
@kernel
def instantiate(self):
pass
@kernel
def init(self):
self.core.break_realtime()
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)
@kernel
def set_frequency_reg_fail2(self):
self.core.break_realtime()
self.dev.set_frequency_reg(FREQ_REGS[0], 37.6 * MHz)
@kernel
def set_frequency_reg(self):
self.core.break_realtime()
self.dev.init()
self.dev.set_frequency_reg(FREQ_REGS[1], 19 * MHz)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def set_frequency_reg_msb(self):
self.core.break_realtime()
self.dev.init()
self.dev.ctrl_reg |= AD9834_B28
self.dev.set_frequency_reg_msb(FREQ_REGS[0], 0x1111)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def set_frequency_reg_lsb(self):
self.core.break_realtime()
self.dev.init()
self.dev.ctrl_reg |= AD9834_B28 | AD9834_HLB
self.dev.set_frequency_reg_lsb(FREQ_REGS[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)
@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)
@kernel
def set_phase_reg_fail(self):
self.core.break_realtime()
self.dev.set_phase_reg(19, 0x123)
@kernel
def set_phase_reg(self):
self.core.break_realtime()
self.dev.init()
self.dev.set_phase_reg(PHASE_REGS[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)
@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)
@kernel
def sleep_dac_powerdown(self):
self.core.break_realtime()
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.dev.sleep(clk_dis=True)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def sleep(self):
self.core.break_realtime()
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)
@kernel
def sign_bit_high_z(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_OPBITEN
self.dev.config_sign_bit_out()
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def sign_bit_msb_2(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_MODE | AD9834_SIGN_PIB | AD9834_DIV2
self.dev.config_sign_bit_out(msb_2=True)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def sign_bit_msb(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_MODE | AD9834_SIGN_PIB
self.dev.config_sign_bit_out(msb=True)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def sign_bit_comp_out(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_MODE
self.dev.config_sign_bit_out(comp_out=True)
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def enable_triangular_waveform(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_OPBITEN
self.dev.enable_triangular_waveform()
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
@kernel
def disable_triangular_waveform(self):
self.core.break_realtime()
self.dev.ctrl_reg |= AD9834_MODE
self.dev.disable_triangular_waveform()
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
class AD9834Test(ExperimentCase):
def test_instantiate(self):
self.execute(AD9834Exp, "instantiate")
def test_init(self):
self.execute(AD9834Exp, "init")
spi_freq = self.dataset_mgr.get("spi_freq")
clk_freq = self.dataset_mgr.get("clk_freq")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(spi_freq, 10 * MHz)
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")
with self.assertRaises(AssertionError):
self.execute(AD9834Exp, "set_frequency_reg_fail2")
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)
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)
def test_set_frequency_reg_lsb(self):
self.execute(AD9834Exp, "set_frequency_reg_lsb")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
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)
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)
def test_set_phase_reg_fail(self):
with self.assertRaises(ValueError):
self.execute(AD9834Exp, "set_phase_reg_fail")
def test_set_phase_reg(self):
self.execute(AD9834Exp, "set_phase_reg")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
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)
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)
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)
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)
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)
def test_awake(self):
self.execute(AD9834Exp, "awake")
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
self.assertEqual(ctrl_reg, 0x0000)
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)
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)
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)
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
)
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)
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)

View File

@ -672,3 +672,34 @@ class _IntBoundary(EnvExperiment):
class IntBoundaryTest(ExperimentCase):
def test_int_boundary(self):
self.create(_IntBoundary).run()
@nac3
class _BoolListType(EnvExperiment):
x: Kernel[list[bool]]
y: Kernel[list[bool]]
@rpc
def assert_bool(self, obj: bool):
assert isinstance(obj, bool)
def build(self):
self.setattr_device("core")
self.x = [True]
self.y = [numpy.True_]
@kernel
def run_bool(self):
self.assert_bool(self.x[0])
@kernel
def run_numpy_bool(self):
self.assert_bool(self.y[0])
class BoolListTypeTest(ExperimentCase):
def test_bool_list(self):
self.create(_BoolListType).run_bool()
def test_np_bool_list(self):
self.create(_BoolListType).run_numpy_bool()

View File

@ -635,11 +635,13 @@ class CoredeviceTest(ExperimentCase):
def execute_and_test_in_log(self, experiment, string):
core_addr = self.device_mgr.get_desc("core")["arguments"]["host"]
mgmt = CommMgmt(core_addr)
mgmt.clear_log()
self.execute(experiment)
log = mgmt.get_log()
self.assertIn(string, log)
mgmt.close()
try:
mgmt.clear_log()
self.execute(experiment)
log = mgmt.get_log()
self.assertIn(string, log)
finally:
mgmt.close()
def test_sequence_error(self):
self.execute_and_test_in_log(SequenceError, "RTIO sequence error")
@ -878,13 +880,6 @@ class DMATest(ExperimentCase):
self.assertLess(dt/count, 11*us)
def test_dma_playback_time(self):
# Skip on Kasli until #946 is resolved.
try:
# hack to detect Kasli.
self.device_mgr.get_desc("ad9914dds0")
except KeyError:
raise unittest.SkipTest("skipped on Kasli for now")
exp = self.create(_DMA)
is_zynq = exp.core.target == "cortexa9"
count = 20000

View File

@ -0,0 +1,20 @@
# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t
# RUN: OutputCheck %s --file-to-check=%t
from artiq.experiment import kernel
class contextmgr:
def __enter__(self):
pass
@kernel
def __exit__(self, n1, n2, n3):
pass
c = contextmgr()
@kernel
def entrypoint():
# CHECK-L: ${LINE:+1}: error: function '__enter__[rpc2 #](...)->NoneType' must be a @kernel
with c:
pass

View File

@ -102,7 +102,7 @@ def short_format(v, metadata={}):
return v_str
elif np.issubdtype(t, np.bool_):
return str(v)
elif np.issubdtype(t, np.unicode_):
elif np.issubdtype(t, np.str_):
return "\"" + elide(v, 50) + "\""
elif t is np.ndarray:
v_t = np.divide(v, scale)

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

@ -85,7 +85,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
@ -171,7 +171,6 @@ html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".

View File

@ -85,12 +85,6 @@ RF generation drivers
.. automodule:: artiq.coredevice.ad9914
:members:
:mod:`artiq.coredevice.ad9834` module
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.ad9834
:members:
:mod:`artiq.coredevice.mirny` module
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -164,3 +158,10 @@ Miscellaneous
.. automodule:: artiq.coredevice.grabber
:members:
:mod:`artiq.coredevice.cxp_grabber` module
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.cxp_grabber
:members:

View File

@ -6,26 +6,30 @@ ARTIQ can be installed using the Nix package manager on Linux, and using the MSY
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,13 +38,14 @@ 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: [
(pkgs.python3.withPackages(ps : [
# List desired Python packages here.
artiq.artiq
#ps.paramiko # needed if and only if flashing boards remotely (artiq_flash -H)
@ -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::
@ -182,9 +198,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+.

28
flake.lock generated
View File

@ -48,11 +48,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1740143775,
"narHash": "sha256-sibL8PPLE4HuPssN5Gu86/RFyQHQh/uhFVSWu+ImaaE=",
"lastModified": 1743494143,
"narHash": "sha256-OxeNED91hCgVsbgwRUpmP5BJ4dtilMxF2otGsQ+UBaQ=",
"ref": "refs/heads/master",
"rev": "f0397b7e09b5b189bbb2df6390ec3d81d8177391",
"revCount": 1554,
"rev": "e4f6fbeeebd8d888f2a12d5be027c9394a37ad89",
"revCount": 1583,
"type": "git",
"url": "https://git.m-labs.hk/m-labs/nac3.git"
},
@ -63,11 +63,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1738680400,
"narHash": "sha256-ooLh+XW8jfa+91F1nhf9OF7qhuA/y1ChLx6lXDNeY5U=",
"lastModified": 1741851582,
"narHash": "sha256-cPfs8qMccim2RBgtKGF+x9IBCduRvd/N5F4nYpU0TVE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "799ba5bffed04ced7067a91798353d360788b30d",
"rev": "6607cf789e541e7873d40d3a8f7815ea92204f32",
"type": "github"
},
"original": {
@ -117,11 +117,11 @@
]
},
"locked": {
"lastModified": 1734267097,
"narHash": "sha256-aWg7XDiOlWnkXfDbKrBn9ITR46/JXfndvYHxFJ1vN78=",
"lastModified": 1742007426,
"narHash": "sha256-Rs421WBPhORJoKXhuEuRBqDLBE2pdn/pSgRRSB7mJKY=",
"owner": "m-labs",
"repo": "sipyco",
"rev": "430978ada3fefe32de01f1b884b3031e48aaef96",
"rev": "34226114510e4f32409c8d99458602e745c80c52",
"type": "github"
},
"original": {
@ -149,11 +149,11 @@
"src-misoc": {
"flake": false,
"locked": {
"lastModified": 1739436988,
"narHash": "sha256-zEihEV6kqRtrZWyu7uCNyHOXE/rluVloPuT4ECYVJ+g=",
"lastModified": 1741935500,
"narHash": "sha256-rbh4++/M1FQw57fUKow1MNXpmFfguUH5LlxfklDvUjs=",
"ref": "refs/heads/master",
"rev": "e3f4fd040b90b05d580bf578ca49244f0b7d861a",
"revCount": 2483,
"rev": "ebeff99217e6e23eb410d7c208d1f17443a57322",
"revCount": 2496,
"submodules": true,
"type": "git",
"url": "https://github.com/m-labs/misoc.git"

View File

@ -132,7 +132,7 @@
# keep llvm_x in sync with nac3
propagatedBuildInputs =
[pkgs.llvm_14 nac3.packages.x86_64-linux.nac3artiq-pgo sipyco.packages.x86_64-linux.sipyco pkgs.qt6.qtsvg artiq-comtools.packages.x86_64-linux.artiq-comtools]
[pkgs.llvm_16 nac3.packages.x86_64-linux.nac3artiq-pgo sipyco.packages.x86_64-linux.sipyco pkgs.qt6.qtsvg artiq-comtools.packages.x86_64-linux.artiq-comtools]
++ (with pkgs.python3Packages; [pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial h5py pyqt6 qasync tqdm lmdb jsonschema platformdirs]);
dontWrapQtApps = true;
@ -157,7 +157,7 @@
# FIXME: automatically propagate llvm_x dependency
# cacert is required in the check stage only, as certificates are to be
# obtained from system elsewhere
nativeCheckInputs = [pkgs.llvm_14 pkgs.cacert];
nativeCheckInputs = [pkgs.llvm_16 pkgs.cacert];
checkPhase = ''
python -m unittest discover -v artiq.test
'';
@ -215,7 +215,7 @@
vivado = pkgs.buildFHSEnv {
name = "vivado";
targetPkgs = vivadoDeps;
profile = "set -e; source /opt/Xilinx/Vivado/2022.2/settings64.sh";
profile = "set -e; source /opt/Xilinx/Vivado/2024.2/settings64.sh";
runScript = "vivado";
};
@ -238,9 +238,9 @@
nativeBuildInputs = [
(pkgs.python3.withPackages (ps: [migen misoc (artiq.withExperimentalFeatures experimentalFeatures) ps.packaging]))
rust
pkgs.llvmPackages_14.clang-unwrapped
pkgs.llvm_14
pkgs.lld_14
pkgs.llvmPackages_16.clang-unwrapped
pkgs.llvm_16
pkgs.lld_16
vivado
rustPlatform.cargoSetupHook
];
@ -439,9 +439,9 @@
[
git
lit
lld_14
llvm_14
llvmPackages_14.clang-unwrapped
lld_16
llvm_16
llvmPackages_16.clang-unwrapped
pdf2svg
python3Packages.sphinx
@ -474,9 +474,9 @@
packages = [
rust
pkgs.llvmPackages_14.clang-unwrapped
pkgs.llvm_14
pkgs.lld_14
pkgs.llvmPackages_16.clang-unwrapped
pkgs.llvm_16
pkgs.lld_16
packages.x86_64-linux.vivado
packages.x86_64-linux.openocd-bscanspi
@ -525,7 +525,7 @@
]
++ ps.paramiko.optional-dependencies.ed25519
))
pkgs.llvm_14
pkgs.llvm_16
pkgs.openssh
packages.x86_64-linux.openocd-bscanspi # for the bscanspi bitstreams
];
@ -555,11 +555,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
)