← Back to CNC Programming

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.

Level: KScope: Haas mill examples; metric unless stated otherwisePrerequisite: Canned-Cycle State and Hole Patterns
After this lesson, you can:
Four positions, twelve drilling eventsRepeating an unchanged absolute-coordinate subprogram three times repeats four positions; it does not create twelve distinct positions.4 positions3 calls12 events
If O2000 keeps the same work offset and absolute coordinates, each of four positions is drilled three times. Different fixtures require deliberate repositioning or a documented offset change.

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

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.

Diagram accompanying the adjacent worked example. Read the accompanying explanation for assumptions and values.Diagram accompanying the adjacent worked example Subprogram Call/Return Stack Main O0001 → M98 P2000 O2000 → M98 P3000 O3000 ← M99 M99 returns to the caller.
Subprograms and the Controller Call Stack — read the adjacent example for the stated conditions.

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

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