Consystence
Start free
Back to blog
Updated 20 July 2026

Generated, not written: deterministic PLC code from the device model

Hand-written PLC code drifts from the HMI, the tag database and the docs — the same pump described in five places, each one diverging, then re-typed at the next site. We generate the control code from the same device model that draws the faceplate and feeds the historian, deterministically, so it cannot drift. Here is how.

Garry Thomas
ArchitectureAutomationEngineering

Update — 20 July 2026. The caveat at the end of this post is closed: the dam-pump-station program running the virtual PLCnext on our workshop rig is now a generated one. Structured Text has also returned to the pipeline’s output as a delivery format — the generated program emitted as IEC 61131-3 ST in PLCopen XML for controllers programmed through the vendor’s engineering tool — alongside the eCLR C# runtime target described below. The generator’s mapping has also grown since publication: eight catalogue types now resolve to six function-block types. The full story is in C#, not Structured Text.

Update — 15 July 2026. The generator no longer targets Structured Text on PLCnext: it now emits the controller program as eCLR C# directly — the same class of artifact our dam-pump-station proving run executed — and the hand-authored CSY_* ST library is frozen as the reference implementation. The determinism below is unchanged: same input, same code, every time. The reasoning is in C#, not Structured Text.

A pump on a dam station gets described more times than anyone admits. Once in the PLC program the integrator typed. Again in the HMI’s tag database. A third time in the alarm list, a fourth in the commissioning spreadsheet, a fifth in the O&M manual nobody opens. Every copy starts identical and every copy drifts — a tag renamed here, a fault code in the controller that never reached the screen. Then a new site is built, and someone re-types all five from scratch.

So we keep one, not five. A device type is defined once, and the platform generates the PLC code from that one model — the same model that renders its faceplate and feeds its historian. The control code cannot drift from the model, because it is generated from it. Same input, same code, every time.

One model, one source of truth

Defining a pump as a device type drives everything downstream. A mapping file is the contract: it ties each device type to a function block plus its wiring metadata — hardware I/O, call parameters, and the GDS port declarations the HMI and historian read. Seven of the catalogue’s device types resolve to five function-block types in the generator today: pumps, motorised valves, VFD motors, flow/pressure/level sensors, and PID loops. Generate a site and the platform emits a PLCnext PROGRAM: each device’s function block declared, called in the scan with its I/O wired, outputs mapped back to hardware — plus the GDS port list, so the faceplate and historian bind to the exact tags it exposes. Variable names come from the operator-readable asset number (PP-01 becomes Pp_01), not a synthetic ID, so the generated code reads the way the plant is labelled.

Generated, not guessed

This is the line we will not blur — the same one as advisory by design, where AI explains but never commands. The generation is deterministic template expansion: Scriban filling in .st.sbn templates, not a language model. The output is a pure function of the request, the type mapping and the templates; between two runs of identical input, only a timestamp in a comment header changes. That matters because PLC code has to be reviewable and reproducible, not probabilistic — you can diff it, sign it off, and get the same bytes next time.

So where is the AI? “IO AI” assembles the request — it chooses which device types, and how many — then calls the deterministic generator: it picks the blocks, it does not write them. The function blocks themselves are hand-authored and human-reviewed in IEC 61131-3: a library of CSY_* blocks, of which CSY_Pump alone is a 323-line state machine with speed ramping, interlocks, condition-monitoring alarms and run-hour tracking. The generator instantiates and wires those blocks, exactly the way an engineer instantiates a Rockwell AOI. The creativity lives in the reviewed library and the device model; the generator is mechanical glue. A static check then runs over the output — resource limits (a program tops out near a thousand GDS variables), naming, tag completeness, leftover placeholders — a structural review, not a compile.

One source, two runtimes

Here is the property the determinism buys. The same request and the same type mapping that produce the PLCnext Structured Text also drive a software simulation of the plant. No ST is parsed; both are generated from the same model and wire the same device blocks — one as Structured Text for the controller, one as the C# the simulator runs natively. The C# blocks are translated from the hand-authored ST and held to it by a behavioural-equivalence test suite, including a full dam-pump-station scenario. So the sim is not an approximation of what will run — it is the same model, wired the same way.

What ships, and what is next

To be precise about the line:

  • Shipped: the deterministic generator (ScribanIEC 61131-3 Structured Text plus GDS port declarations); the hand-authored, reviewed CSY_* function-block library it instantiates; and a behaviourally-equivalent C# simulation driven from the same device model.
  • Roadmap: field-hardware sign-off, and the throughput and scale targets only a real controller can settle.

A caveat we owe you. The simulation runs the C# translation of the function blocks — held equivalent to the hand-authored Structured Text by those tests — not the generated ST program itself, which has not yet run on a virtual or physical PLC. Our headline dam-pump-station proving run, against a virtual PLCnext, executed a separate hand-authored, pure-managed C# program on the controller’s eCLR runtime, not the generated ST. The pipeline is deterministic and shipped; the hardware run is still ahead of us.

Written code drifts because there are copies. Generated code has none — there is one model, and the controller reads from it.