1
0
forked from M-Labs/artiq

Compare commits

...

49 Commits

Author SHA1 Message Date
0389be14fe i2c: fix issues with building 2025-04-10 11:00:21 +08:00
27241cb2c3 fix LLVM string codegen test
See #2713 for the main changes.
2025-04-02 13:59:54 +08:00
a4ad97a3fd FFI: pass string parameters directly 2025-04-01 16:32:28 +08:00
4ddad5fd17 i2c: stop the bus after received NACK 2025-03-28 14:41:57 +08:00
David Nadlinger
d78ebb6bbb dashboard: Dark-mode support for Log pane
Integrates much more naturally with dark mode (e.g. the default dark
system theme on Windows 11) by continuing to use bright text on dark
backgrounds in that case.
2025-03-26 10:03:14 +08:00
464befba63 cxp_grabber pixtracker: set y to y_size after eof 2025-03-25 12:52:29 +08:00
1075e89514 cxp_grabber driver: expose xml & pixel read api 2025-03-24 14:07:21 +08:00
342fa5aaf6 cxp_grabber driver doc: add roiviewer height limit 2025-03-20 15:59:18 +08:00
fc5bc1bb5c cxp_grabber driver: fix roiviewer coordinate order 2025-03-20 15:59:18 +08:00
9e655332e3 cxp test: fix pixel packet generator bit order 2025-03-18 13:00:39 +08:00
b3678e8bfb cxp_grabber: fix pixel unpacker bit order 2025-03-18 13:00:39 +08:00
Yichao Yu
dcc0f8d579 Make master worker ipc threadsafe on the worker side
This allows the other threads to, e.g., access dataset when the main threads
runs the kernels.

Fix #2701
2025-03-17 23:06:17 +08:00
afbd83799c ksupport: invalidate I-cache after rebind
Rebinding modifies instructions directly, if binary is linked using nac3ld
2025-03-17 21:58:16 +08:00
641d4cc362 cxp_grabber driver: add cxp grabber exceptions
driver: add outofsync & timeout exceptions instead of importing
2025-03-15 10:57:25 +08:00
3a9a10a41c exceptions: remove unused InternalError 2025-03-14 14:49:33 +08:00
cdacbf71c5 flake: update dependencies 2025-03-14 12:11:24 +08:00
1533aba3ea cxp_grabber driver: add ROIViewer driver
cxp grabber: add ROIViewer startup support
cxp grabber: add ROIViewer frame download as PGM file
2025-03-14 12:10:04 +08:00
bd1759dbf9 cxp_grabber: add ROIViewer gateware support
core: add ROICropper to crop the ROI for downstream consumption
core: add ROIViewer to buffer the cropped pixels
rtio: connect pixel source to ROIViewer
2025-03-14 12:10:04 +08:00
7e1da9da72 RELEASE_NOTES: update 2025-03-13 14:17:47 +08:00
5e651308bb converter_spi: impl Error to str conversion 2025-03-13 13:56:37 +08:00
f213b8e1ca si5324: allow nack when soft reset
See 0d2f89db.
2025-03-13 13:56:37 +08:00
9fe9abd7fb satman: accept shuttler on satellite kasli
revert #2593 and update remote coremgmt drop link process
2025-03-11 11:45:05 +08:00
90eb59c54d i2c: make write nack an error, force to handle nacks 2025-03-11 11:42:43 +08:00
9a80818e97 doc: cxpgrabber docstring spellchecks 2025-03-11 11:41:34 +08:00
9f1a8e7d4f doc: Remove deprecated conf line 2025-03-11 11:41:34 +08:00
9d2897de5f cxp_grabber driver doc: fix "note" formatting 2025-03-04 15:32:36 +08:00
1794c939b2 RELEASE_NOTES: update 2025-03-04 15:17:05 +08:00
519fdfa864 cxp grabber driver: flake8 fmt 2025-03-04 14:47:28 +08:00
c5473a8a02 cxp_grabber: driver allocate buffer in kernel 2025-03-04 14:47:28 +08:00
9c94ba7f89 doc: add cxp_grabber 2025-03-04 14:47:28 +08:00
76de902f2c RELEASE_NOTES: update 2025-03-04 14:47:28 +08:00
99bf703851 coredevice: add cxp grabber driver
cxp grabber: add sending cxp linktrigger
cxp grabber: add setting and gating ROIs
cxp grabber: add read/write 32bit register support
cxp grabber: add camera setting xml file download support
2025-03-04 14:47:28 +08:00
4b60cd86c5 artiq error: add cxp error 2025-03-04 14:47:28 +08:00
f98ce34480 flake: update misoc 2025-03-03 19:34:22 +08:00
8bd03f5bb4 dma: report last write to FIFO 2025-03-03 15:55:16 +08:00
06a2660f9c analyzer: revert lookahead buffer
CTI can be inferred by the last signal.
2025-03-03 15:55:16 +08:00
d0a2519b98 test_rtio: resume dma playback test for Kasli 2025-03-03 15:55:16 +08:00
690292f467 analyzer: align to sdram page 2025-03-03 15:55:16 +08:00
d80901d216 analyzer: perform burst write to main memory 2025-03-03 15:55:16 +08:00
3d22c50837 dma: perform burst read from main memory
with an additional FIFO to regulate data flow
2025-03-03 15:55:16 +08:00
6c5ff1e64f flake: update dependencies 2025-03-03 15:55:00 +08:00
5379bd9d38 test_rtio: close CommMgmt socket on test skip
Signed-off-by: Simon Renblad <srenblad@m-labs.hk>
2025-03-03 15:08:12 +08:00
66cec318a5 cxp test: add pixel parser unittest
test: use roi result to test pixel parser
2025-03-03 13:34:06 +08:00
2a0c064de7 cxp test: init and add mono pixel packet generator 2025-03-03 13:34:06 +08:00
a0886b602e rtio: add cxp grabber
cxp grabber: add and connect cxp phys, host core, stream decoder
cxp grabber: add and connect ROIs to stream decoder
cxp grabber: add rtio for line trigger, roi cfg and count gating
2025-03-03 13:34:06 +08:00
c0f5d8ba06 cxp grabber: add stream decoder, roi & host core 2025-03-03 13:34:06 +08:00
16d98279d5 cxp grabber: init and add frame handling modules
cxp grabber: add frame header reader and end of line marker
cxp grabber: add pixel unpacker & coordinate tracker
cxp grabber: add pixel parser that support mono8,10,12,14,16
2025-03-03 13:34:06 +08:00
bc94614395 flake: update dependencies 2025-03-03 13:34:06 +08:00
6bce611ef8 tools: replace deprecated np.unicode_ 2025-02-19 22:32:21 +08:00
33 changed files with 1789 additions and 229 deletions

View File

@ -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

View File

@ -115,6 +115,7 @@ class EmbeddingMap:
"0:ZeroDivisionError",
"0:LinAlgError",
"UnwrapNoneError",
"CXPError"
])
def preallocate_runtime_exception_names(self, names):

View File

@ -1524,13 +1524,7 @@ 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)
llargs.append(llarg)
llretty = self.llty_of_type(insn.type, for_return=True)
is_sret = self.needs_sret(llretty)

View 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

View File

@ -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

View File

@ -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 {

View File

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

View File

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

View File

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

View File

@ -1,8 +1,34 @@
#[derive(Debug)]
pub enum Error {
NoI2C,
InvalidBus,
Nack,
SCLLow,
SDALow,
ArbitrationLost,
IOExpanderError,
OtherError,
}
impl From<Error> for &str {
fn from(err: Error) -> &'static str {
match err {
Error::NoI2C => "I2C not supported",
Error::InvalidBus => "Invalid I2C bus",
Error::Nack => "I2C write was not ACKed",
Error::SCLLow => "SCL stuck low",
Error::SDALow => "SDA stuck low",
Error::ArbitrationLost => "SDA arbitration lost",
Error::IOExpanderError => "I2C IO Expander error",
Error::OtherError => "other error",
}
}
}
#[cfg(has_i2c)]
mod imp {
use super::super::{csr, clock};
const INVALID_BUS: &'static str = "Invalid I2C bus";
use super::Error;
fn half_period() { clock::spin_us(100) }
fn sda_bit(busno: u8) -> u8 { 1 << (2 * busno + 1) }
@ -52,7 +78,7 @@ mod imp {
}
}
pub fn init() -> Result<(), &'static str> {
pub fn init() -> Result<(), Error> {
for busno in 0..csr::CONFIG_I2C_BUS_COUNT {
let busno = busno as u8;
scl_oe(busno, false);
@ -74,26 +100,26 @@ mod imp {
}
if !sda_i(busno) {
return Err("SDA is stuck low and doesn't get unstuck");
return Err(Error::SDALow);
}
if !scl_i(busno) {
return Err("SCL is stuck low and doesn't get unstuck");
return Err(Error::SCLLow);
}
// postcondition: SCL and SDA high
}
Ok(())
}
pub fn start(busno: u8) -> Result<(), &'static str> {
pub fn start(busno: u8) -> Result<(), Error> {
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
return Err(INVALID_BUS)
return Err(Error::InvalidBus)
}
// precondition: SCL and SDA high
if !scl_i(busno) {
return Err("SCL is stuck low and doesn't get unstuck");
return Err(Error::SCLLow);
}
if !sda_i(busno) {
return Err("SDA arbitration lost");
return Err(Error::ArbitrationLost);
}
sda_oe(busno, true);
half_period();
@ -102,9 +128,9 @@ mod imp {
Ok(())
}
pub fn restart(busno: u8) -> Result<(), &'static str> {
pub fn restart(busno: u8) -> Result<(), Error> {
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
return Err(INVALID_BUS)
return Err(Error::InvalidBus)
}
// precondition SCL and SDA low
sda_oe(busno, false);
@ -116,9 +142,9 @@ mod imp {
Ok(())
}
pub fn stop(busno: u8) -> Result<(), &'static str> {
pub fn stop(busno: u8) -> Result<(), Error> {
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
return Err(INVALID_BUS)
return Err(Error::InvalidBus)
}
// precondition: SCL and SDA low
half_period();
@ -127,15 +153,15 @@ mod imp {
sda_oe(busno, false);
half_period();
if !sda_i(busno) {
return Err("SDA arbitration lost");
return Err(Error::ArbitrationLost);
}
// postcondition: SCL and SDA high
Ok(())
}
pub fn write(busno: u8, data: u8) -> Result<bool, &'static str> {
pub fn write(busno: u8, data: u8) -> Result<(), Error> {
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
return Err(INVALID_BUS)
return Err(Error::InvalidBus)
}
// precondition: SCL and SDA low
// MSB first
@ -156,12 +182,16 @@ mod imp {
sda_oe(busno, true);
// postcondition: SCL and SDA low
Ok(ack)
if !ack {
return Err(Error::Nack)
}
Ok(())
}
pub fn read(busno: u8, ack: bool) -> Result<u8, &'static str> {
pub fn read(busno: u8, ack: bool) -> Result<u8, Error> {
if busno as u32 >= csr::CONFIG_I2C_BUS_COUNT {
return Err(INVALID_BUS)
return Err(Error::InvalidBus)
}
// precondition: SCL and SDA low
sda_oe(busno, false);
@ -188,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::*;

View File

@ -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)?;
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)?;
}
Ok(())
});
i2c::restart(self.busno)?;
i2c::write(self.busno, self.address | 1)?;
let buf_len = buf.len();
for (i, byte) in buf.iter_mut().enumerate() {
*byte = i2c::read(self.busno, i < buf_len - 1)?;
}
let stop_result = i2c::stop(self.busno);
i2c::stop(self.busno)?;
Ok(())
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)

View File

@ -23,7 +23,7 @@ pub struct IoExpander {
impl IoExpander {
#[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))]
pub fn new(index: u8) -> Result<Self, &'static str> {
pub fn new(index: u8) -> Result<Self, i2c::Error> {
const VIRTUAL_LED_MAPPING0: [(u8, u8, u8); 2] = [(0, 0, 6), (1, 1, 6)];
const VIRTUAL_LED_MAPPING1: [(u8, u8, u8); 2] = [(2, 0, 6), (3, 1, 6)];
@ -79,7 +79,11 @@ impl IoExpander {
gpiob: 0x13,
},
},
_ => return Err("incorrect I/O expander index"),
_ => {
#[cfg(feature = "log")]
log::error!("incorrect I/O expander index");
return Err(i2c::Error::IOExpanderError)
}
};
if !io_expander.check_ack()? {
#[cfg(feature = "log")]
@ -95,14 +99,16 @@ impl IoExpander {
gpiob: 0x03,
};
if !io_expander.check_ack()? {
return Err("Neither MCP23017 nor PCA9539 io expander found.");
#[cfg(feature = "log")]
log::error!("Neither MCP23017 nor PCA9539 io expander found.");
return Err(i2c::Error::IOExpanderError);
};
}
Ok(io_expander)
}
#[cfg(soc_platform = "efc")]
pub fn new() -> Result<Self, &'static str> {
pub fn new() -> Result<Self, i2c::Error> {
const VIRTUAL_LED_MAPPING: [(u8, u8, u8); 2] = [(0, 0, 5), (1, 0, 6)];
let io_expander = IoExpander {
@ -121,13 +127,15 @@ impl IoExpander {
},
};
if !io_expander.check_ack()? {
return Err("MCP23017 io expander not found.");
#[cfg(feature = "log")]
log::error!("MCP23017 io expander not found.");
return Err(i2c::Error::IOExpanderError);
};
Ok(io_expander)
}
#[cfg(soc_platform = "kasli")]
fn select(&self) -> Result<(), &'static str> {
fn select(&self) -> Result<(), i2c::Error> {
let mask: u16 = 1 << self.port;
i2c::switch_select(self.busno, 0x70, mask as u8)?;
i2c::switch_select(self.busno, 0x71, (mask >> 8) as u8)?;
@ -135,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);

View File

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

View File

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

View File

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

View File

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

View File

View File

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

View File

@ -0,0 +1,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

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

@ -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,11 +58,16 @@ 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.warning_bg = QtGui.QBrush(QtGui.QColor(255, 255, 180))
self.error_bg = QtGui.QBrush(QtGui.QColor(255, 150, 150))
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))
def headerData(self, col, orientation, role):
if (orientation == QtCore.Qt.Orientation.Horizontal
@ -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)

View File

@ -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()

View File

@ -502,11 +502,13 @@ class CoredeviceTest(ExperimentCase):
def execute_and_test_in_log(self, experiment, string):
core_addr = self.device_mgr.get_desc("core")["arguments"]["host"]
mgmt = CommMgmt(core_addr)
mgmt.clear_log()
self.execute(experiment)
log = mgmt.get_log()
self.assertIn(string, log)
mgmt.close()
try:
mgmt.clear_log()
self.execute(experiment)
log = mgmt.get_log()
self.assertIn(string, log)
finally:
mgmt.close()
def test_sequence_error(self):
self.execute_and_test_in_log(SequenceError, "RTIO sequence error")
@ -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

View File

@ -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

View File

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

View File

@ -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".

View File

@ -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
View File

@ -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"