diff --git a/debugger/debugger.go b/debugger/debugger.go index 16705889..d04ea1d9 100644 --- a/debugger/debugger.go +++ b/debugger/debugger.go @@ -38,6 +38,9 @@ type Debugger struct { breakpoints *breakpoints traps *traps + // any error from previous emulation step + lastStepError bool + // commandOnHalt says whether an sequence of commands should run automatically // when emulation halts. commandOnHaltPrev is the stored command sequence // used when ONHALT is called with no arguments @@ -73,25 +76,24 @@ type Debugger struct { input []byte } -// NewDebugger is the preferred method of initialisation for the Debugger structure +// NewDebugger creates and initialises everything required for a new debugging +// session. Use the Start() method to actually begin the session. func NewDebugger() (*Debugger, error) { var err error dbg := new(Debugger) dbg.ui = new(ui.PlainTerminal) - if dbg.ui == nil { - return nil, fmt.Errorf("error allocationg memory for UI") - } // prepare hardware tv, err := sdltv.NewSDLTV("NTSC", sdltv.IdealScale) if err != nil { - return nil, err + return nil, fmt.Errorf("error preparing television: %s", err) } - dbg.vcs, err = hardware.New(tv) + + dbg.vcs, err = hardware.NewVCS(tv) if err != nil { - return nil, err + return nil, fmt.Errorf("error preparing VCS: %s", err) } // set up breakpoints/traps @@ -169,7 +171,7 @@ func (dbg *Debugger) Start(interf ui.UserInterface, filename string, initScript if initScript != "" { err = dbg.RunScript(initScript, true) if err != nil { - dbg.print(ui.Error, "* error running debugger initialisation script (%s)\n", err) + dbg.print(ui.Error, "* error running debugger initialisation script: %s\n", err) } } @@ -256,9 +258,12 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error { if dbg.inputloopNext { bpCheck := dbg.breakpoints.check() trCheck := dbg.traps.check() - dbg.inputloopHalt = bpCheck || trCheck + dbg.inputloopHalt = bpCheck || trCheck || dbg.lastStepError } + // reset last step error + dbg.lastStepError = false + // *CRITICAL SECTION* dbg.runLock.Lock() @@ -351,8 +356,20 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error { } else { _, dbg.lastResult, err = dbg.vcs.Step(dbg.noVideoCycleCallback) } + if err != nil { - return err + switch err := err.(type) { + case errors.GopherError: + // do not exit input loop when error is a gopher error + // set lastStepError instead and allow emulation to + // halt + dbg.lastStepError = true + + // print gopher error message + dbg.print(ui.Error, "%s", err) + default: + return err + } } if dbg.commandOnStep != "" { @@ -467,7 +484,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) { if err != nil { switch err := err.(type) { case errors.GopherError: - if err.Errno == errors.UnknownSymbol { + if err.Errno == errors.SymbolUnknown { dbg.print(ui.Feedback, "%s -> not found", parts[1]) return false, nil } diff --git a/debugger/script.go b/debugger/script.go index 6c32b324..a2e1dd6f 100644 --- a/debugger/script.go +++ b/debugger/script.go @@ -3,6 +3,7 @@ package debugger import ( "fmt" "gopher2600/debugger/ui" + "gopher2600/errors" "os" "strings" ) @@ -12,7 +13,7 @@ func (dbg *Debugger) loadScript(scriptfile string) ([]string, error) { // open script and defer closing sf, err := os.Open(scriptfile) if err != nil { - return nil, fmt.Errorf("error opening script (%s)", err) + return nil, errors.NewGopherError(errors.ScriptFileCannotOpen, err) } defer func() { _ = sf.Close() @@ -33,7 +34,7 @@ func (dbg *Debugger) loadScript(scriptfile string) ([]string, error) { return nil, err } if n != len(buffer) { - return nil, fmt.Errorf("error reading scriptfile file (%s)", scriptfile) + return nil, errors.NewGopherError(errors.ScriptFileError, errors.FileTruncated) } // convert buffer to an array of lines diff --git a/debugger/targets.go b/debugger/targets.go index a202d9c5..461f6477 100644 --- a/debugger/targets.go +++ b/debugger/targets.go @@ -1,7 +1,7 @@ package debugger import ( - "fmt" + "gopher2600/errors" "gopher2600/hardware" "gopher2600/television" ) @@ -36,7 +36,7 @@ func parseTarget(vcs *hardware.VCS, keyword string) (target, error) { case "HORIZPOS", "HP": trg, err = vcs.TV.RequestTVState(television.ReqHorizPos) default: - return nil, fmt.Errorf("invalid target (%s)", keyword) + return nil, errors.NewGopherError(errors.InvalidTarget, keyword) } if err != nil { diff --git a/disassembly/disassembly.go b/disassembly/disassembly.go index 251c8d8c..7c3c4ff0 100644 --- a/disassembly/disassembly.go +++ b/disassembly/disassembly.go @@ -32,7 +32,7 @@ func (dsm *Disassembly) ParseMemory(memory *memory.VCSMemory, symtable *symbols. dsm.SequencePoints = make([]uint16, 0, memory.Cart.Memtop()-memory.Cart.Origin()) // create a new non-branching CPU to disassemble memory - mc, err := cpu.New(memory) + mc, err := cpu.NewCPU(memory) if err != nil { return err } @@ -88,7 +88,7 @@ func NewDisassembly(cartridgeFilename string) (*Disassembly, error) { } } - mem, err := memory.New() + mem, err := memory.NewVCSMemory() if err != nil { return nil, err } diff --git a/errors/categories.go b/errors/categories.go new file mode 100644 index 00000000..ef1f3cda --- /dev/null +++ b/errors/categories.go @@ -0,0 +1,37 @@ +package errors + +// list of error numbers +const ( + // Debugger + SymbolsFileCannotOpen Errno = iota + SymbolsFileError + SymbolUnknown + ScriptFileCannotOpen + ScriptFileError + InvalidTarget + + // CPU + UnimplementedInstruction + NullInstruction + ProgramCounterCycled + + // Memory + UnservicedChipWrite + UnknownRegisterName + UnreadableAddress + UnwritableAddress + UnrecognisedAddress + + // Cartridges + CartridgeFileCannotOpen + CartridgeFileError + CartridgeInvalidSize + + // TV + UnknownStateRequest + UnknownCallbackRequest + InvalidStateRequest + + // Peripherals + NoControllersFound +) diff --git a/errors/errors.go b/errors/errors.go index 93a905da..40fbe01a 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -5,81 +5,6 @@ import "fmt" // Errno is used specified the specific error type Errno int -// list of sub-systems used when defining errors -const ( - CategoryDebugger = iota * 8 - CategoryVCS - CategoryCPU - CategoryMemory - CategoryTIA - CategoryRIOT - CategoryTV - CategoryController -) - -// list of error numbers -const ( - // Debugger - NoSymbolsFile Errno = CategoryDebugger + iota - SymbolsFileError - UnknownSymbol - - // VCS - - // CPU - UnimplementedInstruction Errno = CategoryCPU + iota - NullInstruction - ProgramCounterCycled - - // Memory - UnservicedChipWrite Errno = CategoryMemory + iota - UnknownRegisterName - UnreadableAddress - - // TIA - - // RIOT - - // TV - UnknownStateRequest - UnknownCallbackRequest - InvalidStateRequest - - // Controller - NoControllersFound Errno = CategoryController + iota -) - -var messages = map[Errno]string{ - // Debugger - NoSymbolsFile: "no symbols file for %s", - SymbolsFileError: "error processing symbols file (%s)", - UnknownSymbol: "unrecognised symbol (%s)", - - // VCS - - // CPU - UnimplementedInstruction: "unimplemented instruction (%0#x) at (%#04x)", - NullInstruction: "unimplemented instruction (0xff)", - ProgramCounterCycled: "program counter cycled back to 0x0000", - - // Memory - UnservicedChipWrite: "chip memory write signal has not been serviced since previous write (%s)", - UnknownRegisterName: "can't find register name (%s) in list of read addreses in %s memory", - UnreadableAddress: "memory location is not readable", - - // TIA - - // RIOT - - // TV - UnknownStateRequest: "TV does not support %v state", - UnknownCallbackRequest: "TV does not support %v callback", - InvalidStateRequest: "state request for %v is currently invalid", - - // Controller - NoControllersFound: "no controllers found", -} - // Values is the type used to specify arguments for a GopherError type Values []interface{} @@ -89,32 +14,14 @@ type GopherError struct { Values Values } +// NewGopherError is used to create a Gopher2600 specific error +func NewGopherError(errno Errno, values ...interface{}) GopherError { + ge := new(GopherError) + ge.Errno = errno + ge.Values = values + return *ge +} + func (er GopherError) Error() string { return fmt.Sprintf(messages[er.Errno], er.Values...) } - -// Category returns the broad categorisation of a GopherError -func (er GopherError) Category() int { - if er.Errno >= CategoryController { - return CategoryController - } - if er.Errno >= CategoryTV { - return CategoryTV - } - if er.Errno >= CategoryRIOT { - return CategoryRIOT - } - if er.Errno >= CategoryTIA { - return CategoryTIA - } - if er.Errno >= CategoryMemory { - return CategoryMemory - } - if er.Errno >= CategoryCPU { - return CategoryCPU - } - if er.Errno >= CategoryVCS { - return CategoryVCS - } - return CategoryDebugger -} diff --git a/errors/messages.go b/errors/messages.go new file mode 100644 index 00000000..eaf5ae23 --- /dev/null +++ b/errors/messages.go @@ -0,0 +1,41 @@ +package errors + +var messages = map[Errno]string{ + // Debugger + SymbolsFileCannotOpen: "no symbols file for %s", + SymbolsFileError: "error processing symbols file (%s)", + SymbolUnknown: "unrecognised symbol (%s)", + ScriptFileCannotOpen: "cannot open script file (%s)", + InvalidTarget: "invalid target (%s)", + + // CPU + UnimplementedInstruction: "unimplemented instruction (%0#x) at (%#04x)", + NullInstruction: "unimplemented instruction (0xff)", + ProgramCounterCycled: "program counter cycled back to 0x0000", + + // Memory + UnservicedChipWrite: "chip memory write signal has not been serviced since previous write (%s)", + UnknownRegisterName: "can't find register name (%s) in list of read addreses in %s memory", + UnreadableAddress: "memory location is not readable (%#04x)", + UnwritableAddress: "memory location is not writable (%#04x)", + UnrecognisedAddress: "address unrecognised (%v)", + + // Cartridges + CartridgeFileCannotOpen: "cannot open cartridge (%s)", + CartridgeFileError: "error reading cartridge file (%s)", + CartridgeInvalidSize: "cartridge size is not recognised (%d)", + + // TV + UnknownStateRequest: "TV does not support %v state", + UnknownCallbackRequest: "TV does not support %v callback", + InvalidStateRequest: "state request for %v is currently invalid", + + // Peripherals + NoControllersFound: "no controllers found", +} + +// more error strings -- these are strings that are used as arguments to error +// string messages +const ( + FileTruncated string = "file truncated" +) diff --git a/gopher2600.go b/gopher2600.go index efff57e3..d5a7caff 100644 --- a/gopher2600.go +++ b/gopher2600.go @@ -37,7 +37,7 @@ func main() { case "DEBUG": dbg, err := debugger.NewDebugger() if err != nil { - fmt.Printf("* error starting debugger (%s)\n", err) + fmt.Printf("* error starting debugger: %s\n", err) os.Exit(10) } @@ -109,19 +109,19 @@ func fps(cartridgeFile string, justTheVCS bool) error { if justTheVCS { tv = new(television.DummyTV) if tv == nil { - return fmt.Errorf("error creating television for fps profiler") + return fmt.Errorf("error preparing television: %s", err) } } else { tv, err = sdltv.NewSDLTV("NTSC", sdltv.IdealScale) if err != nil { - return fmt.Errorf("error creating television for fps profiler") + return fmt.Errorf("error preparing television: %s", err) } } tv.SetVisibility(true) - vcs, err := hardware.New(tv) + vcs, err := hardware.NewVCS(tv) if err != nil { - return fmt.Errorf("error starting fps profiler (%s)", err) + return fmt.Errorf("error preparing VCS: %s", err) } err = vcs.AttachCartridge(cartridgeFile) @@ -160,13 +160,13 @@ func fps(cartridgeFile string, justTheVCS bool) error { func run(cartridgeFile string) error { tv, err := sdltv.NewSDLTV("NTSC", sdltv.IdealScale) if err != nil { - return fmt.Errorf("error creating television for fps profiler") + return fmt.Errorf("error preparing television: %s", err) } tv.SetVisibility(true) - vcs, err := hardware.New(tv) + vcs, err := hardware.NewVCS(tv) if err != nil { - return fmt.Errorf("error starting fps profiler (%s)", err) + return fmt.Errorf("error preparing VCS: %s", err) } err = vcs.AttachCartridge(cartridgeFile) diff --git a/hardware/cpu/cpu.go b/hardware/cpu/cpu.go index c235479e..dc36d701 100644 --- a/hardware/cpu/cpu.go +++ b/hardware/cpu/cpu.go @@ -43,40 +43,23 @@ type CPU struct { // side-effects. we use this in the disassembly package to make sure // we reach every part of the program NoSideEffects bool + + // silently ignore addressing errors unless StrictAddressing is true + StrictAddressing bool } -// New is the preferred method of initialisation for the CPU structure -func New(mem memory.CPUBus) (*CPU, error) { +// NewCPU is the preferred method of initialisation for the CPU structure +func NewCPU(mem memory.CPUBus) (*CPU, error) { var err error mc := new(CPU) mc.mem = mem - mc.PC, err = register.New(0, 16, "PC", "PC") - if err != nil { - return nil, err - } - - mc.A, err = register.New(0, 8, "A", "A") - if err != nil { - return nil, err - } - - mc.X, err = register.New(0, 8, "X", "X") - if err != nil { - return nil, err - } - - mc.Y, err = register.New(0, 8, "Y", "Y") - if err != nil { - return nil, err - } - - mc.SP, err = register.New(0, 8, "SP", "SP") - if err != nil { - return nil, err - } - + mc.PC = register.NewRegister(0, 16, "PC", "PC") + mc.A = register.NewRegister(0, 8, "A", "A") + mc.X = register.NewRegister(0, 8, "X", "X") + mc.Y = register.NewRegister(0, 8, "Y", "Y") + mc.SP = register.NewRegister(0, 8, "SP", "SP") mc.Status = NewStatusRegister("Status", "SR") mc.opCodes, err = definitions.GetInstructionDefinitions() @@ -113,7 +96,7 @@ func (mc *CPU) IsExecuting() bool { func (mc *CPU) Reset() error { // sanity check if mc.IsExecuting() { - return fmt.Errorf("can't reset CPU in the middle of an instruction") + panic(fmt.Errorf("can't reset CPU in the middle of an instruction")) } mc.PC.Load(0) @@ -136,7 +119,7 @@ func (mc *CPU) Reset() error { func (mc *CPU) LoadPC(indirectAddress uint16) error { // sanity check if mc.IsExecuting() { - return fmt.Errorf("can't alter program counter in the middle of an instruction") + panic(fmt.Errorf("can't alter program counter in the middle of an instruction")) } // because we call this LoadPC() outside of the CPU's ExecuteInstruction() @@ -156,11 +139,41 @@ func (mc *CPU) LoadPC(indirectAddress uint16) error { return nil } +func (mc *CPU) write8Bit(address uint16, value uint8) error { + err := mc.mem.Write(address, value) + + if err != nil { + switch err := err.(type) { + case errors.GopherError: + // don't worry about unwritable addresses (unless strict addressing + // is on) + if mc.StrictAddressing || err.Errno != errors.UnwritableAddress { + return err + } + default: + return err + } + } + + return nil +} + func (mc *CPU) read8Bit(address uint16) (uint8, error) { val, err := mc.mem.Read(address) + if err != nil { - return 0, err + switch err := err.(type) { + case errors.GopherError: + // don't worry about unreadable addresses (unless strict addressing + // is on) + if mc.StrictAddressing || err.Errno != errors.UnreadableAddress { + return 0, err + } + default: + return 0, err + } } + mc.endCycle() return val, nil @@ -192,7 +205,7 @@ func (mc *CPU) read8BitPC() (uint8, error) { } carry, _ := mc.PC.Add(1, false) if carry { - return 0, errors.GopherError{Errno: errors.ProgramCounterCycled, Values: nil} + return 0, errors.NewGopherError(errors.ProgramCounterCycled, nil) } return op, nil } @@ -207,7 +220,7 @@ func (mc *CPU) read16BitPC() (uint16, error) { // the next instruction but I don't believe this has any side-effects carry, _ := mc.PC.Add(2, false) if carry { - return 0, errors.GopherError{Errno: errors.ProgramCounterCycled, Values: nil} + return 0, errors.NewGopherError(errors.ProgramCounterCycled, nil) } return val, nil @@ -276,8 +289,7 @@ func (mc *CPU) branch(flag bool, address uint16, result *result.Instruction) err } // ExecuteInstruction steps CPU forward one instruction, calling -// cycleCallback() after every cycle. note that the CPU will panic if a CPU -// method is called during a callback. +// cycleCallback() after every cycle func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*result.Instruction, error) { // sanity check if mc.IsExecuting() { @@ -314,9 +326,9 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res defn, found := mc.opCodes[operator] if !found { if operator == 0xff { - return nil, errors.GopherError{Errno: errors.NullInstruction, Values: nil} + return nil, errors.NewGopherError(errors.NullInstruction, nil) } - return nil, errors.GopherError{Errno: errors.UnimplementedInstruction, Values: errors.Values{operator, mc.PC.ToUint16() - 1}} + return nil, errors.NewGopherError(errors.UnimplementedInstruction, operator, mc.PC.ToUint16()-1) } result.Defn = defn @@ -439,10 +451,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res } // using 8bit addition because we don't want a page-fault - adder, err := register.NewAnonymous(mc.X, 8) - if err != nil { - return nil, err - } + adder := register.NewAnonRegister(mc.X, 8) adder.Add(indirectAddress, false) // +1 cycle @@ -470,10 +479,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res return nil, err } - adder, err := register.NewAnonymous(mc.Y, 16) - if err != nil { - return nil, err - } + adder := register.NewAnonRegister(mc.Y, 16) adder.Add(indexedAddress&0x00ff, false) address = adder.ToUint16() @@ -501,10 +507,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res return nil, err } - adder, err := register.NewAnonymous(mc.X, 16) - if err != nil { - return nil, err - } + adder := register.NewAnonRegister(mc.X, 16) // add index to LSB of address adder.Add(indirectAddress&0x00ff, false) @@ -533,10 +536,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res return nil, err } - adder, err := register.NewAnonymous(mc.Y, 16) - if err != nil { - return nil, err - } + adder := register.NewAnonRegister(mc.Y, 16) // add index to LSB of address adder.Add(indirectAddress&0x00ff, false) @@ -564,10 +564,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res if err != nil { return nil, err } - adder, err := register.NewAnonymous(indirectAddress, 8) - if err != nil { - return nil, err - } + adder := register.NewAnonRegister(indirectAddress, 8) adder.Add(mc.X, false) address = adder.ToUint16() result.InstructionData = indirectAddress @@ -583,10 +580,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res if err != nil { return nil, err } - adder, err := register.NewAnonymous(indirectAddress, 8) - if err != nil { - return nil, err - } + adder := register.NewAnonRegister(indirectAddress, 8) adder.Add(mc.Y, false) address = adder.ToUint16() result.InstructionData = indirectAddress @@ -622,7 +616,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res // phantom write // +1 cycle if !mc.NoSideEffects { - err = mc.mem.Write(address, value) + err = mc.write8Bit(address, value) if err != nil { return nil, err @@ -660,7 +654,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res case "PHA": if !mc.NoSideEffects { - err = mc.mem.Write(mc.SP.ToUint16(), mc.A.ToUint8()) + err = mc.write8Bit(mc.SP.ToUint16(), mc.A.ToUint8()) if err != nil { return nil, err } @@ -680,7 +674,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res case "PHP": if !mc.NoSideEffects { - err = mc.mem.Write(mc.SP.ToUint16(), mc.Status.ToUint8()) + err = mc.write8Bit(mc.SP.ToUint16(), mc.Status.ToUint8()) if err != nil { return nil, err } @@ -759,7 +753,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res case "STA": if !mc.NoSideEffects { - err = mc.mem.Write(address, mc.A.ToUint8()) + err = mc.write8Bit(address, mc.A.ToUint8()) if err != nil { return nil, err } @@ -767,7 +761,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res case "STX": if !mc.NoSideEffects { - err = mc.mem.Write(address, mc.X.ToUint8()) + err = mc.write8Bit(address, mc.X.ToUint8()) if err != nil { return nil, err } @@ -775,7 +769,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res case "STY": if !mc.NoSideEffects { - err = mc.mem.Write(address, mc.Y.ToUint8()) + err = mc.write8Bit(address, mc.Y.ToUint8()) if err != nil { return nil, err } @@ -804,7 +798,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res case "ASL": var r *register.Register if defn.Effect == definitions.RMW { - r, err = register.NewAnonymous(value, mc.A.Size()) + r = register.NewAnonRegister(value, mc.A.Size()) } else { r = mc.A } @@ -816,7 +810,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res case "LSR": var r *register.Register if defn.Effect == definitions.RMW { - r, err = register.NewAnonymous(value, mc.A.Size()) + r = register.NewAnonRegister(value, mc.A.Size()) } else { r = mc.A } @@ -848,7 +842,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res case "ROR": var r *register.Register if defn.Effect == definitions.RMW { - r, err = register.NewAnonymous(value, mc.A.Size()) + r = register.NewAnonRegister(value, mc.A.Size()) } else { r = mc.A } @@ -860,7 +854,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res case "ROL": var r *register.Register if defn.Effect == definitions.RMW { - r, err = register.NewAnonymous(value, mc.A.Size()) + r = register.NewAnonRegister(value, mc.A.Size()) } else { r = mc.A } @@ -870,30 +864,21 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res value = r.ToUint8() case "INC": - r, err := register.NewAnonymous(value, 8) - if err != nil { - return nil, err - } + r := register.NewAnonRegister(value, 8) r.Add(1, false) mc.Status.Zero = r.IsZero() mc.Status.Sign = r.IsNegative() value = r.ToUint8() case "DEC": - r, err := register.NewAnonymous(value, 8) - if err != nil { - return nil, err - } + r := register.NewAnonRegister(value, 8) r.Add(255, false) mc.Status.Zero = r.IsZero() mc.Status.Sign = r.IsNegative() value = r.ToUint8() case "CMP": - cmp, err := register.NewAnonymous(mc.A, mc.A.Size()) - if err != nil { - return nil, err - } + cmp := register.NewAnonRegister(mc.A, mc.A.Size()) // maybe surprisingly, CMP can be implemented with binary subtract even // if decimal mode is active (the meaning is the same) @@ -902,28 +887,19 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res mc.Status.Sign = cmp.IsNegative() case "CPX": - cmp, err := register.NewAnonymous(mc.X, mc.X.Size()) - if err != nil { - return nil, err - } + cmp := register.NewAnonRegister(mc.X, mc.X.Size()) mc.Status.Carry, _ = cmp.Subtract(value, true) mc.Status.Zero = cmp.IsZero() mc.Status.Sign = cmp.IsNegative() case "CPY": - cmp, err := register.NewAnonymous(mc.Y, mc.Y.Size()) - if err != nil { - return nil, err - } + cmp := register.NewAnonRegister(mc.Y, mc.Y.Size()) mc.Status.Carry, _ = cmp.Subtract(value, true) mc.Status.Zero = cmp.IsZero() mc.Status.Sign = cmp.IsNegative() case "BIT": - cmp, err := register.NewAnonymous(value, mc.A.Size()) - if err != nil { - return nil, err - } + cmp := register.NewAnonRegister(value, mc.A.Size()) mc.Status.Sign = cmp.IsNegative() mc.Status.Overflow = cmp.IsBitV() cmp.AND(mc.A) @@ -1000,7 +976,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res if !mc.NoSideEffects { // push MSB of PC onto stack, and decrement SP // +1 cycle - err = mc.mem.Write(mc.SP.ToUint16(), uint8((mc.PC.ToUint16()&0xFF00)>>8)) + err = mc.write8Bit(mc.SP.ToUint16(), uint8((mc.PC.ToUint16()&0xFF00)>>8)) if err != nil { return nil, err } @@ -1011,7 +987,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res if !mc.NoSideEffects { // push LSB of PC onto stack, and decrement SP // +1 cycle - err = mc.mem.Write(mc.SP.ToUint16(), uint8(mc.PC.ToUint16()&0x00FF)) + err = mc.write8Bit(mc.SP.ToUint16(), uint8(mc.PC.ToUint16()&0x00FF)) if err != nil { return nil, err } @@ -1083,7 +1059,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res // write altered value back to memory for RMW instructions if defn.Effect == definitions.RMW { if !mc.NoSideEffects { - err = mc.mem.Write(address, value) + err = mc.write8Bit(address, value) if err != nil { return nil, err diff --git a/hardware/cpu/cpu_test.go b/hardware/cpu/cpu_test.go index 82913207..a28fd9f9 100644 --- a/hardware/cpu/cpu_test.go +++ b/hardware/cpu/cpu_test.go @@ -465,7 +465,7 @@ func testDecimalMode(t *testing.T, mc *cpu.CPU, mem *MockMem) { func TestCPU(t *testing.T) { mem := NewMockMem() - mc, err := cpu.New(mem) + mc, err := cpu.NewCPU(mem) if err != nil { t.Fatalf(err.Error()) } diff --git a/hardware/cpu/mockmem_test.go b/hardware/cpu/mockmem_test.go index 545c0cb6..f30db7eb 100644 --- a/hardware/cpu/mockmem_test.go +++ b/hardware/cpu/mockmem_test.go @@ -64,7 +64,7 @@ func NewMockVCSMem() (*MockVCSMem, error) { mem := new(MockVCSMem) // use the memory.VCS implementation of memory.Bus - mem.CPUBus, err = memory.New() + mem.CPUBus, err = memory.NewVCSMemory() if err != nil { return nil, err } diff --git a/hardware/cpu/register/decimal_mode.go b/hardware/cpu/register/decimal_mode.go index 9cff22ce..2ce8d73e 100644 --- a/hardware/cpu/register/decimal_mode.go +++ b/hardware/cpu/register/decimal_mode.go @@ -18,12 +18,12 @@ func addDecimal(a, b uint8, carry bool) (r uint8, rcarry bool) { // representations. Returns new carry state func (r *Register) AddDecimal(v interface{}, carry bool) (rcarry bool) { if r.size != 8 { - panic(fmt.Sprintf("decimal mode addition only supported for uint8 values with 8 bit registers")) + panic(fmt.Errorf("decimal mode addition only supported for uint8 values with 8 bit registers")) } val, ok := v.(uint8) if !ok { - panic(fmt.Sprintf("decimal mode addition only supported for uint8 values with 8 bit registers")) + panic(fmt.Errorf("decimal mode addition only supported for uint8 values with 8 bit registers")) } runits := uint8(r.value) & 0x0f @@ -56,12 +56,12 @@ func subtractDecimal(a, b int, carry bool) (r int, rcarry bool) { // representations. Returns new carry state func (r *Register) SubtractDecimal(v interface{}, carry bool) (rcarry bool) { if r.size != 8 { - panic(fmt.Sprintf("decimal mode subtraction only supported for uint8 values with 8 bit registers")) + panic(fmt.Errorf("decimal mode subtraction only supported for uint8 values with 8 bit registers")) } val, ok := v.(uint8) if !ok { - panic(fmt.Sprintf("decimal mode subtraction only supported for uint8 values with 8 bit registers")) + panic(fmt.Errorf("decimal mode subtraction only supported for uint8 values with 8 bit registers")) } runits := int(r.value) & 0x0f diff --git a/hardware/cpu/register/register.go b/hardware/cpu/register/register.go index f01514e5..9bfb0c23 100644 --- a/hardware/cpu/register/register.go +++ b/hardware/cpu/register/register.go @@ -20,22 +20,19 @@ type Register struct { binformat string } -// NewAnonymous initialises a new register without a name -func NewAnonymous(value interface{}, size int) (*Register, error) { - return New(value, size, "", "") +// NewAnonRegister initialises a new register without a name +func NewAnonRegister(value interface{}, size int) *Register { + return NewRegister(value, size, "", "") } -// New is the preferred method of initialisation for Register -func New(value interface{}, size int, label string, shortLabel string) (*Register, error) { +// NewRegister creates a new register of a givel size and name, and initialises +// the value +func NewRegister(value interface{}, size int, label string, shortLabel string) *Register { if size != 8 && size != 16 { - return nil, fmt.Errorf("can't create register (%s) - unsupported bit size (%d)", label, size) + panic(fmt.Errorf("cannot create register (%s) - unsupported bit size (%d)", label, size)) } r := new(Register) - if r == nil { - return nil, fmt.Errorf("can't allocate memory for CPU register (%s)", label) - } - switch value := value.(type) { case *Register: r.value = value.value @@ -48,7 +45,7 @@ func New(value interface{}, size int, label string, shortLabel string) (*Registe case uint16: r.value = uint32(value) default: - return nil, fmt.Errorf("can't create register (%s) - unsupported value type (%s)", label, reflect.TypeOf(value)) + panic(fmt.Errorf("cannot create register (%s) - unsupported value type (%s)", label, reflect.TypeOf(value))) } r.size = size @@ -69,7 +66,7 @@ func New(value interface{}, size int, label string, shortLabel string) (*Registe r.binformat = "%016b" } - return r, nil + return r } // Size returns the number of bits in register @@ -100,7 +97,7 @@ func (r Register) IsBitV() bool { func (r Register) FromInt(v interface{}) string { switch v.(type) { case int: - tr, _ := New(v, r.size, r.label, r.shortLabel) + tr := NewRegister(v, r.size, r.label, r.shortLabel) return fmt.Sprintf("%s=%s", tr.shortLabel, tr.ToHex()) default: return r.shortLabel @@ -174,7 +171,7 @@ func (r *Register) Load(v interface{}) { case uint16: r.value = uint32(v) & r.mask default: - panic(fmt.Sprintf("unsupported value type (%s)", reflect.TypeOf(v))) + panic(fmt.Errorf("unsupported value type (%s)", reflect.TypeOf(v))) } } @@ -211,7 +208,7 @@ func (r *Register) Add(v interface{}, carry bool) (bool, bool) { } postNeg = uint32(v)&r.signBit == r.signBit default: - panic(fmt.Sprintf("unsupported value type (%s)", reflect.TypeOf(v))) + panic(fmt.Errorf("unsupported value type (%s)", reflect.TypeOf(v))) } carry = ^r.mask&r.value != 0 @@ -236,7 +233,7 @@ func (r *Register) Subtract(v interface{}, carry bool) (bool, bool) { case uint8: val = int(v) default: - panic(fmt.Sprintf("unsupported value type (%s)", reflect.TypeOf(v))) + panic(fmt.Errorf("unsupported value type (%s)", reflect.TypeOf(v))) } // no need to do anything if operand is zero @@ -261,7 +258,7 @@ func (r *Register) AND(v interface{}) { case uint8: r.value &= uint32(v) default: - panic(fmt.Sprintf("unsupported value type (%s)", reflect.TypeOf(v))) + panic(fmt.Errorf("unsupported value type (%s)", reflect.TypeOf(v))) } r.value &= r.mask } @@ -286,7 +283,7 @@ func (r *Register) EOR(v interface{}) { case uint8: r.value ^= uint32(v) default: - panic(fmt.Sprintf("unsupported value type (%s)", reflect.TypeOf(v))) + panic(fmt.Errorf("unsupported value type (%s)", reflect.TypeOf(v))) } r.value &= r.mask } @@ -311,7 +308,7 @@ func (r *Register) ORA(v interface{}) { case uint8: r.value |= uint32(v) default: - panic(fmt.Sprintf("unsupported value type (%s)", reflect.TypeOf(v))) + panic(fmt.Errorf("unsupported value type (%s)", reflect.TypeOf(v))) } r.value &= r.mask } diff --git a/hardware/cpu/register/register_test.go b/hardware/cpu/register/register_test.go index 49c55294..e448eb9d 100644 --- a/hardware/cpu/register/register_test.go +++ b/hardware/cpu/register/register_test.go @@ -15,10 +15,7 @@ func r16(t *testing.T) { var carry, overflow bool // initialisation - r16, err := register.New(0, 16, "TEST", "TST") - if err != nil { - t.Fatalf(err.Error()) - } + r16 := register.NewRegister(0, 16, "TEST", "TST") assert.CheckValueVCS(t, r16.IsZero(), true) assert.CheckValueVCS(t, r16, 0) @@ -37,10 +34,7 @@ func r16(t *testing.T) { assert.CheckValueVCS(t, r16.IsZero(), true) // register operand - r16b, err := register.New(10, 16, "TEST B", "TSTB") - if err != nil { - t.Fatalf(err.Error()) - } + r16b := register.NewRegister(10, 16, "TEST B", "TSTB") assert.CheckValueVCS(t, r16b, 10) r16.Add(r16b, true) assert.CheckValueVCS(t, r16, 11) @@ -89,10 +83,7 @@ func r8(t *testing.T) { var carry, overflow bool // initialisation - r8, err := register.New(0, 8, "TEST", "TST") - if err != nil { - t.Fatalf(err.Error()) - } + r8 := register.NewRegister(0, 8, "TEST", "TST") assert.CheckValueVCS(t, r8.IsZero(), true) assert.CheckValueVCS(t, r8, 0) @@ -111,10 +102,7 @@ func r8(t *testing.T) { assert.CheckValueVCS(t, r8.IsZero(), true) // register operand - r8b, err := register.New(10, 8, "TEST B", "TSTB") - if err != nil { - t.Fatalf(err.Error()) - } + r8b := register.NewRegister(10, 8, "TEST B", "TSTB") assert.CheckValueVCS(t, r8b, 10) r8.Add(r8b, true) assert.CheckValueVCS(t, r8, 11) diff --git a/hardware/cpu/result/instruction.go b/hardware/cpu/result/instruction.go index e7b6ecca..dd04f76a 100644 --- a/hardware/cpu/result/instruction.go +++ b/hardware/cpu/result/instruction.go @@ -85,7 +85,7 @@ func (result Instruction) GetString(symtable *symbols.Table, style Style) string case 1: hex = fmt.Sprintf("%02x %s", result.Defn.ObjectCode, hex) default: - panic("unsupported number of bytes in instruction") + hex = fmt.Sprintf("(%d bytes) %s", result.Defn.Bytes, hex) } } @@ -101,7 +101,7 @@ func (result Instruction) GetString(symtable *symbols.Table, style Style) string // -- we create a mock register with the instruction's // address as the initial value - pc, _ := register.NewAnonymous(result.Address, 16) + pc := register.NewAnonRegister(result.Address, 16) // -- add the number of instruction bytes to get the PC as // it would be at the end of the instruction diff --git a/hardware/memory/cartridge.go b/hardware/memory/cartridge.go index a7c88437..1d9f6f44 100644 --- a/hardware/memory/cartridge.go +++ b/hardware/memory/cartridge.go @@ -1,7 +1,7 @@ package memory import ( - "fmt" + "gopher2600/errors" "os" ) @@ -28,9 +28,6 @@ type Cartridge struct { // newCart is the preferred method of initialisation for the cartridges func newCart() *Cartridge { cart := new(Cartridge) - if cart == nil { - return nil - } cart.label = "Cartridge" cart.origin = 0x1000 cart.memtop = 0x1fff @@ -70,14 +67,14 @@ func (cart Cartridge) Read(address uint16) (uint8, error) { // Implementation of CPUBus.Write func (cart *Cartridge) Write(address uint16, data uint8) error { - return fmt.Errorf("refusing to write to cartridge") + return errors.NewGopherError(errors.UnwritableAddress, address) } // Attach loads the bytes from a cartridge (represented by 'filename') func (cart *Cartridge) Attach(filename string) error { cf, err := os.Open(filename) if err != nil { - return fmt.Errorf("error opening cartridge (%s)", err) + return errors.NewGopherError(errors.CartridgeFileCannotOpen, err) } defer func() { _ = cf.Close() @@ -92,7 +89,7 @@ func (cart *Cartridge) Attach(filename string) error { // check that cartridge is of a supported size // TODO: ensure that this is a complete and accurate check if cfi.Size()%bankSize != 0 { - return fmt.Errorf("cartridge (%s) is not of a supported size (%d)", filename, cfi.Size()) + return errors.NewGopherError(errors.CartridgeFileCannotOpen, cfi.Size()) } // allocate enough memory for new cartridge @@ -104,7 +101,7 @@ func (cart *Cartridge) Attach(filename string) error { return err } if n != len(cart.memory) { - return fmt.Errorf("error reading cartridge file (%s)", filename) + return errors.NewGopherError(errors.CartridgeFileError, errors.FileTruncated) } // make sure we're pointing to the first bank diff --git a/hardware/memory/chip.go b/hardware/memory/chip.go index 42c4d476..f1bc6286 100644 --- a/hardware/memory/chip.go +++ b/hardware/memory/chip.go @@ -33,8 +33,8 @@ type ChipMemory struct { lastReadRegister string // the periphQueue is used to write to chip memory in a goroutine friendly - // manner. peripherals can be implemented with goroutines and so we need to - // be careful when accessing memory. + // manner (peripherals have been implemented with goroutines and so we need + // to be careful when accessing the memory array) periphQueue chan *periphPayload } @@ -63,7 +63,7 @@ func (area ChipMemory) Memtop() uint16 { func (area ChipMemory) Peek(address uint16) (uint8, uint16, string, string, error) { sym := vcssymbols.ReadSymbols[address&area.readMask] if sym == "" { - return 0, 0, "", "", errors.GopherError{Errno: errors.UnreadableAddress, Values: nil} + return 0, 0, "", "", errors.NewGopherError(errors.UnreadableAddress, address) } return area.memory[address-area.origin], address & area.readMask, area.Label(), sym, nil } diff --git a/hardware/memory/chip_chipbus.go b/hardware/memory/chip_chipbus.go index 6a2447ca..1f204b99 100644 --- a/hardware/memory/chip_chipbus.go +++ b/hardware/memory/chip_chipbus.go @@ -17,14 +17,14 @@ func (area *ChipMemory) ChipRead() (bool, string, uint8) { return false, "", 0 } -// ChipWrite writes the data to the memory area's address specified by -// registerName +// ChipWrite is an implementation of ChipBus.ChipWrite. it writes the data to +// the memory area's address specified by registerName func (area *ChipMemory) ChipWrite(address uint16, data uint8) { area.memory[address] = data } -// LastReadRegister returns the register name of the last memory -// location *read* by the CPU +// LastReadRegister is an implementation of ChipBus.LastReadRegister. it +// returns the register name of the last memory location *read* by the CPU func (area ChipMemory) LastReadRegister() string { return area.lastReadRegister } diff --git a/hardware/memory/chip_cpubus.go b/hardware/memory/chip_cpubus.go index 97ea976f..26e3faa5 100644 --- a/hardware/memory/chip_cpubus.go +++ b/hardware/memory/chip_cpubus.go @@ -16,12 +16,7 @@ func (area *ChipMemory) Read(address uint16) (uint8, error) { sym := vcssymbols.ReadSymbols[address] if sym == "" { - // silently ignore illegal reads (we're definitely reading from the correct - // memory space but some registers are not readable) - // - // TODO: add a GopherError that can be ignored or noted as appropriate - // for the application - return 0, nil + return 0, errors.NewGopherError(errors.UnreadableAddress, address) } return area.memory[address-area.origin], nil @@ -33,14 +28,12 @@ func (area *ChipMemory) Write(address uint16, data uint8) error { // check that the last write to this memory area has been serviced if area.writeSignal { - return errors.GopherError{Errno: errors.UnservicedChipWrite, Values: errors.Values{vcssymbols.WriteSymbols[area.lastWriteAddress]}} + return errors.NewGopherError(errors.UnservicedChipWrite, vcssymbols.WriteSymbols[area.lastWriteAddress]) } sym := vcssymbols.WriteSymbols[address] if sym == "" { - // silently ignore illegal writes (we're definitely writing to the correct - // memory space but some registers are not writable) - return nil + return errors.NewGopherError(errors.UnwritableAddress, address) } // note address of write diff --git a/hardware/memory/chip_riot.go b/hardware/memory/chip_riot.go index 1d7b3a37..21b4abdb 100644 --- a/hardware/memory/chip_riot.go +++ b/hardware/memory/chip_riot.go @@ -3,9 +3,6 @@ package memory // newRIOT is the preferred method of initialisation for the RIOT memory area func newRIOT() *ChipMemory { area := newChipMem() - if area == nil { - return nil - } area.label = "RIOT" area.origin = 0x0280 area.memtop = 0x0287 diff --git a/hardware/memory/chip_tia.go b/hardware/memory/chip_tia.go index a65d1bb8..d6a6a565 100644 --- a/hardware/memory/chip_tia.go +++ b/hardware/memory/chip_tia.go @@ -3,14 +3,10 @@ package memory // newTIA is the preferred method of initialisation for the TIA memory area func newTIA() *ChipMemory { area := newChipMem() - if area == nil { - return nil - } area.label = "TIA" area.origin = 0x0000 area.memtop = 0x003f area.memory = make([]uint8, area.memtop-area.origin+1) area.readMask = 0x000f - return area } diff --git a/hardware/memory/memory_test.go b/hardware/memory/memory_test.go index 63f44417..ab528e52 100644 --- a/hardware/memory/memory_test.go +++ b/hardware/memory/memory_test.go @@ -10,7 +10,7 @@ const ( ) func TestMemory(t *testing.T) { - mem, err := memory.New() + mem, err := memory.NewVCSMemory() if err != nil { t.Fatalf(err.Error()) } diff --git a/hardware/memory/pia.go b/hardware/memory/pia.go index e70c95d1..06b75721 100644 --- a/hardware/memory/pia.go +++ b/hardware/memory/pia.go @@ -17,9 +17,6 @@ type PIA struct { // newPIA is the preferred method of initialisation for the PIA pia memory area func newPIA() *PIA { pia := new(PIA) - if pia == nil { - return nil - } pia.label = "PIA RAM" pia.origin = 0x0080 pia.memtop = 0x00ff diff --git a/hardware/memory/vcs.go b/hardware/memory/vcs.go index 33242d18..8b2deab1 100644 --- a/hardware/memory/vcs.go +++ b/hardware/memory/vcs.go @@ -2,6 +2,7 @@ package memory import ( "fmt" + "gopher2600/errors" "gopher2600/hardware/memory/vcssymbols" ) @@ -24,43 +25,33 @@ type VCSMemory struct { // TODO: allow reading only when 02 clock is high and writing when it is low -// New is the preferred method of initialisation for VCSMemory -func New() (*VCSMemory, error) { +// NewVCSMemory is the preferred method of initialisation for VCSMemory +func NewVCSMemory() (*VCSMemory, error) { mem := new(VCSMemory) - if mem == nil { - return nil, fmt.Errorf("can't allocate memory for VCS") - } - mem.memmap = make(map[uint16]Area) + mem.RIOT = newRIOT() mem.TIA = newTIA() mem.PIA = newPIA() mem.Cart = newCart() - - if mem.memmap == nil || mem.RIOT == nil || mem.TIA == nil || mem.PIA == nil || mem.Cart == nil { - return nil, fmt.Errorf("can't allocate memory for VCS") + if mem.RIOT == nil || mem.TIA == nil || mem.PIA == nil || mem.Cart == nil { + return nil, fmt.Errorf("error creating memory areas") } // create the memory map; each address in the memory map points to the // memory area it resides in. we only record 'primary' addresses; all // addresses should be passed through the MapAddress() function in order // to iron out any mirrors - - var i uint16 - - for i = mem.TIA.origin; i <= mem.TIA.memtop; i++ { + for i := mem.TIA.origin; i <= mem.TIA.memtop; i++ { mem.memmap[i] = mem.TIA } - - for i = mem.PIA.origin; i <= mem.PIA.memtop; i++ { + for i := mem.PIA.origin; i <= mem.PIA.memtop; i++ { mem.memmap[i] = mem.PIA } - - for i = mem.RIOT.origin; i <= mem.RIOT.memtop; i++ { + for i := mem.RIOT.origin; i <= mem.RIOT.memtop; i++ { mem.memmap[i] = mem.RIOT } - - for i = mem.Cart.origin; i <= mem.Cart.memtop; i++ { + for i := mem.Cart.origin; i <= mem.Cart.memtop; i++ { mem.memmap[i] = mem.Cart } @@ -102,7 +93,7 @@ func (mem VCSMemory) Read(address uint16) (uint8, error) { ma := mem.MapAddress(address) area, present := mem.memmap[ma] if !present { - return 0, fmt.Errorf("%04x not mapped correctly", address) + panic(fmt.Errorf("%04x not mapped correctly", address)) } return area.(CPUBus).Read(ma) } @@ -138,12 +129,12 @@ func (mem VCSMemory) Peek(address interface{}) (uint8, uint16, string, string, e } } default: - return 0, 0, "", "", fmt.Errorf("unrecognised address (%v)", address) + return 0, 0, "", "", errors.NewGopherError(errors.UnrecognisedAddress, address) } area, present := mem.memmap[ma] if !present { - return 0, 0, area.Label(), "", fmt.Errorf("%04x not mapped correctly", address) + panic(fmt.Errorf("%04x not mapped correctly", address)) } return area.(Area).Peek(ma) diff --git a/hardware/peripherals/stick.go b/hardware/peripherals/stick.go index 71983ac2..5b21a270 100644 --- a/hardware/peripherals/stick.go +++ b/hardware/peripherals/stick.go @@ -33,7 +33,7 @@ func NewStick(tia memory.PeriphBus, riot memory.PeriphBus, panel *Panel) *Stick // system assigned index: typically increments on each new controller added. stick.device = joysticks.Connect(1) if stick.device == nil { - stick.err = errors.GopherError{Errno: errors.NoControllersFound, Values: nil} + stick.err = errors.NewGopherError(errors.NoControllersFound, nil) return } diff --git a/hardware/riot/riot.go b/hardware/riot/riot.go index 2933e622..01707d2f 100644 --- a/hardware/riot/riot.go +++ b/hardware/riot/riot.go @@ -33,16 +33,11 @@ type RIOT struct { timerCycles int } -// New is the preferred method of initialisation for the PIA structure -func New(mem memory.ChipBus) *RIOT { +// NewRIOT creates a RIOT, to be used in a VCS emulation +func NewRIOT(mem memory.ChipBus) *RIOT { riot := new(RIOT) - if riot == nil { - return nil - } - riot.timerRegister = "no timer" riot.mem = mem - return riot } @@ -87,7 +82,7 @@ func (riot *RIOT) ReadRIOTMemory() { riot.timerINTIMvalue = value riot.timerCycles = 2 default: - fmt.Printf("unserviced RIOT register", register) + fmt.Printf("unserviced RIOT register (%v)", register) } } } diff --git a/hardware/tia/colorclock/colorclock.go b/hardware/tia/colorclock/colorclock.go index e4819d8d..0bf5fa38 100644 --- a/hardware/tia/colorclock/colorclock.go +++ b/hardware/tia/colorclock/colorclock.go @@ -13,12 +13,7 @@ type ColorClock struct { // New is the preferred method of initialisation for the ColorClock func New() *ColorClock { cc := new(ColorClock) - if cc == nil { - return nil - } - cc.SetResetPattern("010100") // count==56, - return cc } diff --git a/hardware/tia/polycounter/polycounter.go b/hardware/tia/polycounter/polycounter.go index ec1c7df6..c115f9be 100644 --- a/hardware/tia/polycounter/polycounter.go +++ b/hardware/tia/polycounter/polycounter.go @@ -23,7 +23,7 @@ func init() { table6bits[i] = fmt.Sprintf("%06b", p) } if table6bits[63] != "000000" { - panic("error during 6 bit polycounter generation") + panic(fmt.Errorf("error during 6 bit polycounter generation")) } // force the final value to be the invalid polycounter value. this is only @@ -32,13 +32,13 @@ func init() { } // LookupPattern returns the index of the specified pattern -func LookupPattern(pattern string) (int, error) { +func LookupPattern(pattern string) int { for i := 0; i < len(table6bits); i++ { if table6bits[i] == pattern { - return i, nil + return i } } - return 0, fmt.Errorf("could not find pattern (%s) in 6 bit lookup table", pattern) + panic(fmt.Errorf("could not find pattern (%s) in 6 bit lookup table", pattern)) } // Polycounter implements the VCS method of counting. It is doesn't require @@ -59,10 +59,7 @@ type Polycounter struct { // resets during a Tick(). this should be called at least once or the reset // pattern will be "000000" which is probably not what you want func (pk *Polycounter) SetResetPattern(resetPattern string) { - i, err := LookupPattern(resetPattern) - if err != nil { - panic("couldn't find reset pattern in polycounter table") - } + i := LookupPattern(resetPattern) pk.ResetPoint = i } diff --git a/hardware/tia/tia.go b/hardware/tia/tia.go index 7244dfe2..fed328c5 100644 --- a/hardware/tia/tia.go +++ b/hardware/tia/tia.go @@ -50,13 +50,9 @@ func (tia TIA) String() string { return tia.MachineInfo() } -// New is the preferred method of initialisation for the TIA structure -func New(tv television.Television, mem memory.ChipBus) *TIA { +// NewTIA creates a TIA, to be used in a VCS emulation +func NewTIA(tv television.Television, mem memory.ChipBus) *TIA { tia := new(TIA) - if tia == nil { - return nil - } - tia.tv = tv tia.mem = mem @@ -79,7 +75,7 @@ func New(tv television.Television, mem memory.ChipBus) *TIA { tia.hblank = true - tia.Video = video.New(tia.colorClock) + tia.Video = video.NewVideo(tia.colorClock) if tia.Video == nil { return nil } diff --git a/hardware/tia/video/future.go b/hardware/tia/video/future.go index 55d5cb7a..cec49ab0 100644 --- a/hardware/tia/video/future.go +++ b/hardware/tia/video/future.go @@ -15,9 +15,6 @@ type future struct { // newFuture is the preferred method of initialisation for the pending type func newFuture() *future { dc := new(future) - if dc == nil { - return nil - } dc.remainingCycles = -1 dc.payload = true return dc diff --git a/hardware/tia/video/sprite.go b/hardware/tia/video/sprite.go index 7b88ef9a..5e69a859 100644 --- a/hardware/tia/video/sprite.go +++ b/hardware/tia/video/sprite.go @@ -44,10 +44,6 @@ type sprite struct { func newSprite(label string, colorClock *colorclock.ColorClock) *sprite { sp := new(sprite) - if sp == nil { - return nil - } - sp.label = label sp.colorClock = colorClock diff --git a/hardware/tia/video/video.go b/hardware/tia/video/video.go index d23aa34d..aa5e2764 100644 --- a/hardware/tia/video/video.go +++ b/hardware/tia/video/video.go @@ -22,13 +22,9 @@ type Video struct { Collisions collisions } -// New is the preferred method of initialisation for the Video structure -func New(colorClock *colorclock.ColorClock) *Video { +// NewVideo is the preferred method of initialisation for the Video structure +func NewVideo(colorClock *colorclock.ColorClock) *Video { vd := new(Video) - if vd == nil { - return nil - } - vd.colorClock = colorClock // playfield diff --git a/hardware/vcs.go b/hardware/vcs.go index eab85b09..eaa9f0dc 100644 --- a/hardware/vcs.go +++ b/hardware/vcs.go @@ -32,31 +32,32 @@ type VCS struct { controller *peripherals.Stick } -// New is the preferred method of initialisation for the VCS structure -func New(tv television.Television) (*VCS, error) { +// NewVCS creates a new VCS and everything associated with the hardware. It is +// used for all aspects of emulation: debugging sessions, and regular play +func NewVCS(tv television.Television) (*VCS, error) { var err error vcs := new(VCS) vcs.TV = tv - vcs.Mem, err = memory.New() + vcs.Mem, err = memory.NewVCSMemory() if err != nil { return nil, err } - vcs.MC, err = cpu.New(vcs.Mem) + vcs.MC, err = cpu.NewCPU(vcs.Mem) if err != nil { return nil, err } - vcs.TIA = tia.New(vcs.TV, vcs.Mem.TIA) + vcs.TIA = tia.NewTIA(vcs.TV, vcs.Mem.TIA) if vcs.TIA == nil { - return nil, fmt.Errorf("can't allocate memory for VCS TIA") + return nil, fmt.Errorf("can't create TIA") } - vcs.RIOT = riot.New(vcs.Mem.RIOT) + vcs.RIOT = riot.NewRIOT(vcs.Mem.RIOT) if vcs.RIOT == nil { - return nil, fmt.Errorf("can't allocate memory for VCS RIOT") + return nil, fmt.Errorf("can't create RIOT") } vcs.panel = peripherals.NewPanel(vcs.Mem.RIOT) @@ -66,8 +67,8 @@ func New(tv television.Television) (*VCS, error) { // TODO: better contoller support vcs.controller = peripherals.NewStick(vcs.Mem.TIA, vcs.Mem.RIOT, vcs.panel) - if vcs.panel == nil { - return nil, fmt.Errorf("can't create new stick controller") + if vcs.controller == nil { + return nil, fmt.Errorf("can't create stick controller") } return vcs, nil diff --git a/symbols/search.go b/symbols/search.go index c92d5094..f39a4166 100644 --- a/symbols/search.go +++ b/symbols/search.go @@ -11,5 +11,5 @@ func (sym *Table) SearchLocation(location string) (uint16, error) { } } } - return 0, errors.GopherError{Errno: errors.UnknownSymbol, Values: errors.Values{location}} + return 0, errors.NewGopherError(errors.SymbolUnknown, location) } diff --git a/symbols/symbols.go b/symbols/symbols.go index 319f19fb..5d308345 100644 --- a/symbols/symbols.go +++ b/symbols/symbols.go @@ -57,7 +57,7 @@ func ReadSymbolsFile(cartridgeFilename string) (*Table, error) { if cartridgeFilename == "" { return table, nil } - return table, errors.GopherError{Errno: errors.NoSymbolsFile, Values: errors.Values{cartridgeFilename}} + return table, errors.NewGopherError(errors.SymbolsFileCannotOpen, cartridgeFilename) } defer func() { _ = sf.Close() @@ -66,17 +66,17 @@ func ReadSymbolsFile(cartridgeFilename string) (*Table, error) { // get file info sfi, err := sf.Stat() if err != nil { - return table, errors.GopherError{Errno: errors.SymbolsFileError, Values: errors.Values{err}} + return table, errors.NewGopherError(errors.SymbolsFileError, err) } // read symbols file and split into lines sym := make([]byte, sfi.Size()) n, err := sf.Read(sym) if err != nil { - return table, errors.GopherError{Errno: errors.SymbolsFileError, Values: errors.Values{err}} + return table, errors.NewGopherError(errors.SymbolsFileError, err) } if n != len(sym) { - return table, errors.GopherError{Errno: errors.SymbolsFileError, Values: errors.Values{"file truncated"}} + return table, errors.NewGopherError(errors.SymbolsFileError, errors.FileTruncated) } lines := strings.Split(string(sym), "\n") diff --git a/television/headless.go b/television/headless.go index 345ee4d6..b2869411 100644 --- a/television/headless.go +++ b/television/headless.go @@ -14,8 +14,9 @@ const ( ) // HeadlessTV is the minimalist implementation of the Television interface - a -// television without a screen. fuller implementations of the television can -// use this as the basis of the emulation +// television without a screen. Fuller implementations of the television can +// use this as the basis of the emulation by struct embedding. The +// InitHeadlessTV() method is useful in this regard. type HeadlessTV struct { // spec is the specification of the tv type (NTSC or PAL) Spec *specification @@ -52,12 +53,10 @@ type HeadlessTV struct { forceUpdate func() error } -// NewHeadlessTV is the preferred method for initalising a headless TV +// NewHeadlessTV creates a new instance of HeadlessTV for a minimalist +// implementation of a televsion for the VCS emulation func NewHeadlessTV(tvType string) (*HeadlessTV, error) { tv := new(HeadlessTV) - if tv == nil { - return nil, fmt.Errorf("can't allocate memory for headless tv") - } err := InitHeadlessTV(tv, tvType) if err != nil { @@ -228,7 +227,7 @@ func (tv *HeadlessTV) SetPause(pause bool) error { func (tv *HeadlessTV) RequestTVState(request TVStateReq) (*TVState, error) { switch request { default: - return nil, errors.GopherError{Errno: errors.UnknownStateRequest, Values: errors.Values{request}} + return nil, errors.NewGopherError(errors.UnknownStateRequest, request) case ReqFramenum: return tv.frameNum, nil case ReqScanline: @@ -240,5 +239,5 @@ func (tv *HeadlessTV) RequestTVState(request TVStateReq) (*TVState, error) { // RegisterCallback (with dummyTV reciever) is the null implementation func (tv *HeadlessTV) RegisterCallback(request CallbackReq, callback func()) error { - return errors.GopherError{Errno: errors.UnknownCallbackRequest, Values: errors.Values{request}} + return errors.NewGopherError(errors.UnknownCallbackRequest, request) } diff --git a/television/sdltv/sdltv.go b/television/sdltv/sdltv.go index ef0edeeb..51dc930d 100644 --- a/television/sdltv/sdltv.go +++ b/television/sdltv/sdltv.go @@ -1,7 +1,6 @@ package sdltv import ( - "fmt" "gopher2600/television" "sync" "time" @@ -54,14 +53,11 @@ type SDLTV struct { guiLoopLock sync.Mutex } -// NewSDLTV is the preferred method for initalising an SDL TV +// NewSDLTV initiliases a new instance of an SDL based display for the VCS func NewSDLTV(tvType string, scale float32) (*SDLTV, error) { var err error tv := new(SDLTV) - if tv == nil { - return nil, fmt.Errorf("can't allocate memory for sdl tv") - } err = television.InitHeadlessTV(&tv.HeadlessTV, tvType) if err != nil { @@ -79,7 +75,8 @@ func NewSDLTV(tvType string, scale float32) (*SDLTV, error) { // for the renderer tv.pixelWidth = 2 - // pixel scale + // pixel scale is the number of pixels each VCS "pixel" is to be occupy on + // the screen tv.pixelScale = scale // SDL initialisation @@ -124,13 +121,8 @@ func NewSDLTV(tvType string, scale float32) (*SDLTV, error) { // register callbacks from HeadlessTV to SDLTV tv.NewFrame = func() error { - err := tv.update() - if err != nil { - return err - } - tv.scr.swapBuffer() - - return nil + defer tv.scr.swapBuffer() + return tv.update() } // update tv with a black image diff --git a/television/sdltv/tvinterface.go b/television/sdltv/tvinterface.go index 036134b6..7c4f0754 100644 --- a/television/sdltv/tvinterface.go +++ b/television/sdltv/tvinterface.go @@ -85,7 +85,7 @@ func (tv *SDLTV) RegisterCallback(request television.CallbackReq, callback func( tv.onWindowClose = callback tv.guiLoopLock.Unlock() default: - return errors.GopherError{Errno: errors.UnknownCallbackRequest, Values: errors.Values{request}} + return errors.NewGopherError(errors.UnknownCallbackRequest, request) } return nil diff --git a/television/specifications.go b/television/specifications.go index 5769fd31..2ded90ac 100644 --- a/television/specifications.go +++ b/television/specifications.go @@ -20,9 +20,6 @@ var specPAL *specification func init() { specNTSC = new(specification) - if specNTSC == nil { - panic("error during initialisation of NTSC specification") - } specNTSC.ClocksPerHblank = 68 specNTSC.ClocksPerVisible = 160 specNTSC.ClocksPerScanline = 228 @@ -34,9 +31,6 @@ func init() { specNTSC.Colors = ntscColors specPAL = new(specification) - if specPAL == nil { - panic("error during initialisation of PAL specification") - } specPAL.ClocksPerHblank = 68 specPAL.ClocksPerVisible = 160 specPAL.ClocksPerScanline = 228 diff --git a/television/television.go b/television/television.go index 0f69cbac..6ac8e304 100644 --- a/television/television.go +++ b/television/television.go @@ -61,10 +61,10 @@ func (DummyTV) SetPause(pause bool) error { // RequestTVState (with dummyTV reciever) is the null implementation func (DummyTV) RequestTVState(request TVStateReq) (*TVState, error) { - return nil, errors.GopherError{Errno: errors.UnknownStateRequest, Values: errors.Values{request}} + return nil, errors.NewGopherError(errors.UnknownStateRequest, request) } // RegisterCallback (with dummyTV reciever) is the null implementation func (DummyTV) RegisterCallback(request CallbackReq, callback func()) error { - return errors.GopherError{Errno: errors.UnknownCallbackRequest, Values: errors.Values{request}} + return errors.NewGopherError(errors.UnknownCallbackRequest, request) }