From b74d6fb9bae451d6e1bfe4ed30d135d4c02ea136 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Mon, 27 Mar 2017 16:32:23 +0800 Subject: [PATCH] make collision and busy asynchronous errors, and simplify CPU/gateware handshake for output errors and reads --- RELEASE_NOTES.rst | 2 + artiq/coredevice/__init__.py | 6 +- artiq/coredevice/comm_analyzer.py | 11 ++- artiq/coredevice/exceptions.py | 24 ------ artiq/firmware/ksupport/lib.rs | 12 --- artiq/firmware/ksupport/rtio.rs | 24 +----- artiq/gateware/drtio/rt_controller_master.py | 8 +- artiq/gateware/rtio/analyzer.py | 38 ++++++--- artiq/gateware/rtio/core.py | 88 +++++++++++--------- artiq/gateware/rtio/cri.py | 32 ++----- artiq/gateware/rtio/dma.py | 17 ++-- artiq/test/coredevice/test_rtio.py | 13 +-- artiq/test/coredevice/test_spi.py | 84 ------------------- doc/manual/rtio.rst | 19 +++++ 14 files changed, 134 insertions(+), 244 deletions(-) delete mode 100644 artiq/test/coredevice/test_spi.py diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 585c6bb01..82a8dd502 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -26,6 +26,8 @@ Release notes * The kc705 gateware target has been renamed kc705_dds. * ``artiq.coredevice.comm_tcp`` has been renamed ``artiq.coredevice.comm_kernel``, and ``Comm`` has been renamed ``CommKernel``. +* The "collision" and "busy" RTIO errors are reported through the log instead of + raising exceptions. 2.2 diff --git a/artiq/coredevice/__init__.py b/artiq/coredevice/__init__.py index 61b0e2573..70539315d 100644 --- a/artiq/coredevice/__init__.py +++ b/artiq/coredevice/__init__.py @@ -1,11 +1,9 @@ from artiq.coredevice import exceptions, dds, spi -from artiq.coredevice.exceptions import (RTIOUnderflow, RTIOSequenceError, - RTIOCollision, RTIOOverflow, RTIOBusy) +from artiq.coredevice.exceptions import (RTIOUnderflow, RTIOSequenceError, RTIOOverflow) from artiq.coredevice.dds import (PHASE_MODE_CONTINUOUS, PHASE_MODE_ABSOLUTE, PHASE_MODE_TRACKING) __all__ = [] -__all__ += ["RTIOUnderflow", "RTIOSequenceError", "RTIOCollision", - "RTIOOverflow", "RTIOBusy"] +__all__ += ["RTIOUnderflow", "RTIOSequenceError", "RTIOOverflow"] __all__ += ["PHASE_MODE_CONTINUOUS", "PHASE_MODE_ABSOLUTE", "PHASE_MODE_TRACKING"] diff --git a/artiq/coredevice/comm_analyzer.py b/artiq/coredevice/comm_analyzer.py index ce611f96f..d8b6e0434 100644 --- a/artiq/coredevice/comm_analyzer.py +++ b/artiq/coredevice/comm_analyzer.py @@ -23,12 +23,15 @@ class ExceptionType(Enum): legacy_reset_falling = 0b000001 legacy_reset_phy = 0b000010 legacy_reset_phy_falling = 0b000011 + legacy_o_underflow_reset = 0b010000 + legacy_o_sequence_error_reset = 0b010001 + legacy_o_collision_reset = 0b010010 + legacy_i_overflow_reset = 0b100000 - o_underflow_reset = 0b010000 - o_sequence_error_reset = 0b010001 - o_collision_reset = 0b010010 + o_underflow = 0b010100 + o_sequence_error = 0b010101 - i_overflow_reset = 0b100000 + i_overflow = 0b100001 def get_analyzer_dump(host, port=1382): diff --git a/artiq/coredevice/exceptions.py b/artiq/coredevice/exceptions.py index bc1e10998..4debf2318 100644 --- a/artiq/coredevice/exceptions.py +++ b/artiq/coredevice/exceptions.py @@ -86,30 +86,6 @@ class RTIOSequenceError(Exception): """ artiq_builtin = True -class RTIOCollision(Exception): - """Raised when an event is submitted on a given channel with the same - coarse timestamp as the previous one but with a different fine timestamp. - - Coarse timestamps correspond to the RTIO system clock (typically around - 125MHz) whereas fine timestamps correspond to the RTIO SERDES clock - (typically around 1GHz). - - The offending event is discarded and the RTIO core keeps operating. - """ - artiq_builtin = True - -class RTIOBusy(Exception): - """Raised when at least one output event could not be executed because - the given channel was already busy executing a previous event. - - This exception is raised late: after the error condition occurred. More - specifically it is raised on submitting an event on the same channel after - the execution of the faulty event was attempted. - - The offending event was discarded. - """ - artiq_builtin = True - class RTIOOverflow(Exception): """Raised when at least one event could not be registered into the RTIO input FIFO because it was full (CPU not reading fast enough). diff --git a/artiq/firmware/ksupport/lib.rs b/artiq/firmware/ksupport/lib.rs index 8a4be01da..80fb3f2bd 100644 --- a/artiq/firmware/ksupport/lib.rs +++ b/artiq/firmware/ksupport/lib.rs @@ -359,18 +359,6 @@ extern fn dma_playback(timestamp: i64, name: CSlice) { "RTIO sequence error at {0} mu, channel {1}", timestamp as i64, channel as i64, 0) } - if status & rtio::RTIO_O_STATUS_COLLISION != 0 { - csr::rtio_dma::error_collision_reset_write(1); - raise!("RTIOCollision", - "RTIO collision at {0} mu, channel {1}", - timestamp as i64, channel as i64, 0) - } - if status & rtio::RTIO_O_STATUS_BUSY != 0 { - csr::rtio_dma::error_busy_reset_write(1); - raise!("RTIOBusy", - "RTIO busy on channel {0}", - channel as i64, 0, 0) - } } } diff --git a/artiq/firmware/ksupport/rtio.rs b/artiq/firmware/ksupport/rtio.rs index 8ae9fb919..064fb4951 100644 --- a/artiq/firmware/ksupport/rtio.rs +++ b/artiq/firmware/ksupport/rtio.rs @@ -7,8 +7,6 @@ use kernel_proto::*; pub const RTIO_O_STATUS_WAIT: u32 = 1; pub const RTIO_O_STATUS_UNDERFLOW: u32 = 2; pub const RTIO_O_STATUS_SEQUENCE_ERROR: u32 = 4; -pub const RTIO_O_STATUS_COLLISION: u32 = 8; -pub const RTIO_O_STATUS_BUSY: u32 = 16; pub const RTIO_I_STATUS_WAIT_EVENT: u32 = 1; pub const RTIO_I_STATUS_OVERFLOW: u32 = 2; pub const RTIO_I_STATUS_WAIT_STATUS: u32 = 4; @@ -43,29 +41,15 @@ unsafe fn process_exceptional_status(timestamp: i64, channel: i32, status: u32) while csr::rtio::o_status_read() & RTIO_O_STATUS_WAIT != 0 {} } if status & RTIO_O_STATUS_UNDERFLOW != 0 { - csr::rtio::o_underflow_reset_write(1); raise!("RTIOUnderflow", "RTIO underflow at {0} mu, channel {1}, slack {2} mu", timestamp, channel as i64, timestamp - get_counter()) } if status & RTIO_O_STATUS_SEQUENCE_ERROR != 0 { - csr::rtio::o_sequence_error_reset_write(1); raise!("RTIOSequenceError", "RTIO sequence error at {0} mu, channel {1}", timestamp, channel as i64, 0) } - if status & RTIO_O_STATUS_COLLISION != 0 { - csr::rtio::o_collision_reset_write(1); - raise!("RTIOCollision", - "RTIO collision at {0} mu, channel {1}", - timestamp, channel as i64, 0) - } - if status & RTIO_O_STATUS_BUSY != 0 { - csr::rtio::o_busy_reset_write(1); - raise!("RTIOBusy", - "RTIO busy on channel {0}", - channel as i64, 0, 0) - } } pub extern fn output(timestamp: i64, channel: i32, addr: i32, data: i32) { @@ -118,9 +102,7 @@ pub extern fn input_timestamp(timeout: i64, channel: i32) -> u64 { return !0 } - let timestamp = csr::rtio::i_timestamp_read(); - csr::rtio::i_re_write(1); - timestamp + csr::rtio::i_timestamp_read() } } @@ -142,9 +124,7 @@ pub extern fn input_data(channel: i32) -> i32 { channel as i64, 0, 0); } - let data = rtio_i_data_read(0); - csr::rtio::i_re_write(1); - data as i32 + rtio_i_data_read(0) as i32 } } diff --git a/artiq/gateware/drtio/rt_controller_master.py b/artiq/gateware/drtio/rt_controller_master.py index e8cd241ca..596ec749e 100644 --- a/artiq/gateware/drtio/rt_controller_master.py +++ b/artiq/gateware/drtio/rt_controller_master.py @@ -127,8 +127,10 @@ class RTController(Module): o_sequence_error_set = Signal() o_underflow_set = Signal() self.sync.sys_with_rst += [ - If(self.cri.cmd == cri.commands["o_underflow_reset"], o_status_underflow.eq(0)), - If(self.cri.cmd == cri.commands["o_sequence_error_reset"], o_status_sequence_error.eq(0)), + If(self.cri.cmd == cri.commands["write"], + o_status_underflow.eq(0), + o_status_sequence_error.eq(0), + ), If(o_underflow_set, o_status_underflow.eq(1)), If(o_sequence_error_set, o_status_sequence_error.eq(1)) ] @@ -184,7 +186,7 @@ class RTController(Module): NextState("WRITE") ) ), - If(self.cri.cmd == cri.commands["read_request"], NextState("READ")), + If(self.cri.cmd == cri.commands["read"], NextState("READ")), If(self.csrs.o_get_fifo_space.re, NextState("GET_FIFO_SPACE")) ) fsm.act("WRITE", diff --git a/artiq/gateware/rtio/analyzer.py b/artiq/gateware/rtio/analyzer.py index a1ade41db..2461f80c1 100644 --- a/artiq/gateware/rtio/analyzer.py +++ b/artiq/gateware/rtio/analyzer.py @@ -51,6 +51,17 @@ class MessageEncoder(Module, AutoCSR): # # # + read_wait_event = cri.i_status[2] + read_wait_event_r = Signal() + read_done = Signal() + read_overflow = Signal() + self.sync += read_wait_event_r.eq(read_wait_event) + self.comb += \ + If(read_wait_event_r & ~read_wait_event, + If(~cri.i_status[0], read_done.eq(1)), + If(cri.i_status[1], read_overflow.eq(1)) + ) + input_output_stb = Signal() input_output = Record(input_output_layout) self.comb += [ @@ -66,8 +77,7 @@ class MessageEncoder(Module, AutoCSR): input_output.timestamp.eq(cri.i_timestamp), input_output.data.eq(cri.i_data) ), - input_output_stb.eq((cri.cmd == cri_commands["write"]) | - (cri.cmd == cri_commands["read"])) + input_output_stb.eq((cri.cmd == cri_commands["write"]) | read_done) ] exception_stb = Signal() @@ -77,14 +87,22 @@ class MessageEncoder(Module, AutoCSR): exception.channel.eq(cri.chan_sel), exception.rtio_counter.eq(cri.counter), ] - for ename in ("o_underflow_reset", "o_sequence_error_reset", - "o_collision_reset", "i_overflow_reset"): - self.comb += \ - If(cri.cmd == cri_commands[ename], - exception_stb.eq(1), - exception.exception_type.eq( - getattr(ExceptionType, ename).value) - ) + just_written = Signal() + self.sync += just_written.eq(cri.cmd == cri_commands["write"]) + self.comb += [ + If(just_written & cri.o_status[1], + exception_stb.eq(1), + exception.exception_type.eq(ExceptionType.o_underflow.value) + ), + If(just_written & cri.o_status[2], + exception_stb.eq(1), + exception.exception_type.eq(ExceptionType.o_sequence_error.value) + ), + If(read_overflow, + exception_stb.eq(1), + exception.exception_type.eq(ExceptionType.i_overflow.value) + ) + ] stopped = Record(stopped_layout) self.comb += [ diff --git a/artiq/gateware/rtio/core.py b/artiq/gateware/rtio/core.py index 3327acc7d..fc767ac7f 100644 --- a/artiq/gateware/rtio/core.py +++ b/artiq/gateware/rtio/core.py @@ -290,6 +290,7 @@ class Core(Module, AutoCSR): self.cri = cri.Interface() self.reset = CSR() self.reset_phy = CSR() + self.async_error = CSR(2) self.comb += self.cri.arb_gnt.eq(1) # Clocking/Reset @@ -321,11 +322,31 @@ class Core(Module, AutoCSR): # Managers self.submodules.counter = RTIOCounter(len(self.cri.timestamp) - fine_ts_width) - i_datas, i_timestamps = [], [] + # Collision is not an asynchronous error with local RTIO, but + # we treat it as such for consistency with DRTIO, where collisions + # are reported by the satellites. + o_underflow = Signal() + o_sequence_error = Signal() + o_collision = Signal() + o_busy = Signal() + self.sync.rsys += [ + If(self.cri.cmd == cri.commands["write"], + o_underflow.eq(0), + o_sequence_error.eq(0), + ), + If(self.async_error.re, + If(self.async_error.r[0], o_collision.eq(0)), + If(self.async_error.r[1], o_busy.eq(0)), + ) + ] + o_statuses, i_statuses = [], [] + i_datas, i_timestamps = [], [] + i_ack = Signal() sel = self.cri.chan_sel[:16] for n, channel in enumerate(channels): if isinstance(channel, LogChannel): + o_statuses.append(1) i_datas.append(0) i_timestamps.append(0) i_statuses.append(0) @@ -347,29 +368,13 @@ class Core(Module, AutoCSR): self.comb += o_manager.we.eq(selected & (self.cri.cmd == cri.commands["write"])) - underflow = Signal() - sequence_error = Signal() - collision = Signal() - busy = Signal() self.sync.rsys += [ - If(self.cri.cmd == cri.commands["o_underflow_reset"], - underflow.eq(0)), - If(self.cri.cmd == cri.commands["o_sequence_error_reset"], - sequence_error.eq(0)), - If(self.cri.cmd == cri.commands["o_collision_reset"], - collision.eq(0)), - If(self.cri.cmd == cri.commands["o_busy_reset"], - busy.eq(0)), - If(o_manager.underflow, underflow.eq(1)), - If(o_manager.sequence_error, sequence_error.eq(1)), - If(o_manager.collision, collision.eq(1)), - If(o_manager.busy, busy.eq(1)) + If(o_manager.underflow, o_underflow.eq(1)), + If(o_manager.sequence_error, o_sequence_error.eq(1)), + If(o_manager.collision, o_collision.eq(1)), + If(o_manager.busy, o_busy.eq(1)) ] - o_statuses.append(Cat(~o_manager.writable, - underflow, - sequence_error, - collision, - busy)) + o_statuses.append(o_manager.writable) if channel.interface.i is not None: i_manager = _InputManager(channel.interface.i, self.counter, @@ -386,42 +391,49 @@ class Core(Module, AutoCSR): else: i_timestamps.append(0) - self.comb += i_manager.re.eq(selected & (self.cri.cmd == cri.commands["read"])) - overflow = Signal() self.sync.rsys += [ - If(selected & (self.cri.cmd == cri.commands["i_overflow_reset"]), + If(selected & i_ack, overflow.eq(0)), If(i_manager.overflow, overflow.eq(1)) ] - i_statuses.append(Cat(i_manager.readable, overflow)) + self.comb += i_manager.re.eq(selected & i_ack & ~overflow) + i_statuses.append(Cat(i_manager.readable & ~overflow, overflow)) else: i_datas.append(0) i_timestamps.append(0) i_statuses.append(0) - i_status_raw = Signal(2) - self.sync.rsys += i_status_raw.eq(Array(i_statuses)[sel]) + o_status_raw = Signal() + self.comb += [ + o_status_raw.eq(Array(o_statuses)[sel]), + self.cri.o_status.eq(Cat( + ~o_status_raw, o_underflow, o_sequence_error)), + self.async_error.w.eq(Cat(o_collision, o_busy)) + ] + i_status_raw = Signal(2) + self.comb += i_status_raw.eq(Array(i_statuses)[sel]) input_timeout = Signal.like(self.cri.timestamp) input_pending = Signal() self.sync.rsys += [ + i_ack.eq(0), + If(i_ack, + self.cri.i_status.eq(Cat(~i_status_raw[0], i_status_raw[1], 0)), + self.cri.i_data.eq(Array(i_datas)[sel]), + self.cri.i_timestamp.eq(Array(i_timestamps)[sel]), + ), If((self.cri.counter >= input_timeout) | (i_status_raw != 0), + If(input_pending, i_ack.eq(1)), input_pending.eq(0) ), - If(self.cri.cmd == cri.commands["read_request"], + If(self.cri.cmd == cri.commands["read"], input_timeout.eq(self.cri.timestamp), - input_pending.eq(1) + input_pending.eq(1), + self.cri.i_status.eq(0b100) ) ] - self.comb += [ - self.cri.i_data.eq(Array(i_datas)[sel]), - self.cri.i_timestamp.eq(Array(i_timestamps)[sel]), - self.cri.o_status.eq(Array(o_statuses)[sel]), - self.cri.i_status.eq(Cat(~i_status_raw[0], i_status_raw[1], input_pending)), - self.cri.counter.eq(self.counter.value_sys << fine_ts_width) - ] - + self.comb += self.cri.counter.eq(self.counter.value_sys << fine_ts_width) diff --git a/artiq/gateware/rtio/cri.py b/artiq/gateware/rtio/cri.py index 5c8918c76..450baf969 100644 --- a/artiq/gateware/rtio/cri.py +++ b/artiq/gateware/rtio/cri.py @@ -12,15 +12,7 @@ commands = { "write": 1, # i_status should have the "wait for status" bit set until # an event is available, or timestamp is reached. - "read_request": 2, - # consume the read event - "read": 3, - - "o_underflow_reset": 4, - "o_sequence_error_reset": 5, - "o_collision_reset": 6, - "o_busy_reset": 7, - "i_overflow_reset": 8 + "read": 2 } @@ -28,7 +20,7 @@ layout = [ ("arb_req", 1, DIR_M_TO_S), ("arb_gnt", 1, DIR_S_TO_M), - ("cmd", 4, DIR_M_TO_S), + ("cmd", 2, DIR_M_TO_S), # 8 MSBs of chan_sel are used to select core ("chan_sel", 24, DIR_M_TO_S), ("timestamp", 64, DIR_M_TO_S), @@ -36,13 +28,14 @@ layout = [ ("o_data", 512, DIR_M_TO_S), ("o_address", 16, DIR_M_TO_S), # o_status bits: - # <0:wait> <1:underflow> <2:sequence_error> <3:collision> <4:busy> - ("o_status", 5, DIR_S_TO_M), + # <0:wait> <1:underflow> <2:sequence_error> + ("o_status", 3, DIR_S_TO_M), ("i_data", 32, DIR_S_TO_M), ("i_timestamp", 64, DIR_S_TO_M), # i_status bits: # <0:wait for event (command timeout)> <1:overflow> <2:wait for status> + # <0> and <1> are mutually exclusive. <1> has higher priority. ("i_status", 3, DIR_S_TO_M), ("counter", 64, DIR_S_TO_M) @@ -66,16 +59,11 @@ class KernelInitiator(Module, AutoCSR): self.o_data = CSRStorage(512, write_from_dev=True) self.o_address = CSRStorage(16) self.o_we = CSR() - self.o_status = CSRStatus(5) - self.o_underflow_reset = CSR() - self.o_sequence_error_reset = CSR() - self.o_collision_reset = CSR() - self.o_busy_reset = CSR() + self.o_status = CSRStatus(3) self.i_data = CSRStatus(32) self.i_timestamp = CSRStatus(64) self.i_request = CSR() - self.i_re = CSR() self.i_status = CSRStatus(3) self.i_overflow_reset = CSR() @@ -94,13 +82,7 @@ class KernelInitiator(Module, AutoCSR): self.cri.cmd.eq(commands["nop"]), If(self.o_we.re, self.cri.cmd.eq(commands["write"])), - If(self.i_request.re, self.cri.cmd.eq(commands["read_request"])), - If(self.i_re.re, self.cri.cmd.eq(commands["read"])), - If(self.o_underflow_reset.re, self.cri.cmd.eq(commands["o_underflow_reset"])), - If(self.o_sequence_error_reset.re, self.cri.cmd.eq(commands["o_sequence_error_reset"])), - If(self.o_collision_reset.re, self.cri.cmd.eq(commands["o_collision_reset"])), - If(self.o_busy_reset.re, self.cri.cmd.eq(commands["o_busy_reset"])), - If(self.i_overflow_reset.re, self.cri.cmd.eq(commands["i_overflow_reset"])), + If(self.i_request.re, self.cri.cmd.eq(commands["read"])), self.cri.chan_sel.eq(self.chan_sel.storage), self.cri.timestamp.eq(self.timestamp.storage), diff --git a/artiq/gateware/rtio/dma.py b/artiq/gateware/rtio/dma.py index 69f17a5a4..7329168e7 100644 --- a/artiq/gateware/rtio/dma.py +++ b/artiq/gateware/rtio/dma.py @@ -247,11 +247,9 @@ class CRIMaster(Module, AutoCSR): self.arb_req = CSRStorage() self.arb_gnt = CSRStatus() - self.error_status = CSRStatus(5) # same encoding as RTIO status + self.error_status = CSRStatus(3) # same encoding as RTIO status self.error_underflow_reset = CSR() self.error_sequence_error_reset = CSR() - self.error_collision_reset = CSR() - self.error_busy_reset = CSR() self.error_channel = CSRStatus(24) self.error_timestamp = CSRStatus(64) @@ -268,9 +266,8 @@ class CRIMaster(Module, AutoCSR): self.arb_gnt.status.eq(self.cri.arb_gnt) ] - error_set = Signal(4) - for i, rcsr in enumerate([self.error_underflow_reset, self.error_sequence_error_reset, - self.error_collision_reset, self.error_busy_reset]): + error_set = Signal(2) + for i, rcsr in enumerate([self.error_underflow_reset, self.error_sequence_error_reset]): # bit 0 is RTIO wait and always 0 here bit = i + 1 self.sync += [ @@ -320,16 +317,12 @@ class CRIMaster(Module, AutoCSR): NextState("IDLE") ), If(self.cri.o_status[1], NextState("UNDERFLOW")), - If(self.cri.o_status[2], NextState("SEQUENCE_ERROR")), - If(self.cri.o_status[3], NextState("COLLISION")), - If(self.cri.o_status[4], NextState("BUSY")) + If(self.cri.o_status[2], NextState("SEQUENCE_ERROR")) ) - for n, name in enumerate(["UNDERFLOW", "SEQUENCE_ERROR", - "COLLISION", "BUSY"]): + for n, name in enumerate(["UNDERFLOW", "SEQUENCE_ERROR"]): fsm.act(name, self.busy.eq(1), error_set.eq(1 << n), - self.cri.cmd.eq(cri.commands["o_" + name.lower() + "_reset"]), self.sink.ack.eq(1), NextState("IDLE") ) diff --git a/artiq/test/coredevice/test_rtio.py b/artiq/test/coredevice/test_rtio.py index df41d6e44..1c005e7f8 100644 --- a/artiq/test/coredevice/test_rtio.py +++ b/artiq/test/coredevice/test_rtio.py @@ -384,13 +384,14 @@ class CoredeviceTest(ExperimentCase): with self.assertRaises(RTIOSequenceError): self.execute(SequenceError) - def test_collision(self): - with self.assertRaises(RTIOCollision): - self.execute(Collision) + # TODO: capture core device log + # def test_collision(self): + # with self.assertRaises(RTIOCollision): + # self.execute(Collision) - def test_address_collision(self): - with self.assertRaises(RTIOCollision): - self.execute(AddressCollision) + # def test_address_collision(self): + # with self.assertRaises(RTIOCollision): + # self.execute(AddressCollision) def test_watchdog(self): # watchdog only works on the device diff --git a/artiq/test/coredevice/test_spi.py b/artiq/test/coredevice/test_spi.py deleted file mode 100644 index 1b5dea705..000000000 --- a/artiq/test/coredevice/test_spi.py +++ /dev/null @@ -1,84 +0,0 @@ -from artiq.experiment import * -from artiq.test.hardware_testbench import ExperimentCase - - -class WrongError(Exception): - pass - - -class Collision(EnvExperiment): - def build(self): - self.setattr_device("core") - self.setattr_device("spi0") - - @kernel - def run(self): - self.core.reset() - t = now_mu() - try: - self.spi0.set_config_mu() - except RTIOBusy: - raise WrongError() - at_mu(t) - self.spi0.set_config_mu() - - -class Busy(EnvExperiment): - def build(self): - self.setattr_device("core") - self.setattr_device("spi0") - self.setattr_device("led") - - @kernel - def run(self): - self.core.reset() - try: - self.spi0.set_config_mu() - t = now_mu() - self.spi0.set_config_mu() - at_mu(t + self.spi0.ref_period_mu) - self.spi0.set_config_mu() # causes the error - self.led.on() - self.led.sync() # registers the error - self.core.break_realtime() - except RTIOBusy: - raise WrongError() # we don't expect RTIOBusy so far - self.spi0.set_config_mu() # raises the error - - -class DrainErrors(EnvExperiment): - def build(self): - self.setattr_device("core") - self.setattr_device("spi0") - self.setattr_device("led") - - @kernel - def run(self): - self.core.reset() - while True: - try: - self.core.break_realtime() - delay(100*us) - self.spi0.set_config_mu() - self.led.on() - self.led.sync() - self.core.break_realtime() - self.spi0.set_config_mu() - self.led.off() - return - except: - pass - - -class SPITest(ExperimentCase): - def tearDown(self): - self.execute(DrainErrors) - ExperimentCase.tearDown(self) - - def test_collision(self): - with self.assertRaises(RTIOCollision): - self.execute(Collision) - - def test_busy(self): - with self.assertRaises(RTIOBusy): - self.execute(Busy) diff --git a/doc/manual/rtio.rst b/doc/manual/rtio.rst index 81359fb30..a08edad32 100644 --- a/doc/manual/rtio.rst +++ b/doc/manual/rtio.rst @@ -117,6 +117,25 @@ To track down ``RTIOUnderflows`` in an experiment there are a few approaches: code. * The :any:`integrated logic analyzer ` shows the timeline context that lead to the exception. The analyzer is always active and supports plotting of RTIO slack. RTIO slack is the difference between timeline cursor and wall clock time (``now - rtio_counter``). +Collisions +---------- +A collision happens when more than one event is submitted on a given channel with the same coarse timestamp, and that channel does not implement replacement behavior or the fine timestamps are different. + +Coarse timestamps correspond to the RTIO system clock (typically around 125MHz) whereas fine timestamps correspond to the RTIO SERDES clock (typically around 1GHz). Different channels may have different ratios between the coarse and fine timestamp clock frequencies. + +The offending event is discarded and the RTIO core keeps operating. + +This error is reported asynchronously via the core device log: for performance reasons with DRTIO, the CPU does not wait for an error report from the satellite after writing an event. Therefore, it is not possible to raise an exception precisely. + +Busy errors +----------- + +A busy error happens when at least one output event could not be executed because the channel was already busy executing a previous event. + +The offending event was discarded. + +This error is reported asynchronously via the core device log. + Input channels and events -------------------------