Génération de code › Génération de code

Génération de code

Cette page n’est pas encore traduite : voici la version anglaise.

Code generation

Generators of your own are in generators.md; every option of archec gen is in commands.md.

archec gen main.arche --target <t> [-o generated] writes source code from the model. Files you own (app.rs, main.rs, Cargo.toml) are written once and never overwritten; --check fails when a generated file is out of date (for CI). In VS Code: Arche: Generate code….

target what from
embassy Rust / Embassy: one crate per binary. arche.rs (regenerated): the model's types and messages, a Channel per link (depth, overflow) or a Signal (buffer sample), an Out handle and a behaviour trait per part, one task per thread (a Ticker for period + step, select over the inputs), encode/decode and a BusFrame per routed message (CAN id = the route's id), spawn_all. hal std|stm32|rp|nrf|esp32 and chip "…" on the processor pick the HAL; hal rp with threads on two cores starts core 1. deployment
dbc CAN DBC, one file per bus: a frame per routed message, a signal per field, in the same layout as the Embassy codec; a message with a wire format (at, bits, scale, byte-order…, see the spec) has each signal on its bits, with its start bit, byte order, sign, factor and offset deployment routes
wire the codecs of the messages that say their wire format, with their types: wire/c/arche_wire.h/.c (C99, no heap, no libm), wire/rust/ (a no_std crate), wire/go/ (package archewire), wire/python/arche_wire.py (dataclasses). Encode puts each field on its bits, decode fails on a short frame, a wrong checksum or an unknown enum value; the four write the same bytes. --target c, embassy and go use the same code for these messages messages
aadl AADL v2, aadl/<deployment>.aadl, one package for OSATE: data with Data_Model properties per message and type; a processor per processor (a virtual processor per core, a memory for its ram), a bus (Transmission_Time from bitrate); a virtual processor per container, bound to the cores of its cpuset, its processes bound to it; a process per process (or per binary with none) and a thread per thread with Dispatch_Protocol, Period, Deadline, Compute_Execution_Time (0 ms .. wcet, else the step's cost), Priority, Stack_Size; event data ports (a data port for buffer sample, Queue_Size from depth), a device per linked part on no thread, a port connection per link (Latency from latency), Actual_Processor_Binding to the core, Actual_Connection_Binding to the route's bus. A port carrying several messages is typed by a union of them deployment
arxml AUTOSAR Classic ARXML (R4, AUTOSAR_00049), arxml/<deployment>.arxml: an IMPLEMENTATION-DATA-TYPE per type and message (an enum is uint8 with a TEXTTABLE COMPU-METHOD), a SENDER-RECEIVER-INTERFACE per message, an APPLICATION-SW-COMPONENT-TYPE per part with behaviour (SENSOR-ACTUATOR for sensors and hardware) with a P-/R-PORT-PROTOTYPE per port and message, RUNNABLE-ENTITYs on a TIMING-EVENT (thread period + step), DATA-RECEIVED-EVENTs (handlers, receives) and an INIT-EVENT (fn main); the system as a COMPOSITION-SW-COMPONENT-TYPE with an ASSEMBLY-SW-CONNECTOR per link and message; per message routed over a CAN bus, I-SIGNALs in the DBC's layout, an I-SIGNAL-I-PDU, a CAN-FRAME and a CAN-FRAME-TRIGGERING with the route's CAN id, with its PDU and signal triggerings (a structure also has a signal group); an ECU-INSTANCE per processor a thread runs on, with a CAN connector per bus and a frame, PDU and signal port per frame it sends or receives; the SYSTEM, with its root composition, the SWC-TO-ECU-MAPPING of each part, and each routed message's data element mapped on its signals, field by field for a structure. Validates against AUTOSAR's AUTOSAR_00049.xsd (the examples' deployments are checked with xmllint when AUTOSAR_XSD names it). Left out: send and receive points, units and data constraints, and arrays, optional fields and unions in the field-by-field mapping deployment
arxml-adaptive AUTOSAR Adaptive ARXML (R20-11, AUTOSAR_00049), four files in adaptive/<deployment>/. Design.arxml: the data types as arxml writes them; a SERVICE-INTERFACE per interface a port provides or requires — what its provider sends as EVENTs, its operations as METHODs (their ARGUMENTs, a result as an OUT argument, FIRE-AND-FORGET without one), a message the provider only receives as a fire-and-forget METHOD, a message it both sends and receives as a FIELD with getter, setter and notifier — and one of EVENTs per Ethernet out port that sends without an interface; per process (a binary without processes: the binary) an ADAPTIVE-APPLICATION-SW-COMPONENT-TYPE with a P-/R-PORT-PROTOTYPE per service its parts provide or require, and an EXECUTABLE whose ROOT-SW-COMPONENT-PROTOTYPE it is. MachineManifest.arxml: a MACHINE-DESIGN (an ETHERNET-COMMUNICATION-CONNECTOR) and a MACHINE (its PROCESSOR and cores) per processor with an operating system that is not an RTOS or bare metal, the machine function group MachineFG (Startup, Running, Shutdown), an ETHERNET-CLUSTER with a NETWORK-ENDPOINT per machine (attr ip = "10.0.0.2" on the processor or a binary on it, else 192.168.0.10 and up). ExecutionManifest.arxml: a PROCESS per process of its EXECUTABLE, started in MachineFG.Running with a STARTUP-CONFIG — the policy (fifo, rr, else other) and priority of its highest-priority thread. ServiceInstanceManifest.arxml: a SOMEIP-SERVICE-INTERFACE-DEPLOYMENT per service with the ids of --target someip (service, version, methods, events, event group; a field's getter and setter numbered after the methods, its notifier the message's event id), a PROVIDED- or REQUIRED-SOMEIP-SERVICE-INSTANCE per port, its SERVICE-INSTANCE-TO-PORT-PROTOTYPE-MAPPING to the process and a SOMEIP-SERVICE-INSTANCE-TO-MACHINE-MAPPING per machine. A binary on a microcontroller is a Classic ECU (arxml) and is left out, and a service it provides has no provided instance; the notes say which. Left out: UDP/TCP ports and service discovery timings, E2E, security, persistency, platform health management, StdCppImplementationDataTypes. The examples' deployments are checked with xmllint against AUTOSAR_00049.xsd when AUTOSAR_XSD names it deployment
bom bill of materials: bom/bom.csv, a row per definition (and per part with none) with its parts, kind, quantity ([n] copies counted) and each number a budget adds up — mass, power, cost, ram and every name a budget is written for — then totals, overall and per system; bom/bom.md, the same table and the roll-up of each part with everything beneath it. A number in two units gets a column per unit: nothing is converted root systems (no deployment needed)
icd interface control documents in Markdown: icd/<port-type or interface>.md and icd/<bus>.md, with each message's fields (type, unit, range, default, description), byte layout, the links that provide and consume it (latency, buffer, route, transport time), on a bus the frames with CAN id, period and frame time, the types used, and the requirements traced to the parts, messages and interface (or naming them); icd/index.md lists them root systems, deployment routes
containerd for containerd through nerdctl: containers/<container>/Containerfile, a multi-stage build per binary (C from c/<binary>, Rust from embassy/<binary>; generate those into the same directory, the build context) into a slim Debian image, with an entrypoint.sh when it runs several; containers/<processor>/compose.yaml for nerdctl compose up — image, platform (from the processor's isa or core-type), cpuset, mem_limit, network_mode, devices, cap_add, privileged, an rtprio ulimit at its highest real-time priority, environment; containers/deploy.sh builds every image (PUSH=1 pushes it) and says what to start on each processor deployment containers
k8s for Kubernetes / k3s, the images containerd builds: k8s/<container>.yaml, a Deployment of one replica pinned to its processor's node (nodeSelector kubernetes.io/hostname = the processor's name, - for _; kubernetes.io/arch from its isa), resources with requests = limits from its cpuset size and memory-limit (the Guaranteed class: exclusive cores with the kubelet's static CPU manager), hostNetwork for network host, privileged for capabilities all (else capabilities.add), each device mounted from the host (hostPath), its props as env; a deny-all NetworkPolicy for network none; k8s/kustomization.yaml for kubectl apply -k k8s. What Kubernetes cannot say (the exact cores, a bridge latency) is a note deployment containers
ros2 the messages as the ament interface package ros2/ (.msg, .srv); with a deployment that says packaging ros2, ros2_nodes/<binary>/ (rclcpp nodes around the parts' C modules, see ROS 2) and ros2_nodes/<deployment>_bringup/launch/<deployment>.launch.py; colcon build builds them data; deployment
gazebo the plant of a packaging ros2 deployment in Gazebo (gz sim Harmonic): gazebo/worlds/<system>.sdf (sensors, actuators, masses), gazebo/config/bridge.yaml (ros_gz_bridge), gazebo/scripts/sim_adapter.py, gazebo/launch/<system>_sim.launch.py (see Gazebo) system, deployment
mujoco the plant in MuJoCo: mujoco/<system>.xml (MJCF: bodies from the parts' mass, shape and joint, sensors, actuators) and mujoco/plant_bridge.py (the model's messages to and from it; archec sim --plant runs the model against it) (see MuJoCo) system
devicetree a Zephyr devicetree overlay per binary on a board's microcontroller: devicetree/<binary>/boards/<zephyr board>.overlay enables each CAN, UART, I2C and SPI connector of the board the platform links, on its pins (pinctrl: hal_stm32's usart2_tx_pa2 nodes, NRF_PSEL, the RP2040 and ESP32 pinmux macros; a part's own pins over the board's), at the bitrate of the link or the bus (bitrate, current-speed, clock-frequency), an MCP2515 under the SPI for a CAN on SPI, the I2C devices at their address; an alias per bus (arche-chassis-can) and zephyr,canbus; prj.conf with the drivers (written once). The C of --target c opens each CAN or UART bus by that alias under Zephyr (DEVICE_DT_GET(DT_ALIAS(arche_chassis_can)) in drivers.c) deployment on boards
renode the deployment's microcontrollers in Renode, without the boards: renode/<deployment>/<machine>.repl per processor with a binary, Renode's own platform for its chip (using "platforms/cpus/stm32h743.repl"; a chip it has none for is a note, see the boards); machines.resc loads each binary's ELF (where cargo build --release puts the Embassy crate's, a variable to change), a CANHub per CAN bus and a UARTHub per UART link of the platform, and <deployment>.resc starts it; <deployment>.robot for renode-test: the firmware starts its CAN controllers, and the test cases' frames — put on the bus for a part Renode does not run, counted by CAN id for one it runs. Linux processors are left out (a SocketCAN bridge, commented) deployment
ui-web the product's screens as a web app: ui-web/index.html, ui.js (the UI runtime, the web renderer, the browser simulator), ui.css, ui.json (the UI models, sample values, the sim program). It runs on its own from the folder: the model simulated in the page by default, or live values over a WebSocket with ?ws=ws://host:port (the protocol in ui) ui blocks
freertos, zephyr, posix the Arche app's RTOS skeleton (objects, tasks, priorities, stacks) threads / tasks
uppaal UPPAAL timed automata (model.xml) and queries (model.q). There is one template per part with a state machine, its states as locations. Composites and pseudo-states that move on at once are committed. A message a link brings is a broadcast channel: ? on the transition it triggers, ! where an action sends it, or an emitter template for a sender whose sends no transition places. Each within guarantee gets an observer with a clock, and each periodic thread of the deployment ticks. A guard on the component's own variables becomes a UPPAAL guard (see below); the rest of a guard is a comment and is dropped. Of the transitions of one state on one trigger, the first that can fire wins. The queries are A[] not deadlock, E<> P.S per state, and A[] not Obs.late per deadline. state machines, links, contracts
nusmv NuSMV / nuXmv, untimed: one MODULE per state machine (state, a free input event, a TRANS relation by the transitions, the first that can fire winning; one whose guard is not fully translated may fire or not) and CTLSPEC EF per state state machines
components the Arche app's component sources in Rust / C / C++, your code kept between generations (arche-cli gen) components

Monitors in the firmware (--monitors). archec gen --target c --monitors compiles the requirements into each binary of the deployment, the same checks archec replay makes on a log: an EARS requirement watches for its trigger (a message the part receives, a state it enters) and its response within its within (with none, right after the trigger is handled); a formal never/always/implies holds at every check; eventually C within 30ms holds by 30 ms after the binary starts, and A leads to B within 20ms each time A holds (or, for X receives M, M arrives) — the earliest answer awaited is the one that counts, so each takes one flag and one deadline: constant memory, constant time per check. With no within, eventually and leads to cannot fail at run time and are left out. A binary checks the requirements whose parts it runs. c/<binary>/monitors.h lists them; the wiring arms a monitor when the trigger arrives, before the part handles it, clears it when the part sends the message or reaches the state, and checks after each message and from a monitor thread. A violation calls arche_monitor_violation(req, at_ns), yours in monitor_hook.c (written once; it logs). No heap: one small struct per requirement. Define ARCHE_MONITOR_LOCK/UNLOCK as a critical section when the binary's threads preempt each other. With --trace as well, each verdict is also a trace record, arche-trace <ns> monitor <REQ> <1|0>: a failure always, a pass when a response arrives in time. --target embassy --monitors does the same in Rust: a Requirement enum, a Monitors struct behind a blocking mutex (a copy of each watched part's state, kept by its task), a monitor_task, and the MonitorHook trait, implemented by Hook in src/monitor_hook.rs (yours, written once).

One instant, deadlines and watchdogs. What happens at one instant happens in the order the specification gives — as in Lingua Franca, the model says it, not the machine: what is due first runs first, a part does one thing at a time, a message fires the innermost transition first, then the port's on-receive, then its handlers. The simulator (archec sim) and the C follow it. A port's or a transition's deadline with an on-deadline body runs that body instead, for a trigger the part starts on too late; a watchdog w { timeout 50ms; on-timeout … } runs out unless a body says kick w in time. Both are simulated and generated in C (arche_item_t.sent, <part>_kick, <part>_timeouts; the wiring's threads wait for the next timer) and in Rust: in the Embassy crate a channel into a part with a deadline carries (Instant, message), the task compares elapsed() with the deadline and calls on_deadline_<port> or on_deadline(t) instead; a part's watchdogs are a <Part>Watchdogs field (self.wd.kick(…)), and its task's select also waits on an embassy_time::Timer at the next one to run out, then calls on_timeout(w). With --trace, each is a line: arche-trace <ns> deadline <part>.<port or From->To> <µs late>, arche-trace <ns> watchdog <part>.<name> 0. archec difftest main.arche --seed 3 checks the C against the simulator: the same random inputs into both, the C built with a harness on a virtual clock, the two traces compared, the first difference shown.

Calls to another part. port.op(…) in the action language (an operation of an interface the port requires) is <part>_call_<port>_<op>(self, …) in C: the wiring gives it, calling the part at the other end of the link by which part asks; difftest checks it against the simulator. In Rust it is an Out method that sends a request to the provider's task (CALL_…, a channel) and awaits its reply (REPLY_…, a signal); in Go Call, in C++ a std::function member, both set by the binary's main (the specification).

Guards in UPPAAL and NuSMV. A variable of the component, such as var mode: Mode = MANUAL, is followed when two things hold. It must start at a value written in the model. Only the machine's transitions may change it, each time to a value written there too (/ self.mode = AUTO). It then becomes a small integer: the index of its value in the list of values it can take, which the export prints as a comment. A guard on it, such as [self.mode == MANUAL and not self.armed] or [self.tries < LIMIT], is translated exactly. Some variables are not followed, and the export notes each one:

  • one no transition sets is an input;
  • one other code also changes, such as a handler or a function;
  • one a transition sets to a computed value.

The same goes for the rest of a guard: a message field, a call or an or. Those parts are dropped, so the checked model allows more than the real one, never less. A value that a test step or the physics writes into a followed variable is not seen.

Thread priorities are enforced with one executor per priority level, so a more urgent level preempts the ones below it (a slow handler on a low-priority thread cannot hold up the 1 ms loop):

  • on a chip (hal stm32|nrf|rp|esp32), each level is an InterruptExecutor on a spare interrupt (stm32: UART4, UART5…; nrf: SWIn_EGUn; rp: SWI_IRQ_n; esp32: software interrupts 1–3, esp-hal's) at an interrupt priority in the same order, the most urgent level lowest-numbered, P0 left to the HAL. Change the interrupt in main.rs if your firmware uses it. More levels than the chip has priority bits (rp: 3): the least urgent ones share the last executor, and gen says so;
  • on a host (hal std), each level is an executor on its own OS thread at that priority (thread-priority; on Linux, SCHED_FIFO needs CAP_SYS_NICE);
  • tasks with no priority (bus tasks, threads without priority) run in thread mode, the lowest level; with none, the least urgent level takes thread mode. An isr with no priority is above every thread.

Within one level, and on a bare cortex-m skeleton, tasks are cooperative. archec check warns (handler-blocking) when a message handler on a periodic thread can hold its step past the deadline: it runs to its end first.

On a bus, an optional field is a presence byte then its value, a union a case byte then its largest case (the DBC multiplexes on it, or says in a comment when a signal counts). src/can.rs (yours once written) moves the frames between the bus channels and the hardware: Linux SocketCAN on a host (CAN FD when the bus is), bxCAN on CAN1 for hal stm32 (classic frames, up to 1 Mbit/s), the TWAI controller (TWAI0) for hal esp32 (classic frames, at the nearest of 125k/250k/500k/1M bit/s). Check the pins in main.rs. nRF chips, the RP2040 and the ESP32-C2 have no CAN controller, so the channels are left to your driver (an external one such as an MCP2515 on SPI). main.rs is yours once written: a crate generated before this does not start the levels; gen notes it.