Saturday, October 27, 2018

Debugging commands

This leaves us with only two commands remaining. The BRK is a software interrupt, while the NOP command does nothing. Doing nothing is a handy thing as we will discuss shortly.
As mentioned in the previous article, when an IRQ happens, the processor stops what it is doing and executes the interrupt handler. BRK issues an IRQ but unlike a normal IRQ this one is not maskable so will always occur. This results in the code continuing execution at the address that is stored in $FFFE-$FFFF. On early computer systems this was set up to be a special machine language debugging program called a monitor. This allowed the programmer to scatter BRK statements through their code which would trigger the monitor when they were reached. The monitor would display status information about the program and let the programmer issue debugging commands for doing things such as changing the values in memory, changing the execution address, or continuing the program right after the break point.

When a programmer was finished with a specific BRK statement, they would simply replace the value at that location with $EA which is the OPCode for NOP. In other words, in the early days break points were coded into a program!

As if removing break points from a program wasn’t a good enough reason to have the NOP command, it also takes two cycles to execute. This means that if you have timing critical parts of your program you can use NOP instructions as padding to get the right timing. With the 2600, timing is very important so this is not something to be taken for granted.

Testing was a bit tricky for implementing these instructions. Making sure that the NOP was timing correctly probably didn’t need testing as that is handled by the emulator but as the operation of the TIA chip requires precise timing making sure cycles are working important so for these tests run until a cycle is reached instead of until a break is reached. This is kind of required as testing the break does require that the break command actually get executed!

Address Mode
Decimal OPCode
Hexadecimal OpCode
Flags affected: I
Usage: Triggers the Break interrupt causing the program to start executing code from the address stored at $FFFE-$FFFF. Used for debugging with the IRQ address set to a monitor program.
Test Code:
; BRK  -> requires IRQ set to 512, cycles = 71
            LDX #255 ; 2
            TXS     ; 4
loop: BRK        ; 11, 33, 55, END
            ; start here with 22, 44, 66
            INX ; 24, 46, 68
            JMP loop   ;27, 49, 71
.ORG 512
            ; reach here at 11, 33, 55
            TAY  ; 13, 35, 57
            TXA  ; 15, 37, 59
            RTI ; 22, 44, 66
            ;x=2, y = 0, A = 1

pushByteOnStack((state.ipNext / 256) and 255)
pushByteOnStack(state.ipNext and 255)
pushByteOnStack(state.flags or BREAK_FLAG or INTERRUPT_FLAG)
state.ipNext = findAbsoluteAddress(0xfffd)

NOPNo OPeration
Address Mode
Decimal OPCode
Hexadecimal OpCode
Flags affected: None
Usage: This command tends to be used for debugging to replace BRK instructions. It can be used in timing sensitive programs as a 2 cycle delay.
Test Code:
; NOP -> run for 32 cycles
            LDX #0            ;2
            loop: NOP  ; 4, 15, 26
            NOP                ; 6, 17, 28
            NOP                ; 8, 19, 30
            INX                  ; 10, 21, 32
            BNE loop   ;13, 24
            ; expect at 32 cycles for x=3

Saturday, October 13, 2018


Interrupts can be confusing though shouldn’t be. Part of the problem is that they happen outside of the program and are designed to be silent in their nature. The reason interrupts exist is to allow for outside devices to let the processor know that something has happened. When it happens, the 6502 stops executing the current program, runs an interrupt handler, then returns to the program that was running without the executing program being aware that it was interrupted.

The 6502 uses a vector table located at the end of address space that holds addresses for handlers for the 3 types of interrupts that it supports. The Atari 2600 only needs to worry about resets and IRQ caused by issuing a BRK statement but other systems (NES) do use NMIs.

Low byte address
High byte address
Non-maskable interrupt (SEI won’t stop it)
The processor has been reset
Maskable interrupt or BRK has occurred

When an interrupt request (IRQ) happens the processor does the following:
1.    Push the next IP address (high byte then low byte) onto the stack to save current executing position with the program.
2.    Pushes the processor status register onto the stack (essentially performs a PHP command)
3.    Sets the IP address to the address stored in the vector table as outlined in the table above.

This takes a total of seven cycles plus the time of the interrupt code plus the six cycles for the RTI instruction which means that an interrupt that happens during a timing loop will potentially throw off the program’s timing by quite a bit. It is the responsibility of the interrupt handler to make sure that any registers it uses are restored to the values that they originally contained before issuing the RTI.

The RTI instruction, in case it is not obvious already, returns execution back to the program by doing the following:
1.    Pulls the processor status from the stack (essentially a PLP instruction).
2.    Pulls the IP address of the next instruction that would have occurred before the interrupt (low byte then high byte) from the stack and sets the IP address to that value

Writing an interrupt handler is simply a matter of saving off any registers you use before changing the registers and restoring the registers before calling RTI. It is my understanding that the CLI command doesn’t need to be issued unless you want the interrupt handler to be interruptible, which may be the case if you are performing a complex task.

RTIReTurn from Interrupt
Address Mode
Decimal OPCode
Hexadecimal OpCode
Flags affected: CDINVZ (from stack)
Usage: Used by interrupt handlers to return execution back to the program once the interrupt has been handled.
Test Code:
LDX $FC ; for test, set up stack
TXS     ; the interrupt data alreay in there (see org)
CLC     ; the carry flag is set in the stack
RTI     ; return using stack data
.BYTE  33 8 2
.ORG 520
BCC done   ; the carry flag is set in the stack!
LDX #42
done: BRK
; expect X=42
state.flags = pullByteFromStack()
val low = pullByteFromStack()
val high = pullByteFromStack()
state.ipNext = (high * 256 + low)