PLC Code Generators: What AI Can Draft — and What Must Be Verified
The trust problem with generated control logic, why a PLC code generator should produce drafts rather than artifacts, how a compile-and-repair loop works, a worked example of an ambiguous request and the assumptions it surfaces, and what gets refused.
Ask a general-purpose AI model for a PLC program and you will get one. It will be formatted nicely, it will use the right keywords, and it will run on nothing, because it will reference instructions that do not exist on your controller, or nest a loop inside a rung, or quietly pick one meaning of an ambiguous sentence and never tell you. That is the trust problem with a PLC code generator, and it is not solved by a better model. It is solved by deciding what the generator is allowed to produce and what has to happen before its output is treated as real. This post is about that line.
The trust problem with generated control logic
Control logic has two properties that make it different from most generated code. First, it acts on the physical world: a wrong rung moves a motor. Second, a wrong rung is usually plausible. It compiles, it draws, and it looks like every other rung on the page. The failure modes are not crashes; they are a seal-in that does not hold, an interlock one rung too far down, a stop button assumed normally-open when it is wired normally-closed.
A language model is very good at producing plausible text, which is exactly the wrong strength for this problem. Left alone, it will fill every gap in a request with a reasonable-sounding guess and present the result with the same confidence whether the guess was right or not. Anyone who has reviewed generated code knows the feeling: it reads well, and reading well is not the same as being right.
What a PLC code generator can draft, and what it cannot sign
The useful distinction is between a draft and an artifact. A draft is a starting point that a person will review. An artifact is something that goes to a controller. A generator can produce the first. Only a verification process can produce the second.
That sounds like a slogan, so here is what it means in practice for ours. The AI writes IEC 61131-3 structured text, and only structured text, inside a published subset: boolean assignments, IF/THEN/ELSE lowered to set and reset coils, comparisons, numeric assignments, on-delay timers, edge detectors, counters, and a small block library. Whether you think of the tool as a PLC creator or a code generator, the model never touches the ladder, the function block diagram, or the simulation. Those are produced by a deterministic compiler from the text, with fixed rules and the same answer every time. Generation is easy. The product is the verification: the AI drafts, the compiler proves the draft parses, type-checks and simulates, and a person approves it or does not.
How the repair loop works
The mechanism is simple enough to describe in full. The request goes to the model wrapped as untrusted data, together with instructions that describe the subset. The model returns a program. The compiler runs on it. If the program is accepted, you see it, with its ladder, its FBD and its simulation. If not, the compiler's errors, each with a line number, go back to the model as the next turn, and it tries again. There are at most four attempts; after that you get the errors, not a program.
Here is the kind of thing the loop catches. Asked for a fan with hysteresis, a model will often reach for ELSIF, which reads naturally and is outside the subset:
IF Temperature > 30.0 THEN
Fan := TRUE;
ELSIF Temperature < 25.0 THEN
Fan := FALSE;
END_IF;
The compiler answers with the line of that IF statement and the message "ELSIF is not yet convertible to ladder; use separate IF statements." The model rewrites the program as two IF statements, the compiler accepts it, and that is the version you see, together with the number of attempts it took. You never see the rejected draft, because a rejected draft is not a result.
Two things about that loop are deliberate. The compiler is not an AI, so the acceptance decision cannot be talked into a different answer. And the loop can fail: if four attempts do not produce an accepted program, the honest output is the errors.
Worked example: an ambiguous request
Vague requests are the normal case, not the exception. Try this one in the workbench at PLC-Ladder:
Run the fan when it gets hot, and stop it once it has cooled down.
Nothing in that sentence says what "hot" is, whether "cooled down" is the same threshold, or what the temperature signal is. A generator that fills those gaps silently has made three engineering decisions for you. What we require instead is that every assumption appears as a comment line at the top of the program, and the rail lists them next to the diagram:
PROGRAM Main
VAR
Temperature : REAL;
Fan : BOOL;
END_VAR
// Assuming "hot" means Temperature above 30.0 degrees C.
// Assuming "cooled down" means Temperature back below 25.0 (5 degrees of hysteresis so the fan does not chatter).
// Assuming Temperature is a REAL process value in degrees C.
IF Temperature > 30.0 THEN Fan := TRUE; END_IF;
IF Temperature < 25.0 THEN Fan := FALSE; END_IF;
END_PROGRAM
The thresholds are guesses and the program says so. The hysteresis is a decision, and the program says why. The interpretation is now something you can disagree with in one line, which is the entire value of surfacing it. Change 30.0 to 45.0 in the editor and the ladder and the simulation follow.
[SCREENSHOT: the Assumptions panel in the rail listing the three assumption lines beside the two-network ladder for the fan]
Then test the interpretation rather than the syntax. Set Temperature to 31 in the rail and the fan coil sets. Drop it to 27 and the fan holds, because neither rung fires. Drop it to 24 and the reset rung clears it. That is the behaviour you asked for, on the thresholds the program admitted to guessing.
[SCREENSHOT: the simulation at Temperature 27 with Fan still energised, showing the hysteresis band holding]
The fence: what gets refused and why
The last part of a trustworthy generator is what it will not do, and there are two fences.
The first is in front of the model. A request with no control behaviour in it, an essay, general-purpose code, an attempt to change the rules, gets a refusal and no program. A request phrased as "draw me a diagram of..." is still drafted if it describes behaviour, with a note that the presentation instructions were ignored, because diagrams come from the compiler, not the model.
The second fence is the compiler, and it refuses far more often. Loops, arrays, CASE, ELSIF, unknown functions, undeclared variables, a numeric variable used as a contact: each is rejected with a line and a reason. Ask for a loop over eight feeders and the answer is:
line 6: ForStatement is not yet convertible to ladder (supported: assignments, IF/ELSE, timer / edge / counter calls).
That message is less satisfying than a program. It is also the right answer, because the alternative is a ladder diagram that looks correct and means something different from the text. The same rule is what keeps the diagram honest when existing text is converted rather than generated; how a structured text to ladder logic converter works covers that side.
There are honest limits to all of this. The compiler proves structure and simulates behaviour; it does not know your wiring, your I/O map, or your controller's scan time, and the assumption lines are only as good as the reviewer who reads them. The subset is small by design, and requests outside it come back as errors, not approximations. A generator that behaves this way is slower to please than one that always produces something. It is also the only kind we would let near a draft that a person will eventually sign. For what happens after the draft, see testing ladder logic in an online PLC simulator.