Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: m-labs/artiq
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2da1dbae9191
Choose a base ref
...
head repository: m-labs/artiq
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 8f045911f10f
Choose a head ref
  • 10 commits
  • 6 files changed
  • 1 contributor

Commits on Aug 25, 2016

  1. Copy the full SHA
    c8faff2 View commit details
  2. Copy the full SHA
    1f31fa6 View commit details
  3. Copy the full SHA
    19d6dbc View commit details
  4. Copy the full SHA
    87d3b53 View commit details
  5. Copy the full SHA
    1c90058 View commit details
  6. Copy the full SHA
    aef99b5 View commit details
  7. doc/rtio: input vis

    jordens committed Aug 25, 2016
    Copy the full SHA
    6e8ff37 View commit details
  8. doc/rtio: vis RTIOUnderflow

    jordens committed Aug 25, 2016
    Copy the full SHA
    e0569a1 View commit details
  9. Copy the full SHA
    6b5424b View commit details
  10. Copy the full SHA
    8f04591 View commit details
Showing with 120 additions and 18 deletions.
  1. +1 −0 conda/artiq/meta.yaml
  2. +5 −0 doc/manual/conf.py
  3. +109 −17 doc/manual/rtio.rst
  4. +2 −0 doc/manual/static/WaveDrom.js
  5. +1 −0 doc/manual/static/default.js
  6. +2 −1 setup.py
1 change: 1 addition & 0 deletions conda/artiq/meta.yaml
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@ requirements:
- asyncserial
- sphinx
- sphinx-argparse
- sphinxcontrib-wavedrom
- h5py
- dateutil
- pyqt5
5 changes: 5 additions & 0 deletions doc/manual/conf.py
Original file line number Diff line number Diff line change
@@ -53,6 +53,7 @@ def __getattr__(cls, name):
'sphinx.ext.mathjax',
'sphinx.ext.viewcode',
'sphinxarg.ext',
'sphinxcontrib.wavedrom', # see also below for config
]

# Add any paths that contain templates here, relative to this directory.
@@ -290,3 +291,7 @@ def __getattr__(cls, name):

# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False

# -- Options for sphinxcontrib-wavedrom -----------------------------------
offline_skin_js_path = 'static/default.js'
offline_wavedrom_js_path = 'static/WaveDrom.js'
126 changes: 109 additions & 17 deletions doc/manual/rtio.rst
Original file line number Diff line number Diff line change
@@ -40,23 +40,44 @@ When computing the difference of absolute timestamps, use ``mu_to_seconds(t2-t1)
When accumulating time, do it in machine units and not in SI units, so that rounding errors do not accumulate.

The following basic example shows how to place output events on the timeline.
It emits a precisely timed 10 µs pulse:::
It emits a precisely timed 2 µs pulse:::

ttl.on()
delay(10*us)
delay(2*us)
ttl.off()

The device ``ttl`` represents a single digital output channel
(:class:`artiq.coredevice.ttl.TTLOut`).
The :meth:`artiq.coredevice.ttl.TTLOut.on` method places an rising edge on the timeline at the current cursor position (``now``).
Then the cursor is moved forward 10 µs and a falling edge event is placed at the new cursor position.
Then the cursor is moved forward 2 µs and a falling edge event is placed at the new cursor position.
Then later, when the wall clock reaches the respective timestamps the RTIO gateware executes the two events.

The following diagram shows what is going on at the different levels of the software and gateware stack (assuming one machine unit of time is 1 ns):

.. wavedrom::
{
signal: [
{name: 'kernel', wave: 'x32.3x', data: ['on()', 'delay(2*us)', 'off()'], node: '..A.XB'},
{name: 'now', wave: '2...2.', data: ['7000', '9000'], node: '..P..Q'},
{},
{name: 'slack', wave: 'x2x.2x', data: ['4400', '5800']},
{},
{name: 'rtio_counter', wave: 'x2x|2x|2x2x', data: ['2600', '3200', '7000', '9000'], node: ' V.W'},
{name: 'ttl', wave: 'x1.0', node: ' R.S', phase: -7.5},
{ node: ' T.U', phase: -7.5}
],
edge: [
'A~>R', 'P~>R', 'V~>R', 'B~>S', 'Q~>S', 'W~>S',
'R-T', 'S-U', 'T<->U 2µs'
],
}

The sequence is exactly equivalent to:::

ttl.pulse(10*us)
ttl.pulse(2*us)

The :meth:`artiq.coredevice.ttl.TTLOut.pulse` method advances the timeline cursor (using ``delay()``) while other methods such as :meth:`artiq.coredevice.ttl.TTLOut.on`, :meth:`artiq.coredevice.ttl.TTLOut.off`, :meth:`artiq.coredevice.dds._DDSGeneric.set`, or the ``set_*()`` methods of :class:`artiq.coredevice.spi.SPIMaster` do not. The latter are called *zero-duration* methods.


Underflow exceptions
--------------------

@@ -73,6 +94,23 @@ The experiment attempts to handle the exception by moving the cursor forward and
delay(16.6667*ms)
ttl.on()

.. wavedrom::
{
signal: [
{name: 'kernel', wave: 'x34..2.3x', data: ['on()', 'RTIOUnderflow', 'delay()', 'on()'], node: '..AB....C', phase: -3},
{name: 'now_mu', wave: '2.....2', data: ['t0', 't1'], node: '.D.....E', phase: -4},
{},
{name: 'slack', wave: '2x....2', data: ['< 0', '> 0'], node: '.T', phase: -4},
{},
{name: 'rtio_counter', wave: 'x2x.2x....2x2', data: ['t0', '> t0', '< t1', 't1'], node: '............P'},
{name: 'tll', wave: 'x...........1', node: '.R..........S', phase: -.5}
],
edge: [
'A-~>R forbidden', 'D-~>R', 'T-~B exception',
'C~>S allowed', 'E~>S', 'P~>S'
]
}

To track down ``RTIOUnderflows`` in an experiment there are a few approaches:

* Exception backtraces show where underflow has occurred while executing the
@@ -95,6 +133,21 @@ The :meth:`artiq.coredevice.ttl.TTLInOut.count` method of an input channel can l
The :meth:`artiq.coredevice.ttl.TTLInOut.gate_rising` method leaves the timeline cursor at the closure time of the gate and ``count()`` must necessarily wait until the gate closing event has actually been executed which is sometime with ``rtio_counter > now``.
In these situations where ``count()`` leads to a synchronization of timeline cursor and wall clock, a ``delay()`` is necessary to reestablish positive slack so that output events can be placed.

.. wavedrom::
{
signal: [
{name: 'kernel', wave: '3..5|..2.3..x..', data: ['gate_rising()', 'count()', 'delay()', 'pulse()'], node: '.A.B...C.ZD.E'},
{name: 'now_mu', wave: '2.2.|....2.2.', node: '.P.Q.....XV.W'},
{},
{},
{name: 'input gate', wave: 'x1.0', node: '.T.U', phase: -3.5},
{name: 'output', wave: 'x1.0', node: '.R.S', phase: -11.5}
],
edge: [
'A~>T', 'P~>T', 'B~>U', 'Q~>U', 'U~>C', 'D~>R', 'E~>S', 'V~>R', 'W~>S'
]
}

Overflow exceptions
-------------------

@@ -109,28 +162,67 @@ Seamless handover
The timeline cursor persists across kernel invocations.
This is demonstrated in the following example where a pulse is split across two kernels:::

def run():
k1()
k2()

@kernel
def kernel1():
def k1():
ttl.on()
delay(1*s)

@kernel
def kernel2():
def k2():
ttl.off()

def run():
kernel1()
kernel2()
Here, ``run()`` calls ``k1()`` which exits leaving the cursor one second after the rising edge and ``k2()`` then submits a falling edge at that position.

.. wavedrom::
{
signal: [
{name: 'kernel', wave: '3.2..2..|3.', data: ['k1: on()', 'k1: delay(dt)', 'k1->k2 swap', 'k2: off()'], node: '..A........B'},
{name: 'now', wave: '2....2...|.', data: ['t0', 't0+dt'], node: '..P........Q'},
{},
{},
{name: 'rtio_counter', wave: 'x.........|2x|2', data: ['t0', 't0+dt'], node: '...........V..W'},
{name: 'ttl', wave: 'x1..0', node: '.R..S', phase: -10.5},
{ node: ' T..U', phase: -10.5}
],
edge: [
'A~>R', 'P~>R', 'V~>R', 'B~>S', 'Q~>S', 'W~>S',
'R-T', 'S-U', 'T<->U dt'
],
}


Synchronization
---------------

``kernel1()`` exits leaving the cursor one second after the rising edge and ``kernel2()`` then submits a falling edge at that position.
The seamless handover of the timeline (cursor and events) across kernels and experiments implies that a kernel can exit long before the events it has submitted have been executed.
If a previous kernel sets timeline cursor far in the future this effectively locks the system.
When a kernel should wait until all the events on a particular channel have been executed, use the :meth:`artiq.coredevice.ttl.TTLOut.sync` method of a channel:

.. wavedrom::
{
signal: [
{name: 'kernel', wave: 'x3x.|5.|x', data: ['on()', 'sync()'], node: '..A.....Y'},
{name: 'now', wave: '2..', data: ['7000'], node: '..P'},
{},
{},
{name: 'rtio_counter', wave: 'x2x.|..2x', data: ['2000', '7000'], node: ' ....V'},
{name: 'ttl', wave: 'x1', node: ' R', phase: -6.5},
],
edge: [
'A~>R', 'P~>R', 'V~>R', 'V~>Y'
],
}

RTIO reset
-----------

The seamless handover of the timeline (cursor and events) across kernels and experiments implies that a kernel can exit long before the events it has submitted have been executed.
If a previous kernel sets timeline cursor far in the future this effectively locks the system.
It also means that a kernel is not guaranteed to always be executed with positive slack.

The seamless handover also means that a kernel is not guaranteed to always be executed with positive slack.
An experiment can face any of these circumstances (large positive slack, full FIFOs, or negative slack).
Therefore, when switching experiments it can be adequate to clear the RTIO FIFOs and initialize the timeline cursor to "sometime in the near future" using :meth:`artiq.coredevice.core.Core.reset`.

To accommodate for seamless handover the idle kernel is is only executed when no other experiment is pending and when there is significant negative slack.
The example idle kernel implements this mechanism.
Since it never waits for any input, it will rapidly fill the output FIFOs and would produce a large positive slack.
To avoid large positive slack and to accommodate for seamless handover the idle kernel will only run when no other experiment is pending and the example will wait before submitting events until there is significant negative slack.
Loading