M98 and G65 both jump out of the main program into a reusable block of code, and the difference is what travels with the call. M98 is the plain subprogram call: it runs another program by number, optionally several times in a row, and passes nothing; the subprogram sees the world exactly as the main program left it. G65 is the macro call: it also runs another program, but it carries arguments, letters in the call line that arrive in the macro as local variables, and that single capability changes what the called code can be. An M98 subprogram is a recording that plays back identically every time. A G65 macro is a function: the same code drills a bolt circle of any radius, any hole count, any depth, because those values arrive as parameters. Knowing which to reach for, and what each costs in portability, is a rite of passage between running programs and engineering them.

M98: the repeat button

The M98 call names a program and how many times to run it: M98 P1005 runs program O1005 once; on most Fanuc-style controls a repeat count rides along either as M98 P1005 L4 or in the older combined format where the P word packs count and program number together. The subprogram ends with M99, which returns to the line after the call; M99 in a main program loops back to the top, which is its own trick, covered in how to loop a G-code program with M99. The mechanics, with worked code, are in the M98/M99 subprogram example, and the Fanuc M-code list places both codes in the wider M-code family.

What makes M98 powerful despite its simplicity is the combination with modal state and with work offsets. The classic pattern machines one part’s features in a subprogram, then calls it once per fixture station: the main program selects G54 and calls O1005, selects G55 and calls it again, and so on across the tombstone. The subprogram’s coordinates are identical every call; the offset change re-aims them at a different part, a pattern that leans on G54 work offsets doing the aiming. Nesting is allowed, subprograms calling subprograms, to a depth limit that varies by control, usually somewhere between four and ten levels on Fanuc-family machines, a detail explored in subroutine nesting limits.

The limits are just as clear. The subprogram cannot be told anything at call time: no depth, no count, no size. Whatever flexibility it has must come from modal state it inherits, offsets, tool length, active plane, which is flexibility by side effect rather than by design. The moment the reusable code needs to behave differently per call in a way offsets cannot express, M98 has run out, and that is where G65 begins.

G65: the function call

A G65 line names the macro and hands it values: G65 P9010 A0.5 B4.0 Z-0.6 F8.0. Inside program O9010, those letters arrive as local variables under Fanuc’s fixed mapping: A becomes #1, B becomes #2, Z becomes #26, F becomes #9, and so on through a documented table (with three letters, G, L, N, O, P, unavailable because they mean things in the call line itself). The macro body then computes with them: #101 = #26 / 2, conditional logic with IF and GOTO, loops with WHILE, arithmetic, trigonometry. Local variables are private to the invocation, so a macro can call another macro without trampling its caller’s numbers, and each call gets a clean set. Variable numbering, which ranges are local, common, and system, is the subject of CNC variable programming with #100.

That machinery is what turns code into tooling. One bolt-circle macro replaces every bolt-circle pattern the shop will ever paste again: G65 P9010 X2.0 Y2.0 R1.25 H6 Z-0.5 drills six holes on a 1.25 radius about a center, and next month the same macro does twelve on a 3-inch radius. Probing cycles, the ones behind every touch-probe routine on a Fanuc-style machine, are G65 macros under the covers, which is why probe vendors ship them as O9000-series programs. Family-of-parts machining, where one program produces a range of sizes driven by a handful of variables, is the industrial-strength version, covered in macro programming for families of parts.

G65 has a modal sibling worth knowing exists: G66 calls a macro after every subsequent motion block until G67 cancels, useful for per-position operations like a custom cycle at each hole of a pattern. It is rarer in the wild, and understanding G65 first makes G66 a footnote rather than a mystery.

The choice in one table

M98 subprogramG65 macro call
Passes argumentsNoYes, letters map to local variables
Repeat countYes, L or packed PVia loop logic inside the macro
Variables and logicOnly global/common variablesLocal variables, IF/WHILE, arithmetic
Requires control optionNo, base feature everywhereMacro B option, absent on some basic controls
PortabilityHigh across Fanuc-style dialectsFanuc-specific; other controls differ
Best forFixed repeated blocks, offset-per-station patternsParameterized operations, probing, part families

The bottom two rows decide real cases more often than the top four. Macro B is an option, standard on most modern machining centers but genuinely absent on some stripped-down and older controls, and a program full of G65 calls is simply illegal there. And the macro language is Fanuc’s dialect: Haas implements a close cousin, but Siemens does structured programming its own way entirely, and LinuxCNC’s O-word subroutines achieve the same ends with sub, endsub, and named parameters, a syntax explored in O-word subroutine examples. The concepts transfer cleanly between all of them, call, arguments, locals, return, but the spelling does not, so portable thinking means learning the idea once and the dialect per machine.

A worked pair: the same job both ways

Face grooves at four stations, first as M98 with offsets:

(MAIN)
G54 M98 P2001
G55 M98 P2001
G56 M98 P2001
G57 M98 P2001
M30

O2001 (GROOVE AT CURRENT OFFSET)
G00 X0 Y0
G01 Z-0.125 F5.0
G01 X1.5 F8.0
G00 Z0.2
M99

Now the groove as a macro, when stations need different depths:

(MAIN)
G54 G65 P9021 Z-0.125
G55 G65 P9021 Z-0.100
G56 G65 P9021 Z-0.150
G57 G65 P9021 Z-0.125
M30

O9021 (GROOVE, DEPTH IN Z -> #26)
G00 X0 Y0
G01 Z#26 F5.0
G01 X1.5 F8.0
G00 Z0.2
M99

The two mains read almost identically, and that is the point: the macro version costs one variable and buys per-station depth without four copies of the groove code. The rule of thumb falls out naturally. Reach for M98 when the repeated code is genuinely identical and the variation lives in offsets. Reach for G65 the moment you feel the urge to copy a subprogram and edit one number in the copy, because duplicated near-identical code is where programs rot: the day the groove width changes, someone updates three of the four copies, and the fourth scraps a part quietly.

One more practical dimension separates the two: editing risk. Subprograms called by M98 are ordinary programs, listed and editable like any other, and shops treat them casually. Macro libraries live differently: O9000-series programs on Fanuc-style controls can be parameter-protected so they neither display nor accept edits, precisely because a probe macro corrupted by a stray keystroke will happily drive a touch probe into a fixture at feed. If a program calls numbers in the 9000 range and the control refuses to show them, that is the protection working, not a fault. Changing behavior in that world happens through the documented arguments of the call, never by editing the library, and vendors publish which letters their macros accept for exactly this reason.

Reading other people’s calls

In inherited programs, the call structure is the map. A wall of M98 calls with offset changes says: fixture stations, identical parts. O9000-numbers behind G65 calls say: probe routines or builder-supplied macros, usually protected from editing and worth leaving alone. A macro with WHILE loops and #500-series variables says: someone built persistent logic, counters, part tallies, adaptive behavior, and the #500-range variables survive power off, so the program has memory between runs. Tracing a call three levels deep before pressing cycle start is not paranoia; the program’s real behavior lives wherever the deepest M99 returns from, and general program-reading tactics from how to read a CNC program for beginners apply with interest here.

One operational note: subprograms and macros live in program memory by number, and calling a number that is not loaded stops the machine with an alarm. Program transfer between machines therefore has to carry the whole family, main plus every called program, which is a routine stumble when jobs move between machines, and one more reason shops standardize their macro libraries per control.

Bottom line: recordings versus functions

M98 replays a fixed block, and with work offsets it covers the entire multiply-a-part-across-stations world with base features every control has. G65 passes arguments into local variables and unlocks parameterized code: bolt circles that take a radius, probing, part families, logic. Choose M98 for identical repetition, G65 when a copy-and-edit-one-number urge appears, and translate the concepts, not the syntax, when moving to Haas, Siemens, or LinuxCNC dialects. And before running inherited code, follow the calls to the bottom; the part is cut by the deepest program in the stack, not the one on the screen.

Frequently asked questions

What is the difference between G65 and M98?

M98 calls a subprogram with no arguments: the called code runs identically every time, varied only by modal state such as the active work offset, with an optional repeat count. G65 calls a macro and passes arguments, letters in the call line that arrive as local variables (A to #1, B to #2, and so on), so the same code can run with different depths, counts, or sizes per call. M98 is a recording; G65 is a function.

When should I use a subprogram instead of a macro?

When the repeated code is genuinely identical between calls: machining the same features at multiple fixture stations under different work offsets is the classic case, and M98 handles it with base features every Fanuc-style control has. The moment different calls need different values, a depth here, a count there, and you feel the urge to duplicate the subprogram and edit one number, switch to G65 with arguments instead of maintaining near-copies.

Do all CNC machines support G65 macros?

No. Macro B is a control option: standard on most modern machining centers, absent on some basic and older controls, where G65 lines simply alarm. The macro dialect is also Fanuc-specific; Haas runs a close cousin, Siemens structures programs differently, and LinuxCNC uses O-word subroutines with named parameters. The concepts, call, arguments, local variables, transfer everywhere; the syntax must be relearned per control family.

What is the best way to learn subprograms and macro basics?

Recall the mechanics until they are automatic, M98/M99 pairing, the argument-to-variable mapping, which variables are local versus common, then rewrite one real repeated pattern from your own work both ways, as the practice cements it. A free app like G-Code Sprint drills the codes and variable rules in short timed quizzes and repeats what you miss, which keeps the mapping fresh long after the first read.

Why does my machine alarm when it hits an M98 or G65 line?

Three usual causes. The called program number is not loaded in memory, so the control cannot find it; transfers between machines must carry every called program, not just the main. The control lacks the Macro B option, making G65 illegal. Or the nesting limit is exceeded, subprograms calling subprograms past the control’s depth. The alarm text plus the program directory listing identifies which within a minute.

Sources