mirror of
https://github.com/ariejan/i6502.git
synced 2024-06-10 01:29:33 +00:00
Implement ADC, including Carry, Overflow, Decimal
This commit is contained in:
parent
c3ffb2e87b
commit
9c8f47abbc
|
@ -15,7 +15,7 @@ const (
|
||||||
relative
|
relative
|
||||||
zeropage
|
zeropage
|
||||||
zeropageX
|
zeropageX
|
||||||
zoerpageY
|
zeropageY
|
||||||
)
|
)
|
||||||
|
|
||||||
var addressingNames = [...]string{
|
var addressingNames = [...]string{
|
||||||
|
|
46
cpu.go
46
cpu.go
|
@ -7,6 +7,10 @@ type Cpu struct {
|
||||||
|
|
||||||
PC uint16 // 16-bit program counter
|
PC uint16 // 16-bit program counter
|
||||||
P byte // Status Register
|
P byte // Status Register
|
||||||
|
|
||||||
|
A byte // Accumulator
|
||||||
|
X byte // X index register
|
||||||
|
Y byte // Y index register
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -42,9 +46,49 @@ func (c *Cpu) LoadProgram(data []byte, location uint16) {
|
||||||
func (c *Cpu) Step() {
|
func (c *Cpu) Step() {
|
||||||
instruction := c.readNextInstruction()
|
instruction := c.readNextInstruction()
|
||||||
c.PC += uint16(instruction.Size)
|
c.PC += uint16(instruction.Size)
|
||||||
fmt.Println(instruction)
|
// fmt.Println(instruction)
|
||||||
c.execute(instruction)
|
c.execute(instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cpu) execute(instruction Instruction) {
|
func (c *Cpu) execute(instruction Instruction) {
|
||||||
|
switch instruction.opcodeId {
|
||||||
|
case adc:
|
||||||
|
c.ADC(instruction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cpu) resolveOperand(in Instruction) uint8 {
|
||||||
|
switch in.addressingId {
|
||||||
|
case immediate:
|
||||||
|
return in.Op8
|
||||||
|
default:
|
||||||
|
return c.bus.Read(c.memoryAddress(in))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cpu) memoryAddress(in Instruction) uint16 {
|
||||||
|
switch in.addressingId {
|
||||||
|
case absolute:
|
||||||
|
return in.Op16
|
||||||
|
case absoluteX:
|
||||||
|
return in.Op16 + uint16(c.X)
|
||||||
|
case absoluteY:
|
||||||
|
return in.Op16 + uint16(c.Y)
|
||||||
|
case indirect:
|
||||||
|
return c.bus.Read16(in.Op16)
|
||||||
|
case indirectX:
|
||||||
|
return c.bus.Read16(uint16(in.Op8 + c.X))
|
||||||
|
case indirectY:
|
||||||
|
return c.bus.Read16(uint16(in.Op8)) + uint16(c.Y)
|
||||||
|
case relative:
|
||||||
|
panic("Relative addressing not yet implemented.")
|
||||||
|
case zeropage:
|
||||||
|
return uint16(in.Op8)
|
||||||
|
case zeropageX:
|
||||||
|
return uint16(in.Op8 + c.X)
|
||||||
|
case zeropageY:
|
||||||
|
return uint16(in.Op8 + c.Y)
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("Unhandled addressing mode. Are you sure you are running a 6502 ROM?"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
57
cpu_instructions.go
Normal file
57
cpu_instructions.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package i6502
|
||||||
|
|
||||||
|
// Add Memory to Accumulator with Carry
|
||||||
|
func (c *Cpu) ADC(in Instruction) {
|
||||||
|
operand := c.resolveOperand(in)
|
||||||
|
carryIn := c.getStatusInt(sCarry)
|
||||||
|
|
||||||
|
if c.getStatus(sDecimal) {
|
||||||
|
c.adcDecimal(c.A, operand, carryIn)
|
||||||
|
} else {
|
||||||
|
c.adcNormal(c.A, operand, carryIn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performs regular, 8-bit addition
|
||||||
|
func (c *Cpu) adcNormal(a uint8, b uint8, carryIn uint8) {
|
||||||
|
result16 := uint16(a) + uint16(b) + uint16(carryIn)
|
||||||
|
result := uint8(result16)
|
||||||
|
carryOut := (result16 & 0x100) != 0
|
||||||
|
overflow := (a^result)&(b^result)&0x80 != 0
|
||||||
|
|
||||||
|
// Set the carry flag if we exceed 8-bits
|
||||||
|
c.setCarry(carryOut)
|
||||||
|
// Set the overflow bit
|
||||||
|
c.setOverflow(overflow)
|
||||||
|
// Store the resulting value (8-bits)
|
||||||
|
c.A = result
|
||||||
|
// Update sZero and sNegative
|
||||||
|
c.setArithmeticFlags(c.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performs addition in decimal mode
|
||||||
|
func (c *Cpu) adcDecimal(a uint8, b uint8, carryIn uint8) {
|
||||||
|
var carryB uint8 = 0
|
||||||
|
|
||||||
|
low := (a & 0x0F) + (b & 0x0F) + carryIn
|
||||||
|
if (low & 0xFF) > 9 {
|
||||||
|
low += 6
|
||||||
|
}
|
||||||
|
if low > 15 {
|
||||||
|
carryB = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
high := (a >> 4) + (b >> 4) + carryB
|
||||||
|
if (high & 0xFF) > 9 {
|
||||||
|
high += 6
|
||||||
|
}
|
||||||
|
|
||||||
|
result := (low & 0x0F) | (high<<4)&0xF0
|
||||||
|
|
||||||
|
c.setCarry(high > 15)
|
||||||
|
c.setZero(result == 0)
|
||||||
|
c.setNegative(false) // BCD is never negative
|
||||||
|
c.setOverflow(false) // BCD never sets overflow
|
||||||
|
|
||||||
|
c.A = result
|
||||||
|
}
|
55
cpu_status.go
Normal file
55
cpu_status.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package i6502
|
||||||
|
|
||||||
|
// Status Register bits
|
||||||
|
const (
|
||||||
|
sCarry = iota
|
||||||
|
sZero
|
||||||
|
sInterrupt
|
||||||
|
sDecimal
|
||||||
|
sBreak
|
||||||
|
_
|
||||||
|
sOverflow
|
||||||
|
sNegative
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Cpu) getStatusInt(bit uint8) uint8 {
|
||||||
|
return (c.P >> bit) & 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cpu) getStatus(bit uint8) bool {
|
||||||
|
return c.getStatusInt(bit) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cpu) setStatus(bit uint8, state bool) {
|
||||||
|
if state {
|
||||||
|
c.P |= 1 << bit
|
||||||
|
} else {
|
||||||
|
c.P &^= 1 << bit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cpu) setCarry(state bool) {
|
||||||
|
c.setStatus(sCarry, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cpu) setOverflow(state bool) {
|
||||||
|
c.setStatus(sOverflow, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cpu) setZero(state bool) {
|
||||||
|
c.setStatus(sZero, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cpu) setNegative(state bool) {
|
||||||
|
c.setStatus(sNegative, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cpu) setArithmeticFlags(value uint8) {
|
||||||
|
// Set sZero if value is 0
|
||||||
|
c.setStatus(sZero, value == 0)
|
||||||
|
|
||||||
|
// Set sNegative if the 8th bit is 1, we're dealing with
|
||||||
|
// uint8's internally, and using two's complement to identify
|
||||||
|
// negatives
|
||||||
|
c.setStatus(sNegative, (value>>7) == 1)
|
||||||
|
}
|
188
cpu_test.go
188
cpu_test.go
|
@ -64,9 +64,197 @@ func TestProgramLoading(t *testing.T) {
|
||||||
assert.Equal(0x0300, cpu.PC)
|
assert.Equal(0x0300, cpu.PC)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//// NOP
|
||||||
|
|
||||||
func TestNOP(t *testing.T) {
|
func TestNOP(t *testing.T) {
|
||||||
cpu, _, _ := NewRamMachine()
|
cpu, _, _ := NewRamMachine()
|
||||||
cpu.LoadProgram([]byte{0xEA}, 0x0300)
|
cpu.LoadProgram([]byte{0xEA}, 0x0300)
|
||||||
cpu.Step()
|
cpu.Step()
|
||||||
assert.Equal(t, 0x0301, cpu.PC)
|
assert.Equal(t, 0x0301, cpu.PC)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//// ADC
|
||||||
|
|
||||||
|
func TestADCImmediate(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x69, 0x53}, 0x0300)
|
||||||
|
cpu.A = 0x42
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0302, cpu.PC)
|
||||||
|
assert.Equal(t, 0x95, cpu.A)
|
||||||
|
assert.False(t, cpu.getStatus(sCarry))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestADCWithCarry(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x69, 0x53}, 0x0300)
|
||||||
|
cpu.A = 0xC0
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0302, cpu.PC)
|
||||||
|
assert.Equal(t, 0x13, cpu.A)
|
||||||
|
assert.True(t, cpu.getStatus(sCarry))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestADCWithCarryOver(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x69, 0x04}, 0x0300)
|
||||||
|
|
||||||
|
cpu.setStatus(sCarry, true)
|
||||||
|
cpu.A = 0x05
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0302, cpu.PC)
|
||||||
|
assert.Equal(t, 0x0A, cpu.A)
|
||||||
|
assert.False(t, cpu.getStatus(sCarry))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestADCWithOverflow(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x69, 0xD0}, 0x0300)
|
||||||
|
|
||||||
|
cpu.A = 0x90
|
||||||
|
|
||||||
|
// 0x90 + 0xD0 = 0x160
|
||||||
|
// 208 + 144 = 352 => unsigned carry
|
||||||
|
// -48 + -112 = 96 => signed overflow
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0302, cpu.PC)
|
||||||
|
assert.Equal(t, 0x60, cpu.A)
|
||||||
|
assert.True(t, cpu.getStatus(sCarry))
|
||||||
|
assert.True(t, cpu.getStatus(sOverflow))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestADCZero(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x69, 0x00}, 0x0300)
|
||||||
|
cpu.A = 0x00
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0302, cpu.PC)
|
||||||
|
assert.Equal(t, 0x00, cpu.A)
|
||||||
|
assert.True(t, cpu.getStatus(sZero))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestADCNegative(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x69, 0xF7}, 0x0300)
|
||||||
|
cpu.A = 0x00
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0302, cpu.PC)
|
||||||
|
assert.Equal(t, 0xF7, cpu.A)
|
||||||
|
assert.True(t, cpu.getStatus(sNegative))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestADCDecimal(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x69, 0x28}, 0x0300)
|
||||||
|
cpu.setStatus(sDecimal, true)
|
||||||
|
cpu.A = 0x19
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0302, cpu.PC)
|
||||||
|
assert.Equal(t, 0x47, cpu.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestADCZeropage(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x65, 0x53}, 0x0300)
|
||||||
|
cpu.A = 0x42
|
||||||
|
cpu.bus.Write(0x53, 0x12)
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0302, cpu.PC)
|
||||||
|
assert.Equal(t, 0x54, cpu.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestADCZeropageX(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x75, 0x53}, 0x0300)
|
||||||
|
cpu.A = 0x42
|
||||||
|
cpu.X = 0x01
|
||||||
|
cpu.bus.Write(0x54, 0x12)
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0302, cpu.PC)
|
||||||
|
assert.Equal(t, 0x54, cpu.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestADCAbsolute(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x6D, 0x00, 0x80}, 0x0300)
|
||||||
|
cpu.A = 0x42
|
||||||
|
cpu.bus.Write(0x8000, 0x12)
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0303, cpu.PC)
|
||||||
|
assert.Equal(t, 0x54, cpu.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestADCAbsoluteX(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x7D, 0x00, 0x80}, 0x0300)
|
||||||
|
cpu.A = 0x42
|
||||||
|
cpu.X = 0x02
|
||||||
|
cpu.bus.Write(0x8002, 0x12)
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0303, cpu.PC)
|
||||||
|
assert.Equal(t, 0x54, cpu.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestADCAbsoluteY(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x79, 0x00, 0x80}, 0x0300)
|
||||||
|
cpu.A = 0x42
|
||||||
|
cpu.Y = 0x02
|
||||||
|
cpu.bus.Write(0x8002, 0x12)
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0303, cpu.PC)
|
||||||
|
assert.Equal(t, 0x54, cpu.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestADCIndirectX(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x61, 0x80}, 0x0300)
|
||||||
|
cpu.A = 0x42
|
||||||
|
cpu.X = 0x02
|
||||||
|
cpu.bus.Write16(0x82, 0xC000)
|
||||||
|
cpu.bus.Write(0xC000, 0x12)
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0302, cpu.PC)
|
||||||
|
assert.Equal(t, 0x54, cpu.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestADCIndirectY(t *testing.T) {
|
||||||
|
cpu, _, _ := NewRamMachine()
|
||||||
|
cpu.LoadProgram([]byte{0x71, 0x80}, 0x0300)
|
||||||
|
cpu.A = 0x42
|
||||||
|
cpu.Y = 0x02
|
||||||
|
cpu.bus.Write16(0x80, 0xC000)
|
||||||
|
cpu.bus.Write(0xC002, 0x12)
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.Equal(t, 0x0302, cpu.PC)
|
||||||
|
assert.Equal(t, 0x54, cpu.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
|
@ -21,11 +21,11 @@ type Instruction struct {
|
||||||
func (i Instruction) String() (output string) {
|
func (i Instruction) String() (output string) {
|
||||||
switch i.Size {
|
switch i.Size {
|
||||||
case 1:
|
case 1:
|
||||||
output = fmt.Sprintf("~~ 0x%04X: 0x%02X - %s [%s] {%d}\n", i.Address, i.Opcode, instructionNames[i.opcodeId], addressingNames[i.addressingId], i.Cycles)
|
output = fmt.Sprintf("~~~ 0x%04X: 0x%02X - %s [%s] {%d}\n", i.Address, i.Opcode, instructionNames[i.opcodeId], addressingNames[i.addressingId], i.Cycles)
|
||||||
case 2:
|
case 2:
|
||||||
output = fmt.Sprintf("~~ 0x%04X: 0x%02X - %s 0x%02X [%s] {%d}\n", i.Address, i.Opcode, instructionNames[i.opcodeId], i.Op8, addressingNames[i.addressingId], i.Cycles)
|
output = fmt.Sprintf("~~~ 0x%04X: 0x%02X - %s 0x%02X [%s] {%d}\n", i.Address, i.Opcode, instructionNames[i.opcodeId], i.Op8, addressingNames[i.addressingId], i.Cycles)
|
||||||
case 3:
|
case 3:
|
||||||
output = fmt.Sprintf("~~ 0x%04X: 0x%02X - %s 0x%04X [%s] {%d}\n", i.Address, i.Opcode, instructionNames[i.opcodeId], i.Op16, addressingNames[i.addressingId], i.Cycles)
|
output = fmt.Sprintf("~~~ 0x%04X: 0x%02X - %s 0x%04X [%s] {%d}\n", i.Address, i.Opcode, instructionNames[i.opcodeId], i.Op16, addressingNames[i.addressingId], i.Cycles)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -37,7 +37,7 @@ func (c *Cpu) readNextInstruction() Instruction {
|
||||||
|
|
||||||
optype, ok := opTypes[opcode]
|
optype, ok := opTypes[opcode]
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(fmt.Sprintf("Unknown or unimplemented opcde 0x%02X", opcode))
|
panic(fmt.Sprintf("Unknown or unimplemented opcode 0x%02X", opcode))
|
||||||
}
|
}
|
||||||
|
|
||||||
instruction := Instruction{OpType: optype}
|
instruction := Instruction{OpType: optype}
|
||||||
|
|
|
@ -143,4 +143,13 @@ type OpType struct {
|
||||||
|
|
||||||
var opTypes = map[uint8]OpType{
|
var opTypes = map[uint8]OpType{
|
||||||
0xEA: OpType{0xEA, nop, implied, 1, 2},
|
0xEA: OpType{0xEA, nop, implied, 1, 2},
|
||||||
|
|
||||||
|
0x69: OpType{0x69, adc, immediate, 2, 2},
|
||||||
|
0x65: OpType{0x65, adc, zeropage, 2, 2},
|
||||||
|
0x75: OpType{0x75, adc, zeropageX, 2, 4},
|
||||||
|
0x6D: OpType{0x6D, adc, absolute, 3, 4},
|
||||||
|
0x7D: OpType{0x7D, adc, absoluteX, 3, 4},
|
||||||
|
0x79: OpType{0x79, adc, absoluteY, 3, 4},
|
||||||
|
0x61: OpType{0x61, adc, indirectX, 2, 6},
|
||||||
|
0x71: OpType{0x71, adc, indirectY, 2, 5},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user