Following on from the other alternative assembly-syntax topic, whose design has completely different goals…
In my view, the single biggest handicap of the 65816 is its proliferation of mode flags, some of which actually change the length of instructions (using the same mnemonic and opcode) and are therefore relevant to the assembler. In the standard syntax promoted by WDC, the same syntax is used for both "long" 16-bit and "short" 8-bit immediate operands, and so the only way the assembler knows which is which is through internal state managed by assembly directives. Keeping track of this state can be very error-prone for the programmer, and the symptoms of the resulting bugs can be very odd indeed.
Another artefact of the traditional syntax is the proliferation of individual mnemonics for simply moving data around - an astonishing number given the small number of registers present. More modern assembly syntaxes use few mnemonics (sometimes just one) and specify the source and destination explicitly. Most of the non-transfer operations are more economical in their use of mnemonics.
A third fault of the traditional syntax is that the programmer cannot always explicitly specify which size of address to use, without relying on non-standard assembler extensions. With the older 6502 family, it was rare for the programmer to need an absolute 16-bit address when referring to zero-page, but the 65816 now has Direct Page which can refer to any 256-byte area in the first 64K, and the Data Bank Register can shift absolute-mode addresses by multiples of 64K; hence $0000 may dynamically refer to a different address than $00 in two different ways, either or both of which may differ from $000000. The standard 65816 extensions to the traditional 6502 syntax do not adequately address this.
The new syntax proposed below departs significantly from tradition to correct the above, aiming to
support programmers of the 65816 instead of confusing them and getting in the way. A subset may of course be used on the older members of the 6502 family.
The first distinctive feature is that all directly-accessible registers have names which, if they have mode flags influencing size, differ to distinguish those sizes. This makes the assembler stateless, though disassemblers still inherently need to track (or guess) the CPU's mode to correctly determine instruction sizes. Registers which have only one size, or whose size doesn't affect instruction or transfer size, have only one name:
- Accumulator: A (8-bit), AA (16-bit).
- Indexes: X,Y (8-bit), XX,YY (16-bit).
- Stack Ptr: S (8-bit in emulation mode, 16-bit in native mode).
- Status Register: P (8-bit).
- Direct Page Ptr: D (16-bit).
- Data Bank Register: B (8-bit high).
- Program Bank Register: K (8-bit high).
- Program Counter: * or PC (16-bit)
The other distinctive feature is that immediate and memory operands have a completely new syntax. The # prefix formerly identifying immediate operands is dropped; instead addressed memory accesses are explicitly called out by three types of bracket, corresponding to direct-page {}, absolute (), or long [] addresses respectively. Indirect addressing modes contain two nested sets of brackets, indexed modes use + instead of a comma to link the appropriate register in, and stack pushes and pops involve the C post-decrement and pre-increment operators on the S register. All of the 65816's addressing modes can thus be expressed compactly, readably and unambiguously, whether the explicit operand is a literal, a label or a constant expression:
- Immediate: expr
- Direct Page: {expr}
- Direct Page Index: {expr+X} or {expr+XX} or…
- Absolute: (expr)
- Index: (expr+X)
- Absolute Long: [expr]
- Index Long: [expr+X]
- Direct Page Indirect: ({expr})
- Direct Page Index Indirect: ({expr+X})
- Direct Page Indirect Index: ({expr}+Y)
- Direct Page Indirect Long: [{expr}]
- Direct Page Indirect Index Long: [{expr}+Y]
- Stack Pull: {++S}
- Stack Push: {S--}
- Stack Relative: {S+expr} -- the braces here indicate that the bank address is not taken from B, and the explicit offset is 8-bit.
- Stack Indirect Post-Index: ({S+expr}+Y)
Jump and branch instructions do not directly follow the above pattern. If they did, then an absolute jump target would require "(label)", and an indirect jump "((label))". But these instructions don't involve any explicit access of the data at the target address, only loading or adding that address to the PC, which is more similar to using an immediate operand. For that reason, the traditional syntax is retained for jump and branch operands:
- Branch Relative: *+expr
- Branch to Label: label -- assembler will compute correct relative offset, and promote to long branch if required
- Jump Absolute or Long: expr or label
- Jump Indirect: (expr)
- Jump Index Indirect (expr+X)
- Jump Indirect Long: [expr]
Differences of opinion exist as to whether uniform three-letter mnemonics are superior to variable-length ones. The latter may require less typing on average, while the former help to align the operands for readability. If the programmer sets up tab stops appropriately (eg. every 4 columns) and uses a tab as the whitespace immediately following the mnemonic, the operands are just as easy to align as with uniform-length mnemonics, and the programmer receives an extra visual cue to help follow the structure of his code. A preliminary list of suggested mnemonics, based on this principle:
Code:
M move, replaces LDA, LDX, LDY, STA, STX, STY, TAX, TAY, TYA, TXA, TXY, TYX, PHA, PHX, PHY, PHP, PHB, PHD, PHK, PLA, PLX, PLY, PLP, PLB, PLD, TXS, TSX, TSC, TCS, TDC, TCD, PEA, PER, PEI
A add, replaces ADC
S subtract, replaces SBC
I increment, replaces INC, INX, INY
D decrement, replaces DEC, DEX, DEY
C compare, replaces CMP, CPX, CPY
T test bits, replaces BIT
TS test and set, replaces TSB
TC test and clear, replaces TRB
LO logical OR, replaces ORA
LA logical AND, replaces AND
LX logical XOR, replaces EOR
RL rotate left, replaces ROL
RR rotate right, replaces ROR
SL shift left, replaces ASL
SR shift right, replaces LSR
SH swap halves, replaces XBA
CL clear, replaces STZ, CLC, CLD, CLV, CLI, REP
ST set, replaces SEC, SED, SEI, SEP
BPL, BMI, BVC, BVS, BNE, BEQ, BCC, BCS, RTS, RTL, RTI, BRK, COP, NOP, XCE, all perform their traditional duties.
B branch, replaces BRA and BRL
BL branch long, forces BRL
J jump, replaces JMP and JML
JL jump long, forces JML
JS jump subroutine, replaces JSR (does *not* promote to JSL, because it's not transparent to the callee)
JSL jump subroutine long, replaces JSL
MBD move block decrementing, replaces MVP
MBI move block incrementing, replaces MVN
WAIT, STOP replace WAI, STP
WDM William D. Mensch, emitted as a 1-byte instruction to permit skipping a following 1-byte instruction.
NOPL long NOP, is actually WDM as an explicit 2-byte, 2-cycle NOP; the second byte is emitted as a standard 1-byte NOP.
For CL and ST, the flags of the P register are addressed by their familiar initials NVMXDIZC, and may be concatenated like that to set or reset multiple flags at once (emitting a SEP or REP). The assembler will emit one-byte instructions if a single C, D, V or I is given, but can be forced to emit SEP or REP if the letter is doubled. STZ, being a store instruction, is signalled by the operand of CL beginning with a bracket - so there is no namespace conflict.
Many of these new mnemonics (especially M!) require one or two registers to be specified explicitly for disambiguation, even though they replace formerly "implicit mode" mnemonics. Where two operands are needed, the destination register is listed first, as is common practice for RISC assembly. For example, "LDA $00" becomes "M A, {$00}" assuming an 8-bit accumulator, or "M AA, {$00}" if 16-bit. Instructions which operate only on the accumulator, however, do not need to declare this fact (eg. "A {$00}" is a sufficient replacement for "ADC $00").
While not mandatory, the assembler may aid programmers by watching for inconsistent use of register names of different sizes without changing mode meanwhile, and emitting warning messages if such mistakes are found. Technically, the register name is only significant to the assembler when immediate operands are used, but the programmer effectively asserts and reminds himself that a particular register mode is assumed through this mechanism. Sophisticated assemblers may trace through direct branches, jumps and subroutine calls to enhance the accuracy of this check. Indirect jumps, including BRK and COP, should be assumed to terminate the consistency check, since they can be re-pointed at any code whatsoever.