forked from M-Labs/artiq
Implement syscalls for the new compiler.
This commit is contained in:
parent
435559fe50
commit
c72267ecf5
@ -238,6 +238,10 @@ class Stitcher:
|
|||||||
name = function.__code__.co_name
|
name = function.__code__.co_name
|
||||||
|
|
||||||
source_line = linecache.getline(filename, line)
|
source_line = linecache.getline(filename, line)
|
||||||
|
while source_line.lstrip().startswith("@"):
|
||||||
|
line += 1
|
||||||
|
source_line = linecache.getline(filename, line)
|
||||||
|
|
||||||
column = re.search("def", source_line).start(0)
|
column = re.search("def", source_line).start(0)
|
||||||
source_buffer = source.Buffer(source_line, filename, line)
|
source_buffer = source.Buffer(source_line, filename, line)
|
||||||
return source.Range(source_buffer, column, column)
|
return source.Range(source_buffer, column, column)
|
||||||
@ -248,11 +252,16 @@ class Stitcher:
|
|||||||
{"function": function.__name__},
|
{"function": function.__name__},
|
||||||
self._function_loc(function))
|
self._function_loc(function))
|
||||||
|
|
||||||
def _extract_annot(self, function, annot, kind, call_loc):
|
def _extract_annot(self, function, annot, kind, call_loc, is_syscall):
|
||||||
if not isinstance(annot, types.Type):
|
if not isinstance(annot, types.Type):
|
||||||
note = diagnostic.Diagnostic("note",
|
if is_syscall:
|
||||||
"in function called remotely here", {},
|
note = diagnostic.Diagnostic("note",
|
||||||
call_loc)
|
"in system call here", {},
|
||||||
|
call_loc)
|
||||||
|
else:
|
||||||
|
note = diagnostic.Diagnostic("note",
|
||||||
|
"in function called remotely here", {},
|
||||||
|
call_loc)
|
||||||
diag = diagnostic.Diagnostic("error",
|
diag = diagnostic.Diagnostic("error",
|
||||||
"type annotation for {kind}, '{annot}', is not an ARTIQ type",
|
"type annotation for {kind}, '{annot}', is not an ARTIQ type",
|
||||||
{"kind": kind, "annot": repr(annot)},
|
{"kind": kind, "annot": repr(annot)},
|
||||||
@ -264,11 +273,19 @@ class Stitcher:
|
|||||||
else:
|
else:
|
||||||
return annot
|
return annot
|
||||||
|
|
||||||
def _type_of_param(self, function, loc, param):
|
def _type_of_param(self, function, loc, param, is_syscall):
|
||||||
if param.annotation is not inspect.Parameter.empty:
|
if param.annotation is not inspect.Parameter.empty:
|
||||||
# Type specified explicitly.
|
# Type specified explicitly.
|
||||||
return self._extract_annot(function, param.annotation,
|
return self._extract_annot(function, param.annotation,
|
||||||
"argument {}".format(param.name), loc)
|
"argument '{}'".format(param.name), loc,
|
||||||
|
is_syscall)
|
||||||
|
elif is_syscall:
|
||||||
|
# Syscalls must be entirely annotated.
|
||||||
|
diag = diagnostic.Diagnostic("error",
|
||||||
|
"system call argument '{argument}' must have a type annotation",
|
||||||
|
{"argument": param.name},
|
||||||
|
self._function_loc(function))
|
||||||
|
self.engine.process(diag)
|
||||||
elif param.default is not inspect.Parameter.empty:
|
elif param.default is not inspect.Parameter.empty:
|
||||||
# Try and infer the type from the default value.
|
# Try and infer the type from the default value.
|
||||||
# This is tricky, because the default value might not have
|
# This is tricky, because the default value might not have
|
||||||
@ -281,8 +298,8 @@ class Stitcher:
|
|||||||
def proxy_diagnostic(diag):
|
def proxy_diagnostic(diag):
|
||||||
note = diagnostic.Diagnostic("note",
|
note = diagnostic.Diagnostic("note",
|
||||||
"expanded from here while trying to infer a type for an"
|
"expanded from here while trying to infer a type for an"
|
||||||
" unannotated optional argument '{param_name}' from its default value",
|
" unannotated optional argument '{argument}' from its default value",
|
||||||
{"param_name": param.name},
|
{"argument": param.name},
|
||||||
loc)
|
loc)
|
||||||
diag.notes.append(note)
|
diag.notes.append(note)
|
||||||
|
|
||||||
@ -300,7 +317,7 @@ class Stitcher:
|
|||||||
# Let the rest of the program decide.
|
# Let the rest of the program decide.
|
||||||
return types.TVar()
|
return types.TVar()
|
||||||
|
|
||||||
def _quote_rpc_function(self, function, loc):
|
def _quote_foreign_function(self, function, loc, syscall):
|
||||||
signature = inspect.signature(function)
|
signature = inspect.signature(function)
|
||||||
|
|
||||||
arg_types = OrderedDict()
|
arg_types = OrderedDict()
|
||||||
@ -318,44 +335,73 @@ class Stitcher:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if param.default is inspect.Parameter.empty:
|
if param.default is inspect.Parameter.empty:
|
||||||
arg_types[param.name] = self._type_of_param(function, loc, param)
|
arg_types[param.name] = self._type_of_param(function, loc, param,
|
||||||
|
is_syscall=syscall is not None)
|
||||||
|
elif syscall is None:
|
||||||
|
optarg_types[param.name] = self._type_of_param(function, loc, param,
|
||||||
|
is_syscall=syscall is not None)
|
||||||
else:
|
else:
|
||||||
optarg_types[param.name] = self._type_of_param(function, loc, param)
|
diag = diagnostic.Diagnostic("error",
|
||||||
|
"system call argument '{argument}' must not have a default value",
|
||||||
|
{"argument": param.name},
|
||||||
|
self._function_loc(function))
|
||||||
|
self.engine.process(diag)
|
||||||
|
|
||||||
if signature.return_annotation is not inspect.Signature.empty:
|
if signature.return_annotation is not inspect.Signature.empty:
|
||||||
ret_type = self._extract_annot(function, signature.return_annotation,
|
ret_type = self._extract_annot(function, signature.return_annotation,
|
||||||
"return type", loc)
|
"return type", loc, is_syscall=syscall is not None)
|
||||||
else:
|
elif syscall is None:
|
||||||
diag = diagnostic.Diagnostic("fatal",
|
diag = diagnostic.Diagnostic("error",
|
||||||
"function must have a return type specified to be called remotely", {},
|
"function must have a return type annotation to be called remotely", {},
|
||||||
self._function_loc(function))
|
self._function_loc(function))
|
||||||
self.engine.process(diag)
|
self.engine.process(diag)
|
||||||
|
ret_type = types.TVar()
|
||||||
|
else: # syscall is not None
|
||||||
|
diag = diagnostic.Diagnostic("error",
|
||||||
|
"system call must have a return type annotation", {},
|
||||||
|
self._function_loc(function))
|
||||||
|
self.engine.process(diag)
|
||||||
|
ret_type = types.TVar()
|
||||||
|
|
||||||
rpc_type = types.TRPCFunction(arg_types, optarg_types, ret_type,
|
if syscall is None:
|
||||||
service=self._map(function))
|
function_type = types.TRPCFunction(arg_types, optarg_types, ret_type,
|
||||||
|
service=self._map(function))
|
||||||
|
function_name = "__rpc_{}__".format(function_type.service)
|
||||||
|
else:
|
||||||
|
function_type = types.TCFunction(arg_types, ret_type,
|
||||||
|
name=syscall)
|
||||||
|
function_name = "__ffi_{}__".format(function_type.name)
|
||||||
|
|
||||||
rpc_name = "__rpc_{}__".format(rpc_type.service)
|
self.globals[function_name] = function_type
|
||||||
self.globals[rpc_name] = rpc_type
|
self.functions[function] = function_name
|
||||||
self.functions[function] = rpc_name
|
|
||||||
|
|
||||||
return rpc_name
|
return function_name
|
||||||
|
|
||||||
def _quote_function(self, function, loc):
|
def _quote_function(self, function, loc):
|
||||||
if function in self.functions:
|
if function in self.functions:
|
||||||
return self.functions[function]
|
return self.functions[function]
|
||||||
|
|
||||||
if hasattr(function, "artiq_embedded"):
|
if hasattr(function, "artiq_embedded"):
|
||||||
# Insert the typed AST for the new function and restart inference.
|
if function.artiq_embedded.function is not None:
|
||||||
# It doesn't really matter where we insert as long as it is before
|
# Insert the typed AST for the new function and restart inference.
|
||||||
# the final call.
|
# It doesn't really matter where we insert as long as it is before
|
||||||
function_node = self._quote_embedded_function(function)
|
# the final call.
|
||||||
self.typedtree.insert(0, function_node)
|
function_node = self._quote_embedded_function(function)
|
||||||
self.inference_finished = False
|
self.typedtree.insert(0, function_node)
|
||||||
return function_node.name
|
self.inference_finished = False
|
||||||
|
return function_node.name
|
||||||
|
elif function.artiq_embedded.syscall is not None:
|
||||||
|
# Insert a storage-less global whose type instructs the compiler
|
||||||
|
# to perform a system call instead of a regular call.
|
||||||
|
return self._quote_foreign_function(function, loc,
|
||||||
|
syscall=function.artiq_embedded.syscall)
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
else:
|
else:
|
||||||
# Insert a storage-less global whose type instructs the compiler
|
# Insert a storage-less global whose type instructs the compiler
|
||||||
# to perform an RPC instead of a regular call.
|
# to perform an RPC instead of a regular call.
|
||||||
return self._quote_rpc_function(function, loc)
|
return self._quote_foreign_function(function, loc,
|
||||||
|
syscall=None)
|
||||||
|
|
||||||
def stitch_call(self, function, args, kwargs):
|
def stitch_call(self, function, args, kwargs):
|
||||||
function_node = self._quote_embedded_function(function)
|
function_node = self._quote_embedded_function(function)
|
||||||
|
@ -175,7 +175,7 @@ class LLVMIRGenerator:
|
|||||||
typ = typ.find()
|
typ = typ.find()
|
||||||
if types.is_tuple(typ):
|
if types.is_tuple(typ):
|
||||||
return ll.LiteralStructType([self.llty_of_type(eltty) for eltty in typ.elts])
|
return ll.LiteralStructType([self.llty_of_type(eltty) for eltty in typ.elts])
|
||||||
elif types.is_rpc_function(typ):
|
elif types.is_rpc_function(typ) or types.is_c_function(typ):
|
||||||
if for_return:
|
if for_return:
|
||||||
return llvoid
|
return llvoid
|
||||||
else:
|
else:
|
||||||
@ -731,11 +731,20 @@ class LLVMIRGenerator:
|
|||||||
return llvalue
|
return llvalue
|
||||||
|
|
||||||
def _prepare_closure_call(self, insn):
|
def _prepare_closure_call(self, insn):
|
||||||
llclosure, llargs = self.map(insn.target_function()), map(self.map, insn.arguments())
|
llclosure = self.map(insn.target_function())
|
||||||
llenv = self.llbuilder.extract_value(llclosure, 0)
|
llargs = [self.map(arg) for arg in insn.arguments()]
|
||||||
llfun = self.llbuilder.extract_value(llclosure, 1)
|
llenv = self.llbuilder.extract_value(llclosure, 0)
|
||||||
|
llfun = self.llbuilder.extract_value(llclosure, 1)
|
||||||
return llfun, [llenv] + list(llargs)
|
return llfun, [llenv] + list(llargs)
|
||||||
|
|
||||||
|
def _prepare_ffi_call(self, insn):
|
||||||
|
llargs = [self.map(arg) for arg in insn.arguments()]
|
||||||
|
llfunty = ll.FunctionType(self.llty_of_type(insn.type, for_return=True),
|
||||||
|
[llarg.type for llarg in llargs])
|
||||||
|
llfun = ll.Function(self.llmodule, llfunty,
|
||||||
|
insn.target_function().type.name)
|
||||||
|
return llfun, list(llargs)
|
||||||
|
|
||||||
# See session.c:{send,receive}_rpc_value and comm_generic.py:_{send,receive}_rpc_value.
|
# See session.c:{send,receive}_rpc_value and comm_generic.py:_{send,receive}_rpc_value.
|
||||||
def _rpc_tag(self, typ, error_handler):
|
def _rpc_tag(self, typ, error_handler):
|
||||||
if types.is_tuple(typ):
|
if types.is_tuple(typ):
|
||||||
@ -869,6 +878,10 @@ class LLVMIRGenerator:
|
|||||||
insn.target_function().type,
|
insn.target_function().type,
|
||||||
insn.arguments(),
|
insn.arguments(),
|
||||||
llnormalblock=None, llunwindblock=None)
|
llnormalblock=None, llunwindblock=None)
|
||||||
|
elif types.is_c_function(insn.target_function().type):
|
||||||
|
llfun, llargs = self._prepare_ffi_call(insn)
|
||||||
|
return self.llbuilder.call(llfun, llargs,
|
||||||
|
name=insn.name)
|
||||||
else:
|
else:
|
||||||
llfun, llargs = self._prepare_closure_call(insn)
|
llfun, llargs = self._prepare_closure_call(insn)
|
||||||
return self.llbuilder.call(llfun, llargs,
|
return self.llbuilder.call(llfun, llargs,
|
||||||
@ -882,6 +895,10 @@ class LLVMIRGenerator:
|
|||||||
insn.target_function().type,
|
insn.target_function().type,
|
||||||
insn.arguments(),
|
insn.arguments(),
|
||||||
llnormalblock, llunwindblock)
|
llnormalblock, llunwindblock)
|
||||||
|
elif types.is_c_function(insn.target_function().type):
|
||||||
|
llfun, llargs = self._prepare_ffi_call(insn)
|
||||||
|
return self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock,
|
||||||
|
name=insn.name)
|
||||||
else:
|
else:
|
||||||
llfun, llargs = self._prepare_closure_call(insn)
|
llfun, llargs = self._prepare_closure_call(insn)
|
||||||
return self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock,
|
return self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock,
|
||||||
|
@ -258,6 +258,26 @@ class TRPCFunction(TFunction):
|
|||||||
else:
|
else:
|
||||||
raise UnificationError(self, other)
|
raise UnificationError(self, other)
|
||||||
|
|
||||||
|
class TCFunction(TFunction):
|
||||||
|
"""
|
||||||
|
A function type of a runtime-provided C function.
|
||||||
|
|
||||||
|
:ivar name: (str) C function name
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, args, ret, name):
|
||||||
|
super().__init__(args, OrderedDict(), ret)
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def unify(self, other):
|
||||||
|
if isinstance(other, TCFunction) and \
|
||||||
|
self.name == other.name:
|
||||||
|
super().unify(other)
|
||||||
|
elif isinstance(other, TVar):
|
||||||
|
other.unify(self)
|
||||||
|
else:
|
||||||
|
raise UnificationError(self, other)
|
||||||
|
|
||||||
class TBuiltin(Type):
|
class TBuiltin(Type):
|
||||||
"""
|
"""
|
||||||
An instance of builtin type. Every instance of a builtin
|
An instance of builtin type. Every instance of a builtin
|
||||||
@ -371,6 +391,9 @@ def is_function(typ):
|
|||||||
def is_rpc_function(typ):
|
def is_rpc_function(typ):
|
||||||
return isinstance(typ.find(), TRPCFunction)
|
return isinstance(typ.find(), TRPCFunction)
|
||||||
|
|
||||||
|
def is_c_function(typ):
|
||||||
|
return isinstance(typ.find(), TCFunction)
|
||||||
|
|
||||||
def is_builtin(typ, name=None):
|
def is_builtin(typ, name=None):
|
||||||
typ = typ.find()
|
typ = typ.find()
|
||||||
if name is None:
|
if name is None:
|
||||||
@ -423,7 +446,7 @@ class TypePrinter(object):
|
|||||||
return "(%s,)" % self.name(typ.elts[0])
|
return "(%s,)" % self.name(typ.elts[0])
|
||||||
else:
|
else:
|
||||||
return "(%s)" % ", ".join(list(map(self.name, typ.elts)))
|
return "(%s)" % ", ".join(list(map(self.name, typ.elts)))
|
||||||
elif isinstance(typ, (TFunction, TRPCFunction)):
|
elif isinstance(typ, (TFunction, TRPCFunction, TCFunction)):
|
||||||
args = []
|
args = []
|
||||||
args += [ "%s:%s" % (arg, self.name(typ.args[arg])) for arg in typ.args]
|
args += [ "%s:%s" % (arg, self.name(typ.args[arg])) for arg in typ.args]
|
||||||
args += ["?%s:%s" % (arg, self.name(typ.optargs[arg])) for arg in typ.optargs]
|
args += ["?%s:%s" % (arg, self.name(typ.optargs[arg])) for arg in typ.optargs]
|
||||||
@ -431,6 +454,8 @@ class TypePrinter(object):
|
|||||||
|
|
||||||
if isinstance(typ, TRPCFunction):
|
if isinstance(typ, TRPCFunction):
|
||||||
return "rpc({}) {}".format(typ.service, signature)
|
return "rpc({}) {}".format(typ.service, signature)
|
||||||
|
if isinstance(typ, TCFunction):
|
||||||
|
return "ffi({}) {}".format(repr(typ.name), signature)
|
||||||
elif isinstance(typ, TFunction):
|
elif isinstance(typ, TFunction):
|
||||||
return signature
|
return signature
|
||||||
elif isinstance(typ, TBuiltinFunction):
|
elif isinstance(typ, TBuiltinFunction):
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import sys, tempfile
|
import sys
|
||||||
|
|
||||||
from pythonparser import diagnostic
|
from pythonparser import diagnostic
|
||||||
|
|
||||||
from artiq.language.core import *
|
from artiq.language.core import *
|
||||||
from artiq.language.units import ns
|
from artiq.language.types import *
|
||||||
|
|
||||||
from artiq.compiler import Stitcher, Module
|
from artiq.compiler import Stitcher, Module
|
||||||
from artiq.compiler.targets import OR1KTarget
|
from artiq.compiler.targets import OR1KTarget
|
||||||
@ -15,6 +15,11 @@ from artiq.coredevice import exceptions
|
|||||||
class CompileError(Exception):
|
class CompileError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@syscall
|
||||||
|
def rtio_get_counter() -> TInt64:
|
||||||
|
raise NotImplementedError("syscall not simulated")
|
||||||
|
|
||||||
class Core:
|
class Core:
|
||||||
def __init__(self, dmgr, ref_period=8*ns, external_clock=False):
|
def __init__(self, dmgr, ref_period=8*ns, external_clock=False):
|
||||||
self.comm = dmgr.get("comm")
|
self.comm = dmgr.get("comm")
|
||||||
@ -58,8 +63,8 @@ class Core:
|
|||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def get_rtio_counter_mu(self):
|
def get_rtio_counter_mu(self):
|
||||||
return syscall("rtio_get_counter")
|
return rtio_get_counter()
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def break_realtime(self):
|
def break_realtime(self):
|
||||||
at_mu(syscall("rtio_get_counter") + 125000)
|
at_mu(rtio_get_counter() + 125000)
|
||||||
|
@ -9,6 +9,24 @@ PHASE_MODE_ABSOLUTE = 1
|
|||||||
PHASE_MODE_TRACKING = 2
|
PHASE_MODE_TRACKING = 2
|
||||||
|
|
||||||
|
|
||||||
|
@syscall
|
||||||
|
def dds_init(time_mu: TInt64, channel: TInt32) -> TNone:
|
||||||
|
raise NotImplementedError("syscall not simulated")
|
||||||
|
|
||||||
|
@syscall
|
||||||
|
def dds_batch_enter(time_mu: TInt64) -> TNone:
|
||||||
|
raise NotImplementedError("syscall not simulated")
|
||||||
|
|
||||||
|
@syscall
|
||||||
|
def dds_batch_exit() -> TNone:
|
||||||
|
raise NotImplementedError("syscall not simulated")
|
||||||
|
|
||||||
|
@syscall
|
||||||
|
def dds_set(time_mu: TInt64, channel: TInt32, ftw: TInt32,
|
||||||
|
pow: TInt32, phase_mode: TInt32) -> TNone:
|
||||||
|
raise NotImplementedError("syscall not simulated")
|
||||||
|
|
||||||
|
|
||||||
class _BatchContextManager:
|
class _BatchContextManager:
|
||||||
def __init__(self, dds_bus):
|
def __init__(self, dds_bus):
|
||||||
self.dds_bus = dds_bus
|
self.dds_bus = dds_bus
|
||||||
@ -34,13 +52,13 @@ class DDSBus:
|
|||||||
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", now_mu())
|
dds_batch_enter(now_mu())
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def batch_exit(self):
|
def batch_exit(self):
|
||||||
"""Ends a DDS command batch. All buffered DDS commands are issued
|
"""Ends a DDS command batch. All buffered DDS commands are issued
|
||||||
on the bus, and FUD is pulsed at the time the batch started."""
|
on the bus, and FUD is pulsed at the time the batch started."""
|
||||||
syscall("dds_batch_exit")
|
dds_batch_exit()
|
||||||
|
|
||||||
|
|
||||||
class _DDSGeneric:
|
class _DDSGeneric:
|
||||||
@ -91,7 +109,7 @@ class _DDSGeneric:
|
|||||||
"""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", now_mu(), self.channel)
|
dds_init(now_mu(), self.channel)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_phase_mode(self, phase_mode):
|
def set_phase_mode(self, phase_mode):
|
||||||
@ -128,7 +146,7 @@ class _DDSGeneric:
|
|||||||
"""
|
"""
|
||||||
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,
|
dds_set(now_mu(), self.channel,
|
||||||
frequency, round(phase*2**self.pow_width), phase_mode)
|
frequency, round(phase*2**self.pow_width), phase_mode)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
|
@ -1,134 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
import llvmlite_or1k.ir as ll
|
|
||||||
import llvmlite_or1k.binding as llvm
|
|
||||||
|
|
||||||
from artiq.language import units
|
|
||||||
|
|
||||||
_syscalls = {
|
|
||||||
"now_init": "n:I",
|
|
||||||
"now_save": "I:n",
|
|
||||||
"watchdog_set": "i:i",
|
|
||||||
"watchdog_clear": "i:n",
|
|
||||||
"rtio_get_counter": "n:I",
|
|
||||||
"ttl_set_o": "Iib:n",
|
|
||||||
"ttl_set_oe": "Iib:n",
|
|
||||||
"ttl_set_sensitivity": "Iii:n",
|
|
||||||
"ttl_get": "iI:I",
|
|
||||||
"ttl_clock_set": "Iii:n",
|
|
||||||
"dds_init": "Ii:n",
|
|
||||||
"dds_batch_enter": "I:n",
|
|
||||||
"dds_batch_exit": "n:n",
|
|
||||||
"dds_set": "Iiiii:n",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _chr_to_type(c):
|
|
||||||
if c == "n":
|
|
||||||
return ll.VoidType()
|
|
||||||
if c == "b":
|
|
||||||
return ll.IntType(1)
|
|
||||||
if c == "i":
|
|
||||||
return ll.IntType(32)
|
|
||||||
if c == "I":
|
|
||||||
return ll.IntType(64)
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
|
|
||||||
def _str_to_functype(s):
|
|
||||||
assert(s[-2] == ":")
|
|
||||||
type_ret = _chr_to_type(s[-1])
|
|
||||||
type_args = [_chr_to_type(c) for c in s[:-2] if c != "n"]
|
|
||||||
return ll.FunctionType(type_ret, type_args)
|
|
||||||
|
|
||||||
|
|
||||||
def _chr_to_value(c):
|
|
||||||
if c == "n":
|
|
||||||
return base_types.VNone()
|
|
||||||
if c == "b":
|
|
||||||
return base_types.VBool()
|
|
||||||
if c == "i":
|
|
||||||
return base_types.VInt()
|
|
||||||
if c == "I":
|
|
||||||
return base_types.VInt(64)
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
|
|
||||||
def _value_to_str(v):
|
|
||||||
if isinstance(v, base_types.VNone):
|
|
||||||
return "n"
|
|
||||||
if isinstance(v, base_types.VBool):
|
|
||||||
return "b"
|
|
||||||
if isinstance(v, base_types.VInt):
|
|
||||||
if v.nbits == 32:
|
|
||||||
return "i"
|
|
||||||
if v.nbits == 64:
|
|
||||||
return "I"
|
|
||||||
raise ValueError
|
|
||||||
if isinstance(v, base_types.VFloat):
|
|
||||||
return "f"
|
|
||||||
if isinstance(v, fractions.VFraction):
|
|
||||||
return "F"
|
|
||||||
if isinstance(v, lists.VList):
|
|
||||||
return "l" + _value_to_str(v.el_type)
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
|
|
||||||
class LinkInterface:
|
|
||||||
def init_module(self, module):
|
|
||||||
self.module = module
|
|
||||||
llvm_module = self.module.llvm_module
|
|
||||||
|
|
||||||
# RPC
|
|
||||||
func_type = ll.FunctionType(ll.IntType(32), [ll.IntType(32)],
|
|
||||||
var_arg=1)
|
|
||||||
self.rpc = ll.Function(llvm_module, func_type, "__syscall_rpc")
|
|
||||||
|
|
||||||
# syscalls
|
|
||||||
self.syscalls = dict()
|
|
||||||
for func_name, func_type_str in _syscalls.items():
|
|
||||||
func_type = _str_to_functype(func_type_str)
|
|
||||||
self.syscalls[func_name] = ll.Function(
|
|
||||||
llvm_module, func_type, "__syscall_" + func_name)
|
|
||||||
|
|
||||||
def _build_rpc(self, args, builder):
|
|
||||||
r = base_types.VInt()
|
|
||||||
if builder is not None:
|
|
||||||
new_args = []
|
|
||||||
new_args.append(args[0].auto_load(builder)) # RPC number
|
|
||||||
for arg in args[1:]:
|
|
||||||
# type tag
|
|
||||||
arg_type_str = _value_to_str(arg)
|
|
||||||
arg_type_int = 0
|
|
||||||
for c in reversed(arg_type_str):
|
|
||||||
arg_type_int <<= 8
|
|
||||||
arg_type_int |= ord(c)
|
|
||||||
new_args.append(ll.Constant(ll.IntType(32), arg_type_int))
|
|
||||||
|
|
||||||
# pointer to value
|
|
||||||
if not isinstance(arg, base_types.VNone):
|
|
||||||
if isinstance(arg.llvm_value.type, ll.PointerType):
|
|
||||||
new_args.append(arg.llvm_value)
|
|
||||||
else:
|
|
||||||
arg_ptr = arg.new()
|
|
||||||
arg_ptr.alloca(builder)
|
|
||||||
arg_ptr.auto_store(builder, arg.llvm_value)
|
|
||||||
new_args.append(arg_ptr.llvm_value)
|
|
||||||
# end marker
|
|
||||||
new_args.append(ll.Constant(ll.IntType(32), 0))
|
|
||||||
r.auto_store(builder, builder.call(self.rpc, new_args))
|
|
||||||
return r
|
|
||||||
|
|
||||||
def _build_regular_syscall(self, syscall_name, args, builder):
|
|
||||||
r = _chr_to_value(_syscalls[syscall_name][-1])
|
|
||||||
if builder is not None:
|
|
||||||
args = [arg.auto_load(builder) for arg in args]
|
|
||||||
r.auto_store(builder, builder.call(self.syscalls[syscall_name],
|
|
||||||
args))
|
|
||||||
return r
|
|
||||||
|
|
||||||
def build_syscall(self, syscall_name, args, builder):
|
|
||||||
if syscall_name == "rpc":
|
|
||||||
return self._build_rpc(args, builder)
|
|
||||||
else:
|
|
||||||
return self._build_regular_syscall(syscall_name, args, builder)
|
|
@ -1,4 +1,26 @@
|
|||||||
from artiq.language.core import *
|
from artiq.language.core import *
|
||||||
|
from artiq.language.types import *
|
||||||
|
|
||||||
|
|
||||||
|
@syscall
|
||||||
|
def ttl_set_o(time_mu: TInt64, channel: TInt32, enabled: TBool) -> TNone:
|
||||||
|
raise NotImplementedError("syscall not simulated")
|
||||||
|
|
||||||
|
@syscall
|
||||||
|
def ttl_set_oe(time_mu: TInt64, channel: TInt32, enabled: TBool) -> TNone:
|
||||||
|
raise NotImplementedError("syscall not simulated")
|
||||||
|
|
||||||
|
@syscall
|
||||||
|
def ttl_set_sensitivity(time_mu: TInt64, channel: TInt32, sensitivity: TInt32) -> TNone:
|
||||||
|
raise NotImplementedError("syscall not simulated")
|
||||||
|
|
||||||
|
@syscall
|
||||||
|
def ttl_get(channel: TInt32, time_limit_mu: TInt64) -> TInt64:
|
||||||
|
raise NotImplementedError("syscall not simulated")
|
||||||
|
|
||||||
|
@syscall
|
||||||
|
def ttl_clock_set(time_mu: TInt64, channel: TInt32, ftw: TInt32) -> TNone:
|
||||||
|
raise NotImplementedError("syscall not simulated")
|
||||||
|
|
||||||
|
|
||||||
class TTLOut:
|
class TTLOut:
|
||||||
@ -18,14 +40,14 @@ class TTLOut:
|
|||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_o(self, o):
|
def set_o(self, o):
|
||||||
syscall("ttl_set_o", now_mu(), self.channel, o)
|
ttl_set_o(now_mu(), self.channel, o)
|
||||||
self.o_previous_timestamp = now_mu()
|
self.o_previous_timestamp = now_mu()
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def sync(self):
|
def sync(self):
|
||||||
"""Busy-wait until all programmed level switches have been
|
"""Busy-wait until all programmed level switches have been
|
||||||
effected."""
|
effected."""
|
||||||
while syscall("rtio_get_counter") < self.o_previous_timestamp:
|
while self.core.get_rtio_counter_mu() < self.o_previous_timestamp:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
@ -83,7 +105,7 @@ class TTLInOut:
|
|||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_oe(self, oe):
|
def set_oe(self, oe):
|
||||||
syscall("ttl_set_oe", now_mu(), self.channel, oe)
|
ttl_set_oe(now_mu(), self.channel, oe)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def output(self):
|
def output(self):
|
||||||
@ -95,14 +117,14 @@ class TTLInOut:
|
|||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_o(self, o):
|
def set_o(self, o):
|
||||||
syscall("ttl_set_o", now_mu(), self.channel, o)
|
ttl_set_o(now_mu(), self.channel, o)
|
||||||
self.o_previous_timestamp = now_mu()
|
self.o_previous_timestamp = now_mu()
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def sync(self):
|
def sync(self):
|
||||||
"""Busy-wait until all programmed level switches have been
|
"""Busy-wait until all programmed level switches have been
|
||||||
effected."""
|
effected."""
|
||||||
while syscall("rtio_get_counter") < self.o_previous_timestamp:
|
while self.core.get_rtio_counter_mu() < self.o_previous_timestamp:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
@ -133,7 +155,7 @@ class TTLInOut:
|
|||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def _set_sensitivity(self, value):
|
def _set_sensitivity(self, value):
|
||||||
syscall("ttl_set_sensitivity", now_mu(), self.channel, value)
|
ttl_set_sensitivity(now_mu(), self.channel, value)
|
||||||
self.i_previous_timestamp = now_mu()
|
self.i_previous_timestamp = now_mu()
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
@ -189,8 +211,7 @@ class TTLInOut:
|
|||||||
"""Poll the RTIO input during all the previously programmed gate
|
"""Poll the RTIO input during all the previously programmed gate
|
||||||
openings, and returns the number of registered events."""
|
openings, and returns the number of registered events."""
|
||||||
count = 0
|
count = 0
|
||||||
while syscall("ttl_get", self.channel,
|
while ttl_get(self.channel, self.i_previous_timestamp) >= 0:
|
||||||
self.i_previous_timestamp) >= 0:
|
|
||||||
count += 1
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
@ -201,7 +222,7 @@ class TTLInOut:
|
|||||||
|
|
||||||
If the gate is permanently closed, returns a negative value.
|
If the gate is permanently closed, returns a negative value.
|
||||||
"""
|
"""
|
||||||
return syscall("ttl_get", self.channel, self.i_previous_timestamp)
|
return ttl_get(self.channel, self.i_previous_timestamp)
|
||||||
|
|
||||||
|
|
||||||
class TTLClockGen:
|
class TTLClockGen:
|
||||||
@ -254,7 +275,7 @@ class TTLClockGen:
|
|||||||
that are not powers of two cause jitter of one RTIO clock cycle at the
|
that are not powers of two cause jitter of one RTIO clock cycle at the
|
||||||
output.
|
output.
|
||||||
"""
|
"""
|
||||||
syscall("ttl_clock_set", now_mu(), self.channel, frequency)
|
ttl_clock_set(now_mu(), self.channel, frequency)
|
||||||
self.previous_timestamp = now_mu()
|
self.previous_timestamp = now_mu()
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
@ -271,5 +292,5 @@ class TTLClockGen:
|
|||||||
def sync(self):
|
def sync(self):
|
||||||
"""Busy-wait until all programmed frequency switches and stops have
|
"""Busy-wait until all programmed frequency switches and stops have
|
||||||
been effected."""
|
been effected."""
|
||||||
while syscall("rtio_get_counter") < self.o_previous_timestamp:
|
while self.core.get_rtio_counter_mu() < self.o_previous_timestamp:
|
||||||
pass
|
pass
|
||||||
|
@ -7,15 +7,19 @@ from collections import namedtuple
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["int64", "round64", "kernel", "portable",
|
|
||||||
"set_time_manager", "set_syscall_manager", "set_watchdog_factory",
|
__all__ = ["int64", "round64",
|
||||||
|
"kernel", "portable", "syscall",
|
||||||
|
"set_time_manager", "set_watchdog_factory",
|
||||||
"ARTIQException"]
|
"ARTIQException"]
|
||||||
|
|
||||||
# global namespace for kernels
|
# global namespace for kernels
|
||||||
kernel_globals = ("sequential", "parallel",
|
kernel_globals = (
|
||||||
|
"sequential", "parallel",
|
||||||
"delay_mu", "now_mu", "at_mu", "delay",
|
"delay_mu", "now_mu", "at_mu", "delay",
|
||||||
"seconds_to_mu", "mu_to_seconds",
|
"seconds_to_mu", "mu_to_seconds",
|
||||||
"syscall", "watchdog")
|
"watchdog"
|
||||||
|
)
|
||||||
__all__.extend(kernel_globals)
|
__all__.extend(kernel_globals)
|
||||||
|
|
||||||
|
|
||||||
@ -78,11 +82,12 @@ def round64(x):
|
|||||||
return int64(round(x))
|
return int64(round(x))
|
||||||
|
|
||||||
|
|
||||||
_ARTIQEmbeddedInfo = namedtuple("_ARTIQEmbeddedInfo", "core_name function")
|
_ARTIQEmbeddedInfo = namedtuple("_ARTIQEmbeddedInfo",
|
||||||
|
"core_name function syscall")
|
||||||
|
|
||||||
def kernel(arg):
|
def kernel(arg):
|
||||||
"""This decorator marks an object's method for execution on the core
|
"""
|
||||||
|
This decorator marks an object's method for execution on the core
|
||||||
device.
|
device.
|
||||||
|
|
||||||
When a decorated method is called from the Python interpreter, the ``core``
|
When a decorated method is called from the Python interpreter, the ``core``
|
||||||
@ -106,15 +111,15 @@ def kernel(arg):
|
|||||||
def run_on_core(self, *k_args, **k_kwargs):
|
def run_on_core(self, *k_args, **k_kwargs):
|
||||||
return getattr(self, arg).run(function, ((self,) + k_args), k_kwargs)
|
return getattr(self, arg).run(function, ((self,) + k_args), k_kwargs)
|
||||||
run_on_core.artiq_embedded = _ARTIQEmbeddedInfo(
|
run_on_core.artiq_embedded = _ARTIQEmbeddedInfo(
|
||||||
core_name=arg, function=function)
|
core_name=arg, function=function, syscall=None)
|
||||||
return run_on_core
|
return run_on_core
|
||||||
return inner_decorator
|
return inner_decorator
|
||||||
else:
|
else:
|
||||||
return kernel("core")(arg)
|
return kernel("core")(arg)
|
||||||
|
|
||||||
|
|
||||||
def portable(function):
|
def portable(function):
|
||||||
"""This decorator marks a function for execution on the same device as its
|
"""
|
||||||
|
This decorator marks a function for execution on the same device as its
|
||||||
caller.
|
caller.
|
||||||
|
|
||||||
In other words, a decorated function called from the interpreter on the
|
In other words, a decorated function called from the interpreter on the
|
||||||
@ -122,9 +127,31 @@ def portable(function):
|
|||||||
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).
|
||||||
"""
|
"""
|
||||||
function.artiq_embedded = _ARTIQEmbeddedInfo(core_name="", function=function)
|
function.artiq_embedded = \
|
||||||
|
_ARTIQEmbeddedInfo(core_name=None, function=function, syscall=None)
|
||||||
return function
|
return function
|
||||||
|
|
||||||
|
def syscall(arg):
|
||||||
|
"""
|
||||||
|
This decorator marks a function as a system call. When executed on a core
|
||||||
|
device, a C function with the provided name (or the same name as
|
||||||
|
the Python function, if not provided) will be called. When executed on
|
||||||
|
host, the Python function will be called as usual.
|
||||||
|
|
||||||
|
Every argument and the return value must be annotated with ARTIQ types.
|
||||||
|
|
||||||
|
Only drivers should normally define syscalls.
|
||||||
|
"""
|
||||||
|
if isinstance(arg, str):
|
||||||
|
def inner_decorator(function):
|
||||||
|
function.artiq_embedded = \
|
||||||
|
_ARTIQEmbeddedInfo(core_name=None, function=None,
|
||||||
|
syscall=function.__name__)
|
||||||
|
return function
|
||||||
|
return inner_decorator
|
||||||
|
else:
|
||||||
|
return syscall(arg.__name__)(arg)
|
||||||
|
|
||||||
|
|
||||||
class _DummyTimeManager:
|
class _DummyTimeManager:
|
||||||
def _not_implemented(self, *args, **kwargs):
|
def _not_implemented(self, *args, **kwargs):
|
||||||
@ -152,22 +179,6 @@ def set_time_manager(time_manager):
|
|||||||
_time_manager = time_manager
|
_time_manager = time_manager
|
||||||
|
|
||||||
|
|
||||||
class _DummySyscallManager:
|
|
||||||
def do(self, *args):
|
|
||||||
raise NotImplementedError(
|
|
||||||
"Attempted to interpret kernel without a syscall manager")
|
|
||||||
|
|
||||||
_syscall_manager = _DummySyscallManager()
|
|
||||||
|
|
||||||
|
|
||||||
def set_syscall_manager(syscall_manager):
|
|
||||||
"""Set the system call manager used for simulating the core device's
|
|
||||||
runtime in the Python interpreter.
|
|
||||||
"""
|
|
||||||
global _syscall_manager
|
|
||||||
_syscall_manager = syscall_manager
|
|
||||||
|
|
||||||
|
|
||||||
class _Sequential:
|
class _Sequential:
|
||||||
"""In a sequential block, statements are executed one after another, with
|
"""In a sequential block, statements are executed one after another, with
|
||||||
the time increasing as one moves down the statement list."""
|
the time increasing as one moves down the statement list."""
|
||||||
@ -240,17 +251,6 @@ def mu_to_seconds(mu, core=None):
|
|||||||
return mu*core.ref_period
|
return mu*core.ref_period
|
||||||
|
|
||||||
|
|
||||||
def syscall(*args):
|
|
||||||
"""Invokes a service of the runtime.
|
|
||||||
|
|
||||||
Kernels use this function to interface to the outside world: program RTIO
|
|
||||||
events, make RPCs, etc.
|
|
||||||
|
|
||||||
Only drivers should normally use ``syscall``.
|
|
||||||
"""
|
|
||||||
return _syscall_manager.do(*args)
|
|
||||||
|
|
||||||
|
|
||||||
class _DummyWatchdog:
|
class _DummyWatchdog:
|
||||||
def __init__(self, timeout):
|
def __init__(self, timeout):
|
||||||
pass
|
pass
|
||||||
|
Loading…
Reference in New Issue
Block a user