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: 5b8f34bae2ac
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: 2a95d2777099
Choose a head ref
  • 2 commits
  • 23 files changed
  • 1 contributor

Commits on Dec 3, 2014

  1. Verified

    This commit was signed with the committer’s verified signature.
    JamiKettunen Jami Kettunen
    Copy the full SHA
    a41009f View commit details
  2. Copy the full SHA
    2a95d27 View commit details
16 changes: 11 additions & 5 deletions artiq/coredevice/comm_dummy.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
from operator import itemgetter
from fractions import Fraction

from artiq.language.context import AutoContext
from artiq.language.units import ms, ns
from artiq.coredevice.runtime import LinkInterface


class _RuntimeEnvironment(LinkInterface):
def __init__(self, ref_period):
self.ref_period = ref_period
self.initial_time = 0
self.internal_ref_period = ref_period
self.warmup_time = 1*ms

def emit_object(self):
return str(self.llvm_module)


class Comm:
class Comm(AutoContext):
implicit_core = False

def get_runtime_env(self):
return _RuntimeEnvironment(Fraction(1, 1000000000))
return _RuntimeEnvironment(1*ns)

def switch_clock(self, external):
pass

def load(self, kcode):
print("================")
21 changes: 10 additions & 11 deletions artiq/coredevice/comm_serial.py
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@

from artiq.language import core as core_language
from artiq.language import units
from artiq.language.context import *
from artiq.coredevice.runtime import Environment
from artiq.coredevice import runtime_exceptions

@@ -59,13 +60,17 @@ def _read_exactly(f, n):
return r


class Comm:
def __init__(self, dev="/dev/ttyUSB1", baud=115200):
self._fd = os.open(dev, os.O_RDWR | os.O_NOCTTY)
class Comm(AutoContext):
serial_dev = Parameter("/dev/ttyUSB1")
baud_rate = Parameter(115200)
implicit_core = False

def build(self):
self._fd = os.open(self.serial_dev, os.O_RDWR | os.O_NOCTTY)
self.port = os.fdopen(self._fd, "r+b", buffering=0)
self.set_baud(115200)
self.set_remote_baud(baud)
self.set_baud(baud)
self.set_remote_baud(self.baud_rate)
self.set_baud(self.baud_rate)

def set_baud(self, baud):
iflag, oflag, cflag, lflag, ispeed, ospeed, cc = \
@@ -109,12 +114,6 @@ def close(self):
self.set_remote_baud(115200)
self.port.close()

def __enter__(self):
return self

def __exit__(self, type, value, traceback):
self.close()

def _get_device_msg(self):
while True:
(reply, ) = struct.unpack("B", _read_exactly(self.port, 1))
22 changes: 13 additions & 9 deletions artiq/coredevice/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import os

from artiq.language.core import *
from artiq.language.context import *

from artiq.transforms.inline import inline
from artiq.transforms.lower_units import lower_units
from artiq.transforms.quantize_time import quantize_time
@@ -10,8 +13,8 @@
from artiq.transforms.interleave import interleave
from artiq.transforms.lower_time import lower_time
from artiq.transforms.unparse import unparse

from artiq.py2llvm import get_runtime_binary
from artiq.language.core import *


def _announce_unparse(label, node):
@@ -41,19 +44,20 @@ def _no_debug_unparse(label, node):
pass


class Core:
def __init__(self, comm, external_clock=None, runtime_env=None):
if runtime_env is None:
runtime_env = comm.get_runtime_env()
self.runtime_env = runtime_env
self.comm = comm
class Core(AutoContext):
comm = Device("comm")
external_clock = Parameter(None)
implicit_core = False

def build(self):
self.runtime_env = self.comm.get_runtime_env()
self.core = self

if external_clock is None:
if self.external_clock is None:
self.ref_period = self.runtime_env.internal_ref_period
self.comm.switch_clock(False)
else:
self.ref_period = external_clock
self.ref_period = self.external_clock
self.comm.switch_clock(True)
self.initial_time = int64(self.runtime_env.warmup_time/self.ref_period)

2 changes: 1 addition & 1 deletion artiq/coredevice/dds.py
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ class DDS(AutoContext):
the DDS device.
"""
dds_sysclk = Parameter()
dds_sysclk = Parameter(1*GHz)
reg_channel = Parameter()
rtio_switch = Parameter()

16 changes: 12 additions & 4 deletions artiq/language/context.py
Original file line number Diff line number Diff line change
@@ -110,13 +110,21 @@ def __init__(self, mvs=None, **kwargs):
for k in dir(self):
v = getattr(self, k)
if isinstance(v, _AttributeKind):
value = self.mvs.get_missing_value(k, v)
if self.mvs is None:
if (isinstance(v, Parameter)
and v.default is not NoDefault):
value = v.default
else:
raise AttributeError("Attribute '{}' not specified"
" and no MVS present".format(k))
else:
value = self.mvs.get_missing_value(k, v, self)
setattr(self, k, value)

self.build()

def get_missing_value(self, name, kind):
"""Attempts to retrieve ``parameter`` from the object's attributes.
def get_missing_value(self, name, kind, requester):
"""Attempts to retrieve ``name`` from the object's attributes.
If not present, forwards the request to the parent MVS.
The presence of this method makes ``AutoContext`` act as a MVS.
@@ -125,7 +133,7 @@ def get_missing_value(self, name, kind):
try:
return getattr(self, name)
except AttributeError:
return self.mvs.get_missing_value(name, kind)
return self.mvs.get_missing_value(name, kind, requester)

def build(self):
"""This is called by ``__init__`` after the parameter initialization
49 changes: 49 additions & 0 deletions artiq/management/dpdb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from collections import OrderedDict
import importlib

from artiq.language.context import *


def create_device(desc, mvs):
module = importlib.import_module(desc["module"])
device_class = getattr(module, desc["class"])
return device_class(mvs, **desc["parameters"])


class DeviceParamDB:
def __init__(self, devices, parameters):
self.devices = devices
self.parameters = parameters
self.active_devices = OrderedDict()

def get_missing_value(self, name, kind, requester):
if isinstance(kind, Device):
if name in self.active_devices:
return self.active_devices[name]
elif name in self.devices:
desc = self.devices[name]
while isinstance(desc, str):
# alias
desc = self.devices[desc]
dev = create_device(desc, self)
self.active_devices[name] = dev
return dev
else:
raise KeyError("Unknown device '{}' of type '{}'"
" requested by {}"
.format(name, kind.type_hint, requester))
elif isinstance(kind, Parameter):
if name in self.parameters:
return self.parameters[name]
elif kind.default is not NoDefault:
return kind.default
else:
raise KeyError("Unknown parameter: " + name)
else:
raise NotImplementedError

def close(self):
for dev in reversed(list(self.active_devices.values())):
if hasattr(dev, "close"):
dev.close()
self.active_devices = OrderedDict()
22 changes: 16 additions & 6 deletions artiq/sim/devices.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
from random import Random

from artiq.language.core import delay
from artiq.language.core import delay, kernel
from artiq.language.context import AutoContext, Parameter
from artiq.language import units
from artiq.sim import time


class Core:
class Core(AutoContext):
implicit_core = False

_level = 0

def run(self, k_function, k_args, k_kwargs):
return k_function(*k_args, **k_kwargs)
Core._level += 1
r = k_function(*k_args, **k_kwargs)
Core._level -= 1
if Core._level == 0:
print(time.manager.format_timeline())
return r


class Input(AutoContext):
name = Parameter()
implicit_core = False

def build(self):
self.prng = Random()

@kernel
def wait_edge(self):
duration = self.prng.randrange(0, 20)*units.ms
time.manager.event(("wait_edge", self.name, duration))
delay(duration)

@kernel
def count_gate(self, duration):
result = self.prng.randrange(0, 100)
time.manager.event(("count_gate", self.name, duration, result))
@@ -32,16 +42,16 @@ def count_gate(self, duration):

class WaveOutput(AutoContext):
name = Parameter()
implicit_core = False

@kernel
def pulse(self, frequency, duration):
time.manager.event(("pulse", self.name, frequency, duration))
delay(duration)


class VoltageOutput(AutoContext):
name = Parameter()
implicit_core = False

@kernel
def set(self, value):
time.manager.event(("set_voltage", self.name, value))
49 changes: 19 additions & 30 deletions doc/manual/getting_started.rst
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ Connecting to the core device
As a very first step, we will turn on a LED on the core device. Create a file ``led.py`` containing the following: ::

from artiq import *
from artiq.coredevice import comm_serial, core, gpio


class LED(AutoContext):
led = Device("gpio_out")
@@ -16,18 +16,14 @@ As a very first step, we will turn on a LED on the core device. Create a file ``
def run(self):
self.led.on()

if __name__ == "__main__":
with comm_serial.Comm() as comm:
core_driver = core.Core(comm)
led_driver = gpio.GPIOOut(core=core_driver, channel=0)
exp = LED(core=core_driver, led=led_driver)
exp.run()

The central part of our code is our ``LED`` class, that derives from :class:`artiq.language.core.AutoContext`. ``AutoContext`` is part of the mechanism that attaches device drivers and retrieves parameters according to a database. We are not using the database yet; instead, we import and create the device drivers and establish communication with the core device manually. Abstract attributes such as ``Device("gpio_out")`` list the devices (and parameters) that our class needs in order to operate. ``AutoContext`` replaces them with the actual device drivers (and parameter values).. Finally, the ``@kernel`` decorator tells the system that the ``run`` method must be executed on the core device (instead of the host).
The central part of our code is our ``LED`` class, that derives from :class:`artiq.language.core.AutoContext`. ``AutoContext`` is part of the mechanism that attaches device drivers and retrieves parameters according to a database. Abstract attributes such as ``Device("gpio_out")`` list the devices (and parameters) that our class needs in order to operate, and the names of the attributes (e.g. ``led``) are used to search the database. ``AutoContext`` replaces them with the actual device drivers (and parameter values). Finally, the ``@kernel`` decorator tells the system that the ``run`` method must be executed on the core device (instead of the host).

Copy the files ``ddb.pyon`` and ``pdb.pyon`` (containing the device and parameter databases) from the ``examples`` folder of ARTIQ into the same directory as ``led.py`` (alternatively, you can use the ``-d`` and ``-p`` options of ``artiq_run.py``). You can open the database files using a text editor - their contents are in a human-readable format.

Run this example with: ::
Run your code using ``artiq_run.py``, which is part of the ARTIQ front-end tools: ::

python3 led.py
$ artiq_run.py led

The LED of the device should turn on. Congratulations! You have a basic ARTIQ system up and running.

@@ -53,9 +49,9 @@ Modify the code as follows: ::

You can then turn the LED off and on by entering 0 or 1 at the prompt that appears: ::

$ python3 led.py
$ artiq_run.py led
Enter desired LED state: 1
$ python3 led.py
$ artiq_run.py led
Enter desired LED state: 0

What happens is the ARTIQ compiler notices that the ``input_led_state`` function does not have a ``@kernel`` decorator and thus must be executed on the host. When the core device calls it, it sends a request to the host to execute it. The host displays the prompt, collects user input, and sends the result back to the core device, which sets the LED state accordingly.
@@ -82,25 +78,18 @@ The point of running code on the core device is the ability to meet demanding re
Create a new file ``rtio.py`` containing the following: ::

from artiq import *
from artiq.coredevice import comm_serial, core, rtio

class Tutorial(AutoContext):
o = Device("ttl_out")
ttl0 = Device("ttl_out")

@kernel
def run(self):
for i in range(1000000):
self.o.pulse(2*us)
self.ttl0.pulse(2*us)
delay(2*us)

if __name__ == "__main__":
with comm_serial.Comm() as comm:
core_driver = core.Core(comm)
out_driver = rtio.RTIOOut(core=core_driver, channel=2)
exp = Tutorial(core=core_driver, o=out_driver)
exp.run()

Connect an oscilloscope or logic analyzer to the RTIO channel 2 (pin C11 on the Papilio Pro, TTL0) and run ``python3 rtio.py``. Notice that the generated signal's period is precisely 4 microseconds, and that it has a duty cycle of precisely 50%. This is not what you would expect if the delay and the pulse were implemented with CPU-controlled GPIO: overhead from the loop management, function calls, etc. would increase the signal's period, and asymmetry in the overhead would cause duty cycle distortion.
Connect an oscilloscope or logic analyzer to TTL0 (pin C11 on the Papilio Pro) and run ``artiq_run.py led``. Notice that the generated signal's period is precisely 4 microseconds, and that it has a duty cycle of precisely 50%. This is not what you would expect if the delay and the pulse were implemented with CPU-controlled GPIO: overhead from the loop management, function calls, etc. would increase the signal's period, and asymmetry in the overhead would cause duty cycle distortion.

Instead, inside the core device, output timing is generated by the gateware and the CPU only programs switching commands with certain timestamps that the CPU computes. This guarantees precise timing as long as the CPU can keep generating timestamps that are increasing fast enough. In case it fails to do that (and attempts to program an event with a timestamp in the past), the :class:`artiq.coredevice.runtime_exceptions.RTIOUnderflow` exception is raised. The kernel causing it may catch it (using a regular ``try... except...`` construct), or it will be propagated to the host.

@@ -113,14 +102,14 @@ Try reducing the period of the generated waveform until the CPU cannot keep up w

class Tutorial(AutoContext):
led = Device("gpio_out")
o = Device("ttl_out")
ttl0 = Device("ttl_out")

@kernel
def run(self):
self.led.off()
try:
for i in range(1000000):
self.o.pulse(...)
self.ttl0.pulse(...)
delay(...)
except RTIOUnderflow:
self.led.on()
@@ -135,21 +124,21 @@ Try the following code and observe the generated pulses on a 2-channel oscillosc

for i in range(1000000):
with parallel:
self.o1.pulse(2*us)
self.o2.pulse(4*us)
self.ttl0.pulse(2*us)
self.ttl1.pulse(4*us)
delay(4*us)

If you assign ``o2`` to the RTIO channel 3, the signal will be generated on the pin C10 (TTL1) of the Papilio Pro.
TTL1 is assigned to the pin C10 of the Papilio Pro. The name of the attributes (``ttl0`` and ``ttl1``) is used to look up hardware in the device database.

Within a parallel block, some statements can be made sequential again using a ``with sequential`` construct. Observe the pulses generated by this code: ::

for i in range(1000000):
with parallel:
with sequential:
self.o1.pulse(2*us)
self.ttl0.pulse(2*us)
delay(1*us)
self.o1.pulse(1*us)
self.o2.pulse(4*us)
self.ttl0.pulse(1*us)
self.ttl1.pulse(4*us)
delay(4*us)

.. warning::
Loading