Added MMU and WIP rudimentary apple //e boot test

Basic memory management has been implemented since $c100-$cfff needs flipping
with soft switches during Apple //e boot. All memory reads & writes now go
through the MMU. Memory is also dynamically allocated and associated with the
CPU state.
This commit is contained in:
Will Angenent 2018-05-08 19:32:55 +01:00
parent d45e1a4aac
commit 5d1c25a724
7 changed files with 245 additions and 67 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ test-cpu
cpu.prof
mem.prof
mos6502go.test
apple2e.rom

View File

@ -0,0 +1,28 @@
package main
import (
"flag"
"mos6502go/cpu"
"mos6502go/mmu"
)
func main() {
showInstructions := flag.Bool("show-instructions", false, "Show instructions code while running")
disableBell := flag.Bool("disable-bell", false, "Disable bell")
flag.Parse()
cpu.InitDisasm()
memory := mmu.InitRAM()
mmu.InitApple2eROM(memory)
var s cpu.State
s.Memory = memory
s.MemoryMap = &memory.MemoryMap
s.Init()
bootVector := 0xfffc
lsb := (*s.MemoryMap)[uint8(bootVector>>8)][uint8(bootVector&0xff)] // TODO move readMemory to mmu
msb := (*s.MemoryMap)[uint8((bootVector+1)>>8)][uint8((bootVector+1)&0xff)]
s.PC = uint16(lsb) + uint16(msb)<<8
cpu.Run(&s, *showInstructions, nil, *disableBell, 0)
}

View File

@ -5,18 +5,20 @@ import (
"flag"
"fmt"
"mos6502go/cpu"
"mos6502go/mmu"
"mos6502go/utils"
)
func main() {
cpu.InitDisasm()
showInstructions := flag.Bool("show-instructions", false, "Show instructions code while running")
skipTest0 := flag.Bool("skip-functional-test", false, "Skip functional test")
skipTest1 := flag.Bool("skip-interrupt-test", false, "Skip interrupt test")
breakAddressString := flag.String("break", "", "Break on address")
flag.Parse()
cpu.InitDisasm()
memory := mmu.InitRAM()
var Roms = []string{
"6502_functional_test.bin.gz",
"6502_interrupt_test.bin.gz",
@ -32,8 +34,10 @@ func main() {
}
fmt.Printf("Running %s\n", rom)
var s cpu.State
s.Init()
s.PC = 0x800
cpu.RunningTests = true
if i == 0 {
@ -44,15 +48,28 @@ func main() {
cpu.RunningInterruptTests = true
}
bytes, err := utils.ReadMemoryFromFile(rom)
bytes, err := utils.ReadMemoryFromGzipFile(rom)
if err != nil {
panic(err)
}
for i := 0; i < len(bytes); i++ {
s.Memory[i] = bytes[i]
// Copy main RAM area 0x0000-0xbfff
for i := 0; i < 0xc000; i++ {
memory.PhysicalMemory.MainMemory[i] = bytes[i]
}
// Map writable RAM area in 0xc000-0xffff
var RomPretendingToBeRAM [0x4000]uint8
for i := 0x0; i < 0x4000; i++ {
RomPretendingToBeRAM[i] = bytes[0xc000+i]
}
for i := 0x0; i < 0x40; i++ {
memory.MemoryMap[0xc0+uint8(i)] = RomPretendingToBeRAM[i*0x100 : i*0x100+0x100]
}
s.Memory = memory
s.MemoryMap = &memory.MemoryMap
var breakAddress *uint16
if *breakAddressString != "" {
breakAddressValue, err := hex.DecodeString(*breakAddressString)
@ -71,7 +88,7 @@ func main() {
breakAddress = &foo
}
cpu.Run(&s, *showInstructions, breakAddress)
cpu.Run(&s, *showInstructions, breakAddress, false, 0)
fmt.Printf("Finished running %s\n\n", rom)
}
}

View File

@ -2,6 +2,7 @@ package cpu
import (
"fmt"
"mos6502go/mmu"
"os"
)
@ -23,7 +24,8 @@ var (
)
type State struct {
Memory [0x10000]uint8
Memory *mmu.Memory
MemoryMap *mmu.MemoryMap // For easy access, this is a shortcut for Memory.MemoryMap
pendingInterrupt bool
pendingNMI bool
A uint8
@ -43,7 +45,6 @@ func (s *State) Init() {
s.X = 0
s.Y = 0
s.P = CpuFlagR | CpuFlagB | CpuFlagZ
s.PC = 0x800
s.SP = 0xff
s.pendingInterrupt = false
s.pendingNMI = false
@ -102,14 +103,14 @@ func (s *State) isN() bool {
}
func push8(s *State, value uint8) {
s.Memory[0x100+uint16(s.SP)] = value
(*s.MemoryMap)[mmu.StackPage][s.SP] = value
s.SP -= 1
s.SP &= 0xff
}
func push16(s *State, value uint16) {
s.Memory[0x100+uint16(s.SP)] = uint8(value >> 8)
s.Memory[0xff+uint16(s.SP)] = uint8(value & 0xff)
(*s.MemoryMap)[mmu.StackPage][s.SP] = uint8(value >> 8)
(*s.MemoryMap)[mmu.StackPage][s.SP-1] = uint8(value & 0xff)
s.SP -= 2
s.SP &= 0xff
}
@ -117,24 +118,29 @@ func push16(s *State, value uint16) {
func pop8(s *State) uint8 {
s.SP += 1
s.SP &= 0xff
return s.Memory[0x100+uint16(s.SP)]
return (*s.MemoryMap)[mmu.StackPage][s.SP]
}
func pop16(s *State) uint16 {
s.SP += 2
s.SP &= 0xff
lsb := uint16(s.Memory[0xff+uint16(s.SP)])
msb := uint16(s.Memory[0x100+uint16(s.SP)])
msb := uint16((*s.MemoryMap)[mmu.StackPage][s.SP])
lsb := uint16((*s.MemoryMap)[mmu.StackPage][s.SP-1])
return lsb + msb<<8
}
func readMemory(s *State, address uint16) uint8 {
return s.Memory[address]
if (address >= 0xc000) && (address < 0xc100) {
fmt.Printf("TODO read %04x\n", address)
return 0
}
return (*s.MemoryMap)[uint8(address>>8)][uint8(address&0xff)]
}
// Handle a write to a magic test address that triggers an interrupt and/or an NMI
func writeInterruptTestOpenCollector(s *State, address uint16, value uint8) {
oldValue := s.Memory[address]
oldValue := readMemory(s, address)
oldInterrupt := (oldValue & 0x1) == 0x1
oldNMI := (oldValue & 0x2) == 0x2
@ -150,7 +156,7 @@ func writeInterruptTestOpenCollector(s *State, address uint16, value uint8) {
s.pendingNMI = NMI
}
s.Memory[address] = value
(*s.MemoryMap)[uint8(address>>8)][uint8(address&0xff)] = value
}
func writeMemory(s *State, address uint16, value uint8) {
@ -159,20 +165,35 @@ func writeMemory(s *State, address uint16, value uint8) {
return
}
s.Memory[address] = value
if address >= 0xc000 {
if address == mmu.CLRCXROM {
mmu.MapFirstHalfOfIO(s.Memory)
} else if address == mmu.SETCXROM {
mmu.MapSecondHalfOfIO(s.Memory)
} else {
fmt.Printf("TODO write %04x\n", address)
}
return
}
if address >= 0x400 && address < 0x800 {
fmt.Printf("Text page write %04x: %02x\n", address, value)
}
(*s.MemoryMap)[uint8(address>>8)][uint8(address&0xff)] = value
if RunningFunctionalTests && address == 0x200 {
testNumber := s.Memory[0x200]
testNumber := readMemory(s, 0x200)
if testNumber == 0xf0 {
fmt.Println("Opcode testing completed")
} else {
fmt.Printf("Test %d OK\n", s.Memory[0x200])
fmt.Printf("Test %d OK\n", readMemory(s, 0x200))
}
}
}
func branch(s *State, cycles *int, instructionName string, doBranch bool) {
value := s.Memory[s.PC+1]
value := readMemory(s, s.PC+1)
var relativeAddress uint16
if (value & 0x80) == 0 {
@ -203,28 +224,28 @@ func branch(s *State, cycles *int, instructionName string, doBranch bool) {
func getAddressFromAddressMode(s *State, addressMode byte) (result uint16, pageBoundaryCrossed bool) {
switch addressMode {
case AmZeroPage:
result = uint16(s.Memory[s.PC+1])
result = uint16(readMemory(s, s.PC+1))
case AmZeroPageX:
result = (uint16(s.Memory[s.PC+1]) + uint16(s.X)) & 0xff
result = (uint16(readMemory(s, s.PC+1)) + uint16(s.X)) & 0xff
case AmZeroPageY:
result = (uint16(s.Memory[s.PC+1]) + uint16(s.Y)) & 0xff
result = (uint16(readMemory(s, s.PC+1)) + uint16(s.Y)) & 0xff
case AmAbsolute:
result = uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8
result = uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8
case AmAbsoluteX:
value := uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8
value := uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8
pageBoundaryCrossed = (value & 0xff00) != ((value + uint16(s.X)) & 0xff00)
result = value + uint16(s.X)
case AmAbsoluteY:
value := uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8
value := uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8
pageBoundaryCrossed = (value & 0xff00) != ((value + uint16(s.Y)) & 0xff00)
result = value + uint16(s.Y)
case AmIndirectX:
zeroPageAddress := (s.Memory[s.PC+1] + s.X) & 0xff
result = uint16(s.Memory[zeroPageAddress]) + uint16(s.Memory[zeroPageAddress+1])<<8
zeroPageAddress := (readMemory(s, s.PC+1) + s.X) & 0xff
result = uint16(readMemory(s, uint16(zeroPageAddress))) + uint16(readMemory(s, uint16(zeroPageAddress)+1))<<8
case AmIndirectY:
address := s.Memory[s.PC+1]
lsb := uint16(s.Memory[address])
msb := uint16(s.Memory[address+1])
address := uint16(readMemory(s, s.PC+1))
lsb := uint16(readMemory(s, address))
msb := uint16(readMemory(s, address+1))
value := lsb + msb<<8
pageBoundaryCrossed = (value & 0xff00) != ((value + uint16(s.Y)) & 0xff00)
result = value + uint16(s.Y)
@ -238,47 +259,47 @@ func getAddressFromAddressMode(s *State, addressMode byte) (result uint16, pageB
func readMemoryWithAddressMode(s *State, addressMode byte) (result uint8, pageBoundaryCrossed bool) {
switch addressMode {
case AmImmediate:
result = s.Memory[s.PC+1]
result = readMemory(s, s.PC+1)
s.PC += 2
case AmZeroPage:
var address uint16
address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode)
result = s.Memory[address]
result = readMemory(s, address)
s.PC += 2
case AmZeroPageX:
var address uint16
address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode)
result = s.Memory[address]
result = readMemory(s, address)
s.PC += 2
case AmZeroPageY:
var address uint16
address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode)
result = s.Memory[address]
result = readMemory(s, address)
s.PC += 2
case AmAbsolute:
var address uint16
address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode)
result = s.Memory[address]
result = readMemory(s, address)
s.PC += 3
case AmAbsoluteX:
var address uint16
address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode)
result = s.Memory[address]
result = readMemory(s, address)
s.PC += 3
case AmAbsoluteY:
var address uint16
address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode)
result = s.Memory[address]
result = readMemory(s, address)
s.PC += 3
case AmIndirectX:
var address uint16
address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode)
result = s.Memory[address]
result = readMemory(s, address)
s.PC += 2
case AmIndirectY:
var address uint16
address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode)
result = s.Memory[address]
result = readMemory(s, address)
s.PC += 2
default:
result = 0
@ -482,7 +503,7 @@ func sbc(s *State, cycles *int, addressMode byte) {
}
func bit(s *State, address uint16) {
value := s.Memory[address]
value := readMemory(s, address)
s.setN(value)
s.setV((value & 0x40) != 0)
s.setZ(value & s.A)
@ -494,14 +515,14 @@ func preProcessShift(s *State, cycles *int, addressMode byte) (address uint16, v
value = s.A
} else {
address, _ = getAddressFromAddressMode(s, addressMode)
value = s.Memory[address]
value = readMemory(s, address)
}
if addressMode == AmAccumulator {
value = s.A
} else {
address, _ = getAddressFromAddressMode(s, addressMode)
value = s.Memory[address]
value = readMemory(s, address)
}
return
@ -559,7 +580,7 @@ func brk(s *State, cycles *int) {
s.P |= CpuFlagB
push8(s, s.P)
s.P |= CpuFlagI
s.PC = uint16(s.Memory[0xffff])<<8 + uint16(s.Memory[0xfffe])
s.PC = uint16(readMemory(s, 0xffff))<<8 + uint16(readMemory(s, 0xfffe))
*cycles += 7
}
@ -568,7 +589,7 @@ func irq(s *State, cycles *int) {
s.P &= ^CpuFlagB
push8(s, s.P)
s.P |= CpuFlagI
s.PC = uint16(s.Memory[0xffff])<<8 + uint16(s.Memory[0xfffe])
s.PC = uint16(readMemory(s, 0xffff))<<8 + uint16(readMemory(s, 0xfffe))
*cycles += 7
}
@ -577,14 +598,18 @@ func nmi(s *State, cycles *int) {
s.P &= ^CpuFlagB
push8(s, s.P)
s.P |= CpuFlagI
s.PC = uint16(s.Memory[0xfffb])<<8 + uint16(s.Memory[0xfffa])
s.PC = uint16(readMemory(s, 0xfffb))<<8 + uint16(readMemory(s, 0xfffa))
*cycles += 7
}
func Run(s *State, showInstructions bool, breakAddress *uint16) {
func Run(s *State, showInstructions bool, breakAddress *uint16, disableBell bool, wantedCycles int) {
cycles := 0
for {
if (wantedCycles != 0) && (cycles >= wantedCycles) {
return
}
if RunningTests && (s.PC == 0x3869) {
fmt.Println("Functional tests passed")
return
@ -611,7 +636,7 @@ func Run(s *State, showInstructions bool, breakAddress *uint16) {
PrintInstruction(s)
}
opcode := s.Memory[s.PC]
opcode := readMemory(s, s.PC)
addressMode := OpCodes[opcode].AddressingMode.Mode
if breakAddress != nil && s.PC == *breakAddress {
@ -622,7 +647,7 @@ func Run(s *State, showInstructions bool, breakAddress *uint16) {
switch opcode {
case 0x4c: // JMP $0000
value := uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8
value := uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8
if RunningTests && s.PC == value {
fmt.Printf("Trap at $%04x\n", value)
os.Exit(0)
@ -630,15 +655,21 @@ func Run(s *State, showInstructions bool, breakAddress *uint16) {
s.PC = value
cycles += 3
case 0x6c: // JMP ($0000)
value := uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8
s.PC = uint16(s.Memory[value]) + uint16(s.Memory[value+1])<<8
value := uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8
s.PC = uint16(readMemory(s, value)) + uint16(readMemory(s, value+1))<<8
cycles += 5
case 0x20: // JSR $0000
value := uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8
value := uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8
cycles += 6
if disableBell && value == 0xfca8 {
s.PC += 3
continue
}
push16(s, s.PC+2)
s.PC = value
cycles += 6
case 0x60: // RTS
value := pop16(s)
@ -819,12 +850,12 @@ func Run(s *State, showInstructions bool, breakAddress *uint16) {
cycles += 6
case 0x24: // BIT $00
address := s.Memory[s.PC+1]
address := readMemory(s, s.PC+1)
bit(s, uint16(address))
s.PC += 2
cycles += 3
case 0x2C: // BIT $0000
address := uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8
address := uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8
bit(s, address)
s.PC += 3
cycles += 4
@ -869,7 +900,7 @@ func Run(s *State, showInstructions bool, breakAddress *uint16) {
case 0xe6, 0xf6, 0xee, 0xfe: // INC
address, _ := getAddressFromAddressMode(s, addressMode)
value := s.Memory[address]
value := readMemory(s, address)
value = (value + 1) & 0xff
s.setZ(value)
s.setN(value)
@ -878,7 +909,7 @@ func Run(s *State, showInstructions bool, breakAddress *uint16) {
case 0xc6, 0xd6, 0xce, 0xde: // DEC
address, _ := getAddressFromAddressMode(s, addressMode)
value := s.Memory[address]
value := readMemory(s, address)
value = (value - 1) & 0xff
s.setZ(value)
s.setN(value)

View File

@ -37,7 +37,7 @@ func printInstruction(s *State, instruction string) {
}
func PrintInstruction(s *State) {
opcodeValue := s.Memory[s.PC]
opcodeValue := (*s.MemoryMap)[uint8((s.PC)>>8)][uint8((s.PC)&0xff)]
opcode := OpCodes[opcodeValue]
mnemonic := opcode.Mnemonic
size := opcode.AddressingMode.OperandSize
@ -53,7 +53,7 @@ func PrintInstruction(s *State) {
var suffix string
if opcode.AddressingMode.Mode == AmRelative {
value = uint16(s.Memory[s.PC+1])
value = uint16((*s.MemoryMap)[uint8((s.PC+1)>>8)][uint8((s.PC+1)&0xff)])
var relativeAddress uint16
if (value & 0x80) == 0 {
relativeAddress = s.PC + 2 + uint16(value)
@ -64,15 +64,15 @@ func PrintInstruction(s *State) {
suffix = fmt.Sprintf(stringFormat, relativeAddress)
opcodes = fmt.Sprintf("%02x %02x ", opcodeValue, value)
} else if size == 1 {
value = uint16(s.Memory[s.PC+1])
value = uint16((*s.MemoryMap)[uint8((s.PC+1)>>8)][uint8((s.PC+1)&0xff)])
suffix = fmt.Sprintf(stringFormat, value)
opcodes = fmt.Sprintf("%02x %02x ", opcodeValue, value)
} else if size == 2 {
lower := s.Memory[s.PC+1]
higher := s.Memory[s.PC+2]
value = uint16(lower) + uint16(higher)*0x100
lsb := (*s.MemoryMap)[uint8((s.PC+1)>>8)][uint8((s.PC+1)&0xff)]
msb := (*s.MemoryMap)[uint8((s.PC+2)>>8)][uint8((s.PC+2)&0xff)]
value = uint16(lsb) + uint16(msb)*0x100
suffix = fmt.Sprintf(stringFormat, value)
opcodes = fmt.Sprintf("%02x %02x %02x ", opcodeValue, lower, higher)
opcodes = fmt.Sprintf("%02x %02x %02x ", opcodeValue, lsb, msb)
}
printInstruction(s, fmt.Sprintf("%s %s %s", opcodes, mnemonic, suffix))
@ -90,7 +90,7 @@ func DumpMemory(s *State, offset uint16) {
}
fmt.Printf("%04x ", offset+i)
}
fmt.Printf(" %02x", s.Memory[offset+i])
fmt.Printf(" %02x", (*s.MemoryMap)[uint8((offset+i)>>8)][uint8((offset+i)&0xff)])
}
fmt.Print("\n")
}

101
mmu/mmu.go Normal file
View File

@ -0,0 +1,101 @@
package mmu
import (
"fmt"
"io/ioutil"
)
const RomPath = "apple2e.rom"
const StackPage = 1
// https://mirrors.apple2.org.za/apple.cabi.net/Languages.Programming/MemoryMap.IIe.64K.128K.txt
const (
KEYBOARD = 0xC000 // keyboard data (latched) (RD-only)
CLR80COL = 0xC000 // use 80-column memory mapping (WR-only)
SET80COL = 0xC001
CLRAUXRD = 0xC002 // read from auxilliary 48K
SETAUXRD = 0xC003
CLRAUXWR = 0xC004 // write to auxilliary 48K
SETAUXWR = 0xC005
CLRCXROM = 0xC006 // use external slot ROM
SETCXROM = 0xC007
CLRAUXZP = 0xC008 // use auxilliary ZP, stack, & LC
SETAUXZP = 0xC009
CLRC3ROM = 0xC00A // use external slot C3 ROM
SETC3ROM = 0xC00B
CLR80VID = 0xC00C // use 80-column display mode
SET80VID = 0xC00D
CLRALTCH = 0xC00E // use alternate character set ROM
SETALTCH = 0xC00F
STROBE = 0xC010 // strobe (unlatch) keyboard data
)
type PhysicalMemory struct {
MainMemory [0xc000]uint8
UpperROM [0x3000]uint8
RomC1 [0x1000]uint8
RomC2 [0x1000]uint8
}
type MemoryMap map[uint8][]uint8
type Memory struct {
MemoryMap MemoryMap
PhysicalMemory PhysicalMemory
}
func MapFirstHalfOfIO(m *Memory) {
for i := 0x1; i < 0x10; i++ {
m.MemoryMap[uint8(i)+0xc0] = m.PhysicalMemory.RomC1[i*0x100 : i*0x100+0x100]
}
}
func MapSecondHalfOfIO(m *Memory) {
for i := 0x1; i < 0x10; i++ {
m.MemoryMap[uint8(i)+0xc0] = m.PhysicalMemory.RomC2[i*0x100 : i*0x100+0x100]
}
}
func readApple2eROM(m *Memory) {
bytes, err := ioutil.ReadFile(RomPath)
if err != nil {
panic(fmt.Sprintf("Unable to read ROM: %s", err))
}
// Copy both I/O areas over c000-cfff, including unused c000-c0ff
for i := 0x0000; i < 0x1000; i++ {
m.PhysicalMemory.RomC1[i] = bytes[i]
m.PhysicalMemory.RomC2[i] = bytes[i+0x4000]
}
// Copy ROM over for 0xd000-0xffff area
for i := 0x0; i < 0x3000; i++ {
m.PhysicalMemory.UpperROM[i] = bytes[i+0x1000]
}
}
func InitApple2eROM(m *Memory) {
readApple2eROM(m)
// Map 0xc100-0xcfff
MapFirstHalfOfIO(m)
// Map 0xd000-0xffff
for i := 0x0; i < 0x30; i++ {
m.MemoryMap[uint8(i)+0xd0] = m.PhysicalMemory.UpperROM[i*0x100 : i*0x100+0x100]
}
}
func InitRAM() (memory *Memory) {
memory = new(Memory)
memory.MemoryMap = make(MemoryMap)
// Map main RAM
for i := 0x0; i < 0xc0; i++ {
memory.MemoryMap[uint8(i)] = memory.PhysicalMemory.MainMemory[i*0x100 : i*0x100+0x100]
}
return
}

View File

@ -6,7 +6,7 @@ import (
"os"
)
func ReadMemoryFromFile(filename string) (data []byte, err error) {
func ReadMemoryFromGzipFile(filename string) (data []byte, err error) {
f, err := os.Open(filename)
if err != nil {
return nil, err