openc166 0.1.15__py3-none-win_amd64.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.
- openc166/__init__.py +574 -0
- openc166/_native.py +155 -0
- openc166/openc166.dll +0 -0
- openc166/py.typed +0 -0
- openc166-0.1.15.data/scripts/openc166.exe +0 -0
- openc166-0.1.15.dist-info/METADATA +308 -0
- openc166-0.1.15.dist-info/RECORD +9 -0
- openc166-0.1.15.dist-info/WHEEL +5 -0
- openc166-0.1.15.dist-info/licenses/LICENSE +21 -0
openc166/__init__.py
ADDED
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""openc166: a Keil A166-compatible assembler for the Infineon C166/C167.
|
|
3
|
+
|
|
4
|
+
>>> import openc166
|
|
5
|
+
>>> result = openc166.assemble('''
|
|
6
|
+
... PROG SECTION CODE AT 0E000H
|
|
7
|
+
... MOV R4, #0BEEFH
|
|
8
|
+
... RET
|
|
9
|
+
... PROG ENDS
|
|
10
|
+
... END
|
|
11
|
+
... ''')
|
|
12
|
+
>>> result.ok, result.diagnostics
|
|
13
|
+
(True, [])
|
|
14
|
+
>>> image = result.image()
|
|
15
|
+
>>> hex(image.base), image.data.hex()
|
|
16
|
+
('0xe000', 'e6f4efbecb00')
|
|
17
|
+
|
|
18
|
+
assemble_many() and assemble_files() assemble several modules and link
|
|
19
|
+
them, as Keil's A166 and L166 would: EXTERN symbols take their values from
|
|
20
|
+
PUBLIC ones in other modules.
|
|
21
|
+
|
|
22
|
+
assemble() never raises for mistakes in the source: they are listed in
|
|
23
|
+
Result.diagnostics. Result.image() raises AssemblyError if there are
|
|
24
|
+
errors, so code that only wants the bytes can write
|
|
25
|
+
``openc166.assemble(src).image().data``.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import ctypes
|
|
31
|
+
import os
|
|
32
|
+
import threading
|
|
33
|
+
import weakref
|
|
34
|
+
from dataclasses import dataclass, field
|
|
35
|
+
from typing import Dict, Iterable, List, Mapping, Optional, Tuple, Union
|
|
36
|
+
|
|
37
|
+
from . import _native
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"assemble",
|
|
41
|
+
"assemble_file",
|
|
42
|
+
"assemble_many",
|
|
43
|
+
"assemble_files",
|
|
44
|
+
"Result",
|
|
45
|
+
"Diagnostic",
|
|
46
|
+
"Chunk",
|
|
47
|
+
"Image",
|
|
48
|
+
"Symbol",
|
|
49
|
+
"Section",
|
|
50
|
+
"AssemblyError",
|
|
51
|
+
"__version__",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
_lib = _native.lib
|
|
55
|
+
|
|
56
|
+
__version__: str = _lib.oc166_version().decode("ascii")
|
|
57
|
+
|
|
58
|
+
PathLike = Union[str, "os.PathLike[str]"]
|
|
59
|
+
SourceText = Union[str, bytes]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _text(b: Optional[bytes]) -> str:
|
|
63
|
+
return b.decode("utf-8", "replace") if b else ""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class Diagnostic:
|
|
68
|
+
"""An error or warning. str() gives "file:line:column: error A62: message"."""
|
|
69
|
+
|
|
70
|
+
severity: str # "error" or "warning"
|
|
71
|
+
code: str # Keil A166 number such as "A62", or "OCn" for openc166's own
|
|
72
|
+
message: str
|
|
73
|
+
file: str
|
|
74
|
+
line: int # 1-based; 0 if not tied to a line
|
|
75
|
+
column: int # 1-based; 0 if unknown
|
|
76
|
+
text: str = field(repr=False, compare=False, default="")
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def is_error(self) -> bool:
|
|
80
|
+
return self.severity == "error"
|
|
81
|
+
|
|
82
|
+
def __str__(self) -> str:
|
|
83
|
+
return self.text
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class Chunk:
|
|
88
|
+
"""Bytes emitted at consecutive addresses."""
|
|
89
|
+
|
|
90
|
+
address: int
|
|
91
|
+
data: bytes
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def end(self) -> int:
|
|
95
|
+
"""Address after the last byte."""
|
|
96
|
+
return self.address + len(self.data)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass(frozen=True)
|
|
100
|
+
class Image:
|
|
101
|
+
"""One contiguous memory image: data[0] belongs at address 'base'."""
|
|
102
|
+
|
|
103
|
+
base: int
|
|
104
|
+
data: bytes
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def end(self) -> int:
|
|
108
|
+
"""Address after the last byte."""
|
|
109
|
+
return self.base + len(self.data)
|
|
110
|
+
|
|
111
|
+
def __bytes__(self) -> bytes:
|
|
112
|
+
return self.data
|
|
113
|
+
|
|
114
|
+
def __len__(self) -> int:
|
|
115
|
+
return len(self.data)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass(frozen=True)
|
|
119
|
+
class Symbol:
|
|
120
|
+
"""A name defined in the source.
|
|
121
|
+
|
|
122
|
+
kind is how it was defined: "label", "equ", "set", "bit", "lit" or
|
|
123
|
+
"extern". An "extern" has the value of the PUBLIC symbol it names, and
|
|
124
|
+
its 'text' is the type from the EXTERN line, such as "WORD".
|
|
125
|
+
type says what value holds:
|
|
126
|
+
"number" a number or address
|
|
127
|
+
"gpr" a word register: 0..15 for R0..R15
|
|
128
|
+
"gpr_byte" a byte register: 0..15 for RL0, RH0, RL1, ...
|
|
129
|
+
"sfr" a special function register: its address
|
|
130
|
+
"bit" a bit: address of the word that holds it; see 'bit'
|
|
131
|
+
"cc" a condition code: its 4-bit encoding
|
|
132
|
+
"" a LIT, which has no value; see 'text'
|
|
133
|
+
value is None for a LIT or if it could not be worked out (an error).
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
name: str
|
|
137
|
+
kind: str
|
|
138
|
+
type: str
|
|
139
|
+
value: Optional[int]
|
|
140
|
+
label_type: str = "" # "near", "far", "byte", "word", "dword" or ""
|
|
141
|
+
bit: Optional[int] = None # bit number 0..15 of a "bit"
|
|
142
|
+
text: str = "" # replacement text of a LIT, type of an EXTERN
|
|
143
|
+
line: int = 0 # listing line of the definition, counted in its module
|
|
144
|
+
module: str = "" # file name of the module that defines it
|
|
145
|
+
exported: bool = False # named by PUBLIC or GLOBAL, so other modules can use it
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass(frozen=True)
|
|
149
|
+
class Section:
|
|
150
|
+
"""Where a SECTION went.
|
|
151
|
+
|
|
152
|
+
type is "CODE", "DATA", "CONST", "HDATA", "HCONST", "XDATA", "XCONST"
|
|
153
|
+
or "BIT". align is "BIT", "BYTE", "WORD", "DWORD", "PAGE", "SEGMENT",
|
|
154
|
+
"BITADDRESSABLE" or "PECADDRESSABLE". placement says how the address was chosen: "at"
|
|
155
|
+
(AT in the source), "given" (the 'sections' argument of assemble()) or
|
|
156
|
+
"located" (by openc166, in the first free place from the origin on).
|
|
157
|
+
class_name is the class from the SECTION line ('ROM' gives "ROM"), or "".
|
|
158
|
+
group is the DGROUP or CGROUP the section belongs to, or "".
|
|
159
|
+
size counts bytes; a BIT section counts every byte it touches. For a BIT
|
|
160
|
+
section, bit is the bit (0..7) of address it starts at and bit_size its
|
|
161
|
+
size in bits; other sections have bit 0 and bit_size 8 * size.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
name: str
|
|
165
|
+
type: str
|
|
166
|
+
align: str
|
|
167
|
+
address: int
|
|
168
|
+
size: int
|
|
169
|
+
placement: str
|
|
170
|
+
class_name: str = ""
|
|
171
|
+
group: str = ""
|
|
172
|
+
bit: int = 0
|
|
173
|
+
bit_size: int = -1 # -1 gives 8 * size
|
|
174
|
+
|
|
175
|
+
def __post_init__(self) -> None:
|
|
176
|
+
if self.bit_size < 0:
|
|
177
|
+
object.__setattr__(self, "bit_size", 8 * self.size)
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def end(self) -> int:
|
|
181
|
+
"""Address after the last byte."""
|
|
182
|
+
return self.address + self.size
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class AssemblyError(Exception):
|
|
186
|
+
"""The source has errors. 'diagnostics' lists them; 'result' is the whole Result."""
|
|
187
|
+
|
|
188
|
+
def __init__(self, result: "Result"):
|
|
189
|
+
self.result = result
|
|
190
|
+
self.diagnostics: List[Diagnostic] = result.errors
|
|
191
|
+
lines = [str(d) for d in self.diagnostics[:20]]
|
|
192
|
+
if len(self.diagnostics) > 20:
|
|
193
|
+
lines.append("... and {} more".format(len(self.diagnostics) - 20))
|
|
194
|
+
super().__init__("{} error(s):\n{}".format(len(self.diagnostics), "\n".join(lines)))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class Result:
|
|
198
|
+
"""The outcome of assembling a source."""
|
|
199
|
+
|
|
200
|
+
def __init__(self, handle: int):
|
|
201
|
+
self._handle = handle
|
|
202
|
+
self._lock = threading.Lock()
|
|
203
|
+
self._finalizer = weakref.finalize(self, _lib.oc166_result_free, handle)
|
|
204
|
+
self._listing: Optional[str] = None
|
|
205
|
+
|
|
206
|
+
self.cpu: str = _lib.oc166_result_cpu(handle).decode("ascii")
|
|
207
|
+
"""CPU the source was assembled for: "c166" or "c167"."""
|
|
208
|
+
|
|
209
|
+
self.diagnostics: List[Diagnostic] = []
|
|
210
|
+
"""Errors and warnings in source order."""
|
|
211
|
+
d = _native.Diagnostic()
|
|
212
|
+
for i in range(_lib.oc166_result_diagnostic_count(handle)):
|
|
213
|
+
_lib.oc166_result_diagnostic(handle, i, ctypes.byref(d))
|
|
214
|
+
self.diagnostics.append(
|
|
215
|
+
Diagnostic(
|
|
216
|
+
"error" if d.is_error else "warning",
|
|
217
|
+
_text(d.code),
|
|
218
|
+
_text(d.message),
|
|
219
|
+
_text(d.file),
|
|
220
|
+
d.line,
|
|
221
|
+
d.column,
|
|
222
|
+
_text(d.text),
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
self.chunks: List[Chunk] = []
|
|
227
|
+
"""Code and data in source order."""
|
|
228
|
+
c = _native.Chunk()
|
|
229
|
+
for i in range(_lib.oc166_result_chunk_count(handle)):
|
|
230
|
+
_lib.oc166_result_chunk(handle, i, ctypes.byref(c))
|
|
231
|
+
data = ctypes.string_at(c.data, c.size) if c.size else b""
|
|
232
|
+
self.chunks.append(Chunk(c.address, data))
|
|
233
|
+
|
|
234
|
+
self.modules: List[str] = [
|
|
235
|
+
_text(_lib.oc166_result_module(handle, i))
|
|
236
|
+
for i in range(_lib.oc166_result_module_count(handle))
|
|
237
|
+
]
|
|
238
|
+
"""File name of each module: one per source."""
|
|
239
|
+
|
|
240
|
+
self.all_symbols: List[Symbol] = []
|
|
241
|
+
"""The names defined in every module, sorted by name, then module."""
|
|
242
|
+
s = _native.Symbol()
|
|
243
|
+
for i in range(_lib.oc166_result_symbol_count(handle)):
|
|
244
|
+
_lib.oc166_result_symbol(handle, i, ctypes.byref(s))
|
|
245
|
+
kind = _text(s.kind)
|
|
246
|
+
sym_type = _text(s.type)
|
|
247
|
+
module = _lib.oc166_result_symbol_module(handle, i)
|
|
248
|
+
self.all_symbols.append(
|
|
249
|
+
Symbol(
|
|
250
|
+
name=_text(s.name),
|
|
251
|
+
kind=kind,
|
|
252
|
+
type=sym_type,
|
|
253
|
+
value=s.value if s.known and kind != "lit" else None,
|
|
254
|
+
label_type=_text(s.label_type),
|
|
255
|
+
bit=s.bit if sym_type == "bit" else None,
|
|
256
|
+
text=_text(s.text),
|
|
257
|
+
line=s.line,
|
|
258
|
+
module=self.modules[module] if 0 <= module < len(self.modules) else "",
|
|
259
|
+
exported=_lib.oc166_result_symbol_exported(handle, i) == 1,
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
self.symbols: Dict[str, Symbol] = {}
|
|
264
|
+
"""Defined names, spelled as in their definition, sorted by name.
|
|
265
|
+
|
|
266
|
+
If several modules define a name, this holds the PUBLIC or GLOBAL
|
|
267
|
+
one, or else the one in the first of them; all_symbols has them all.
|
|
268
|
+
"""
|
|
269
|
+
for sym in self.all_symbols:
|
|
270
|
+
if sym.name not in self.symbols or (sym.exported and not self.symbols[sym.name].exported):
|
|
271
|
+
self.symbols[sym.name] = sym
|
|
272
|
+
self._folded = {name.upper(): sym for name, sym in self.symbols.items()}
|
|
273
|
+
|
|
274
|
+
self.sections: Dict[str, Section] = {}
|
|
275
|
+
"""Sections in order of first appearance, by name as first spelled."""
|
|
276
|
+
sec = _native.Section()
|
|
277
|
+
first_bit = ctypes.c_int()
|
|
278
|
+
bit_count = ctypes.c_int64()
|
|
279
|
+
for i in range(_lib.oc166_result_section_count(handle)):
|
|
280
|
+
_lib.oc166_result_section(handle, i, ctypes.byref(sec))
|
|
281
|
+
_lib.oc166_result_section_bits(handle, i, ctypes.byref(first_bit), ctypes.byref(bit_count))
|
|
282
|
+
self.sections[_text(sec.name)] = Section(
|
|
283
|
+
name=_text(sec.name),
|
|
284
|
+
type=_text(sec.type),
|
|
285
|
+
align=_text(sec.align),
|
|
286
|
+
address=sec.address,
|
|
287
|
+
size=sec.size,
|
|
288
|
+
placement=_text(sec.placement),
|
|
289
|
+
class_name=_text(_lib.oc166_result_section_class(handle, i)),
|
|
290
|
+
group=_text(_lib.oc166_result_section_group(handle, i)),
|
|
291
|
+
bit=first_bit.value,
|
|
292
|
+
bit_size=bit_count.value,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def errors(self) -> List[Diagnostic]:
|
|
297
|
+
return [d for d in self.diagnostics if d.is_error]
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def warnings(self) -> List[Diagnostic]:
|
|
301
|
+
return [d for d in self.diagnostics if not d.is_error]
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def ok(self) -> bool:
|
|
305
|
+
"""True if there are no errors (warnings are allowed)."""
|
|
306
|
+
return not any(d.is_error for d in self.diagnostics)
|
|
307
|
+
|
|
308
|
+
def symbol(self, name: str) -> Symbol:
|
|
309
|
+
"""Looks a symbol up ignoring case, as the assembler does. Raises KeyError."""
|
|
310
|
+
return self._folded[name.upper()]
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def listing(self) -> str:
|
|
314
|
+
"""The listing: addresses, code bytes, source lines and diagnostics."""
|
|
315
|
+
if self._listing is None:
|
|
316
|
+
with self._lock:
|
|
317
|
+
text = _lib.oc166_result_listing(self._handle)
|
|
318
|
+
if text is None:
|
|
319
|
+
raise MemoryError("cannot format the listing")
|
|
320
|
+
self._listing = _text(text)
|
|
321
|
+
return self._listing
|
|
322
|
+
|
|
323
|
+
def image(self, base: Optional[int] = None, fill: int = 0xFF) -> Image:
|
|
324
|
+
"""Lays all code and data out as one image.
|
|
325
|
+
|
|
326
|
+
The image starts at 'base', or at the lowest address used. Gaps are
|
|
327
|
+
filled with 'fill'. Raises AssemblyError if the source has errors,
|
|
328
|
+
and ValueError if code lies below 'base' or the image would be
|
|
329
|
+
larger than 16 MB.
|
|
330
|
+
"""
|
|
331
|
+
if not self.ok:
|
|
332
|
+
raise AssemblyError(self)
|
|
333
|
+
if not 0 <= fill <= 0xFF:
|
|
334
|
+
raise ValueError("fill must be 0..255")
|
|
335
|
+
if base is not None and base < 0:
|
|
336
|
+
raise ValueError("base must not be negative")
|
|
337
|
+
img = _native.Image()
|
|
338
|
+
with self._lock:
|
|
339
|
+
rc = _lib.oc166_result_image(
|
|
340
|
+
self._handle, base is not None, base or 0, fill, ctypes.byref(img)
|
|
341
|
+
)
|
|
342
|
+
if rc != 0:
|
|
343
|
+
raise ValueError(_text(_lib.oc166_result_last_error(self._handle)))
|
|
344
|
+
data = ctypes.string_at(img.data, img.size) if img.size else b""
|
|
345
|
+
return Image(img.base, data)
|
|
346
|
+
|
|
347
|
+
def intel_hex(self, record_size: int = 16) -> str:
|
|
348
|
+
"""The code and data as Intel HEX text.
|
|
349
|
+
|
|
350
|
+
Data records hold up to 'record_size' bytes (1..255). An extended
|
|
351
|
+
linear address record (type 04) comes before each 64 KB bank, and
|
|
352
|
+
lines end with CR LF, as Keil OH166 writes them. Only the bytes used
|
|
353
|
+
are written: gaps stay empty. Raises AssemblyError if the source has
|
|
354
|
+
errors, and ValueError for a bad record size.
|
|
355
|
+
"""
|
|
356
|
+
return self._text_output(_lib.oc166_result_intel_hex, record_size)
|
|
357
|
+
|
|
358
|
+
def srec(self, record_size: int = 16, address_size: Optional[int] = None) -> str:
|
|
359
|
+
"""The code and data as Motorola S-records.
|
|
360
|
+
|
|
361
|
+
address_size is 2 for S1 records, 3 for S2, 4 for S3, or None for
|
|
362
|
+
the smallest that holds every address. Data records hold up to
|
|
363
|
+
'record_size' bytes. Raises AssemblyError if the source has errors,
|
|
364
|
+
and ValueError if an address does not fit or a size is out of range.
|
|
365
|
+
"""
|
|
366
|
+
return self._text_output(_lib.oc166_result_srec, record_size, address_size or 0)
|
|
367
|
+
|
|
368
|
+
def _text_output(self, func, *args: int) -> str:
|
|
369
|
+
if not self.ok:
|
|
370
|
+
raise AssemblyError(self)
|
|
371
|
+
if any(not -(2**31) <= a < 2**31 for a in args):
|
|
372
|
+
raise ValueError("size out of range")
|
|
373
|
+
with self._lock:
|
|
374
|
+
text = func(self._handle, *args)
|
|
375
|
+
if text is None:
|
|
376
|
+
raise ValueError(_text(_lib.oc166_result_last_error(self._handle)))
|
|
377
|
+
return text.decode("ascii")
|
|
378
|
+
|
|
379
|
+
def __repr__(self) -> str:
|
|
380
|
+
return "<openc166.Result cpu={} errors={} warnings={} chunks={}>".format(
|
|
381
|
+
self.cpu, len(self.errors), len(self.warnings), len(self.chunks)
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def assemble(
|
|
386
|
+
source: Union[str, bytes],
|
|
387
|
+
*,
|
|
388
|
+
file_name: Optional[PathLike] = None,
|
|
389
|
+
cpu: Optional[str] = None,
|
|
390
|
+
origin: int = 0,
|
|
391
|
+
include_dirs: Iterable[PathLike] = (),
|
|
392
|
+
sections: Optional[Mapping[str, int]] = None,
|
|
393
|
+
dialect: str = "keil",
|
|
394
|
+
controls: Iterable[str] = (),
|
|
395
|
+
) -> Result:
|
|
396
|
+
"""Assembles Keil A166 (or TASKING a166) source text.
|
|
397
|
+
|
|
398
|
+
source the text. A str is encoded as UTF-8; pass bytes to keep
|
|
399
|
+
another encoding (Keil sources are often Windows-1252).
|
|
400
|
+
file_name names the source in diagnostics; $INCLUDE also searches
|
|
401
|
+
its directory. The source is not read from it.
|
|
402
|
+
cpu "c166" (the default) or "c167". $MOD167 in the source
|
|
403
|
+
selects the C167 too.
|
|
404
|
+
origin address of code outside any section, and the lowest
|
|
405
|
+
address for sections without AT.
|
|
406
|
+
include_dirs more directories searched by $INCLUDE.
|
|
407
|
+
sections addresses for sections without AT, by name (any case),
|
|
408
|
+
like the SECTIONS control of Keil's L166:
|
|
409
|
+
{"MYDATA": 0xF600}. The other sections go in order of
|
|
410
|
+
appearance to the first free place from the origin on.
|
|
411
|
+
dialect "keil" (the default) for Keil A166 sources, or "tasking"
|
|
412
|
+
for TASKING a166 sources, which first pass through
|
|
413
|
+
openc166's m166 preprocessor (@SET, @IF, ...). TASKING
|
|
414
|
+
sources are always for the C167.
|
|
415
|
+
controls controls read before the source, as on the assembler's
|
|
416
|
+
command line: ["CASE", "STDNAMES(reg.def)"].
|
|
417
|
+
"""
|
|
418
|
+
source = _source_bytes(source)
|
|
419
|
+
name = os.fsencode(os.fspath(file_name)) if file_name is not None else None
|
|
420
|
+
return _run(
|
|
421
|
+
lambda opts: _lib.oc166_assemble(opts, source, len(source), name),
|
|
422
|
+
cpu,
|
|
423
|
+
origin,
|
|
424
|
+
include_dirs,
|
|
425
|
+
sections,
|
|
426
|
+
dialect,
|
|
427
|
+
controls,
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _source_bytes(source: SourceText) -> bytes:
|
|
432
|
+
if isinstance(source, str):
|
|
433
|
+
return source.encode("utf-8")
|
|
434
|
+
if not isinstance(source, (bytes, bytearray, memoryview)):
|
|
435
|
+
raise TypeError("source must be str or bytes")
|
|
436
|
+
return bytes(source)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _run(call, cpu, origin, include_dirs, sections, dialect, controls) -> Result:
|
|
440
|
+
"""Makes the options, calls call(options) and wraps the result."""
|
|
441
|
+
opts = _lib.oc166_options_new()
|
|
442
|
+
if not opts:
|
|
443
|
+
raise MemoryError("cannot allocate options")
|
|
444
|
+
try:
|
|
445
|
+
if cpu is not None and _lib.oc166_options_set_cpu(opts, cpu.encode("ascii", "replace")) != 0:
|
|
446
|
+
raise ValueError("unknown CPU {!r} (use 'c166' or 'c167')".format(cpu))
|
|
447
|
+
if _lib.oc166_options_set_dialect(opts, str(dialect).encode("ascii", "replace")) != 0:
|
|
448
|
+
raise ValueError("unknown dialect {!r} (use 'keil' or 'tasking')".format(dialect))
|
|
449
|
+
if isinstance(controls, (str, bytes)):
|
|
450
|
+
raise TypeError("controls must be a list of strings, not a single string")
|
|
451
|
+
for c in controls:
|
|
452
|
+
if _lib.oc166_options_add_control(opts, str(c).encode("utf-8")) != 0:
|
|
453
|
+
raise MemoryError("cannot add control")
|
|
454
|
+
if _lib.oc166_options_set_origin(opts, origin) != 0:
|
|
455
|
+
raise ValueError("origin must not be negative")
|
|
456
|
+
for d in include_dirs:
|
|
457
|
+
_lib.oc166_options_add_include_dir(opts, os.fsencode(os.fspath(d)))
|
|
458
|
+
for sec_name, address in (sections or {}).items():
|
|
459
|
+
if not isinstance(address, int) or not 0 <= address <= 0xFFFFFF:
|
|
460
|
+
raise ValueError(
|
|
461
|
+
"bad address {!r} for section {!r} (use 0..0FFFFFFH)".format(address, sec_name)
|
|
462
|
+
)
|
|
463
|
+
if _lib.oc166_options_set_section_address(opts, str(sec_name).encode("utf-8"), address) != 0:
|
|
464
|
+
raise ValueError("bad section name {!r}".format(sec_name))
|
|
465
|
+
handle = call(opts)
|
|
466
|
+
finally:
|
|
467
|
+
_lib.oc166_options_free(opts)
|
|
468
|
+
if not handle:
|
|
469
|
+
raise MemoryError("out of memory while assembling")
|
|
470
|
+
return Result(handle)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def assemble_file(
|
|
474
|
+
path: PathLike,
|
|
475
|
+
*,
|
|
476
|
+
cpu: Optional[str] = None,
|
|
477
|
+
origin: int = 0,
|
|
478
|
+
include_dirs: Iterable[PathLike] = (),
|
|
479
|
+
sections: Optional[Mapping[str, int]] = None,
|
|
480
|
+
dialect: str = "keil",
|
|
481
|
+
controls: Iterable[str] = (),
|
|
482
|
+
) -> Result:
|
|
483
|
+
"""Reads and assembles a source file. See assemble() for the options."""
|
|
484
|
+
with open(path, "rb") as f:
|
|
485
|
+
source = f.read()
|
|
486
|
+
return assemble(
|
|
487
|
+
source,
|
|
488
|
+
file_name=path,
|
|
489
|
+
cpu=cpu,
|
|
490
|
+
origin=origin,
|
|
491
|
+
include_dirs=include_dirs,
|
|
492
|
+
sections=sections,
|
|
493
|
+
dialect=dialect,
|
|
494
|
+
controls=controls,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def assemble_many(
|
|
499
|
+
sources: Union[Mapping[PathLike, SourceText], Iterable[Tuple[PathLike, SourceText]]],
|
|
500
|
+
*,
|
|
501
|
+
cpu: Optional[str] = None,
|
|
502
|
+
origin: int = 0,
|
|
503
|
+
include_dirs: Iterable[PathLike] = (),
|
|
504
|
+
sections: Optional[Mapping[str, int]] = None,
|
|
505
|
+
dialect: str = "keil",
|
|
506
|
+
controls: Iterable[str] = (),
|
|
507
|
+
) -> Result:
|
|
508
|
+
"""Assembles several modules and links them into one program.
|
|
509
|
+
|
|
510
|
+
sources is a dict or a list of (file name, source text) pairs; each is
|
|
511
|
+
a module, as one .a66 file is for Keil's A166. As L166 would, the
|
|
512
|
+
modules are joined: an EXTERN symbol takes the value of the PUBLIC or
|
|
513
|
+
GLOBAL symbol of that name in another module, and sections with the
|
|
514
|
+
same name and class are combined. Code outside sections starts at the
|
|
515
|
+
origin in every module. See assemble() for the other options.
|
|
516
|
+
|
|
517
|
+
>>> result = assemble_many({
|
|
518
|
+
... "main.a66": "EXTERN LIMIT:DATA16\\n"
|
|
519
|
+
... "PROG SECTION CODE AT 0E000H\\n"
|
|
520
|
+
... " MOV R4, #LIMIT\\n"
|
|
521
|
+
... "PROG ENDS\\n"
|
|
522
|
+
... " END\\n",
|
|
523
|
+
... "consts.a66": "PUBLIC LIMIT\\nLIMIT EQU 1234H\\n END\\n",
|
|
524
|
+
... })
|
|
525
|
+
>>> result.image().data.hex(), result.symbol("LIMIT").module
|
|
526
|
+
('e6f43412', 'consts.a66')
|
|
527
|
+
"""
|
|
528
|
+
items = list(sources.items()) if isinstance(sources, Mapping) else list(sources)
|
|
529
|
+
if not items:
|
|
530
|
+
raise ValueError("no sources")
|
|
531
|
+
names = [os.fsencode(os.fspath(name)) for name, _ in items]
|
|
532
|
+
texts = [_source_bytes(text) for _, text in items]
|
|
533
|
+
count = len(items)
|
|
534
|
+
c_texts = (ctypes.c_char_p * count)(*texts)
|
|
535
|
+
c_lengths = (ctypes.c_size_t * count)(*(len(t) for t in texts))
|
|
536
|
+
c_names = (ctypes.c_char_p * count)(*names)
|
|
537
|
+
return _run(
|
|
538
|
+
lambda opts: _lib.oc166_assemble_many(opts, count, c_texts, c_lengths, c_names),
|
|
539
|
+
cpu,
|
|
540
|
+
origin,
|
|
541
|
+
include_dirs,
|
|
542
|
+
sections,
|
|
543
|
+
dialect,
|
|
544
|
+
controls,
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def assemble_files(
|
|
549
|
+
paths: Iterable[PathLike],
|
|
550
|
+
*,
|
|
551
|
+
cpu: Optional[str] = None,
|
|
552
|
+
origin: int = 0,
|
|
553
|
+
include_dirs: Iterable[PathLike] = (),
|
|
554
|
+
sections: Optional[Mapping[str, int]] = None,
|
|
555
|
+
dialect: str = "keil",
|
|
556
|
+
controls: Iterable[str] = (),
|
|
557
|
+
) -> Result:
|
|
558
|
+
"""Reads source files and assembles them as modules of one program.
|
|
559
|
+
|
|
560
|
+
See assemble_many().
|
|
561
|
+
"""
|
|
562
|
+
items = []
|
|
563
|
+
for path in paths:
|
|
564
|
+
with open(path, "rb") as f:
|
|
565
|
+
items.append((path, f.read()))
|
|
566
|
+
return assemble_many(
|
|
567
|
+
items,
|
|
568
|
+
cpu=cpu,
|
|
569
|
+
origin=origin,
|
|
570
|
+
include_dirs=include_dirs,
|
|
571
|
+
sections=sections,
|
|
572
|
+
dialect=dialect,
|
|
573
|
+
controls=controls,
|
|
574
|
+
)
|
openc166/_native.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Loads the openc166 shared library and declares its C functions.
|
|
3
|
+
|
|
4
|
+
The C interface is documented in include/openc166/openc166.h. The library
|
|
5
|
+
is looked for next to this file; the OPENC166_LIBRARY environment variable
|
|
6
|
+
names a different file (used to test a build tree).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import ctypes
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
# OC166_API_VERSION this module was written for.
|
|
14
|
+
API_VERSION = 1
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Diagnostic(ctypes.Structure):
|
|
18
|
+
_fields_ = [
|
|
19
|
+
("is_error", ctypes.c_int),
|
|
20
|
+
("code", ctypes.c_char_p),
|
|
21
|
+
("message", ctypes.c_char_p),
|
|
22
|
+
("file", ctypes.c_char_p),
|
|
23
|
+
("line", ctypes.c_int),
|
|
24
|
+
("column", ctypes.c_int),
|
|
25
|
+
("text", ctypes.c_char_p),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Chunk(ctypes.Structure):
|
|
30
|
+
_fields_ = [
|
|
31
|
+
("address", ctypes.c_int64),
|
|
32
|
+
("data", ctypes.POINTER(ctypes.c_uint8)),
|
|
33
|
+
("size", ctypes.c_size_t),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Image(ctypes.Structure):
|
|
38
|
+
_fields_ = [
|
|
39
|
+
("base", ctypes.c_int64),
|
|
40
|
+
("data", ctypes.POINTER(ctypes.c_uint8)),
|
|
41
|
+
("size", ctypes.c_size_t),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Symbol(ctypes.Structure):
|
|
46
|
+
_fields_ = [
|
|
47
|
+
("name", ctypes.c_char_p),
|
|
48
|
+
("kind", ctypes.c_char_p),
|
|
49
|
+
("type", ctypes.c_char_p),
|
|
50
|
+
("label_type", ctypes.c_char_p),
|
|
51
|
+
("value", ctypes.c_int64),
|
|
52
|
+
("bit", ctypes.c_int),
|
|
53
|
+
("known", ctypes.c_int),
|
|
54
|
+
("text", ctypes.c_char_p),
|
|
55
|
+
("line", ctypes.c_int),
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Section(ctypes.Structure):
|
|
60
|
+
_fields_ = [
|
|
61
|
+
("name", ctypes.c_char_p),
|
|
62
|
+
("type", ctypes.c_char_p),
|
|
63
|
+
("align", ctypes.c_char_p),
|
|
64
|
+
("address", ctypes.c_int64),
|
|
65
|
+
("size", ctypes.c_int64),
|
|
66
|
+
("placement", ctypes.c_char_p),
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _library_paths():
|
|
71
|
+
override = os.environ.get("OPENC166_LIBRARY")
|
|
72
|
+
if override:
|
|
73
|
+
return [override]
|
|
74
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
75
|
+
if sys.platform in ("win32", "cygwin"):
|
|
76
|
+
names = ["openc166.dll", "libopenc166.dll", "cygopenc166.dll"]
|
|
77
|
+
elif sys.platform == "darwin":
|
|
78
|
+
names = ["libopenc166.dylib"]
|
|
79
|
+
else:
|
|
80
|
+
names = ["libopenc166.so"]
|
|
81
|
+
return [os.path.join(here, name) for name in names]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _load():
|
|
85
|
+
problems = []
|
|
86
|
+
for path in _library_paths():
|
|
87
|
+
if not os.path.exists(path):
|
|
88
|
+
problems.append(path + ": not found")
|
|
89
|
+
continue
|
|
90
|
+
try:
|
|
91
|
+
return ctypes.CDLL(path)
|
|
92
|
+
except OSError as e:
|
|
93
|
+
problems.append("{}: {}".format(path, e))
|
|
94
|
+
raise ImportError("cannot load the openc166 library:\n " + "\n ".join(problems))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
lib = _load()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _declare(name, restype, *argtypes):
|
|
101
|
+
f = getattr(lib, name)
|
|
102
|
+
f.restype = restype
|
|
103
|
+
f.argtypes = list(argtypes)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
_p = ctypes.c_void_p
|
|
107
|
+
_size = ctypes.c_size_t
|
|
108
|
+
_int = ctypes.c_int
|
|
109
|
+
_str = ctypes.c_char_p
|
|
110
|
+
|
|
111
|
+
_declare("oc166_api_version", _int)
|
|
112
|
+
_declare("oc166_version", _str)
|
|
113
|
+
_declare("oc166_options_new", _p)
|
|
114
|
+
_declare("oc166_options_free", None, _p)
|
|
115
|
+
_declare("oc166_options_set_cpu", _int, _p, _str)
|
|
116
|
+
_declare("oc166_options_set_dialect", _int, _p, _str)
|
|
117
|
+
_declare("oc166_options_add_control", _int, _p, _str)
|
|
118
|
+
_declare("oc166_options_set_origin", _int, _p, ctypes.c_int64)
|
|
119
|
+
_declare("oc166_options_add_include_dir", _int, _p, _str)
|
|
120
|
+
_declare("oc166_options_set_section_address", _int, _p, _str, ctypes.c_int64)
|
|
121
|
+
_declare("oc166_assemble", _p, _p, _str, _size, _str)
|
|
122
|
+
_declare("oc166_result_free", None, _p)
|
|
123
|
+
_declare("oc166_result_error_count", _size, _p)
|
|
124
|
+
_declare("oc166_result_warning_count", _size, _p)
|
|
125
|
+
_declare("oc166_result_cpu", _str, _p)
|
|
126
|
+
_declare("oc166_result_diagnostic_count", _size, _p)
|
|
127
|
+
_declare("oc166_result_diagnostic", _int, _p, _size, ctypes.POINTER(Diagnostic))
|
|
128
|
+
_declare("oc166_result_chunk_count", _size, _p)
|
|
129
|
+
_declare("oc166_result_chunk", _int, _p, _size, ctypes.POINTER(Chunk))
|
|
130
|
+
_declare("oc166_result_image", _int, _p, _int, ctypes.c_int64, ctypes.c_uint8, ctypes.POINTER(Image))
|
|
131
|
+
_declare("oc166_result_intel_hex", _str, _p, _int)
|
|
132
|
+
_declare("oc166_result_srec", _str, _p, _int, _int)
|
|
133
|
+
_declare("oc166_result_last_error", _str, _p)
|
|
134
|
+
_declare("oc166_result_listing", _str, _p)
|
|
135
|
+
_declare("oc166_result_symbol_count", _size, _p)
|
|
136
|
+
_declare("oc166_result_symbol", _int, _p, _size, ctypes.POINTER(Symbol))
|
|
137
|
+
_declare("oc166_result_section_count", _size, _p)
|
|
138
|
+
_declare("oc166_result_section", _int, _p, _size, ctypes.POINTER(Section))
|
|
139
|
+
_declare("oc166_assemble_many", _p, _p, _size, ctypes.POINTER(_str), ctypes.POINTER(_size), ctypes.POINTER(_str))
|
|
140
|
+
_declare("oc166_result_module_count", _size, _p)
|
|
141
|
+
_declare("oc166_result_module", _str, _p, _size)
|
|
142
|
+
_declare("oc166_result_symbol_module", _int, _p, _size)
|
|
143
|
+
_declare("oc166_result_symbol_exported", _int, _p, _size)
|
|
144
|
+
_declare("oc166_result_section_class", _str, _p, _size)
|
|
145
|
+
_declare("oc166_result_section_group", _str, _p, _size)
|
|
146
|
+
_declare(
|
|
147
|
+
"oc166_result_section_bits", _int, _p, _size, ctypes.POINTER(_int), ctypes.POINTER(ctypes.c_int64)
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if lib.oc166_api_version() != API_VERSION:
|
|
151
|
+
raise ImportError(
|
|
152
|
+
"the openc166 library has interface version {}, this module needs {}".format(
|
|
153
|
+
lib.oc166_api_version(), API_VERSION
|
|
154
|
+
)
|
|
155
|
+
)
|
openc166/openc166.dll
ADDED
|
Binary file
|
openc166/py.typed
ADDED
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openc166
|
|
3
|
+
Version: 0.1.15
|
|
4
|
+
Summary: Keil A166-compatible assembler for the Infineon C166/C167 microcontrollers
|
|
5
|
+
Keywords: c166,c167,st10,assembler,keil,a166,infineon,ecu
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: MacOS
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Assembly
|
|
14
|
+
Classifier: Programming Language :: C++
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Software Development :: Assemblers
|
|
17
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Project-URL: Homepage, https://github.com/Dante383/openc166
|
|
20
|
+
Project-URL: Issues, https://github.com/Dante383/openc166/issues
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# openc166
|
|
25
|
+
|
|
26
|
+
An open-source assembler for the Infineon C166 and C167 microcontroller
|
|
27
|
+
family (including ST10 parts), compatible with the Keil A166 assembler.
|
|
28
|
+
|
|
29
|
+
It turns Keil-style assembly source into a **raw binary image** at a fixed
|
|
30
|
+
address, or into an Intel HEX or Motorola S-record file. That suits small
|
|
31
|
+
programs loaded into RAM or flash and started directly, such as ECU
|
|
32
|
+
payloads, bootstrap loaders, and test code. No linker or object-file
|
|
33
|
+
converter is needed.
|
|
34
|
+
|
|
35
|
+
openc166 runs on Linux, Windows and macOS. It needs only a C++20 compiler and
|
|
36
|
+
CMake.
|
|
37
|
+
|
|
38
|
+
## Status
|
|
39
|
+
|
|
40
|
+
Early development (version 0.x). What works today:
|
|
41
|
+
|
|
42
|
+
- **The complete C166 and C167 instruction set**, with all addressing modes.
|
|
43
|
+
Every instruction form is checked byte for byte against Keil A166 V5.38:
|
|
44
|
+
over 5,000 checks of code bytes, addresses and symbol values, all
|
|
45
|
+
identical.
|
|
46
|
+
- Keil expression syntax: operators, `HIGH`/`LOW`, `SOF`/`SEG`/`PAG`/`POF`,
|
|
47
|
+
`BOF`, `DATA3`/`DATA4`/`DATA8`/`DATA16`, character constants, bit
|
|
48
|
+
addresses such as `PSW.3` or `0FD00H.5`, and extended-SFR checks inside
|
|
49
|
+
`EXTR` sequences.
|
|
50
|
+
- Sections with an address (`SECTION CODE AT 0E000H`) and without one.
|
|
51
|
+
openc166 places sections without `AT` as Keil's L166 linker would, or at
|
|
52
|
+
addresses you give (`--section NAME=ADDR`), so code that leaves placement
|
|
53
|
+
to the linker can still be assembled straight to a binary. `DGROUP` and
|
|
54
|
+
`CGROUP` keep sections in one data page or code segment, as in L166.
|
|
55
|
+
`BIT` sections and `DBIT` hold bit variables, placed bit by bit as L166
|
|
56
|
+
places them.
|
|
57
|
+
- `PROC`/`ENDP`, `ORG`, `EVEN`, `EQU`/`SET`/`BIT`, `DB`/`DW`/`DD`,
|
|
58
|
+
`DS`/`DSB`/`DSW`/`DSD`, and `DPPTR`/`DSPTR`/`DBPTR`, including Keil's
|
|
59
|
+
automatic alignment of word data.
|
|
60
|
+
- Segmented mode (`$SEGMENTED`): `ASSUME`, `DPPn:` prefixes, and Keil's
|
|
61
|
+
rules for which data page register an address goes through.
|
|
62
|
+
- `DEFR`/`DEFA`/`DEFB`, the register-definition directives that register
|
|
63
|
+
files use, with Keil's rules for repeated definitions.
|
|
64
|
+
- A built-in `REG167.INC` register file, written from Infineon's manuals, so
|
|
65
|
+
`$INCLUDE(REG167.INC)` works without Keil installed.
|
|
66
|
+
- Keil's rules for label arithmetic, which decide between short and long
|
|
67
|
+
instruction forms.
|
|
68
|
+
- Generic `JMP` and `CALL`: like Keil, openc166 picks the shortest
|
|
69
|
+
instruction that reaches the target and shortens forward jumps over
|
|
70
|
+
several passes. Label types (`NEAR`, `FAR`, `WORD`, ...) and the `SHORT`
|
|
71
|
+
and type operators (`FAR x`, `WORD PTR x`, ...) work as in Keil.
|
|
72
|
+
- `$INCLUDE` and `$INCDIR`, with Keil's search order and listing format.
|
|
73
|
+
- Conditional assembly: `$SET`/`$RESET`, `$IF`/`$ELSEIF`/`$ELSE`/`$ENDIF`
|
|
74
|
+
and the `IF`/`ELSEIF`/`ELSE`/`ENDIF` statements, with Keil's quirks (only
|
|
75
|
+
bit 0 of a condition counts) and listing format.
|
|
76
|
+
- `LIT` text replacement (`PORT LIT 'R2'`), with Keil's rules for where
|
|
77
|
+
names are replaced.
|
|
78
|
+
- Keil's standard macros: `MACRO`/`ENDM` with parameters and `LOCAL`
|
|
79
|
+
labels, `REPT`, `IRP`, `IRPC`, `EXITM`, `NUL`, `&` and `%`, with Keil's
|
|
80
|
+
rules for splitting arguments. `$GEN`/`$NOGEN` choose whether the listing
|
|
81
|
+
shows expanded lines.
|
|
82
|
+
- The `$MOD167`, `$CASE`, `$SEGMENTED` and `$TABS` controls, with Keil's
|
|
83
|
+
rule that these come before the first statement. Listing-only controls
|
|
84
|
+
are accepted and ignored.
|
|
85
|
+
- Keil error and warning codes (A74, A111, ...), so messages line up with
|
|
86
|
+
the Keil manual.
|
|
87
|
+
- Raw binary, Intel HEX and Motorola S-record output. The HEX records match
|
|
88
|
+
Keil OH166's `H167` format.
|
|
89
|
+
- Several source files linked into one program, as L166 does: `EXTERN`,
|
|
90
|
+
`PUBLIC` and `GLOBAL` symbols, and sections of the same name combined
|
|
91
|
+
across files. Each file is assembled separately (a "module"), so a name
|
|
92
|
+
that is not `PUBLIC` stays private to its file.
|
|
93
|
+
- Register banks (`REGBANK`/`REGDEF`), placed in `0FC00H..0FDFFH` as L166
|
|
94
|
+
places them, and the `NAME`, `SSKDEF`, `PECDEF` and `TYPEDEC` directives.
|
|
95
|
+
- **TASKING sources** (`--dialect tasking`): the common part of TASKING's
|
|
96
|
+
m166 preprocessor (`@SET`, `@EVAL`, `@IF`/`@ELSE`/`@ENDI`, `@DEFINED`),
|
|
97
|
+
TASKING's controls and `STDNAMES` register files, and the places where
|
|
98
|
+
its assembler differs from Keil's. Infineon's MiniMon monitor and flash
|
|
99
|
+
drivers, which gkflasher's ECU code comes from, rebuild byte for byte
|
|
100
|
+
from their TASKING sources. See
|
|
101
|
+
[docs/tasking.md](https://github.com/Dante383/openc166/blob/main/docs/tasking.md).
|
|
102
|
+
- A Python package and a C API, for tools that assemble code themselves.
|
|
103
|
+
|
|
104
|
+
Not done yet:
|
|
105
|
+
|
|
106
|
+
- Keil's MPL macro language (`%DEFINE` and the like); put `$NOMPL` at the
|
|
107
|
+
top of the file to use `%` as text
|
|
108
|
+
- OMF-166 object files and libraries
|
|
109
|
+
- TASKING's m166 macros with parameters (`@DEFINE`), loops and string
|
|
110
|
+
functions
|
|
111
|
+
|
|
112
|
+
See [docs/compatibility.md](https://github.com/Dante383/openc166/blob/main/docs/compatibility.md) for exact behaviour and
|
|
113
|
+
the few places where openc166 deliberately differs from Keil.
|
|
114
|
+
|
|
115
|
+
## Building
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
cmake -S . -B build
|
|
119
|
+
cmake --build build
|
|
120
|
+
ctest --test-dir build
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The program is `build/cli/openc166`. On Windows with Visual Studio it is
|
|
124
|
+
under `build\cli\Debug\` or `build\cli\Release\`. The shared library with
|
|
125
|
+
the C API is built in `build/src/`.
|
|
126
|
+
|
|
127
|
+
## Usage
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
openc166 payload.a66 # writes payload.bin
|
|
131
|
+
openc166 payload.a66 -o out.bin -l out.lst # choose names, add a listing
|
|
132
|
+
openc166 payload.a66 -o payload.hex # Intel HEX
|
|
133
|
+
openc166 payload.a66 -o payload.s19 # S-records (S1)
|
|
134
|
+
openc166 main.a66 lib.a66 -o payload.bin # link two source files
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Several source files are linked into one program, the way Keil's L166
|
|
138
|
+
linker joins the object files of A166:
|
|
139
|
+
|
|
140
|
+
- An `EXTERN` name in one file takes the value of the `PUBLIC` or `GLOBAL`
|
|
141
|
+
name in another. A name that no file exports is an error.
|
|
142
|
+
- Sections with the same name and class are joined into one: the parts of
|
|
143
|
+
a `PUBLIC` or `GLOBAL` section follow one another, the parts of a
|
|
144
|
+
`COMMON` section lie on top of each other.
|
|
145
|
+
- Without `-o`, the output is named after the first file. With `-l`, the
|
|
146
|
+
listing has one part per file and a symbol table that names the file
|
|
147
|
+
each symbol comes from.
|
|
148
|
+
- Code outside sections starts at `--origin` in every file, so normally
|
|
149
|
+
each file puts its code in sections.
|
|
150
|
+
|
|
151
|
+
`$INCLUDE` looks in each file's own directory.
|
|
152
|
+
[docs/compatibility.md](https://github.com/Dante383/openc166/blob/main/docs/compatibility.md#linking-several-files)
|
|
153
|
+
has the exact rules.
|
|
154
|
+
|
|
155
|
+
The output format follows the extension of the `-o` file: `.hex`, `.h86`
|
|
156
|
+
and `.ihx` give Intel HEX; `.srec`, `.s19`, `.s28`, `.s37`, `.mot` and
|
|
157
|
+
`.sx` give S-records; anything else gives a raw binary. `--format` chooses
|
|
158
|
+
it explicitly.
|
|
159
|
+
|
|
160
|
+
A raw binary starts at the lowest address used. For example, code in
|
|
161
|
+
`SECTION CODE AT 0E000H` produces a file whose first byte belongs at
|
|
162
|
+
`0E000H`. Gaps between pieces of code are filled. HEX and S-record files
|
|
163
|
+
carry the address of every record, so they hold only the bytes used.
|
|
164
|
+
|
|
165
|
+
| Option | Meaning |
|
|
166
|
+
|---|---|
|
|
167
|
+
| `-f, --format FMT` | `bin`, `hex` or `srec` (default: from the output extension) |
|
|
168
|
+
| `--origin ADDR` | address of code outside sections, and the lowest address for sections without `AT` (default 0) |
|
|
169
|
+
| `--section NAME=ADDR` | put section `NAME`, which has no `AT`, at `ADDR` (may be repeated) |
|
|
170
|
+
| `--base ADDR` | binary: address of the first byte of the file |
|
|
171
|
+
| `--fill BYTE` | binary: value of gaps between pieces of code (default `0FFh`) |
|
|
172
|
+
| `--record-size N` | HEX and S-records: data bytes per record (default 16) |
|
|
173
|
+
| `--cpu c166\|c167` | instruction set (`$MOD167` in the source also selects C167) |
|
|
174
|
+
| `-I DIR` | also search `DIR` for `$INCLUDE` files (may be repeated) |
|
|
175
|
+
| `--dialect D` | `keil` (default) or `tasking`, for TASKING a166 sources |
|
|
176
|
+
| `-c, --control TEXT` | controls read before the sources, e.g. `-c "CASE STDNAMES(reg167.def)"` (may be repeated) |
|
|
177
|
+
|
|
178
|
+
Sections without `AT` go in order of first appearance to the first free,
|
|
179
|
+
suitably aligned place from the origin on, as L166 places them by default.
|
|
180
|
+
`--section` overrides that for chosen sections, like L166's `SECTIONS`
|
|
181
|
+
control. The listing ends with a table of where each section went.
|
|
182
|
+
[examples/sections.a66](https://github.com/Dante383/openc166/blob/main/examples/sections.a66) shows both.
|
|
183
|
+
|
|
184
|
+
Intel HEX files use extended linear address records (type 04) for addresses
|
|
185
|
+
above 64 KB, like OH166 with its `H167` option. S-records use S1, S2 or S3
|
|
186
|
+
records by extension (`.s19`, `.s28`, `.s37`); otherwise the smallest kind
|
|
187
|
+
that holds every address.
|
|
188
|
+
|
|
189
|
+
The exit status is 0 on success, 1 on assembly errors (no output is
|
|
190
|
+
written), and 2 for usage or file errors. Messages use the format
|
|
191
|
+
`file:line:column: error A74: ...`.
|
|
192
|
+
|
|
193
|
+
[examples/payload.a66](https://github.com/Dante383/openc166/blob/main/examples/payload.a66) is a short, commented example.
|
|
194
|
+
[examples/mtos/](https://github.com/Dante383/openc166/tree/main/examples/mtos) holds real ECU payloads for Siemens SIMK43
|
|
195
|
+
ECUs, taken from gkflasher.
|
|
196
|
+
|
|
197
|
+
## Python
|
|
198
|
+
|
|
199
|
+
```sh
|
|
200
|
+
pip install openc166 # or "pip install ." in a checkout
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Wheels are published for Linux, Windows and macOS on x86 and ARM; other
|
|
204
|
+
systems build from source, which needs CMake and a C++20 compiler. To get
|
|
205
|
+
just the `openc166` command, `pipx install openc166` works too.
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
import openc166
|
|
209
|
+
|
|
210
|
+
result = openc166.assemble("""
|
|
211
|
+
$MOD167
|
|
212
|
+
PROG SECTION CODE AT 0E000H
|
|
213
|
+
MOV R4, #0BEEFH
|
|
214
|
+
RET
|
|
215
|
+
PROG ENDS
|
|
216
|
+
END
|
|
217
|
+
""")
|
|
218
|
+
image = result.image() # raises openc166.AssemblyError if there are errors
|
|
219
|
+
image.base # 0xE000, the address of image.data[0]
|
|
220
|
+
image.data # b'\xe6\xf4\xef\xbe\xcb\x00'
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
- `openc166.assemble(source, *, file_name=None, cpu=None, origin=0, include_dirs=(), sections=None, dialect="keil", controls=())`
|
|
224
|
+
assembles a `str` or `bytes`. `sections` gives addresses for sections
|
|
225
|
+
without `AT`, e.g. `{"VARS": 0xF600}`. `dialect="tasking"` reads TASKING
|
|
226
|
+
source; `controls` is a list of controls read before the source, as with
|
|
227
|
+
`-c`. `openc166.assemble_file(path, ...)`
|
|
228
|
+
reads a file; `$INCLUDE` then also searches the file's directory.
|
|
229
|
+
- The result has `ok`, `diagnostics` (each with `severity`, `code`,
|
|
230
|
+
`message`, `file`, `line`, `column`; `str()` gives the usual one-line
|
|
231
|
+
message), `errors`, `warnings`, `chunks` (address and bytes of each piece
|
|
232
|
+
of code), `symbols` (a dict of name to `Symbol`), `symbol(name)` (lookup
|
|
233
|
+
ignoring case), `sections` (a dict of name to `Section`: `type`, `align`,
|
|
234
|
+
`address`, `size`, and `placement`, which is `"at"`, `"given"` or
|
|
235
|
+
`"located"`), `listing` and `cpu`.
|
|
236
|
+
- `openc166.assemble_many({"main.a66": text1, "lib.a66": text2}, ...)`
|
|
237
|
+
links several sources into one program, like several files on the
|
|
238
|
+
command line; `openc166.assemble_files([path1, path2], ...)` reads them
|
|
239
|
+
from files. Each `Symbol` then says which file defines it (`module`) and
|
|
240
|
+
whether it is `PUBLIC` or `GLOBAL` (`exported`). `result.modules` lists
|
|
241
|
+
the file names; `result.symbols` holds one symbol per name (the exported
|
|
242
|
+
one if there is one), `result.all_symbols` all of them. Each `Section`
|
|
243
|
+
has its `class_name` and `group`.
|
|
244
|
+
- `result.image(base=None, fill=0xFF)` lays all code out as one block, like
|
|
245
|
+
the command-line tool.
|
|
246
|
+
- `result.intel_hex(record_size=16)` and
|
|
247
|
+
`result.srec(record_size=16, address_size=None)` return Intel HEX and
|
|
248
|
+
S-record text (`address_size` 2, 3 or 4 picks S1, S2 or S3 records).
|
|
249
|
+
|
|
250
|
+
The package needs Python 3.9 or newer. It also installs the `openc166`
|
|
251
|
+
command. It loads the library through `ctypes`, so one wheel works with
|
|
252
|
+
every Python version.
|
|
253
|
+
|
|
254
|
+
## C API
|
|
255
|
+
|
|
256
|
+
[include/openc166/openc166.h](https://github.com/Dante383/openc166/blob/main/include/openc166/openc166.h) declares a plain C
|
|
257
|
+
interface, exported by the shared library (`libopenc166.so`,
|
|
258
|
+
`libopenc166.dylib` or `openc166.dll`). Any language that can call C can
|
|
259
|
+
use it: `oc166_assemble` assembles one source, `oc166_assemble_many`
|
|
260
|
+
links several. The header documents every function; the Python package is
|
|
261
|
+
an example of a complete binding.
|
|
262
|
+
|
|
263
|
+
## Testing
|
|
264
|
+
|
|
265
|
+
- `ctest --test-dir build` runs the unit tests, the C API test and the
|
|
266
|
+
Python package tests, and assembles the examples, checking every byte.
|
|
267
|
+
- `tests/acceptance/minimon.py` rebuilds Infineon's MiniMon from its
|
|
268
|
+
TASKING sources and compares the result with its HEX files. MiniMon is
|
|
269
|
+
not included; set `MINIMON_DIR` to its directory (the one that holds
|
|
270
|
+
`MCPROG` and `Driver`) and `ctest` runs it too.
|
|
271
|
+
- `tests/diff/` contains the differential test against Keil A166. Keil is
|
|
272
|
+
**not** included. If you have your own copy, run:
|
|
273
|
+
|
|
274
|
+
```sh
|
|
275
|
+
KEIL_A166=/path/to/A166.EXE python3 tests/diff/keil_diff.py tests/diff/cases/*.a66
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
It compares every line: address, code bytes, warnings and errors, and
|
|
279
|
+
the final value of every symbol. Add `--image` to also link Keil's output
|
|
280
|
+
with L166 and OH166 and compare the complete memory image, and `--codes`
|
|
281
|
+
to list lines where both assemblers report an error but with different
|
|
282
|
+
codes. Lines marked `; DIVERGES:` are deliberate differences (see
|
|
283
|
+
[docs/compatibility.md](https://github.com/Dante383/openc166/blob/main/docs/compatibility.md)) and are skipped.
|
|
284
|
+
|
|
285
|
+
Given a directory instead of a file, it treats each `.a66` file in it as
|
|
286
|
+
one module, links Keil's objects with L166, and compares the linked
|
|
287
|
+
image and the linker's errors with openc166's:
|
|
288
|
+
|
|
289
|
+
```sh
|
|
290
|
+
KEIL_A166=/path/to/A166.EXE python3 tests/diff/keil_diff.py tests/diff/cases/link/*/
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
On Linux and macOS, Keil runs under Wine. To generate test lines that
|
|
294
|
+
cover the whole instruction set, run
|
|
295
|
+
`tests/diff/gen_sweep.py build/generated/openc166/isa.json c167 > sweep.a66`.
|
|
296
|
+
|
|
297
|
+
The test inputs were written independently for this project. Keil's output
|
|
298
|
+
is used only for the local comparison and is never stored in the
|
|
299
|
+
repository.
|
|
300
|
+
|
|
301
|
+
## License
|
|
302
|
+
|
|
303
|
+
MIT; see [LICENSE](https://github.com/Dante383/openc166/blob/main/LICENSE). The bundled [doctest](https://github.com/Dante383/openc166/tree/main/third_party/doctest)
|
|
304
|
+
test framework is also MIT.
|
|
305
|
+
|
|
306
|
+
openc166 is not affiliated with Arm/Keil or Infineon. "Keil", "A166",
|
|
307
|
+
"C166", "C167" and "TASKING" are trademarks of their respective owners. The
|
|
308
|
+
names are used only to describe compatibility.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
openc166/__init__.py,sha256=in9eCKDEZSj-yuYzx51NSBGqeZYxlOnBTlrw5KVsy1I,21856
|
|
2
|
+
openc166/_native.py,sha256=O1bnYN2NNHmGaerUSNNHsaukP2ThBZNsEnQNLuARAA8,5247
|
|
3
|
+
openc166/openc166.dll,sha256=Y7XhMPfCtE33a1HPDD6e7cKi_jD0z6romKPN8Hsybew,837120
|
|
4
|
+
openc166/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
openc166-0.1.15.data/scripts/openc166.exe,sha256=INjZtJM-SqgTgF9k9bNv18TUDQAw2VUNHb7sCcdxbzo,869376
|
|
6
|
+
openc166-0.1.15.dist-info/METADATA,sha256=Nv6tPorlUm59VHpUwPs0jR5YLnf4x0BEDKShmPwWEn4,15056
|
|
7
|
+
openc166-0.1.15.dist-info/WHEEL,sha256=MavvTN48CE-QbhQ_kyQzVg79Yjk576_oKsIlJsE4vMI,102
|
|
8
|
+
openc166-0.1.15.dist-info/licenses/LICENSE,sha256=e2iZ1haqvx1-l9jomaSPVVH23WXENsT65NNPq-yL9Kk,1091
|
|
9
|
+
openc166-0.1.15.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Piotr Zdolski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|