Saturday, October 13, 2018

Interrupts


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
Interrupt
Explanation
$FFFA
$FFFB
NMI
Non-maskable interrupt (SEI won’t stop it)
$FFFC
$FFFD
Reset
The processor has been reset
$FFFE
$FFFF
IRQ
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
Size
Cycles
Implied
77
$4D
1
6
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
BRK
.ORG $1FD
.BYTE  33 8 2
.ORG 520
BCC done   ; the carry flag is set in the stack!
LDX #42
done: BRK
; expect X=42
Implementation:
state.flags = pullByteFromStack()
val low = pullByteFromStack()
val high = pullByteFromStack()
state.ipNext = (high * 256 + low)

Saturday, September 29, 2018

Rotating and Shifting Bits right



Shifting and rotating right works same as rotating left except in opposite direction so the bits move from high to low. The right shift command is LSR while the rotation command is ROR.
In the previous article we packed a playing card into a byte. We had a bit to representing if the card was revealed, 4 bits to hold the face value, and 2 bits to hold the suit. This would give us a packed byte in the format 0RFFFFSS. To unpack the card byte we can do the following



LDA cardToUnpack
TAX
AND #3
STA cardSuit
TXA
LSR
LSR
TAX
AND #15
STA cardFace
TXA
LSR
LSR
LSR
LSR
STA cardShowing
As with multiplication, shifting right is the equivalent of dividing by powers of 2. Unfortunately non-powers of two are a bit more complicated to perform than non-power of two multiplications.



Working with multi-byte shifting is a bit different with multiple bytes as you start with the highest byte and work your way towards the lowest byte using LSR on the high byte (ROR if doing a multi-byte rotation) and using ROR for all the remaining bytes working towards the lowest byte. Here is a two-byte example:



LDA highByte
LSR
STA highByte
LDA lowByte
ROR
STA lowByte
RORROtate Right by one bit
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Accumulator
106
$6A
1
2
Zero Page
102
$66
2
5
Zero Page,X
118
$76
2
6
Absolute
110
$6E
3
6
Absolute,X
126
$7E
3
7
Flags affected: CNZ
Usage: Rotates bits right using the carry bit to fill in the missing bit and putting the low order bit into the carry flag. Used primarily for division by powers of 2 and reading serial data though can be used to reposition bits.
Test Code:
; ROR accumulator
CLC
LDA #1
ROR A
BRK
; expect A=0, Z=1, C=1, N=0
; ROR with memory
LDX #1
ROR 254
ROR 254,X
ROR 256
ROR 256,X
BRK
.ORG 254
.BYTE 1 254 $EF 254
;expect MFE=0 MFF=255 M100=$77 M101=$FF N=1, Z=0, C=0
Implementation:
// Accumulator
state.acc = performRightBitShift(state.acc, true)
// Zero Page
val address = mem.read(state.ip+1)
mem.write(address, performRightBitShift(mem.read(address), true))
// Zero Page, X
val address = mem.read(state.ip+1)+state.x
mem.write(address, performRightBitShift(mem.read(address), true))
// Absolute
val address = findAbsoluteAddress(state.ip)
mem.write(address, performRightBitShift(mem.read(address), true))
// Absolute, X
val address = findAbsoluteAddress(state.ip)+state.x
mem.write(address, performRightBitShift(mem.read(address), true))
LSRLogical Shift Right by one bit
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Accumulator
74
$4A
1
2
Zero Page
70
$46
2
5
Zero Page,X
86
$56
2
6
Absolute
78
$4E
3
6
Absolute,X
94
$5E
3
7
Flags affected: CNZ
Usage: Shifts bits right using  0 to fill in the missing bit and putting the low order bit into the carry flag. Used primarily for single byte division by powers of 2 and reading serial data though can be used to reposition bits.
Test Code:
; LSR accumulator
LDA #1
SEC
LSR A
BRK
; expect A=0, Z=1, C=1, N=0
; LSR with memory
LDX #1
LSR 254
LSR 254,X
LSR 256
LSR 256,X
BRK
.ORG 254
.BYTE 128 127 $EE $76
;expect MFE=64 MFF=63 M100=$77 M101=$3B N=1, Z=0, C=0
Implementation:
// Accumulator
state.acc = performRightBitShift(state.acc, false)
// Zero Page
val address = mem.read(state.ip+1)
mem.write(address, performRightBitShift(mem.read(address), false))
// Zero Page, X
val address = mem.read(state.ip+1)+state.x
mem.write(address, performRightBitShift(mem.read(address), false))
// Absolute
val address = findAbsoluteAddress(state.ip)
mem.write(address, performRightBitShift(mem.read(address), false))
// Absolute, X
val address = findAbsoluteAddress(state.ip)+state.x
mem.write(address, performRightBitShift(mem.read(address), false))





Saturday, September 15, 2018

Rotating and shifting bits Left


The 6502 rotation operations assume a byte’s highest bit is leftmost and lowest bit is rightmost. The ASL command shifts the bits to the left. Shifting simply moves what value was in the highest bit into the carry flag, then moves the bits to the right by 1 and places a 0 in the lowest bit. In other words, 10101010 becomes 01010101 with a carry of 1.
Related to the ASL command is the ROL command which rotates the bits to the left. Rotating is the same as shifting with the exception that the value in the carry flag is shifted into the high bit. This effectively means that if you rotate a byte 9 times (8 bits plus 1 carry bit) you will end up with the number that you started with. The following diagram illustrates both of these instructions




This is all fascinating, but why would you ever want to do this? The two most common reasons is to reposition bits if you have bit-packed values and for multiplying.

As mentioned earlier, due to low amount of memory early machines had, every bit counts. Lets say you wanted a playing card to be represented in a byte. You may have a bit to representing if the card was revealed, 4 bits to hold the face value, and 2 bits to hold the suit. This would give us a packed byte in the format 0RFFFFSS. Here is how you would pack the 3 bytes into a single byte.

LDA cardShowing
ASL
ASL
ASL
ASL
ORA cardFace
ASL
ASL
ORA cardSuit

The 6502 does not have any multiplication or division instructions so those operations need to be done manually. This is where rotation really comes in handy. Shifting left is the equivalent to multiplying by 2. Shifting twice is the same as multiplying by 4, and so on for the powers of 2. Combining the shifting multiplications with additions lets you multiply by any value. For instance, multiplying by 10 can be done by multiplying by 8 then adding to that value the original value multiplied by 2.

Multi-byte shifting or multiplication is done as follows:
LDA lowByte
ASL
STA lowByte
LDA highByte
ROL
STA highByte

The idea here is that the ASL puts the high bit into the carry flag and the ROL uses the carry from the previous byte.

ROLROtate Left by one bit
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Accumulator
42
$2A
1
2
Zero Page
38
$26
2
5
Zero Page,X
54
$36
2
6
Absolute
46
$2E
3
6
Absolute,X
62
$3E
3
7
Flags affected: CNZ
Usage: Rotates bits left using the carry bit to fill in the missing bit and putting the sign bit into the carry flag. Used primarily for multi-byte multiplication by powers of 2 and reading serial data though can be used to reposition bits.
Test Code:
; ROL accumulator
            CLC
            LDA #128
            ROL A
            BRK
            ; expect A=0, Z=1, C=1, N=0


; ROL with memory    
            LDX #1
            ROL 254
            ROL 254,X
            ROL 256
            ROL 256,X
            BRK
.ORG 254
.BYTE 128 127 $77 $77
            ;expect MFE=0 MFF=255 M100=$EE M101=$EE N=1, Z=0, C=0

Implementation:
// Accumulator
state.acc = performLeftBitShift(state.acc, true)
// Zero Page
val address = mem.read(state.ip+1)
mem.write(address, performLeftBitShift(mem.read(address), true))
// Zero Page, X
val address = mem.read(state.ip+1)+state.x
mem.write(address, performLeftBitShift(mem.read(address), true))
// Absolute
val address = findAbsoluteAddress(state.ip)
mem.write(address, performLeftBitShift(mem.read(address), true))
// Absolute, X
val address = findAbsoluteAddress(state.ip)+state.x
mem.write(address, performLeftBitShift(mem.read(address), true))



ASLArithmetic Shift Left by one bit
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Accumulator
10
$0A
1
2
Zero Page
6
$06
2
5
Zero Page,X
22
$16
2
6
Absolute
14
$0E
3
6
Absolute,X
30
$1E
3
7
Flags affected: CNZ
Usage: Rotates bits left using the carry bit to fill in the missing bit and putting the sign bit into the carry flag. Used primarily for single byte multiplication by powers of 2 and reading serial data though can be used to reposition bits.
Test Code:
; ASL accumulator
            LDA #128
            SEC
            ASL A
            BRK
            ; expect A=0, Z=1, C=1, N=0


; ASL with memory    
            LDX #1
            ASL 254
            ASL 254,X
            ASL 256
            ASL 256,X
            BRK
.ORG 254
.BYTE 128 127 $F7 $77
            ;expect MFE=0 MFF=254 M100=$EE M101=$EE N=1, Z=0, C=0

Implementation:
// Accumulator
state.acc = performLeftBitShift(state.acc, false)
// Zero Page
val address = mem.read(state.ip+1)
mem.write(address, performLeftBitShift(mem.read(address), false))
// Zero Page, X
val address = mem.read(state.ip+1)+state.x
mem.write(address, performLeftBitShift(mem.read(address), false))
// Absolute
val address = findAbsoluteAddress(state.ip)
mem.write(address, performLeftBitShift(mem.read(address), false))
// Absolute, X
val address = findAbsoluteAddress(state.ip)+state.x
mem.write(address, performLeftBitShift(mem.read(address), false))