-
Notifications
You must be signed in to change notification settings - Fork 189
/
core.py
270 lines (223 loc) · 9.21 KB
/
core.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
import os, sys, tempfile, subprocess, io
from numpy import int32, int64
import nac3artiq
from artiq.language.core import *
from artiq.language.core import _ConstGenericMarker
from artiq.language import core as core_language
from artiq.language.units import *
from artiq.language.embedding_map import EmbeddingMap
from artiq.coredevice.comm_kernel import CommKernel, CommKernelDummy
@extern
def rtio_init():
raise NotImplementedError("syscall not simulated")
@extern
def rtio_get_destination_status(destination: int32) -> bool:
raise NotImplementedError("syscall not simulated")
@extern
def rtio_get_counter() -> int64:
raise NotImplementedError("syscall not simulated")
artiq_builtins = {
"none": none,
"virtual": virtual,
"_ConstGenericMarker": _ConstGenericMarker,
"Option": Option,
}
@nac3
class Core:
"""Core device driver.
:param host: hostname or IP address of the core device.
:param ref_period: period of the reference clock for the RTIO subsystem.
On platforms that use clock multiplication and SERDES-based PHYs,
this is the period after multiplication. For example, with a RTIO core
clocked at 125MHz and a SERDES multiplication factor of 8, the
reference period is 1ns.
The time machine unit is equal to this period.
:param ref_multiplier: ratio between the RTIO fine timestamp frequency
and the RTIO coarse timestamp frequency (e.g. SERDES multiplication
factor).
"""
ref_period: KernelInvariant[float]
ref_multiplier: KernelInvariant[int32]
coarse_ref_period: KernelInvariant[float]
def __init__(self, dmgr, host, ref_period, ref_multiplier=8, target="rv32g"):
self.ref_period = ref_period
self.ref_multiplier = ref_multiplier
self.coarse_ref_period = ref_period*ref_multiplier
if host is None:
self.comm = CommKernelDummy()
else:
self.comm = CommKernel(host)
self.first_run = True
self.dmgr = dmgr
self.core = self
self.comm.core = self
self.target = target
self.analyzed = False
self.compiler = nac3artiq.NAC3(target, artiq_builtins)
def close(self):
self.comm.close()
def compile(self, method, args, kwargs, embedding_map, file_output=None, target=None):
if target is not None:
# NAC3TODO: subkernels
raise NotImplementedError
if not self.analyzed:
self.compiler.analyze(core_language._registered_functions, core_language._registered_classes)
self.analyzed = True
if hasattr(method, "__self__"):
obj = method.__self__
name = method.__name__
else:
obj = method
name = ""
if file_output is None:
return self.compiler.compile_method_to_mem(obj, name, args, embedding_map)
else:
self.compiler.compile_method_to_file(obj, name, args, file_output, embedding_map)
def run(self, function, args, kwargs):
embedding_map = EmbeddingMap()
kernel_library = self.compile(function, args, kwargs, embedding_map)
if self.first_run:
self.comm.check_system_info()
self.first_run = False
symbolizer = lambda addresses: symbolize(kernel_library, addresses)
self.comm.load(kernel_library)
self.comm.run()
self.comm.serve(embedding_map, symbolizer)
@portable
def seconds_to_mu(self, seconds: float) -> int64:
"""Convert seconds to the corresponding number of machine units
(RTIO cycles).
:param seconds: time (in seconds) to convert.
"""
return int64(seconds//self.ref_period)
@portable
def mu_to_seconds(self, mu: int64) -> float:
"""Convert machine units (RTIO cycles) to seconds.
:param mu: cycle count to convert.
"""
return float(mu)*self.ref_period
@kernel
def delay(self, dt: float):
delay_mu(self.seconds_to_mu(dt))
@kernel
def get_rtio_counter_mu(self) -> int64:
"""Retrieve the current value of the hardware RTIO timeline counter.
As the timing of kernel code executed on the CPU is inherently
non-deterministic, the return value is by necessity only a lower bound
for the actual value of the hardware register at the instant when
execution resumes in the caller.
For a more detailed description of these concepts, see :doc:`/rtio`.
"""
return rtio_get_counter()
@kernel
def wait_until_mu(self, cursor_mu: int64):
"""Block execution until the hardware RTIO counter reaches the given
value (see :meth:`get_rtio_counter_mu`).
If the hardware counter has already passed the given time, the function
returns immediately.
"""
while self.get_rtio_counter_mu() < cursor_mu:
pass
@kernel
def get_rtio_destination_status(self, destination: int32) -> bool:
"""Returns whether the specified RTIO destination is up.
This is particularly useful in startup kernels to delay
startup until certain DRTIO destinations are up."""
return rtio_get_destination_status(destination)
@kernel
def reset(self):
"""Clear RTIO FIFOs, release RTIO PHY reset, and set the time cursor
at the current value of the hardware RTIO counter plus a margin of
125000 machine units."""
rtio_init()
at_mu(rtio_get_counter() + int64(125000))
@kernel
def break_realtime(self):
"""Set the time cursor after the current value of the hardware RTIO
counter plus a margin of 125000 machine units.
If the time cursor is already after that position, this function
does nothing."""
min_now = rtio_get_counter() + int64(125000)
if now_mu() < min_now:
at_mu(min_now)
class RunTool:
def __init__(self, pattern, **tempdata):
self._pattern = pattern
self._tempdata = tempdata
self._tempnames = {}
self._tempfiles = {}
def __enter__(self):
for key, data in self._tempdata.items():
if data is None:
fd, filename = tempfile.mkstemp()
os.close(fd)
self._tempnames[key] = filename
else:
with tempfile.NamedTemporaryFile(delete=False) as f:
f.write(data)
self._tempnames[key] = f.name
cmdline = []
for argument in self._pattern:
cmdline.append(argument.format(**self._tempnames))
# https://bugs.python.org/issue17023
windows = os.name == "nt"
process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True, shell=windows)
stdout, stderr = process.communicate()
if process.returncode != 0:
raise Exception("{} invocation failed: {}".
format(cmdline[0], stderr))
self._tempfiles["__stdout__"] = io.StringIO(stdout)
for key in self._tempdata:
if self._tempdata[key] is None:
self._tempfiles[key] = open(self._tempnames[key], "rb")
return self._tempfiles
def __exit__(self, exc_typ, exc_value, exc_trace):
for file in self._tempfiles.values():
file.close()
for filename in self._tempnames.values():
os.unlink(filename)
def symbolize(library, addresses):
if addresses == []:
return []
# We got a list of return addresses, i.e. addresses of instructions
# just after the call. Offset them back to get an address somewhere
# inside the call instruction (or its delay slot), since that's what
# the backtrace entry should point at.
last_inlined = None
offset_addresses = [hex(addr - 1) for addr in addresses]
with RunTool(["llvm-addr2line", "--addresses", "--functions", "--inlines",
"--demangle", "--exe={library}"] + offset_addresses,
library=library) \
as results:
lines = iter(results["__stdout__"].read().rstrip().split("\n"))
backtrace = []
while True:
try:
address_or_function = next(lines)
except StopIteration:
break
if address_or_function[:2] == "0x":
address = int(address_or_function[2:], 16) + 1 # remove offset
function = next(lines)
inlined = False
else:
address = backtrace[-1][4] # inlined
function = address_or_function
inlined = True
location = next(lines)
filename, line = location.rsplit(":", 1)
if filename == "??" or filename == "<synthesized>":
continue
if line == "?":
line = -1
else:
line = int(line)
# can't get column out of addr2line D:
if inlined:
last_inlined.append((filename, line, -1, function, address))
else:
last_inlined = []
backtrace.append((filename, line, -1, function, address,
last_inlined))
return backtrace