DEV Community

tia-forge
tia-forge

Posted on

Why AI Writes SCL But Rarely Ladder Logic: Text Is Cheap, Diagrams Need an IR

Ask an AI assistant for a PLC program that runs a conveyor — start button, stop button, e-stop, overload, contactor feedback, and a fault that latches unless you reset it.

You get SCL. Or ST. It looks plausible: a TON, an SR, a few IFs, sensible variable names.

Now ask for the same thing as a ladder diagram. You get an ASCII sketch, or a sentence telling you to put Start in parallel with Run and then in series with the NC contact of Stop.

This is not laziness. Text languages and graphical languages sit at different costs for a token-predicting model, and ladder logic is on the expensive side of that gap.

The gap is three layers, not a matter of drawing skill

Layer SCL / ST Ladder (LD)
Model output The token sequence is the source file Must be a graph: networks, series/parallel, branches, joins, element UIds
Intermediate representation None needed Required. Something has to record the topology before anything can be generated
Generation Write to disk, compile, fix a line, retry A deterministic generator assigns UIds and wires and emits strict SimaticML XML
Gate Compile errors are still fixable One validation issue and no XML is produced at all

A model emits tokens. SCL is tokens. The path from "model output" to "compilable source" is essentially zero-length, and when it fails you get a line number.

Ladder logic isn't text. What a rung actually encodes is topology: which contacts are in parallel, where the branch rejoins, which network a timer belongs to, which element feeds which. Natural language can't carry that, so an AI that "writes ladder" has to first write down the diagram as data.

That's the whole trick, and it's why so few tools do it: you don't ask the model to draw the diagram, you ask it to emit the diagram as a data structure, and a deterministic program does the drawing.

Somebody has to define a ladder IR and write a generator that turns it into SimaticML. SCL reuses a compiler that already exists. Ladder doesn't have that path for free.

What the pipeline looks like when it exists

  • IR — the ladder document is JSON: networks, elements, operands, connections.
  • Validation gate — before generation, the document is checked layer by layer: document shape, block interface, networks and parallel branches, operands, block calls. One issue and no XML is emitted, so you never hand TIA Portal a block that cannot be imported.
  • Deterministic generation — the same document always produces byte-identical SimaticML; UIds and wire connections are assigned by the generator, output targeting V19 <SW.Blocks.FC|FB|OB>.
  • Into the project — TiaLink imports the XML into the TIA Portal project you have open, compiles the whole software, and reads back objects and call relationships. Same-named objects are not overwritten by default, and the project is not saved automatically.

The last two points matter more than they look: "generated" and "imported" are not the same as "the application compiles". The validation-and-generation half is LadDoc Cloud; the import-and-compile half is TiaLink.

A real run: one message in, four objects out, whole-software compile 0 errors / 0 warnings

Environment: TIA Portal V19, S7-1500 (CPU 1516-3 PN/DP), gateway on the same machine, empty project.

One message, and no follow-up message after it:

We have a conveyor that has to run on a PLC. On the field side there is a start button, a stop button, an e-stop, an overload relay, a contactor feedback signal, and a reset button; on the output side the motor contactor plus two fault indicators.

Press start: the motor runs and stays running. Press stop, trip the e-stop, or trip the overload: the motor stops.

If the motor is running and no contactor feedback arrives within 2 seconds, raise a feedback fault, stop the motor, and latch it. Once the protection has recovered, the reset button clears the fault.

The main program has to call it every scan cycle. The e-stop and the safety circuit are handled by hardware; the program only does interlocking and annunciation.

Nothing was specified about block decomposition, block numbers, DB versus tags, or how the instance DB should be created. That was the agent's call; the operator's job is to state the requirement and verify the result.

What it delivered:

Object Type Number Purpose
DB_Conveyor global DB DB10 Field signals: 6 inputs + 3 outputs
ConveyorControl FB FB1000 Control logic, 4 networks
ConveyorControl_DB instance DB DB1 The FB instance: timer and latch state
Main OB OB1 Calls the FB every scan cycle

Miss any one of the four and it isn't an application: without the OB call the logic never runs, without the instance DB the call doesn't even compile.

Network 1 — run seal-in. Start in parallel with Run for self-holding, then in series with four NC contacts (Stop, EStop, Overload, Fault), driving the Run coil.

Network 1: start and seal-in, with Stop/EStop/Overload/Fault as NC contacts in the rung

Network 2 — feedback timer. While Run is true, start a TON with PT bound to a constant DELAY (2 s).

Network 2: a TON started by Run, 2 second preset

Network 3 — feedback fault. RunTimer.Q reached while RunFeedback is still FALSE sets RunFeedbackFault.

Network 3: timer done and feedback still missing sets the fault bit

Network 4 — fault latch. Overload, RunFeedbackFault and Fault in parallel, self-holding, cleared through the NC contact of Reset.

Network 4: overload, feedback fault and the fault bit latch in parallel, Reset clears

The timer is a multi-instance inside the FB (RunTimer declared in Static), so its state lives in the instance DB. That's part of "the application runs", not decoration.

The first whole-software compile failed:

Compiling: Main (OB1)  Network 1: Missing instance DB.
Enter fullscreen mode Exit fullscreen mode

The OB called the FB, but the FB's instance DB did not exist yet. After creating ConveyorControl_DB, re-importing and recompiling:

Compiling: Main (OB1): Block was successfully compiled.
DB_Conveyor (DB10): Block was successfully compiled.
ConveyorControl (FB1000): Block was successfully compiled.
ConveyorControl_DB (DB1): Block was successfully compiled.
Compiling finished (errors: 0; warnings: 0)
Enter fullscreen mode Exit fullscreen mode

That's the difference between "a block" and "an application": a legal block doesn't mean the application runs.

Two more failures, from the same class of block

A counter without a data type. TIA's import reported CTU 缺少 value_type ("missing value_type"). Fix: have the generator declare the counter's data type, regenerate, re-import.

A temp variable with an initial value. Import reported Scratch: The Openness import failed: 该值无法更改。 ("this value cannot be changed"). Temp variables can't carry initial values; remove it and regenerate.

All three failures point at the same division of labour:

The agent's self-check owns semantics; TIA's import and compile own TIA's rules, and those are stricter.

"Validation passed" does not mean "TIA accepts it". Paste the raw error text back to the agent, let it fix against TIA's rules and regenerate. Don't hand-edit the generated file, and don't argue with the validator. Three rounds max; if it still fails, re-read the requirement.

Boundaries worth stating before anyone copies this

  • Compile 0/0 does not mean the behaviour is correct. Compiling only proves the block fits a CPU. A TON written where a TP belonged also compiles 0 errors / 0 warnings and no motor ever starts. Verify behaviour separately (simulation, or a real machine).
  • Outputs need mapping to your hardware. In the reference application many signals live in %M bits; map them to %Q before wiring anything.
  • Safety does not go to an AI. E-stop, safety gates, hardware interlocking belong to functional safety. The program only interlocks and annunciates; the final implementation must be reviewed by a person.
  • There are environment prerequisites. The gateway runs on the same machine as TIA Portal and currently targets V19. Without TIA Portal installed you can read, but not import.

Reproducing it

Two MCP servers, configured once:

{
  "mcpServers": {
    "tialink": {
      "type": "http",
      "url": "http://localhost:8080/mcp",
      "headers": { "Authorization": "Bearer <your TiaLink API token>" }
    },
    "lad": {
      "type": "http",
      "url": "https://tia.hubwiz.com/lad/mcp",
      "headers": { "Authorization": "Bearer <your LadDoc API key>" }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

http://localhost:8080/mcp is the on-machine gateway — TiaLink, use whatever address your install listens on; https://tia.hubwiz.com/lad/mcp is the hosted ladder service, LadDoc Cloud. Any Streamable HTTP MCP client works — Claude Code, Cursor, VS Code.

Billing: handshake requests and lad_version are free; each validate/generate call consumes one credit. New accounts get a free allowance, then credits are bought in packs — no subscription.

Keep the keys out of repositories, screenshots and tickets.

How to tell whether "AI generates ladder logic" is real

Don't ask whether it can generate ladder. Ask for three numbers:

  1. Are all the objects there? Global DB / FB / instance DB / OB — all four, or just a block?
  2. Does the main program call it every cycle? Or was a lone FB handed over with no OB call?
  3. What did the whole software compile report? Not "the block compiled" — the whole software, plus a read-back of the object list and call relationships that matches what the agent claims.

If it can produce those three, "it generates ladder" is a fact. If it can only produce a diagram that looks like ladder, it's a picture.


Disclosure: LadDoc Cloud and TiaLink are our products. The error messages, the compile output and the four networks above come from a single real run (TIA Portal V19, S7-1500); one run does not generalise to every project.

Earlier in this series: 60 ladder exercises you can import into TIA Portal, 113 ladder instructions, one page each, three kinds of LAD→SCL tooling.

The full walkthrough — signal table, workflow, network-by-network commentary, verification checklist — is lesson 3 of PLC AI in Practice.

Top comments (0)