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)
|
||||
--------------------
|
||||
|
||||
* Hardware support:
|
||||
- CoaXPress grabber support on ZC706 with Hello-FPGA CXP 4R FMC card.
|
||||
- Improved SDRAM memory controller and DMA cores puts Kasli DMA performance on par with
|
||||
other platforms.
|
||||
- DRTIO repeater support across GT/EEM. This enables Shuttler support on DRTIO satellites.
|
||||
- Core device reflashing over the network through the new ``flash`` tool in ``artiq_coremgmt``.
|
||||
It also supports configuring and reflashing DRTIO satellites over the DRTIO link.
|
||||
- Fastino monitoring with Moninj.
|
||||
- Zotino monitoring now displays the values in volts.
|
||||
- Support for the ultra-low-cost EBAZ4205 Zynq-7000 control card, with core device driver
|
||||
for the AD9834 DDS, tested with the ZonRi Technology Co., Ltd. AD9834-Module.
|
||||
* Dashboard:
|
||||
- Experiment windows can have different colors, selected by the user.
|
||||
- Zotino monitoring now displays the values in volts.
|
||||
- The Log pane now adapts to dark system color themes.
|
||||
- Schedule display columns can now be reordered and shown/hidden using the table
|
||||
header context menu.
|
||||
- State files are now automatically backed up upon successful loading.
|
||||
* afws_client now uses the "happy eyeballs" algorithm (RFC 6555) for a faster and more
|
||||
* ``afws_client`` now uses the "happy eyeballs" algorithm (RFC 6555) for a faster and more
|
||||
reliable connection to the server.
|
||||
* The Zadig driver installer was added to the MSYS2 offline installer.
|
||||
* Fastino monitoring with Moninj is now supported.
|
||||
* Qt6 support.
|
||||
* Python 3.12 support.
|
||||
* Compiler can now give automatic suggestions for ``kernel_invariants``.
|
||||
* Idle kernels now restart when written with ``artiq_coremgmt`` and stop when erased/removed from config.
|
||||
* New support for the EBAZ4205 Zynq-SoC control card.
|
||||
* New core device driver for the AD9834 DDS, tested with the ZonRi Technology Co., Ltd. AD9834-Module.
|
||||
* Support for coredevice reflashing through the new ``flash`` tool in ``artiq_coremgmt``.
|
||||
* ``artiq_coremgmt`` now supports configuring satellites.
|
||||
* Updated Rust support for Zynq-7000 firmware.
|
||||
* Qt6 support.
|
||||
* Python 3.12 support.
|
||||
* The Zadig driver installer was added to the MSYS2 offline installer.
|
||||
* ``artiq.coredevice.fmcdio_vhdci_eem`` has been removed.
|
||||
|
||||
ARTIQ-8
|
||||
|
@ -115,6 +115,7 @@ class EmbeddingMap:
|
||||
"0:ZeroDivisionError",
|
||||
"0:LinAlgError",
|
||||
"UnwrapNoneError",
|
||||
"CXPError"
|
||||
])
|
||||
|
||||
def preallocate_runtime_exception_names(self, names):
|
||||
|
@ -1524,12 +1524,6 @@ class LLVMIRGenerator:
|
||||
|
||||
for i, arg in enumerate(insn.arguments()):
|
||||
llarg = self.map(arg)
|
||||
if isinstance(llarg.type, (ll.LiteralStructType, ll.IdentifiedStructType)):
|
||||
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)
|
||||
|
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'
|
||||
|
||||
|
||||
class InternalError(Exception):
|
||||
"""Raised when the runtime encounters an internal error condition."""
|
||||
artiq_builtin = True
|
||||
|
||||
|
||||
class CacheError(Exception):
|
||||
"""Raised when putting a value into a cache row would violate memory safety."""
|
||||
artiq_builtin = True
|
||||
@ -195,3 +190,7 @@ class SPIError(Exception):
|
||||
class UnwrapNoneError(Exception):
|
||||
"""Raised when unwrapping a none Option."""
|
||||
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`
|
||||
static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [
|
||||
static EXCEPTION_ID_LOOKUP: [(&str, u32); 23] = [
|
||||
("RTIOUnderflow", 0),
|
||||
("RTIOOverflow", 1),
|
||||
("RTIODestinationUnreachable", 2),
|
||||
@ -352,6 +352,7 @@ static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [
|
||||
("ZeroDivisionError", 19),
|
||||
("LinAlgError", 20),
|
||||
("UnwrapNoneError", 21),
|
||||
("CXPError", 22)
|
||||
];
|
||||
|
||||
pub fn get_exception_id(name: &str) -> u32 {
|
||||
|
@ -209,7 +209,7 @@ fn terminate(exceptions: &'static [Option<eh_artiq::Exception<'static>>],
|
||||
loop {}
|
||||
}
|
||||
|
||||
extern fn cache_get<'a>(key: &CSlice<u8>) -> *const CSlice<'a, i32> {
|
||||
extern fn cache_get<'a>(key: CSlice<u8>) -> *const CSlice<'a, i32> {
|
||||
send(&CacheGetRequest {
|
||||
key: str::from_utf8(key.as_ref()).unwrap()
|
||||
});
|
||||
@ -218,7 +218,7 @@ extern fn cache_get<'a>(key: &CSlice<u8>) -> *const CSlice<'a, i32> {
|
||||
})
|
||||
}
|
||||
|
||||
extern "C-unwind" fn cache_put(key: &CSlice<u8>, list: &CSlice<i32>) {
|
||||
extern "C-unwind" fn cache_put(key: CSlice<u8>, list: &CSlice<i32>) {
|
||||
send(&CachePutRequest {
|
||||
key: str::from_utf8(key.as_ref()).unwrap(),
|
||||
value: list.as_ref()
|
||||
@ -251,7 +251,7 @@ fn dma_record_flush() {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C-unwind" fn dma_record_start(name: &CSlice<u8>) {
|
||||
extern "C-unwind" fn dma_record_start(name: CSlice<u8>) {
|
||||
let name = str::from_utf8(name.as_ref()).unwrap();
|
||||
|
||||
unsafe {
|
||||
@ -264,6 +264,7 @@ extern "C-unwind" fn dma_record_start(name: &CSlice<u8>) {
|
||||
dma_record_output as *const () as u32).unwrap();
|
||||
library.rebind(b"rtio_output_wide",
|
||||
dma_record_output_wide as *const () as u32).unwrap();
|
||||
board_misoc::cache::flush_cpu_icache();
|
||||
|
||||
DMA_RECORDER.active = true;
|
||||
send(&DmaRecordStart(name));
|
||||
@ -283,6 +284,7 @@ extern "C-unwind" fn dma_record_stop(duration: i64, enable_ddma: bool) {
|
||||
rtio::output as *const () as u32).unwrap();
|
||||
library.rebind(b"rtio_output_wide",
|
||||
rtio::output_wide as *const () as u32).unwrap();
|
||||
board_misoc::cache::flush_cpu_icache();
|
||||
|
||||
DMA_RECORDER.active = false;
|
||||
send(&DmaRecordStop {
|
||||
@ -359,7 +361,7 @@ extern fn dma_record_output_wide(target: i32, words: &CSlice<i32>) {
|
||||
}
|
||||
}
|
||||
|
||||
extern fn dma_erase(name: &CSlice<u8>) {
|
||||
extern fn dma_erase(name: CSlice<u8>) {
|
||||
let name = str::from_utf8(name.as_ref()).unwrap();
|
||||
|
||||
send(&DmaEraseRequest { name: name });
|
||||
@ -372,7 +374,7 @@ struct DmaTrace {
|
||||
uses_ddma: bool,
|
||||
}
|
||||
|
||||
extern "C-unwind" fn dma_retrieve(name: &CSlice<u8>) -> DmaTrace {
|
||||
extern "C-unwind" fn dma_retrieve(name: CSlice<u8>) -> DmaTrace {
|
||||
let name = str::from_utf8(name.as_ref()).unwrap();
|
||||
|
||||
send(&DmaRetrieveRequest { name: name });
|
||||
|
@ -90,15 +90,24 @@ fn map_frequency_settings(settings: &FrequencySettings) -> Result<FrequencySetti
|
||||
|
||||
fn write(reg: u8, val: u8) -> Result<()> {
|
||||
i2c::start(BUSNO).unwrap();
|
||||
if !i2c::write(BUSNO, ADDRESS << 1).unwrap() {
|
||||
return Err("Si5324 failed to ack write address")
|
||||
i2c::write(BUSNO, ADDRESS << 1).map_err(|err|
|
||||
match err {
|
||||
i2c::Error::Nack => "Si5324 failed to ack write address",
|
||||
err => err.into()
|
||||
}
|
||||
if !i2c::write(BUSNO, reg).unwrap() {
|
||||
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()
|
||||
}
|
||||
if !i2c::write(BUSNO, val).unwrap() {
|
||||
return Err("Si5324 failed to ack value")
|
||||
)?;
|
||||
i2c::write(BUSNO, val).map_err(|err|
|
||||
match err {
|
||||
i2c::Error::Nack => "Si5324 failed to ack value",
|
||||
err => err.into()
|
||||
}
|
||||
)?;
|
||||
i2c::stop(BUSNO).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
@ -106,29 +115,47 @@ fn write(reg: u8, val: u8) -> Result<()> {
|
||||
#[cfg(si5324_soft_reset)]
|
||||
fn write_no_ack_value(reg: u8, val: u8) -> Result<()> {
|
||||
i2c::start(BUSNO).unwrap();
|
||||
if !i2c::write(BUSNO, ADDRESS << 1).unwrap() {
|
||||
return Err("Si5324 failed to ack write address")
|
||||
i2c::write(BUSNO, ADDRESS << 1).map_err(|err|
|
||||
match err {
|
||||
i2c::Error::Nack => "Si5324 failed to ack write address",
|
||||
err => err.into()
|
||||
}
|
||||
if !i2c::write(BUSNO, reg).unwrap() {
|
||||
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::write(BUSNO, val).unwrap();
|
||||
)?;
|
||||
match i2c::write(BUSNO, val) {
|
||||
Ok(()) | Err(i2c::Error::Nack) => Ok(()),
|
||||
err => err
|
||||
}?;
|
||||
i2c::stop(BUSNO).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read(reg: u8) -> Result<u8> {
|
||||
i2c::start(BUSNO).unwrap();
|
||||
if !i2c::write(BUSNO, ADDRESS << 1).unwrap() {
|
||||
return Err("Si5324 failed to ack write address")
|
||||
i2c::write(BUSNO, ADDRESS << 1).map_err(|err|
|
||||
match err {
|
||||
i2c::Error::Nack => "Si5324 failed to ack write address",
|
||||
err => err.into()
|
||||
}
|
||||
if !i2c::write(BUSNO, reg).unwrap() {
|
||||
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();
|
||||
if !i2c::write(BUSNO, (ADDRESS << 1) | 1).unwrap() {
|
||||
return Err("Si5324 failed to ack read address")
|
||||
i2c::write(BUSNO, (ADDRESS << 1) | 1).map_err(|err|
|
||||
match err {
|
||||
i2c::Error::Nack => "Si5324 failed to ack read address",
|
||||
err => err.into()
|
||||
}
|
||||
)?;
|
||||
let val = i2c::read(BUSNO, false).unwrap();
|
||||
i2c::stop(BUSNO).unwrap();
|
||||
Ok(val)
|
||||
|
@ -1,12 +1,27 @@
|
||||
pub enum Error {
|
||||
NoSPI,
|
||||
InvalidBus,
|
||||
OtherError,
|
||||
}
|
||||
|
||||
impl From<Error> for &str {
|
||||
fn from(err: Error) -> &'static str {
|
||||
match err {
|
||||
Error::NoSPI => "SPI not supported",
|
||||
Error::InvalidBus => "Invalid SPI bus",
|
||||
Error::OtherError => "other error",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(has_converter_spi)]
|
||||
mod imp {
|
||||
use board_misoc::csr;
|
||||
use super::Error;
|
||||
|
||||
const INVALID_BUS: &'static str = "Invalid SPI bus";
|
||||
|
||||
pub fn set_config(busno: u8, flags: u8, length: u8, div: u8, cs: u8) -> Result<(), &'static str> {
|
||||
pub fn set_config(busno: u8, flags: u8, length: u8, div: u8, cs: u8) -> Result<(), Error> {
|
||||
if busno != 0 {
|
||||
return Err(INVALID_BUS)
|
||||
return Err(Error::InvalidBus)
|
||||
}
|
||||
unsafe {
|
||||
while csr::converter_spi::writable_read() == 0 {}
|
||||
@ -33,9 +48,9 @@ mod imp {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write(busno: u8, data: u32) -> Result<(), &'static str> {
|
||||
pub fn write(busno: u8, data: u32) -> Result<(), Error> {
|
||||
if busno != 0 {
|
||||
return Err(INVALID_BUS)
|
||||
return Err(Error::InvalidBus)
|
||||
}
|
||||
unsafe {
|
||||
while csr::converter_spi::writable_read() == 0 {}
|
||||
@ -44,9 +59,9 @@ mod imp {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read(busno: u8) -> Result<u32, &'static str> {
|
||||
pub fn read(busno: u8) -> Result<u32, Error> {
|
||||
if busno != 0 {
|
||||
return Err(INVALID_BUS)
|
||||
return Err(Error::InvalidBus)
|
||||
}
|
||||
Ok(unsafe {
|
||||
while csr::converter_spi::writable_read() == 0 {}
|
||||
@ -57,9 +72,11 @@ mod imp {
|
||||
|
||||
#[cfg(not(has_converter_spi))]
|
||||
mod imp {
|
||||
pub fn set_config(_busno: u8, _flags: u8, _length: u8, _div: u8, _cs: u8) -> Result<(), ()> { Err(()) }
|
||||
pub fn write(_busno: u8,_data: u32) -> Result<(), ()> { Err(()) }
|
||||
pub fn read(_busno: u8,) -> Result<u32, ()> { Err(()) }
|
||||
use super::Error;
|
||||
|
||||
pub fn set_config(_busno: u8, _flags: u8, _length: u8, _div: u8, _cs: u8) -> Result<(), Error> { Err(Error::NoSPI) }
|
||||
pub fn write(_busno: u8,_data: u32) -> Result<(), Error> { Err(Error::NoSPI) }
|
||||
pub fn read(_busno: u8,) -> Result<u32, Error> { Err(Error::NoSPI) }
|
||||
}
|
||||
|
||||
pub use self::imp::*;
|
||||
|
@ -1,8 +1,34 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
NoI2C,
|
||||
InvalidBus,
|
||||
Nack,
|
||||
SCLLow,
|
||||
SDALow,
|
||||
ArbitrationLost,
|
||||
IOExpanderError,
|
||||
OtherError,
|
||||
}
|
||||
|
||||
impl From<Error> for &str {
|
||||
fn from(err: Error) -> &'static str {
|
||||
match err {
|
||||
Error::NoI2C => "I2C not supported",
|
||||
Error::InvalidBus => "Invalid I2C bus",
|
||||
Error::Nack => "I2C write was not ACKed",
|
||||
Error::SCLLow => "SCL stuck low",
|
||||
Error::SDALow => "SDA stuck low",
|
||||
Error::ArbitrationLost => "SDA arbitration lost",
|
||||
Error::IOExpanderError => "I2C IO Expander error",
|
||||
Error::OtherError => "other error",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(has_i2c)]
|
||||
mod imp {
|
||||
use super::super::{csr, clock};
|
||||
|
||||
const INVALID_BUS: &'static str = "Invalid I2C bus";
|
||||
use super::Error;
|
||||
|
||||
fn half_period() { clock::spin_us(100) }
|
||||
fn sda_bit(busno: u8) -> u8 { 1 << (2 * busno + 1) }
|
||||
@ -52,7 +78,7 @@ mod imp {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() -> Result<(), &'static str> {
|
||||
pub fn init() -> Result<(), Error> {
|
||||
for busno in 0..csr::CONFIG_I2C_BUS_COUNT {
|
||||
let busno = busno as u8;
|
||||
scl_oe(busno, false);
|
||||
@ -74,26 +100,26 @@ mod imp {
|
||||
}
|
||||
|
||||
if !sda_i(busno) {
|
||||
return Err("SDA is stuck low and doesn't get unstuck");
|
||||
return Err(Error::SDALow);
|
||||
}
|
||||
if !scl_i(busno) {
|
||||
return Err("SCL is stuck low and doesn't get unstuck");
|
||||
return Err(Error::SCLLow);
|
||||
}
|
||||
// postcondition: SCL and SDA high
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start(busno: u8) -> Result<(), &'static str> {
|
||||
pub fn start(busno: u8) -> Result<(), Error> {
|
||||
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
|
||||
return Err(INVALID_BUS)
|
||||
return Err(Error::InvalidBus)
|
||||
}
|
||||
// precondition: SCL and SDA high
|
||||
if !scl_i(busno) {
|
||||
return Err("SCL is stuck low and doesn't get unstuck");
|
||||
return Err(Error::SCLLow);
|
||||
}
|
||||
if !sda_i(busno) {
|
||||
return Err("SDA arbitration lost");
|
||||
return Err(Error::ArbitrationLost);
|
||||
}
|
||||
sda_oe(busno, true);
|
||||
half_period();
|
||||
@ -102,9 +128,9 @@ mod imp {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn restart(busno: u8) -> Result<(), &'static str> {
|
||||
pub fn restart(busno: u8) -> Result<(), Error> {
|
||||
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
|
||||
return Err(INVALID_BUS)
|
||||
return Err(Error::InvalidBus)
|
||||
}
|
||||
// precondition SCL and SDA low
|
||||
sda_oe(busno, false);
|
||||
@ -116,9 +142,9 @@ mod imp {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(busno: u8) -> Result<(), &'static str> {
|
||||
pub fn stop(busno: u8) -> Result<(), Error> {
|
||||
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
|
||||
return Err(INVALID_BUS)
|
||||
return Err(Error::InvalidBus)
|
||||
}
|
||||
// precondition: SCL and SDA low
|
||||
half_period();
|
||||
@ -127,15 +153,15 @@ mod imp {
|
||||
sda_oe(busno, false);
|
||||
half_period();
|
||||
if !sda_i(busno) {
|
||||
return Err("SDA arbitration lost");
|
||||
return Err(Error::ArbitrationLost);
|
||||
}
|
||||
// postcondition: SCL and SDA high
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write(busno: u8, data: u8) -> Result<bool, &'static str> {
|
||||
pub fn write(busno: u8, data: u8) -> Result<(), Error> {
|
||||
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
|
||||
return Err(INVALID_BUS)
|
||||
return Err(Error::InvalidBus)
|
||||
}
|
||||
// precondition: SCL and SDA low
|
||||
// MSB first
|
||||
@ -156,12 +182,16 @@ mod imp {
|
||||
sda_oe(busno, true);
|
||||
// postcondition: SCL and SDA low
|
||||
|
||||
Ok(ack)
|
||||
if !ack {
|
||||
return Err(Error::Nack)
|
||||
}
|
||||
|
||||
pub fn read(busno: u8, ack: bool) -> Result<u8, &'static str> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read(busno: u8, ack: bool) -> Result<u8, Error> {
|
||||
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
|
||||
return Err(INVALID_BUS)
|
||||
return Err(Error::InvalidBus)
|
||||
}
|
||||
// precondition: SCL and SDA low
|
||||
sda_oe(busno, false);
|
||||
@ -188,32 +218,30 @@ mod imp {
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn switch_select(busno: u8, address: u8, mask: u8) -> Result<(), &'static str> {
|
||||
pub fn switch_select(busno: u8, address: u8, mask: u8) -> Result<(), Error> {
|
||||
// address in 7-bit form
|
||||
// mask in format of 1 << channel (or 0 for disabling output)
|
||||
// PCA9548 support only for now
|
||||
start(busno)?;
|
||||
if !write(busno, address << 1)? {
|
||||
return Err("PCA9548 failed to ack write address")
|
||||
}
|
||||
if !write(busno, mask)? {
|
||||
return Err("PCA9548 failed to ack control word")
|
||||
}
|
||||
stop(busno)?;
|
||||
Ok(())
|
||||
let write_result = write(busno, address << 1)
|
||||
.and_then( |_| write(busno, mask) );
|
||||
let stop_result = stop(busno);
|
||||
|
||||
write_result.and(stop_result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(has_i2c))]
|
||||
mod imp {
|
||||
const NO_I2C: &'static str = "No I2C support on this platform";
|
||||
pub fn init() -> Result<(), &'static str> { Err(NO_I2C) }
|
||||
pub fn start(_busno: u8) -> Result<(), &'static str> { Err(NO_I2C) }
|
||||
pub fn restart(_busno: u8) -> Result<(), &'static str> { Err(NO_I2C) }
|
||||
pub fn stop(_busno: u8) -> Result<(), &'static str> { Err(NO_I2C) }
|
||||
pub fn write(_busno: u8, _data: u8) -> Result<bool, &'static str> { Err(NO_I2C) }
|
||||
pub fn read(_busno: u8, _ack: bool) -> Result<u8, &'static str> { Err(NO_I2C) }
|
||||
pub fn switch_select(_busno: u8, _address: u8, _mask: u8) -> Result<(), &'static str> { Err(NO_I2C) }
|
||||
use super::Error;
|
||||
|
||||
pub fn init() -> Result<(), Error> { Err(Error::NoI2C) }
|
||||
pub fn start(_busno: u8) -> Result<(), Error> { Err(Error::NoI2C) }
|
||||
pub fn restart(_busno: u8) -> Result<(), Error> { Err(Error::NoI2C) }
|
||||
pub fn stop(_busno: u8) -> Result<(), Error> { Err(Error::NoI2C) }
|
||||
pub fn write(_busno: u8, _data: u8) -> Result<bool, Error> { Err(Error::NoI2C) }
|
||||
pub fn read(_busno: u8, _ack: bool) -> Result<u8, Error> { Err(Error::NoI2C) }
|
||||
pub fn switch_select(_busno: u8, _address: u8, _mask: u8) -> Result<(), Error> { Err(Error::NoI2C) }
|
||||
}
|
||||
|
||||
pub use self::imp::*;
|
||||
|
@ -29,36 +29,38 @@ impl EEPROM {
|
||||
}
|
||||
|
||||
#[cfg(soc_platform = "kasli")]
|
||||
fn select(&self) -> Result<(), &'static str> {
|
||||
fn select(&self) -> Result<(), i2c::Error> {
|
||||
let mask: u16 = 1 << self.port;
|
||||
i2c::switch_select(self.busno, 0x70, mask as u8)?;
|
||||
i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read<'a>(&self, addr: u8, buf: &'a mut [u8]) -> Result<(), &'static str> {
|
||||
pub fn read<'a>(&self, addr: u8, buf: &'a mut [u8]) -> Result<(), i2c::Error> {
|
||||
self.select()?;
|
||||
|
||||
i2c::start(self.busno)?;
|
||||
i2c::write(self.busno, self.address)?;
|
||||
i2c::write(self.busno, addr)?;
|
||||
|
||||
i2c::restart(self.busno)?;
|
||||
i2c::write(self.busno, self.address | 1)?;
|
||||
let read_result = i2c::write(self.busno, self.address)
|
||||
.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)?;
|
||||
}
|
||||
|
||||
i2c::stop(self.busno)?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let stop_result = i2c::stop(self.busno);
|
||||
|
||||
read_result.and(stop_result)
|
||||
}
|
||||
|
||||
/// > The 24AA02XEXX is programmed at the factory with a
|
||||
/// > globally unique node address stored in the upper half
|
||||
/// > of the array and permanently write-protected.
|
||||
pub fn read_eui48<'a>(&self) -> Result<[u8; 6], &'static str> {
|
||||
pub fn read_eui48<'a>(&self) -> Result<[u8; 6], i2c::Error> {
|
||||
let mut buffer = [0u8; 6];
|
||||
self.read(0xFA, &mut buffer)?;
|
||||
Ok(buffer)
|
||||
|
@ -23,7 +23,7 @@ pub struct IoExpander {
|
||||
|
||||
impl IoExpander {
|
||||
#[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))]
|
||||
pub fn new(index: u8) -> Result<Self, &'static str> {
|
||||
pub fn new(index: u8) -> Result<Self, i2c::Error> {
|
||||
const VIRTUAL_LED_MAPPING0: [(u8, u8, u8); 2] = [(0, 0, 6), (1, 1, 6)];
|
||||
const VIRTUAL_LED_MAPPING1: [(u8, u8, u8); 2] = [(2, 0, 6), (3, 1, 6)];
|
||||
|
||||
@ -79,7 +79,11 @@ impl IoExpander {
|
||||
gpiob: 0x13,
|
||||
},
|
||||
},
|
||||
_ => return Err("incorrect I/O expander index"),
|
||||
_ => {
|
||||
#[cfg(feature = "log")]
|
||||
log::error!("incorrect I/O expander index");
|
||||
return Err(i2c::Error::IOExpanderError)
|
||||
}
|
||||
};
|
||||
if !io_expander.check_ack()? {
|
||||
#[cfg(feature = "log")]
|
||||
@ -95,14 +99,16 @@ impl IoExpander {
|
||||
gpiob: 0x03,
|
||||
};
|
||||
if !io_expander.check_ack()? {
|
||||
return Err("Neither MCP23017 nor PCA9539 io expander found.");
|
||||
#[cfg(feature = "log")]
|
||||
log::error!("Neither MCP23017 nor PCA9539 io expander found.");
|
||||
return Err(i2c::Error::IOExpanderError);
|
||||
};
|
||||
}
|
||||
Ok(io_expander)
|
||||
}
|
||||
|
||||
#[cfg(soc_platform = "efc")]
|
||||
pub fn new() -> Result<Self, &'static str> {
|
||||
pub fn new() -> Result<Self, i2c::Error> {
|
||||
const VIRTUAL_LED_MAPPING: [(u8, u8, u8); 2] = [(0, 0, 5), (1, 0, 6)];
|
||||
|
||||
let io_expander = IoExpander {
|
||||
@ -121,13 +127,15 @@ impl IoExpander {
|
||||
},
|
||||
};
|
||||
if !io_expander.check_ack()? {
|
||||
return Err("MCP23017 io expander not found.");
|
||||
#[cfg(feature = "log")]
|
||||
log::error!("MCP23017 io expander not found.");
|
||||
return Err(i2c::Error::IOExpanderError);
|
||||
};
|
||||
Ok(io_expander)
|
||||
}
|
||||
|
||||
#[cfg(soc_platform = "kasli")]
|
||||
fn select(&self) -> Result<(), &'static str> {
|
||||
fn select(&self) -> Result<(), i2c::Error> {
|
||||
let mask: u16 = 1 << self.port;
|
||||
i2c::switch_select(self.busno, 0x70, mask as u8)?;
|
||||
i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8)?;
|
||||
@ -135,37 +143,42 @@ impl IoExpander {
|
||||
}
|
||||
|
||||
#[cfg(soc_platform = "efc")]
|
||||
fn select(&self) -> Result<(), &'static str> {
|
||||
fn select(&self) -> Result<(), i2c::Error> {
|
||||
let mask: u16 = 1 << self.port;
|
||||
i2c::switch_select(self.busno, 0x70, mask as u8)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&self, addr: u8, value: u8) -> Result<(), &'static str> {
|
||||
fn write(&self, addr: u8, value: u8) -> Result<(), i2c::Error> {
|
||||
i2c::start(self.busno)?;
|
||||
i2c::write(self.busno, self.address)?;
|
||||
i2c::write(self.busno, addr)?;
|
||||
i2c::write(self.busno, value)?;
|
||||
i2c::stop(self.busno)?;
|
||||
Ok(())
|
||||
let write_result = i2c::write(self.busno, self.address)
|
||||
.and_then( |_| i2c::write(self.busno, addr))
|
||||
.and_then( |_| i2c::write(self.busno, value));
|
||||
|
||||
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
|
||||
self.select()?;
|
||||
i2c::start(self.busno)?;
|
||||
let ack = i2c::write(self.busno, self.address)?;
|
||||
let ack = match i2c::write(self.busno, self.address) {
|
||||
Ok(()) => true,
|
||||
Err(i2c::Error::Nack) => false,
|
||||
Err(err) => return Err(err)
|
||||
};
|
||||
i2c::stop(self.busno)?;
|
||||
Ok(ack)
|
||||
}
|
||||
|
||||
fn update_iodir(&self) -> Result<(), &'static str> {
|
||||
fn update_iodir(&self) -> Result<(), i2c::Error> {
|
||||
self.write(self.registers.iodira, self.iodir[0])?;
|
||||
self.write(self.registers.iodirb, self.iodir[1])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> Result<(), &'static str> {
|
||||
pub fn init(&mut self) -> Result<(), i2c::Error> {
|
||||
self.select()?;
|
||||
|
||||
for (_led, port, bit) in self.virtual_led_mapping.iter() {
|
||||
@ -180,7 +193,7 @@ impl IoExpander {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_oe(&mut self, port: u8, outputs: u8) -> Result<(), &'static str> {
|
||||
pub fn set_oe(&mut self, port: u8, outputs: u8) -> Result<(), i2c::Error> {
|
||||
self.iodir[port as usize] &= !outputs;
|
||||
self.update_iodir()?;
|
||||
Ok(())
|
||||
@ -194,7 +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() {
|
||||
let level = unsafe { (csr::virtual_leds::status_read() >> led) & 1 };
|
||||
self.set(*port, *bit, level != 0);
|
||||
|
@ -10,7 +10,7 @@ use core::cell::RefCell;
|
||||
|
||||
const BUFFER_SIZE: usize = 512 * 1024;
|
||||
|
||||
#[repr(align(64))]
|
||||
#[repr(align(2048))]
|
||||
struct Buffer {
|
||||
data: [u8; BUFFER_SIZE],
|
||||
}
|
||||
|
@ -14,11 +14,12 @@ mod remote_i2c {
|
||||
use drtio_routing;
|
||||
use rtio_mgt::drtio;
|
||||
use sched::{Io, Mutex};
|
||||
use super::local_i2c;
|
||||
|
||||
pub fn start(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||
routing_table: &drtio_routing::RoutingTable,
|
||||
linkno: u8, destination: u8, busno: u8
|
||||
) -> Result<(), &'static str> {
|
||||
) -> Result<(), local_i2c::Error> {
|
||||
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||
&drtioaux::Packet::I2cStartRequest {
|
||||
destination: destination,
|
||||
@ -26,15 +27,15 @@ mod remote_i2c {
|
||||
});
|
||||
match reply {
|
||||
Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => {
|
||||
if succeeded { Ok(()) } else { Err("i2c basic reply error") }
|
||||
if succeeded { Ok(()) } else { Err(local_i2c::Error::OtherError) }
|
||||
}
|
||||
Ok(packet) => {
|
||||
error!("received unexpected aux packet: {:?}", packet);
|
||||
Err("received unexpected aux packet")
|
||||
Err(local_i2c::Error::OtherError)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("aux packet error ({})", e);
|
||||
Err("aux packet error")
|
||||
Err(local_i2c::Error::OtherError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -42,7 +43,7 @@ mod remote_i2c {
|
||||
pub fn restart(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||
routing_table: &drtio_routing::RoutingTable,
|
||||
linkno: u8, destination: u8, busno: u8
|
||||
) -> Result<(), &'static str> {
|
||||
) -> Result<(), local_i2c::Error> {
|
||||
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||
&drtioaux::Packet::I2cRestartRequest {
|
||||
destination: destination,
|
||||
@ -50,15 +51,15 @@ mod remote_i2c {
|
||||
});
|
||||
match reply {
|
||||
Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => {
|
||||
if succeeded { Ok(()) } else { Err("i2c basic reply error") }
|
||||
if succeeded { Ok(()) } else { Err(local_i2c::Error::OtherError) }
|
||||
}
|
||||
Ok(packet) => {
|
||||
error!("received unexpected aux packet: {:?}", packet);
|
||||
Err("received unexpected aux packet")
|
||||
Err(local_i2c::Error::OtherError)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("aux packet error ({})", e);
|
||||
Err("aux packet error")
|
||||
Err(local_i2c::Error::OtherError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -66,7 +67,7 @@ mod remote_i2c {
|
||||
pub fn stop(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||
routing_table: &drtio_routing::RoutingTable,
|
||||
linkno: u8, destination: u8, busno: u8
|
||||
) -> Result<(), &'static str> {
|
||||
) -> Result<(), local_i2c::Error> {
|
||||
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||
&drtioaux::Packet::I2cStopRequest {
|
||||
destination: destination,
|
||||
@ -74,15 +75,15 @@ mod remote_i2c {
|
||||
});
|
||||
match reply {
|
||||
Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => {
|
||||
if succeeded { Ok(()) } else { Err("i2c basic reply error") }
|
||||
if succeeded { Ok(()) } else { Err(local_i2c::Error::OtherError) }
|
||||
}
|
||||
Ok(packet) => {
|
||||
error!("received unexpected aux packet: {:?}", packet);
|
||||
Err("received unexpected aux packet")
|
||||
Err(local_i2c::Error::OtherError)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("aux packet error ({})", e);
|
||||
Err("aux packet error")
|
||||
Err(local_i2c::Error::OtherError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -90,7 +91,7 @@ mod remote_i2c {
|
||||
pub fn write(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||
routing_table: &drtio_routing::RoutingTable,
|
||||
linkno: u8, destination: u8, busno: u8, data: u8
|
||||
) -> Result<bool, &'static str> {
|
||||
) -> Result<(), local_i2c::Error> {
|
||||
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||
&drtioaux::Packet::I2cWriteRequest {
|
||||
destination: destination,
|
||||
@ -99,15 +100,21 @@ mod remote_i2c {
|
||||
});
|
||||
match reply {
|
||||
Ok(drtioaux::Packet::I2cWriteReply { succeeded, ack }) => {
|
||||
if succeeded { Ok(ack) } else { Err("i2c write reply error") }
|
||||
if succeeded && ack {
|
||||
Ok(())
|
||||
} else if !ack {
|
||||
Err(local_i2c::Error::Nack)
|
||||
} else {
|
||||
Err(local_i2c::Error::OtherError)
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
error!("received unexpected aux packet");
|
||||
Err("received unexpected aux packet")
|
||||
Err(local_i2c::Error::OtherError)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("aux packet error ({})", e);
|
||||
Err("aux packet error")
|
||||
Err(local_i2c::Error::OtherError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,7 +122,7 @@ mod remote_i2c {
|
||||
pub fn read(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||
routing_table: &drtio_routing::RoutingTable,
|
||||
linkno: u8, destination: u8, busno: u8, ack: bool
|
||||
) -> Result<u8, &'static str> {
|
||||
) -> Result<u8, local_i2c::Error> {
|
||||
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||
&drtioaux::Packet::I2cReadRequest {
|
||||
destination: destination,
|
||||
@ -124,15 +131,15 @@ mod remote_i2c {
|
||||
});
|
||||
match reply {
|
||||
Ok(drtioaux::Packet::I2cReadReply { succeeded, data }) => {
|
||||
if succeeded { Ok(data) } else { Err("i2c read reply error") }
|
||||
if succeeded { Ok(data) } else { Err(local_i2c::Error::OtherError) }
|
||||
}
|
||||
Ok(_) => {
|
||||
error!("received unexpected aux packet");
|
||||
Err("received unexpected aux packet")
|
||||
Err(local_i2c::Error::OtherError)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("aux packet error ({})", e);
|
||||
Err("aux packet error")
|
||||
Err(local_i2c::Error::OtherError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -140,7 +147,7 @@ mod remote_i2c {
|
||||
pub fn switch_select(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||
routing_table: &drtio_routing::RoutingTable,
|
||||
linkno: u8, destination: u8, busno: u8, address: u8, mask: u8
|
||||
) -> Result<(), &'static str> {
|
||||
) -> Result<(), local_i2c::Error> {
|
||||
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||
&drtioaux::Packet::I2cSwitchSelectRequest {
|
||||
destination: destination,
|
||||
@ -150,15 +157,15 @@ mod remote_i2c {
|
||||
});
|
||||
match reply {
|
||||
Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => {
|
||||
if succeeded { Ok(()) } else { Err("i2c basic reply error") }
|
||||
if succeeded { Ok(()) } else { Err(local_i2c::Error::OtherError) }
|
||||
}
|
||||
Ok(packet) => {
|
||||
error!("received unexpected aux packet: {:?}", packet);
|
||||
Err("received unexpected aux packet")
|
||||
Err(local_i2c::Error::OtherError)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("aux packet error ({})", e);
|
||||
Err("aux packet error")
|
||||
Err(local_i2c::Error::OtherError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,11 +177,12 @@ mod remote_spi {
|
||||
use drtio_routing;
|
||||
use rtio_mgt::drtio;
|
||||
use sched::{Io, Mutex};
|
||||
use super::local_spi;
|
||||
|
||||
pub fn set_config(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||
routing_table: &drtio_routing::RoutingTable,
|
||||
linkno: u8, destination: u8, busno: u8, flags: u8, length: u8, div: u8, cs: u8
|
||||
) -> Result<(), ()> {
|
||||
) -> Result<(), local_spi::Error> {
|
||||
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, &drtioaux::Packet::SpiSetConfigRequest {
|
||||
destination: destination,
|
||||
busno: busno,
|
||||
@ -185,15 +193,15 @@ mod remote_spi {
|
||||
});
|
||||
match reply {
|
||||
Ok(drtioaux::Packet::SpiBasicReply { succeeded }) => {
|
||||
if succeeded { Ok(()) } else { Err(()) }
|
||||
if succeeded { Ok(()) } else { Err(local_spi::Error::OtherError) }
|
||||
}
|
||||
Ok(packet) => {
|
||||
error!("received unexpected aux packet: {:?}", packet);
|
||||
Err(())
|
||||
Err(local_spi::Error::OtherError)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("aux packet error ({})", e);
|
||||
Err(())
|
||||
Err(local_spi::Error::OtherError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -201,7 +209,7 @@ mod remote_spi {
|
||||
pub fn write(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||
routing_table: &drtio_routing::RoutingTable,
|
||||
linkno: u8, destination: u8, busno: u8, data: u32
|
||||
) -> Result<(), ()> {
|
||||
) -> Result<(), local_spi::Error> {
|
||||
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, &drtioaux::Packet::SpiWriteRequest {
|
||||
destination: destination,
|
||||
busno: busno,
|
||||
@ -209,22 +217,22 @@ mod remote_spi {
|
||||
});
|
||||
match reply {
|
||||
Ok(drtioaux::Packet::SpiBasicReply { succeeded }) => {
|
||||
if succeeded { Ok(()) } else { Err(()) }
|
||||
if succeeded { Ok(()) } else { Err(local_spi::Error::OtherError) }
|
||||
}
|
||||
Ok(packet) => {
|
||||
error!("received unexpected aux packet: {:?}", packet);
|
||||
Err(())
|
||||
Err(local_spi::Error::OtherError)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("aux packet error ({})", e);
|
||||
Err(())
|
||||
Err(local_spi::Error::OtherError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||
routing_table: &drtio_routing::RoutingTable, linkno: u8, destination: u8, busno: u8
|
||||
) -> Result<u32, ()> {
|
||||
) -> Result<u32, local_spi::Error> {
|
||||
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||
&drtioaux::Packet::SpiReadRequest {
|
||||
destination: destination,
|
||||
@ -232,15 +240,15 @@ mod remote_spi {
|
||||
});
|
||||
match reply {
|
||||
Ok(drtioaux::Packet::SpiReadReply { succeeded, data }) => {
|
||||
if succeeded { Ok(data) } else { Err(()) }
|
||||
if succeeded { Ok(data) } else { Err(local_spi::Error::OtherError) }
|
||||
}
|
||||
Ok(packet) => {
|
||||
error!("received unexpected aux packet: {:?}", packet);
|
||||
Err(())
|
||||
Err(local_spi::Error::OtherError)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("aux packet error ({})", e);
|
||||
Err(())
|
||||
Err(local_spi::Error::OtherError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -306,7 +314,8 @@ pub fn process_kern_hwreq(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subker
|
||||
}
|
||||
&kern::I2cWriteRequest { busno, data } => {
|
||||
match dispatch!(io, aux_mutex, ddma_mutex, subkernel_mutex, local_i2c, remote_i2c, routing_table, busno, write, data) {
|
||||
Ok(ack) => kern_send(io, &kern::I2cWriteReply { succeeded: true, ack: ack }),
|
||||
Ok(()) => kern_send(io, &kern::I2cWriteReply { succeeded: true, ack: true }),
|
||||
Err(local_i2c::Error::Nack) => kern_send(io, &kern::I2cWriteReply { succeeded: true, ack: false }),
|
||||
Err(_) => kern_send(io, &kern::I2cWriteReply { succeeded: false, ack: false })
|
||||
}
|
||||
}
|
||||
|
@ -1050,8 +1050,10 @@ fn process_kern_hwreq(request: &kern::Message, self_destination: u8) -> Result<b
|
||||
}
|
||||
&kern::I2cWriteRequest { busno, data } => {
|
||||
match i2c::write(busno as u8, data) {
|
||||
Ok(ack) => kern_send(
|
||||
&kern::I2cWriteReply { succeeded: true, ack: ack }),
|
||||
Ok(()) => kern_send(
|
||||
&kern::I2cWriteReply { succeeded: true, ack: true }),
|
||||
Err(i2c::Error::Nack) => kern_send(
|
||||
&kern::I2cWriteReply { succeeded: true, ack: false }),
|
||||
Err(_) => kern_send(
|
||||
&kern::I2cWriteReply { succeeded: false, ack: false })
|
||||
}
|
||||
|
@ -321,8 +321,10 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
|
||||
drtioaux::Packet::I2cWriteRequest { destination: _destination, busno, data } => {
|
||||
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||
match i2c::write(busno, data) {
|
||||
Ok(ack) => drtioaux::send(0,
|
||||
&drtioaux::Packet::I2cWriteReply { succeeded: true, ack: ack }),
|
||||
Ok(()) => drtioaux::send(0,
|
||||
&drtioaux::Packet::I2cWriteReply { succeeded: true, ack: true }),
|
||||
Err(i2c::Error::Nack) => drtioaux::send(0,
|
||||
&drtioaux::Packet::I2cWriteReply { succeeded: true, ack: false }),
|
||||
Err(_) => drtioaux::send(0,
|
||||
&drtioaux::Packet::I2cWriteReply { succeeded: false, ack: false })
|
||||
}
|
||||
@ -647,7 +649,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
|
||||
drtioaux::Packet::CoreMgmtDropLinkAck { destination: _destination } => {
|
||||
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||
|
||||
#[cfg(not(has_drtio_eem))]
|
||||
#[cfg(not(soc_platform = "efc"))]
|
||||
unsafe {
|
||||
csr::gt_drtio::txenable_write(0);
|
||||
}
|
||||
@ -949,7 +951,7 @@ fn startup() {
|
||||
io_expander.service().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(has_drtio_eem))]
|
||||
#[cfg(not(soc_platform = "efc"))]
|
||||
unsafe {
|
||||
csr::gt_drtio::txenable_write(0xffffffffu32 as _);
|
||||
}
|
||||
|
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 += [
|
||||
membus.cyc.eq(self.sink.stb),
|
||||
membus.stb.eq(self.sink.stb),
|
||||
membus.cti.eq(Mux(self.sink.last, 0b111, 0b010)),
|
||||
self.sink.ack.eq(membus.ack),
|
||||
membus.we.eq(1),
|
||||
membus.dat_w.eq(dma.convert_signal(self.sink.data, cpu_dw//8))
|
||||
]
|
||||
|
||||
if messages_per_dw > 1:
|
||||
for i in range(dw//8):
|
||||
self.comb += membus.sel[i].eq(
|
||||
@ -201,8 +203,9 @@ class Analyzer(Module, AutoCSR):
|
||||
|
||||
self.submodules.message_encoder = MessageEncoder(
|
||||
tsc, cri, self.enable.storage)
|
||||
hi_wm = 64 if fifo_depth > 64 else None
|
||||
self.submodules.fifo = stream.SyncFIFO(
|
||||
[("data", message_len)], fifo_depth, True)
|
||||
[("data", message_len)], fifo_depth, True, hi_wm=hi_wm)
|
||||
self.submodules.converter = stream.Converter(
|
||||
message_len, len(membus.dat_w), reverse=True,
|
||||
report_valid_token_count=True)
|
||||
|
@ -35,23 +35,39 @@ class WishboneReader(Module):
|
||||
# # #
|
||||
|
||||
bus_stb = Signal()
|
||||
data_reg_loaded = Signal()
|
||||
|
||||
transfer_cyc = Signal(max=64, reset=64-1)
|
||||
transfer_cyc_ce = Signal()
|
||||
transfer_cyc_rst = Signal()
|
||||
self.sync += [
|
||||
If(transfer_cyc_rst,
|
||||
transfer_cyc.eq(transfer_cyc.reset),
|
||||
).Elif(transfer_cyc_ce,
|
||||
transfer_cyc.eq(transfer_cyc - 1),
|
||||
)
|
||||
]
|
||||
|
||||
last = Signal()
|
||||
|
||||
self.comb += [
|
||||
bus_stb.eq(self.sink.stb & (~data_reg_loaded | self.source.ack)),
|
||||
# source ack (from FIFO) signals FIFO space availability
|
||||
bus_stb.eq(self.sink.stb & self.source.ack),
|
||||
last.eq(transfer_cyc == 0),
|
||||
|
||||
transfer_cyc_rst.eq(self.source.stb & self.source.ack & (self.sink.eop | last)),
|
||||
transfer_cyc_ce.eq(self.source.stb & self.source.ack),
|
||||
|
||||
bus.cyc.eq(bus_stb),
|
||||
bus.stb.eq(bus_stb),
|
||||
bus.cti.eq(Mux((self.sink.eop | last), 0b111, 0b010)),
|
||||
bus.adr.eq(self.sink.address),
|
||||
|
||||
self.sink.ack.eq(bus.ack),
|
||||
self.source.stb.eq(data_reg_loaded),
|
||||
]
|
||||
self.sync += [
|
||||
If(self.source.ack, data_reg_loaded.eq(0)),
|
||||
If(bus.ack,
|
||||
data_reg_loaded.eq(1),
|
||||
self.source.stb.eq(bus.ack),
|
||||
|
||||
self.source.data.eq(convert_signal(bus.dat_r, cpu_dw//8)),
|
||||
self.source.eop.eq(self.sink.eop)
|
||||
)
|
||||
self.source.last.eq(self.sink.eop | last),
|
||||
self.source.eop.eq(self.sink.eop),
|
||||
]
|
||||
|
||||
|
||||
@ -341,13 +357,16 @@ class DMA(Module):
|
||||
|
||||
flow_enable = Signal()
|
||||
self.submodules.dma = DMAReader(membus, flow_enable, cpu_dw)
|
||||
self.submodules.fifo = stream.SyncFIFO(
|
||||
[("data", len(membus.dat_w))], 128, True, lo_wm=64)
|
||||
self.submodules.slicer = RecordSlicer(len(membus.dat_w))
|
||||
self.submodules.time_offset = TimeOffset()
|
||||
self.submodules.cri_master = CRIMaster()
|
||||
self.cri = self.cri_master.cri
|
||||
|
||||
self.comb += [
|
||||
self.dma.source.connect(self.slicer.sink),
|
||||
self.dma.source.connect(self.fifo.sink),
|
||||
self.fifo.source.connect(self.slicer.sink),
|
||||
self.slicer.source.connect(self.time_offset.sink),
|
||||
self.time_offset.source.connect(self.cri_master.sink)
|
||||
]
|
||||
|
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):
|
||||
def __init__(self):
|
||||
def __init__(self, palette):
|
||||
QtCore.QAbstractTableModel.__init__(self)
|
||||
|
||||
self.headers = ["Source", "Message"]
|
||||
@ -58,9 +58,14 @@ class _Model(QtCore.QAbstractItemModel):
|
||||
|
||||
self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.SystemFont.FixedFont)
|
||||
|
||||
self.white = QtGui.QBrush(QtGui.QColor(255, 255, 255))
|
||||
self.black = QtGui.QBrush(QtGui.QColor(0, 0, 0))
|
||||
self.debug_fg = QtGui.QBrush(QtGui.QColor(55, 55, 55))
|
||||
self.default_bg = palette.base()
|
||||
self.default_fg = palette.text()
|
||||
self.debug_fg = palette.placeholderText()
|
||||
is_dark_mode = self.default_bg.color().lightness() < self.default_fg.color().lightness()
|
||||
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))
|
||||
|
||||
@ -163,13 +168,13 @@ class _Model(QtCore.QAbstractItemModel):
|
||||
elif level >= logging.WARNING:
|
||||
return self.warning_bg
|
||||
else:
|
||||
return self.white
|
||||
return self.default_bg
|
||||
elif role == QtCore.Qt.ItemDataRole.ForegroundRole:
|
||||
level = self.entries[msgnum][0]
|
||||
if level <= logging.DEBUG:
|
||||
return self.debug_fg
|
||||
else:
|
||||
return self.black
|
||||
return self.default_fg
|
||||
elif role == QtCore.Qt.ItemDataRole.DisplayRole:
|
||||
v = self.entries[msgnum]
|
||||
column = index.column()
|
||||
@ -265,7 +270,7 @@ class LogDock(QDockWidgetCloseDetect):
|
||||
cw = QtGui.QFontMetrics(self.font()).averageCharWidth()
|
||||
self.log.header().resizeSection(0, 26*cw)
|
||||
|
||||
self.model = _Model()
|
||||
self.model = _Model(self.palette())
|
||||
self.proxy_model = _LogFilterProxyModel()
|
||||
self.proxy_model.setSourceModel(self.model)
|
||||
self.log.setModel(self.proxy_model)
|
||||
|
@ -15,6 +15,7 @@ import traceback
|
||||
from collections import OrderedDict
|
||||
import importlib.util
|
||||
import linecache
|
||||
import threading
|
||||
|
||||
import h5py
|
||||
|
||||
@ -36,23 +37,42 @@ from artiq import __version__ as artiq_version
|
||||
|
||||
|
||||
ipc = None
|
||||
ipc_lock = threading.Lock()
|
||||
|
||||
|
||||
def get_object():
|
||||
line = ipc.readline().decode()
|
||||
return pyon.decode(line)
|
||||
ipc_lock.acquire()
|
||||
try:
|
||||
line = ipc.readline()
|
||||
finally:
|
||||
ipc_lock.release()
|
||||
return pyon.decode(line.decode())
|
||||
|
||||
|
||||
def put_object(obj):
|
||||
ds = pyon.encode(obj)
|
||||
ipc.write((ds + "\n").encode())
|
||||
ds = (pyon.encode(obj) + "\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 parent_action(*args, **kwargs):
|
||||
request = {"action": action, "args": args, "kwargs": kwargs}
|
||||
put_object(request)
|
||||
reply = get_object()
|
||||
reply = put_and_get_object(request)
|
||||
if "action" in reply:
|
||||
if reply["action"] == "terminate":
|
||||
sys.exit()
|
||||
|
@ -502,10 +502,12 @@ class CoredeviceTest(ExperimentCase):
|
||||
def execute_and_test_in_log(self, experiment, string):
|
||||
core_addr = self.device_mgr.get_desc("core")["arguments"]["host"]
|
||||
mgmt = CommMgmt(core_addr)
|
||||
try:
|
||||
mgmt.clear_log()
|
||||
self.execute(experiment)
|
||||
log = mgmt.get_log()
|
||||
self.assertIn(string, log)
|
||||
finally:
|
||||
mgmt.close()
|
||||
|
||||
def test_sequence_error(self):
|
||||
@ -720,13 +722,6 @@ class DMATest(ExperimentCase):
|
||||
self.assertLess(dt/count, 11*us)
|
||||
|
||||
def test_dma_playback_time(self):
|
||||
# Skip on Kasli until #946 is resolved.
|
||||
try:
|
||||
# hack to detect Kasli.
|
||||
self.device_mgr.get_desc("ad9914dds0")
|
||||
except KeyError:
|
||||
raise unittest.SkipTest("skipped on Kasli for now")
|
||||
|
||||
exp = self.create(_DMA)
|
||||
is_zynq = exp.core.target_cls == CortexA9Target
|
||||
count = 20000
|
||||
|
@ -4,21 +4,21 @@
|
||||
from artiq.language.core 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
|
||||
# lead to miscompilations (at least in LLVM 11).
|
||||
|
||||
|
||||
@kernel
|
||||
def entrypoint():
|
||||
# CHECK: call void @accept_str\({ i8\*, i32 }\* nonnull byval
|
||||
# CHECK: call void @accept_str\({ i8\*, i32 }
|
||||
accept_str("foo")
|
||||
|
||||
# CHECK: call void @return_str\({ i8\*, i32 }\* nonnull sret
|
||||
return_str()
|
||||
|
||||
|
||||
# CHECK: declare void @accept_str\({ i8\*, i32 }\* byval\({ i8\*, i32 }\)\)
|
||||
# CHECK: declare void @accept_str\({ i8\*, i32 }\)
|
||||
@syscall
|
||||
def accept_str(name: TStr) -> TNone:
|
||||
pass
|
||||
|
@ -102,7 +102,7 @@ def short_format(v, metadata={}):
|
||||
return v_str
|
||||
elif np.issubdtype(t, np.bool_):
|
||||
return str(v)
|
||||
elif np.issubdtype(t, np.unicode_):
|
||||
elif np.issubdtype(t, np.str_):
|
||||
return "\"" + elide(v, 50) + "\""
|
||||
elif t is np.ndarray:
|
||||
v_t = np.divide(v, scale)
|
||||
|
@ -176,7 +176,6 @@ html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
|
@ -170,3 +170,10 @@ Miscellaneous
|
||||
|
||||
.. automodule:: artiq.coredevice.grabber
|
||||
: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": {
|
||||
"locked": {
|
||||
"lastModified": 1739446958,
|
||||
"narHash": "sha256-+/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc=",
|
||||
"lastModified": 1741851582,
|
||||
"narHash": "sha256-cPfs8qMccim2RBgtKGF+x9IBCduRvd/N5F4nYpU0TVE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2ff53fe64443980e139eaa286017f53f88336dd0",
|
||||
"rev": "6607cf789e541e7873d40d3a8f7815ea92204f32",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -129,11 +129,11 @@
|
||||
"src-misoc": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1739436988,
|
||||
"narHash": "sha256-zEihEV6kqRtrZWyu7uCNyHOXE/rluVloPuT4ECYVJ+g=",
|
||||
"lastModified": 1741001607,
|
||||
"narHash": "sha256-05BGqWV4Zc9ArwaW0uuBYWjg4oTeP4vznPQQjEpQPEM=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "e3f4fd040b90b05d580bf578ca49244f0b7d861a",
|
||||
"revCount": 2483,
|
||||
"rev": "fa73f42f3c163833f17fc99399bb41005970c503",
|
||||
"revCount": 2495,
|
||||
"submodules": true,
|
||||
"type": "git",
|
||||
"url": "https://github.com/m-labs/misoc.git"
|
||||
|
Loading…
x
Reference in New Issue
Block a user