Subprograms
Subprograms let you repeat a block of code without copying it. This page covers M98/M99, Haas P/L syntax, nested calls, repeating hole patterns, and the safety rules that prevent drift between repetitions.
- Trace a call and return
- Count drilling events and unique positions
- Explain shared versus nested variables
Concept
A subprogram is a separate O-numbered program called from the main program. On a Haas control, M98 P2000 L3 calls O2000 three times. M99 returns to the line after M98. Subprograms reduce repetition, make programs shorter, and centralize changes — if the hole depth changes, you edit one line instead of sixteen.
Why It Matters
Without subprograms, drilling 16 holes in a pattern can use one G81 block followed by 15 XY moves; a subprogram is useful when the pattern itself repeats. With a subprogram, you write the hole once and call it 16 times. But subprograms inherit modal state — if the subprogram uses G91, the main program must restore G90 after. This is the #1 cause of subprogram bugs: leaked modal state.
How It Works
Haas Syntax
M98 P2000 L3 (call O2000, repeat 3 times)
Inside O2000, M99 returns to the line after M98. The subprogram has its own O-number and lives as a separate program on the machine. On Haas mills, M98 P2000 L3 calls O2000 three times. Other controllers use different call syntax; check the manual.
Nested Subprograms
A subprogram can itself call another subprogram (up to 4 levels deep on most controllers). This is how you build reusable building blocks: the main program calls a pattern routine, which calls a single-hole routine. Nested calls return in reverse order: inner M99 returns to the outer M98 line.
Safe Pattern: Always Restore State
Before M98, always: (1) know the current G90/G91 state, (2) ensure G80 cancels any active cycle, (3) declare G90 explicitly after each call. The example below uses absolute coordinates only — no G91 — so there is no drift between holes.
Example: Four-Hole Pattern
Four holes on a 40 mm square at (0,0), (40,0), (40,40), (0,40). The main program positions XY in absolute G90, then calls O2000 which drills at the current position and returns to Z50.
Main program O0029 (entry setup and tool already verified):
O0029 (MAIN - ENTRY SETUP AND TOOL ALREADY VERIFIED)
G21 G17 G90 G94 G40 G80 G98
G00 Z50.
G00 X0. Y0.
M98 P2000
G90 G00 X40. Y0.
M98 P2000
G90 G00 X40. Y40.
M98 P2000
G90 G00 X0. Y40.
M98 P2000
G80
G90 G00 Z50.
M30
Subprogram O2000 (a separate program stored on the machine):
O2000
G90 G98 G81 Z-15. R2. F120.
G80
G90 G00 Z50.
M99
Trace: Call 1 drills at (0,0). Call 2 drills at (40,0). Call 3 drills at (40,40). Call 4 drills at (0,40). Each call: G81 drills at the current XY, G98 retracts to initial plane Z50, G80 cancels cycle, explicit G00 Z50 ensures safe height, M99 returns. Every hole is at Z−15 — no drift.
Note on G80: G80 cancels the canned-cycle mode. After G81 completes, the Z position depends on G98 (retract to initial plane) or G99 (retract to R plane). G80 itself does not retract — that is why we write explicit G00 Z50. after G80.
When to Use Subprograms
- Repeated hole patterns — bolt circles, grid arrays, repeated pocket shapes.
- Multiple identical parts on a tombstone — one subprogram, called at each part origin.
- Repeated features — same chamfer, same groove, same profile at different positions.
- Organization — break a long program into readable chunks by operation.
When Subprograms Earn Their Keep
A subprogram is not automatically better than inline code. A single hole does not need O2000. A subprogram earns its keep when the same motion block repeats several times in one setup — a hole pattern, a pocket array, a bolt circle, or a series of identical slots on a tombstone. If the pattern appears once, inlining it is shorter and easier to verify. If the pattern appears 4 times on a fixture, or 12 times around a circle, the subprogram removes 12 copies of the same code that could drift apart during editing.
The classic bolt-circle example: 12 holes at 30° spacing on a 50 mm radius. Without a subprogram, you list every (X,Y) pair. With a suitably designed macro-capable O2000 and M98 P2000 L12, the main program supplies a starting angle and the subprogram computes one hole. Even better, the angle increments inside the subprogram so the next call starts where the last one ended — and the subprogram must satisfy its documented coordinate-mode contract on return. The #1 subprogram bug is leaked incremental mode: the main program is in G90, the subprogram switches to G91 for the pattern, and forgets to switch back. The next main-program block then moves by an increment instead of to an absolute position, and the tool appears to "jump."
Subprograms also have a stack depth limit on every controller — as specified in the selected machine manual. Nesting a subprogram inside a subprogram is allowed, but going too deep triggers an alarm. Keep nesting shallow: one level for patterns, two at most, and never rely on undocumented depth limits in production.
On the shop floor, a subprogram is also a testing unit. You can run O2000 alone with a dry-run block to check the hole position before running the full main program. Isolating one hole in its own O-number means you can fix the geometry in one place, and every call inherits the fix. This is the same reason functions exist in programming — single source of truth, repeated calls, isolated debugging.
A Complete Subprogram Example: Four Fixture Offsets
Suppose you drill the same 4-hole rectangular pattern on each of four parts set up on four fixture offsets (G54, G55, G56, G57) on one tombstone — 16 holes total. The main program establishes tool length and clearance first, then selects each fixture offset and calls the same pattern subprogram. The subprogram uses absolute local coordinates relative to each fixture origin, so the same local (X,Y) maps to a different machine (X,Y) under each G5x.
O0001 (MAIN - 4 FIXTURES x 4 HOLES)
G21 G17 G90 G94 G40 G80
G00 G43 H01 Z50. M08 (establish clearance BEFORE XY)
S1500 M03
G54
M98 P2000 (pattern on G54)
G55
M98 P2000 (pattern on G55)
G56
M98 P2000
G57
M98 P2000
G80 G00 Z50. M09
M05
M30
O2000 (4-HOLE RECTANGLE, ABSOLUTE LOCAL)
G00 X0 Y0.
G98 G81 R2. Z-15. F120.
X20. Y0
X20. Y20
X0 Y20
G80
G00 Z50.
M99
Key points: (1) G81 is issued inside the subprogram on the first local hole X0 Y0, not before it — otherwise the first block outside the subprogram would drill an extra hole at wherever XY happened to be. (2) The four holes are the rectangle corners (0,0), (20,0), (20,20), (0,20) — not a drifted incremental chain. (3) G80 and a retract to Z50 happen before M99, so between fixtures the tool is at clearance height. (4) The local coordinates stay inside the subprogram; each G5x offsets them to the correct machine XY.
Do not try to run O2000 by itself as a "standalone test." A subprogram expects a main program to have already established G54, H01, spindle, and clearance. To test it, write a tiny main program that calls O2000 once, or use the single-block dry-run. Pressing cycle start on O2000 directly either alarms or runs with unknown offsets.
Why Not Just Repeat the Code Inline?
A beginner's first instinct is to copy the same four-hole block twelve times. That works for twelve holes, but it means twelve chances to mistype an X coordinate, twelve places to update when the rectangle moves 5 mm, and twelve copies of the G81 line that must stay consistent. A subprogram is the single source of truth: the geometry is written once, and the main program calls it. When the drawing changes, you edit O2000 once and every call inherits the edit. This is the same reason functions exist in any programming language — DRY, but for the shop floor.
The cost is indirection. A subprogram call hides motion that a reader has to flip to another O-number to see. That is why this page insists on absolute local coordinates inside the subprogram, not incremental: with absolute coordinates, you can read O2000 by itself and understand exactly what XY positions it visits. With incremental, you have to trace the calling context to know where the first move starts.
Nested Subprograms: When to Go One Level Deeper
A main program that calls one subprogram is the easy case. Production setups often need a second level: the main program calls an O2000 that itself calls O3000 for a repeated detail inside each pattern. On a FANUC-style control, subprogram nesting has a controller-specific limit that must be checked in its manual. Keep nesting shallow: one level for hole patterns, two at most for reusable detail features, and never rely on an undocumented limit. If you need three or four levels, the program is usually telling you to split the work into multiple main programs instead.
The main trap in nested subprograms is leaked modal state. If O2000 switches to G91 and calls O3000, O3000 inherits G91 unless it restores G90. The actual position error depends on the motion blocks each level issues — mode inheritance alone does not move the tool. The defensive pattern is: every subprogram that changes a modal mode restores that mode before M99, even if you "know" the caller expects it. This costs a line and saves hours of debugging.
Passing Data to a Subprogram
The four-fixture example above hard-codes the rectangle corners (0,0),(20,0),(20,20),(0,20). That is fine for one part, but the same rectangle called at 30 different sizes becomes a maintenance problem. Production subprograms use macro variables (#1–#33 local, #100–#199 common) to pass in the corner positions, stepover, and depth. The main program sets #1 = X start, #2 = Y start, #3 = X length, #4 = Y length, then calls O2000; the subprogram computes the four corners itself.
In the referenced Haas macro implementation, M98 does NOT create a new local-variable nesting layer the way G65 does: #1 set in the main program and changed inside M98 P2000 stays changed when control returns. Only G65 (macro call) creates private local variables. In a teaching shop, treat #1–#33 as shared until you specifically teach G65, and give the main program and subprogram different variable numbers to avoid clobbering. Common variables (#100–#199) are also shared across the whole program.
Subprograms and the Controller Call Stack
Every M98 pushes a return address onto a small internal stack; every M99 pops one off. The stack is not infinite, and deep or recursive M98 calls (calling O2000 from inside O2000) can exceed the nesting limit on a FANUC-style control. A missing M99 does not by itself imply a stack-overflow alarm. The outcome depends on the controller, program boundaries, and subsequent executable blocks; inspect the actual control flow and alarm message.
On a FANUC-style control, the call count L word (M98 P2000 L4) repeats the subprogram four times in a row without returning to the main program between calls. This is useful for a bolt circle or a tombstone pattern, but it means the subprogram must position itself for each repeat — either via incremental motion inside the subprogram (with a G90 restore) or by incrementing a variable. Do not use L4 to call a subprogram that ends at the same XY every time; the controller drills the same hole four times.
Count Events and Distinct Positions Separately
Trace a repeated pattern with two columns: the coordinate reached and the cycle trigger that cuts there. Four coordinates repeated three times give twelve drilling events but only four distinct locations unless the caller or subprogram changes the coordinate mapping. This distinction matters when checking both cycle time and an accidental second cut in a completed hole. If the main program selects G54, G55, G56 and G57 for four fixtures, verify each stored origin and the clearance between fixtures separately. A valid offset number does not prove that its value belongs to the intended fixture.
For each call, record the entry plane, active offset, feed mode and drilling-cycle state. On return, check that the subprogram has cancelled the cycle and established the caller's documented return height. The main program must not infer clearance from M99 itself: returning control changes the program location, not automatically the physical tool position. Use a call-by-call trace before deciding that repeated code is equivalent to the intended repeated geometry.
Common Mistakes
- Confusing P and O — Haas uses M98 P2000 (call), not O2000. The subprogram itself is labeled O2000.
- Using G91 in subprogram without restoring G90 — subsequent main-program coordinates become incremental, causing holes to drift.
- Assuming G80 retracts to Z50 — G80 cancels cycle mode; the return height was set by G98/G99. Always write explicit G00 Z retract.
- Leaving cycle active between calls — cancel with G80 before M99 to make exit state explicit.
- Too many nested levels — most controllers limit nesting to 4 levels. Keep subprograms shallow.
- Modifying offsets inside subprograms — if the subprogram changes H or D, the offset leaks into the next call. Reset offsets explicitly.
Practice
1. M98 P3000 L5 does what?
Ans
Calls O3000 five times, then returns to the line after M98.
2. Why does the subprogram write G90 G00 Z50. before M99?
Ans
To guarantee the tool is at safe height Z50 before returning. G80 cancels cycle mode but does not guarantee Z position; being explicit removes ambiguity.
3. Why does the main program write G90 before each X/Y position?
Ans
To prevent any leftover modal G91 from the subprogram. Declaring G90 explicitly makes each hole position an absolute coordinate, not a delta from the previous one.
4. What is the maximum nesting depth on most controllers?
Ans
Typically 4 levels. A subprogram can call another subprogram, which calls another, up to 4 deep. Beyond that, the controller alarms.
Sources
- Haas mill macros: local-variable nesting for G65 versus M97/M98.
- Haas M98: P and L subprogram calls.