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
opencrbasic/timing.py
ADDED
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
"""`-t`: the `[Times]` section CR6Comp writes to the message file -- one `line N: <label>` per
|
|
2
|
+
timed statement. Every rule and its evidence is in `docs/cli.md` "Timing output (-t)"; coverage is
|
|
3
|
+
only what oracle probing established, anything else (non-constant arguments, other instructions)
|
|
4
|
+
prints no entry.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import TypeGuard, cast
|
|
9
|
+
|
|
10
|
+
from . import consteval, mode
|
|
11
|
+
from . import syntax as S
|
|
12
|
+
from .profiles import Profile
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _const(expr: S.Expr | None, env: consteval.Env) -> object | None:
|
|
16
|
+
try:
|
|
17
|
+
return consteval.value_of(expr, env)
|
|
18
|
+
except consteval.NotConstant:
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _num(v: object | None) -> float | None:
|
|
23
|
+
if isinstance(v, (int, float)) and not isinstance(v, bool):
|
|
24
|
+
return float(v)
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _round_half_up(x: float) -> int:
|
|
29
|
+
# C's usual `(int)(x + 0.5)` for a non-negative `x` -- avoids Python's round-half-to-even,
|
|
30
|
+
# which would silently disagree with the exe at an exact `.5` (docs/cli.md "-t").
|
|
31
|
+
return int(x + 0.5)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _flatten(stmts):
|
|
35
|
+
for stmt in stmts:
|
|
36
|
+
yield stmt
|
|
37
|
+
if isinstance(stmt, S.If):
|
|
38
|
+
yield from _flatten(stmt.inline or [])
|
|
39
|
+
yield from _flatten(stmt.inline_else or [])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def times_lines(program: S.Program, profile: Profile, main_file: str) -> list[str]:
|
|
43
|
+
"""The `[Times]` section's entries (without the `[Times]` header itself), in program order.
|
|
44
|
+
|
|
45
|
+
`main_file` is accepted for backwards compatibility but no longer used to filter statements
|
|
46
|
+
(see the module docstring's last paragraph); kept so callers do not need to change.
|
|
47
|
+
"""
|
|
48
|
+
del main_file
|
|
49
|
+
env = consteval.Env(profile, dict(program.consts))
|
|
50
|
+
try:
|
|
51
|
+
pipeline = (
|
|
52
|
+
mode.decide(
|
|
53
|
+
program.statements,
|
|
54
|
+
profile,
|
|
55
|
+
lambda e: consteval.is_constant(e, env),
|
|
56
|
+
lambda e: consteval.value_of(e, env),
|
|
57
|
+
)
|
|
58
|
+
== "Pipeline"
|
|
59
|
+
)
|
|
60
|
+
except Exception:
|
|
61
|
+
pipeline = False
|
|
62
|
+
lines: list[str] = []
|
|
63
|
+
scan_count = 0
|
|
64
|
+
inside = False # within a Scan(...)'s own span (see module docstring)
|
|
65
|
+
in_sub = False
|
|
66
|
+
prev_kw = ""
|
|
67
|
+
for stmt in _flatten(program.statements):
|
|
68
|
+
if isinstance(stmt, S.SubDef):
|
|
69
|
+
in_sub = True
|
|
70
|
+
continue
|
|
71
|
+
if not isinstance(stmt, S.Instruction):
|
|
72
|
+
continue
|
|
73
|
+
kw = stmt.keyword
|
|
74
|
+
if S.ends_routine(stmt):
|
|
75
|
+
in_sub = False
|
|
76
|
+
if kw == "SCAN":
|
|
77
|
+
if prev_kw != "SLOWSEQUENCE":
|
|
78
|
+
if scan_count:
|
|
79
|
+
lines.append("")
|
|
80
|
+
lines.append(f"line {stmt.line}: Scan: {scan_count}" + (" (Subroutine)" if in_sub else ""))
|
|
81
|
+
scan_count += 1
|
|
82
|
+
inside = True
|
|
83
|
+
else:
|
|
84
|
+
inside = False
|
|
85
|
+
prev_kw = kw
|
|
86
|
+
continue
|
|
87
|
+
if kw == "NEXTSCAN":
|
|
88
|
+
inside = False
|
|
89
|
+
prev_kw = kw
|
|
90
|
+
continue
|
|
91
|
+
prev_kw = kw
|
|
92
|
+
if not inside:
|
|
93
|
+
continue
|
|
94
|
+
model = _model(profile)
|
|
95
|
+
if kw == "PORTSET" and len(stmt.args) == 2:
|
|
96
|
+
lines.append(f"line {stmt.line}: PortSet = 100")
|
|
97
|
+
elif kw == "SW12" and model != "cr3xx":
|
|
98
|
+
lines.append(f"line {stmt.line}: Sw12 = 100")
|
|
99
|
+
elif kw == "SWVX" and model != "cr6":
|
|
100
|
+
lines.append(f"line {stmt.line}: SwVx = 100")
|
|
101
|
+
elif kw == "BATTERY" and model == "cr1000x":
|
|
102
|
+
lines.append(f"line {stmt.line}: Battery = 800")
|
|
103
|
+
elif kw == "PANELTEMP" and model != "cr3xx":
|
|
104
|
+
_paneltemp(stmt, env, lines, 12 if model == "cr1000x" else 10)
|
|
105
|
+
elif kw in ("DELAY", "MUXSELECT") and model == "cr3xx":
|
|
106
|
+
pass
|
|
107
|
+
elif kw == "DELAY":
|
|
108
|
+
_delay(stmt, env, lines, pipeline)
|
|
109
|
+
elif kw in _MEASURE:
|
|
110
|
+
_measure(stmt, env, lines)
|
|
111
|
+
elif kw in _THERM:
|
|
112
|
+
_therm(stmt, env, lines)
|
|
113
|
+
elif kw in _BRIDGE:
|
|
114
|
+
_bridge(stmt, env, lines)
|
|
115
|
+
elif kw == "AM25T":
|
|
116
|
+
_am25t(stmt, env, lines)
|
|
117
|
+
elif kw in _CLOSED:
|
|
118
|
+
_closed(stmt, env, lines, pipeline)
|
|
119
|
+
return lines
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _model(profile: Profile) -> str:
|
|
123
|
+
"""Which timing cost model a logger follows: the CR6, the CR1000X, or the CR300/CR350."""
|
|
124
|
+
return {"cr6": "cr6", "cr1000x": "cr1000x"}.get(profile.name, "cr3xx")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _paneltemp(stmt: S.Instruction, env: consteval.Env, lines: list[str], base: int = 10) -> None:
|
|
128
|
+
if len(stmt.args) < 2:
|
|
129
|
+
return
|
|
130
|
+
timeout = _num(_const(stmt.args[1], env))
|
|
131
|
+
if not timeout or timeout <= 0:
|
|
132
|
+
return
|
|
133
|
+
cost = (base + _round_half_up(10000.0 / timeout)) * 100
|
|
134
|
+
lines.append(f"line {stmt.line}: PanelTemp = {cost}")
|
|
135
|
+
lines.append(f"line {stmt.line}: PanelTemp = {cost}")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
_UNIT_US = {"USEC": 1, "MSEC": 1000, "SEC": 1000000, "MIN": 60000000}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _signed32(x: int) -> int:
|
|
142
|
+
x &= 0xFFFFFFFF
|
|
143
|
+
return x - (1 << 32) if x >= 1 << 31 else x
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _floor100(x: int) -> int:
|
|
147
|
+
"""`x` rounded down to a multiple of 100 in unsigned 32-bit arithmetic, shown signed (a Delay
|
|
148
|
+
of -5 mSec prints -4996; docs/cli.md)."""
|
|
149
|
+
return _signed32((x & 0xFFFFFFFF) // 100 * 100)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _trunc100(x: int) -> int:
|
|
153
|
+
"""Measurement costs: the sum wraps to signed 32 bits, then is truncated toward zero to a
|
|
154
|
+
multiple of 100 (a 1,000,000-rep VoltSe prints -109967100; docs/cli.md)."""
|
|
155
|
+
v = _signed32(x)
|
|
156
|
+
return v // 100 * 100 if v >= 0 else -(-v // 100 * 100)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _delay(stmt: S.Instruction, env: consteval.Env, lines: list[str], pipeline: bool) -> None:
|
|
160
|
+
# Timed only for RepDelay == 0 in PipelineMode; units case-insensitive (docs/cli.md).
|
|
161
|
+
if len(stmt.args) < 3 or not pipeline:
|
|
162
|
+
return
|
|
163
|
+
value = _num(_const(stmt.args[1], env))
|
|
164
|
+
units = stmt.args[2]
|
|
165
|
+
if _num(_const(stmt.args[0], env)) != 0 or not value:
|
|
166
|
+
return
|
|
167
|
+
if not isinstance(units, S.Name) or units.name.upper() not in _UNIT_US:
|
|
168
|
+
return
|
|
169
|
+
cost = _floor100(int(value * _UNIT_US[units.name.upper()]) + 100)
|
|
170
|
+
lines.append(f"line {stmt.line}: Delay = {cost}")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# (reps, range, channel, flag, settle, integ) argument positions; `flag_adds` is True when the
|
|
174
|
+
# flag (MeasOff / RevDiff) adds one measurement per rep to a single-ended / doubles a differential.
|
|
175
|
+
_MEASURE = {
|
|
176
|
+
"VOLTSE": (1, 2, 3, 4, 5, 6, False),
|
|
177
|
+
"VOLTDIFF": (1, 2, 3, 4, 5, 6, True),
|
|
178
|
+
"TCSE": (1, 2, 3, 6, 7, 8, False),
|
|
179
|
+
"TCDIFF": (1, 2, 3, 6, 7, 8, True),
|
|
180
|
+
}
|
|
181
|
+
_PLAIN_RANGES = {800, 802, 804}
|
|
182
|
+
_C_RANGES = {820, 822, 824}
|
|
183
|
+
_AUTO_RANGES = {810, 830} # AutoRange, AutoRangeC
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _measure(stmt: S.Instruction, env: consteval.Env, lines: list[str]) -> None:
|
|
187
|
+
"""VoltSe/VoltDiff/TCSe/TCDiff (docs/cli.md "Voltage and thermocouple cost"). Not modelled
|
|
188
|
+
(no entry): AutoRange, non-constant arguments, negative settle/integ."""
|
|
189
|
+
ri, gi, ci, fi, si, ii, doubling = _MEASURE[stmt.keyword]
|
|
190
|
+
if len(stmt.args) <= ii:
|
|
191
|
+
return
|
|
192
|
+
model = _model(env.profile)
|
|
193
|
+
if model != "cr6":
|
|
194
|
+
reps, rng, flag, settle, integ = (_num(_const(stmt.args[k], env)) for k in (ri, gi, fi, si, ii))
|
|
195
|
+
if (
|
|
196
|
+
(reps is None or rng is None or flag is None or settle is None or integ is None)
|
|
197
|
+
or reps < 1
|
|
198
|
+
or settle < 0
|
|
199
|
+
or integ < 0
|
|
200
|
+
):
|
|
201
|
+
return
|
|
202
|
+
chan_n = _num(_const(stmt.args[ci], env))
|
|
203
|
+
if chan_n is None:
|
|
204
|
+
return
|
|
205
|
+
reps = int(reps)
|
|
206
|
+
if model == "cr3xx":
|
|
207
|
+
cost = (
|
|
208
|
+
_c3_cost(1 + (1 if flag else 0), reps, settle)
|
|
209
|
+
if doubling
|
|
210
|
+
else _c3_cost(1, reps, settle, 1 if flag else 0)
|
|
211
|
+
)
|
|
212
|
+
else:
|
|
213
|
+
if rng not in _PLAIN_RANGES and rng not in _C_RANGES and rng not in _AUTO_RANGES:
|
|
214
|
+
return
|
|
215
|
+
n = reps * (2 if flag else 1) if doubling else reps + (1 if flag else 0)
|
|
216
|
+
c = rng in _C_RANGES or rng == 830
|
|
217
|
+
valid = _x1000_chan(chan_n, doubling)
|
|
218
|
+
if valid is None:
|
|
219
|
+
return
|
|
220
|
+
auto = (n if doubling else reps) if rng in _AUTO_RANGES else 0
|
|
221
|
+
cost = _x1000_cost(n, settle, integ, c, reps, auto, c and bool(valid))
|
|
222
|
+
lines.append(f"line {stmt.line}: {_LABEL[stmt.keyword]} = {cost}")
|
|
223
|
+
return
|
|
224
|
+
reps, rng, flag, settle, integ = (_num(_const(stmt.args[k], env)) for k in (ri, gi, fi, si, ii))
|
|
225
|
+
chan = stmt.args[ci]
|
|
226
|
+
um = re.fullmatch(r"U(\d+)", chan.name.upper()) if isinstance(chan, S.Name) else None
|
|
227
|
+
u_chan = um is not None and (not doubling or int(um.group(1)) % 2 == 1) # even U is no differential pair
|
|
228
|
+
if um is not None and not u_chan:
|
|
229
|
+
chan = S.Number(cast(S.Name, chan).line, value=0)
|
|
230
|
+
if not u_chan and _num(_const(chan, env)) is None:
|
|
231
|
+
return
|
|
232
|
+
if (
|
|
233
|
+
(reps is None or rng is None or flag is None or settle is None or integ is None)
|
|
234
|
+
or reps < 1
|
|
235
|
+
or settle < 0
|
|
236
|
+
or integ < 0
|
|
237
|
+
):
|
|
238
|
+
return
|
|
239
|
+
auto = rng in _AUTO_RANGES
|
|
240
|
+
if rng not in _PLAIN_RANGES and rng not in _C_RANGES and not auto:
|
|
241
|
+
return
|
|
242
|
+
n = reps * (2 if flag else 1) if doubling else reps + (1 if flag else 0)
|
|
243
|
+
extra = 0
|
|
244
|
+
thirds_u = 0
|
|
245
|
+
if rng in _C_RANGES or rng == 830:
|
|
246
|
+
extra = 50 * reps
|
|
247
|
+
if u_chan:
|
|
248
|
+
thirds_u = 160
|
|
249
|
+
cost = _meas_cost(n, settle, integ, extra, thirds_u, (n if doubling else reps) if auto else 0)
|
|
250
|
+
lines.append(f"line {stmt.line}: {_LABEL[stmt.keyword]} = {cost}")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# CR1000X (docs/cli.md "CR1000X and CR300/CR350 timing"): a measurement pass costs 185 + 32*ks +
|
|
254
|
+
# 32*m (us), settle quantum 32 us (ks = (settle+16)//32, 14 when 0), integration m =
|
|
255
|
+
# round(31250/integ) (8 when 0); C ranges add 64 per pass and 50 per rep; AutoRange adds one
|
|
256
|
+
# settling pass (5 + settle part + 64 if C) per measurement.
|
|
257
|
+
def _x1000_parts(settle: float, integ: float) -> tuple[int, int]:
|
|
258
|
+
ks = 14 if settle == 0 else int(settle + 16) // 32
|
|
259
|
+
m = 8 if integ == 0 else max(1, int(31250 / integ + 0.5))
|
|
260
|
+
return 32 * ks, 32 * m
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _x1000_cost(
|
|
264
|
+
n: int, settle: float, integ: float, c: bool = False, c_reps: int = 0, auto_n: int = 0, u: bool = False
|
|
265
|
+
) -> int:
|
|
266
|
+
"""`c`: a C range; `u`: that C range on a valid channel (SE 1..16, differential 1..8), the
|
|
267
|
+
only case the 64 us per pass applies."""
|
|
268
|
+
s_part, x_part = _x1000_parts(settle, integ)
|
|
269
|
+
cost = 180 + n * (185 + (64 if u else 0) + s_part + x_part) + (50 * c_reps if c else 0)
|
|
270
|
+
cost += auto_n * (5 + s_part + (64 if u else 0))
|
|
271
|
+
return _trunc100(cost)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _x1000_chan(chan: float | None, differential: bool) -> bool | None:
|
|
275
|
+
"""Whether `chan` is a valid channel on the CR1000X; None where the cost is not modelled
|
|
276
|
+
(negative channels, channel 0 of a differential instruction)."""
|
|
277
|
+
if chan is None or chan < 0 or (differential and chan == 0):
|
|
278
|
+
return None
|
|
279
|
+
return 1 <= chan <= (8 if differential else 16)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# CR300/CR350: a pass costs 51136 + settle (500 when 0); the fixed part is 50 + 60*m for m passes
|
|
283
|
+
# per rep (RevDiff, RevEx, double-measurement forms); a MeasOff pass is 51021 + settle with no
|
|
284
|
+
# fixed part of its own. Range and integration time have no effect.
|
|
285
|
+
_C3_PASS = 51136
|
|
286
|
+
_C3_OFFSET_PASS = 51021
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _c3_fixed(m: int) -> int:
|
|
290
|
+
return 50 + 60 * m
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _c3_cost(m: int, reps: int, settle: float, offset_pass: int = 0) -> int:
|
|
294
|
+
s = int(settle) or 500
|
|
295
|
+
return _trunc100(_c3_fixed(m) + reps * m * (_C3_PASS + s) + offset_pass * (_C3_OFFSET_PASS + s))
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _meas_cost(n: float, settle: float, integ: float, extra: float = 0, thirds_u: int = 0, auto_reps: float = 0) -> int:
|
|
299
|
+
ks = int((24 * (settle or 500) + 127) // 256)
|
|
300
|
+
kx = 24 if integ == 0 else max(1, int((24e6 / integ + 128) // 256))
|
|
301
|
+
settle_part = -(-(32 * ks + thirds_u) // 3)
|
|
302
|
+
# AutoRange adds one settle-only pass per rep, 1 us cheaper than a measurement's (docs/cli.md).
|
|
303
|
+
return _trunc100(int(140 + extra + n * (816 + settle_part + 32 * kx // 3) + auto_reps * (815 + settle_part)))
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# Therm107/108/109(Dest, Reps, SEChan, VxChan, Settle, Integ, Mult, Offset): two measurements per
|
|
307
|
+
# rep, plain range, no channel dependence (docs/cli.md "Thermistor cost").
|
|
308
|
+
_THERM = {"THERM107": "Therm107", "THERM108": "Therm108", "THERM109": "Therm109"}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _therm(stmt: S.Instruction, env: consteval.Env, lines: list[str]) -> None:
|
|
312
|
+
a = _args(stmt, env, 1, 4, 5)
|
|
313
|
+
if a is None or a[0] < 1 or a[1] < 0 or a[2] < 0:
|
|
314
|
+
return
|
|
315
|
+
model = _model(env.profile)
|
|
316
|
+
n = (2 * int(a[0])) % 512 # the thermistor measurement count wraps at 512 (2 * Reps; docs/cli.md)
|
|
317
|
+
if n == 0:
|
|
318
|
+
return # the real compiler crashes (divide by zero) for Reps = 256, 512, ...
|
|
319
|
+
if model == "cr3xx":
|
|
320
|
+
cost = _c3_cost(2, n // 2, a[1])
|
|
321
|
+
elif model == "cr1000x":
|
|
322
|
+
cost = _x1000_cost(n, a[1], a[2])
|
|
323
|
+
else:
|
|
324
|
+
cost = _meas_cost(n, a[1], a[2])
|
|
325
|
+
lines.append(f"line {stmt.line}: {_THERM[stmt.keyword]} = {cost}")
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
_LABEL = {"VOLTSE": "VoltSe", "VOLTDIFF": "VoltDiff", "TCSE": "TCSe", "TCDIFF": "TCDiff"}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
# Bridge instructions: (label, first range, second range or None, channel, MeasPEx, RevDiff or
|
|
332
|
+
# None, RevEx, settle, integ, base measurements per rep, single-ended, no range dependence,
|
|
333
|
+
# entry pairs). Argument positions after `Dest` (docs/cli.md "Bridge cost").
|
|
334
|
+
_BRIDGE = {
|
|
335
|
+
"BRHALF": ("BrHalf", 2, None, 3, 5, None, 7, 8, 9, 1, True, False),
|
|
336
|
+
"BRHALF3W": ("BrHalf3W", 2, None, 3, 5, None, 7, 8, 9, 2, True, False),
|
|
337
|
+
"BRHALF4W": ("BrHalf4W", 2, 3, 4, 6, 8, 9, 10, 11, 2, False, False),
|
|
338
|
+
"BRFULL": ("BrFull", 2, None, 3, 5, 7, 8, 9, 10, 1, False, False),
|
|
339
|
+
"BRFULL6W": ("BrFull6W", 2, 3, 4, 6, 8, 9, 10, 11, 2, False, False),
|
|
340
|
+
"RESISTANCE": ("Resistance", 2, None, 3, 5, 7, 8, 9, 10, 1, False, True),
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _bridge(stmt: S.Instruction, env: consteval.Env, lines: list[str]) -> None:
|
|
345
|
+
label, g1, g2, ci, mpi, rdi, rei, si, ii, base, single, resistance = _BRIDGE[stmt.keyword]
|
|
346
|
+
if len(stmt.args) <= ii:
|
|
347
|
+
return
|
|
348
|
+
idx = [1, g1, mpi, rei, si, ii] + ([g2] if g2 else []) + ([rdi] if rdi else [])
|
|
349
|
+
vals = [_num(_const(stmt.args[k], env)) for k in idx]
|
|
350
|
+
if None in vals:
|
|
351
|
+
return
|
|
352
|
+
vals = cast(list[float], vals)
|
|
353
|
+
reps, rng, mp, rev_ex, settle, integ = vals[:6]
|
|
354
|
+
rest = vals[6:]
|
|
355
|
+
rng2 = rest.pop(0) if g2 else rng
|
|
356
|
+
rev_diff = rest.pop(0) if rdi else 0
|
|
357
|
+
chan = stmt.args[ci]
|
|
358
|
+
if not _chan_is_u(chan) and _num(_const(chan, env)) is None:
|
|
359
|
+
return
|
|
360
|
+
if reps < 1 or settle < 0 or integ < 0:
|
|
361
|
+
return
|
|
362
|
+
model = _model(env.profile)
|
|
363
|
+
if model != "cr6":
|
|
364
|
+
valid = _x1000_chan(_num(_const(chan, env)), not single) if model == "cr1000x" else None
|
|
365
|
+
if resistance or (model == "cr1000x" and valid is None):
|
|
366
|
+
return
|
|
367
|
+
per = base * (2 if rev_diff else 1) * (2 if rev_ex else 1)
|
|
368
|
+
group = max(int(mp), 1)
|
|
369
|
+
left = int(reps)
|
|
370
|
+
entries = 0
|
|
371
|
+
while left > 0 and (model == "cr1000x" or entries < 3):
|
|
372
|
+
g = min(group, left)
|
|
373
|
+
left -= g
|
|
374
|
+
entries += 1
|
|
375
|
+
if model == "cr3xx":
|
|
376
|
+
cost = _trunc100(_c3_fixed(per) + g * per * (_C3_PASS + (int(settle) or 500)))
|
|
377
|
+
else:
|
|
378
|
+
for r in (rng, rng2):
|
|
379
|
+
if r not in _PLAIN_RANGES and r not in _C_RANGES and r not in _AUTO_RANGES:
|
|
380
|
+
return
|
|
381
|
+
c = any(r in _C_RANGES or r == 830 for r in (rng, rng2))
|
|
382
|
+
auto = per * g if (g2 is None and rng in _AUTO_RANGES) else 0
|
|
383
|
+
cost = _x1000_cost(g * per, settle, integ, c, g * base, auto, c and bool(valid))
|
|
384
|
+
lines.append(f"line {stmt.line}: {label} = {cost}")
|
|
385
|
+
return
|
|
386
|
+
c_range = False
|
|
387
|
+
for r in (rng, rng2):
|
|
388
|
+
if r not in _PLAIN_RANGES and r not in _C_RANGES:
|
|
389
|
+
return
|
|
390
|
+
c_range |= r in _C_RANGES
|
|
391
|
+
if resistance:
|
|
392
|
+
c_range = False
|
|
393
|
+
if mp < 1:
|
|
394
|
+
return
|
|
395
|
+
u = 0
|
|
396
|
+
if (
|
|
397
|
+
c_range
|
|
398
|
+
and _chan_is_u(chan)
|
|
399
|
+
and (single or int(cast(re.Match[str], re.fullmatch(r"U(\d+)", chan.name.upper())).group(1)) % 2 == 1)
|
|
400
|
+
):
|
|
401
|
+
u = 160
|
|
402
|
+
per = base * (2 if rev_diff else 1) * (2 if rev_ex else 1)
|
|
403
|
+
group = max(int(mp), 1)
|
|
404
|
+
left = int(reps)
|
|
405
|
+
while left > 0:
|
|
406
|
+
g = min(group, left)
|
|
407
|
+
left -= g
|
|
408
|
+
if resistance:
|
|
409
|
+
lines.append(f"line {stmt.line}: {label} = {_meas_cost(per, settle, integ)}")
|
|
410
|
+
extra = 50 * g * base if c_range else 0
|
|
411
|
+
lines.append(f"line {stmt.line}: {label} = {_meas_cost(g * per, settle, integ, extra, u)}")
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _am25t(stmt: S.Instruction, env: consteval.Env, lines: list[str]) -> None:
|
|
415
|
+
"""AM25T(Dest, Reps, Range, DiffChan, MuxChan, TCType, Tref, ClkPort, ResPort, VxChan, RevDiff,
|
|
416
|
+
Settle, Integ, Mult, Offset) (docs/cli.md "AM25T cost")."""
|
|
417
|
+
a = _args(stmt, env, 1, 2, 10, 11, 12)
|
|
418
|
+
if a is None or len(stmt.args) < 15:
|
|
419
|
+
return
|
|
420
|
+
reps, rng, rev, settle, integ = a
|
|
421
|
+
chan = stmt.args[3]
|
|
422
|
+
cm = re.fullmatch(r"U(\d+)", chan.name.upper()) if isinstance(chan, S.Name) else None
|
|
423
|
+
cnum = int(cm.group(1)) if cm else _num(_const(chan, env))
|
|
424
|
+
if cnum is None or reps < 1 or settle < 0 or integ < 0:
|
|
425
|
+
return
|
|
426
|
+
auto = rng in _AUTO_RANGES
|
|
427
|
+
c_range = rng in _C_RANGES or rng == 830
|
|
428
|
+
model = _model(env.profile)
|
|
429
|
+
if model != "cr6":
|
|
430
|
+
if cm is not None or not (c_range or auto or rng in _PLAIN_RANGES or model == "cr3xx"):
|
|
431
|
+
return
|
|
432
|
+
m = 2 if rev else 1
|
|
433
|
+
reps = int(reps)
|
|
434
|
+
if model == "cr3xx":
|
|
435
|
+
first = _trunc100(_c3_fixed(m) + m * (_C3_PASS + (int(settle) or 500)))
|
|
436
|
+
total = _trunc100(_c3_fixed(m) + reps * m * (_C3_PASS + 80 + (int(settle) or 500)))
|
|
437
|
+
out = [first] * 3 + [500, total]
|
|
438
|
+
else:
|
|
439
|
+
s_part, x_part = _x1000_parts(settle, integ)
|
|
440
|
+
first = _trunc100(180 + m * (185 + s_part + x_part) + (50 if c_range else 0))
|
|
441
|
+
total = _trunc100(
|
|
442
|
+
180 + reps * (m * (265 + s_part + x_part) + (50 if c_range else 0) + (m * (85 + s_part) if auto else 0))
|
|
443
|
+
)
|
|
444
|
+
out = [first] * 3 + [300, total]
|
|
445
|
+
out.append(400 + 200 * int(cnum))
|
|
446
|
+
lines.extend(f"line {stmt.line}: AM25T = {v}" for v in out)
|
|
447
|
+
return
|
|
448
|
+
if not (c_range or auto or rng in _PLAIN_RANGES):
|
|
449
|
+
return
|
|
450
|
+
n = 2 if rev else 1
|
|
451
|
+
ks = int((24 * (settle or 500) + 127) // 256)
|
|
452
|
+
kx = 24 if integ == 0 else max(1, int((24e6 / integ + 128) // 256))
|
|
453
|
+
settle_part = -(-32 * ks // 3)
|
|
454
|
+
per = 816 + settle_part + 32 * kx // 3
|
|
455
|
+
out = []
|
|
456
|
+
if _chan_is_u(stmt.args[9]):
|
|
457
|
+
first = _trunc100(140 + n * per + (50 if c_range else 0))
|
|
458
|
+
out = [first] * 3 + [300]
|
|
459
|
+
total = 140 + int(reps) * (n * (per + 80) + (n * (815 + settle_part + 80) if auto else 0) + (50 if c_range else 0))
|
|
460
|
+
out += [_trunc100(total), (40000 if cm else 0) + 400 + 200 * int(cnum)]
|
|
461
|
+
lines.extend(f"line {stmt.line}: AM25T = {v}" for v in out)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _args(stmt: S.Instruction, env: consteval.Env, *idx: int) -> list[float] | None:
|
|
465
|
+
if len(stmt.args) <= max(idx):
|
|
466
|
+
return None
|
|
467
|
+
vals = [_num(_const(stmt.args[i], env)) for i in idx]
|
|
468
|
+
return None if None in vals else cast(list[float], vals)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _chan_is_u(expr: S.Expr | None) -> TypeGuard[S.Name]:
|
|
472
|
+
return isinstance(expr, S.Name) and re.fullmatch(r"U\d+", expr.name.upper()) is not None
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _closed(stmt: S.Instruction, env: consteval.Env, lines: list[str], pipeline: bool) -> None:
|
|
476
|
+
"""Instructions whose cost is a closed form of one or two constant arguments
|
|
477
|
+
(docs/cli.md "Closed forms"); no entry when an argument is not constant."""
|
|
478
|
+
kw = stmt.keyword
|
|
479
|
+
out: list[int] = []
|
|
480
|
+
label = _CLOSED[kw]
|
|
481
|
+
if kw == "PULSEPORT":
|
|
482
|
+
a = _args(stmt, env, 1)
|
|
483
|
+
out = [_floor100(2 * max(int(a[0]), 0) + 100)] if a else []
|
|
484
|
+
elif kw == "EXCITEV":
|
|
485
|
+
a = _args(stmt, env, 2)
|
|
486
|
+
out = [_floor100(max(int(a[0]), 0) + 100)] if a else []
|
|
487
|
+
elif kw == "PULSECOUNT":
|
|
488
|
+
out = [100] if _args(stmt, env, 1) else []
|
|
489
|
+
elif kw == "CALIBRATE":
|
|
490
|
+
model = _model(env.profile)
|
|
491
|
+
out = {"cr6": [2000, 7700] * 3, "cr1000x": [1500, 5700] * 3}.get(model, [])
|
|
492
|
+
elif kw == "CS616":
|
|
493
|
+
a = _args(stmt, env, 1)
|
|
494
|
+
out = [200 + 500 * int(a[0])] if a else []
|
|
495
|
+
elif kw == "PERIODAVG":
|
|
496
|
+
a = _args(stmt, env, 1, 5 if env.profile.name in ("cr300", "cr350") else 7)
|
|
497
|
+
out = [_signed32(int(a[0]) * int(a[1]) * 1000 + 200)] if a else []
|
|
498
|
+
elif kw == "MUXSELECT":
|
|
499
|
+
a = _args(stmt, env, 2, 3, 4)
|
|
500
|
+
if a and pipeline:
|
|
501
|
+
base = 100 if a[2] == 0 else 13000
|
|
502
|
+
out = [_floor100(base + int(a[1]) * (2000 * int(a[0]) + 50))]
|
|
503
|
+
elif kw == "ACPOWER":
|
|
504
|
+
out = _acpower(stmt, env) if _model(env.profile) == "cr6" else _acpower_x1000(stmt, env)
|
|
505
|
+
lines.extend(f"line {stmt.line}: {label} = {v}" for v in out)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _acpower(stmt: S.Instruction, env: consteval.Env) -> list[int]:
|
|
509
|
+
# LineFrq 50/60 are special; any other whole-number frequency (a fraction is truncated) follows
|
|
510
|
+
# the closed form of docs/cli.md "ACPower"; below 1 Hz the real exe reports an error.
|
|
511
|
+
a = _args(stmt, env, 1, 2, 9)
|
|
512
|
+
if not a or len(stmt.args) < 10 or not 1 <= a[1] < 1e6:
|
|
513
|
+
return []
|
|
514
|
+
for k in (3, 6):
|
|
515
|
+
if not _chan_is_u(stmt.args[k]) and _num(_const(stmt.args[k], env)) is None:
|
|
516
|
+
return []
|
|
517
|
+
f = int(a[1])
|
|
518
|
+
if f in (50, 60):
|
|
519
|
+
u_cost, n_cost = {50: (41100, 278300), 60: (34500, 231900)}[f]
|
|
520
|
+
head = 61200 if f == 50 else 51200
|
|
521
|
+
else:
|
|
522
|
+
q = 1000 // f
|
|
523
|
+
u_cost, n_cost = (1003 * q + 2171) // 100 * 100, (2021 * q + 2160) // 100 * 100
|
|
524
|
+
head = 1200 + 1000 * (2000 // f)
|
|
525
|
+
volt, cur = (u_cost if _chan_is_u(stmt.args[k]) else n_cost for k in (3, 6))
|
|
526
|
+
cfg = int(a[0])
|
|
527
|
+
seq = {2: [volt, cur, cur], 3: [volt, cur] * 3, 1: [volt] + [cur] * max(int(a[2]), 0)}.get(cfg, [volt, cur])
|
|
528
|
+
return [1000] + [v for x in seq for v in (head, x)]
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _acpower_x1000(stmt: S.Instruction, env: consteval.Env) -> list[int]:
|
|
532
|
+
"""CR1000X ACPower: numeric channels only, first entry 400; LineFrq 50/60 are table values,
|
|
533
|
+
2..20 follow 992*q + c (q = 1000//f; c in [1536,1600) by the data, 1550 used)."""
|
|
534
|
+
if _model(env.profile) != "cr1000x":
|
|
535
|
+
return []
|
|
536
|
+
a = _args(stmt, env, 1, 2, 9)
|
|
537
|
+
if not a or len(stmt.args) < 10:
|
|
538
|
+
return []
|
|
539
|
+
for k in (3, 6):
|
|
540
|
+
if _num(_const(stmt.args[k], env)) is None:
|
|
541
|
+
return []
|
|
542
|
+
f = int(a[1])
|
|
543
|
+
if f == 50:
|
|
544
|
+
head, x = 61200, 40500
|
|
545
|
+
elif f == 60:
|
|
546
|
+
head, x = 51200, 33800
|
|
547
|
+
elif 2 <= a[1] <= 20:
|
|
548
|
+
head, x = 1200 + 1000 * (2000 // f), (992 * (1000 // f) + 1550) // 100 * 100
|
|
549
|
+
else:
|
|
550
|
+
return []
|
|
551
|
+
cfg = int(a[0])
|
|
552
|
+
seq = {2: 3, 3: 6, 1: 1 + max(int(a[2]), 0)}.get(cfg, 2)
|
|
553
|
+
return [400] + [v for _ in range(seq) for v in (head, x)]
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
_CLOSED = {
|
|
557
|
+
"PULSEPORT": "PulsePort",
|
|
558
|
+
"EXCITEV": "ExciteV",
|
|
559
|
+
"PULSECOUNT": "Pulsecount",
|
|
560
|
+
"CALIBRATE": "Calibrate",
|
|
561
|
+
"CS616": "CS616",
|
|
562
|
+
"PERIODAVG": "PeriodAvg",
|
|
563
|
+
"MUXSELECT": "MuxSelect",
|
|
564
|
+
"ACPOWER": "ACPower",
|
|
565
|
+
}
|