From 095efecd3266e02a25cdaf593f92096890981af7 Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 12 Dec 2019 18:30:32 +0000 Subject: [PATCH] o hardware/memory - moved cartridge code into new package - moved bus definitions into new package o recorder - changed file-format --- debugger/commands.go | 41 +++-- debugger/doc.go | 6 +- disassembly/disassembly.go | 8 +- disassembly/memory.go | 4 +- hardware/cpu/cpu.go | 6 +- hardware/memory/addresses/addresses.go | 12 +- hardware/memory/addresses/doc.go | 10 +- hardware/memory/{ => bus}/bus.go | 4 +- hardware/memory/cartridge/cartmapper.go | 28 ++++ hardware/memory/{ => cartridge}/cartridge.go | 158 ++---------------- .../memory/{ => cartridge}/cartridge_atari.go | 5 +- .../memory/{ => cartridge}/cartridge_cbs.go | 5 +- .../{ => cartridge}/cartridge_ejected.go | 5 +- .../{ => cartridge}/cartridge_mnetwork.go | 7 +- .../{ => cartridge}/cartridge_parkerbros.go | 5 +- .../{ => cartridge}/cartridge_tigervision.go | 19 ++- hardware/memory/cartridge/doc.go | 22 +++ hardware/memory/cartridge/fingerprint.go | 82 +++++++++ hardware/memory/chip.go | 19 ++- hardware/memory/doc.go | 30 +--- hardware/memory/memory.go | 44 +++-- hardware/memory/pia.go | 5 +- hardware/peripherals/panel.go | 8 +- hardware/peripherals/ports.go | 12 +- hardware/riot/riot.go | 6 +- hardware/riot/timer.go | 8 +- hardware/tia/audio/registers.go | 6 +- hardware/tia/step.go | 4 +- hardware/tia/tia.go | 8 +- hardware/tia/video/collisions.go | 6 +- hardware/tia/video/video.go | 16 +- recorder/fileformat.go | 3 - 32 files changed, 299 insertions(+), 303 deletions(-) rename hardware/memory/{ => bus}/bus.go (94%) create mode 100644 hardware/memory/cartridge/cartmapper.go rename hardware/memory/{ => cartridge}/cartridge.go (58%) rename hardware/memory/{ => cartridge}/cartridge_atari.go (99%) rename hardware/memory/{ => cartridge}/cartridge_cbs.go (96%) rename hardware/memory/{ => cartridge}/cartridge_ejected.go (92%) rename hardware/memory/{ => cartridge}/cartridge_mnetwork.go (97%) rename hardware/memory/{ => cartridge}/cartridge_parkerbros.go (98%) rename hardware/memory/{ => cartridge}/cartridge_tigervision.go (86%) create mode 100644 hardware/memory/cartridge/doc.go create mode 100644 hardware/memory/cartridge/fingerprint.go diff --git a/debugger/commands.go b/debugger/commands.go index ded34268..a3babcb9 100644 --- a/debugger/commands.go +++ b/debugger/commands.go @@ -83,7 +83,7 @@ var commandTemplate = []string{ cmdGrep + " %S", cmdHexLoad + " %N %N {%N}", cmdInsert + " %F", - cmdLast + " (DEFN)", + cmdLast + " (DEFN|BYTECODE)", cmdList + " [BREAKS|TRAPS|WATCHES|ALL]", cmdMemMap, cmdReflect + " (ON|OFF)", @@ -587,20 +587,35 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool) return doNothing, nil case cmdLast: - option, ok := tokens.Get() - if ok { - switch strings.ToUpper(option) { - case "DEFN": - dbg.print(terminal.StyleFeedback, "%s", dbg.vcs.CPU.LastResult.Defn) - } + if dbg.vcs.CPU.LastResult.Defn == nil { + // special condition for when LAST is called before any execution + // has taken place + dbg.print(terminal.StyleFeedback, "no instruction decoded yet") } else { - var printTag terminal.Style - if dbg.vcs.CPU.LastResult.Final { - printTag = terminal.StyleCPUStep - } else { - printTag = terminal.StyleVideoStep + done := false + resultStyle := result.StyleExecution + + option, ok := tokens.Get() + if ok { + switch strings.ToUpper(option) { + case "DEFN": + dbg.print(terminal.StyleFeedback, "%s", dbg.vcs.CPU.LastResult.Defn) + done = true + + case "BYTECODE": + resultStyle = result.StyleDisasm + } + } + + if !done { + var printTag terminal.Style + if dbg.vcs.CPU.LastResult.Final { + printTag = terminal.StyleCPUStep + } else { + printTag = terminal.StyleVideoStep + } + dbg.print(printTag, "%s", dbg.vcs.CPU.LastResult.GetString(dbg.disasm.Symtable, resultStyle)) } - dbg.print(printTag, "%s", dbg.vcs.CPU.LastResult.GetString(dbg.disasm.Symtable, result.StyleExecution)) } case cmdMemMap: diff --git a/debugger/doc.go b/debugger/doc.go index 290cca25..5b411c6e 100644 --- a/debugger/doc.go +++ b/debugger/doc.go @@ -37,7 +37,7 @@ // // dbg.Start(initScript, cartloader) // -// The initscript is a script previously created either by the script.Scribe or -// by hand. The cartloader argument must be an instance of cartloader. The -// emulation proper handles the loading of the cartridge data. +// The initscript is a script previously created either by the script.Scribe +// package or by hand. The cartloader argument must be an instance of +// cartloader. package debugger diff --git a/disassembly/disassembly.go b/disassembly/disassembly.go index f36f330c..90e85764 100644 --- a/disassembly/disassembly.go +++ b/disassembly/disassembly.go @@ -5,8 +5,8 @@ import ( "gopher2600/cartridgeloader" "gopher2600/errors" "gopher2600/hardware/cpu" - "gopher2600/hardware/memory" "gopher2600/hardware/memory/addresses" + "gopher2600/hardware/memory/cartridge" "gopher2600/symbols" "io" "strings" @@ -18,7 +18,7 @@ type bank [disasmMask + 1]Entry // Disassembly represents the annotated disassembly of a 6507 binary type Disassembly struct { - cart *memory.Cartridge + cart *cartridge.Cartridge // discovered/inferred cartridge attributes nonCartJmps bool @@ -76,7 +76,7 @@ func FromCartridge(cartload cartridgeloader.Loader) (*Disassembly, error) { // standard symbols table even in the event of an error symtable, _ := symbols.ReadSymbolsFile(cartload.Filename) - cart := memory.NewCartridge() + cart := cartridge.NewCartridge() err := cart.Attach(cartload) if err != nil { @@ -93,7 +93,7 @@ func FromCartridge(cartload cartridgeloader.Loader) (*Disassembly, error) { // FromMemory disassembles an existing instance of cartridge memory using a // cpu with no flow control. -func FromMemory(cart *memory.Cartridge, symtable *symbols.Table) (*Disassembly, error) { +func FromMemory(cart *cartridge.Cartridge, symtable *symbols.Table) (*Disassembly, error) { dsm := &Disassembly{} dsm.cart = cart diff --git a/disassembly/memory.go b/disassembly/memory.go index 92fa5733..a3347c35 100644 --- a/disassembly/memory.go +++ b/disassembly/memory.go @@ -1,14 +1,14 @@ package disassembly import ( - "gopher2600/hardware/memory" + "gopher2600/hardware/memory/cartridge" "gopher2600/hardware/memory/memorymap" ) // disasmMemory is a simplified memory model that allows the emulated CPU to // read cartridge memory. type disasmMemory struct { - cart *memory.Cartridge + cart *cartridge.Cartridge } func (dismem *disasmMemory) Read(address uint16) (uint8, error) { diff --git a/hardware/cpu/cpu.go b/hardware/cpu/cpu.go index 7414ce52..e3fbf728 100644 --- a/hardware/cpu/cpu.go +++ b/hardware/cpu/cpu.go @@ -6,8 +6,8 @@ import ( "gopher2600/hardware/cpu/definitions" "gopher2600/hardware/cpu/registers" "gopher2600/hardware/cpu/result" - "gopher2600/hardware/memory" "gopher2600/hardware/memory/addresses" + "gopher2600/hardware/memory/bus" "log" ) @@ -27,7 +27,7 @@ type CPU struct { acc8 *registers.Register acc16 *registers.ProgramCounter - mem memory.CPUBus + mem bus.CPUBus opCodes []*definitions.InstructionDefinition // isExecuting is used for sanity checks - to make sure we're not calling CPU @@ -59,7 +59,7 @@ type CPU struct { } // NewCPU is the preferred method of initialisation for the CPU structure -func NewCPU(mem memory.CPUBus) (*CPU, error) { +func NewCPU(mem bus.CPUBus) (*CPU, error) { mc := &CPU{mem: mem} mc.PC = registers.NewProgramCounter(0) diff --git a/hardware/memory/addresses/addresses.go b/hardware/memory/addresses/addresses.go index 8178f30e..078303b3 100644 --- a/hardware/memory/addresses/addresses.go +++ b/hardware/memory/addresses/addresses.go @@ -126,14 +126,14 @@ func init() { } } -// Named TIA registers +// TIA registers // // These value are used by the emulator to specifiy known addresses. For // example, when writing collision information we know we need the CXM0P // register. these named values make the code more readable // -// For simplicity values are enumerated from 0; value is added to the origin -// address of the TIA in ChipBus.ChipWrite implementation +// Values are enumerated from 0; value is added to the origin address of the +// TIA in ChipBus.ChipWrite implementation const ( CXM0P uint16 = iota CXM1P @@ -151,14 +151,14 @@ const ( INPT5 ) -// Named RIOT registers +// RIOT registers // // These value are used by the emulator to specifiy known addresses. For // example, the timer updates itself every cycle and stores time remaining // value in the INTIM register. // -// For simplicity values are enumerated from 0; value is added to the origin -// address of the TIA in ChipBus.ChipWrite implementation +// Values are enumerated from 0; value is added to the origin address of the +// TIA in ChipBus.ChipWrite implementation const ( SWCHA uint16 = iota SWACNT diff --git a/hardware/memory/addresses/doc.go b/hardware/memory/addresses/doc.go index bd3179cc..15507eab 100644 --- a/hardware/memory/addresses/doc.go +++ b/hardware/memory/addresses/doc.go @@ -10,9 +10,11 @@ // is noticeably slower than accessing a sparse array. There is probably no // need to use this arrays outside of the emulation code. // -// The Named TIA and RIOT registers probably don't need referring to outside -// the emulation code. +// "TIA Registers" and "RIOT Registers" are so named because to those areas, +// those addresses look like registers. They probably don't need referring to +// outside the emulation code. // -// DataMasks help implement VCS data/address bus artefacts and probably don't -// need to be referred to outside the emulation code. +// DataMasks help implement VCS data/address bus artefacts (fully explained +// beloew) and probably don't need to be referred to outside the emulation +// code. package addresses diff --git a/hardware/memory/bus.go b/hardware/memory/bus/bus.go similarity index 94% rename from hardware/memory/bus.go rename to hardware/memory/bus/bus.go index bbdba8a8..3e2b8507 100644 --- a/hardware/memory/bus.go +++ b/hardware/memory/bus/bus.go @@ -1,4 +1,6 @@ -package memory +// Package bus defines the memory bus concept. For an explanation see the +// memory package documentation. +package bus // CPUBus defines the operations for the memory system when accessed from the CPU // All memory areas implement this interface because they are all accessible diff --git a/hardware/memory/cartridge/cartmapper.go b/hardware/memory/cartridge/cartmapper.go new file mode 100644 index 00000000..2ee0b65a --- /dev/null +++ b/hardware/memory/cartridge/cartmapper.go @@ -0,0 +1,28 @@ +package cartridge + +// cartMapper implementations hold the actual data from the loaded ROM and +// keeps track of which banks are mapped to individual addresses. for +// convenience, functions with an address argument recieve that address +// normalised to a range of 0x0000 to 0x0fff +type cartMapper interface { + initialise() + read(addr uint16) (data uint8, err error) + write(addr uint16, data uint8) error + numBanks() int + getBank(addr uint16) (bank int) + setBank(addr uint16, bank int) error + saveState() interface{} + restoreState(interface{}) error + ram() []uint8 + + // tigervision cartridges have a very wierd bank-switching method that + // require a way of notifying the cartridge of writes to addresses outside + // of cartridge space + listen(addr uint16, data uint8) +} + +// optionalSuperchip are implemented by cartMappers that have an optional +// superchip +type optionalSuperchip interface { + addSuperchip() bool +} diff --git a/hardware/memory/cartridge.go b/hardware/memory/cartridge/cartridge.go similarity index 58% rename from hardware/memory/cartridge.go rename to hardware/memory/cartridge/cartridge.go index 31d339c2..7c188a19 100644 --- a/hardware/memory/cartridge.go +++ b/hardware/memory/cartridge/cartridge.go @@ -1,74 +1,33 @@ -package memory +package cartridge import ( "crypto/sha1" "fmt" "gopher2600/cartridgeloader" "gopher2600/errors" + "gopher2600/hardware/memory/bus" "gopher2600/hardware/memory/memorymap" "strings" ) -// cartMapper implementations hold the actual data from the loaded ROM and -// keeps track of which banks are mapped to individual addresses. for -// convenience, functions with an address argument recieve that address -// normalised to a range of 0x0000 to 0x0fff -type cartMapper interface { - initialise() - read(addr uint16) (data uint8, err error) - write(addr uint16, data uint8) error - numBanks() int - getBank(addr uint16) (bank int) - setBank(addr uint16, bank int) error - saveState() interface{} - restoreState(interface{}) error - ram() []uint8 - - // listen differs from write in that the address is the unmapped address on - // the address bus. for convenience, memory functions deal with addresses - // that have been mapped and normalised so they count from zero. - // cartMapper.listen() is the exception. - listen(addr uint16, data uint8) error -} - -// optionalSuperchip are implemented by cartMappers that have an optional -// superchip -type optionalSuperchip interface { - addSuperchip() bool -} - // Cartridge defines the information and operations for a VCS cartridge type Cartridge struct { - DebuggerBus - CPUBus + bus.DebuggerBus + bus.CPUBus - origin uint16 - memtop uint16 - - // full path to the cartridge as stored on disk Filename string - - // the format requested by the CartridgeLoader - RequestedFormat string - - // hash of binary loaded from disk. any subsequent pokes to cartridge - // memory will not be reflected in the value - Hash string + Hash string // the specific cartridge data, mapped appropriately to the memory // interfaces mapper cartMapper } -// NewCartridge is the preferred method of initialisation for the cartridges +// NewCartridge is the preferred method of initialisation for the cartridge +// type func NewCartridge() *Cartridge { - cart := &Cartridge{ - origin: memorymap.OriginCart, - memtop: memorymap.MemtopCart, - } - + cart := &Cartridge{} cart.Eject() - return cart } @@ -78,7 +37,7 @@ func (cart Cartridge) String() string { // Peek is an implementation of memory.DebuggerBus func (cart Cartridge) Peek(addr uint16) (uint8, error) { - addr &= cart.origin - 1 + addr &= memorymap.OriginCart - 1 return cart.mapper.read(addr) } @@ -90,13 +49,13 @@ func (cart Cartridge) Poke(addr uint16, data uint8) error { // Read is an implementation of memory.CPUBus // * optimisation: called a lot. pointer to Cartridge to prevent duffcopy func (cart *Cartridge) Read(addr uint16) (uint8, error) { - addr &= cart.origin - 1 + addr &= memorymap.OriginCart - 1 return cart.mapper.read(addr) } // Write is an implementation of memory.CPUBus func (cart *Cartridge) Write(addr uint16, data uint8) error { - addr &= cart.origin - 1 + addr &= memorymap.OriginCart - 1 return cart.mapper.write(addr, data) } @@ -108,86 +67,6 @@ func (cart *Cartridge) Eject() { cart.mapper = newEjected() } -// fingerprint8k attempts a divination of 8k cartridge data and decide on a -// suitable cartMapper implementation -func (cart Cartridge) fingerprint8k(data []byte) func([]byte) (cartMapper, error) { - if fingerprintTigervision(data) { - return newTigervision - } - - if fingerprintParkerBros(data) { - return newparkerBros - } - - return newAtari8k -} - -// fingerprint16k attempts a divination of 16k cartridge data and decide on a -// suitable cartMapper implementation -func (cart Cartridge) fingerprint16k(data []byte) func([]byte) (cartMapper, error) { - if fingerprintMnetwork(data) { - return newMnetwork - } - - return newAtari16k -} - -func (cart *Cartridge) fingerprint(data []byte) error { - var err error - - switch len(data) { - case 2048: - cart.mapper, err = newAtari2k(data) - if err != nil { - return err - } - - case 4096: - cart.mapper, err = newAtari4k(data) - if err != nil { - return err - } - - case 8192: - cart.mapper, err = cart.fingerprint8k(data)(data) - if err != nil { - return err - } - - case 12288: - cart.mapper, err = newCBS(data) - if err != nil { - return err - } - - case 16384: - cart.mapper, err = cart.fingerprint16k(data)(data) - if err != nil { - return err - } - - case 32768: - cart.mapper, err = newAtari32k(data) - if err != nil { - return err - } - - case 65536: - return errors.New(errors.CartridgeError, "65536 bytes not yet supported") - - default: - return errors.New(errors.CartridgeError, fmt.Sprintf("unrecognised cartridge size (%d bytes)", len(data))) - } - - // if cartridge mapper implements the optionalSuperChip interface then try - // to add the additional RAM - if superchip, ok := cart.mapper.(optionalSuperchip); ok { - superchip.addSuperchip() - } - - return nil -} - // IsEjected returns true if no cartridge is attached func (cart *Cartridge) IsEjected() bool { return cart.Hash == ejectedHash @@ -203,7 +82,6 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error { // note name of cartridge cart.Filename = cartload.Filename - cart.RequestedFormat = cartload.Format cart.mapper = newEjected() // generate hash @@ -293,7 +171,7 @@ func (cart Cartridge) NumBanks() int { // GetBank returns the current bank number for the specified address func (cart Cartridge) GetBank(addr uint16) int { - addr &= cart.origin - 1 + addr &= memorymap.OriginCart - 1 return cart.mapper.getBank(addr) } @@ -301,7 +179,7 @@ func (cart Cartridge) GetBank(addr uint16) int { // bank. For many cart mappers this just means switching banks for the entire // cartridge func (cart *Cartridge) SetBank(addr uint16, bank int) error { - addr &= cart.origin - 1 + addr &= memorymap.OriginCart - 1 return cart.mapper.setBank(addr, bank) } @@ -321,9 +199,9 @@ func (cart Cartridge) RAM() []uint8 { return cart.mapper.ram() } -// Listen for data at the specified address. return CartridgeListen error if -// nothing was done with the information. Callers to Listen() will probably -// want to filter out that error. -func (cart Cartridge) Listen(addr uint16, data uint8) error { - return cart.mapper.listen(addr, data) +// Listen for data at the specified address. very wierd requirement of the +// tigervision cartridge format. If there was a better way of implementing the +// tigervision format, there'd be no need for this function. +func (cart Cartridge) Listen(addr uint16, data uint8) { + cart.mapper.listen(addr, data) } diff --git a/hardware/memory/cartridge_atari.go b/hardware/memory/cartridge/cartridge_atari.go similarity index 99% rename from hardware/memory/cartridge_atari.go rename to hardware/memory/cartridge/cartridge_atari.go index 8eda9a9d..f622cb99 100644 --- a/hardware/memory/cartridge_atari.go +++ b/hardware/memory/cartridge/cartridge_atari.go @@ -1,4 +1,4 @@ -package memory +package cartridge import ( "fmt" @@ -146,8 +146,7 @@ func (cart atari) ram() []uint8 { return cart.superchip } -func (cart atari) listen(addr uint16, data uint8) error { - return nil +func (cart atari) listen(addr uint16, data uint8) { } // atari4k is the original and most straightforward format diff --git a/hardware/memory/cartridge_cbs.go b/hardware/memory/cartridge/cartridge_cbs.go similarity index 96% rename from hardware/memory/cartridge_cbs.go rename to hardware/memory/cartridge/cartridge_cbs.go index 44a1b134..7a3eba82 100644 --- a/hardware/memory/cartridge_cbs.go +++ b/hardware/memory/cartridge/cartridge_cbs.go @@ -1,4 +1,4 @@ -package memory +package cartridge import ( "fmt" @@ -126,6 +126,5 @@ func (cart cbs) ram() []uint8 { return cart.superchip } -func (cart cbs) listen(addr uint16, data uint8) error { - return nil +func (cart cbs) listen(addr uint16, data uint8) { } diff --git a/hardware/memory/cartridge_ejected.go b/hardware/memory/cartridge/cartridge_ejected.go similarity index 92% rename from hardware/memory/cartridge_ejected.go rename to hardware/memory/cartridge/cartridge_ejected.go index fb348472..7e05329d 100644 --- a/hardware/memory/cartridge_ejected.go +++ b/hardware/memory/cartridge/cartridge_ejected.go @@ -1,4 +1,4 @@ -package memory +package cartridge import ( "fmt" @@ -60,6 +60,5 @@ func (cart ejected) ram() []uint8 { return []uint8{} } -func (cart ejected) listen(addr uint16, data uint8) error { - return nil +func (cart ejected) listen(addr uint16, data uint8) { } diff --git a/hardware/memory/cartridge_mnetwork.go b/hardware/memory/cartridge/cartridge_mnetwork.go similarity index 97% rename from hardware/memory/cartridge_mnetwork.go rename to hardware/memory/cartridge/cartridge_mnetwork.go index 3eb824d0..cf341b79 100644 --- a/hardware/memory/cartridge_mnetwork.go +++ b/hardware/memory/cartridge/cartridge_mnetwork.go @@ -1,4 +1,4 @@ -package memory +package cartridge import ( "fmt" @@ -188,7 +188,7 @@ func (cart *mnetwork) bankSwitchOnAccess(addr uint16) bool { case 0x0fe7: cart.lowerSegment = 7 - // from bankswitch_size.txt: "You select which 256 byte block appears + // from bankswitch_sizes.txt: "You select which 256 byte block appears // here by accessing 1FF8 to 1FFB." // // "here" refers to the read range 0x0900 to 0x09ff and the write range @@ -272,6 +272,5 @@ func (cart *mnetwork) ram() []uint8 { return mem } -func (cart *mnetwork) listen(addr uint16, data uint8) error { - return nil +func (cart *mnetwork) listen(addr uint16, data uint8) { } diff --git a/hardware/memory/cartridge_parkerbros.go b/hardware/memory/cartridge/cartridge_parkerbros.go similarity index 98% rename from hardware/memory/cartridge_parkerbros.go rename to hardware/memory/cartridge/cartridge_parkerbros.go index b46ccf46..d6f4e0e4 100644 --- a/hardware/memory/cartridge_parkerbros.go +++ b/hardware/memory/cartridge/cartridge_parkerbros.go @@ -1,4 +1,4 @@ -package memory +package cartridge import ( "fmt" @@ -219,6 +219,5 @@ func (cart parkerBros) ram() []uint8 { return []uint8{} } -func (cart parkerBros) listen(addr uint16, data uint8) error { - return nil +func (cart parkerBros) listen(addr uint16, data uint8) { } diff --git a/hardware/memory/cartridge_tigervision.go b/hardware/memory/cartridge/cartridge_tigervision.go similarity index 86% rename from hardware/memory/cartridge_tigervision.go rename to hardware/memory/cartridge/cartridge_tigervision.go index 672e54bf..a657944d 100644 --- a/hardware/memory/cartridge_tigervision.go +++ b/hardware/memory/cartridge/cartridge_tigervision.go @@ -1,4 +1,4 @@ -package memory +package cartridge import ( "fmt" @@ -136,17 +136,22 @@ func (cart *tigervision) ram() []uint8 { return []uint8{} } -func (cart *tigervision) listen(addr uint16, data uint8) error { - // tigervision is seemingly unique in that in bank-switches when an address +func (cart *tigervision) listen(addr uint16, data uint8) { + // tigervision is seemingly unique in that it bank switches when an address // outside of cartridge space is written to. for this to work, we need the // listen() function. - // althought address 3F is used primarily for bank switching in actual - // fact writing anywhere in TIA space is okay + // although address 3F is used primarily, in actual fact writing anywhere + // in TIA space is okay. from the description from Kevin Horton's document + // (quoted above) whenever an address in TIA space is written to, the lower + // 3 bits of the value being written is used to set the segment. + if addr < 0x40 { // only the lowest three bits of the data value are used cart.segment[0] = int(data & 0x03) - return nil } - return nil + + // this bank switching method causes a problem when the CPU wants to write + // to TIA space for real and not cause a bankswitch. for this reason, + // tigervision cartridges use mirror addresses to write to the TIA. } diff --git a/hardware/memory/cartridge/doc.go b/hardware/memory/cartridge/doc.go new file mode 100644 index 00000000..abf2f8c3 --- /dev/null +++ b/hardware/memory/cartridge/doc.go @@ -0,0 +1,22 @@ +// Package cartridge fully implements loading of mapping of cartridge memory. +// +// There are many different types of cartridge most of which are supported by +// the package. Some cartridge types contain additional RAM but the main +// difference is how they map additional ROM to the relatively small address +// space available for cartridges in the VCS. This is called bank-switching. +// All of these differences are handled transparently by the package. +// +// Currently supported cartridge types are: +// +// - Atari 2k / 4k / 8k / 16k and 32k +// +// - the above with additional Superchip (additional RAM in other words) +// +// - Parker Bros. +// +// - MNetwork +// +// - Tigervision +// +// - CBS +package cartridge diff --git a/hardware/memory/cartridge/fingerprint.go b/hardware/memory/cartridge/fingerprint.go new file mode 100644 index 00000000..cfbdb2c6 --- /dev/null +++ b/hardware/memory/cartridge/fingerprint.go @@ -0,0 +1,82 @@ +package cartridge + +import ( + "fmt" + "gopher2600/errors" +) + +func (cart Cartridge) fingerprint8k(data []byte) func([]byte) (cartMapper, error) { + if fingerprintTigervision(data) { + return newTigervision + } + + if fingerprintParkerBros(data) { + return newparkerBros + } + + return newAtari8k +} + +func (cart Cartridge) fingerprint16k(data []byte) func([]byte) (cartMapper, error) { + if fingerprintMnetwork(data) { + return newMnetwork + } + + return newAtari16k +} + +func (cart *Cartridge) fingerprint(data []byte) error { + var err error + + switch len(data) { + case 2048: + cart.mapper, err = newAtari2k(data) + if err != nil { + return err + } + + case 4096: + cart.mapper, err = newAtari4k(data) + if err != nil { + return err + } + + case 8192: + cart.mapper, err = cart.fingerprint8k(data)(data) + if err != nil { + return err + } + + case 12288: + cart.mapper, err = newCBS(data) + if err != nil { + return err + } + + case 16384: + cart.mapper, err = cart.fingerprint16k(data)(data) + if err != nil { + return err + } + + case 32768: + cart.mapper, err = newAtari32k(data) + if err != nil { + return err + } + + case 65536: + return errors.New(errors.CartridgeError, "65536 bytes not yet supported") + + default: + return errors.New(errors.CartridgeError, fmt.Sprintf("unrecognised cartridge size (%d bytes)", len(data))) + } + + // if cartridge mapper implements the optionalSuperChip interface then try + // to add the additional RAM + if superchip, ok := cart.mapper.(optionalSuperchip); ok { + superchip.addSuperchip() + } + + return nil +} diff --git a/hardware/memory/chip.go b/hardware/memory/chip.go index 3d21de0d..b8c70f41 100644 --- a/hardware/memory/chip.go +++ b/hardware/memory/chip.go @@ -4,18 +4,23 @@ import ( "fmt" "gopher2600/errors" "gopher2600/hardware/memory/addresses" + "gopher2600/hardware/memory/bus" ) // ChipMemory defines the information for and operations allowed for those // memory areas accessed by the VCS chips as well as the CPU type ChipMemory struct { - DebuggerBus - ChipBus - CPUBus - PeriphBus + bus.DebuggerBus + bus.ChipBus + bus.CPUBus + bus.PeriphBus + // because we're servicing two different memory areas with this type, we + // need to store the origin and memtop values here, rather than using the + // constants from the memorymap package directly origin uint16 memtop uint16 + memory []uint8 // additional mask to further reduce address space when read from the CPU @@ -49,13 +54,13 @@ func (area ChipMemory) Poke(address uint16, value uint8) error { } // ChipRead is an implementation of memory.ChipBus -func (area *ChipMemory) ChipRead() (bool, ChipData) { +func (area *ChipMemory) ChipRead() (bool, bus.ChipData) { if area.writeSignal { area.writeSignal = false - return true, ChipData{Name: addresses.Write[area.lastWriteAddress], Value: area.writeData} + return true, bus.ChipData{Name: addresses.Write[area.lastWriteAddress], Value: area.writeData} } - return false, ChipData{} + return false, bus.ChipData{} } // ChipWrite is an implementation of memory.ChipBus diff --git a/hardware/memory/doc.go b/hardware/memory/doc.go index 8b38751a..f68db36e 100644 --- a/hardware/memory/doc.go +++ b/hardware/memory/doc.go @@ -57,32 +57,6 @@ // // The arrow pointing away from the Cartridge area indicates that the CPU can // only read from the cartridge, it cannot write to it. Unless that is, the -// cartridge has internal RAM. The differences in cartridge abilities in this -// regard is handled by the cartMapper interface. -// -// The cartMapper interface allows the transparent implementation of the -// different cartridge formats that have been used by the VCS. We've already -// mentioned cartridge RAM but the major difference betwen cartridge types is -// how they handle so-called bank-switching. -// -// The differences between the cartridge types is too much to go into here but -// a good reference for this can be found here: -// -// http://blog.kevtris.org/blogfiles/Atari%202600%20Mappers.txt -// -// Currently supported cartridge types are: -// -// - Atari 2k / 4k / 8k / 16k and 32k -// -// - the above with additional Superchip (additional RAM in other words) -// -// - Parker Bros. -// -// - MNetwork -// -// - Tigervision -// -// - CBS -// -// Other cartridge types can easily be added using the cartMapper system. +// cartridge has internal RAM. See the cartridge package documentation for the +// discussion on this. package memory diff --git a/hardware/memory/memory.go b/hardware/memory/memory.go index eccb67af..640e980d 100644 --- a/hardware/memory/memory.go +++ b/hardware/memory/memory.go @@ -3,22 +3,24 @@ package memory import ( "gopher2600/errors" "gopher2600/hardware/memory/addresses" + "gopher2600/hardware/memory/bus" + "gopher2600/hardware/memory/cartridge" "gopher2600/hardware/memory/memorymap" ) // VCSMemory is the monolithic representation of the memory in 2600. type VCSMemory struct { - CPUBus + bus.CPUBus // memmap is a hash for every address in the VCS address space, returning // one of the four memory areas - Memmap []DebuggerBus + Memmap []bus.DebuggerBus // the four memory areas RIOT *ChipMemory TIA *ChipMemory PIA *PIA - Cart *Cartridge + Cart *cartridge.Cartridge // the following are only used by the debugging interface. it would be // lovely to remove these for non-debugging emulation but there's not much @@ -46,12 +48,12 @@ type VCSMemory struct { func NewVCSMemory() (*VCSMemory, error) { mem := &VCSMemory{} - mem.Memmap = make([]DebuggerBus, memorymap.Memtop+1) + mem.Memmap = make([]bus.DebuggerBus, memorymap.Memtop+1) mem.RIOT = newRIOT() mem.TIA = newTIA() mem.PIA = newPIA() - mem.Cart = NewCartridge() + mem.Cart = cartridge.NewCartridge() if mem.RIOT == nil || mem.TIA == nil || mem.PIA == nil || mem.Cart == nil { return nil, errors.New(errors.MemoryError, "cannot create memory areas") @@ -59,19 +61,19 @@ func NewVCSMemory() (*VCSMemory, error) { // create the memory map by associating all addresses in each memory area // with that area - for i := mem.TIA.origin; i <= mem.TIA.memtop; i++ { + for i := memorymap.OriginTIA; i <= memorymap.MemtopTIA; i++ { mem.Memmap[i] = mem.TIA } - for i := mem.PIA.origin; i <= mem.PIA.memtop; i++ { + for i := memorymap.OriginPIA; i <= memorymap.MemtopPIA; i++ { mem.Memmap[i] = mem.PIA } - for i := mem.RIOT.origin; i <= mem.RIOT.memtop; i++ { + for i := memorymap.OriginRIOT; i <= memorymap.MemtopRIOT; i++ { mem.Memmap[i] = mem.RIOT } - for i := mem.Cart.origin; i <= mem.Cart.memtop; i++ { + for i := memorymap.OriginCart; i <= memorymap.MemtopCart; i++ { mem.Memmap[i] = mem.Cart } @@ -79,7 +81,7 @@ func NewVCSMemory() (*VCSMemory, error) { } // GetArea returns the actual memory of the specified area type -func (mem *VCSMemory) GetArea(area memorymap.Area) (DebuggerBus, error) { +func (mem *VCSMemory) GetArea(area memorymap.Area) (bus.DebuggerBus, error) { switch area { case memorymap.TIA: return mem.TIA, nil @@ -104,7 +106,7 @@ func (mem *VCSMemory) Read(address uint16) (uint8, error) { return 0, err } - data, err := area.(CPUBus).Read(ma) + data, err := area.(bus.CPUBus).Read(ma) // some memory areas do not change all the bits on the data bus, leaving // some bits of the address in the result @@ -147,19 +149,11 @@ func (mem *VCSMemory) Write(address uint16, data uint8) error { mem.LastAccessID = mem.accessCount mem.accessCount++ - // as incredible as it may seem some cartridges react to memory writes to - // addresses not in the cartridge space. for example, tigervision - // cartridges switch banks whenever any (non-mapped) address in the range - // 0x00 to 0x3f is written to. - err = mem.Cart.Listen(address, data) + // as incredible as it may seem tigervision cartridges react to memory + // writes to (unmapped) addresses in the range 0x00 to 0x3f. the Listen() + // function is a horrible solution to this but I can't see how else to + // handle it. + mem.Cart.Listen(address, data) - // the only error we expect from the cartMapper is and UnwritableAddress - // error, which most cartridge types will respond with in all circumstances - if err != nil { - if _, ok := err.(errors.AtariError); !ok { - return err - } - } - - return area.(CPUBus).Write(ma, data) + return area.(bus.CPUBus).Write(ma, data) } diff --git a/hardware/memory/pia.go b/hardware/memory/pia.go index 7ffc81f1..5c0af662 100644 --- a/hardware/memory/pia.go +++ b/hardware/memory/pia.go @@ -2,14 +2,15 @@ package memory import ( "fmt" + "gopher2600/hardware/memory/bus" "gopher2600/hardware/memory/memorymap" "strings" ) // PIA defines the information for and operation allowed for PIA PIA type PIA struct { - DebuggerBus - CPUBus + bus.DebuggerBus + bus.CPUBus origin uint16 memtop uint16 diff --git a/hardware/peripherals/panel.go b/hardware/peripherals/panel.go index 0d259331..4a2c4c0a 100644 --- a/hardware/peripherals/panel.go +++ b/hardware/peripherals/panel.go @@ -1,11 +1,9 @@ -// Panel uses the concurrent chip bus interface - package peripherals import ( "gopher2600/errors" - "gopher2600/hardware/memory" "gopher2600/hardware/memory/addresses" + "gopher2600/hardware/memory/bus" "strings" ) @@ -15,7 +13,7 @@ type Panel struct { id PeriphID - riot memory.PeriphBus + riot bus.PeriphBus p0pro bool p1pro bool color bool @@ -24,7 +22,7 @@ type Panel struct { } // NewPanel is the preferred method of initialisation for the Panel type -func NewPanel(riot memory.PeriphBus) *Panel { +func NewPanel(riot bus.PeriphBus) *Panel { pan := &Panel{ id: PanelID, riot: riot, diff --git a/hardware/peripherals/ports.go b/hardware/peripherals/ports.go index 0d56c2c4..b8ee390d 100644 --- a/hardware/peripherals/ports.go +++ b/hardware/peripherals/ports.go @@ -2,8 +2,8 @@ package peripherals import ( "gopher2600/errors" - "gopher2600/hardware/memory" "gopher2600/hardware/memory/addresses" + "gopher2600/hardware/memory/bus" ) // Ports is the containing structure for the two player ports @@ -13,7 +13,7 @@ type Ports struct { } // NewPorts is the preferred method of initialisation for the Ports type -func NewPorts(riot memory.PeriphBus, tia memory.PeriphBus, panel *Panel) *Ports { +func NewPorts(riot bus.PeriphBus, tia bus.PeriphBus, panel *Panel) *Ports { return &Ports{ Player0: newPlayer0(riot, tia, panel), Player1: newPlayer1(riot, tia, panel), @@ -26,8 +26,8 @@ type player struct { id PeriphID - riot memory.PeriphBus - tia memory.PeriphBus + riot bus.PeriphBus + tia bus.PeriphBus panel *Panel // joystick @@ -48,7 +48,7 @@ type player struct { buttonMask uint8 } -func newPlayer0(riot memory.PeriphBus, tia memory.PeriphBus, panel *Panel) *player { +func newPlayer0(riot bus.PeriphBus, tia bus.PeriphBus, panel *Panel) *player { pl := &player{ id: PlayerZeroID, riot: riot, @@ -71,7 +71,7 @@ func newPlayer0(riot memory.PeriphBus, tia memory.PeriphBus, panel *Panel) *play return pl } -func newPlayer1(riot memory.PeriphBus, tia memory.PeriphBus, panel *Panel) *player { +func newPlayer1(riot bus.PeriphBus, tia bus.PeriphBus, panel *Panel) *player { pl := &player{ id: PlayerOneID, riot: riot, diff --git a/hardware/riot/riot.go b/hardware/riot/riot.go index 792247f8..e765db4b 100644 --- a/hardware/riot/riot.go +++ b/hardware/riot/riot.go @@ -1,19 +1,19 @@ package riot import ( - "gopher2600/hardware/memory" + "gopher2600/hardware/memory/bus" "strings" ) // RIOT contains all the sub-components of the VCS RIOT sub-system type RIOT struct { - mem memory.ChipBus + mem bus.ChipBus Timer *timer } // NewRIOT creates a RIOT, to be used in a VCS emulation -func NewRIOT(mem memory.ChipBus) *RIOT { +func NewRIOT(mem bus.ChipBus) *RIOT { riot := &RIOT{mem: mem} riot.Timer = newTimer(mem) diff --git a/hardware/riot/timer.go b/hardware/riot/timer.go index 61f0d6ec..959ac735 100644 --- a/hardware/riot/timer.go +++ b/hardware/riot/timer.go @@ -2,12 +2,12 @@ package riot import ( "fmt" - "gopher2600/hardware/memory" "gopher2600/hardware/memory/addresses" + "gopher2600/hardware/memory/bus" ) type timer struct { - mem memory.ChipBus + mem bus.ChipBus // register is the name of the currently selected RIOT timer register string @@ -45,7 +45,7 @@ type timer struct { cyclesElapsed int } -func newTimer(mem memory.ChipBus) *timer { +func newTimer(mem bus.ChipBus) *timer { tmr := &timer{ mem: mem, register: "TIM1024", @@ -71,7 +71,7 @@ func (tmr timer) String() string { ) } -func (tmr *timer) serviceMemory(data memory.ChipData) bool { +func (tmr *timer) serviceMemory(data bus.ChipData) bool { switch data.Name { case "TIM1T": tmr.register = data.Name diff --git a/hardware/tia/audio/registers.go b/hardware/tia/audio/registers.go index 9e16d26b..adb864c4 100644 --- a/hardware/tia/audio/registers.go +++ b/hardware/tia/audio/registers.go @@ -1,14 +1,12 @@ package audio -import ( - "gopher2600/hardware/memory" -) +import "gopher2600/hardware/memory/bus" // UpdateRegisters checks the TIA memory for changes to registers that are // interesting to the audio sub-system // // Returns true if memory.ChipData has not been serviced. -func (au *Audio) UpdateRegisters(data memory.ChipData) bool { +func (au *Audio) UpdateRegisters(data bus.ChipData) bool { switch data.Name { case "AUDC0": au.channel0.regControl = data.Value & 0x0f diff --git a/hardware/tia/step.go b/hardware/tia/step.go index a71bdb89..8b8b0cc1 100644 --- a/hardware/tia/step.go +++ b/hardware/tia/step.go @@ -2,7 +2,7 @@ package tia import ( "gopher2600/errors" - "gopher2600/hardware/memory" + "gopher2600/hardware/memory/bus" "gopher2600/television" ) @@ -34,7 +34,7 @@ func (tia *TIA) Step(serviceMemory bool) (bool, error) { // update debugging information tia.videoCycles++ - var memoryData memory.ChipData + var memoryData bus.ChipData // update memory if required if serviceMemory { diff --git a/hardware/tia/tia.go b/hardware/tia/tia.go index 5119a7e5..59c2fcb6 100644 --- a/hardware/tia/tia.go +++ b/hardware/tia/tia.go @@ -2,7 +2,7 @@ package tia import ( "fmt" - "gopher2600/hardware/memory" + "gopher2600/hardware/memory/bus" "gopher2600/hardware/tia/audio" "gopher2600/hardware/tia/future" "gopher2600/hardware/tia/phaseclock" @@ -15,7 +15,7 @@ import ( // TIA contains all the sub-components of the VCS TIA sub-system type TIA struct { tv television.Television - mem memory.ChipBus + mem bus.ChipBus // number of video cycles since the last WSYNC. also cycles back to 0 on // RSYNC and when polycounter reaches count 56 @@ -92,7 +92,7 @@ func (tia TIA) String() string { } // NewTIA creates a TIA, to be used in a VCS emulation -func NewTIA(tv television.Television, mem memory.ChipBus) *TIA { +func NewTIA(tv television.Television, mem bus.ChipBus) *TIA { tia := TIA{tv: tv, mem: mem, hblank: true} var err error @@ -124,7 +124,7 @@ func NewTIA(tv television.Television, mem memory.ChipBus) *TIA { // UpdateTIA checks for side effects in the TIA sub-system. // // Returns true if ChipData has *not* been serviced. -func (tia *TIA) UpdateTIA(data memory.ChipData) bool { +func (tia *TIA) UpdateTIA(data bus.ChipData) bool { switch data.Name { case "VSYNC": tia.sig.VSync = data.Value&0x02 == 0x02 diff --git a/hardware/tia/video/collisions.go b/hardware/tia/video/collisions.go index 5aa53030..fac09df2 100644 --- a/hardware/tia/video/collisions.go +++ b/hardware/tia/video/collisions.go @@ -2,12 +2,12 @@ package video import ( "fmt" - "gopher2600/hardware/memory" "gopher2600/hardware/memory/addresses" + "gopher2600/hardware/memory/bus" ) type collisions struct { - mem memory.ChipBus + mem bus.ChipBus cxm0p uint8 cxm1p uint8 @@ -19,7 +19,7 @@ type collisions struct { cxppmm uint8 } -func newCollisions(mem memory.ChipBus) *collisions { +func newCollisions(mem bus.ChipBus) *collisions { col := &collisions{mem: mem} col.clear() return col diff --git a/hardware/tia/video/video.go b/hardware/tia/video/video.go index ebcf8bf2..0544d9ac 100644 --- a/hardware/tia/video/video.go +++ b/hardware/tia/video/video.go @@ -1,8 +1,8 @@ package video import ( - "gopher2600/hardware/memory" "gopher2600/hardware/memory/addresses" + "gopher2600/hardware/memory/bus" "gopher2600/hardware/tia/future" "gopher2600/hardware/tia/phaseclock" "gopher2600/hardware/tia/polycounter" @@ -34,7 +34,7 @@ type Video struct { // pisel locatoin of the sprites in relation to the hsync counter (or // screen) func NewVideo(pclk *phaseclock.PhaseClock, hsync *polycounter.Polycounter, - mem memory.ChipBus, tv television.Television, + mem bus.ChipBus, tv television.Television, hblank, hmoveLatch *bool) *Video { vd := &Video{ @@ -301,7 +301,7 @@ func (vd *Video) Pixel() (uint8, television.DebugColorSignal) { // CTRLPF is serviced in UpdateSpriteVariations() // // Returns true if ChipData has *not* been serviced. -func (vd *Video) UpdatePlayfield(tiaDelay future.Scheduler, data memory.ChipData) bool { +func (vd *Video) UpdatePlayfield(tiaDelay future.Scheduler, data bus.ChipData) bool { // homebrew Donkey Kong shows the need for a delay of at least two cycles // to write new playfield data switch data.Name { @@ -322,7 +322,7 @@ func (vd *Video) UpdatePlayfield(tiaDelay future.Scheduler, data memory.ChipData // require a short pause, using the TIA scheduler // // Returns true if ChipData has *not* been serviced. -func (vd *Video) UpdateSpriteHMOVE(tiaDelay future.Scheduler, data memory.ChipData) bool { +func (vd *Video) UpdateSpriteHMOVE(tiaDelay future.Scheduler, data bus.ChipData) bool { switch data.Name { // horizontal movement values range from -8 to +7 for convenience we // convert this to the range 0 to 15. from TIA_HW_Notes.txt: @@ -375,7 +375,7 @@ func (vd *Video) UpdateSpriteHMOVE(tiaDelay future.Scheduler, data memory.ChipDa // registers // // Returns true if memory.ChipData has not been serviced. -func (vd *Video) UpdateSpritePositioning(data memory.ChipData) bool { +func (vd *Video) UpdateSpritePositioning(data bus.ChipData) bool { switch data.Name { // the reset registers *must* be serviced after HSYNC has been ticked. // resets are resolved after a short delay, governed by the sprite itself @@ -399,7 +399,7 @@ func (vd *Video) UpdateSpritePositioning(data memory.ChipData) bool { // UpdateColor checks the TIA memory for changes to color registers // // Returns true if memory.ChipData has not been serviced. -func (vd *Video) UpdateColor(data memory.ChipData) bool { +func (vd *Video) UpdateColor(data bus.ChipData) bool { switch data.Name { case "COLUP0": vd.Player0.setColor(data.Value & 0xfe) @@ -423,7 +423,7 @@ func (vd *Video) UpdateColor(data memory.ChipData) bool { // occur after a call to Pixel() // // Returns true if memory.ChipData has not been serviced. -func (vd *Video) UpdateSpritePixels(data memory.ChipData) bool { +func (vd *Video) UpdateSpritePixels(data bus.ChipData) bool { // the barnstormer ROM demonstrate perfectly how GRP0 is affected if we // alter its state before a call to Pixel(). if we write do alter state // before Pixel(), then an unwanted artefact can be seen on scanline 61. @@ -450,7 +450,7 @@ func (vd *Video) UpdateSpritePixels(data memory.ChipData) bool { // because it affects the ball sprite. // // Returns true if memory.ChipData has not been serviced. -func (vd *Video) UpdateSpriteVariations(data memory.ChipData) bool { +func (vd *Video) UpdateSpriteVariations(data bus.ChipData) bool { switch data.Name { case "CTRLPF": vd.Ball.setSize((data.Value & 0x30) >> 4) diff --git a/recorder/fileformat.go b/recorder/fileformat.go index 0f84a92e..c67af7e5 100644 --- a/recorder/fileformat.go +++ b/recorder/fileformat.go @@ -31,7 +31,6 @@ const fieldSep = ", " const ( lineMagicString int = iota lineCartName - lineCartFormat lineCartHash lineTVtype numHeaderLines @@ -45,7 +44,6 @@ func (rec *Recorder) writeHeader() error { // add header information lines[lineMagicString] = magicString lines[lineCartName] = rec.vcs.Mem.Cart.Filename - lines[lineCartFormat] = rec.vcs.Mem.Cart.RequestedFormat lines[lineCartHash] = rec.vcs.Mem.Cart.Hash lines[lineTVtype] = fmt.Sprintf("%v\n", rec.vcs.TV.GetSpec().ID) @@ -73,7 +71,6 @@ func (plb *Playback) readHeader(lines []string) error { // read header plb.CartLoad.Filename = lines[lineCartName] - plb.CartLoad.Format = lines[lineCartFormat] plb.CartLoad.Hash = lines[lineCartHash] plb.TVtype = lines[lineTVtype]