forked from M-Labs/artiq
Compare commits
49 Commits
4a99514a75
...
0389be14fe
Author | SHA1 | Date | |
---|---|---|---|
0389be14fe | |||
27241cb2c3 | |||
a4ad97a3fd | |||
4ddad5fd17 | |||
|
d78ebb6bbb | ||
464befba63 | |||
1075e89514 | |||
342fa5aaf6 | |||
fc5bc1bb5c | |||
9e655332e3 | |||
b3678e8bfb | |||
|
dcc0f8d579 | ||
afbd83799c | |||
641d4cc362 | |||
3a9a10a41c | |||
cdacbf71c5 | |||
1533aba3ea | |||
bd1759dbf9 | |||
7e1da9da72 | |||
5e651308bb | |||
f213b8e1ca | |||
9fe9abd7fb | |||
90eb59c54d | |||
9a80818e97 | |||
9f1a8e7d4f | |||
9d2897de5f | |||
1794c939b2 | |||
519fdfa864 | |||
c5473a8a02 | |||
9c94ba7f89 | |||
76de902f2c | |||
99bf703851 | |||
4b60cd86c5 | |||
f98ce34480 | |||
8bd03f5bb4 | |||
06a2660f9c | |||
d0a2519b98 | |||
690292f467 | |||
d80901d216 | |||
3d22c50837 | |||
6c5ff1e64f | |||
5379bd9d38 | |||
66cec318a5 | |||
2a0c064de7 | |||
a0886b602e | |||
c0f5d8ba06 | |||
16d98279d5 | |||
bc94614395 | |||
6bce611ef8 |
@ -6,24 +6,31 @@ 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.
|
- The Log pane now adapts to dark system color themes.
|
||||||
- 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
|
||||||
|
@ -115,6 +115,7 @@ class EmbeddingMap:
|
|||||||
"0:ZeroDivisionError",
|
"0:ZeroDivisionError",
|
||||||
"0:LinAlgError",
|
"0:LinAlgError",
|
||||||
"UnwrapNoneError",
|
"UnwrapNoneError",
|
||||||
|
"CXPError"
|
||||||
])
|
])
|
||||||
|
|
||||||
def preallocate_runtime_exception_names(self, names):
|
def preallocate_runtime_exception_names(self, names):
|
||||||
|
@ -1524,13 +1524,7 @@ class LLVMIRGenerator:
|
|||||||
|
|
||||||
for i, arg in enumerate(insn.arguments()):
|
for i, arg in enumerate(insn.arguments()):
|
||||||
llarg = self.map(arg)
|
llarg = self.map(arg)
|
||||||
if isinstance(llarg.type, (ll.LiteralStructType, ll.IdentifiedStructType)):
|
llargs.append(llarg)
|
||||||
llslot = self.llbuilder.alloca(llarg.type)
|
|
||||||
self.llbuilder.store(llarg, llslot)
|
|
||||||
llargs.append(llslot)
|
|
||||||
llarg_attrs[i] = "byval"
|
|
||||||
else:
|
|
||||||
llargs.append(llarg)
|
|
||||||
|
|
||||||
llretty = self.llty_of_type(insn.type, for_return=True)
|
llretty = self.llty_of_type(insn.type, for_return=True)
|
||||||
is_sret = self.needs_sret(llretty)
|
is_sret = self.needs_sret(llretty)
|
||||||
|
340
artiq/coredevice/cxp_grabber.py
Normal file
340
artiq/coredevice/cxp_grabber.py
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
from numpy import array, int32, int64, ndarray
|
||||||
|
|
||||||
|
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.experiment import *
|
||||||
|
|
||||||
|
|
||||||
|
class OutOfSyncException(Exception):
|
||||||
|
"""Raised when an incorrect number of ROI engine outputs has been
|
||||||
|
retrieved from the RTIO input FIFO."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CXPGrabberTimeoutException(Exception):
|
||||||
|
"""Raised when a timeout occurs while attempting to read CoaXPress Grabber RTIO input events."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@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, y0: TInt32, x1: 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")
|
||||||
|
|
||||||
|
|
||||||
|
def write_file(data, file_path):
|
||||||
|
"""
|
||||||
|
Write big-endian encoded data to PC
|
||||||
|
|
||||||
|
:param data: a list of 32-bit integers
|
||||||
|
:param file_path: a relative path on PC
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
To download the XML file to PC: ::
|
||||||
|
|
||||||
|
# Prepare a big enough buffer
|
||||||
|
buffer = [0] * 25600
|
||||||
|
|
||||||
|
# Read the XML file and write it to PC
|
||||||
|
cxp_grabber.read_local_xml(buffer)
|
||||||
|
write_file(buffer, "camera_setting.xml")
|
||||||
|
|
||||||
|
"""
|
||||||
|
array(data, dtype=">i").tofile(file_path)
|
||||||
|
|
||||||
|
|
||||||
|
def write_pgm(frame, file_path, pixel_width):
|
||||||
|
"""
|
||||||
|
Write the frame as PGM file to PC.
|
||||||
|
|
||||||
|
:param frame: a 2D array of 32-bit integers
|
||||||
|
:param file_path: a relative path on PC
|
||||||
|
:param pixel_width: bit depth that the PGM will use (8 or 16)
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
To capture a 32x64 frame and write it as a 8-bit PGM file to PC: ::
|
||||||
|
|
||||||
|
# Prepare a 32x64 2D array
|
||||||
|
frame = numpy.array([[0] * 32] * 64)
|
||||||
|
|
||||||
|
# Setup the camera to use LinkTriger0 and start acquisition
|
||||||
|
# (Read the camera setting XML file for details)
|
||||||
|
cxp_grabber.write32(TRIG_SETTING_ADDR, 0)
|
||||||
|
...
|
||||||
|
|
||||||
|
# Setup ROI viewer coordinate and start the viewer capture
|
||||||
|
cxp_grabber.start_roi_viewer(0, 0, 32, 64)
|
||||||
|
|
||||||
|
# Send LinkTrigger0
|
||||||
|
cxp_grabber.send_cxp_linktrigger(0)
|
||||||
|
|
||||||
|
# Read the frame from ROI viewer and write it as a 8-bit PGM image to PC
|
||||||
|
cxp_grabber.read_roi_viewer_frame(frame)
|
||||||
|
write_pgm(frame, "frame.pgm", 8)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not isinstance(frame, ndarray):
|
||||||
|
raise ValueError("Frame must be a numpy array")
|
||||||
|
|
||||||
|
if pixel_width == 8:
|
||||||
|
frame = frame.astype("u1")
|
||||||
|
elif pixel_width == 16:
|
||||||
|
# PGM use big-endian
|
||||||
|
frame = frame.astype(">u2")
|
||||||
|
else:
|
||||||
|
raise ValueError("PGM file format only supports 8-bit or 16-bit per pixel")
|
||||||
|
|
||||||
|
# Save as PGM binary variant
|
||||||
|
# https://en.wikipedia.org/wiki/Netpbm#Description
|
||||||
|
with open(file_path, "wb") as file:
|
||||||
|
max_value = (2**pixel_width) - 1
|
||||||
|
width, height = len(frame[0]), len(frame)
|
||||||
|
file.write(f"P5\n{width} {height}\n{max_value}\n".encode("ASCII"))
|
||||||
|
file.write(frame.tobytes())
|
||||||
|
|
||||||
|
|
||||||
|
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:`CXPGrabberTimeoutException` 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 CXPGrabberTimeoutException(
|
||||||
|
"Timeout before CoaXPress 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 CXPGrabberTimeoutException(
|
||||||
|
"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 read_local_xml(self, buffer):
|
||||||
|
"""
|
||||||
|
Read the XML setting file from the camera if available.
|
||||||
|
Data will be in 32-bit big-endian encoding.
|
||||||
|
The file format may be .zip or .xml depending on the camera model.
|
||||||
|
|
||||||
|
.. warning:: This is NOT a real-time operation.
|
||||||
|
|
||||||
|
:param buffer: list to be filled
|
||||||
|
:returns: number of 32-bit words read
|
||||||
|
"""
|
||||||
|
return cxp_download_xml_file(buffer)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def start_roi_viewer(self, x0, y0, x1, y1):
|
||||||
|
"""
|
||||||
|
Defines the coordinates of ROI viewer and start the capture.
|
||||||
|
|
||||||
|
Unlike :exc:`setup_roi`, ROI viewer has a maximum height limit of 1024 and total size limit of 4096 pixels.
|
||||||
|
|
||||||
|
.. warning:: This is NOT a real-time operation.
|
||||||
|
"""
|
||||||
|
cxp_start_roi_viewer(x0, y0, x1, y1)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def read_roi_viewer_frame(self, frame):
|
||||||
|
"""
|
||||||
|
Read the ROI viewer frame.
|
||||||
|
|
||||||
|
The user must :exc:`start_roi_viewer` and trigger the camera before the frame is available.
|
||||||
|
|
||||||
|
.. warning:: This is NOT a real-time operation.
|
||||||
|
|
||||||
|
:param frame: a 2D array of 32-bit integers
|
||||||
|
:returns: the frame bit depth
|
||||||
|
"""
|
||||||
|
buffer = [0] * 1024
|
||||||
|
width, height, pixel_width = cxp_download_roi_viewer_frame(buffer)
|
||||||
|
if height != len(frame) or width != len(frame[0]):
|
||||||
|
raise ValueError(
|
||||||
|
"The frame matrix size is not the same as ROI viewer frame size"
|
||||||
|
)
|
||||||
|
|
||||||
|
for y in range(height):
|
||||||
|
offset = (((width + 3) & (~3)) // 4) * y
|
||||||
|
for x in range(width):
|
||||||
|
# each buffer element holds 4 pixels
|
||||||
|
frame[y][x] = (buffer[offset + (x // 4)] >> (16 * (x % 4))) & 0xFFFF
|
||||||
|
return pixel_width
|
@ -129,11 +129,6 @@ class CoreException:
|
|||||||
'\n\nEnd of Core Device Traceback\n'
|
'\n\nEnd of Core Device Traceback\n'
|
||||||
|
|
||||||
|
|
||||||
class InternalError(Exception):
|
|
||||||
"""Raised when the runtime encounters an internal error condition."""
|
|
||||||
artiq_builtin = True
|
|
||||||
|
|
||||||
|
|
||||||
class CacheError(Exception):
|
class CacheError(Exception):
|
||||||
"""Raised when putting a value into a cache row would violate memory safety."""
|
"""Raised when putting a value into a cache row would violate memory safety."""
|
||||||
artiq_builtin = True
|
artiq_builtin = True
|
||||||
@ -195,3 +190,7 @@ class SPIError(Exception):
|
|||||||
class UnwrapNoneError(Exception):
|
class UnwrapNoneError(Exception):
|
||||||
"""Raised when unwrapping a none Option."""
|
"""Raised when unwrapping a none Option."""
|
||||||
artiq_builtin = True
|
artiq_builtin = True
|
||||||
|
|
||||||
|
class CXPError(Exception):
|
||||||
|
"""Raised when CXP transaction fails."""
|
||||||
|
artiq_builtin = True
|
||||||
|
@ -329,7 +329,7 @@ extern fn stop_fn(_version: c_int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Must be kept in sync with `artiq.compiler.embedding`
|
// Must be kept in sync with `artiq.compiler.embedding`
|
||||||
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 });
|
||||||
|
@ -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,32 +218,30 @@ 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)? {
|
let write_result = write(busno, address << 1)
|
||||||
return Err("PCA9548 failed to ack write address")
|
.and_then( |_| write(busno, mask) );
|
||||||
}
|
let stop_result = stop(busno);
|
||||||
if !write(busno, mask)? {
|
|
||||||
return Err("PCA9548 failed to ack control word")
|
write_result.and(stop_result)
|
||||||
}
|
|
||||||
stop(busno)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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,36 +29,38 @@ 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)?;
|
||||||
i2c::write(self.busno, self.address)?;
|
let read_result = i2c::write(self.busno, self.address)
|
||||||
i2c::write(self.busno, addr)?;
|
.and_then( |_| i2c::write(self.busno, addr))
|
||||||
|
.and_then( |_| i2c::restart(self.busno))
|
||||||
|
.and_then( |_| i2c::write(self.busno, self.address | 1))
|
||||||
|
.and_then( |_| {
|
||||||
|
let buf_len = buf.len();
|
||||||
|
for (i, byte) in buf.iter_mut().enumerate() {
|
||||||
|
*byte = i2c::read(self.busno, i < buf_len - 1)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
i2c::restart(self.busno)?;
|
let stop_result = i2c::stop(self.busno);
|
||||||
i2c::write(self.busno, self.address | 1)?;
|
|
||||||
let buf_len = buf.len();
|
|
||||||
for (i, byte) in buf.iter_mut().enumerate() {
|
|
||||||
*byte = i2c::read(self.busno, i < buf_len - 1)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
i2c::stop(self.busno)?;
|
read_result.and(stop_result)
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// > 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,37 +143,42 @@ 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)?;
|
let write_result = i2c::write(self.busno, self.address)
|
||||||
i2c::write(self.busno, addr)?;
|
.and_then( |_| i2c::write(self.busno, addr))
|
||||||
i2c::write(self.busno, value)?;
|
.and_then( |_| i2c::write(self.busno, value));
|
||||||
i2c::stop(self.busno)?;
|
|
||||||
Ok(())
|
let stop_result = i2c::stop(self.busno);
|
||||||
|
write_result.and(stop_result)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +193,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 +207,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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
]
|
476
artiq/gateware/cxp_grabber/frame.py
Normal file
476
artiq/gateware/cxp_grabber/frame.py
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
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):
|
||||||
|
byte = [self.sink.data[i * 8 : (i + 1) * 8] for i in range(sink_dw // 8)]
|
||||||
|
sink_cases[i] = [
|
||||||
|
ring_buf[sink_dw*i:sink_dw*(i+1)].eq(Cat([b[::-1] for b in byte])),
|
||||||
|
]
|
||||||
|
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))][::-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)]),
|
||||||
|
),
|
||||||
|
If(pix.eof,
|
||||||
|
pix.y.eq(self.y_size),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -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))
|
||||||
|
|
0
artiq/gateware/test/cxp_grabber/__init__.py
Normal file
0
artiq/gateware/test/cxp_grabber/__init__.py
Normal file
58
artiq/gateware/test/cxp_grabber/packet_generator.py
Normal file
58
artiq/gateware/test/cxp_grabber/packet_generator.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
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 _switch_bit_order(s, width):
|
||||||
|
bits = "{:0{width}b}".format(s, width=width)
|
||||||
|
return int(bits[::-1], 2)
|
||||||
|
|
||||||
|
|
||||||
|
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 += _switch_bit_order(gray, pixel_width) << 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
|
||||||
|
word = []
|
||||||
|
for j in range(4):
|
||||||
|
word += [C(_switch_bit_order((serialized >> 8 * j) & 0xFF, 8), 8)]
|
||||||
|
|
||||||
|
eop = 1 if ((i == words_per_image_line - 1) and with_eol_marked) else 0
|
||||||
|
packet.append(
|
||||||
|
_WORDLAYOUT(data=Cat(word), 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()
|
@ -43,7 +43,7 @@ class _LogFilterProxyModel(QtCore.QSortFilterProxyModel):
|
|||||||
|
|
||||||
|
|
||||||
class _Model(QtCore.QAbstractItemModel):
|
class _Model(QtCore.QAbstractItemModel):
|
||||||
def __init__(self):
|
def __init__(self, palette):
|
||||||
QtCore.QAbstractTableModel.__init__(self)
|
QtCore.QAbstractTableModel.__init__(self)
|
||||||
|
|
||||||
self.headers = ["Source", "Message"]
|
self.headers = ["Source", "Message"]
|
||||||
@ -58,11 +58,16 @@ class _Model(QtCore.QAbstractItemModel):
|
|||||||
|
|
||||||
self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.SystemFont.FixedFont)
|
self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.SystemFont.FixedFont)
|
||||||
|
|
||||||
self.white = QtGui.QBrush(QtGui.QColor(255, 255, 255))
|
self.default_bg = palette.base()
|
||||||
self.black = QtGui.QBrush(QtGui.QColor(0, 0, 0))
|
self.default_fg = palette.text()
|
||||||
self.debug_fg = QtGui.QBrush(QtGui.QColor(55, 55, 55))
|
self.debug_fg = palette.placeholderText()
|
||||||
self.warning_bg = QtGui.QBrush(QtGui.QColor(255, 255, 180))
|
is_dark_mode = self.default_bg.color().lightness() < self.default_fg.color().lightness()
|
||||||
self.error_bg = QtGui.QBrush(QtGui.QColor(255, 150, 150))
|
if is_dark_mode:
|
||||||
|
self.warning_bg = QtGui.QBrush(QtGui.QColor(90, 74, 0))
|
||||||
|
self.error_bg = QtGui.QBrush(QtGui.QColor(98, 24, 24))
|
||||||
|
else:
|
||||||
|
self.warning_bg = QtGui.QBrush(QtGui.QColor(255, 255, 180))
|
||||||
|
self.error_bg = QtGui.QBrush(QtGui.QColor(255, 150, 150))
|
||||||
|
|
||||||
def headerData(self, col, orientation, role):
|
def headerData(self, col, orientation, role):
|
||||||
if (orientation == QtCore.Qt.Orientation.Horizontal
|
if (orientation == QtCore.Qt.Orientation.Horizontal
|
||||||
@ -163,13 +168,13 @@ class _Model(QtCore.QAbstractItemModel):
|
|||||||
elif level >= logging.WARNING:
|
elif level >= logging.WARNING:
|
||||||
return self.warning_bg
|
return self.warning_bg
|
||||||
else:
|
else:
|
||||||
return self.white
|
return self.default_bg
|
||||||
elif role == QtCore.Qt.ItemDataRole.ForegroundRole:
|
elif role == QtCore.Qt.ItemDataRole.ForegroundRole:
|
||||||
level = self.entries[msgnum][0]
|
level = self.entries[msgnum][0]
|
||||||
if level <= logging.DEBUG:
|
if level <= logging.DEBUG:
|
||||||
return self.debug_fg
|
return self.debug_fg
|
||||||
else:
|
else:
|
||||||
return self.black
|
return self.default_fg
|
||||||
elif role == QtCore.Qt.ItemDataRole.DisplayRole:
|
elif role == QtCore.Qt.ItemDataRole.DisplayRole:
|
||||||
v = self.entries[msgnum]
|
v = self.entries[msgnum]
|
||||||
column = index.column()
|
column = index.column()
|
||||||
@ -265,7 +270,7 @@ class LogDock(QDockWidgetCloseDetect):
|
|||||||
cw = QtGui.QFontMetrics(self.font()).averageCharWidth()
|
cw = QtGui.QFontMetrics(self.font()).averageCharWidth()
|
||||||
self.log.header().resizeSection(0, 26*cw)
|
self.log.header().resizeSection(0, 26*cw)
|
||||||
|
|
||||||
self.model = _Model()
|
self.model = _Model(self.palette())
|
||||||
self.proxy_model = _LogFilterProxyModel()
|
self.proxy_model = _LogFilterProxyModel()
|
||||||
self.proxy_model.setSourceModel(self.model)
|
self.proxy_model.setSourceModel(self.model)
|
||||||
self.log.setModel(self.proxy_model)
|
self.log.setModel(self.proxy_model)
|
||||||
|
@ -15,6 +15,7 @@ import traceback
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import linecache
|
import linecache
|
||||||
|
import threading
|
||||||
|
|
||||||
import h5py
|
import h5py
|
||||||
|
|
||||||
@ -36,23 +37,42 @@ from artiq import __version__ as artiq_version
|
|||||||
|
|
||||||
|
|
||||||
ipc = None
|
ipc = None
|
||||||
|
ipc_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
def get_object():
|
def get_object():
|
||||||
line = ipc.readline().decode()
|
ipc_lock.acquire()
|
||||||
return pyon.decode(line)
|
try:
|
||||||
|
line = ipc.readline()
|
||||||
|
finally:
|
||||||
|
ipc_lock.release()
|
||||||
|
return pyon.decode(line.decode())
|
||||||
|
|
||||||
|
|
||||||
def put_object(obj):
|
def put_object(obj):
|
||||||
ds = pyon.encode(obj)
|
ds = (pyon.encode(obj) + "\n").encode()
|
||||||
ipc.write((ds + "\n").encode())
|
ipc_lock.acquire()
|
||||||
|
try:
|
||||||
|
ipc.write(ds)
|
||||||
|
finally:
|
||||||
|
ipc_lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
def put_and_get_object(obj):
|
||||||
|
ds = (pyon.encode(obj) + "\n").encode()
|
||||||
|
ipc_lock.acquire()
|
||||||
|
try:
|
||||||
|
ipc.write(ds)
|
||||||
|
line = ipc.readline()
|
||||||
|
finally:
|
||||||
|
ipc_lock.release()
|
||||||
|
return pyon.decode(line.decode())
|
||||||
|
|
||||||
|
|
||||||
def make_parent_action(action):
|
def make_parent_action(action):
|
||||||
def parent_action(*args, **kwargs):
|
def parent_action(*args, **kwargs):
|
||||||
request = {"action": action, "args": args, "kwargs": kwargs}
|
request = {"action": action, "args": args, "kwargs": kwargs}
|
||||||
put_object(request)
|
reply = put_and_get_object(request)
|
||||||
reply = get_object()
|
|
||||||
if "action" in reply:
|
if "action" in reply:
|
||||||
if reply["action"] == "terminate":
|
if reply["action"] == "terminate":
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
@ -502,11 +502,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")
|
||||||
@ -720,13 +722,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_cls == CortexA9Target
|
is_zynq = exp.core.target_cls == CortexA9Target
|
||||||
count = 20000
|
count = 20000
|
||||||
|
@ -4,21 +4,21 @@
|
|||||||
from artiq.language.core import *
|
from artiq.language.core import *
|
||||||
from artiq.language.types import *
|
from artiq.language.types import *
|
||||||
|
|
||||||
# Make sure `byval` and `sret` are specified both at the call site and the
|
# Make sure `sret` is specified both at the call site and the
|
||||||
# declaration. This isn't caught by the LLVM IR validator, but mismatches
|
# declaration. This isn't caught by the LLVM IR validator, but mismatches
|
||||||
# lead to miscompilations (at least in LLVM 11).
|
# lead to miscompilations (at least in LLVM 11).
|
||||||
|
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def entrypoint():
|
def entrypoint():
|
||||||
# CHECK: call void @accept_str\({ i8\*, i32 }\* nonnull byval
|
# CHECK: call void @accept_str\({ i8\*, i32 }
|
||||||
accept_str("foo")
|
accept_str("foo")
|
||||||
|
|
||||||
# CHECK: call void @return_str\({ i8\*, i32 }\* nonnull sret
|
# CHECK: call void @return_str\({ i8\*, i32 }\* nonnull sret
|
||||||
return_str()
|
return_str()
|
||||||
|
|
||||||
|
|
||||||
# CHECK: declare void @accept_str\({ i8\*, i32 }\* byval\({ i8\*, i32 }\)\)
|
# CHECK: declare void @accept_str\({ i8\*, i32 }\)
|
||||||
@syscall
|
@syscall
|
||||||
def accept_str(name: TStr) -> TNone:
|
def accept_str(name: TStr) -> TNone:
|
||||||
pass
|
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)
|
||||||
|
@ -176,7 +176,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".
|
||||||
|
@ -170,3 +170,10 @@ Miscellaneous
|
|||||||
|
|
||||||
.. automodule:: artiq.coredevice.grabber
|
.. automodule:: artiq.coredevice.grabber
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
:mod:`artiq.coredevice.cxp_grabber` module
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. automodule:: artiq.coredevice.cxp_grabber
|
||||||
|
:members:
|
||||||
|
14
flake.lock
generated
14
flake.lock
generated
@ -44,11 +44,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1739446958,
|
"lastModified": 1741851582,
|
||||||
"narHash": "sha256-+/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc=",
|
"narHash": "sha256-cPfs8qMccim2RBgtKGF+x9IBCduRvd/N5F4nYpU0TVE=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "2ff53fe64443980e139eaa286017f53f88336dd0",
|
"rev": "6607cf789e541e7873d40d3a8f7815ea92204f32",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -129,11 +129,11 @@
|
|||||||
"src-misoc": {
|
"src-misoc": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1739436988,
|
"lastModified": 1741001607,
|
||||||
"narHash": "sha256-zEihEV6kqRtrZWyu7uCNyHOXE/rluVloPuT4ECYVJ+g=",
|
"narHash": "sha256-05BGqWV4Zc9ArwaW0uuBYWjg4oTeP4vznPQQjEpQPEM=",
|
||||||
"ref": "refs/heads/master",
|
"ref": "refs/heads/master",
|
||||||
"rev": "e3f4fd040b90b05d580bf578ca49244f0b7d861a",
|
"rev": "fa73f42f3c163833f17fc99399bb41005970c503",
|
||||||
"revCount": 2483,
|
"revCount": 2495,
|
||||||
"submodules": true,
|
"submodules": true,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/m-labs/misoc.git"
|
"url": "https://github.com/m-labs/misoc.git"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user