August 21, 2026 · SIMATIC AXXLadStructured TextPLC programmingIndustrial Automation

I rebuilt one small Function in XLad
My SIMATIC AX tank-control project already had a working pump sequence in Structured Text. The pump waited for its source valve, a priming timer, and proof that source water was available. Eleven AxUnit tests protected that behavior.
I wanted to see how Ladder works in SIMATIC AX, so I picked one small Function as an example: FC_PumpStartPermitted. It was simple enough to rebuild as one rung and easy to compare with the original ST code. This gave me one small Ladder test in a real project.
I rebuilt the same nine-input Boolean logic in XLad. The existing ST function block still handles timing, operating modes, speed selection, alarms, and the Function call. The call signature stayed the same. Both S7 and LLVM builds completed with zero errors, and all eleven existing tests passed.
This remains an R&D demonstration. The software has not been downloaded to a physical PLC or commissioned on real tank hardware.
I started with the existing ST Function
Before this change, the pump-start permissive was one hand-written ST Function. It combined six positive permissions with three conditions that had to be false:
FC_PumpStartPermitted := xSystemEnable
AND NOT xStop
AND xFillFaultPermitted
AND NOT xLevelHighHighActive
AND xSafetyChainOk
AND NOT xPumpVfdFault
AND xSourceWaterAvailable
AND xSourceValveOpenCommand
AND xPumpPrimed;

Before the change, the pump-start permissive is one hand-written Structured Text Function containing six positive permissions and three inverted stop or fault conditions.
I chose this Function because the logic is discrete, Boolean, and easy to read as one power path. A controls engineer can scan the contacts from left to right and see exactly why the pump is permitted to start.
The priming timer, retained mode state, alarm latches, analog calculations, speed clamps, and output orchestration stay in the existing ST block. That code already works and is easier to understand as a sequence in text. For this experiment, I only needed one small Function to see how XLad behaves.
The POU name and filename are separate in XLad
In AX Code, I created a new XLad file under src, selected Function, entered FC_PumpStartPermitted, and kept the existing Otomakeit.AXTankDemo namespace.

The XLad wizard defines the POU identity: Function FC_PumpStartPermitted in namespace Otomakeit.AXTankDemo.
XLad 1.3.0 created the correct Function identity, but the physical source file was still named pou.ld. The editor also prepared blank networks. The installed XLad implementation uses pou as its default filename and adds a number only when that filename already exists. The Function name was entered correctly; the source filename simply follows a separate default.
Before I edited the interface or the rung, I used Rename… in AX Code Explorer to rename the editable pou.ld source file to FC_PumpStartPermitted.ld. Later, the full-project transpile created the filename-derived output under .gen.

The editable pou.ld source is renamed in Explorer with Rename… or F2.
Renaming the source first keeps the generated filename clean. In the verified XLad 1.3.0 workflow, project-wide apax xlad transpile removes a stale pou.ld.st; a single-file --input transpile can leave stale output behind.
I used the same simple Boolean interface
The XLad Function returns one BOOL and accepts the same nine BOOL inputs as the old ST implementation:
| Order | Input | Meaning in the rung |
|---|---|---|
| 1 | xSystemEnable | Normal contact |
| 2 | xStop | Inverted contact |
| 3 | xFillFaultPermitted | Normal contact |
| 4 | xLevelHighHighActive | Inverted contact |
| 5 | xSafetyChainOk | Normal contact |
| 6 | xPumpVfdFault | Inverted contact |
| 7 | xSourceWaterAvailable | Normal contact |
| 8 | xSourceValveOpenCommand | Normal contact |
| 9 | xPumpPrimed | Normal contact |

The Function keeps one Boolean result and the same nine explicit Boolean inputs used by the Structured Text caller.
I kept the interface simple. It has no structured values, references, interfaces, outputs, in/out variables, or manually declared temporary values. It describes one pump-start decision and nothing else.
I built one Ladder rung
I kept one network and built one series rung. Six normal contacts represent positive permissions. Three inverted contacts represent Stop, high-high level, and VFD fault. The final coil writes the Function result.

Six positive permissions and three inverted stop or fault conditions form one readable pump-start rung.
The rung preserves the same logic as the old ST expression. It has one path through the nine conditions and one result coil. A reader can follow the decision from xSystemEnable at the left rail through to FC_PumpStartPermitted at the right.
I transpiled the Ladder source and removed the old ST Function
While I was building the replacement, the old ST Function and the new XLad Function temporarily had the same name and signature. I kept the working ST version until the Ladder source transpiled successfully.
I saved the .ld file and ran a full project transpile:
apax xlad transpile
The generated output is:
src/.gen/transpiledSTFromLD/FC_PumpStartPermitted.ld.st
Its first line is explicit:
// This is an auto-generated file. Do not modify.
FC_PumpStartPermitted.ld is the editable source. FC_PumpStartPermitted.ld.st is generated output and must not be hand-edited.
After the successful transpile, I confirmed that no stale pou.ld.st remained and removed the old hand-written FC_PumpStartPermitted block from FB_PumpVfdControl.st.

After XLad transpiles successfully, the old hand-written definition is removed while the surrounding control logic remains unchanged.
The project now has one editable definition for this POU. Building while both definitions exist produces a duplicate-definition error. Removing the working ST version before the Ladder source transpiles would also remove the known-good implementation too early.
I checked the generated ST and the existing caller
The generated Function declares three temporary contacts for the inverted conditions:
_CONTACT_0_2 := NOT(xStop);
_CONTACT_0_4 := NOT(xLevelHighHighActive);
_CONTACT_0_6 := NOT(xPumpVfdFault);
It then combines those temporary values with the six positive permissions. XLad may append AND TRUE and a standalone semicolon to its generated form. Those are normal generated implementation details.
The existing ST caller remains unchanged:
xPumpPermitted := FC_PumpStartPermitted(
xSystemEnable := tank.commands.xSystemEnable,
xStop := tank.commands.xStop,
xFillFaultPermitted := xFillFaultPermitted,
xLevelHighHighActive := tank.status.xLevelHighHighActive,
xSafetyChainOk := tank.status.xSafetyChainOk,
xPumpVfdFault := tank.status.xPumpVfdFault,
xSourceWaterAvailable := tank.hwInputs.i_xSourceWaterAvailable,
xSourceValveOpenCommand := tank.hwOutputs.q_xLiquidSourceValveOpenCmd,
xPumpPrimed := tank.status.xPumpPrimed
);

XLad generates the Boolean Function, and the existing Structured Text block keeps the same typed Function call.
This is useful because the caller does not need any change. It still receives one Boolean result calculated from the same nine named Boolean inputs, even though I now author the Function body as Ladder.
XLad added its required system libraries
When XLad first opened the project, it added two libraries to apax.yml:
"@ax/system-edgedetection": ^10.4.66
"@ax/system-bistable": ^10.4.66
Apax refreshed apax-lock.json at the same time. These are expected XLad setup changes. They are separate from the Function source itself, but they belong in the final review because the project must reproduce the tool’s resolved dependency state.

The final file set contains the editable Ladder source, generated ST artifact, removed hand-written ST definition, and XLad dependency setup.
The generated-file repository policy is still a team decision. Whether .ld.st files are tracked or always regenerated in CI does not change the authoring rule: people edit .ld; XLad owns .ld.st.
I built both targets and ran all tests
The application compiled for both project targets:
- S7: zero errors
- LLVM: zero errors

The S7 target compiles with zero errors.

The local LLVM target also compiles with zero errors.
Compilation proves that the generated Function and caller agree. I also ran the complete existing AxUnit suite to check that the behavior stayed the same.

All eleven existing AxUnit checks still pass after the Function is rebuilt in XLad.
The verified result was:
total: 11
executed: 11
passed: 11
failed: 0
Those tests still cover scaling, scaling clamps, pump-speed limits, low-low recovery, high-high shutdown, dry-run behavior, water proof after priming, mode behavior, outlet representation, and the priming delay. The experiment changes how this Function is authored. It does not introduce new process-control behavior.
What I learned from this test
I rebuilt one simple Function because I wanted a small, clear test of Ladder in SIMATIC AX. And it actually works great. I like it.
The contacts and coil feel very similar to Ladder in TIA Portal. I can see the pump-start decision as one rung, while the priming timer, modes, alarms, speed logic, calls, and outputs stay in the Structured Text code where they already work.
This test shows that I can use Ladder for a simple part when the graphical view helps, and keep the rest of the application in ST. I also keep one editable source for each POU: .ld for this Ladder Function, with .ld.st generated by XLad.
So, hooray—Ladder is in SIMATIC AX now. It works, and it feels very similar to TIA Portal. This was a great surprise and very good news.
What the software checks prove
The application builds and AxUnit results prove the software compiles and the existing tests pass. Hardware compilation, PLC download, online monitoring, electrical checks, and commissioning are still outside this experiment. The three-second priming value, flow-switch polarity, transmitter ranges, VFD scaling, and actual water-path behavior still require validation on the intended hardware.
For now, the tank demo has one pump-start Function in XLad and the tested stateful application continues in Structured Text.
