opencrbasic 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opencrbasic/__init__.py +3 -0
- opencrbasic/__main__.py +5 -0
- opencrbasic/analysis.py +184 -0
- opencrbasic/bindings.py +1034 -0
- opencrbasic/catalog.py +173 -0
- opencrbasic/checkconfig.py +144 -0
- opencrbasic/checker/__init__.py +977 -0
- opencrbasic/checker/cr3xx.py +3118 -0
- opencrbasic/checker/ess.py +81 -0
- opencrbasic/checker/expressions.py +3581 -0
- opencrbasic/checker/instr_data.py +1802 -0
- opencrbasic/checker/instr_measure.py +1547 -0
- opencrbasic/checker/instr_rules.py +3540 -0
- opencrbasic/checker/instructions.py +17 -0
- opencrbasic/checker/intervals.py +678 -0
- opencrbasic/checker/longconv.py +133 -0
- opencrbasic/checker/scan_time.py +580 -0
- opencrbasic/checker/storage.py +456 -0
- opencrbasic/checker/structs.py +240 -0
- opencrbasic/checker/structure.py +6213 -0
- opencrbasic/checker/symbols.py +609 -0
- opencrbasic/checker/terminals.py +249 -0
- opencrbasic/checker/unused.py +186 -0
- opencrbasic/checker/var_types.py +564 -0
- opencrbasic/checker/warnings.py +661 -0
- opencrbasic/checks.py +491 -0
- opencrbasic/cli.py +391 -0
- opencrbasic/commands.py +128 -0
- opencrbasic/compiler.py +320 -0
- opencrbasic/consteval.py +327 -0
- opencrbasic/data/background_calibration.json +4 -0
- opencrbasic/data/before_beginprog.json +99 -0
- opencrbasic/data/cr1000x/background_calibration.json +4 -0
- opencrbasic/data/cr1000x/before_beginprog.json +138 -0
- opencrbasic/data/cr1000x/ess_variables.json +1087 -0
- opencrbasic/data/cr1000x/fnotch.json +29 -0
- opencrbasic/data/cr1000x/functions.json +1337 -0
- opencrbasic/data/cr1000x/instructions.json +4836 -0
- opencrbasic/data/cr1000x/measure.json +121 -0
- opencrbasic/data/cr1000x/mode.json +111 -0
- opencrbasic/data/cr1000x/pipeline_tasks.json +88 -0
- opencrbasic/data/cr1000x/settings_fields.json +321 -0
- opencrbasic/data/cr1000x/status_members.json +318 -0
- opencrbasic/data/cr1000x/symbols.json +872 -0
- opencrbasic/data/cr1000x/terminals.json +225 -0
- opencrbasic/data/cr300/before_beginprog.json +42 -0
- opencrbasic/data/cr300/ess_variables.json +1087 -0
- opencrbasic/data/cr300/fnotch.json +22 -0
- opencrbasic/data/cr300/functions.json +1261 -0
- opencrbasic/data/cr300/instructions.json +2837 -0
- opencrbasic/data/cr300/measure.json +33 -0
- opencrbasic/data/cr300/mode.json +121 -0
- opencrbasic/data/cr300/settings_fields.json +259 -0
- opencrbasic/data/cr300/status_members.json +303 -0
- opencrbasic/data/cr300/symbols.json +584 -0
- opencrbasic/data/cr300/terminals.json +193 -0
- opencrbasic/data/cr350/before_beginprog.json +44 -0
- opencrbasic/data/cr350/ess_variables.json +1087 -0
- opencrbasic/data/cr350/fnotch.json +22 -0
- opencrbasic/data/cr350/functions.json +1262 -0
- opencrbasic/data/cr350/instructions.json +2848 -0
- opencrbasic/data/cr350/measure.json +35 -0
- opencrbasic/data/cr350/mode.json +121 -0
- opencrbasic/data/cr350/settings_fields.json +292 -0
- opencrbasic/data/cr350/status_members.json +303 -0
- opencrbasic/data/cr350/symbols.json +572 -0
- opencrbasic/data/cr350/terminals.json +190 -0
- opencrbasic/data/ess_variables.json +1087 -0
- opencrbasic/data/fnotch.json +34 -0
- opencrbasic/data/functions.json +1337 -0
- opencrbasic/data/instructions.json +4965 -0
- opencrbasic/data/measure.json +123 -0
- opencrbasic/data/mode.json +121 -0
- opencrbasic/data/notices_cr6.json +890 -0
- opencrbasic/data/pipeline_tasks.json +84 -0
- opencrbasic/data/settings_fields.json +322 -0
- opencrbasic/data/status_members.json +319 -0
- opencrbasic/data/status_table.json +684 -0
- opencrbasic/data/symbols.json +902 -0
- opencrbasic/data/terminals.json +195 -0
- opencrbasic/data/type_codes.json +249 -0
- opencrbasic/diagnostics.py +196 -0
- opencrbasic/editing.py +827 -0
- opencrbasic/formatting.py +310 -0
- opencrbasic/helpdocs.py +677 -0
- opencrbasic/hover.py +667 -0
- opencrbasic/inlay.py +169 -0
- opencrbasic/lexer.py +183 -0
- opencrbasic/lsp/__init__.py +5 -0
- opencrbasic/lsp/__main__.py +7 -0
- opencrbasic/lsp/config.py +202 -0
- opencrbasic/lsp/features.py +602 -0
- opencrbasic/lsp/server.py +741 -0
- opencrbasic/lsp/session.py +1189 -0
- opencrbasic/lsp/worker.py +128 -0
- opencrbasic/mode.py +143 -0
- opencrbasic/navigation.py +401 -0
- opencrbasic/parser.py +3264 -0
- opencrbasic/positions.py +274 -0
- opencrbasic/preprocess.py +269 -0
- opencrbasic/profiles.py +266 -0
- opencrbasic/provenance.py +17 -0
- opencrbasic/quickfix.py +331 -0
- opencrbasic/rename.py +662 -0
- opencrbasic/signature.py +33 -0
- opencrbasic/source.py +600 -0
- opencrbasic/sources.py +125 -0
- opencrbasic/syntax.py +337 -0
- opencrbasic/tables.py +2173 -0
- opencrbasic/tables_hook.py +14 -0
- opencrbasic/tdf.py +142 -0
- opencrbasic/timing.py +565 -0
- opencrbasic-1.0.0.dist-info/METADATA +223 -0
- opencrbasic-1.0.0.dist-info/RECORD +117 -0
- opencrbasic-1.0.0.dist-info/WHEEL +4 -0
- opencrbasic-1.0.0.dist-info/entry_points.txt +3 -0
- opencrbasic-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
"""Measurement-time budget of a `Scan` (CR6; delays, ports and pulse counts also CR1000X).
|
|
2
|
+
|
|
3
|
+
Every instruction adds a number of 100 microsecond units to the open `Scan` when it is compiled;
|
|
4
|
+
`NextScan` compares the sum with the interval (`docs/instructions.md`, "Scan time budget").
|
|
5
|
+
`tools/trace_times.py` records the real additions; the models here reproduce them (CR6, 2026-10-10,
|
|
6
|
+
`tests/fixtures/scan_time.json`).
|
|
7
|
+
|
|
8
|
+
Units of 100 us throughout. Instructions the model does not know add an unknown extra, so the
|
|
9
|
+
total of a scan holding one is a lower bound (`partial`).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import math
|
|
13
|
+
import re
|
|
14
|
+
import struct
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import cast
|
|
17
|
+
|
|
18
|
+
from .. import consteval
|
|
19
|
+
from .. import syntax as S
|
|
20
|
+
from ..profiles import Profile
|
|
21
|
+
|
|
22
|
+
_US_PER_UNIT = {0: 1, 1: 1_000, 2: 1_000_000, 3: 60_000_000, 4: 3_600_000_000, 5: 86_400_000_000}
|
|
23
|
+
UNIT_FACTOR = {1: 10, 2: 10_000, 3: 600_000, 4: 36_000_000, 5: 864_000_000} # 100 us per unit
|
|
24
|
+
UNIT_NAMES = {0: "uSec", 1: "mSec", 2: "Sec", 3: "Min", 4: "Hr", 5: "Day"}
|
|
25
|
+
SCAN_UNITS = (1, 2, 3)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def delay_us(value: float, unit: int) -> int:
|
|
29
|
+
"""The unsigned 32-bit microsecond count of a constant `Delay(0,...)` interval: `value * unit`
|
|
30
|
+
modulo 2^32; outside the Long range the interval is a Double whose low word is read as a
|
|
31
|
+
float32, and a non-finite one counts as 2^31 (oracle, CR6/CR1000X, round 57,
|
|
32
|
+
docs/checker.md#task-memory-overflow-of-long-delays-round-57)."""
|
|
33
|
+
if not -(2**31) <= value < 2**31:
|
|
34
|
+
value = struct.unpack("<f", struct.pack("<d", float(value))[:4])[0]
|
|
35
|
+
if not math.isfinite(value):
|
|
36
|
+
return 2**31
|
|
37
|
+
return int(value * _US_PER_UNIT[unit]) % 2**32
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def excite_delay_us(value: float) -> int:
|
|
41
|
+
"""`ExciteV`'s `Delay`: the Long conversion as unsigned 32 bits (same section)."""
|
|
42
|
+
return consteval.to_long(value) % 2**32
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def task_memory_overflow(us: int) -> bool:
|
|
46
|
+
"""`FUN_00522b50` splits a delay into 43000 us loops with a signed 16-bit count; a negative
|
|
47
|
+
count unrolls it past the 5119 task words (same section)."""
|
|
48
|
+
return bool((us // 43000) & 0x8000)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def amount(excess: int, unit: int) -> int:
|
|
52
|
+
"""`FUN_0046c5c0`: an excess in 100 us units as a count of `unit`, rounded up (uSec: x100)."""
|
|
53
|
+
return excess * 100 if unit == 0 else -(-excess // UNIT_FACTOR[unit])
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_OPENERS = frozenset({"SELECT", "WHILE"})
|
|
57
|
+
_CLOSERS = frozenset({"ENDIF", "ENDSELECT", "WEND"})
|
|
58
|
+
# measurement ranges (FUN_00514690): code -> (gain index, open-detect "C" variant)
|
|
59
|
+
_RANGES = {
|
|
60
|
+
800: (0, False),
|
|
61
|
+
802: (1, False),
|
|
62
|
+
804: (2, False),
|
|
63
|
+
810: (3, False),
|
|
64
|
+
820: (0, True),
|
|
65
|
+
822: (1, True),
|
|
66
|
+
824: (2, True),
|
|
67
|
+
830: (3, True),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _f32(x: float) -> float:
|
|
72
|
+
try:
|
|
73
|
+
return struct.unpack("f", struct.pack("f", x))[0]
|
|
74
|
+
except OverflowError: # (a C float cast overflows to infinity: fN1 `1E300`, round 31)
|
|
75
|
+
return math.copysign(math.inf, x)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def i32(x: int) -> int:
|
|
79
|
+
"""C `int` arithmetic: wrapped to 32 bits."""
|
|
80
|
+
return (x + 2**31) % 2**32 - 2**31
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _cint(x: float) -> int:
|
|
84
|
+
"""x87 float -> int: truncated; out of range gives -2147483648."""
|
|
85
|
+
return int(x) if -(2**31) <= x < 2**31 else -(2**31)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _cdiv(a: int, b: int) -> int:
|
|
89
|
+
"""C integer division (truncates toward zero)."""
|
|
90
|
+
q = abs(a) // abs(b)
|
|
91
|
+
return q if (a < 0) == (b < 0) else -q
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class Model:
|
|
96
|
+
"""The constants of one compiler's time accounting (from its measurement code generator)."""
|
|
97
|
+
|
|
98
|
+
overhead: int # first analog measurement of a scan
|
|
99
|
+
refund: int # taken back when less than 50 ms of slack remains
|
|
100
|
+
integ_num: float # integration code = int(integ_num / fN1 + 0.5)
|
|
101
|
+
integ_zero: int # ... for fN1 = 0
|
|
102
|
+
default_settle: int # documentation only
|
|
103
|
+
round_up: int # added before the final division by 100
|
|
104
|
+
busy_extra: int # added to the longest integration time (units) for the calibration gap
|
|
105
|
+
|
|
106
|
+
def integ_code(self, fnotch: float) -> int | None:
|
|
107
|
+
if _f32(fnotch) == 0: # (`1E-50` is a float zero, round 31)
|
|
108
|
+
return self.integ_zero
|
|
109
|
+
if not math.isfinite(fnotch) or fnotch <= 0:
|
|
110
|
+
return None
|
|
111
|
+
code = int(_f32(_f32(self.integ_num) / _f32(fnotch)) + 0.5)
|
|
112
|
+
if code > 0xFFFF:
|
|
113
|
+
return None
|
|
114
|
+
return code or 1
|
|
115
|
+
|
|
116
|
+
def integ_time(self, k: int) -> int: # microseconds-ish, before settling
|
|
117
|
+
raise NotImplementedError
|
|
118
|
+
|
|
119
|
+
def settle_code(self, settling: int) -> int:
|
|
120
|
+
raise NotImplementedError
|
|
121
|
+
|
|
122
|
+
def settle_units(self, code: int) -> int:
|
|
123
|
+
raise NotImplementedError
|
|
124
|
+
|
|
125
|
+
def open_detect_code(self) -> int:
|
|
126
|
+
raise NotImplementedError
|
|
127
|
+
|
|
128
|
+
def fixed_code(self, constant: int) -> int:
|
|
129
|
+
"""Settling code of a constant the compiler itself passes (PanelTemp 900, Battery 200)."""
|
|
130
|
+
raise NotImplementedError
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class _CR6(Model):
|
|
134
|
+
def integ_time(self, k):
|
|
135
|
+
return (k << 5) // 3 + 1
|
|
136
|
+
|
|
137
|
+
def settle_code(self, settling):
|
|
138
|
+
return _cint(_f32(_f32(_f32(i32((settling or 500) + 810)) * 3.0) / 32.0) + 0.5)
|
|
139
|
+
|
|
140
|
+
def settle_units(self, code):
|
|
141
|
+
return i32(_cint(_f32(_f32(code * 32.0) / 3.0)) + 5)
|
|
142
|
+
|
|
143
|
+
def open_detect_code(self):
|
|
144
|
+
return 5
|
|
145
|
+
|
|
146
|
+
def fixed_code(self, constant):
|
|
147
|
+
return int(_f32(_f32(constant * 3.0) / 32.0) + 0.5)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class _CR1000X(Model):
|
|
151
|
+
def integ_time(self, k):
|
|
152
|
+
return k * 32 + 180
|
|
153
|
+
|
|
154
|
+
def settle_code(self, settling):
|
|
155
|
+
return _cint(_f32(_f32(settling or 450) / 32.0) + 0.5)
|
|
156
|
+
|
|
157
|
+
def settle_units(self, code):
|
|
158
|
+
return i32(_cint(_f32(code * 32.0)) + 5)
|
|
159
|
+
|
|
160
|
+
def open_detect_code(self):
|
|
161
|
+
return 2
|
|
162
|
+
|
|
163
|
+
def fixed_code(self, constant):
|
|
164
|
+
return int(_f32(constant / 32.0) + 0.5)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
MODELS = {
|
|
168
|
+
"cr6": _CR6(78, 77, 93750.0, 24, 500, 0x28, 13),
|
|
169
|
+
"cr1000x": _CR1000X(16, 15, 31250.0, 8, 450, 0x50, 17),
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def analog_units(
|
|
174
|
+
m: Model,
|
|
175
|
+
reps: int,
|
|
176
|
+
gain: int,
|
|
177
|
+
fnotch: float,
|
|
178
|
+
settling: int,
|
|
179
|
+
single_ended: bool,
|
|
180
|
+
meas_off: bool = False,
|
|
181
|
+
reverse: bool = False,
|
|
182
|
+
excite: bool = False,
|
|
183
|
+
open_detect: bool = False,
|
|
184
|
+
groups: int = 1,
|
|
185
|
+
) -> int | None:
|
|
186
|
+
"""Time of one analog measurement call. `meas_off` (single-ended) adds one more measurement,
|
|
187
|
+
`reverse` (differential) and `excite` double the total, autorange (gain 3) settles twice, the
|
|
188
|
+
open-detect ranges settle longer and add 50 per rep. Any settling time counts as converted
|
|
189
|
+
(not clamped by its range check), in 32-bit C arithmetic (round 31)."""
|
|
190
|
+
k = m.integ_code(fnotch)
|
|
191
|
+
if k is None or not 1 <= reps <= 100_000:
|
|
192
|
+
return None
|
|
193
|
+
integ = m.integ_time(k)
|
|
194
|
+
settle = m.settle_units(m.settle_code(settling) + (m.open_detect_code() if open_detect else 0))
|
|
195
|
+
total = i32((settle + integ) * reps)
|
|
196
|
+
if gain == 3:
|
|
197
|
+
total = i32(total + reps * settle)
|
|
198
|
+
if excite:
|
|
199
|
+
total = i32(total * 2)
|
|
200
|
+
total = i32(total * groups)
|
|
201
|
+
if meas_off and single_ended:
|
|
202
|
+
total = i32(total + integ + settle)
|
|
203
|
+
if reverse and not single_ended:
|
|
204
|
+
total = i32(total * 2)
|
|
205
|
+
if open_detect:
|
|
206
|
+
total = i32(total + reps * 0x32)
|
|
207
|
+
return _cdiv(i32(total + m.round_up), 100) + 1
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def panel_units(m: Model, fnotch: float) -> int | None:
|
|
211
|
+
"""`PanelTemp(Dest, fN1)`: two measurements with a fixed settling argument (900)."""
|
|
212
|
+
k = m.integ_code(fnotch)
|
|
213
|
+
if k is None:
|
|
214
|
+
return None
|
|
215
|
+
return (m.settle_units(m.fixed_code(900)) + m.integ_time(k) + m.round_up) // 100 + 1
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def battery_units(m: Model) -> int:
|
|
219
|
+
"""CR1000X `Battery`: one analog call with integration code 10 and a fixed settling of 200."""
|
|
220
|
+
return (m.settle_units(m.fixed_code(200)) + m.integ_time(10) + m.round_up) // 100 + 1
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@dataclass
|
|
224
|
+
class Budget:
|
|
225
|
+
scan: S.Instruction
|
|
226
|
+
end: S.Instruction
|
|
227
|
+
value: int # Scan interval argument
|
|
228
|
+
unit: int # its Units code
|
|
229
|
+
interval: int # 100 us units
|
|
230
|
+
msg_unit: int # the unit the messages count in (a SubScan can leave its own)
|
|
231
|
+
total: int = 0 # after the analog adjustment
|
|
232
|
+
analog: bool = False
|
|
233
|
+
pulse_only: bool = True # nothing but PulseCount adds time (such a scan never warns)
|
|
234
|
+
max_integ: int = 0 # longest integration time (units) of an analog call
|
|
235
|
+
partial: bool = False
|
|
236
|
+
sub_short: list = field(default_factory=list) # (NextSubScan statement, amount, unit)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _value(expr: S.Expr | None, env: consteval.Env) -> object | None:
|
|
240
|
+
if expr is None or not consteval.is_constant(expr, env):
|
|
241
|
+
return None
|
|
242
|
+
try:
|
|
243
|
+
return consteval.value_of(expr, env)
|
|
244
|
+
except consteval.NotConstant:
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _num(expr: S.Expr | None, env: consteval.Env) -> float | None:
|
|
249
|
+
v = _value(expr, env)
|
|
250
|
+
return float(v) if isinstance(v, (int, float)) and not isinstance(v, bool) and math.isfinite(v) else None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _long(expr: S.Expr | None, env: consteval.Env) -> int | None:
|
|
254
|
+
v = _value(expr, env)
|
|
255
|
+
if isinstance(v, bool):
|
|
256
|
+
return -1 if v else 0
|
|
257
|
+
return consteval.to_long(v) if isinstance(v, (int, float)) else None
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _settling(expr: S.Expr | None, env: consteval.Env) -> int | None:
|
|
261
|
+
"""A settling time as the compiler converts it (a string is -2147483648, round 31; `INF`
|
|
262
|
+
times as a float overflow does, `1E39`)."""
|
|
263
|
+
if isinstance(expr, S.Name) and expr.name.upper() == "INF" and "INF" not in env.consts:
|
|
264
|
+
return consteval.to_long(math.inf)
|
|
265
|
+
v = _value(expr, env)
|
|
266
|
+
return consteval.to_long(v) if isinstance(v, str) else _long(expr, env)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# keyword -> (reps position, range position, flag position, settle position, fnotch position,
|
|
270
|
+
# single-ended)
|
|
271
|
+
_ANALOG = {
|
|
272
|
+
"VOLTSE": (2, 3, 5, 6, 7, True),
|
|
273
|
+
"VOLTDIFF": (2, 3, 5, 6, 7, False),
|
|
274
|
+
"TCSE": (2, 3, 7, 8, 9, True),
|
|
275
|
+
"TCDIFF": (2, 3, 7, 8, 9, False),
|
|
276
|
+
"CURRENTSE": (2, 3, 5, 6, 7, True),
|
|
277
|
+
}
|
|
278
|
+
# bridges (round 31): keyword -> (ranges, channel, MeasPEx, RevEx, RevDiff, settle, fnotch positions,
|
|
279
|
+
# single-ended, measurement groups)
|
|
280
|
+
_BRIDGE = {
|
|
281
|
+
"BRHALF": ((3,), 4, 6, 8, None, 9, 10, True, 1),
|
|
282
|
+
"BRHALF3W": ((3,), 4, 6, 8, None, 9, 10, True, 2),
|
|
283
|
+
"BRFULL": ((3,), 4, 6, 8, 9, 10, 11, False, 1),
|
|
284
|
+
"BRHALF4W": ((3, 4), 5, 7, 9, 10, 11, 12, False, 2),
|
|
285
|
+
"BRFULL6W": ((3, 4), 5, 7, 9, 10, 11, 12, False, 2),
|
|
286
|
+
}
|
|
287
|
+
_PLAIN = ((0, False), (1, False), (2, False)) # fixed ranges without open detect
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _bridge_units(m: Model, args: list, env: consteval.Env, spec: tuple) -> tuple[int, bool, int] | None:
|
|
291
|
+
"""One analog call per MeasPEx group of reps (one call when MeasPEx equals Reps); each doubles
|
|
292
|
+
per RevEx, RevDiff (differential) and second measurement group. Autorange and open-detect
|
|
293
|
+
ranges of the two-group bridges, burst channels and Reps above 100000 are not modelled
|
|
294
|
+
(round 31, traced CR6, docs/checker.md "Bridge channel spans and settling-time timing")."""
|
|
295
|
+
ranges, cp, mp, xp, dp, sp, ip, single, groups = spec
|
|
296
|
+
if len(args) < ip:
|
|
297
|
+
return None
|
|
298
|
+
reps, chan, meas, revex = (_long(args[i - 1], env) for i in (2, cp, mp, xp))
|
|
299
|
+
revdiff = _long(args[dp - 1], env) if dp else 0
|
|
300
|
+
rngs = [_long(args[i - 1], env) for i in ranges]
|
|
301
|
+
settling, fn = _settling(args[sp - 1], env), _num(args[ip - 1], env)
|
|
302
|
+
if (
|
|
303
|
+
reps is None
|
|
304
|
+
or chan is None
|
|
305
|
+
or meas is None
|
|
306
|
+
or revex is None
|
|
307
|
+
or revdiff is None
|
|
308
|
+
or settling is None
|
|
309
|
+
or fn is None
|
|
310
|
+
):
|
|
311
|
+
return None
|
|
312
|
+
if None in rngs:
|
|
313
|
+
return None
|
|
314
|
+
rngs = cast(list[int], rngs)
|
|
315
|
+
if (
|
|
316
|
+
chan <= 0
|
|
317
|
+
or not 1 <= reps <= 100_000
|
|
318
|
+
or any(r not in _RANGES for r in rngs)
|
|
319
|
+
or (groups > 1 and any(_RANGES[r] not in _PLAIN for r in rngs))
|
|
320
|
+
):
|
|
321
|
+
return None
|
|
322
|
+
meas = max(meas, 1)
|
|
323
|
+
chunks = [reps] if meas == reps else [meas] * (reps // meas) + ([reps % meas] if reps % meas else [])
|
|
324
|
+
total = 0
|
|
325
|
+
for n in chunks:
|
|
326
|
+
units = analog_units(
|
|
327
|
+
m,
|
|
328
|
+
n,
|
|
329
|
+
_RANGES[rngs[0]][0],
|
|
330
|
+
fn,
|
|
331
|
+
settling,
|
|
332
|
+
single,
|
|
333
|
+
excite=(revex & 0xFF) != 0,
|
|
334
|
+
reverse=(revdiff & 0xFF) != 0,
|
|
335
|
+
open_detect=_RANGES[rngs[0]][1],
|
|
336
|
+
groups=groups,
|
|
337
|
+
)
|
|
338
|
+
if units is None:
|
|
339
|
+
return None
|
|
340
|
+
total = i32(total + units)
|
|
341
|
+
code = m.integ_code(fn)
|
|
342
|
+
if code is None:
|
|
343
|
+
return None
|
|
344
|
+
return total, True, m.integ_time(code) // 100
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def instruction_units(stmt: S.Instruction, env: consteval.Env, profile: Profile) -> tuple[int, bool, int] | None:
|
|
348
|
+
"""`(units, analog, integration time in units)` the instruction adds, or None when not modelled / not
|
|
349
|
+
readable."""
|
|
350
|
+
kw = stmt.keyword.upper()
|
|
351
|
+
args = stmt.args
|
|
352
|
+
if kw == "DELAY":
|
|
353
|
+
if len(args) != 3:
|
|
354
|
+
return None
|
|
355
|
+
option, value, unit = (_num(a, env) for a in args)
|
|
356
|
+
if option is None or int(option) != 0 or value is None or unit is None or int(unit) not in _US_PER_UNIT:
|
|
357
|
+
return None
|
|
358
|
+
if 0 <= value < 1:
|
|
359
|
+
return None
|
|
360
|
+
micro = delay_us(value, int(unit))
|
|
361
|
+
if micro == 0 or task_memory_overflow(micro):
|
|
362
|
+
return None
|
|
363
|
+
if micro >= 2**31: # rounded up instead (an empirical fit, round 57; mechanism not derived)
|
|
364
|
+
return -(-micro // 100), False, 0
|
|
365
|
+
return (2 * (micro // 2) + 100) // 100, False, 0
|
|
366
|
+
if kw == "EXCITEV":
|
|
367
|
+
delay = _long(args[2], env) if len(args) == 3 else None
|
|
368
|
+
if delay is None:
|
|
369
|
+
return None
|
|
370
|
+
delay = excite_delay_us(delay)
|
|
371
|
+
if task_memory_overflow(max(delay - 10, 0)):
|
|
372
|
+
return None
|
|
373
|
+
return ((delay + 100) % 2**32) // 100, False, 0
|
|
374
|
+
if kw == "PORTSET":
|
|
375
|
+
return 1, False, 0
|
|
376
|
+
if kw in ("SW12", "PULSECOUNT"):
|
|
377
|
+
return 1, False, 0
|
|
378
|
+
m = MODELS.get(profile.name)
|
|
379
|
+
if m is None:
|
|
380
|
+
return None
|
|
381
|
+
if kw == "PORTGET" or (kw == "BATTERY" and profile.name == "cr6"):
|
|
382
|
+
return 0, False, 0
|
|
383
|
+
if kw == "BATTERY":
|
|
384
|
+
return battery_units(m), True, 0 # only the floor (oracle, CR1000X Scan(3,mSec))
|
|
385
|
+
if kw == "PANELTEMP" and len(args) == 2:
|
|
386
|
+
fn = _num(args[1], env)
|
|
387
|
+
units = panel_units(m, fn) if fn is not None else None
|
|
388
|
+
return (2 * units, True, m.integ_time(m.integ_code(fn)) // 100) if units and fn is not None else None
|
|
389
|
+
if kw in _ANALOG:
|
|
390
|
+
rp, gp, fp, sp, ip, single = _ANALOG[kw]
|
|
391
|
+
if len(args) < ip:
|
|
392
|
+
return None
|
|
393
|
+
reps, rng, flag = (_long(args[i - 1], env) for i in (rp, gp, fp))
|
|
394
|
+
settling = _settling(args[sp - 1], env)
|
|
395
|
+
fn = _num(args[ip - 1], env)
|
|
396
|
+
if (reps is None or rng is None or flag is None or settling is None or fn is None) or rng not in _RANGES:
|
|
397
|
+
return None
|
|
398
|
+
gain, open_detect = _RANGES[rng]
|
|
399
|
+
cr6_current = kw == "CURRENTSE" and profile.name == "cr6" # MeasOff and "C" ignored
|
|
400
|
+
on = (flag & 0xFF) != 0 and not cr6_current
|
|
401
|
+
open_detect = open_detect and not cr6_current
|
|
402
|
+
flags = dict(meas_off=on and single, reverse=on and not single, open_detect=open_detect)
|
|
403
|
+
units = analog_units(m, reps, gain, fn, settling, single, **flags)
|
|
404
|
+
return (units, True, m.integ_time(m.integ_code(fn)) // 100) if units is not None else None
|
|
405
|
+
if kw in _BRIDGE:
|
|
406
|
+
return _bridge_units(m, args, env, _BRIDGE[kw])
|
|
407
|
+
if kw in ("THERM107", "THERM108", "THERM109") and len(args) >= 6:
|
|
408
|
+
reps, settling = _long(args[1], env), _long(args[4], env)
|
|
409
|
+
fn = _num(args[5], env)
|
|
410
|
+
if (reps is None or settling is None or fn is None) or not 1 <= reps <= 255 or not 0 <= settling < 6_000_000:
|
|
411
|
+
return None
|
|
412
|
+
units = analog_units(m, reps, 0, fn, settling, True, excite=True)
|
|
413
|
+
return (units, True, m.integ_time(m.integ_code(fn)) // 100) if units else None
|
|
414
|
+
if kw == "CS616" and len(args) >= 2:
|
|
415
|
+
reps = _long(args[1], env)
|
|
416
|
+
return (reps * 5 + 2, False, 0) if reps is not None and 1 <= reps <= 255 else None
|
|
417
|
+
return None
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def budgets(program: S.Program, env: consteval.Env, profile: Profile, include_zero: bool = False):
|
|
421
|
+
"""One `Budget` per main-sequence `Scan` with a constant mSec/Sec/Min interval, in order."""
|
|
422
|
+
from .instr_rules import _scan_raw
|
|
423
|
+
|
|
424
|
+
model = MODELS.get(profile.name) or MODELS["cr6"]
|
|
425
|
+
slow = False
|
|
426
|
+
cur: Budget | None = None
|
|
427
|
+
frames: list[
|
|
428
|
+
tuple[int, int, int, int | None, int] | None
|
|
429
|
+
] = [] # open SubScans: (start, count, interval, unit, saved unit)
|
|
430
|
+
total = 0
|
|
431
|
+
for stmt in program.statements:
|
|
432
|
+
kw = stmt.keyword.upper() if isinstance(stmt, S.Instruction) else ""
|
|
433
|
+
text = stmt.text or ""
|
|
434
|
+
if kw == "NEXT" and re.match(r"(?i)\s*Next\s+Scan\b", text):
|
|
435
|
+
kw = "NEXTSCAN"
|
|
436
|
+
elif kw == "NEXT" and re.match(r"(?i)\s*Next\s+SubScan\b", text):
|
|
437
|
+
kw = "NEXTSUBSCAN"
|
|
438
|
+
if kw == "SLOWSEQUENCE":
|
|
439
|
+
slow = True
|
|
440
|
+
elif isinstance(stmt, S.Instruction) and (kw == "SCAN" and not slow):
|
|
441
|
+
raw = _scan_raw(stmt, env)
|
|
442
|
+
if include_zero and len(stmt.args) > 1:
|
|
443
|
+
v, u = _value(stmt.args[0], env), _value(stmt.args[1], env)
|
|
444
|
+
if (
|
|
445
|
+
u in (2, 3)
|
|
446
|
+
and not isinstance(u, bool)
|
|
447
|
+
and isinstance(v, (int, float))
|
|
448
|
+
and not isinstance(v, bool)
|
|
449
|
+
and (0 <= v < 1 or v > 2147483647)
|
|
450
|
+
):
|
|
451
|
+
raw = (0, u)
|
|
452
|
+
if raw and raw[1] in SCAN_UNITS:
|
|
453
|
+
value, unit = raw
|
|
454
|
+
cur = Budget(stmt, stmt, value, unit, value * _US_PER_UNIT[unit] // 100, unit)
|
|
455
|
+
else:
|
|
456
|
+
cur = None
|
|
457
|
+
frames, total = [], 0
|
|
458
|
+
elif cur is None:
|
|
459
|
+
continue
|
|
460
|
+
elif isinstance(stmt, S.Instruction) and (kw == "NEXTSCAN"):
|
|
461
|
+
if frames:
|
|
462
|
+
cur.partial = True
|
|
463
|
+
cur.end = stmt
|
|
464
|
+
cur.total = total
|
|
465
|
+
if cur.analog:
|
|
466
|
+
cur.total = i32(cur.total + model.overhead)
|
|
467
|
+
if i32(cur.interval - cur.total) < 500:
|
|
468
|
+
cur.total = i32(cur.total - model.refund)
|
|
469
|
+
yield cur
|
|
470
|
+
cur = None
|
|
471
|
+
elif isinstance(stmt, S.Instruction) and (kw == "SUBSCAN"):
|
|
472
|
+
args = [_value(a, env) for a in stmt.args]
|
|
473
|
+
count = args[2] if len(args) == 3 else None
|
|
474
|
+
ok = isinstance(count, int) and not isinstance(count, bool) and count > 0
|
|
475
|
+
step = 0
|
|
476
|
+
if ok and args[0] != 0:
|
|
477
|
+
u = _US_PER_UNIT.get(args[1]) if isinstance(args[1], int) else None
|
|
478
|
+
if u is None or not consteval.is_finite_number(args[0]) or args[0] <= 0:
|
|
479
|
+
ok = False
|
|
480
|
+
else:
|
|
481
|
+
step = int(args[0]) * u // 100
|
|
482
|
+
if not ok:
|
|
483
|
+
cur.partial = True
|
|
484
|
+
sub_unit = (
|
|
485
|
+
args[1]
|
|
486
|
+
if ok and isinstance(args[1], int) and not isinstance(args[1], bool) and args[1] in UNIT_NAMES
|
|
487
|
+
else None
|
|
488
|
+
)
|
|
489
|
+
if ok and sub_unit is None:
|
|
490
|
+
cur.partial = True
|
|
491
|
+
frames.append((total, cast(int, count), step, sub_unit, cur.msg_unit) if ok else None)
|
|
492
|
+
if sub_unit is not None:
|
|
493
|
+
cur.msg_unit = sub_unit
|
|
494
|
+
elif kw == "NEXTSUBSCAN":
|
|
495
|
+
frame = frames.pop() if frames else None
|
|
496
|
+
if frame is None:
|
|
497
|
+
continue
|
|
498
|
+
start, count, step, _sub_unit, saved = frame
|
|
499
|
+
body = total - start
|
|
500
|
+
restore = True
|
|
501
|
+
if count < 2 or step == 0 or body == 0:
|
|
502
|
+
total += (count - 1) * body
|
|
503
|
+
elif step < body:
|
|
504
|
+
cur.sub_short.append((stmt, amount(body - step, cur.msg_unit), cur.msg_unit))
|
|
505
|
+
total += (count - 1) * body
|
|
506
|
+
elif step - body > 1:
|
|
507
|
+
total += (count - 1) * step
|
|
508
|
+
restore = False # the real compiler returns before restoring the unit
|
|
509
|
+
else:
|
|
510
|
+
total += (count - 1) * body
|
|
511
|
+
if restore:
|
|
512
|
+
cur.msg_unit = saved
|
|
513
|
+
elif isinstance(stmt, S.Instruction):
|
|
514
|
+
got = instruction_units(stmt, env, profile)
|
|
515
|
+
if got is None:
|
|
516
|
+
if kw in _KNOWN_TIMED:
|
|
517
|
+
cur.partial = True
|
|
518
|
+
cur.analog = cur.analog or kw in _ANALOG_KINDS
|
|
519
|
+
else:
|
|
520
|
+
total = i32(total + got[0])
|
|
521
|
+
if kw != "PULSECOUNT":
|
|
522
|
+
cur.pulse_only = False
|
|
523
|
+
cur.analog = cur.analog or got[1]
|
|
524
|
+
cur.max_integ = max(cur.max_integ, got[2])
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
# of those, the ones that take the analog-measurement overhead
|
|
528
|
+
_ANALOG_KINDS = frozenset(
|
|
529
|
+
{
|
|
530
|
+
"VOLTSE",
|
|
531
|
+
"VOLTDIFF",
|
|
532
|
+
"TCSE",
|
|
533
|
+
"TCDIFF",
|
|
534
|
+
"PANELTEMP",
|
|
535
|
+
"THERM107",
|
|
536
|
+
"THERM108",
|
|
537
|
+
"THERM109",
|
|
538
|
+
"BRHALF",
|
|
539
|
+
"BRHALF3W",
|
|
540
|
+
"BRHALF4W",
|
|
541
|
+
"BRFULL",
|
|
542
|
+
"BRFULL6W",
|
|
543
|
+
"RESISTANCE",
|
|
544
|
+
"CURRENTSE",
|
|
545
|
+
}
|
|
546
|
+
)
|
|
547
|
+
# instructions that add time but are not modelled (a scan holding one has a lower-bound total)
|
|
548
|
+
_KNOWN_TIMED = frozenset(
|
|
549
|
+
{
|
|
550
|
+
"VOLTSE",
|
|
551
|
+
"VOLTDIFF",
|
|
552
|
+
"TCSE",
|
|
553
|
+
"TCDIFF",
|
|
554
|
+
"PANELTEMP",
|
|
555
|
+
"THERM107",
|
|
556
|
+
"THERM108",
|
|
557
|
+
"THERM109",
|
|
558
|
+
"CS616",
|
|
559
|
+
"DELAY",
|
|
560
|
+
"BRHALF",
|
|
561
|
+
"BRHALF3W",
|
|
562
|
+
"BRHALF4W",
|
|
563
|
+
"BRFULL",
|
|
564
|
+
"BRFULL6W",
|
|
565
|
+
"RESISTANCE",
|
|
566
|
+
"CURRENTSE",
|
|
567
|
+
"PERIODAVG",
|
|
568
|
+
"AM25T",
|
|
569
|
+
"THERMISTOR",
|
|
570
|
+
"VIBRATINGWIRE",
|
|
571
|
+
"EXCITEV",
|
|
572
|
+
"EXCITEI",
|
|
573
|
+
"CALIBRATE",
|
|
574
|
+
"PANELTEMP2",
|
|
575
|
+
"RESISTANCE3W",
|
|
576
|
+
"RESISTANCE2W",
|
|
577
|
+
"TCPSYC",
|
|
578
|
+
"MUXSELECT",
|
|
579
|
+
}
|
|
580
|
+
)
|