diff --git a/artiq/coredevice/__init__.py b/artiq/coredevice/__init__.py index da8c72b1d..9ea9f5c33 100644 --- a/artiq/coredevice/__init__.py +++ b/artiq/coredevice/__init__.py @@ -1,12 +1,12 @@ from artiq.coredevice import exceptions, dds, spi from artiq.coredevice.exceptions import (RTIOUnderflow, RTIOSequenceError, - RTIOCollision, RTIOOverflow, + RTIOCollision, RTIOOverflow, RTIOBusy, DDSBatchError, CacheError) from artiq.coredevice.dds import (PHASE_MODE_CONTINUOUS, PHASE_MODE_ABSOLUTE, PHASE_MODE_TRACKING) __all__ = [] __all__ += ["RTIOUnderflow", "RTIOSequenceError", "RTIOCollision", - "RTIOOverflow", "DDSBatchError", "CacheError"] + "RTIOOverflow", "RTIOBusy", "DDSBatchError", "CacheError"] __all__ += ["PHASE_MODE_CONTINUOUS", "PHASE_MODE_ABSOLUTE", "PHASE_MODE_TRACKING"] diff --git a/artiq/coredevice/exceptions.py b/artiq/coredevice/exceptions.py index aa00ad784..6317edfe6 100644 --- a/artiq/coredevice/exceptions.py +++ b/artiq/coredevice/exceptions.py @@ -98,6 +98,18 @@ class RTIOCollision(Exception): """ 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/coredevice/spi.py b/artiq/coredevice/spi.py index 20095a8ff..80909e527 100644 --- a/artiq/coredevice/spi.py +++ b/artiq/coredevice/spi.py @@ -1,5 +1,5 @@ from artiq.language.core import (kernel, portable, seconds_to_mu, now_mu, - delay_mu, int) + delay_mu, int, mu_to_seconds) from artiq.language.units import MHz from artiq.coredevice.rtio import rtio_output, rtio_input_data @@ -58,7 +58,7 @@ class SPIMaster: @portable def frequency_to_div(self, f): - return int(1/(f*self.ref_period)) + 1 + return int(1/(f*mu_to_seconds(self.ref_period_mu))) + 1 @kernel def set_config(self, flags=0, write_freq=20*MHz, read_freq=20*MHz): diff --git a/artiq/gateware/rtio/core.py b/artiq/gateware/rtio/core.py index a1523f7ab..2fb9ee02b 100644 --- a/artiq/gateware/rtio/core.py +++ b/artiq/gateware/rtio/core.py @@ -54,6 +54,26 @@ class _RTIOCounter(Module): self.comb += gt.i.eq(self.value_rtio), self.value_sys.eq(gt.o) +class _BlindTransfer(Module): + def __init__(self): + self.i = Signal() + self.o = Signal() + + ps = PulseSynchronizer("rio", "rsys") + ps_ack = PulseSynchronizer("rsys", "rio") + self.submodules += ps, ps_ack + blind = Signal() + self.sync.rio += [ + If(self.i, blind.eq(1)), + If(ps_ack.o, blind.eq(0)) + ] + self.comb += [ + ps.i.eq(self.i & ~blind), + ps_ack.i.eq(ps.o), + self.o.eq(ps.o) + ] + + # CHOOSING A GUARD TIME # # The buffer must be transferred to the FIFO soon enough to account for: @@ -104,6 +124,7 @@ class _OutputManager(Module): self.underflow = Signal() # valid 1 cycle after we, pulsed self.sequence_error = Signal() self.collision = Signal() + self.busy = Signal() # pulsed # # # @@ -220,12 +241,19 @@ class _OutputManager(Module): self.comb += fifo.re.eq(fifo.readable & (~dout_stb | dout_ack)) # FIFO read through buffer - # TODO: report error on stb & busy self.comb += [ dout_ack.eq( dout.timestamp[fine_ts_width:] == counter.value_rtio), interface.stb.eq(dout_stb & dout_ack) ] + + busy_transfer = _BlindTransfer() + self.submodules += busy_transfer + self.comb += [ + busy_transfer.i.eq(interface.stb & interface.busy), + self.busy.eq(busy_transfer.o), + ] + if data_width: self.comb += interface.data.eq(dout.data) if address_width: @@ -248,7 +276,7 @@ class _InputManager(Module): self.readable = Signal() self.re = Signal() - + self.overflow = Signal() # pulsed # # # @@ -281,18 +309,11 @@ class _InputManager(Module): fifo.re.eq(self.re) ] - overflow_sync = PulseSynchronizer("rio", "rsys") - overflow_ack_sync = PulseSynchronizer("rsys", "rio") - self.submodules += overflow_sync, overflow_ack_sync - overflow_blind = Signal() - self.comb += overflow_sync.i.eq(fifo.we & ~fifo.writable & ~overflow_blind) - self.sync.rio += [ - If(fifo.we & ~fifo.writable, overflow_blind.eq(1)), - If(overflow_ack_sync.o, overflow_blind.eq(0)) - ] + overflow_transfer = _BlindTransfer() + self.submodules += overflow_transfer self.comb += [ - overflow_ack_sync.i.eq(overflow_sync.o), - self.overflow.eq(overflow_sync.o) + overflow_transfer.i.eq(fifo.we & ~fifo.writable), + self.overflow.eq(overflow_transfer.o), ] @@ -339,10 +360,11 @@ class _KernelCSRs(AutoCSR): self.o_address = CSRStorage(address_width) self.o_timestamp = CSRStorage(full_ts_width) self.o_we = CSR() - self.o_status = CSRStatus(4) + 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() if data_width: self.i_data = CSRStatus(data_width) @@ -430,6 +452,7 @@ class RTIO(Module): underflow = Signal() sequence_error = Signal() collision = Signal() + busy = Signal() self.sync.rsys += [ If(selected & self.kcsrs.o_underflow_reset.re, underflow.eq(0)), @@ -437,14 +460,18 @@ class RTIO(Module): sequence_error.eq(0)), If(selected & self.kcsrs.o_collision_reset.re, collision.eq(0)), + If(selected & self.kcsrs.o_busy_reset.re, + 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.collision, collision.eq(1)), + If(o_manager.busy, busy.eq(1)) ] o_statuses.append(Cat(~o_manager.writable, underflow, sequence_error, - collision)) + collision, + busy)) if channel.interface.i is not None: i_manager = _InputManager(channel.interface.i, self.counter, diff --git a/artiq/runtime/rtio.c b/artiq/runtime/rtio.c index 90dd7fb5e..92f02f651 100644 --- a/artiq/runtime/rtio.c +++ b/artiq/runtime/rtio.c @@ -39,6 +39,12 @@ static void rtio_process_exceptional_status( "RTIO collision at {0} mu, channel {1}", timestamp, channel, 0); } + if(status & RTIO_O_STATUS_BUSY) { + rtio_o_busy_reset_write(1); + artiq_raise_from_c("RTIOBusy", + "RTIO busy on channel {0}", + channel, 0, 0); + } } diff --git a/artiq/runtime/rtio.h b/artiq/runtime/rtio.h index c32dca484..aad6460ba 100644 --- a/artiq/runtime/rtio.h +++ b/artiq/runtime/rtio.h @@ -7,6 +7,7 @@ #define RTIO_O_STATUS_UNDERFLOW 2 #define RTIO_O_STATUS_SEQUENCE_ERROR 4 #define RTIO_O_STATUS_COLLISION 8 +#define RTIO_O_STATUS_BUSY 16 #define RTIO_I_STATUS_EMPTY 1 #define RTIO_I_STATUS_OVERFLOW 2 diff --git a/artiq/test/coredevice/test_spi.py b/artiq/test/coredevice/test_spi.py new file mode 100644 index 000000000..53b1a3ec0 --- /dev/null +++ b/artiq/test/coredevice/test_spi.py @@ -0,0 +1,45 @@ +from artiq.experiment import * +from artiq.test.hardware_testbench import ExperimentCase + + +class Collision(EnvExperiment): + def build(self): + self.setattr_device("core") + self.setattr_device("spi0") + + @kernel + def run(self): + self.core.break_realtime() + t = now_mu() + self.spi0.set_config_mu() + 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.break_realtime() + 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() + self.spi0.set_config_mu() # raises the error + + +class SPITest(ExperimentCase): + 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/artiq/test/hardware_testbench.py b/artiq/test/hardware_testbench.py index b8c1de6f8..3cb0d3700 100644 --- a/artiq/test/hardware_testbench.py +++ b/artiq/test/hardware_testbench.py @@ -109,7 +109,7 @@ class ExperimentCase(unittest.TestCase): # skip if ddb does not match requirements raise unittest.SkipTest(*e.args) - def execute(self, cls, *args, **kwargs): + def execute(self, cls, **kwargs): expid = { "file": sys.modules[cls.__module__].__file__, "class_name": cls.__name__, @@ -124,3 +124,8 @@ class ExperimentCase(unittest.TestCase): except CompileError as error: # Reduce amount of text on terminal. raise error from None + except Exception as exn: + if hasattr(exn, "artiq_core_exception"): + exn.args = "{}\n{}".format(exn.args[0], + exn.artiq_core_exception), + raise exn