Device-driver codegen — architecture (arc42)¶
Architecture documentation for the device-driver codegen toolchain (see Device-driver codegen), structured per the arc42 template and encoded with sphinx-needs using the useblocks “x-as-code” arc42 conventions.
Each architectural element :refines: or :implements: a parent
requirement so the trace is preserved end-to-end.
This chapter is split across pages (see the toctree): the framing sections §1–§3 (goals, constraints, context and scope — including the crate-dependency-graph mermaid) live here on the index; the solution strategy and its ADRs (§4) live in Solution strategy; the building-block decomposition (§5) lives in Building block view; the runtime build-time and bring-up sequences (§6) live in Runtime view; the crosscutting tail (§7 deployment, §8 crosscutting concepts, §10 quality requirements, §12 glossary) lives in Crosscutting concepts, deployment, quality, and glossary; the decisions pointer (§9) lives in Architectural decisions; and the risks and technical debt (§11) live in Risks and technical debt.
1. Introduction and goals¶
The toolchain’s reason-to-exist is build-time monomorphisation of
fieldbus device drivers: vendor-supplied ESI XML files describe each
EtherCAT device’s PDOs, mailbox, and OD; we want strongly-typed
decode_inputs / encode_outputs code per device, with zero XML
at runtime and no hand-written boilerplate per terminal.
Quality goals capture the qualities the architecture is optimised for.
The same set of ESI inputs (modulo file ordering) shall produce a byte-identical generated module across machines, toolchain versions, and clock walls. Generation order, hash-map iteration order, and timestamp inclusion are explicitly excluded as sources of nondeterminism. Required so the generated file is reviewable in diffs and cachable in CI. |
Each crate in the toolchain shall depend only on crates to its left in the parse → codegen → tooling chain (see Toolchain layering (crate d... (ARCH_0050)). Crossover dependencies — e.g. the parser reaching for ethercrab types, the build helper bypassing the codegen layer — are rejected. The layering is the design; collapsing it deletes its value. |
A consumer of the generated modules shall pay no runtime cost
for the codegen layer’s existence: no XML parse, no allocation
for OD tables when |
The |
2. Constraints¶
The build helper shall live within cargo’s |
The ethercrab backend shall target the ethercrab API exposed by
|
Generated |
Scope narrowed (2026-05). The parser ( |
The ESI schema ( |
3. Context and scope¶
Four layers, strict left-to-right dependency. Each crate has one
job and depends only on crates to its left. The
graph LR
subgraph Parse["1. Parse layer"]
P["ethercat-esi<br/>XML → typed IR"]
end
subgraph Gen["2. Codegen layer"]
G["ethercat-esi-codegen<br/>IR → TokenStream"]
B["ethercat-esi-codegen-ethercrab<br/>concrete backend"]
end
subgraph RT["3. Runtime trait"]
RTC["ethercat-esi-rt<br/>EsiDevice / EsiConfigurable"]
end
subgraph Tool["4. Tooling layer"]
BR["ethercat-esi-build<br/>build.rs glue"]
CLI["ethercat-esi-cli<br/>cargo esi expand / list"]
VER["ethercat-esi-verify<br/>diff ESI vs SII .bin"]
end
subgraph Cons["Consumers"]
USER["any ethercrab user<br/>(includes generated code)"]
SCE["taktora-connector-ethercat<br/>thin EsiDevice adapter"]
end
P --> G
G --> B
B --> RTC
B --> BR
B --> CLI
P --> VER
RTC --> BR
BR --> USER
USER --> SCE
|
The toolchain runs entirely at build time. Runtime consumers
only see the generated module and link against
|