forked from M-Labs/artiq
Compare commits
87 Commits
c48598fc32
...
180a0fface
Author | SHA1 | Date | |
---|---|---|---|
|
180a0fface | ||
fb08762a30 | |||
d4f54f0b68 | |||
e0310caa2f | |||
a4e262bec6 | |||
14b62dc2f1 | |||
e186a983d5 | |||
ed24bcfb69 | |||
3a9a10a41c | |||
49ed898900 | |||
a264fa3b70 | |||
adbe8c1ec0 | |||
72095fa2f6 | |||
cdacbf71c5 | |||
1533aba3ea | |||
bd1759dbf9 | |||
7e1da9da72 | |||
5e651308bb | |||
f213b8e1ca | |||
9fe9abd7fb | |||
90eb59c54d | |||
9a80818e97 | |||
9f1a8e7d4f | |||
7f007b41cb | |||
9d2897de5f | |||
1794c939b2 | |||
519fdfa864 | |||
c5473a8a02 | |||
9c94ba7f89 | |||
76de902f2c | |||
99bf703851 | |||
4b60cd86c5 | |||
466f8943a4 | |||
f98ce34480 | |||
8bd03f5bb4 | |||
06a2660f9c | |||
d0a2519b98 | |||
690292f467 | |||
d80901d216 | |||
3d22c50837 | |||
6c5ff1e64f | |||
5379bd9d38 | |||
66cec318a5 | |||
2a0c064de7 | |||
a0886b602e | |||
c0f5d8ba06 | |||
16d98279d5 | |||
bc94614395 | |||
a0fff20855 | |||
6bce611ef8 | |||
994f60edd5 | |||
47a13e4ede | |||
bb6ff80777 | |||
7702987436 | |||
a2383def27 | |||
c9e2ba1536 | |||
c1d3314ca1 | |||
d1177836f2 | |||
d9cc35c724 | |||
c8d0ab9afe | |||
ae12270363 | |||
a360628e95 | |||
9558dd20e1 | |||
d4bd4e5879 | |||
c50f3a1c12 | |||
0f0f030267 | |||
c1f2ff3717 | |||
7619dff08d | |||
415895413c | |||
0da4dbf2f2 | |||
ebb4058d09 | |||
|
2b48822fe3 | ||
8ce6048c96 | |||
33c91d73bb | |||
d49ef555d1 | |||
0bd9bcf28c | |||
63db4af1fc | |||
626c709b4e | |||
8ff433596b | |||
dc21f0b6dd | |||
087eb514c1 | |||
|
7534a8fe04 | ||
7669cfce3d | |||
99fe642cab | |||
d8184cfb56 | |||
5b52f187d0 | |||
9c99d116bb |
@ -25,7 +25,7 @@ Core technologies employed include `Python <https://www.python.org/>`_, `Migen <
|
|||||||
License
|
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
|
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
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -6,24 +6,30 @@ Release notes
|
|||||||
ARTIQ-9 (Unreleased)
|
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:
|
* Dashboard:
|
||||||
- Experiment windows can have different colors, selected by the user.
|
- 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
|
- Schedule display columns can now be reordered and shown/hidden using the table
|
||||||
header context menu.
|
header context menu.
|
||||||
- State files are now automatically backed up upon successful loading.
|
- 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.
|
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``.
|
* 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.
|
* 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.
|
* Updated Rust support for Zynq-7000 firmware.
|
||||||
* New core device driver for the AD9834 DDS, tested with the ZonRi Technology Co., Ltd. AD9834-Module.
|
* Qt6 support.
|
||||||
* Support for coredevice reflashing through the new ``flash`` tool in ``artiq_coremgmt``.
|
* Python 3.12 support.
|
||||||
* ``artiq_coremgmt`` now supports configuring satellites.
|
* The Zadig driver installer was added to the MSYS2 offline installer.
|
||||||
* ``artiq.coredevice.fmcdio_vhdci_eem`` has been removed.
|
* ``artiq.coredevice.fmcdio_vhdci_eem`` has been removed.
|
||||||
|
|
||||||
ARTIQ-8
|
ARTIQ-8
|
||||||
|
@ -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)
|
|
@ -559,9 +559,17 @@ class AD9910:
|
|||||||
After the SPI transfer, the shared IO update pin is pulsed to
|
After the SPI transfer, the shared IO update pin is pulsed to
|
||||||
activate the data.
|
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.
|
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 ftw: Frequency tuning word: 32-bit.
|
||||||
:param pow_: Phase tuning word: 16-bit unsigned.
|
:param pow_: Phase tuning word: 16-bit unsigned.
|
||||||
:param asf: Amplitude scale factor: 14-bit unsigned.
|
:param asf: Amplitude scale factor: 14-bit unsigned.
|
||||||
|
@ -728,15 +728,7 @@ def decoded_dump_to_target(manager, devices, dump, uniform_interval):
|
|||||||
logger.warning("unable to determine DDS sysclk")
|
logger.warning("unable to determine DDS sysclk")
|
||||||
dds_sysclk = 3e9 # guess
|
dds_sysclk = 3e9 # guess
|
||||||
|
|
||||||
if isinstance(dump.messages[-1], StoppedMessage):
|
messages = sorted(dump.messages, key=get_message_time)
|
||||||
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)
|
|
||||||
|
|
||||||
channel_handlers = create_channel_handlers(
|
channel_handlers = create_channel_handlers(
|
||||||
manager, devices, ref_period,
|
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)
|
interval = manager.get_channel("interval", 64, ty=WaveformType.ANALOG)
|
||||||
slack = manager.get_channel("rtio_slack", 64, ty=WaveformType.ANALOG)
|
slack = manager.get_channel("rtio_slack", 64, ty=WaveformType.ANALOG)
|
||||||
|
|
||||||
|
stopped_messages = []
|
||||||
|
|
||||||
manager.set_time(0)
|
manager.set_time(0)
|
||||||
start_time = 0
|
start_time = 0
|
||||||
for m in messages:
|
for m in messages:
|
||||||
@ -762,7 +756,10 @@ def decoded_dump_to_target(manager, devices, dump, uniform_interval):
|
|||||||
manager.set_start_time(start_time)
|
manager.set_start_time(start_time)
|
||||||
t0 = start_time
|
t0 = start_time
|
||||||
for i, message in enumerate(messages):
|
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)
|
t = get_message_time(message)
|
||||||
if t >= 0:
|
if t >= 0:
|
||||||
if uniform_interval:
|
if uniform_interval:
|
||||||
@ -776,3 +773,9 @@ def decoded_dump_to_target(manager, devices, dump, uniform_interval):
|
|||||||
if isinstance(message, OutputMessage):
|
if isinstance(message, OutputMessage):
|
||||||
slack.set_value_double(
|
slack.set_value_double(
|
||||||
(message.timestamp - message.rtio_counter)*ref_period)
|
(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)
|
||||||
|
@ -824,8 +824,13 @@ class CommKernel:
|
|||||||
python_exn_type = embedding_map.retrieve_object(core_exn.id)
|
python_exn_type = embedding_map.retrieve_object(core_exn.id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
python_exn = python_exn_type(
|
message = nested_exceptions[0][1].format(*nested_exceptions[0][2])
|
||||||
nested_exceptions[-1][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:
|
except Exception as ex:
|
||||||
python_exn = RuntimeError(
|
python_exn = RuntimeError(
|
||||||
f"Exception type={python_exn_type}, which couldn't be "
|
f"Exception type={python_exn_type}, which couldn't be "
|
||||||
|
299
artiq/coredevice/cxp_grabber.py
Normal file
299
artiq/coredevice/cxp_grabber.py
Normal 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())
|
@ -2,6 +2,7 @@ import builtins
|
|||||||
from numpy.linalg import LinAlgError
|
from numpy.linalg import LinAlgError
|
||||||
from artiq.language.core import nac3, UnwrapNoneError
|
from artiq.language.core import nac3, UnwrapNoneError
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file provides class definition for all the exceptions declared in `EmbeddingMap` in `artiq.language.embedding_map`
|
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
|
For ARTIQ specific exceptions, inherit from `Exception` class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
AssertionError = builtins.AssertionError
|
AssertionError = builtins.AssertionError
|
||||||
AttributeError = builtins.AttributeError
|
AttributeError = builtins.AttributeError
|
||||||
IndexError = builtins.IndexError
|
IndexError = builtins.IndexError
|
||||||
@ -23,6 +25,7 @@ ValueError = builtins.ValueError
|
|||||||
ZeroDivisionError = builtins.ZeroDivisionError
|
ZeroDivisionError = builtins.ZeroDivisionError
|
||||||
OSError = builtins.OSError
|
OSError = builtins.OSError
|
||||||
|
|
||||||
|
|
||||||
@nac3
|
@nac3
|
||||||
class RTIOUnderflow(Exception):
|
class RTIOUnderflow(Exception):
|
||||||
"""Raised when the CPU or DMA core fails to submit a RTIO event early
|
"""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."""
|
"""Raised when RTIO PLL has lost lock."""
|
||||||
artiq_builtin = True
|
artiq_builtin = True
|
||||||
|
|
||||||
|
|
||||||
@nac3
|
@nac3
|
||||||
class I2CError(Exception):
|
class I2CError(Exception):
|
||||||
"""Raised when a I2C transaction fails."""
|
"""Raised when a I2C transaction fails."""
|
||||||
@ -90,7 +94,13 @@ class SPIError(Exception):
|
|||||||
artiq_builtin = True
|
artiq_builtin = True
|
||||||
|
|
||||||
|
|
||||||
|
@nac3
|
||||||
class UnwrapNoneError(Exception):
|
class UnwrapNoneError(Exception):
|
||||||
"""Raised when unwrapping a none Option."""
|
"""Raised when unwrapping a none Option."""
|
||||||
artiq_builtin = True
|
artiq_builtin = True
|
||||||
|
|
||||||
|
|
||||||
|
@nac3
|
||||||
|
class CXPError(Exception):
|
||||||
|
"""Raised when CXP transaction fails."""
|
||||||
|
artiq_builtin = True
|
||||||
|
@ -79,18 +79,12 @@ class _ArgumentEditor(EntryTreeWidget):
|
|||||||
argument["desc"] = procdesc
|
argument["desc"] = procdesc
|
||||||
argument["state"] = state
|
argument["state"] = state
|
||||||
self.update_argument(name, argument)
|
self.update_argument(name, argument)
|
||||||
self.dock.apply_colors()
|
self.dock.apply_window_color()
|
||||||
|
|
||||||
def apply_color(self, palette, color):
|
def apply_color(self, color):
|
||||||
self.setPalette(palette)
|
self.set_background_color(color)
|
||||||
for child in self.findChildren(QtWidgets.QWidget):
|
self.set_gradient_color(color)
|
||||||
child.setPalette(palette)
|
self.set_group_color(color)
|
||||||
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))
|
|
||||||
|
|
||||||
# Hooks that allow user-supplied argument editors to react to imminent user
|
# Hooks that allow user-supplied argument editors to react to imminent user
|
||||||
# actions. Here, we always keep the manager-stored submission arguments
|
# 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.argeditor = editor_class(self.manager, self, self.expurl)
|
||||||
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
|
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
|
||||||
self.argeditor.restore_state(argeditor_state)
|
self.argeditor.restore_state(argeditor_state)
|
||||||
self.apply_colors()
|
self.apply_window_color()
|
||||||
|
|
||||||
def contextMenuEvent(self, event):
|
def contextMenuEvent(self, event):
|
||||||
menu = QtWidgets.QMenu(self)
|
menu = QtWidgets.QMenu(self)
|
||||||
@ -353,22 +347,25 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||||||
title=f"Select {key.replace('_', ' ').title()} color")
|
title=f"Select {key.replace('_', ' ').title()} color")
|
||||||
if color.isValid():
|
if color.isValid():
|
||||||
self.manager.set_color(self.expurl, key, color.name())
|
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)
|
colors = self.manager.get_colors(self.expurl)
|
||||||
if colors is None:
|
if not colors:
|
||||||
palette = QtWidgets.QApplication.palette()
|
return
|
||||||
colors = {
|
self.setStyle(_ColoredTitleBar(colors["title_bar"]))
|
||||||
"window": palette.color(QtGui.QPalette.ColorRole.Window).name(),
|
|
||||||
"title_bar": palette.color(QtGui.QPalette.ColorRole.Highlight).name(),
|
def apply_window_color(self):
|
||||||
}
|
colors = self.manager.get_colors(self.expurl)
|
||||||
self.manager.colors[self.expurl] = colors
|
if not colors:
|
||||||
|
return
|
||||||
colors["window_text"] = "#000000" if QtGui.QColor(
|
colors["window_text"] = "#000000" if QtGui.QColor(
|
||||||
colors["window"]).lightness() > 128 else "#FFFFFF"
|
colors["window"]).lightness() > 128 else "#FFFFFF"
|
||||||
self.modify_palette(colors)
|
self.modify_palette(colors)
|
||||||
self.setStyle(_ColoredTitleBar(colors["title_bar"]))
|
self.argeditor.apply_color(colors["window"])
|
||||||
self.argeditor.apply_color(self.palette(), (colors["window"]))
|
|
||||||
|
|
||||||
def modify_palette(self, colors):
|
def modify_palette(self, colors):
|
||||||
palette = self.palette()
|
palette = self.palette()
|
||||||
@ -381,8 +378,13 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||||||
self.setPalette(palette)
|
self.setPalette(palette)
|
||||||
|
|
||||||
def reset_colors(self):
|
def reset_colors(self):
|
||||||
|
colors = self.manager.get_colors(self.expurl)
|
||||||
|
if not colors:
|
||||||
|
return
|
||||||
self.manager.reset_colors(self.expurl)
|
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):
|
async def _recompute_sched_options_task(self):
|
||||||
try:
|
try:
|
||||||
@ -511,6 +513,24 @@ class _QuickOpenDialog(QtWidgets.QDialog):
|
|||||||
self.close()
|
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:
|
class ExperimentManager:
|
||||||
#: Global registry for custom argument editor classes, indexed by the experiment
|
#: Global registry for custom argument editor classes, indexed by the experiment
|
||||||
#: `argument_ui` string; can be populated by dashboard plugins such as ndscan.
|
#: `argument_ui` string; can be populated by dashboard plugins such as ndscan.
|
||||||
@ -525,12 +545,12 @@ class ExperimentManager:
|
|||||||
self.schedule_ctl = schedule_ctl
|
self.schedule_ctl = schedule_ctl
|
||||||
self.experiment_db_ctl = experiment_db_ctl
|
self.experiment_db_ctl = experiment_db_ctl
|
||||||
|
|
||||||
self.dock_states = dict()
|
self.dock_states = _LRUDict()
|
||||||
self.submission_scheduling = dict()
|
self.submission_scheduling = _LRUDict()
|
||||||
self.submission_options = dict()
|
self.submission_options = _LRUDict()
|
||||||
self.submission_arguments = dict()
|
self.submission_arguments = _LRUDict()
|
||||||
self.argument_ui_names = dict()
|
self.argument_ui_names = _LRUDict()
|
||||||
self.colors = dict()
|
self.colors = _LRUDict()
|
||||||
|
|
||||||
self.datasets = dict()
|
self.datasets = dict()
|
||||||
dataset_sub.add_setmodel_callback(self.set_dataset_model)
|
dataset_sub.add_setmodel_callback(self.set_dataset_model)
|
||||||
@ -563,7 +583,7 @@ class ExperimentManager:
|
|||||||
self.colors[expurl][key] = value
|
self.colors[expurl][key] = value
|
||||||
|
|
||||||
def get_colors(self, expurl):
|
def get_colors(self, expurl):
|
||||||
return self.colors.get(expurl)
|
return self.colors.get(expurl, {})
|
||||||
|
|
||||||
def reset_colors(self, expurl):
|
def reset_colors(self, expurl):
|
||||||
if expurl in self.colors:
|
if expurl in self.colors:
|
||||||
@ -678,7 +698,12 @@ class ExperimentManager:
|
|||||||
self.open_experiments[expurl] = dock
|
self.open_experiments[expurl] = dock
|
||||||
dock.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
|
dock.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
|
||||||
self.main_window.centralWidget().addSubWindow(dock)
|
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.show()
|
||||||
dock.sigClosed.connect(partial(self.on_dock_closed, expurl))
|
dock.sigClosed.connect(partial(self.on_dock_closed, expurl))
|
||||||
if expurl in self.dock_states:
|
if expurl in self.dock_states:
|
||||||
@ -790,24 +815,24 @@ class ExperimentManager:
|
|||||||
for expurl, dock in self.open_experiments.items():
|
for expurl, dock in self.open_experiments.items():
|
||||||
self.dock_states[expurl] = dock.save_state()
|
self.dock_states[expurl] = dock.save_state()
|
||||||
return {
|
return {
|
||||||
"scheduling": self.submission_scheduling,
|
"scheduling": dict(self.submission_scheduling),
|
||||||
"options": self.submission_options,
|
"options": dict(self.submission_options),
|
||||||
"arguments": self.submission_arguments,
|
"arguments": dict(self.submission_arguments),
|
||||||
"docks": self.dock_states,
|
"docks": dict(self.dock_states),
|
||||||
"argument_uis": self.argument_ui_names,
|
"argument_uis": dict(self.argument_ui_names),
|
||||||
"open_docks": set(self.open_experiments.keys()),
|
"open_docks": set(self.open_experiments.keys()),
|
||||||
"colors": self.colors
|
"colors": dict(self.colors)
|
||||||
}
|
}
|
||||||
|
|
||||||
def restore_state(self, state):
|
def restore_state(self, state):
|
||||||
if self.open_experiments:
|
if self.open_experiments:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
self.dock_states = state["docks"]
|
self.dock_states.update(state["docks"])
|
||||||
self.submission_scheduling = state["scheduling"]
|
self.submission_scheduling.update(state["scheduling"])
|
||||||
self.submission_options = state["options"]
|
self.submission_options.update(state["options"])
|
||||||
self.submission_arguments = state["arguments"]
|
self.submission_arguments.update(state["arguments"])
|
||||||
self.argument_ui_names = state.get("argument_uis", {})
|
self.argument_ui_names.update(state.get("argument_uis", {}))
|
||||||
self.colors = state.get("colors", {})
|
self.colors.update(state.get("colors", {}))
|
||||||
for expurl in state["open_docks"]:
|
for expurl in state["open_docks"]:
|
||||||
self.open_experiment(expurl)
|
self.open_experiment(expurl)
|
||||||
|
|
||||||
|
@ -500,7 +500,7 @@ pub extern fn main() -> i32 {
|
|||||||
println!(r"|_| |_|_|____/ \___/ \____|");
|
println!(r"|_| |_|_|____/ \___/ \____|");
|
||||||
println!("");
|
println!("");
|
||||||
println!("MiSoC Bootloader");
|
println!("MiSoC Bootloader");
|
||||||
println!("Copyright (c) 2017-2024 M-Labs Limited");
|
println!("Copyright (c) 2017-2025 M-Labs Limited");
|
||||||
println!("");
|
println!("");
|
||||||
|
|
||||||
#[cfg(has_ethmac)]
|
#[cfg(has_ethmac)]
|
||||||
|
@ -329,7 +329,7 @@ extern fn stop_fn(_version: c_int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Must be kept in sync with `nac3artiq::Nac3::new`
|
// 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),
|
("RTIOUnderflow", 0),
|
||||||
("RTIOOverflow", 1),
|
("RTIOOverflow", 1),
|
||||||
("RTIODestinationUnreachable", 2),
|
("RTIODestinationUnreachable", 2),
|
||||||
@ -352,6 +352,7 @@ static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [
|
|||||||
("ZeroDivisionError", 19),
|
("ZeroDivisionError", 19),
|
||||||
("LinAlgError", 20),
|
("LinAlgError", 20),
|
||||||
("UnwrapNoneError", 21),
|
("UnwrapNoneError", 21),
|
||||||
|
("CXPError", 22)
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn get_exception_id(name: &str) -> u32 {
|
pub fn get_exception_id(name: &str) -> u32 {
|
||||||
|
@ -209,7 +209,7 @@ fn terminate(exceptions: &'static [Option<eh_artiq::Exception<'static>>],
|
|||||||
loop {}
|
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 {
|
send(&CacheGetRequest {
|
||||||
key: str::from_utf8(key.as_ref()).unwrap()
|
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 {
|
send(&CachePutRequest {
|
||||||
key: str::from_utf8(key.as_ref()).unwrap(),
|
key: str::from_utf8(key.as_ref()).unwrap(),
|
||||||
value: list.as_ref()
|
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();
|
let name = str::from_utf8(name.as_ref()).unwrap();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -264,6 +264,7 @@ extern "C-unwind" fn dma_record_start(name: &CSlice<u8>) {
|
|||||||
dma_record_output as *const () as u32).unwrap();
|
dma_record_output as *const () as u32).unwrap();
|
||||||
library.rebind(b"rtio_output_wide",
|
library.rebind(b"rtio_output_wide",
|
||||||
dma_record_output_wide as *const () as u32).unwrap();
|
dma_record_output_wide as *const () as u32).unwrap();
|
||||||
|
board_misoc::cache::flush_cpu_icache();
|
||||||
|
|
||||||
DMA_RECORDER.active = true;
|
DMA_RECORDER.active = true;
|
||||||
send(&DmaRecordStart(name));
|
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();
|
rtio::output as *const () as u32).unwrap();
|
||||||
library.rebind(b"rtio_output_wide",
|
library.rebind(b"rtio_output_wide",
|
||||||
rtio::output_wide as *const () as u32).unwrap();
|
rtio::output_wide as *const () as u32).unwrap();
|
||||||
|
board_misoc::cache::flush_cpu_icache();
|
||||||
|
|
||||||
DMA_RECORDER.active = false;
|
DMA_RECORDER.active = false;
|
||||||
send(&DmaRecordStop {
|
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();
|
let name = str::from_utf8(name.as_ref()).unwrap();
|
||||||
|
|
||||||
send(&DmaEraseRequest { name: name });
|
send(&DmaEraseRequest { name: name });
|
||||||
@ -372,7 +374,7 @@ struct DmaTrace {
|
|||||||
uses_ddma: bool,
|
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();
|
let name = str::from_utf8(name.as_ref()).unwrap();
|
||||||
|
|
||||||
send(&DmaRetrieveRequest { name: name });
|
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 ()) {
|
unsafe fn attribute_writeback(typeinfo: *const ()) {
|
||||||
|
#[repr(C)]
|
||||||
struct Attr {
|
struct Attr {
|
||||||
offset: usize,
|
offset: usize,
|
||||||
tag: CSlice<'static, u8>,
|
tag: CSlice<'static, u8>,
|
||||||
name: CSlice<'static, u8>
|
name: CSlice<'static, u8>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
struct Type {
|
struct Type {
|
||||||
attributes: *const *const Attr,
|
attributes: *const *const Attr,
|
||||||
objects: *const *const ()
|
objects: *const *const ()
|
||||||
|
@ -90,15 +90,24 @@ fn map_frequency_settings(settings: &FrequencySettings) -> Result<FrequencySetti
|
|||||||
|
|
||||||
fn write(reg: u8, val: u8) -> Result<()> {
|
fn write(reg: u8, val: u8) -> Result<()> {
|
||||||
i2c::start(BUSNO).unwrap();
|
i2c::start(BUSNO).unwrap();
|
||||||
if !i2c::write(BUSNO, ADDRESS << 1).unwrap() {
|
i2c::write(BUSNO, ADDRESS << 1).map_err(|err|
|
||||||
return Err("Si5324 failed to ack write address")
|
match err {
|
||||||
}
|
i2c::Error::Nack => "Si5324 failed to ack write address",
|
||||||
if !i2c::write(BUSNO, reg).unwrap() {
|
err => err.into()
|
||||||
return Err("Si5324 failed to ack register")
|
}
|
||||||
}
|
)?;
|
||||||
if !i2c::write(BUSNO, val).unwrap() {
|
i2c::write(BUSNO, reg).map_err(|err|
|
||||||
return Err("Si5324 failed to ack value")
|
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();
|
i2c::stop(BUSNO).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -106,29 +115,47 @@ fn write(reg: u8, val: u8) -> Result<()> {
|
|||||||
#[cfg(si5324_soft_reset)]
|
#[cfg(si5324_soft_reset)]
|
||||||
fn write_no_ack_value(reg: u8, val: u8) -> Result<()> {
|
fn write_no_ack_value(reg: u8, val: u8) -> Result<()> {
|
||||||
i2c::start(BUSNO).unwrap();
|
i2c::start(BUSNO).unwrap();
|
||||||
if !i2c::write(BUSNO, ADDRESS << 1).unwrap() {
|
i2c::write(BUSNO, ADDRESS << 1).map_err(|err|
|
||||||
return Err("Si5324 failed to ack write address")
|
match err {
|
||||||
}
|
i2c::Error::Nack => "Si5324 failed to ack write address",
|
||||||
if !i2c::write(BUSNO, reg).unwrap() {
|
err => err.into()
|
||||||
return Err("Si5324 failed to ack register")
|
}
|
||||||
}
|
)?;
|
||||||
i2c::write(BUSNO, val).unwrap();
|
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();
|
i2c::stop(BUSNO).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read(reg: u8) -> Result<u8> {
|
fn read(reg: u8) -> Result<u8> {
|
||||||
i2c::start(BUSNO).unwrap();
|
i2c::start(BUSNO).unwrap();
|
||||||
if !i2c::write(BUSNO, ADDRESS << 1).unwrap() {
|
i2c::write(BUSNO, ADDRESS << 1).map_err(|err|
|
||||||
return Err("Si5324 failed to ack write address")
|
match err {
|
||||||
}
|
i2c::Error::Nack => "Si5324 failed to ack write address",
|
||||||
if !i2c::write(BUSNO, reg).unwrap() {
|
err => err.into()
|
||||||
return Err("Si5324 failed to ack register")
|
}
|
||||||
}
|
)?;
|
||||||
|
i2c::write(BUSNO, reg).map_err(|err|
|
||||||
|
match err {
|
||||||
|
i2c::Error::Nack => "Si5324 failed to ack register",
|
||||||
|
err => err.into()
|
||||||
|
}
|
||||||
|
)?;
|
||||||
i2c::restart(BUSNO).unwrap();
|
i2c::restart(BUSNO).unwrap();
|
||||||
if !i2c::write(BUSNO, (ADDRESS << 1) | 1).unwrap() {
|
i2c::write(BUSNO, (ADDRESS << 1) | 1).map_err(|err|
|
||||||
return Err("Si5324 failed to ack read address")
|
match err {
|
||||||
}
|
i2c::Error::Nack => "Si5324 failed to ack read address",
|
||||||
|
err => err.into()
|
||||||
|
}
|
||||||
|
)?;
|
||||||
let val = i2c::read(BUSNO, false).unwrap();
|
let val = i2c::read(BUSNO, false).unwrap();
|
||||||
i2c::stop(BUSNO).unwrap();
|
i2c::stop(BUSNO).unwrap();
|
||||||
Ok(val)
|
Ok(val)
|
||||||
|
@ -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)]
|
#[cfg(has_converter_spi)]
|
||||||
mod imp {
|
mod imp {
|
||||||
use board_misoc::csr;
|
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<(), Error> {
|
||||||
|
|
||||||
pub fn set_config(busno: u8, flags: u8, length: u8, div: u8, cs: u8) -> Result<(), &'static str> {
|
|
||||||
if busno != 0 {
|
if busno != 0 {
|
||||||
return Err(INVALID_BUS)
|
return Err(Error::InvalidBus)
|
||||||
}
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
while csr::converter_spi::writable_read() == 0 {}
|
while csr::converter_spi::writable_read() == 0 {}
|
||||||
@ -33,9 +48,9 @@ mod imp {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(busno: u8, data: u32) -> Result<(), &'static str> {
|
pub fn write(busno: u8, data: u32) -> Result<(), Error> {
|
||||||
if busno != 0 {
|
if busno != 0 {
|
||||||
return Err(INVALID_BUS)
|
return Err(Error::InvalidBus)
|
||||||
}
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
while csr::converter_spi::writable_read() == 0 {}
|
while csr::converter_spi::writable_read() == 0 {}
|
||||||
@ -44,9 +59,9 @@ mod imp {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(busno: u8) -> Result<u32, &'static str> {
|
pub fn read(busno: u8) -> Result<u32, Error> {
|
||||||
if busno != 0 {
|
if busno != 0 {
|
||||||
return Err(INVALID_BUS)
|
return Err(Error::InvalidBus)
|
||||||
}
|
}
|
||||||
Ok(unsafe {
|
Ok(unsafe {
|
||||||
while csr::converter_spi::writable_read() == 0 {}
|
while csr::converter_spi::writable_read() == 0 {}
|
||||||
@ -57,9 +72,11 @@ mod imp {
|
|||||||
|
|
||||||
#[cfg(not(has_converter_spi))]
|
#[cfg(not(has_converter_spi))]
|
||||||
mod imp {
|
mod imp {
|
||||||
pub fn set_config(_busno: u8, _flags: u8, _length: u8, _div: u8, _cs: u8) -> Result<(), ()> { Err(()) }
|
use super::Error;
|
||||||
pub fn write(_busno: u8,_data: u32) -> Result<(), ()> { Err(()) }
|
|
||||||
pub fn read(_busno: u8,) -> Result<u32, ()> { Err(()) }
|
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::*;
|
pub use self::imp::*;
|
||||||
|
@ -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)]
|
#[cfg(has_i2c)]
|
||||||
mod imp {
|
mod imp {
|
||||||
use super::super::{csr, clock};
|
use super::super::{csr, clock};
|
||||||
|
use super::Error;
|
||||||
const INVALID_BUS: &'static str = "Invalid I2C bus";
|
|
||||||
|
|
||||||
fn half_period() { clock::spin_us(100) }
|
fn half_period() { clock::spin_us(100) }
|
||||||
fn sda_bit(busno: u8) -> u8 { 1 << (2 * busno + 1) }
|
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 {
|
for busno in 0..csr::CONFIG_I2C_BUS_COUNT {
|
||||||
let busno = busno as u8;
|
let busno = busno as u8;
|
||||||
scl_oe(busno, false);
|
scl_oe(busno, false);
|
||||||
@ -74,26 +100,26 @@ mod imp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !sda_i(busno) {
|
if !sda_i(busno) {
|
||||||
return Err("SDA is stuck low and doesn't get unstuck");
|
return Err(Error::SDALow);
|
||||||
}
|
}
|
||||||
if !scl_i(busno) {
|
if !scl_i(busno) {
|
||||||
return Err("SCL is stuck low and doesn't get unstuck");
|
return Err(Error::SCLLow);
|
||||||
}
|
}
|
||||||
// postcondition: SCL and SDA high
|
// postcondition: SCL and SDA high
|
||||||
}
|
}
|
||||||
Ok(())
|
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 {
|
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
|
||||||
return Err(INVALID_BUS)
|
return Err(Error::InvalidBus)
|
||||||
}
|
}
|
||||||
// precondition: SCL and SDA high
|
// precondition: SCL and SDA high
|
||||||
if !scl_i(busno) {
|
if !scl_i(busno) {
|
||||||
return Err("SCL is stuck low and doesn't get unstuck");
|
return Err(Error::SCLLow);
|
||||||
}
|
}
|
||||||
if !sda_i(busno) {
|
if !sda_i(busno) {
|
||||||
return Err("SDA arbitration lost");
|
return Err(Error::ArbitrationLost);
|
||||||
}
|
}
|
||||||
sda_oe(busno, true);
|
sda_oe(busno, true);
|
||||||
half_period();
|
half_period();
|
||||||
@ -102,9 +128,9 @@ mod imp {
|
|||||||
Ok(())
|
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 {
|
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
|
||||||
return Err(INVALID_BUS)
|
return Err(Error::InvalidBus)
|
||||||
}
|
}
|
||||||
// precondition SCL and SDA low
|
// precondition SCL and SDA low
|
||||||
sda_oe(busno, false);
|
sda_oe(busno, false);
|
||||||
@ -116,9 +142,9 @@ mod imp {
|
|||||||
Ok(())
|
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 {
|
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
|
||||||
return Err(INVALID_BUS)
|
return Err(Error::InvalidBus)
|
||||||
}
|
}
|
||||||
// precondition: SCL and SDA low
|
// precondition: SCL and SDA low
|
||||||
half_period();
|
half_period();
|
||||||
@ -127,15 +153,15 @@ mod imp {
|
|||||||
sda_oe(busno, false);
|
sda_oe(busno, false);
|
||||||
half_period();
|
half_period();
|
||||||
if !sda_i(busno) {
|
if !sda_i(busno) {
|
||||||
return Err("SDA arbitration lost");
|
return Err(Error::ArbitrationLost);
|
||||||
}
|
}
|
||||||
// postcondition: SCL and SDA high
|
// postcondition: SCL and SDA high
|
||||||
Ok(())
|
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 {
|
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
|
||||||
return Err(INVALID_BUS)
|
return Err(Error::InvalidBus)
|
||||||
}
|
}
|
||||||
// precondition: SCL and SDA low
|
// precondition: SCL and SDA low
|
||||||
// MSB first
|
// MSB first
|
||||||
@ -156,12 +182,16 @@ mod imp {
|
|||||||
sda_oe(busno, true);
|
sda_oe(busno, true);
|
||||||
// postcondition: SCL and SDA low
|
// 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 {
|
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
|
||||||
return Err(INVALID_BUS)
|
return Err(Error::InvalidBus)
|
||||||
}
|
}
|
||||||
// precondition: SCL and SDA low
|
// precondition: SCL and SDA low
|
||||||
sda_oe(busno, false);
|
sda_oe(busno, false);
|
||||||
@ -188,17 +218,13 @@ mod imp {
|
|||||||
Ok(data)
|
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
|
// address in 7-bit form
|
||||||
// mask in format of 1 << channel (or 0 for disabling output)
|
// mask in format of 1 << channel (or 0 for disabling output)
|
||||||
// PCA9548 support only for now
|
// PCA9548 support only for now
|
||||||
start(busno)?;
|
start(busno)?;
|
||||||
if !write(busno, address << 1)? {
|
write(busno, address << 1)?;
|
||||||
return Err("PCA9548 failed to ack write address")
|
write(busno, mask)?;
|
||||||
}
|
|
||||||
if !write(busno, mask)? {
|
|
||||||
return Err("PCA9548 failed to ack control word")
|
|
||||||
}
|
|
||||||
stop(busno)?;
|
stop(busno)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -206,14 +232,15 @@ mod imp {
|
|||||||
|
|
||||||
#[cfg(not(has_i2c))]
|
#[cfg(not(has_i2c))]
|
||||||
mod imp {
|
mod imp {
|
||||||
const NO_I2C: &'static str = "No I2C support on this platform";
|
use super::Error;
|
||||||
pub fn init() -> Result<(), &'static str> { Err(NO_I2C) }
|
|
||||||
pub fn start(_busno: u8) -> Result<(), &'static str> { Err(NO_I2C) }
|
pub fn init() -> Result<(), Error> { Err(Error::NoI2C) }
|
||||||
pub fn restart(_busno: u8) -> Result<(), &'static str> { Err(NO_I2C) }
|
pub fn start(_busno: u8) -> Result<(), Error> { Err(Error::NoI2C) }
|
||||||
pub fn stop(_busno: u8) -> Result<(), &'static str> { Err(NO_I2C) }
|
pub fn restart(_busno: u8) -> Result<(), Error> { Err(Error::NoI2C) }
|
||||||
pub fn write(_busno: u8, _data: u8) -> Result<bool, &'static str> { Err(NO_I2C) }
|
pub fn stop(_busno: u8) -> Result<(), Error> { Err(Error::NoI2C) }
|
||||||
pub fn read(_busno: u8, _ack: bool) -> Result<u8, &'static str> { Err(NO_I2C) }
|
pub fn write(_busno: u8, _data: u8) -> Result<bool, Error> { Err(Error::NoI2C) }
|
||||||
pub fn switch_select(_busno: u8, _address: u8, _mask: u8) -> Result<(), &'static str> { Err(NO_I2C) }
|
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::*;
|
pub use self::imp::*;
|
||||||
|
@ -29,14 +29,14 @@ impl EEPROM {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(soc_platform = "kasli")]
|
#[cfg(soc_platform = "kasli")]
|
||||||
fn select(&self) -> Result<(), &'static str> {
|
fn select(&self) -> Result<(), i2c::Error> {
|
||||||
let mask: u16 = 1 << self.port;
|
let mask: u16 = 1 << self.port;
|
||||||
i2c::switch_select(self.busno, 0x70, mask as u8)?;
|
i2c::switch_select(self.busno, 0x70, mask as u8)?;
|
||||||
i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8)?;
|
i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8)?;
|
||||||
Ok(())
|
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()?;
|
self.select()?;
|
||||||
|
|
||||||
i2c::start(self.busno)?;
|
i2c::start(self.busno)?;
|
||||||
@ -58,7 +58,7 @@ impl EEPROM {
|
|||||||
/// > The 24AA02XEXX is programmed at the factory with a
|
/// > The 24AA02XEXX is programmed at the factory with a
|
||||||
/// > globally unique node address stored in the upper half
|
/// > globally unique node address stored in the upper half
|
||||||
/// > of the array and permanently write-protected.
|
/// > 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];
|
let mut buffer = [0u8; 6];
|
||||||
self.read(0xFA, &mut buffer)?;
|
self.read(0xFA, &mut buffer)?;
|
||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
|
@ -23,7 +23,7 @@ pub struct IoExpander {
|
|||||||
|
|
||||||
impl IoExpander {
|
impl IoExpander {
|
||||||
#[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))]
|
#[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_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)];
|
const VIRTUAL_LED_MAPPING1: [(u8, u8, u8); 2] = [(2, 0, 6), (3, 1, 6)];
|
||||||
|
|
||||||
@ -79,7 +79,11 @@ impl IoExpander {
|
|||||||
gpiob: 0x13,
|
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()? {
|
if !io_expander.check_ack()? {
|
||||||
#[cfg(feature = "log")]
|
#[cfg(feature = "log")]
|
||||||
@ -95,14 +99,16 @@ impl IoExpander {
|
|||||||
gpiob: 0x03,
|
gpiob: 0x03,
|
||||||
};
|
};
|
||||||
if !io_expander.check_ack()? {
|
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)
|
Ok(io_expander)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(soc_platform = "efc")]
|
#[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)];
|
const VIRTUAL_LED_MAPPING: [(u8, u8, u8); 2] = [(0, 0, 5), (1, 0, 6)];
|
||||||
|
|
||||||
let io_expander = IoExpander {
|
let io_expander = IoExpander {
|
||||||
@ -121,13 +127,15 @@ impl IoExpander {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
if !io_expander.check_ack()? {
|
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)
|
Ok(io_expander)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(soc_platform = "kasli")]
|
#[cfg(soc_platform = "kasli")]
|
||||||
fn select(&self) -> Result<(), &'static str> {
|
fn select(&self) -> Result<(), i2c::Error> {
|
||||||
let mask: u16 = 1 << self.port;
|
let mask: u16 = 1 << self.port;
|
||||||
i2c::switch_select(self.busno, 0x70, mask as u8)?;
|
i2c::switch_select(self.busno, 0x70, mask as u8)?;
|
||||||
i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8)?;
|
i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8)?;
|
||||||
@ -135,13 +143,13 @@ impl IoExpander {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(soc_platform = "efc")]
|
#[cfg(soc_platform = "efc")]
|
||||||
fn select(&self) -> Result<(), &'static str> {
|
fn select(&self) -> Result<(), i2c::Error> {
|
||||||
let mask: u16 = 1 << self.port;
|
let mask: u16 = 1 << self.port;
|
||||||
i2c::switch_select(self.busno, 0x70, mask as u8)?;
|
i2c::switch_select(self.busno, 0x70, mask as u8)?;
|
||||||
Ok(())
|
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::start(self.busno)?;
|
||||||
i2c::write(self.busno, self.address)?;
|
i2c::write(self.busno, self.address)?;
|
||||||
i2c::write(self.busno, addr)?;
|
i2c::write(self.busno, addr)?;
|
||||||
@ -150,22 +158,26 @@ impl IoExpander {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_ack(&self) -> Result<bool, &'static str> {
|
fn check_ack(&self) -> Result<bool, i2c::Error> {
|
||||||
// Check for ack from io expander
|
// Check for ack from io expander
|
||||||
self.select()?;
|
self.select()?;
|
||||||
i2c::start(self.busno)?;
|
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)?;
|
i2c::stop(self.busno)?;
|
||||||
Ok(ack)
|
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.iodira, self.iodir[0])?;
|
||||||
self.write(self.registers.iodirb, self.iodir[1])?;
|
self.write(self.registers.iodirb, self.iodir[1])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&mut self) -> Result<(), &'static str> {
|
pub fn init(&mut self) -> Result<(), i2c::Error> {
|
||||||
self.select()?;
|
self.select()?;
|
||||||
|
|
||||||
for (_led, port, bit) in self.virtual_led_mapping.iter() {
|
for (_led, port, bit) in self.virtual_led_mapping.iter() {
|
||||||
@ -180,7 +192,7 @@ impl IoExpander {
|
|||||||
Ok(())
|
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.iodir[port as usize] &= !outputs;
|
||||||
self.update_iodir()?;
|
self.update_iodir()?;
|
||||||
Ok(())
|
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() {
|
for (led, port, bit) in self.virtual_led_mapping.iter() {
|
||||||
let level = unsafe { (csr::virtual_leds::status_read() >> led) & 1 };
|
let level = unsafe { (csr::virtual_leds::status_read() >> led) & 1 };
|
||||||
self.set(*port, *bit, level != 0);
|
self.set(*port, *bit, level != 0);
|
||||||
|
@ -10,7 +10,7 @@ use core::cell::RefCell;
|
|||||||
|
|
||||||
const BUFFER_SIZE: usize = 512 * 1024;
|
const BUFFER_SIZE: usize = 512 * 1024;
|
||||||
|
|
||||||
#[repr(align(64))]
|
#[repr(align(2048))]
|
||||||
struct Buffer {
|
struct Buffer {
|
||||||
data: [u8; BUFFER_SIZE],
|
data: [u8; BUFFER_SIZE],
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,12 @@ mod remote_i2c {
|
|||||||
use drtio_routing;
|
use drtio_routing;
|
||||||
use rtio_mgt::drtio;
|
use rtio_mgt::drtio;
|
||||||
use sched::{Io, Mutex};
|
use sched::{Io, Mutex};
|
||||||
|
use super::local_i2c;
|
||||||
|
|
||||||
pub fn start(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
pub fn start(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
routing_table: &drtio_routing::RoutingTable,
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8
|
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,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::I2cStartRequest {
|
&drtioaux::Packet::I2cStartRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
@ -26,15 +27,15 @@ mod remote_i2c {
|
|||||||
});
|
});
|
||||||
match reply {
|
match reply {
|
||||||
Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => {
|
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) => {
|
Ok(packet) => {
|
||||||
error!("received unexpected aux packet: {:?}", packet);
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
Err("received unexpected aux packet")
|
Err(local_i2c::Error::OtherError)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("aux packet error ({})", 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,
|
pub fn restart(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
routing_table: &drtio_routing::RoutingTable,
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8
|
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,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::I2cRestartRequest {
|
&drtioaux::Packet::I2cRestartRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
@ -50,15 +51,15 @@ mod remote_i2c {
|
|||||||
});
|
});
|
||||||
match reply {
|
match reply {
|
||||||
Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => {
|
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) => {
|
Ok(packet) => {
|
||||||
error!("received unexpected aux packet: {:?}", packet);
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
Err("received unexpected aux packet")
|
Err(local_i2c::Error::OtherError)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("aux packet error ({})", 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,
|
pub fn stop(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
routing_table: &drtio_routing::RoutingTable,
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8
|
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,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::I2cStopRequest {
|
&drtioaux::Packet::I2cStopRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
@ -74,15 +75,15 @@ mod remote_i2c {
|
|||||||
});
|
});
|
||||||
match reply {
|
match reply {
|
||||||
Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => {
|
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) => {
|
Ok(packet) => {
|
||||||
error!("received unexpected aux packet: {:?}", packet);
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
Err("received unexpected aux packet")
|
Err(local_i2c::Error::OtherError)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("aux packet error ({})", 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,
|
pub fn write(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
routing_table: &drtio_routing::RoutingTable,
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8, data: u8
|
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,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::I2cWriteRequest {
|
&drtioaux::Packet::I2cWriteRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
@ -99,15 +100,21 @@ mod remote_i2c {
|
|||||||
});
|
});
|
||||||
match reply {
|
match reply {
|
||||||
Ok(drtioaux::Packet::I2cWriteReply { succeeded, ack }) => {
|
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(_) => {
|
Ok(_) => {
|
||||||
error!("received unexpected aux packet");
|
error!("received unexpected aux packet");
|
||||||
Err("received unexpected aux packet")
|
Err(local_i2c::Error::OtherError)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("aux packet error ({})", 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,
|
pub fn read(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
routing_table: &drtio_routing::RoutingTable,
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8, ack: bool
|
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,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::I2cReadRequest {
|
&drtioaux::Packet::I2cReadRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
@ -124,15 +131,15 @@ mod remote_i2c {
|
|||||||
});
|
});
|
||||||
match reply {
|
match reply {
|
||||||
Ok(drtioaux::Packet::I2cReadReply { succeeded, data }) => {
|
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(_) => {
|
Ok(_) => {
|
||||||
error!("received unexpected aux packet");
|
error!("received unexpected aux packet");
|
||||||
Err("received unexpected aux packet")
|
Err(local_i2c::Error::OtherError)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("aux packet error ({})", 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,
|
pub fn switch_select(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
routing_table: &drtio_routing::RoutingTable,
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8, address: u8, mask: u8
|
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,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::I2cSwitchSelectRequest {
|
&drtioaux::Packet::I2cSwitchSelectRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
@ -150,15 +157,15 @@ mod remote_i2c {
|
|||||||
});
|
});
|
||||||
match reply {
|
match reply {
|
||||||
Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => {
|
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) => {
|
Ok(packet) => {
|
||||||
error!("received unexpected aux packet: {:?}", packet);
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
Err("received unexpected aux packet")
|
Err(local_i2c::Error::OtherError)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("aux packet error ({})", 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 drtio_routing;
|
||||||
use rtio_mgt::drtio;
|
use rtio_mgt::drtio;
|
||||||
use sched::{Io, Mutex};
|
use sched::{Io, Mutex};
|
||||||
|
use super::local_spi;
|
||||||
|
|
||||||
pub fn set_config(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
pub fn set_config(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
routing_table: &drtio_routing::RoutingTable,
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8, flags: u8, length: u8, div: u8, cs: u8
|
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 {
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, &drtioaux::Packet::SpiSetConfigRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
busno: busno,
|
busno: busno,
|
||||||
@ -185,15 +193,15 @@ mod remote_spi {
|
|||||||
});
|
});
|
||||||
match reply {
|
match reply {
|
||||||
Ok(drtioaux::Packet::SpiBasicReply { succeeded }) => {
|
Ok(drtioaux::Packet::SpiBasicReply { succeeded }) => {
|
||||||
if succeeded { Ok(()) } else { Err(()) }
|
if succeeded { Ok(()) } else { Err(local_spi::Error::OtherError) }
|
||||||
}
|
}
|
||||||
Ok(packet) => {
|
Ok(packet) => {
|
||||||
error!("received unexpected aux packet: {:?}", packet);
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
Err(())
|
Err(local_spi::Error::OtherError)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("aux packet error ({})", 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,
|
pub fn write(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
routing_table: &drtio_routing::RoutingTable,
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8, data: u32
|
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 {
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, &drtioaux::Packet::SpiWriteRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
busno: busno,
|
busno: busno,
|
||||||
@ -209,22 +217,22 @@ mod remote_spi {
|
|||||||
});
|
});
|
||||||
match reply {
|
match reply {
|
||||||
Ok(drtioaux::Packet::SpiBasicReply { succeeded }) => {
|
Ok(drtioaux::Packet::SpiBasicReply { succeeded }) => {
|
||||||
if succeeded { Ok(()) } else { Err(()) }
|
if succeeded { Ok(()) } else { Err(local_spi::Error::OtherError) }
|
||||||
}
|
}
|
||||||
Ok(packet) => {
|
Ok(packet) => {
|
||||||
error!("received unexpected aux packet: {:?}", packet);
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
Err(())
|
Err(local_spi::Error::OtherError)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("aux packet error ({})", 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,
|
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
|
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,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::SpiReadRequest {
|
&drtioaux::Packet::SpiReadRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
@ -232,15 +240,15 @@ mod remote_spi {
|
|||||||
});
|
});
|
||||||
match reply {
|
match reply {
|
||||||
Ok(drtioaux::Packet::SpiReadReply { succeeded, data }) => {
|
Ok(drtioaux::Packet::SpiReadReply { succeeded, data }) => {
|
||||||
if succeeded { Ok(data) } else { Err(()) }
|
if succeeded { Ok(data) } else { Err(local_spi::Error::OtherError) }
|
||||||
}
|
}
|
||||||
Ok(packet) => {
|
Ok(packet) => {
|
||||||
error!("received unexpected aux packet: {:?}", packet);
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
Err(())
|
Err(local_spi::Error::OtherError)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("aux packet error ({})", 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 } => {
|
&kern::I2cWriteRequest { busno, data } => {
|
||||||
match dispatch!(io, aux_mutex, ddma_mutex, subkernel_mutex, local_i2c, remote_i2c, routing_table, busno, write, 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 })
|
Err(_) => kern_send(io, &kern::I2cWriteReply { succeeded: false, ack: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,7 +240,7 @@ fn startup() {
|
|||||||
let subkernel_mutex = subkernel_mutex.clone();
|
let subkernel_mutex = subkernel_mutex.clone();
|
||||||
let drtio_routing_table = drtio_routing_table.clone();
|
let drtio_routing_table = drtio_routing_table.clone();
|
||||||
let up_destinations = up_destinations.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)]
|
#[cfg(has_grabber)]
|
||||||
|
@ -369,15 +369,18 @@ pub mod drtio {
|
|||||||
}
|
}
|
||||||
drtioaux::Packet::DestinationOkReply => (),
|
drtioaux::Packet::DestinationOkReply => (),
|
||||||
drtioaux::Packet::DestinationSequenceErrorReply { channel } => {
|
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 };
|
unsafe { SEEN_ASYNC_ERRORS |= ASYNC_ERROR_SEQUENCE_ERROR };
|
||||||
}
|
}
|
||||||
drtioaux::Packet::DestinationCollisionReply { channel } => {
|
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 };
|
unsafe { SEEN_ASYNC_ERRORS |= ASYNC_ERROR_COLLISION };
|
||||||
}
|
}
|
||||||
drtioaux::Packet::DestinationBusyReply { channel } => {
|
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 };
|
unsafe { SEEN_ASYNC_ERRORS |= ASYNC_ERROR_BUSY };
|
||||||
}
|
}
|
||||||
packet => error!("[DEST#{}] received unexpected aux packet: {:?}", destination, packet),
|
packet => error!("[DEST#{}] received unexpected aux packet: {:?}", destination, packet),
|
||||||
|
@ -1050,8 +1050,10 @@ fn process_kern_hwreq(request: &kern::Message, self_destination: u8) -> Result<b
|
|||||||
}
|
}
|
||||||
&kern::I2cWriteRequest { busno, data } => {
|
&kern::I2cWriteRequest { busno, data } => {
|
||||||
match i2c::write(busno as u8, data) {
|
match i2c::write(busno as u8, data) {
|
||||||
Ok(ack) => kern_send(
|
Ok(()) => kern_send(
|
||||||
&kern::I2cWriteReply { succeeded: true, ack: ack }),
|
&kern::I2cWriteReply { succeeded: true, ack: true }),
|
||||||
|
Err(i2c::Error::Nack) => kern_send(
|
||||||
|
&kern::I2cWriteReply { succeeded: true, ack: false }),
|
||||||
Err(_) => kern_send(
|
Err(_) => kern_send(
|
||||||
&kern::I2cWriteReply { succeeded: false, ack: false })
|
&kern::I2cWriteReply { succeeded: false, ack: false })
|
||||||
}
|
}
|
||||||
|
@ -321,8 +321,10 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
|
|||||||
drtioaux::Packet::I2cWriteRequest { destination: _destination, busno, data } => {
|
drtioaux::Packet::I2cWriteRequest { destination: _destination, busno, data } => {
|
||||||
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
match i2c::write(busno, data) {
|
match i2c::write(busno, data) {
|
||||||
Ok(ack) => drtioaux::send(0,
|
Ok(()) => drtioaux::send(0,
|
||||||
&drtioaux::Packet::I2cWriteReply { succeeded: true, ack: ack }),
|
&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,
|
Err(_) => drtioaux::send(0,
|
||||||
&drtioaux::Packet::I2cWriteReply { succeeded: false, ack: false })
|
&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 } => {
|
drtioaux::Packet::CoreMgmtDropLinkAck { destination: _destination } => {
|
||||||
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
#[cfg(not(has_drtio_eem))]
|
#[cfg(not(soc_platform = "efc"))]
|
||||||
unsafe {
|
unsafe {
|
||||||
csr::gt_drtio::txenable_write(0);
|
csr::gt_drtio::txenable_write(0);
|
||||||
}
|
}
|
||||||
@ -949,7 +951,7 @@ fn startup() {
|
|||||||
io_expander.service().unwrap();
|
io_expander.service().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(has_drtio_eem))]
|
#[cfg(not(soc_platform = "efc"))]
|
||||||
unsafe {
|
unsafe {
|
||||||
csr::gt_drtio::txenable_write(0xffffffffu32 as _);
|
csr::gt_drtio::txenable_write(0xffffffffu32 as _);
|
||||||
}
|
}
|
||||||
|
0
artiq/gateware/cxp_grabber/__init__.py
Normal file
0
artiq/gateware/cxp_grabber/__init__.py
Normal file
352
artiq/gateware/cxp_grabber/core.py
Normal file
352
artiq/gateware/cxp_grabber/core.py
Normal 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),
|
||||||
|
]
|
472
artiq/gateware/cxp_grabber/frame.py
Normal file
472
artiq/gateware/cxp_grabber/frame.py
Normal 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
|
||||||
|
|
||||||
|
|
@ -3,11 +3,11 @@ from migen.genlib.resetsync import AsyncResetSynchronizer
|
|||||||
from migen.genlib.cdc import MultiReg
|
from migen.genlib.cdc import MultiReg
|
||||||
|
|
||||||
from misoc.cores.code_8b10b import Encoder, Decoder
|
from misoc.cores.code_8b10b import Encoder, Decoder
|
||||||
|
from misoc.cores.gtx_7series_init import *
|
||||||
from misoc.interconnect.csr import *
|
from misoc.interconnect.csr import *
|
||||||
|
|
||||||
from artiq.gateware.drtio.core import TransceiverInterface, ChannelInterface
|
from artiq.gateware.drtio.core import TransceiverInterface, ChannelInterface
|
||||||
from artiq.gateware.drtio.transceiver.clock_aligner import BruteforceClockAligner
|
from artiq.gateware.drtio.transceiver.clock_aligner import BruteforceClockAligner
|
||||||
from artiq.gateware.drtio.transceiver.gtx_7series_init import *
|
|
||||||
|
|
||||||
|
|
||||||
class GTX_20X(Module):
|
class GTX_20X(Module):
|
||||||
|
@ -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)
|
|
||||||
]
|
|
@ -160,10 +160,12 @@ class DMAWriter(Module, AutoCSR):
|
|||||||
self.comb += [
|
self.comb += [
|
||||||
membus.cyc.eq(self.sink.stb),
|
membus.cyc.eq(self.sink.stb),
|
||||||
membus.stb.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),
|
self.sink.ack.eq(membus.ack),
|
||||||
membus.we.eq(1),
|
membus.we.eq(1),
|
||||||
membus.dat_w.eq(dma.convert_signal(self.sink.data, cpu_dw//8))
|
membus.dat_w.eq(dma.convert_signal(self.sink.data, cpu_dw//8))
|
||||||
]
|
]
|
||||||
|
|
||||||
if messages_per_dw > 1:
|
if messages_per_dw > 1:
|
||||||
for i in range(dw//8):
|
for i in range(dw//8):
|
||||||
self.comb += membus.sel[i].eq(
|
self.comb += membus.sel[i].eq(
|
||||||
@ -201,8 +203,9 @@ class Analyzer(Module, AutoCSR):
|
|||||||
|
|
||||||
self.submodules.message_encoder = MessageEncoder(
|
self.submodules.message_encoder = MessageEncoder(
|
||||||
tsc, cri, self.enable.storage)
|
tsc, cri, self.enable.storage)
|
||||||
|
hi_wm = 64 if fifo_depth > 64 else None
|
||||||
self.submodules.fifo = stream.SyncFIFO(
|
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(
|
self.submodules.converter = stream.Converter(
|
||||||
message_len, len(membus.dat_w), reverse=True,
|
message_len, len(membus.dat_w), reverse=True,
|
||||||
report_valid_token_count=True)
|
report_valid_token_count=True)
|
||||||
|
@ -35,23 +35,39 @@ class WishboneReader(Module):
|
|||||||
# # #
|
# # #
|
||||||
|
|
||||||
bus_stb = Signal()
|
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 += [
|
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.cyc.eq(bus_stb),
|
||||||
bus.stb.eq(bus_stb),
|
bus.stb.eq(bus_stb),
|
||||||
|
bus.cti.eq(Mux((self.sink.eop | last), 0b111, 0b010)),
|
||||||
bus.adr.eq(self.sink.address),
|
bus.adr.eq(self.sink.address),
|
||||||
|
|
||||||
self.sink.ack.eq(bus.ack),
|
self.sink.ack.eq(bus.ack),
|
||||||
self.source.stb.eq(data_reg_loaded),
|
self.source.stb.eq(bus.ack),
|
||||||
]
|
|
||||||
self.sync += [
|
self.source.data.eq(convert_signal(bus.dat_r, cpu_dw//8)),
|
||||||
If(self.source.ack, data_reg_loaded.eq(0)),
|
self.source.last.eq(self.sink.eop | last),
|
||||||
If(bus.ack,
|
self.source.eop.eq(self.sink.eop),
|
||||||
data_reg_loaded.eq(1),
|
|
||||||
self.source.data.eq(convert_signal(bus.dat_r, cpu_dw//8)),
|
|
||||||
self.source.eop.eq(self.sink.eop)
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -341,13 +357,16 @@ class DMA(Module):
|
|||||||
|
|
||||||
flow_enable = Signal()
|
flow_enable = Signal()
|
||||||
self.submodules.dma = DMAReader(membus, flow_enable, cpu_dw)
|
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.slicer = RecordSlicer(len(membus.dat_w))
|
||||||
self.submodules.time_offset = TimeOffset()
|
self.submodules.time_offset = TimeOffset()
|
||||||
self.submodules.cri_master = CRIMaster()
|
self.submodules.cri_master = CRIMaster()
|
||||||
self.cri = self.cri_master.cri
|
self.cri = self.cri_master.cri
|
||||||
|
|
||||||
self.comb += [
|
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.slicer.source.connect(self.time_offset.sink),
|
||||||
self.time_offset.source.connect(self.cri_master.sink)
|
self.time_offset.source.connect(self.cri_master.sink)
|
||||||
]
|
]
|
||||||
|
82
artiq/gateware/rtio/phy/cxp_grabber.py
Normal file
82
artiq/gateware/rtio/phy/cxp_grabber.py
Normal 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))
|
||||||
|
|
@ -29,13 +29,14 @@ class Satellite(BaseSoC, AMPSoC):
|
|||||||
}
|
}
|
||||||
mem_map.update(BaseSoC.mem_map)
|
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,
|
BaseSoC.__init__(self,
|
||||||
cpu_type="vexriscv",
|
cpu_type="vexriscv",
|
||||||
hw_rev=hw_rev,
|
hw_rev=efc_hw_rev,
|
||||||
cpu_bus_width=64,
|
cpu_bus_width=64,
|
||||||
sdram_controller_type="minicon",
|
sdram_controller_type="minicon",
|
||||||
l2_size=128*1024,
|
l2_size=128*1024,
|
||||||
|
l2_line_size=64,
|
||||||
clk_freq=125e6,
|
clk_freq=125e6,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
AMPSoC.__init__(self)
|
AMPSoC.__init__(self)
|
||||||
@ -155,15 +156,27 @@ class Satellite(BaseSoC, AMPSoC):
|
|||||||
Subsignal('mosi', Pins('fmc0:LA00_CC_N')),
|
Subsignal('mosi', Pins('fmc0:LA00_CC_N')),
|
||||||
Subsignal('cs_n', Pins('fmc0:LA02_P fmc0:LA01_CC_N')),
|
Subsignal('cs_n', Pins('fmc0:LA02_P fmc0:LA01_CC_N')),
|
||||||
IOStandard("LVCMOS18")),
|
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")),
|
('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)
|
platform.add_extension(shuttler_io)
|
||||||
|
|
||||||
self.submodules.converter_spi = spi2.SPIMaster(spi2.SPIInterface(self.platform.request("dac_spi", 0)))
|
self.submodules.converter_spi = spi2.SPIMaster(spi2.SPIInterface(self.platform.request("dac_spi", 0)))
|
||||||
@ -247,15 +260,18 @@ def main():
|
|||||||
builder_args(parser)
|
builder_args(parser)
|
||||||
parser.set_defaults(output_dir="artiq_efc")
|
parser.set_defaults(output_dir="artiq_efc")
|
||||||
parser.add_argument("-V", "--variant", default="shuttler")
|
parser.add_argument("-V", "--variant", default="shuttler")
|
||||||
parser.add_argument("--hw-rev", choices=["v1.0", "v1.1"], default="v1.1",
|
parser.add_argument("--efc-hw-rev", choices=["v1.0", "v1.1"], default="v1.1",
|
||||||
help="Hardware revision")
|
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,
|
parser.add_argument("--gateware-identifier-str", default=None,
|
||||||
help="Override ROM identifier")
|
help="Override ROM identifier")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
argdict = dict()
|
argdict = dict()
|
||||||
argdict["gateware_identifier_str"] = args.gateware_identifier_str
|
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)
|
soc = Satellite(**argdict)
|
||||||
build_artiq_soc(soc, builder_argdict(args))
|
build_artiq_soc(soc, builder_argdict(args))
|
||||||
|
@ -68,6 +68,7 @@ class StandaloneBase(MiniSoC, AMPSoC):
|
|||||||
cpu_bus_width=cpu_bus_width,
|
cpu_bus_width=cpu_bus_width,
|
||||||
sdram_controller_type="minicon",
|
sdram_controller_type="minicon",
|
||||||
l2_size=128*1024,
|
l2_size=128*1024,
|
||||||
|
l2_line_size=64,
|
||||||
integrated_sram_size=8192,
|
integrated_sram_size=8192,
|
||||||
ethmac_nrxslots=4,
|
ethmac_nrxslots=4,
|
||||||
ethmac_ntxslots=4,
|
ethmac_ntxslots=4,
|
||||||
@ -176,6 +177,7 @@ class MasterBase(MiniSoC, AMPSoC):
|
|||||||
cpu_bus_width=cpu_bus_width,
|
cpu_bus_width=cpu_bus_width,
|
||||||
sdram_controller_type="minicon",
|
sdram_controller_type="minicon",
|
||||||
l2_size=128*1024,
|
l2_size=128*1024,
|
||||||
|
l2_line_size=64,
|
||||||
integrated_sram_size=8192,
|
integrated_sram_size=8192,
|
||||||
ethmac_nrxslots=4,
|
ethmac_nrxslots=4,
|
||||||
ethmac_ntxslots=4,
|
ethmac_ntxslots=4,
|
||||||
@ -427,6 +429,7 @@ class SatelliteBase(BaseSoC, AMPSoC):
|
|||||||
cpu_bus_width=cpu_bus_width,
|
cpu_bus_width=cpu_bus_width,
|
||||||
sdram_controller_type="minicon",
|
sdram_controller_type="minicon",
|
||||||
l2_size=128*1024,
|
l2_size=128*1024,
|
||||||
|
l2_line_size=64,
|
||||||
clk_freq=rtio_clk_freq,
|
clk_freq=rtio_clk_freq,
|
||||||
rtio_sys_merge=True,
|
rtio_sys_merge=True,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
@ -90,6 +90,7 @@ class _StandaloneBase(MiniSoC, AMPSoC):
|
|||||||
cpu_bus_width=64,
|
cpu_bus_width=64,
|
||||||
sdram_controller_type="minicon",
|
sdram_controller_type="minicon",
|
||||||
l2_size=128*1024,
|
l2_size=128*1024,
|
||||||
|
l2_line_size=64,
|
||||||
integrated_sram_size=8192,
|
integrated_sram_size=8192,
|
||||||
ethmac_nrxslots=4,
|
ethmac_nrxslots=4,
|
||||||
ethmac_ntxslots=4,
|
ethmac_ntxslots=4,
|
||||||
@ -127,6 +128,7 @@ class _StandaloneBase(MiniSoC, AMPSoC):
|
|||||||
Instance("BUFG", i_I=cdr_clk, o_O=cdr_clk_buf)
|
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.crg.configure(cdr_clk_buf)
|
||||||
|
|
||||||
self.submodules += SMAClkinForward(self.platform)
|
self.submodules += SMAClkinForward(self.platform)
|
||||||
@ -187,6 +189,7 @@ class _MasterBase(MiniSoC, AMPSoC):
|
|||||||
cpu_bus_width=64,
|
cpu_bus_width=64,
|
||||||
sdram_controller_type="minicon",
|
sdram_controller_type="minicon",
|
||||||
l2_size=128*1024,
|
l2_size=128*1024,
|
||||||
|
l2_line_size=64,
|
||||||
integrated_sram_size=8192,
|
integrated_sram_size=8192,
|
||||||
ethmac_nrxslots=4,
|
ethmac_nrxslots=4,
|
||||||
ethmac_ntxslots=4,
|
ethmac_ntxslots=4,
|
||||||
@ -335,6 +338,7 @@ class _SatelliteBase(BaseSoC, AMPSoC):
|
|||||||
cpu_bus_width=64,
|
cpu_bus_width=64,
|
||||||
sdram_controller_type="minicon",
|
sdram_controller_type="minicon",
|
||||||
l2_size=128*1024,
|
l2_size=128*1024,
|
||||||
|
l2_line_size=64,
|
||||||
integrated_sram_size=8192,
|
integrated_sram_size=8192,
|
||||||
clk_freq=clk_freq,
|
clk_freq=clk_freq,
|
||||||
rtio_sys_merge=True,
|
rtio_sys_merge=True,
|
||||||
|
0
artiq/gateware/test/cxp_grabber/__init__.py
Normal file
0
artiq/gateware/test/cxp_grabber/__init__.py
Normal file
51
artiq/gateware/test/cxp_grabber/packet_generator.py
Normal file
51
artiq/gateware/test/cxp_grabber/packet_generator.py
Normal 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
|
100
artiq/gateware/test/cxp_grabber/test_pixel_parser.py
Normal file
100
artiq/gateware/test/cxp_grabber/test_pixel_parser.py
Normal 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()
|
@ -31,23 +31,53 @@ class EntryTreeWidget(QtWidgets.QTreeWidget):
|
|||||||
self.setHorizontalScrollMode(self.ScrollMode.ScrollPerPixel)
|
self.setHorizontalScrollMode(self.ScrollMode.ScrollPerPixel)
|
||||||
self.setVerticalScrollMode(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.viewport().installEventFilter(WheelFilter(self.viewport(), True))
|
||||||
|
|
||||||
self._groups = dict()
|
self._groups = dict()
|
||||||
self._arg_to_widgets = dict()
|
self._arg_to_widgets = dict()
|
||||||
self._arguments = dict()
|
self._arguments = dict()
|
||||||
|
|
||||||
|
self.set_background_color()
|
||||||
self.gradient = QtGui.QLinearGradient(
|
self.gradient = QtGui.QLinearGradient(
|
||||||
0, 0, 0, QtGui.QFontMetrics(self.font()).lineSpacing() * 2.5)
|
0, 0, 0, QtGui.QFontMetrics(self.font()).lineSpacing() * 2.5)
|
||||||
self.gradient.setColorAt(0, self.palette().base().color())
|
self.set_gradient_color()
|
||||||
self.gradient.setColorAt(1, self.palette().midlight().color())
|
self.set_group_color()
|
||||||
|
|
||||||
self.bottom_item = QtWidgets.QTreeWidgetItem()
|
self.bottom_item = QtWidgets.QTreeWidgetItem()
|
||||||
self.addTopLevelItem(self.bottom_item)
|
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):
|
def set_argument(self, key, argument):
|
||||||
self._arguments[key] = argument
|
self._arguments[key] = argument
|
||||||
widgets = dict()
|
widgets = dict()
|
||||||
@ -63,8 +93,7 @@ class EntryTreeWidget(QtWidgets.QTreeWidget):
|
|||||||
widgets["entry"] = entry
|
widgets["entry"] = entry
|
||||||
widgets["widget_item"] = widget_item
|
widgets["widget_item"] = widget_item
|
||||||
|
|
||||||
for col in range(3):
|
self.set_gradient_color()
|
||||||
widget_item.setBackground(col, self.gradient)
|
|
||||||
font = widget_item.font(0)
|
font = widget_item.font(0)
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
widget_item.setFont(0, font)
|
widget_item.setFont(0, font)
|
||||||
@ -108,12 +137,12 @@ class EntryTreeWidget(QtWidgets.QTreeWidget):
|
|||||||
return self._groups[key]
|
return self._groups[key]
|
||||||
group = QtWidgets.QTreeWidgetItem([key])
|
group = QtWidgets.QTreeWidgetItem([key])
|
||||||
for col in range(3):
|
for col in range(3):
|
||||||
group.setBackground(col, self.palette().mid())
|
|
||||||
font = group.font(col)
|
font = group.font(col)
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
group.setFont(col, font)
|
group.setFont(col, font)
|
||||||
self.insertTopLevelItem(self.indexFromItem(self.bottom_item).row(), group)
|
self.insertTopLevelItem(self.indexFromItem(self.bottom_item).row(), group)
|
||||||
self._groups[key] = group
|
self._groups[key] = group
|
||||||
|
self.set_group_color()
|
||||||
return group
|
return group
|
||||||
|
|
||||||
def _disable_other_scans(self, current_key):
|
def _disable_other_scans(self, current_key):
|
||||||
|
@ -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)
|
|
@ -672,3 +672,34 @@ class _IntBoundary(EnvExperiment):
|
|||||||
class IntBoundaryTest(ExperimentCase):
|
class IntBoundaryTest(ExperimentCase):
|
||||||
def test_int_boundary(self):
|
def test_int_boundary(self):
|
||||||
self.create(_IntBoundary).run()
|
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()
|
||||||
|
@ -635,11 +635,13 @@ class CoredeviceTest(ExperimentCase):
|
|||||||
def execute_and_test_in_log(self, experiment, string):
|
def execute_and_test_in_log(self, experiment, string):
|
||||||
core_addr = self.device_mgr.get_desc("core")["arguments"]["host"]
|
core_addr = self.device_mgr.get_desc("core")["arguments"]["host"]
|
||||||
mgmt = CommMgmt(core_addr)
|
mgmt = CommMgmt(core_addr)
|
||||||
mgmt.clear_log()
|
try:
|
||||||
self.execute(experiment)
|
mgmt.clear_log()
|
||||||
log = mgmt.get_log()
|
self.execute(experiment)
|
||||||
self.assertIn(string, log)
|
log = mgmt.get_log()
|
||||||
mgmt.close()
|
self.assertIn(string, log)
|
||||||
|
finally:
|
||||||
|
mgmt.close()
|
||||||
|
|
||||||
def test_sequence_error(self):
|
def test_sequence_error(self):
|
||||||
self.execute_and_test_in_log(SequenceError, "RTIO sequence error")
|
self.execute_and_test_in_log(SequenceError, "RTIO sequence error")
|
||||||
@ -878,13 +880,6 @@ class DMATest(ExperimentCase):
|
|||||||
self.assertLess(dt/count, 11*us)
|
self.assertLess(dt/count, 11*us)
|
||||||
|
|
||||||
def test_dma_playback_time(self):
|
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)
|
exp = self.create(_DMA)
|
||||||
is_zynq = exp.core.target == "cortexa9"
|
is_zynq = exp.core.target == "cortexa9"
|
||||||
count = 20000
|
count = 20000
|
||||||
|
20
artiq/test/lit/embedding/error_with_rpc.py
Normal file
20
artiq/test/lit/embedding/error_with_rpc.py
Normal 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
|
@ -102,7 +102,7 @@ def short_format(v, metadata={}):
|
|||||||
return v_str
|
return v_str
|
||||||
elif np.issubdtype(t, np.bool_):
|
elif np.issubdtype(t, np.bool_):
|
||||||
return str(v)
|
return str(v)
|
||||||
elif np.issubdtype(t, np.unicode_):
|
elif np.issubdtype(t, np.str_):
|
||||||
return "\"" + elide(v, 50) + "\""
|
return "\"" + elide(v, 50) + "\""
|
||||||
elif t is np.ndarray:
|
elif t is np.ndarray:
|
||||||
v_t = np.divide(v, scale)
|
v_t = np.divide(v, scale)
|
||||||
|
@ -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>`_.
|
- 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``.
|
- 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":{
|
"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.
|
* 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).
|
* 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
|
$ 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
|
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]
|
$ 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.
|
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.
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ master_doc = 'index'
|
|||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = 'ARTIQ'
|
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
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |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.
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
#html_theme_path = []
|
#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
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
# "<project> v<release> documentation".
|
# "<project> v<release> documentation".
|
||||||
|
@ -85,12 +85,6 @@ RF generation drivers
|
|||||||
.. automodule:: artiq.coredevice.ad9914
|
.. automodule:: artiq.coredevice.ad9914
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
:mod:`artiq.coredevice.ad9834` module
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. automodule:: artiq.coredevice.ad9834
|
|
||||||
:members:
|
|
||||||
|
|
||||||
:mod:`artiq.coredevice.mirny` module
|
:mod:`artiq.coredevice.mirny` module
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
@ -164,3 +158,10 @@ Miscellaneous
|
|||||||
|
|
||||||
.. automodule:: artiq.coredevice.grabber
|
.. automodule:: artiq.coredevice.grabber
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
:mod:`artiq.coredevice.cxp_grabber` module
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. automodule:: artiq.coredevice.cxp_grabber
|
||||||
|
:members:
|
||||||
|
@ -6,26 +6,30 @@ ARTIQ can be installed using the Nix package manager on Linux, and using the MSY
|
|||||||
Installing via Nix (Linux)
|
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
|
$ mkdir -p ~/.config/nix
|
||||||
$ echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
|
$ 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
|
$ 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";
|
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;
|
pkgs = extrapkg.pkgs;
|
||||||
artiq = extrapkg.packages.x86_64-linux;
|
artiq = extrapkg.packages.x86_64-linux;
|
||||||
in {
|
in {
|
||||||
defaultPackage.x86_64-linux = pkgs.buildEnv {
|
# This section defines the new environment
|
||||||
|
packages.x86_64-linux.default = pkgs.buildEnv {
|
||||||
name = "artiq-env";
|
name = "artiq-env";
|
||||||
paths = [
|
paths = [
|
||||||
# ========================================
|
# ========================================
|
||||||
# EDIT BELOW
|
# ADD PACKAGES BELOW
|
||||||
# ========================================
|
# ========================================
|
||||||
(pkgs.python3.withPackages(ps: [
|
(pkgs.python3.withPackages(ps : [
|
||||||
# List desired Python packages here.
|
# List desired Python packages here.
|
||||||
artiq.artiq
|
artiq.artiq
|
||||||
#ps.paramiko # needed if and only if flashing boards remotely (artiq_flash -H)
|
#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
|
#ps.qiskit
|
||||||
# Note that NixOS also provides packages ps.numpy and ps.scipy, but it is
|
# 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
|
# 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.korad_ka3005p
|
||||||
#artiq.novatech409b
|
#artiq.novatech409b
|
||||||
# List desired non-Python packages here
|
|
||||||
# Other potentially interesting non-Python packages from the NixOS package collection:
|
# Other potentially interesting non-Python packages from the NixOS package collection:
|
||||||
#pkgs.gtkwave
|
#pkgs.gtkwave
|
||||||
#pkgs.spyder
|
#pkgs.spyder
|
||||||
#pkgs.R
|
#pkgs.R
|
||||||
#pkgs.julia
|
#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
|
nixConfig = { # work around https://github.com/NixOS/nix/issues/6771
|
||||||
extra-trusted-public-keys = "nixbld.m-labs.hk-1:5aSRVA5b320xbNvu30tqxVPXpld73bhtOeH6uAjRyHc=";
|
extra-trusted-public-keys = "nixbld.m-labs.hk-1:5aSRVA5b320xbNvu30tqxVPXpld73bhtOeH6uAjRyHc=";
|
||||||
extra-substituters = "https://nixbld.m-labs.hk";
|
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.
|
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::
|
.. note::
|
||||||
|
|
||||||
If you find you prefer using flakes to your original ``nix profile`` installation, you can remove it from your system by running: ::
|
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
|
$ 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]
|
$ 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)?"
|
"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-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-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)?
|
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
|
extra-experimental-features = flakes
|
||||||
accept-flake-config = true
|
accept-flake-config = true
|
||||||
|
|
||||||
.. note::
|
.. 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"
|
"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.
|
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.
|
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
|
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: ::
|
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::
|
.. warning::
|
||||||
|
|
||||||
@ -182,9 +198,9 @@ Upgrading ARTIQ
|
|||||||
Upgrading with Nix
|
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
|
Upgrading with MSYS2
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -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``.
|
`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
28
flake.lock
generated
@ -48,11 +48,11 @@
|
|||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1740143775,
|
"lastModified": 1743494143,
|
||||||
"narHash": "sha256-sibL8PPLE4HuPssN5Gu86/RFyQHQh/uhFVSWu+ImaaE=",
|
"narHash": "sha256-OxeNED91hCgVsbgwRUpmP5BJ4dtilMxF2otGsQ+UBaQ=",
|
||||||
"ref": "refs/heads/master",
|
"ref": "refs/heads/master",
|
||||||
"rev": "f0397b7e09b5b189bbb2df6390ec3d81d8177391",
|
"rev": "e4f6fbeeebd8d888f2a12d5be027c9394a37ad89",
|
||||||
"revCount": 1554,
|
"revCount": 1583,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.m-labs.hk/m-labs/nac3.git"
|
"url": "https://git.m-labs.hk/m-labs/nac3.git"
|
||||||
},
|
},
|
||||||
@ -63,11 +63,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1738680400,
|
"lastModified": 1741851582,
|
||||||
"narHash": "sha256-ooLh+XW8jfa+91F1nhf9OF7qhuA/y1ChLx6lXDNeY5U=",
|
"narHash": "sha256-cPfs8qMccim2RBgtKGF+x9IBCduRvd/N5F4nYpU0TVE=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "799ba5bffed04ced7067a91798353d360788b30d",
|
"rev": "6607cf789e541e7873d40d3a8f7815ea92204f32",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -117,11 +117,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1734267097,
|
"lastModified": 1742007426,
|
||||||
"narHash": "sha256-aWg7XDiOlWnkXfDbKrBn9ITR46/JXfndvYHxFJ1vN78=",
|
"narHash": "sha256-Rs421WBPhORJoKXhuEuRBqDLBE2pdn/pSgRRSB7mJKY=",
|
||||||
"owner": "m-labs",
|
"owner": "m-labs",
|
||||||
"repo": "sipyco",
|
"repo": "sipyco",
|
||||||
"rev": "430978ada3fefe32de01f1b884b3031e48aaef96",
|
"rev": "34226114510e4f32409c8d99458602e745c80c52",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -149,11 +149,11 @@
|
|||||||
"src-misoc": {
|
"src-misoc": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1739436988,
|
"lastModified": 1741935500,
|
||||||
"narHash": "sha256-zEihEV6kqRtrZWyu7uCNyHOXE/rluVloPuT4ECYVJ+g=",
|
"narHash": "sha256-rbh4++/M1FQw57fUKow1MNXpmFfguUH5LlxfklDvUjs=",
|
||||||
"ref": "refs/heads/master",
|
"ref": "refs/heads/master",
|
||||||
"rev": "e3f4fd040b90b05d580bf578ca49244f0b7d861a",
|
"rev": "ebeff99217e6e23eb410d7c208d1f17443a57322",
|
||||||
"revCount": 2483,
|
"revCount": 2496,
|
||||||
"submodules": true,
|
"submodules": true,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/m-labs/misoc.git"
|
"url": "https://github.com/m-labs/misoc.git"
|
||||||
|
34
flake.nix
34
flake.nix
@ -132,7 +132,7 @@
|
|||||||
|
|
||||||
# keep llvm_x in sync with nac3
|
# keep llvm_x in sync with nac3
|
||||||
propagatedBuildInputs =
|
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]);
|
++ (with pkgs.python3Packages; [pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial h5py pyqt6 qasync tqdm lmdb jsonschema platformdirs]);
|
||||||
|
|
||||||
dontWrapQtApps = true;
|
dontWrapQtApps = true;
|
||||||
@ -157,7 +157,7 @@
|
|||||||
# FIXME: automatically propagate llvm_x dependency
|
# FIXME: automatically propagate llvm_x dependency
|
||||||
# cacert is required in the check stage only, as certificates are to be
|
# cacert is required in the check stage only, as certificates are to be
|
||||||
# obtained from system elsewhere
|
# obtained from system elsewhere
|
||||||
nativeCheckInputs = [pkgs.llvm_14 pkgs.cacert];
|
nativeCheckInputs = [pkgs.llvm_16 pkgs.cacert];
|
||||||
checkPhase = ''
|
checkPhase = ''
|
||||||
python -m unittest discover -v artiq.test
|
python -m unittest discover -v artiq.test
|
||||||
'';
|
'';
|
||||||
@ -215,7 +215,7 @@
|
|||||||
vivado = pkgs.buildFHSEnv {
|
vivado = pkgs.buildFHSEnv {
|
||||||
name = "vivado";
|
name = "vivado";
|
||||||
targetPkgs = vivadoDeps;
|
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";
|
runScript = "vivado";
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -238,9 +238,9 @@
|
|||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
(pkgs.python3.withPackages (ps: [migen misoc (artiq.withExperimentalFeatures experimentalFeatures) ps.packaging]))
|
(pkgs.python3.withPackages (ps: [migen misoc (artiq.withExperimentalFeatures experimentalFeatures) ps.packaging]))
|
||||||
rust
|
rust
|
||||||
pkgs.llvmPackages_14.clang-unwrapped
|
pkgs.llvmPackages_16.clang-unwrapped
|
||||||
pkgs.llvm_14
|
pkgs.llvm_16
|
||||||
pkgs.lld_14
|
pkgs.lld_16
|
||||||
vivado
|
vivado
|
||||||
rustPlatform.cargoSetupHook
|
rustPlatform.cargoSetupHook
|
||||||
];
|
];
|
||||||
@ -439,9 +439,9 @@
|
|||||||
[
|
[
|
||||||
git
|
git
|
||||||
lit
|
lit
|
||||||
lld_14
|
lld_16
|
||||||
llvm_14
|
llvm_16
|
||||||
llvmPackages_14.clang-unwrapped
|
llvmPackages_16.clang-unwrapped
|
||||||
pdf2svg
|
pdf2svg
|
||||||
|
|
||||||
python3Packages.sphinx
|
python3Packages.sphinx
|
||||||
@ -474,9 +474,9 @@
|
|||||||
packages = [
|
packages = [
|
||||||
rust
|
rust
|
||||||
|
|
||||||
pkgs.llvmPackages_14.clang-unwrapped
|
pkgs.llvmPackages_16.clang-unwrapped
|
||||||
pkgs.llvm_14
|
pkgs.llvm_16
|
||||||
pkgs.lld_14
|
pkgs.lld_16
|
||||||
|
|
||||||
packages.x86_64-linux.vivado
|
packages.x86_64-linux.vivado
|
||||||
packages.x86_64-linux.openocd-bscanspi
|
packages.x86_64-linux.openocd-bscanspi
|
||||||
@ -525,7 +525,7 @@
|
|||||||
]
|
]
|
||||||
++ ps.paramiko.optional-dependencies.ed25519
|
++ ps.paramiko.optional-dependencies.ed25519
|
||||||
))
|
))
|
||||||
pkgs.llvm_14
|
pkgs.llvm_16
|
||||||
pkgs.openssh
|
pkgs.openssh
|
||||||
packages.x86_64-linux.openocd-bscanspi # for the bscanspi bitstreams
|
packages.x86_64-linux.openocd-bscanspi # for the bscanspi bitstreams
|
||||||
];
|
];
|
||||||
@ -555,11 +555,15 @@
|
|||||||
# Read "Ok" line when remote successfully locked
|
# Read "Ok" line when remote successfully locked
|
||||||
read LOCK_OK
|
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}
|
artiq_flash -t kc705 -H rpi-1 -d ${packages.x86_64-linux.artiq-board-kc705-nist_clock}
|
||||||
sleep 30
|
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
|
python -m unittest discover -v artiq.test.coredevice
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user