mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
o cpu
- implemented BRK and RTI instructions - not fully tested - tidied up when NoSideEffect is checked o debugger - fixed breakAndTrapCallback - fixed instruction result validity check crash caused when instruction step resulted a runtime error o cartridge - added support for half-size cartridges - tidied up error handling o various commentary cleanups/clarifications o move test files to test/ directory
This commit is contained in:
parent
4613bcc4cf
commit
49d429b6bf
16 changed files with 261 additions and 136 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,9 +26,9 @@ const (
|
|||
UnrecognisedAddress
|
||||
|
||||
// Cartridges
|
||||
CartridgeFileCannotOpen
|
||||
CartridgeFileError
|
||||
CartridgeInvalidSize
|
||||
CartridgeUnsupported
|
||||
CartridgeMissing
|
||||
|
||||
// TV
|
||||
UnknownTVRequest
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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)
|
BIN
test/test.test
Executable file
BIN
test/test.test
Executable file
Binary file not shown.
Loading…
Add table
Reference in a new issue