The 6502 registers youre probably most familiar with, are the accumulator and the X- and Y-registers. There are three more registers though, which you may not access directly through code, but are at least as important as the former three. In this post, I’ll explain all about the Status register, and how this works with other instructions. This is a somewhat longer post, so buckle up and dive in!
The status register, also referred to as the status flag register, consists of eight bits of data regarding the last instruction that was executed. Each bit is a flag, which can be considered to be a switch that is either on or off. From most to least significant bit, the status register holds the following information:
#%76543210 NVxBDIZC
Bit 0: the (C)arry flag
The carry (or borrow) flag is used in math and rotate operations. If the result of an arithmetic operation is less than the initial value, the carry flag is set (ie. being switched on, getting a value of 1). If it is equal, the carry flag holds its initial value, and when the result is larger, the carry flag is cleared (ie. switched off, getting a value of 0). When comparing two values, the carry flag is set if the original value is greater than or equal to the value it’s compared to. With shifting instructions (ASL
, LSR
, ROL
, ROR
), the carry flag gets the value that’s been shifted off by the operation. Finally, there are two instructions which can set (SEC
) and clear (CLC
) this carry flag.
Here’s a few examples with Assembly instructions how these reflect the Carry flag:
Arithmetic instruction: adding up two values
LDA #$90 ; load a value of #$90 hex, or #144 in decimal, into the accumulator CLC ; clear the carry flag: C now has a value of 0 ADC #$80 ; add #$80 (or #128 in decimal) to the accumulator ; the accumulator now has a value of #$10: ; #$90 + #$80 = #$110, but the accumulator only has eight bits, so the ; 1 on the left gets "lost" from the accumulator (this is called ; overflow). Because #$10 is less than #$90 (the initial value) though, ; the Carry flag gets set, so C now holds a value of 1. BCC +here ; (B)ranch if the (C)arry flag is (C)lear. If the Carry flag is 0, ; the script will branch out to where the + label is. Because in ; this case the Carry flag is 1, the script will not branch out. +here:
Compare instruction
LDA #$20 ; Load a value of #$20 (#32 in decimal) into the accumulator CMP #$30 ; Compare the value of the accumulator with #$30 (#48 in decimal) ; Because #$30 is greater than #$20, the Carry flag will be set to 1. BCS +here ; (B)ranch if the (C)arry flag is (S)et. Because the Carry flag is ; set to 1, the script will branch out to the +here label. +here:
Shifting instruction: shift value to the right
LDA #%10101010 ; Load a binary value of #%10101010 (#$AA in hex, #170 in decimal) ; into the accumulator. LSR ; Shift the value of the accumulator to the right. This changes ; the value of the accumulator to #%01010101 (all bits get shifted ; to the right one spot). The rightmost bit gets shifted off; ; the Carry flag now holds this bit's value (in this case 0). BCC +here ; If the Carry flag is clear (0), the script branches out to +here. ; In this case, the Carry flag is 0, so it will branch out. +here:
Bit 1: the (Z)ero flag
The zero flag is usually set when the result of an operation equals zero, and unset if the result is not zero. With compare instructions, this flag will be set if the compared values are equal. One edge case is the BIT
instruction: this basically does an AND operation without modifying the contents of the accumulator, and sets the zero flag accordingly if the result would have been zero.
A few Assembly examples:
Check if accumulator is zero
LDA #$00 ; Load zero into the accumulator; this sets the Zero flag to 1. BEQ +here ; Check if the zero flag is set - if so, branch out to +here. ; In this example, the zero flag has been set, so the code will ; branch out. +here:
Check if two values are equal
LDA #$10 ; Load a value of #$10 (or decimal #16) into the accumulator. CMP #$20 ; Compare this value to #$20 (decimal #32). These values are ; not equal obviously, so the Zero flag is cleared (set to 0). BNE +here ; Check if the zero flag is clear - if so, branch out to +here. ; In this example, the zero flag is cleared, so the code will ; branch out. +here:
Toggle check
LDA temp ; Load the value of a temporary variable into the accumulator. BIT #$01 ; Do a logical AND with #%00000001 (or #$01, or #1) without ; manipulating the accumulator. This will set the zero flag ; if the result is zero (ie. the least significant bit of ; the temp variable is 0), and will clear the zero flag if ; it is not zero. BNE +here ; Check if the zero flag is cleared. If so, branch out to +here. +here;
Bit 2: the (I)nterrupt Disable flag
If this bit is set, the processor will disable interrupts like NMI, Reset and IRQ. We won’t touch upon this any further right now, as you probably won’t encounter this bit while writing code for NESmaker projects.
Bit 3: the (D)ecimal flag
This flag is used to toggle between decimal (1) and default (0) mode. In decimal mode, #$09 + #$01 would result in #$10 instead of #$0A. The NES’ version of the 6502 processor has this mode disabled, so we won’t go deeper into this one as well.
Bit 4: the (B)reak flag
This flag gets set if an interrupt request has been triggered by a BRK
instruction. You may use this for debugging purposes, although my guess is you won’t really touch upon this flag in practice.
Bit 5: unused (x)
This flag is always set to 1 and further unused.
Bit 6: the o(V)erflow flag
When executing arithmetic instructions (addition and subtraction) on signed integers, this flag gets set if the sign changes after the instruction. This is best explained with an example:
LDA #$10 ; Load #$10 (#16) into the accumulator SEC ; Set the carry flag to 1 SBC #$20 ; Subtract #$20 (#32) from the accumulator. This will result in a value ; of #$F0 which, considering the values to be signed integers, equals ; a decimal value of -16. Since the result (negative) has a different sign ; than the input values (both positive), the oVerflow flag gets set to 1.
There’s one exception: when a BIT
instruction has been executed, the overflow flag gets set to whatever value the 6th bit of the input has.
Bit 7: the (N)egative flag
This flag also considers the values to be signed integers. If an instruction results in a negative value, this flag gets set; otherwise it gets cleared. This essentially means the value of the (N)egative flag is the same as the value of the most significant (or leftmost) bit of the result: a result of #%1xxxxxxx
sets the (N)egative flag to 1, a result of #%0xxxxxxx
sets it to 0.
With compare instructions, this flag is set if the register’s value is less than the value it gets compared to. Example branch instructions that check this negative flag are BPL
(Branch if PLus, which checks if the negative flag is cleared) and BMI
(Branch if MInus, which checks if the negative flag is set).
To conclude
I hope this lengthly post has given you somewhat more insight in how arithmetic, compare and branch instructions are actually handled by the processor, and that this helps you in writing the correct branch instruction more easily.