diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 5d4ae8725..68fdfdbf6 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -2,6 +2,7 @@ import os from artiq.transforms.inline import inline from artiq.transforms.lower_units import lower_units +from artiq.transforms.quantize_time import quantize_time from artiq.transforms.remove_inter_assigns import remove_inter_assigns from artiq.transforms.fold_constants import fold_constants from artiq.transforms.remove_dead_code import remove_dead_code @@ -54,6 +55,9 @@ class Core: remove_inter_assigns(func_def) debug_unparse("remove_inter_assigns_1", func_def) + quantize_time(func_def, self.runtime_env.ref_period) + debug_unparse("quantize_time", func_def) + fold_constants(func_def) debug_unparse("fold_constants_1", func_def) @@ -63,9 +67,7 @@ class Core: interleave(func_def) debug_unparse("interleave", func_def) - lower_time(func_def, - getattr(self.runtime_env, "initial_time", 0), - self.runtime_env.ref_period) + lower_time(func_def, getattr(self.runtime_env, "initial_time", 0)) debug_unparse("lower_time", func_def) remove_inter_assigns(func_def) diff --git a/artiq/transforms/lower_time.py b/artiq/transforms/lower_time.py index be84a57f8..f991cf8f6 100644 --- a/artiq/transforms/lower_time.py +++ b/artiq/transforms/lower_time.py @@ -1,64 +1,29 @@ +"""This transform implements time management functions (delay/now/at) +using an accumulator 'now' and simple replacement rules: + + delay(t) -> now += t + now() -> now + at(t) -> now = t + +Time parameters must be quantized to integers before running this transform. +The accumulator is initialized to an int64 value at the beginning of the +output function. + +""" + import ast from artiq.transforms.tools import value_to_ast from artiq.language.core import int64 -def _time_to_cycles(ref_period, node): - divided = ast.copy_location( - ast.BinOp(left=node, - op=ast.Div(), - right=value_to_ast(ref_period)), - node) - return ast.copy_location( - ast.Call(func=ast.Name("round64", ast.Load()), - args=[divided], - keywords=[], starargs=[], kwargs=[]), - divided) - - -def _time_to_cycles_opt(ref_period, node): - if (isinstance(node, ast.Call) - and isinstance(node.func, ast.Name) - and node.func.id == "cycles_to_time"): - return node.args[0] - else: - return _time_to_cycles(ref_period, node) - - -def _cycles_to_time(ref_period, node): - return ast.copy_location( - ast.BinOp(left=node, - op=ast.Mult(), - right=value_to_ast(ref_period)), - node) - - class _TimeLowerer(ast.NodeTransformer): - def __init__(self, ref_period): - self.ref_period = ref_period - def visit_Call(self, node): - # optimize time_to_cycles(now()) -> now - if (isinstance(node.func, ast.Name) - and node.func.id == "time_to_cycles" - and isinstance(node.args[0], ast.Call) - and isinstance(node.args[0].func, ast.Name) - and node.args[0].func.id == "now"): + if isinstance(node.func, ast.Name) and node.func.id == "now": return ast.copy_location(ast.Name("now", ast.Load()), node) - - self.generic_visit(node) - if isinstance(node.func, ast.Name): - funcname = node.func.id - if funcname == "now": - return _cycles_to_time( - self.ref_period, - ast.copy_location(ast.Name("now", ast.Load()), node)) - elif funcname == "time_to_cycles": - return _time_to_cycles(self.ref_period, node.args[0]) - elif funcname == "cycles_to_time": - return _cycles_to_time(self.ref_period, node.args[0]) - return node + else: + self.generic_visit(node) + return node def visit_Expr(self, node): r = node @@ -69,23 +34,19 @@ class _TimeLowerer(ast.NodeTransformer): r = ast.copy_location( ast.AugAssign(target=ast.Name("now", ast.Store()), op=ast.Add(), - value=_time_to_cycles_opt( - self.ref_period, - node.value.args[0])), + value=node.value.args[0]), node) elif funcname == "at": r = ast.copy_location( ast.Assign(targets=[ast.Name("now", ast.Store())], - value=_time_to_cycles_opt( - self.ref_period, - node.value.args[0])), + value=node.value.args[0]), node) self.generic_visit(r) return r -def lower_time(func_def, initial_time, ref_period): - _TimeLowerer(ref_period).visit(func_def) +def lower_time(func_def, initial_time): + _TimeLowerer().visit(func_def) func_def.body.insert(0, ast.copy_location( ast.Assign(targets=[ast.Name("now", ast.Store())], value=value_to_ast(int64(initial_time))), diff --git a/artiq/transforms/quantize_time.py b/artiq/transforms/quantize_time.py new file mode 100644 index 000000000..d545d0984 --- /dev/null +++ b/artiq/transforms/quantize_time.py @@ -0,0 +1,82 @@ +"""This transform turns calls to delay/now/at that use non-integer time +expressed in seconds into calls that use int64 time expressed in multiples of +ref_period. + +It does so by inserting multiplication/division/rounding operations around +those calls. + +The time_to_cycles and cycles_to_time core language functions are also +implemented here. + +""" + +import ast + +from artiq.transforms.tools import value_to_ast + + +def _call_now(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( + ast.BinOp(left=node, + op=ast.Div(), + right=value_to_ast(ref_period)), + node) + return ast.copy_location( + ast.Call(func=ast.Name("round64", ast.Load()), + args=[divided], + keywords=[], starargs=[], kwargs=[]), + divided) + + +def _cycles_to_time(ref_period, node): + return ast.copy_location( + ast.BinOp(left=node, + op=ast.Mult(), + right=value_to_ast(ref_period)), + node) + + +class _TimeQuantizer(ast.NodeTransformer): + def __init__(self, ref_period): + self.ref_period = ref_period + + def visit_Call(self, node): + funcname = node.func.id + if funcname == "now": + return _cycles_to_time(self.ref_period, _call_now(node)) + elif funcname == "delay" or funcname == "at": + if (isinstance(node.args[0], ast.Call) + and node.args[0].func.id == "cycles_to_time"): + # optimize: + # delay/at(cycles_to_time(x)) -> delay/at(x) + node.args[0] = self.visit(node.args[0].args[0]) + else: + node.args[0] = _time_to_cycles(self.ref_period, + self.visit(node.args[0])) + return node + elif funcname == "time_to_cycles": + if (isinstance(node.args[0], ast.Call) + 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])) + elif funcname == "cycles_to_time": + return _cycles_to_time(self.ref_period, + self.visit(node.args[0])) + else: + self.generic_visit(node) + return node + + +def quantize_time(func_def, ref_period): + _TimeQuantizer(ref_period).visit(func_def)