diff --git a/debugger/debugger.go b/debugger/debugger.go index 209ef15f..112d4c41 100644 --- a/debugger/debugger.go +++ b/debugger/debugger.go @@ -32,10 +32,12 @@ type Debugger struct { runUntilHalt bool // halt conditions - // note that the UI probably allows the user to halt (eg. ctrl-c) breakpoints *breakpoints traps *traps + // note that the UI probably allows the user to manually break or trap at + // will, with for example, ctrl-c + // we accumulate break and trap messsages until we can service them breakMessages string trapMessages string @@ -54,7 +56,7 @@ type Debugger struct { commandOnHalt string commandOnHaltStored string - // similarly, commandOnStep is the sequence of commands to run afer ever + // similarly, commandOnStep is the sequence of commands to run afer every // cpu/video cycle commandOnStep string commandOnStepStored string @@ -239,7 +241,7 @@ func (dbg *Debugger) loadCartridge(cartridgeFilename string) error { // breakandtrapCallback() func (dbg *Debugger) videoCycleCallback(result *result.Instruction) error { - dbg.breakandtrapCallback(result) + dbg.breakAndTrapCallback(result) dbg.lastResult = result if dbg.commandOnStep != "" { _, err := dbg.parseInput(dbg.commandOnStep) @@ -250,13 +252,15 @@ func (dbg *Debugger) videoCycleCallback(result *result.Instruction) error { return dbg.inputLoop(false) } -func (dbg *Debugger) breakandtrapCallback(result *result.Instruction) error { +func (dbg *Debugger) breakAndTrapCallback(result *result.Instruction) error { // because we call this callback mid-instruction, the programme counter // maybe in it's non-final state - we don't want to break or trap in these // instances if the final effect of the instruction changes the programme // counter to some other value - if (result.Defn.Effect == definitions.Flow || result.Defn.Effect == definitions.Subroutine) && !result.Final { - return nil + if result.Defn != nil { + if (result.Defn.Effect == definitions.Flow || result.Defn.Effect == definitions.Subroutine) && !result.Final { + return nil + } } dbg.breakMessages = dbg.breakpoints.check(dbg.breakMessages) @@ -393,7 +397,7 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error { if dbg.inputloopVideoClock { _, dbg.lastResult, err = dbg.vcs.Step(dbg.videoCycleCallback) } else { - _, dbg.lastResult, err = dbg.vcs.Step(dbg.breakandtrapCallback) + _, dbg.lastResult, err = dbg.vcs.Step(dbg.breakAndTrapCallback) } if err != nil { @@ -409,15 +413,15 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error { default: return err } - } - - // check validity of instruction result - if dbg.lastResult.Final { - err := dbg.lastResult.IsValid() - if err != nil { - fmt.Println(dbg.lastResult.Defn) - fmt.Println(dbg.lastResult) - panic(err) + } else { + // check validity of instruction result + if dbg.lastResult.Final { + err := dbg.lastResult.IsValid() + if err != nil { + fmt.Println(dbg.lastResult.Defn) + fmt.Println(dbg.lastResult) + panic(err) + } } } diff --git a/errors/categories.go b/errors/categories.go index 7405b4e0..62bbe57c 100644 --- a/errors/categories.go +++ b/errors/categories.go @@ -26,9 +26,9 @@ const ( UnrecognisedAddress // Cartridges - CartridgeFileCannotOpen CartridgeFileError - CartridgeInvalidSize + CartridgeUnsupported + CartridgeMissing // TV UnknownTVRequest diff --git a/errors/messages.go b/errors/messages.go index 505de186..848ba33c 100644 --- a/errors/messages.go +++ b/errors/messages.go @@ -24,9 +24,9 @@ var messages = map[Errno]string{ UnrecognisedAddress: "address unrecognised (%v)", // Cartridges - CartridgeFileCannotOpen: "cannot open cartridge (%s)", - CartridgeFileError: "error reading cartridge file (%s)", - CartridgeInvalidSize: "cartridge size is not recognised (%d)", + CartridgeFileError: "error reading cartridge file (%s)", + CartridgeUnsupported: "cartridge unsupported (%s)", + CartridgeMissing: "no cartridge attached", // TV UnknownTVRequest: "TV does not support %v request", diff --git a/hardware/cpu/cpu.go b/hardware/cpu/cpu.go index c7fa9315..182aa219 100644 --- a/hardware/cpu/cpu.go +++ b/hardware/cpu/cpu.go @@ -2,7 +2,9 @@ package cpu // TODO List // --------- -// . NMOS indexed addressing extra read when crossing page boundaries +// o NMOS indexed addressing extra read when crossing page boundaries +// o check that NoSideEffects is consistent in its intention +// o check that all calls to endCycle() occur when they're supposed to import ( "fmt" @@ -14,6 +16,8 @@ import ( "log" ) +const irqInterruptVector = 0xfffe + // CPU is the main container structure for the package type CPU struct { PC *register.Register @@ -139,7 +143,14 @@ func (mc *CPU) LoadPC(indirectAddress uint16) error { return nil } +// note that write8Bit, unline read8Bit(), does not call endCycle() this is +// because we need to differentiate between different addressing modes at +// different times. func (mc *CPU) write8Bit(address uint16, value uint8) error { + if mc.NoSideEffects { + return nil + } + err := mc.mem.Write(address, value) if err != nil { @@ -158,6 +169,7 @@ func (mc *CPU) write8Bit(address uint16, value uint8) error { return nil } +// note that read8Bit calls endCycle as appropriate func (mc *CPU) read8Bit(address uint16) (uint8, error) { val, err := mc.mem.Read(address) @@ -352,11 +364,21 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res // implied mode does not use any additional bytes. however, the next // instruction is read but the PC is not incremented - // phantom read - // +1 cycle - _, err := mc.read8Bit(mc.PC.ToUint16()) - if err != nil { - return nil, err + if defn.Mnemonic == "BRK" { + // BRK is unusual in that it increases the PC by two bytes despite + // being an implied addressing mode. + // +1 cycle + _, err = mc.read8BitPC() + if err != nil { + return nil, err + } + } else { + // phantom read + // +1 cycle + _, err := mc.read8Bit(mc.PC.ToUint16()) + if err != nil { + return nil, err + } } case definitions.Immediate: @@ -627,12 +649,10 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res // phantom write // +1 cycle - if !mc.NoSideEffects { - err = mc.write8Bit(address, value) + err = mc.write8Bit(address, value) - if err != nil { - return nil, err - } + if err != nil { + return nil, err } mc.endCycle() } @@ -665,11 +685,9 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res mc.Status.Overflow = false case "PHA": - if !mc.NoSideEffects { - err = mc.write8Bit(mc.SP.ToUint16(), mc.A.ToUint8()) - if err != nil { - return nil, err - } + err = mc.write8Bit(mc.SP.ToUint16(), mc.A.ToUint8()) + if err != nil { + return nil, err } mc.SP.Add(255, false) @@ -685,11 +703,9 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res mc.A.Load(value) case "PHP": - if !mc.NoSideEffects { - err = mc.write8Bit(mc.SP.ToUint16(), mc.Status.ToUint8()) - if err != nil { - return nil, err - } + err = mc.write8Bit(mc.SP.ToUint16(), mc.Status.ToUint8()) + if err != nil { + return nil, err } mc.SP.Add(255, false) @@ -764,27 +780,21 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res mc.Status.Sign = mc.Y.IsNegative() case "STA": - if !mc.NoSideEffects { - err = mc.write8Bit(address, mc.A.ToUint8()) - if err != nil { - return nil, err - } + err = mc.write8Bit(address, mc.A.ToUint8()) + if err != nil { + return nil, err } case "STX": - if !mc.NoSideEffects { - err = mc.write8Bit(address, mc.X.ToUint8()) - if err != nil { - return nil, err - } + err = mc.write8Bit(address, mc.X.ToUint8()) + if err != nil { + return nil, err } case "STY": - if !mc.NoSideEffects { - err = mc.write8Bit(address, mc.Y.ToUint8()) - if err != nil { - return nil, err - } + err = mc.write8Bit(address, mc.Y.ToUint8()) + if err != nil { + return nil, err } case "INX": @@ -985,24 +995,20 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res // +1 cycle mc.endCycle() - if !mc.NoSideEffects { - // push MSB of PC onto stack, and decrement SP - // +1 cycle - err = mc.write8Bit(mc.SP.ToUint16(), uint8((mc.PC.ToUint16()&0xFF00)>>8)) - if err != nil { - return nil, err - } + // push MSB of PC onto stack, and decrement SP + // +1 cycle + err = mc.write8Bit(mc.SP.ToUint16(), uint8((mc.PC.ToUint16()&0xFF00)>>8)) + if err != nil { + return nil, err } mc.SP.Add(255, false) mc.endCycle() - if !mc.NoSideEffects { - // push LSB of PC onto stack, and decrement SP - // +1 cycle - err = mc.write8Bit(mc.SP.ToUint16(), uint8(mc.PC.ToUint16()&0x00FF)) - if err != nil { - return nil, err - } + // push LSB of PC onto stack, and decrement SP + // +1 cycle + err = mc.write8Bit(mc.SP.ToUint16(), uint8(mc.PC.ToUint16()&0x00FF)) + if err != nil { + return nil, err } mc.SP.Add(255, false) mc.endCycle() @@ -1047,15 +1053,69 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res } case "BRK": - // TODO: implement BRK + // push PC onto register (same effect as JSR) + err := mc.write8Bit(mc.SP.ToUint16(), uint8((mc.PC.ToUint16()&0xFF00)>>8)) + if err != nil { + return nil, err + } + mc.SP.Add(255, false) + mc.endCycle() + + err = mc.write8Bit(mc.SP.ToUint16(), uint8(mc.PC.ToUint16()&0x00FF)) + if err != nil { + return nil, err + } + mc.SP.Add(255, false) + mc.endCycle() + + // push status register (same effect as PHP) + err = mc.write8Bit(mc.SP.ToUint16(), mc.Status.ToUint8()) + if err != nil { + return nil, err + } + mc.SP.Add(255, false) + mc.endCycle() + + // set the break flag + mc.Status.Break = true + + // perform jump + brkAddress, err := mc.read16Bit(irqInterruptVector) + if err != nil { + return nil, err + } + if !mc.NoSideEffects { + mc.PC.Load(brkAddress) + } case "RTI": - // TODO: implement RTI + // pull status register (same effect as PLP) + mc.SP.Add(1, false) + mc.endCycle() + value, err = mc.read8Bit(mc.SP.ToUint16()) + if err != nil { + return nil, err + } + mc.Status.FromUint8(value) + + // pull program counter (same effect as RTS) + if !mc.NoSideEffects { + mc.SP.Add(1, false) + mc.endCycle() + rtiAddress, err := mc.read16Bit(mc.SP.ToUint16()) + if err != nil { + return nil, err + } + mc.SP.Add(1, false) + mc.PC.Load(rtiAddress) + mc.PC.Add(1, false) + mc.endCycle() + } // undocumented instructions case "dop": - // does nothing + // does nothing (2 byte nop) case "lax": mc.A.Load(value) @@ -1077,13 +1137,12 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*result.Instruction)) (*res // for RMW instructions: write altered value back to memory if defn.Effect == definitions.RMW { - if !mc.NoSideEffects { - err = mc.write8Bit(address, value) - if err != nil { - return nil, err + err = mc.write8Bit(address, value) + if err != nil { + return nil, err - } } + // +1 cycle mc.endCycle() } diff --git a/hardware/cpu/result/columnise.go b/hardware/cpu/result/columnise.go index 63d411fb..d01fb8c8 100644 --- a/hardware/cpu/result/columnise.go +++ b/hardware/cpu/result/columnise.go @@ -2,6 +2,8 @@ package result import "fmt" +// columnise forces the string into the given width. used for outputting +// disassembly into columns func columnise(s string, width int) string { if width > len(s) { t := make([]byte, width-len(s)) diff --git a/hardware/memory/cartridge.go b/hardware/memory/cartridge.go index 2f6f7848..18feafa2 100644 --- a/hardware/memory/cartridge.go +++ b/hardware/memory/cartridge.go @@ -1,28 +1,17 @@ package memory import ( + "fmt" "gopher2600/errors" "os" ) -// MissingCartridgeError returned by those functions that really require a -// cartridge to be inserted. -type MissingCartridgeError struct{} - -func (MissingCartridgeError) Error() string { - return "no cartridge attached" -} - -const bankSize = 4096 - // Cartridge defines the information and operations for a VCS cartridge type Cartridge struct { CPUBus Area AreaInfo - memory []uint8 - bank uint16 } // newCart is the preferred method of initialisation for the cartridges @@ -58,10 +47,9 @@ func (cart *Cartridge) Clear() { // Implementation of CPUBus.Read func (cart Cartridge) Read(address uint16) (uint8, error) { if len(cart.memory) == 0 { - return 0, new(MissingCartridgeError) + return 0, errors.NewGopherError(errors.CartridgeMissing) } oa := address - cart.origin - oa += cart.bank * bankSize return cart.memory[oa], nil } @@ -74,7 +62,7 @@ func (cart *Cartridge) Write(address uint16, data uint8) error { func (cart *Cartridge) Attach(filename string) error { cf, err := os.Open(filename) if err != nil { - return errors.NewGopherError(errors.CartridgeFileCannotOpen, err) + return errors.NewGopherError(errors.CartridgeFileError, err) } defer func() { _ = cf.Close() @@ -86,26 +74,77 @@ func (cart *Cartridge) Attach(filename string) error { return err } - // check that cartridge is of a supported size - // TODO: ensure that this is a complete and accurate check - if cfi.Size()%bankSize != 0 { - return errors.NewGopherError(errors.CartridgeFileCannotOpen, cfi.Size()) - } + switch cfi.Size() { + case 4096: + // this is a regular cartridge of 4096 bytes + // o Pitfall + // o Advenure + // o Yars Revenge + // o most 2600 cartridges... + // for this cartridge type we simply read the entire cartridge into a + // memory allocation of 4096 bytes. there is no need for further memory + // mapping. - // allocate enough memory for new cartridge - cart.memory = make([]uint8, cfi.Size()) + // allocate enough memory for new cartridge + cart.memory = make([]uint8, 4096) - // read cartridge - n, err := cf.Read(cart.memory) - if err != nil { - return err - } - if n != len(cart.memory) { - return errors.NewGopherError(errors.CartridgeFileError, errors.FileTruncated) - } + // read cartridge + n, err := cf.Read(cart.memory) + if err != nil { + return err + } + if n != 4096 { + return errors.NewGopherError(errors.CartridgeFileError, errors.FileTruncated) + } - // make sure we're pointing to the first bank - cart.bank = 0 + case 2048: + // this is a half-size cartridge of 2048 bytes + // o Combat + // o Dragster + // o Outlaw + // o Surround + // o mostly early cartrdiges + // for this cartridge type we simply read the cartridge twice into a + // memory space of 4096. there is no need for further memory mappping + // using this method; however, POKEing into cartridge space will also + // need to be performed twice. as this isn't normal 2600 behaviour + // though, I'm not too concerned. + + // allocate enough memory for new cartridge -- for now, allocate the + // full 4096 and read cartridge twice + cart.memory = make([]uint8, 4096) + + // read cartridge + n, err := cf.Read(cart.memory[:2048]) + if err != nil { + return err + } + if n != 2048 { + return errors.NewGopherError(errors.CartridgeFileError, errors.FileTruncated) + } + + // read cartridge again (into second half of memory) + cf.Seek(0, 0) + n, err = cf.Read(cart.memory[2048:]) + if err != nil { + return err + } + if n != 2048 { + return errors.NewGopherError(errors.CartridgeFileError, errors.FileTruncated) + } + + case 8192: + return errors.NewGopherError(errors.CartridgeUnsupported, "8192 bytes not yet supported") + + case 16384: + return errors.NewGopherError(errors.CartridgeUnsupported, "16384 bytes not yet supported") + + case 32768: + return errors.NewGopherError(errors.CartridgeUnsupported, "32768 bytes not yet supported") + + default: + return errors.NewGopherError(errors.CartridgeUnsupported, fmt.Sprintf("file size unrecognised %d bytes", cfi.Size())) + } return nil } @@ -114,15 +153,13 @@ func (cart *Cartridge) Attach(filename string) error { // attaches a bank of empty memory - for convenience of the debugger func (cart *Cartridge) Eject() { cart.memory = make([]uint8, 4096) - cart.bank = 0 } // Peek is the implementation of Memory.Area.Peek func (cart Cartridge) Peek(address uint16) (uint8, uint16, string, string, error) { if len(cart.memory) == 0 { - return 0, 0, "", "", new(MissingCartridgeError) + return 0, 0, "", "", errors.NewGopherError(errors.CartridgeMissing) } oa := address - cart.origin - oa += cart.bank * bankSize return cart.memory[oa], address, cart.Label(), "", nil } diff --git a/hardware/memory/memory_whitebox_test.go b/hardware/memory/memory_whitebox_test.go index ff6e561a..6a22885f 100644 --- a/hardware/memory/memory_whitebox_test.go +++ b/hardware/memory/memory_whitebox_test.go @@ -3,8 +3,4 @@ package memory import "testing" func TestCartridge(t *testing.T) { - cart := newCart() - if bankSize != cart.memtop-cart.origin+1 { - t.Errorf("cartridge bank size and/or memtop/origin incorrectly defined") - } } diff --git a/hardware/riot/riot.go b/hardware/riot/riot.go index 5eb8bd4d..f1b920e6 100644 --- a/hardware/riot/riot.go +++ b/hardware/riot/riot.go @@ -82,7 +82,7 @@ func (riot *RIOT) ReadRIOTMemory() { riot.timerINTIMvalue = value riot.timerCycles = 2 default: - fmt.Printf("unserviced RIOT register (%v)", register) + fmt.Printf("unserviced RIOT register (%v)\n", register) } // write value to INTIM straight-away diff --git a/hardware/tia/video/collisions.go b/hardware/tia/video/collisions.go index d31c3402..15d5a013 100644 --- a/hardware/tia/video/collisions.go +++ b/hardware/tia/video/collisions.go @@ -27,6 +27,8 @@ func (col *collisions) clear() { col.cxppmm = 0 } +// NOTE that collisions are detected in the video.Pixel() command + func (col *collisions) SetMemory(mem memory.ChipBus) { mem.ChipWrite(vcssymbols.CXM0P, col.cxm0p) mem.ChipWrite(vcssymbols.CXM1P, col.cxm1p) diff --git a/hardware/tia/video/video.go b/hardware/tia/video/video.go index a873ee63..0420e919 100644 --- a/hardware/tia/video/video.go +++ b/hardware/tia/video/video.go @@ -26,6 +26,18 @@ type Video struct { // okay because in all instances the delay is so short there is no chance // of another write being scheduled before the previous request has been // resolved + // TODO: I'm now not sure if the above statement is true. the BRK + // instruction for instance pushes values onto the stack very quickly. if + // the SP is inside register space then the BRK command can trigger these + // future writes. as it currently stands, the FutureWrite object will + // sanity-panic if a new event is scheduled before a previous event has + // completed. Should we remove the sanity-panic for the BRK instruction and + // allow write events to be lost? or should have a queue of FutureWrite + // events. The second option doesn't seem correct - from my rudimentary + // understanding of electronics that is. The sanity-panic isn't really + // needed of course, but I don't want to remove it completely just yet. The + // other conclusion we could mull over is that the schedule delay values + // are wrong. FutureWrite future } diff --git a/hardware/vcs.go b/hardware/vcs.go index f43da1ba..cadc6ea8 100644 --- a/hardware/vcs.go +++ b/hardware/vcs.go @@ -210,7 +210,7 @@ func (vcs *VCS) Reset() error { } err := vcs.MC.LoadPC(AddressReset) - if _, ok := err.(*memory.MissingCartridgeError); !ok { + if err != nil { return err } diff --git a/symbols/search.go b/symbols/search.go index ed0418b9..b6735c9d 100644 --- a/symbols/search.go +++ b/symbols/search.go @@ -5,12 +5,13 @@ import ( "strings" ) -// TableID is used to select and identify a symbol table -type TableID int +// TableType is used to select and identify a symbol table +// when searching +type TableType int // list of valid symbol tables const ( - UnspecifiedSymTable TableID = iota + UnspecifiedSymTable TableType = iota LocationSymTable ReadSymTable WriteSymTable @@ -18,7 +19,7 @@ const ( // SearchSymbol return the address of the supplied symbol. search is // case-insensitive -func (sym *Table) SearchSymbol(symbol string, table TableID) (TableID, string, uint16, error) { +func (sym *Table) SearchSymbol(symbol string, table TableType) (TableType, string, uint16, error) { symbolUpper := strings.ToUpper(symbol) if table == UnspecifiedSymTable || table == LocationSymTable { diff --git a/symbols/symbols.go b/symbols/symbols.go index 4f3b702d..118cdacc 100644 --- a/symbols/symbols.go +++ b/symbols/symbols.go @@ -26,7 +26,7 @@ func StandardSymbolTable() (*Table, error) { table := new(Table) table.ReadSymbols = vcssymbols.ReadSymbols table.WriteSymbols = vcssymbols.WriteSymbols - table.genMaxWidth() + table.genMaxWidths() return table, nil } @@ -48,12 +48,14 @@ func ReadSymbolsFile(cartridgeFilename string) (*Table, error) { table.WriteSymbols[k] = v } - table.genMaxWidth() + table.genMaxWidths() }() // try to open symbols file symFilename := cartridgeFilename ext := path.Ext(symFilename) + + // try to figure out the case of the file extension if ext == ".BIN" { symFilename = fmt.Sprintf("%s.SYM", symFilename[:len(symFilename)-len(ext)]) } else { @@ -73,7 +75,7 @@ func ReadSymbolsFile(cartridgeFilename string) (*Table, error) { _ = sf.Close() }() - // get file info + // get file info of symbols file sfi, err := sf.Stat() if err != nil { return table, errors.NewGopherError(errors.SymbolsFileError, err) @@ -125,11 +127,8 @@ func ReadSymbolsFile(cartridgeFilename string) (*Table, error) { return table, nil } -func (table *Table) genMaxWidth() { - // get max width of symbol in each list -- it may seem that we could keep - // track of these width values as we go along but we can't really because - // the overwriting of previous symbols, during the loops over - // vcsRead/WriteSymbols above, causes havoc +// find the widest location and read/write symbol +func (table *Table) genMaxWidths() { for _, s := range table.Locations { if len(s) > table.MaxLocationWidth { table.MaxLocationWidth = len(s) diff --git a/television/dummy.go b/television/dummy.go index 5c8d43a2..8023399f 100644 --- a/television/dummy.go +++ b/television/dummy.go @@ -6,6 +6,12 @@ import "gopher2600/errors" // for tools that don't need a television or related information at all. type DummyTV struct{ Television } +// NewDummyTV is the preferred method of initialisation for DummyTV - you can +// get away with an plain new(DummyTV) but this is probably more convenient +func NewDummyTV(tvType string, scale float32) (*DummyTV, error) { + return new(DummyTV), nil +} + // MachineInfoTerse (with DummyTV reciever) is the null implementation func (DummyTV) MachineInfoTerse() string { return "" diff --git a/gopher2600_test.go b/test/gopher2600_test.go similarity index 59% rename from gopher2600_test.go rename to test/gopher2600_test.go index eec13e4d..7e64a086 100644 --- a/gopher2600_test.go +++ b/test/gopher2600_test.go @@ -4,14 +4,19 @@ import ( "fmt" "gopher2600/hardware" "gopher2600/television" + "gopher2600/television/sdltv" "testing" ) -func BenchmarkCPU(b *testing.B) { +func BenchmarkSDLTV(b *testing.B) { var err error - tv := new(television.DummyTV) - if tv == nil { + tv, err := sdltv.NewSDLTV("NTSC", 1.0) + if err != nil { + panic(fmt.Errorf("error preparing television: %s", err)) + } + err = tv.RequestSetAttr(television.ReqSetVisibility, true) + if err != nil { panic(fmt.Errorf("error preparing television: %s", err)) } @@ -25,7 +30,9 @@ func BenchmarkCPU(b *testing.B) { panic(err) } - for steps := 1000000; steps >= 0; steps-- { + b.ResetTimer() + + for steps := 0; steps < b.N; steps++ { _, _, err = vcs.Step(hardware.StubVideoCycleCallback) if err != nil { panic(err) diff --git a/test/test.test b/test/test.test new file mode 100755 index 00000000..2e565a52 Binary files /dev/null and b/test/test.test differ