⭅ Previous (Emulator Design) Next (Emulation Tips) ⭆

More 6502 Details. Negative Numbers, Status, and Interrupts

Also available on Youtube.

Last time we looked at CPU instructions for the 6502, and got a general idea for how these work.

Before we’re ready to start implementing the full instruction set, there are a few more details we need to understand. We’ll be looking at representing negative numbers, the status register, the stack, and interrupts.

Negative Numbers (Twos Complement)

So far we’ve only looked at representing positive numbers in a computer. Though with a trick, the same hardware that can do math with positive numbers can work with (carefully represented) negative numbers.

The most popular form of representing negative numbers is called “twos complement”. Nearly all CPUs, including the 6502 in the NES, use twos complement to represent negative numbers.

In twos complement, the left most bit is used to indicate whether a number is negative. This means only 7 bits are left over for the value.

Negative numbers count up, starting at -128 :

Unsigned 8 bit value range: 0 to 255
Signed 8 bit value range: -128 to 127
-128 = 0b1000 0000
-127 = 0b1000 0001
  -1 = 0b1111 1111
   0 = 0b0000 0000
   1 = 0b0000 0001
 127 = 0b0111 1111

We call a number “signed” if it is using the leftmost bit to represent positive / negative, and “unsigned” if the number is positive and using all the bits to represent the value.

A trick with twos complement is that all common operations work as normal, assuming there is enough space to represent the result.

Adding -1 and 1:
0b   1111 1111
0b   0000 0001

0b 1 0000 0000
only 8 bits stored:
0b   0000 0000

Twos complement is “compatible” with unsigned numbers, in two ways. The representation of positive numbers is the same, so if your positive is within the range of a signed number no conversion is required. And adding / subtracting etcetera happens the same for signed and unsigned values, so the CPU doesn’t really know or care if your value is signed or unsigned. It is up to the programmer to know whether a value is to be understood as signed or unsigned.

One gotcha is that signed numbers have a slightly narrower range of representable values, but the status register can help us catch issues here.

6502 Status Register

As we saw in the registers article, the status register is an 8 bit register in the CPU. It generally cannot be accessed directly, but instead is updated as a side effect of many instructions.

Lets look at the 8 bits of the status register to see what it tracks:

76543210
NV B IZC

N : Negative
V : Overflow
B : Break
I : Interrupts disabled
Z : Zero
C : Carry

We can work through these one at a time. These are described assuming a given cpu enables a flag. Though not all instructions touch every flag. If an instruction does not use a flag, it is left with its old value.

Negative

Set whenever the leftmost bit aka the sign bit of a result is set. This only matters if your operation was on signed numbers. 128 (unsigned) would also have the leftmost bit set and thus set the negative flag.

This is useful for implementing “less than” checks. Subtract two numbers, A - B, and if negative is set, the A was less than B.

Overflow (Signed overflow)

Set to track overflows for signed operations.

We can think about overflow as a way to detect when a number becomes too positive, or too negative, to keep the sign working as expected. Since the most negative number we can represent in 8 bits is -128, and the most positive number 127, this tracks whether the result would have been < -128 or >127.

This is only useful when working with signed numbers. A similar idea for unsigned numbers is in the carry bit.

Break

Used to track whether an interrupt was called by the break instruction, or by a hardware interrupt. More on interrupts in a bit.

Interrupt Disable

Certain system interrupts can be disabled, and are done so by setting this bit. There are two instructions to set/clear this bit, aka enable or disable interrupts.

Zero

Simple, set whenever the result is zero. This is used often for checking whether two values are equal. Subtract two numbers, then check if the zero bit is set.

Carry (Unsigned overflow)

Set if the result needed more bits than were available for an unsigned number. Remember that 255 is the largest number representable by 8 bits. It looks like(spaces added for legibility) 0b1111 1111. If we were to try adding one to this, it would be 0b 1 0000 0000. Notice that this number now takes 9 bits to represent. In an 8 bit machine, however, the 1 would disappear and we would be left with 0b0000 0000. The carry bit would be set in this scenario.

Fortunately, many math operations update the overflow bit, to tell us when the result was computed but didnt fit. Another way to describe this is that the result would have been >255.

Updating the Status register

When does the status register need to be updated? Any good 6502 reference should document, per instruction, which flags get updated. If the flag is to be updated, it gets a new value as described above. Otherwise it is left alone.

We like the 6502 reference from Masswerk.

Interrupts

We briefly mentioned interrupts when explaining the break flag above. As the name suggests, interrupts are an interruption to the normal processing of the CPU. Interrupts can be triggered by hardware, or the Break instruction.

Some times the system needs to urgently notify the CPU of something, for example to meet timing requirements. The graphics chip, for example, interrupts the CPU to inform it that the screen has finished drawing, and the CPU is now able to prepare the next frame of graphics.

An interrupt causes the CPU to save its progress, and jump to code in another location. When this other code, called the “interrupt handler” is complete, it restores its state and can continue processing where it was before.

Some interrupts can be disabled by the interrupt bit we saw above. Others, like the non-maskable (aka non-disable-able) interrupt (NMI), cannot.

How does the CPU save its state? That brings us to the stack.

The Stack

The stack is a way of tracking usage of temporary values of memory. Values added to the stack can be retrieved in the reverse order they were added. That is, values added most recently will be retrieved first. Sometimes this is called Last-In-First-Out, or LIFO.

Its called the stack because it can be thought of as a stack of books:

       <- Top of stack aka SP. Smaller address
0xAD   
0xDE
0xEF
0xBE  <- Bottom of stack. Larger address.

The stack is managed by the 8 bit SP register.

When we add or “push” a value onto the stack, the value goes into the address held by SP. SP is then decreased by 1. Removing does the opposite. The 6502 called this “pulling” a value, though in modern terms this is often called “popping” a value. Pulling decreases SP, then gives the value currently at SP.

One quirk with the 6502 is that SP is only 8 bits, yet refers to memory. The stack refers to memory addresses from 0x0100 to 0x01FF. The real stack address can be found by adding 0x0100 to SP.

6502 Explored!

With that we’ve finally seen all the key features of the 6502 cpu. Next we’ll cover a few tips for implementing a 6502 emulator.

⭅ Previous (Emulator Design) Next (Emulation Tips) ⭆

We publish about 1 post a week discussing emulation and retro systems. Join our email list to get notified when a new post is available. You can unsubscribe at any time.