forked from M-Labs/artiq
1
0
Fork 0

expose machine units to user

This commit is contained in:
Sebastien Bourdeauducq 2015-07-01 22:22:53 +02:00
parent 944bfafefa
commit 9d6287a6a3
14 changed files with 219 additions and 209 deletions

View File

@ -121,10 +121,9 @@ class Core(AutoDB):
self.first_run = False self.first_run = False
@kernel @kernel
def get_rtio_time(self): def get_rtio_counter_mu(self):
return cycles_to_time(syscall("rtio_get_counter")) return syscall("rtio_get_counter")
@kernel @kernel
def break_realtime(self): def break_realtime(self):
t = syscall("rtio_get_counter") + 125000 at_mu(syscall("rtio_get_counter") + 125000)
at(cycles_to_time(t))

View File

@ -37,7 +37,7 @@ class DDSBus(AutoDB):
def batch_enter(self): def batch_enter(self):
"""Starts a DDS command batch. All DDS commands are buffered """Starts a DDS command batch. All DDS commands are buffered
after this call, until ``batch_exit`` is called.""" after this call, until ``batch_exit`` is called."""
syscall("dds_batch_enter", time_to_cycles(now())) syscall("dds_batch_enter", now_mu())
@kernel @kernel
def batch_exit(self): def batch_exit(self):
@ -76,12 +76,24 @@ class DDS(AutoDB):
""" """
return ftw*self.sysclk/2**32 return ftw*self.sysclk/2**32
@portable
def turns_to_pow(self, turns):
"""Returns the phase offset word corresponding to the given phase
in turns."""
return round(turns*2**14)
@portable
def pow_to_turns(self, pow):
"""Returns the phase in turns corresponding to the given phase offset
word."""
return pow/2**14
@kernel @kernel
def init(self): def init(self):
"""Resets and initializes the DDS channel. """Resets and initializes the DDS channel.
The runtime does this for all channels upon core device startup.""" The runtime does this for all channels upon core device startup."""
syscall("dds_init", time_to_cycles(now()), self.channel) syscall("dds_init", now_mu(), self.channel)
@kernel @kernel
def set_phase_mode(self, phase_mode): def set_phase_mode(self, phase_mode):
@ -104,9 +116,11 @@ class DDS(AutoDB):
self.phase_mode = phase_mode self.phase_mode = phase_mode
@kernel @kernel
def set(self, frequency, phase=0, phase_mode=_PHASE_MODE_DEFAULT): def set_mu(self, frequency, phase=0, phase_mode=_PHASE_MODE_DEFAULT):
"""Sets the DDS channel to the specified frequency and phase. """Sets the DDS channel to the specified frequency and phase.
This uses machine units (FTW and POW).
:param frequency: frequency to generate. :param frequency: frequency to generate.
:param phase: adds an offset, in turns, to the phase. :param phase: adds an offset, in turns, to the phase.
:param phase_mode: if specified, overrides the default phase mode set :param phase_mode: if specified, overrides the default phase mode set
@ -114,7 +128,11 @@ class DDS(AutoDB):
""" """
if phase_mode == _PHASE_MODE_DEFAULT: if phase_mode == _PHASE_MODE_DEFAULT:
phase_mode = self.phase_mode phase_mode = self.phase_mode
syscall("dds_set", now_mu(), self.channel,
frequency, round(phase*2**14), phase_mode)
syscall("dds_set", time_to_cycles(now()), self.channel, @kernel
self.frequency_to_ftw(frequency), round(phase*2**14), def set(self, frequency, phase=0, phase_mode=_PHASE_MODE_DEFAULT):
phase_mode) """Like ``set_mu``, but uses Hz and turns."""
self.set_mu(self.frequency_to_ftw(frequency),
self.turns_to_pow(phase), phase_mode)

View File

@ -2,47 +2,6 @@ from artiq.language.core import *
from artiq.language.db import * from artiq.language.db import *
class LLTTLOut(AutoDB):
"""Low-level RTIO TTL output driver.
Allows setting RTIO TTL outputs at arbitrary times, without time
unit conversion.
This is meant to be used mostly in drivers; consider using
``TTLOut`` instead.
This should be used with output-only channels.
"""
class DBKeys:
core = Device()
channel = Argument()
@kernel
def set_o(self, t, value):
"""Sets the output value of the RTIO channel.
:param t: timestamp in RTIO cycles (64-bit integer).
:param value: value to set at the output.
"""
syscall("ttl_set_o", t, self.channel, value)
@kernel
def on(self, t):
"""Turns the RTIO channel on.
:param t: timestamp in RTIO cycles (64-bit integer).
"""
self.set_o(t, True)
@kernel
def off(self, t):
"""Turns the RTIO channel off.
:param t: timestamp in RTIO cycles (64-bit integer).
"""
self.set_o(t, False)
class TTLOut(AutoDB): class TTLOut(AutoDB):
"""RTIO TTL output driver. """RTIO TTL output driver.
@ -61,9 +20,9 @@ class TTLOut(AutoDB):
self.o_previous_timestamp = int64(0) self.o_previous_timestamp = int64(0)
@kernel @kernel
def _set_o(self, o): def set_o(self, o):
syscall("ttl_set_o", time_to_cycles(now()), self.channel, o) syscall("ttl_set_o", now_mu(), self.channel, o)
self.o_previous_timestamp = time_to_cycles(now()) self.o_previous_timestamp = now_mu()
@kernel @kernel
def sync(self): def sync(self):
@ -74,16 +33,25 @@ class TTLOut(AutoDB):
@kernel @kernel
def on(self): def on(self):
"""Sets the output to a logic high state.""" """Sets the output to a logic high state."""
self._set_o(True) self.set_o(True)
@kernel @kernel
def off(self): def off(self):
"""Sets the output to a logic low state.""" """Sets the output to a logic low state."""
self._set_o(False) self.set_o(False)
@kernel
def pulse_mu(self, duration):
"""Pulses the output high for the specified duration
(in machine units)."""
self.on()
delay_mu(duration)
self.off()
@kernel @kernel
def pulse(self, duration): def pulse(self, duration):
"""Pulses the output high for the specified duration.""" """Pulses the output high for the specified duration
(in seconds)."""
self.on() self.on()
delay(duration) delay(duration)
self.off() self.off()
@ -117,21 +85,21 @@ class TTLInOut(AutoDB):
self.i_previous_timestamp = int64(0) self.i_previous_timestamp = int64(0)
@kernel @kernel
def _set_oe(self, oe): def set_oe(self, oe):
syscall("ttl_set_oe", time_to_cycles(now()), self.channel, oe) syscall("ttl_set_oe", now_mu(), self.channel, oe)
@kernel @kernel
def output(self): def output(self):
self._set_oe(True) self.set_oe(True)
@kernel @kernel
def input(self): def input(self):
self._set_oe(False) self.set_oe(False)
@kernel @kernel
def _set_o(self, o): def set_o(self, o):
syscall("ttl_set_o", time_to_cycles(now()), self.channel, o) syscall("ttl_set_o", now_mu(), self.channel, o)
self.o_previous_timestamp = time_to_cycles(now()) self.o_previous_timestamp = now_mu()
@kernel @kernel
def sync(self): def sync(self):
@ -142,43 +110,78 @@ class TTLInOut(AutoDB):
@kernel @kernel
def on(self): def on(self):
"""Sets the output to a logic high state.""" """Sets the output to a logic high state."""
self._set_o(True) self.set_o(True)
@kernel @kernel
def off(self): def off(self):
"""Sets the output to a logic low state.""" """Sets the output to a logic low state."""
self._set_o(False) self.set_o(False)
@kernel
def pulse_mu(self, duration):
"""Pulses the output high for the specified duration
(in machine units)."""
self.on()
delay_mu(duration)
self.off()
@kernel @kernel
def pulse(self, duration): def pulse(self, duration):
"""Pulses the output high for the specified duration.""" """Pulses the output high for the specified duration
(in seconds)."""
self.on() self.on()
delay(duration) delay(duration)
self.off() self.off()
@kernel @kernel
def _set_sensitivity(self, value): def _set_sensitivity(self, value):
syscall("ttl_set_sensitivity", time_to_cycles(now()), self.channel, value) syscall("ttl_set_sensitivity", now_mu(), self.channel, value)
self.i_previous_timestamp = time_to_cycles(now()) self.i_previous_timestamp = now_mu()
@kernel
def gate_rising_mu(self, duration):
"""Register rising edge events for the specified duration
(in machine units)."""
self._set_sensitivity(1)
delay_mu(duration)
self._set_sensitivity(0)
@kernel
def gate_falling_mu(self, duration):
"""Register falling edge events for the specified duration
(in machine units)."""
self._set_sensitivity(2)
delay_mu(duration)
self._set_sensitivity(0)
@kernel
def gate_both_mu(self, duration):
"""Register both rising and falling edge events for the specified
duration (in machine units)."""
self._set_sensitivity(3)
delay_mu(duration)
self._set_sensitivity(0)
@kernel @kernel
def gate_rising(self, duration): def gate_rising(self, duration):
"""Register rising edge events for the specified duration.""" """Register rising edge events for the specified duration
(in seconds)."""
self._set_sensitivity(1) self._set_sensitivity(1)
delay(duration) delay(duration)
self._set_sensitivity(0) self._set_sensitivity(0)
@kernel @kernel
def gate_falling(self, duration): def gate_falling(self, duration):
"""Register falling edge events for the specified duration.""" """Register falling edge events for the specified duration
(in seconds)."""
self._set_sensitivity(2) self._set_sensitivity(2)
delay(duration) delay(duration)
self._set_sensitivity(0) self._set_sensitivity(0)
@kernel @kernel
def gate_both(self, duration): def gate_both_mu(self, duration):
"""Register both rising and falling edge events for the specified """Register both rising and falling edge events for the specified
duration.""" duration (in seconds)."""
self._set_sensitivity(3) self._set_sensitivity(3)
delay(duration) delay(duration)
self._set_sensitivity(0) self._set_sensitivity(0)
@ -200,5 +203,4 @@ class TTLInOut(AutoDB):
If the gate is permanently closed, returns a negative value. If the gate is permanently closed, returns a negative value.
""" """
return cycles_to_time(syscall("ttl_get", self.channel, return syscall("ttl_get", self.channel, self.i_previous_timestamp)
self.i_previous_timestamp))

View File

@ -99,7 +99,7 @@ class _Frame:
def _arm(self): def _arm(self):
self.segment_delays = [ self.segment_delays = [
time_to_cycles(s.get_duration()*delay_margin_factor, self.core) seconds_to_mu(s.get_duration()*delay_margin_factor, self.core)
for s in self.segments] for s in self.segments]
def _invalidate(self): def _invalidate(self):
@ -125,7 +125,8 @@ class _Frame:
if not self.pdq.armed: if not self.pdq.armed:
raise ArmError raise ArmError
t = time_to_cycles(now()) - time_to_cycles(trigger_duration/2) call_t = now_mu()
trigger_start_t = call_t - seconds_to_mu(trigger_duration/2)
if self.pdq.current_frame >= 0: if self.pdq.current_frame >= 0:
# PDQ is in the middle of a frame. Check it is us. # PDQ is in the middle of a frame. Check it is us.
@ -136,15 +137,16 @@ class _Frame:
# to play our first segment. # to play our first segment.
self.pdq.current_frame = self.frame_number self.pdq.current_frame = self.frame_number
self.pdq.next_segment = 0 self.pdq.next_segment = 0
t2 = t - time_to_cycles(frame_setup) at_mu(trigger_start_t - seconds_to_mu(frame_setup))
self.pdq.frame0.set_value(t2, self.frame_number & 1) self.pdq.frame0.set_o(bool(self.frame_number & 1))
self.pdq.frame1.set_value(t2, (self.frame_number & 2) >> 1) self.pdq.frame1.set_o(bool((self.frame_number & 2) >> 1))
self.pdq.frame2.set_value(t2, (self.frame_number & 4) >> 2) self.pdq.frame2.set_o(bool((self.frame_number & 4) >> 2))
self.pdq.trigger.on(t) at_mu(trigger_start_t)
self.pdq.trigger.off(t + time_to_cycles(trigger_duration)) self.pdq.trigger.pulse(trigger_duration)
delay(cycles_to_time(self.segment_delays[self.pdq.next_segment])) at_mu(call_t)
delay_mu(self.segment_delays[self.pdq.next_segment])
self.pdq.next_segment += 1 self.pdq.next_segment += 1
# test for end of frame # test for end of frame
@ -157,19 +159,15 @@ class CompoundPDQ2(AutoDB):
class DBKeys: class DBKeys:
core = Device() core = Device()
pdq2_devices = Argument() pdq2_devices = Argument()
rtio_trigger = Argument() trigger_device = Argument()
rtio_frame = Argument() frame_devices = Argument()
def build(self): def build(self):
self.pdq2s = [self.dbh.get_device(d) for d in self.pdq2_devices] self.pdq2s = [self.dbh.get_device(d) for d in self.pdq2_devices]
self.trigger = ttl.LLTTLOut( self.trigger = self.dbh.get_device(self.trigger_device)
core=self.core, channel=self.rtio_trigger) self.frame0 = self.dbh.get_device(self.frame_devices[0])
self.frame0 = ttl.LLTTLOut( self.frame1 = self.dbh.get_device(self.frame_devices[1])
core=self.core, channel=self.rtio_frame[0]) self.frame2 = self.dbh.get_device(self.frame_devices[2])
self.frame1 = ttl.LLTTLOut(
core=self.core, channel=self.rtio_frame[1])
self.frame2 = ttl.LLTTLOut(
core=self.core, channel=self.rtio_frame[2])
self.frames = [] self.frames = []
self.current_frame = -1 self.current_frame = -1

View File

@ -28,7 +28,6 @@ class int64(int):
True True
>>> a + b >>> a + b
6 6
""" """
pass pass
@ -62,7 +61,6 @@ def round64(x):
This function is equivalent to ``int64(round(x))`` but, when targeting This function is equivalent to ``int64(round(x))`` but, when targeting
static compilation, prevents overflow when the rounded value is too large static compilation, prevents overflow when the rounded value is too large
to fit in a 32-bit integer. to fit in a 32-bit integer.
""" """
return int64(round(x)) return int64(round(x))
@ -88,7 +86,6 @@ def kernel(arg):
The decorator takes an optional parameter that defaults to ``core`` and The decorator takes an optional parameter that defaults to ``core`` and
specifies the name of the attribute to use as core device driver. specifies the name of the attribute to use as core device driver.
""" """
if isinstance(arg, str): if isinstance(arg, str):
def real_decorator(k_function): def real_decorator(k_function):
@ -117,7 +114,6 @@ def portable(f):
host will be executed on the host (no compilation and execution on the host will be executed on the host (no compilation and execution on the
core device). A decorated function called from a kernel will be executed core device). A decorated function called from a kernel will be executed
on the core device (no RPC). on the core device (no RPC).
""" """
f.k_function_info = _KernelFunctionInfo(core_name="", k_function=f) f.k_function_info = _KernelFunctionInfo(core_name="", k_function=f)
return f return f
@ -131,9 +127,10 @@ class _DummyTimeManager:
enter_sequential = _not_implemented enter_sequential = _not_implemented
enter_parallel = _not_implemented enter_parallel = _not_implemented
exit = _not_implemented exit = _not_implemented
take_time_mu = _not_implemented
get_time_mu = _not_implemented
set_time_mu = _not_implemented
take_time = _not_implemented take_time = _not_implemented
get_time = _not_implemented
set_time = _not_implemented
_time_manager = _DummyTimeManager() _time_manager = _DummyTimeManager()
@ -143,7 +140,6 @@ def set_time_manager(time_manager):
directly inside the Python interpreter. The time manager responds to the directly inside the Python interpreter. The time manager responds to the
entering and leaving of parallel/sequential blocks, delays, etc. and entering and leaving of parallel/sequential blocks, delays, etc. and
provides a time-stamped logging facility for events. provides a time-stamped logging facility for events.
""" """
global _time_manager global _time_manager
_time_manager = time_manager _time_manager = time_manager
@ -160,7 +156,6 @@ _syscall_manager = _DummySyscallManager()
def set_syscall_manager(syscall_manager): def set_syscall_manager(syscall_manager):
"""Set the system call manager used for simulating the core device's """Set the system call manager used for simulating the core device's
runtime in the Python interpreter. runtime in the Python interpreter.
""" """
global _syscall_manager global _syscall_manager
_syscall_manager = syscall_manager _syscall_manager = syscall_manager
@ -168,7 +163,8 @@ def set_syscall_manager(syscall_manager):
# global namespace for kernels # global namespace for kernels
kernel_globals = ("sequential", "parallel", kernel_globals = ("sequential", "parallel",
"delay", "now", "at", "time_to_cycles", "cycles_to_time", "delay_mu", "now_mu", "at_mu", "delay",
"seconds_to_mu", "mu_to_seconds",
"syscall", "watchdog") "syscall", "watchdog")
@ -190,7 +186,6 @@ class _Parallel:
The execution time of a parallel block is the execution time of its longest The execution time of a parallel block is the execution time of its longest
statement. A parallel block may contain sequential blocks, which themselves statement. A parallel block may contain sequential blocks, which themselves
may contain parallel blocks, etc. may contain parallel blocks, etc.
""" """
def __enter__(self): def __enter__(self):
_time_manager.enter_parallel() _time_manager.enter_parallel()
@ -200,50 +195,49 @@ class _Parallel:
parallel = _Parallel() parallel = _Parallel()
def delay(duration): def delay_mu(duration):
"""Increases the RTIO time by the given amount. """Increases the RTIO time by the given amount (in machine units)."""
_time_manager.take_time_mu(duration)
"""
def now_mu():
"""Retrieves the current RTIO time, in machine units."""
return _time_manager.get_time_mu()
def at_mu(time):
"""Sets the RTIO time to the specified absolute value, in machine units."""
_time_manager.set_time_mu(time)
def delay(duration):
"""Increases the RTIO time by the given amount (in seconds)."""
_time_manager.take_time(duration) _time_manager.take_time(duration)
def now(): def seconds_to_mu(seconds, core=None):
"""Retrieves the current RTIO time, in seconds. """Converts seconds to the corresponding number of machine units
(RTIO cycles).
""" :param seconds: time (in seconds) to convert.
return _time_manager.get_time() :param core: core device for which to perform the conversion. Specify only
def at(time):
"""Sets the RTIO time to the specified absolute value.
"""
_time_manager.set_time(time)
def time_to_cycles(time, core=None):
"""Converts time to the corresponding number of RTIO cycles.
:param time: Time (in seconds) to convert.
:param core: Core device for which to perform the conversion. Specify only
when running in the interpreter (not in kernel).
"""
if core is None:
raise ValueError("Core device must be specified for time conversion")
return round64(time//core.ref_period)
def cycles_to_time(cycles, core=None):
"""Converts RTIO cycles to the corresponding time.
:param time: Cycle count to convert.
:param core: Core device for which to perform the conversion. Specify only
when running in the interpreter (not in kernel). when running in the interpreter (not in kernel).
""" """
if core is None: if core is None:
raise ValueError("Core device must be specified for time conversion") raise ValueError("Core device must be specified for time conversion")
return cycles*core.ref_period return round64(seconds//core.ref_period)
def mu_to_seconds(mu, core=None):
"""Converts machine units (RTIO cycles) to seconds.
:param mu: cycle count to convert.
:param core: core device for which to perform the conversion. Specify only
when running in the interpreter (not in kernel).
"""
if core is None:
raise ValueError("Core device must be specified for time conversion")
return mu*core.ref_period
def syscall(*args): def syscall(*args):

View File

@ -7,13 +7,15 @@ from artiq.sim import time
class Core(AutoDB): class Core(AutoDB):
_level = 0 def build(self):
self.ref_period = 1
self._level = 0
def run(self, k_function, k_args, k_kwargs): def run(self, k_function, k_args, k_kwargs):
Core._level += 1 self._level += 1
r = k_function(*k_args, **k_kwargs) r = k_function(*k_args, **k_kwargs)
Core._level -= 1 self._level -= 1
if Core._level == 0: if self._level == 0:
print(time.manager.format_timeline()) print(time.manager.format_timeline())
return r return r

View File

@ -30,37 +30,39 @@ class Manager:
self.timeline = [] self.timeline = []
def enter_sequential(self): def enter_sequential(self):
new_context = SequentialTimeContext(self.get_time()) new_context = SequentialTimeContext(self.get_time_mu())
self.stack.append(new_context) self.stack.append(new_context)
def enter_parallel(self): def enter_parallel(self):
new_context = ParallelTimeContext(self.get_time()) new_context = ParallelTimeContext(self.get_time_mu())
self.stack.append(new_context) self.stack.append(new_context)
def exit(self): def exit(self):
old_context = self.stack.pop() old_context = self.stack.pop()
self.take_time(old_context.block_duration) self.take_time(old_context.block_duration)
def take_time(self, duration): def take_time_mu(self, duration):
self.stack[-1].take_time(duration) self.stack[-1].take_time(duration)
def get_time(self): def get_time_mu(self):
return self.stack[-1].current_time return self.stack[-1].current_time
def set_time(self, t): def set_time_mu(self, t):
dt = t - self.get_time() dt = t - self.get_time_mu()
if dt < 0*s: if dt < 0*s:
raise ValueError("Attempted to go back in time") raise ValueError("Attempted to go back in time")
self.take_time(dt) self.take_time(dt)
take_time = take_time_mu
def event(self, description): def event(self, description):
self.timeline.append((self.get_time(), description)) self.timeline.append((self.get_time_mu(), description))
def format_timeline(self): def format_timeline(self):
r = "" r = ""
prev_time = 0*s prev_time = 0*s
for time, description in sorted(self.timeline, key=itemgetter(0)): for time, description in sorted(self.timeline, key=itemgetter(0)):
r += "@{:10} (+{:10}) ".format(str(time), str(time-prev_time)) r += "@{:.9f} (+{:.9f}) ".format(time, time-prev_time)
for item in description: for item in description:
r += "{:16}".format(str(item)) r += "{:16}".format(str(item))
r += "\n" r += "\n"

View File

@ -74,17 +74,20 @@ class _PulseLogger(AutoDB):
self.first_timestamp = t self.first_timestamp = t
self.output_list.append((self.name, t-self.first_timestamp, l, f)) self.output_list.append((self.name, t-self.first_timestamp, l, f))
def int_usec(self, mu):
return round(mu_to_seconds(mu, self.core)*1000000)
def on(self, t, f): def on(self, t, f):
self._append(t, True, f) self._append(self.int_usec(t), True, f)
def off(self, t): def off(self, t):
self._append(t, False, 0) self._append(self.int_usec(t), False, 0)
@kernel @kernel
def pulse(self, f, duration): def pulse(self, f, duration):
self.on(round(now()*1000000000), f) self.on(now_mu(), f)
delay(duration) delay(duration)
self.off(round(now()*1000000000)) self.off(now_mu())
class _Pulses(AutoDB): class _Pulses(AutoDB):
@ -288,9 +291,9 @@ class _RTIOSequenceError(AutoDB):
@kernel @kernel
def run(self): def run(self):
t = now() t = now_mu()
self.o.pulse(25*us) self.o.pulse(25*us)
at(t) at_mu(t)
self.o.pulse(25*us) self.o.pulse(25*us)

View File

@ -10,7 +10,7 @@ optimize_in = """
def run(): def run():
dds_sysclk = Fraction(1000000000, 1) dds_sysclk = Fraction(1000000000, 1)
n = time_to_cycles((1.2345 * Fraction(1, 1000000000))) n = seconds_to_mu((1.2345 * Fraction(1, 1000000000)))
with sequential: with sequential:
frequency = 345 * Fraction(1000000, 1) frequency = 345 * Fraction(1000000, 1)
frequency_to_ftw_return = int((((2 ** 32) * frequency) / dds_sysclk)) frequency_to_ftw_return = int((((2 ** 32) * frequency) / dds_sysclk))
@ -19,7 +19,7 @@ def run():
ftw2 = ftw ftw2 = ftw
ftw_to_frequency_return = ((ftw2 * dds_sysclk) / (2 ** 32)) ftw_to_frequency_return = ((ftw2 * dds_sysclk) / (2 ** 32))
f = ftw_to_frequency_return f = ftw_to_frequency_return
phi = ((1000 * cycles_to_time(n)) * f) phi = ((1000 * mu_to_seconds(n)) * f)
do_something(int(phi)) do_something(int(phi))
""" """

View File

@ -28,13 +28,14 @@ def _get_duration(stmt):
return -1 return -1
elif isinstance(stmt, ast.Call): elif isinstance(stmt, ast.Call):
name = stmt.func.id name = stmt.func.id
if name == "delay": assert(name != "delay")
if name == "delay_mu":
try: try:
da = eval_constant(stmt.args[0]) da = eval_constant(stmt.args[0])
except NotConstant: except NotConstant:
da = -1 da = -1
return da return da
elif name == "at": elif name == "at_mu":
return -1 return -1
else: else:
return 0 return 0
@ -69,7 +70,7 @@ def _interleave_timelines(timelines):
ref_stmt = stmt.stmt ref_stmt = stmt.stmt
delay_stmt = ast.copy_location( delay_stmt = ast.copy_location(
ast.Expr(ast.Call( ast.Expr(ast.Call(
func=ast.Name("delay", ast.Load()), func=ast.Name("delay_mu", ast.Load()),
args=[value_to_ast(dt)], args=[value_to_ast(dt)],
keywords=[], starargs=[], kwargs=[])), keywords=[], starargs=[], kwargs=[])),
ref_stmt) ref_stmt)

View File

@ -1,15 +1,15 @@
""" """
This transform implements time management functions (delay/now/at) This transform implements time management functions (delay_mu/now_mu/at_mu)
using an accumulator 'now' and simple replacement rules: using an accumulator 'now' and simple replacement rules:
delay(t) -> now += t delay_mu(t) -> now += t
now() -> now now_mu() -> now
at(t) -> now = t at_mu(t) -> now = t
Time parameters must be quantized to integers before running this transform. The function delay(), that uses seconds, must be lowered to delay_mu() before
invoking this transform.
The accumulator is initialized to an int64 value at the beginning of the The accumulator is initialized to an int64 value at the beginning of the
output function. output function.
""" """
import ast import ast
@ -17,7 +17,7 @@ import ast
class _TimeLowerer(ast.NodeTransformer): class _TimeLowerer(ast.NodeTransformer):
def visit_Call(self, node): def visit_Call(self, node):
if node.func.id == "now": if node.func.id == "now_mu":
return ast.copy_location(ast.Name("now", ast.Load()), node) return ast.copy_location(ast.Name("now", ast.Load()), node)
else: else:
self.generic_visit(node) self.generic_visit(node)
@ -27,13 +27,13 @@ class _TimeLowerer(ast.NodeTransformer):
r = node r = node
if isinstance(node.value, ast.Call): if isinstance(node.value, ast.Call):
funcname = node.value.func.id funcname = node.value.func.id
if funcname == "delay": if funcname == "delay_mu":
r = ast.copy_location( r = ast.copy_location(
ast.AugAssign(target=ast.Name("now", ast.Store()), ast.AugAssign(target=ast.Name("now", ast.Store()),
op=ast.Add(), op=ast.Add(),
value=node.value.args[0]), value=node.value.args[0]),
node) node)
elif funcname == "at": elif funcname == "at_mu":
r = ast.copy_location( r = ast.copy_location(
ast.Assign(targets=[ast.Name("now", ast.Store())], ast.Assign(targets=[ast.Name("now", ast.Store())],
value=node.value.args[0]), value=node.value.args[0]),

View File

@ -1,12 +1,12 @@
""" """
This transform turns calls to delay/now/at that use non-integer time This transform turns calls to delay() that use non-integer time
expressed in seconds into calls that use int64 time expressed in multiples of expressed in seconds into calls to delay_mu() that use int64 time
ref_period. expressed in multiples of ref_period.
It does so by inserting multiplication/division/rounding operations around It does so by inserting multiplication/division/rounding operations around
those calls. those calls.
The time_to_cycles and cycles_to_time core language functions are also The seconds_to_mu and mu_to_seconds core language functions are also
implemented here, as well as watchdog to syscall conversion. implemented here, as well as watchdog to syscall conversion.
""" """
@ -15,14 +15,7 @@ import ast
from artiq.transforms.tools import value_to_ast from artiq.transforms.tools import value_to_ast
def _call_now(node): def _seconds_to_mu(ref_period, node):
return ast.copy_location(
ast.Call(func=ast.Name("now", ast.Load()),
args=[], keywords=[], starargs=[], kwargs=[]),
node)
def _time_to_cycles(ref_period, node):
divided = ast.copy_location( divided = ast.copy_location(
ast.BinOp(left=node, ast.BinOp(left=node,
op=ast.Div(), op=ast.Div(),
@ -35,7 +28,7 @@ def _time_to_cycles(ref_period, node):
divided) divided)
def _cycles_to_time(ref_period, node): def _mu_to_seconds(ref_period, node):
return ast.copy_location( return ast.copy_location(
ast.BinOp(left=node, ast.BinOp(left=node,
op=ast.Mult(), op=ast.Mult(),
@ -50,29 +43,22 @@ class _TimeQuantizer(ast.NodeTransformer):
def visit_Call(self, node): def visit_Call(self, node):
funcname = node.func.id funcname = node.func.id
if funcname == "now": if funcname == "delay":
return _cycles_to_time(self.ref_period, _call_now(node)) node.func.id = "delay_mu"
elif funcname == "delay" or funcname == "at":
if (isinstance(node.args[0], ast.Call) if (isinstance(node.args[0], ast.Call)
and node.args[0].func.id == "cycles_to_time"): and node.args[0].func.id == "mu_to_seconds"):
# optimize: # optimize:
# delay/at(cycles_to_time(x)) -> delay/at(x) # delay(mu_to_seconds(x)) -> delay_mu(x)
node.args[0] = self.visit(node.args[0].args[0]) node.args[0] = self.visit(node.args[0].args[0])
else: else:
node.args[0] = _time_to_cycles(self.ref_period, node.args[0] = _seconds_to_mu(self.ref_period,
self.visit(node.args[0])) self.visit(node.args[0]))
return node return node
elif funcname == "time_to_cycles": elif funcname == "seconds_to_mu":
if (isinstance(node.args[0], ast.Call) return _seconds_to_mu(self.ref_period,
and node.args[0].func.id == "now"):
# optimize:
# time_to_cycles(now()) -> now()
return _call_now(node)
else:
return _time_to_cycles(self.ref_period,
self.visit(node.args[0])) self.visit(node.args[0]))
elif funcname == "cycles_to_time": elif funcname == "mu_to_seconds":
return _cycles_to_time(self.ref_period, return _mu_to_seconds(self.ref_period,
self.visit(node.args[0])) self.visit(node.args[0]))
else: else:
self.generic_visit(node) self.generic_visit(node)
@ -123,6 +109,5 @@ class _TimeQuantizer(ast.NodeTransformer):
return node return node
def quantize_time(func_def, ref_period): def quantize_time(func_def, ref_period):
_TimeQuantizer(ref_period).visit(func_def) _TimeQuantizer(ref_period).visit(func_def)

View File

@ -6,8 +6,9 @@ from artiq.language import units
embeddable_funcs = ( embeddable_funcs = (
core_language.delay, core_language.at, core_language.now, core_language.delay_mu, core_language.at_mu, core_language.now_mu,
core_language.time_to_cycles, core_language.cycles_to_time, core_language.delay,
core_language.seconds_to_mu, core_language.mu_to_seconds,
core_language.syscall, core_language.watchdog, core_language.syscall, core_language.watchdog,
range, bool, int, float, round, len, range, bool, int, float, round, len,
core_language.int64, core_language.round64, core_language.int64, core_language.round64,
@ -92,7 +93,7 @@ def eval_constant(node):
_replaceable_funcs = { _replaceable_funcs = {
"bool", "int", "float", "round", "bool", "int", "float", "round",
"int64", "round64", "Fraction", "int64", "round64", "Fraction",
"time_to_cycles", "cycles_to_time" "seconds_to_mu", "mu_to_seconds"
} }

View File

@ -43,6 +43,12 @@
"class": "TTLOut", "class": "TTLOut",
"arguments": {"channel": 4} "arguments": {"channel": 4}
}, },
"ttl3": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 5}
},
"led": { "led": {
"type": "local", "type": "local",
"module": "artiq.coredevice.ttl", "module": "artiq.coredevice.ttl",
@ -113,10 +119,9 @@
"class": "CompoundPDQ2", "class": "CompoundPDQ2",
"arguments": { "arguments": {
"pdq2_devices": ["qc_q1_0", "qc_q1_1", "qc_q1_2", "qc_q1_3"], "pdq2_devices": ["qc_q1_0", "qc_q1_1", "qc_q1_2", "qc_q1_3"],
"rtio_trigger": 7, "trigger_device": "ttl3",
"rtio_frame": [2, 3, 4] "frame_devices": ["ttl0", "ttl1", "ttl2"]
}, }
"comment": "Uses ttl 0, 1, 2 and 5"
}, },
"lda": { "lda": {