Toggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage
Fixed cycle delay
Shortest possible CPU code that creates N cycles of delay, depending on constraints.
Code
All code samples are written for CA65.
Assumptions:
No page wrap occurs during any branch instruction. If a page wrap occurs, it adds +1 cycle for each loop, completely thwarting the accurate delay.
No interrupt / NMI occurs during the delay code.
It is permissible for DMA to steal cycles during the loops. If you are expecting that to happen, you have to manually adjust the delay cycle count (and it is in fact possible to do so) in order to get the correct delay.
Explanations on the requirements
@rts12 means you know a memory address that contains byte $60 (RTS).
cycle instruction that fits your constraints (such as LDA $00), followed by RTS.
Instructions, addressing modes, byte counts, cycle counts and notes
Addressing mode
Instruction type
Bytes
Cycle count
Example instruction
Notes
Implied
Inter-register
1
2
TAX
NOP has no side effects. Flag-manipulations like CLC, and SEC``CLV are used when their effects are desired.
Implied
Stack push
1
3
PHA
PHP is only paired with PLP.
Implied
Stack pop
1
4
PLA
Implied
Return
1
6
RTS
Used indirectly when paired with JSR. Similarly for RTI.
Immediate
2
2
CMP #$C5
Includes instructions like LDA, LDX and LDY. Other ALU instructions are used in more complex situations.
Relative
Branch
2
2—4
BCC *+2
Branch takes 3 cycles when taken, 2 otherwise. A page crossing adds +1 cycle when branch is taken, but because of difficulties setting that up, we don’t use it.
Zeropage
Read, write
2
3
LDA $A5
Zeropage
RMW
2
5
INC @zptemp
Writing to zeropage is only permitted when @zptemp is available. Technically we could save @zptemp into register and restore at end, but it is bytewise inferior to other techniques.
Zeropage indexed
Read, write
2
4
LDA $EA,X
Inferior to 2 × NOP, but useful for hiding additional code to be executed in a loop.
Zeropage indexed
RMW
2
6
INC @zptemp,X
Only doable when X is known to be 0, or when entire zeropage can be clobbered.
Indexed indirect
Read, write
2
6
STA (@ptrtemp,X)
Only doable when X is known to be 0.
Indexed indirect
RMW
2
8
SLO (@ptrtemp,X)
The most cost-effective instruction. Only doable when X is known to be 0, lest we write to a random address. All instructions in this category are unofficial.
Indirect indexed
Read
2
5—6
LDA (@ptrtemp),Y
Never used by this code.
Indirect indexed
Write
2
6
STA (@ptrtemp),Y
Only doable when Y is known to be 0.
Indirect indexed
RMW
2
8
SLO (@ptrtemp),Y
All instructions in this category are unofficial.
Absolute
Jump
3
3
JMP *+3
Absolute
Read, write
3
4
LDA $2808
Inferior to 2 × NOP, but can be used carefully to hide additional code to be executed in a loop.
Absolute
RMW
3
6
INC $4018
Inferior to 3 × NOP.
Absolute indexed
Read
3
4—5
LDA $0200,X
Inferior to shorter alternatives.
Absolute indexed
Write
3
5
STA $0200,X
Inferior to shorter alternatives.
Absolute indexed
RMW
3
7
INC $4018,X
Only doable when writing into the given address is harmless considering the possible values of X.
All instructions cost at least 2 cycles. There is no way to do 1 cycle of delay (though −1 cycles may sometimes appear in branch cost calculations).
3 cycles
2 bytes
C5 C5 CMP $C5
Clobbers Z&N, and C
24 24 BIT $24
Clobbers Z&N, and V
A5 A5 LDA $A5
Clobbers A, and Z&N
A6 A6 LDX $A6
Clobbers X, and Z&N
A4 A4 LDY $A4
Clobbers Y, and Z&N
3 bytes
4C xx xx JMP *+3
No requirements
Not relocatable means that the target address is hardcoded into the code. In ROM hacking, it sometimes makes sense to move code blobs around, and a hardcoded address makes it difficult to relocate the code. This restriction does not apply to branches, which use relative addressing. It is also assumed to not apply to JSR instructions, as chances are the JSR target is outside the code being relocated.
4 cycles
2 bytes
EA ... NOP × 2
No requirements
zp-indexed modes such as LDA $00,X also do 4 cycles, but having side effects, these two-byte instructions are inferior to a simple 2 × NOP.
There is also an unofficial opcode NOP $00,X (34 00), but there is no reason to use this instruction when the official equivalent has the same performance.
5 cycles
3 bytes
18 CLC
90 00 BCC *+2
Clobbers C
B8 CLV
50 00 BVC *+2
Clobbers V
EA NOP
A5 A5 LDA $A5
Clobbers A, and Z&N
EA NOP
A6 A6 LDX $A6
Clobbers X, and Z&N
EA NOP
A4 A4 LDY $A4
Clobbers Y, and Z&N
4 bytes
EA NOP
4C xx xx JMP *+3
No requirements
abs-indexed modes such as LDA $1234,X cause 4 or 5 cycles of delay, depending whether a page wrap occurred. Because you need extra setup code to make sure that a wrap does occur, you do not see this mode in these samples, outside situations where circumstances permit.
6 cycles
3 bytes
EA ... NOP × 3
No requirements
zp-indexed RMW instructions such as INC @zptemp,X do 6 cycles. Unless we know the value of X, it might write into any address between $00-$FF. This option is only useful if the entire range of $00-$FF is free for clobbering with random data, or if X has a known value.
ix instructions like LDA ($00,X) do 6 cycles, but the value of X decides where a pointer is read from, and said pointer can point anywhere. We only do that when the value of X is known.
iy instructions like LDA ($00),Y also do 5-6 cycles, but in addition to the note above, we cannot predict whether a wrap occurs or not. So we don’t use this mode.
Absolute RMW instructions like INC $4018 do 6 cycles, but weighing 3 bytes with side-effects it would be inferior to 3 × NOP.
7 cycles
2 bytes
08 PHP
28 PLP
No requirements
PHP-PLP is very efficient for 7 cycles of delay, but it does modify stack contents. S register remains unchanged though.
PLA-PHA does not overwrite any bytes in stack. It just writes back the same byte. But it does clobber A and Z+N. It is not interrupt-unsafe either: If an interrupt happens, the stack byte does get temporarily clobbered, but the value is still in A when the interrupt exits, and gets written back in stack.
abs-indexed RMW instructions such as INC abs,X do 7 cycles. We only do this when either we know the value of X (for instance, INC $4018,X is safe when X is 0—7, or when the entire 256-byte page can be safely overwritten with random data.
8 cycles
4 bytes
EA ... NOP × 4
No requirements
Unofficial ix and iy RMW instructions such as SLO ($00,X) or SLO ($00),Y would do 8 cycles for 2 bytes of code. We only do that if we know X or Y to be zero, and we have a known pointer to safely rewritable data.
9 cycles
3 bytes
EA NOP
08 PHP
28 PLP
No requirements
Jumping into the middle of another instruction and thereby reusing code is a very efficient way of reducing code size. Note that all code samples using branches on this page require that no page wrap occurs.
10 cycles
4 bytes
08 PHP
C5 C5 CMP $C5
28 PLP
No requirements
The ROL-ROR sequence preserves the original value of the memory address. Carry is also preserved.
11 cycles
4 bytes
EA ... NOP × 2
08 PHP
28 PLP
No requirements
12 cycles
3 bytes
20 xx xx JSR @rts12
Requires @rts12
4 bytes
36 36 ROL $36,X
76 36 ROR $36,X
Clobbers Z&N
5 bytes
08 PHP
18 CLC
90 00 BCC *+2
28 PLP
No requirements
JSR-RTS causes 12 cycles of delay. But it does write a function return address in the stack, which may be unwanted in some applications. S is not modified.
Again, ROL-ROR does not have side effects (as long as an interrupt does not happen in the middle), except for Z+N.
13 cycles
5 bytes
EA ... NOP × 3
08 PHP
28 PLP
No requirements
14 cycles
4 bytes
08 PHP \ × 2
28 PLP /
No requirements
15 cycles
5 bytes
08 PHP
BA TSX
28 PLP
9A TXS
28 PLP
Clobbers X
C5 C5 CMP $C5
20 xx xx JSR @rts12
Clobbers Z&N, and C; and requires @rts12
24 24 BIT $24
20 xx xx JSR @rts12
Clobbers Z&N, and V; and requires @rts12
A5 A5 LDA $A5
20 xx xx JSR @rts12
Clobbers A, and Z&N; and requires @rts12
A4 A4 LDY $A4
20 xx xx JSR @rts12
Clobbers Y, and Z&N; and requires @rts12
6 bytes
08 PHP
28 PLP
EA ... NOP × 4
No requirements
16 cycles
5 bytes
EA NOP
08 PHP \ × 2
28 PLP /
No requirements
17 cycles
6 bytes
08 PHP
48 PHA
A5 A5 LDA $A5
68 PLA
28 PLP
No requirements
18 cycles
6 bytes
EA ... NOP × 2
08 PHP \ × 2
28 PLP /
No requirements
19 cycles
5 bytes
08 PHP
28 PLP
20 xx xx JSR @rts12
Requires @rts12
6 bytes
08 PHP
36 36 ROL $36,X
76 36 ROR $36,X
28 PLP
No requirements
20 cycles
5 bytes
A9 2A LDA #$2A ;hides 'ROL A'
38 SEC
10 FC BPL *-2
08 PHP
A2 05 LDX #5
EA NOP
CA DEX
10 FC BPL *-2
28 PLP
Clobbers X
08 PHP
A0 06 LDY #6
EA NOP
88 DEY
D0 FC BNE *-2
28 PLP
Clobbers Y
9 bytes
08 PHP
48 PHA
A9 E9 LDA #$E9 ;hides 'SBC #$2A'
2A ROL A ;first loop only
B0 FC BCS *-2
68 PLA
28 PLP
No requirements
Sanity checks
It is possible to verify on compile time that no page wrap occurs, by replacing all branches with these macros:
.macro branch_check opc, dest
opc dest
.assert >* = >(dest), warning, "branch_check: failed, crosses page"
.endmacro
.macro bccnw dest
branch_check bcc, dest
.endmacro
.macro bcsnw dest
branch_check bcs, dest
.endmacro
.macro beqnw dest
branch_check beq, dest
.endmacro
.macro bnenw dest
branch_check bne, dest
.endmacro
.macro bminw dest
branch_check bmi, dest
.endmacro
.macro bplnw dest
branch_check bpl, dest
.endmacro
.macro bvcnw dest
branch_check bvc, dest
.endmacro
.macro bvsnw dest
branch_check bvs, dest
.endmacro
Delay code for functions that produce runtime-determined amount of delay
Bisqwit’s “vending machine” for producing a ca65-compatible delay_n macro for arbitrary number of cycles, with more fine-grained configurable constraints: http://bisqwit.iki.fi/utils/nesdelay.php The samples on this page are excerpts from files generated by this online tool.