Working on SCMA-compatible assembler.

This commit is contained in:
Zellyn Hunter 2014-03-04 17:42:51 -08:00
parent d9fa2336d3
commit 42bde82f34
25 changed files with 2673 additions and 153 deletions

190
asm/asm.go Normal file
View File

@ -0,0 +1,190 @@
package asm
import (
"fmt"
"github.com/zellyn/go6502/asm/context"
"github.com/zellyn/go6502/asm/inst"
"github.com/zellyn/go6502/asm/lines"
)
type Flavor interface {
ParseInstr(Line lines.Line) (inst.I, error)
DefaultOrigin() (uint16, error)
context.Context
}
type Assembler struct {
Flavor Flavor
Opener lines.Opener
Insts []*inst.I
LastLabel string
}
func NewAssembler(flavor Flavor, opener lines.Opener) *Assembler {
return &Assembler{Flavor: flavor, Opener: opener}
}
// Load loads a new assembler file, deleting any previous data.
func (a *Assembler) Load(filename string) error {
context := lines.Context{Filename: filename}
ls, err := lines.NewFileLineSource(filename, context, a.Opener)
if err != nil {
return err
}
lineSources := []lines.LineSource{ls}
ifdefs := []bool{}
for len(lineSources) > 0 {
line, done, err := lineSources[0].Next()
if err != nil {
return err
}
if done {
lineSources = lineSources[1:]
continue
}
in, err := a.Flavor.ParseInstr(line)
if len(ifdefs) > 0 && !ifdefs[0] && in.Type != inst.TypeIfdefElse && in.Type != inst.TypeIfdefEnd {
// we're in an inactive ifdef branch
continue
}
if in.Label != "" && in.Label[0] != '.' {
a.Flavor.SetLastLabel(in.Label)
}
in.FixLabels(a.Flavor.GetLastLabel())
if err != nil {
return err
}
if _, err := a.passInst(&in, false, false); err != nil {
return err
}
switch in.Type {
case inst.TypeUnknown:
return fmt.Errorf("Unknown instruction: %s", line)
case inst.TypeMacroStart:
return fmt.Errorf("Macro start not (yet) implemented: %s", line)
case inst.TypeMacroCall:
return fmt.Errorf("Macro call not (yet) implemented: %s", line)
case inst.TypeIfdef:
if len(in.Exprs) == 0 {
panic(fmt.Sprintf("Ifdef got parsed with no expression: %s", line))
}
val, err := in.Exprs[0].Eval(a.Flavor)
if err != nil {
return fmt.Errorf("Cannot eval ifdef condition: %v", err)
}
ifdefs = append([]bool{val != 0}, ifdefs...)
case inst.TypeIfdefElse:
if len(ifdefs) == 0 {
return fmt.Errorf("Ifdef else branch encountered outside ifdef: %s", line)
}
ifdefs[0] = !ifdefs[0]
case inst.TypeIfdefEnd:
if len(ifdefs) == 0 {
return fmt.Errorf("Ifdef end encountered outside ifdef: %s", line)
}
ifdefs = ifdefs[1:]
case inst.TypeInclude:
subContext := lines.Context{Filename: in.TextArg, Parent: in.Line}
subLs, err := lines.NewFileLineSource(in.TextArg, subContext, a.Opener)
if err != nil {
return fmt.Errorf("error including file: %v", err)
}
lineSources = append([]lines.LineSource{subLs}, lineSources...)
continue // no need to append
case inst.TypeTarget:
return fmt.Errorf("Target not (yet) implemented: %s", line)
case inst.TypeSegment:
return fmt.Errorf("Segment not (yet) implemented: %s", line)
case inst.TypeEnd:
return nil
default:
}
a.Insts = append(a.Insts, &in)
}
return nil
}
// Clear out stuff that may be hanging around from the previous pass, set origin to default, etc.
func (a *Assembler) initPass() {
a.Flavor.SetLastLabel("") // No last label (yet)
a.Flavor.RemoveChanged() // Remove any variables whose value ever changed.
if org, err := a.Flavor.DefaultOrigin(); err == nil {
a.Flavor.SetAddr(org)
} else {
a.Flavor.ClearAddr()
}
}
// passInst performs a pass on a single instruction. Depending on
// whether the instruction width can be determined, it updates or
// clears the current address. If setWidth is true, it forces the
// instruction to decide its final width. If final is true, and the
// instruction cannot be finalized, it returns an error.
func (a *Assembler) passInst(in *inst.I, setWidth, final bool) (isFinal bool, err error) {
fmt.Printf("PLUGH: in.Compute(a.Flavor, true, true) on %s\n", in)
isFinal, err = in.Compute(a.Flavor, setWidth, final)
fmt.Printf("PLUGH: isFinal=%v, in.Final=%v, in.WidthKnown=%v, in.MinWidth=%v\n", isFinal, in.Final, in.WidthKnown, in.MinWidth)
if err != nil {
return false, err
}
if in.WidthKnown && in.MinWidth != in.MaxWidth {
panic(fmt.Sprintf("inst.I %s: WidthKnown=true, but MinWidth=%d, MaxWidth=%d", in, in.MinWidth, in.MaxWidth))
}
if in.WidthKnown && a.Flavor.AddrKnown() {
addr, _ := a.Flavor.GetAddr()
a.Flavor.SetAddr(addr + in.MinWidth)
} else {
a.Flavor.ClearAddr()
}
return isFinal, nil
}
// Pass performs an assembly pass. If setWidth is true, it causes all
// instructions to set their final width. If final is true, it returns
// an error for any instruction that cannot be finalized.
func (a *Assembler) Pass(setWidth, final bool) (isFinal bool, err error) {
fmt.Printf("PLUGH: Pass(%v, %v): %d instructions\n", setWidth, final, len(a.Insts))
setWidth = setWidth || final // final ⊢ setWidth
a.initPass()
isFinal = true
for _, in := range a.Insts {
instFinal, err := a.passInst(in, setWidth, final)
if err != nil {
return false, err
}
if final && !instFinal {
return false, fmt.Errorf("Cannot finalize instruction: %s", in)
}
fmt.Printf("PLUGH: instFinal=%v, in.Final=%v, in.WidthKnown=%v, in.MinWidth=%v\n", instFinal, in.Final, in.WidthKnown, in.MinWidth)
isFinal = isFinal && instFinal
}
return isFinal, nil
}
// RawBytes returns the raw bytes, sequentially. Intended for testing,
// the return value gives no indication of address changes.
func (a *Assembler) RawBytes() ([]byte, error) {
result := []byte{}
for _, in := range a.Insts {
if !in.Final {
return []byte{}, fmt.Errorf("cannot finalize value: %s", in)
}
result = append(result, in.Data...)
}
return result, nil
}
func (a *Assembler) Reset() {
a.Insts = nil
a.LastLabel = ""
}

63
asm/asm.org Normal file
View File

@ -0,0 +1,63 @@
| Concept | SCSC | AS65 | Merlin |
|-------------------------------+------------------+------------------+--------|
| origin | .OR | org | |
| | | | |
| target address | .TA | | |
| | | | |
| include | .IN | | |
| | | | |
| comment | * | ; | |
| | >1 space | | |
| | | | |
| label | at left | | |
| | | | |
| equ | .EQ | = | |
| | | equ | |
| | | | |
| binary | | %00000001 | |
| | | | |
| macro | .MA macro_name | macro_name macro | |
| | .EM | endm | |
| | | | |
| if | .DO | if foo = 1 | |
| | .ELSE | endif | |
| | [.ELSE] (toggle) | | |
| | .FIN | | |
| data | | | |
| - word (LE) | .DA expr | | |
| - byte (LSB) | .DA #expr | | |
| - byte (MSB) | .DA /expr | | |
| | | | |
| ascii string | | | |
| - normal use | .AS "foo" | | |
| - arb. delimeter | .AS dfood | | |
| - high bit set | .AS -"foo" | | |
| - last high bit opposite | .AT "foo" | | |
| | | | |
| hex string | .HS | | |
| | | | |
| reserve space (block storage) | .BS <bytes> | | |
| | | | |
Things to ignore
| Concept | SCSC | AS65 |
|---------+------------------------+------|
| title | .TI <lines/page>,title | |
| file | .TF filename | |
* AS65 notes
https://github.com/Klaus2m5/6502_65C02_functional_tests/blob/master/6502_functional_test.a65
* SCSC notes
.DO -- Conditional Assembly 5-9
.ELSE -- Conditional Assembly 5-9
.FIN -- Conditional Assembly 5-9
.MA -- Macro Definition 5-11
.EM -- End of Macro 5-11

97
asm/context/context.go Normal file
View File

@ -0,0 +1,97 @@
package context
import "fmt"
type Context interface {
Set(name string, value uint16)
Get(name string) (uint16, bool)
SetAddr(uint16)
ClearAddr()
GetAddr() (uint16, bool)
Zero() (uint16, error) // type ZeroFunc
RemoveChanged()
AddrKnown() bool
GetLastLabel() string
SetLastLabel(string)
Clear()
}
type SimpleContext struct {
symbols map[string]symbolValue
addr int32
lastLabel string
}
type symbolValue struct {
v uint16
changed bool // Did the value ever change?
}
func (sc *SimpleContext) fix() {
if sc.symbols == nil {
sc.symbols = make(map[string]symbolValue)
}
}
func (sc *SimpleContext) Zero() (uint16, error) {
return 0, fmt.Errorf("Not implemented: context.SimpleContext.Zero()")
}
func (sc *SimpleContext) Get(name string) (uint16, bool) {
if name == "*" {
return sc.GetAddr()
}
sc.fix()
s, found := sc.symbols[name]
return s.v, found
}
func (sc *SimpleContext) ClearAddr() {
sc.addr = -1
}
func (sc *SimpleContext) SetAddr(addr uint16) {
sc.addr = int32(addr)
}
func (sc *SimpleContext) GetAddr() (uint16, bool) {
if sc.addr == -1 {
return 0, false
}
return uint16(sc.addr), true
}
func (sc *SimpleContext) AddrKnown() bool {
return sc.addr != -1
}
func (sc *SimpleContext) Set(name string, value uint16) {
sc.fix()
s, found := sc.symbols[name]
if found && s.v != value {
s.changed = true
}
s.v = value
sc.symbols[name] = s
}
func (sc *SimpleContext) RemoveChanged() {
sc.fix()
for n, s := range sc.symbols {
if s.changed {
delete(sc.symbols, n)
}
}
}
func (sc *SimpleContext) GetLastLabel() string {
return sc.lastLabel
}
func (sc *SimpleContext) SetLastLabel(l string) {
sc.lastLabel = l
}
func (sc *SimpleContext) Clear() {
sc.symbols = make(map[string]symbolValue)
}

13
asm/doc.go Normal file
View File

@ -0,0 +1,13 @@
/*
Package asm provides routines for assembling 6502 code. It currently
emulates the S-C Macro Assembler. The goal is to support (at least)
as65 and Merlin assembly files too.
Once those three (two ancient, one modern) are complete, adding
additional flavors should be straightforward.
TODO(zellyn): make errors return line and character position.
TODO(zellyn): scma requires .EQ and .BS to have known values. Is this universal?
*/
package asm

210
asm/expr/expression.go Normal file
View File

@ -0,0 +1,210 @@
package expr
import (
"errors"
"fmt"
"github.com/zellyn/go6502/asm/context"
)
type UnknownLabelError struct {
Label string
}
func (e UnknownLabelError) Error() string {
return fmt.Sprintf(`unknown label: "%s"`, e.Label)
}
type Operator int
const (
OpUnknown Operator = iota
OpLeaf // No op - this is a leaf node
OpPlus // add
OpMinus // subtract / negate (with Right==nil)
OpMul // multiply
OpDiv // divide
OpLsb // Low byte
OpMsb // High byte
OpLt // Less than
OpGt // Greater than
OpEq // Equal to
OpByte // A single byte of storage
)
var OpStrings = map[Operator]string{
OpPlus: "+",
OpMinus: "-",
OpMul: "*",
OpDiv: "/",
OpLsb: "lsb",
OpMsb: "msb",
OpByte: "byte",
OpLt: "<",
OpGt: ">",
OpEq: "=",
}
type E struct {
Left *E
Right *E
Op Operator
Text string
Val uint16
}
func (e E) String() string {
switch e.Op {
case OpLeaf:
if e.Text != "" {
return e.Text
}
return fmt.Sprintf("$%04x", e.Val)
case OpPlus, OpMinus, OpMul, OpDiv, OpLsb, OpMsb, OpByte, OpLt, OpGt, OpEq:
if e.Right != nil {
return fmt.Sprintf("(%s %s %s)", OpStrings[e.Op], *e.Left, *e.Right)
}
return fmt.Sprintf("(%s %s)", OpStrings[e.Op], *e.Left)
case OpUnknown:
return "?"
default:
panic(fmt.Sprintf("Unprintable op type: %v", e.Op))
}
}
func (e *E) Equal(o *E) bool {
if e == nil || o == nil {
return e == nil && o == nil
}
if e.Op != o.Op {
return false
}
if e.Text != o.Text {
return false
}
if e.Val != o.Val {
return false
}
return e.Left.Equal(o.Left) && e.Right.Equal(o.Right)
}
// Width returns the width in bytes of an expression. It'll be two for anything except
// expressions that start with Lsb or Msb operators.
func (e *E) Width() uint16 {
switch e.Op {
case OpLsb, OpMsb, OpByte:
return 1
}
return 2
}
func (e *E) Eval(ctx context.Context) (uint16, error) {
if e == nil {
return 0, errors.New("cannot Eval() nil expression")
}
switch e.Op {
case OpLeaf:
if e.Text == "" {
return e.Val, nil
}
if val, ok := ctx.Get(e.Text); ok {
return val, nil
}
return 0, UnknownLabelError{Label: e.Text}
case OpMinus:
l, err := e.Left.Eval(ctx)
if err != nil {
return 0, err
}
if e.Right == nil {
return -l, nil
}
r, err := e.Right.Eval(ctx)
if err != nil {
return 0, err
}
return l - r, nil
case OpMsb, OpLsb:
l, err := e.Left.Eval(ctx)
if err != nil {
return 0, err
}
if e.Op == OpMsb {
return l >> 8, nil
}
return l & 0xff, nil
case OpByte:
return e.Val, nil
case OpPlus, OpMul, OpDiv, OpLt, OpGt, OpEq:
l, err := e.Left.Eval(ctx)
if err != nil {
return 0, err
}
r, err := e.Right.Eval(ctx)
if err != nil {
return 0, err
}
switch e.Op {
case OpPlus:
return l + r, nil
case OpMul:
return l * r, nil
case OpLt:
if l < r {
return 1, nil
}
return 0, nil
case OpGt:
if l > r {
return 1, nil
}
return 0, nil
case OpEq:
if l == r {
return 1, nil
}
return 0, nil
case OpDiv:
if r == 0 {
return ctx.Zero()
}
return l / r, nil
}
panic(fmt.Sprintf("bad code - missing switch case: %d", e.Op))
}
panic(fmt.Sprintf("unknown operator type: %d", e.Op))
}
// CheckedEval calls Eval, but also turns UnknownLabelErrors into labelMissing booleans.
func (e *E) CheckedEval(ctx context.Context) (val uint16, labelMissing bool, err error) {
val, err = e.Eval(ctx)
switch err.(type) {
case nil:
return val, false, nil
case UnknownLabelError:
return val, true, err
}
return val, false, err
}
// FixLabels attempts to turn .1 into LAST_LABEL.1
func (e *E) FixLabels(last string) error {
if e.Text != "" && e.Text[0] == '.' {
if last == "" {
return fmt.Errorf("Reference to sub-label '%s' before full label.", e.Text)
}
e.Text = last + e.Text
}
if e.Left != nil {
if err := e.Left.FixLabels(last); err != nil {
return err
}
}
if e.Right != nil {
return e.Right.FixLabels(last)
}
return nil
}

View File

@ -0,0 +1,50 @@
package expr
import (
"testing"
)
func TestExpressionString(t *testing.T) {
tests := []struct {
expr E
want string
}{
{
E{},
"?",
},
{
E{Op: OpLeaf, Text: "*"},
"*",
},
{
E{
Op: OpPlus,
Left: &E{Op: OpLeaf, Text: "Label"},
Right: &E{Op: OpLeaf, Val: 42},
},
"(+ Label $002a)",
},
{
E{
Op: OpMinus,
Left: &E{Op: OpLeaf, Text: "Label"},
Right: &E{Op: OpLeaf, Val: 42},
},
"(- Label $002a)",
},
{
E{
Op: OpMinus,
Left: &E{Op: OpLeaf, Val: 42},
},
"(- $002a)",
},
}
for i, tt := range tests {
got := tt.expr.String()
if got != tt.want {
t.Errorf(`%d: want String(expr)="%s"; got "%s"`, i, tt.want, got)
}
}
}

33
asm/flavors/as65/as65.go Normal file
View File

@ -0,0 +1,33 @@
package as65
import (
"errors"
"github.com/zellyn/go6502/asm/context"
"github.com/zellyn/go6502/asm/inst"
"github.com/zellyn/go6502/asm/lines"
)
// AS65 implements the AS65-compatible assembler flavor.
// See http://www.kingswood-consulting.co.uk/assemblers/
type AS65 struct {
context.SimpleContext
}
func New() *AS65 {
return &AS65{}
}
// Parse an entire instruction, or return an appropriate error.
func (a *AS65) ParseInstr(line lines.Line) (inst.I, error) {
return inst.I{}, nil
}
func (a *AS65) Zero() (uint16, error) {
return 0, errors.New("Division by zero.")
}
func (a *AS65) DefaultOrigin() (uint16, error) {
return 0, nil
}

View File

@ -0,0 +1,199 @@
package flavors
import (
"encoding/hex"
"os"
"strings"
"testing"
"github.com/zellyn/go6502/asm"
"github.com/zellyn/go6502/asm/flavors/scma"
"github.com/zellyn/go6502/asm/lines"
)
func TestMultiline(t *testing.T) {
o := lines.NewTestOpener()
ss := asm.NewAssembler(scma.New(), o)
// aa := asm.NewAssembler(as65.New(), o)
// mm := asm.NewAssembler(merlin.New(), o)
tests := []struct {
a *asm.Assembler // assembler
name string // name
i []string // main file: lines
ii map[string][]string // other files: lines
b string // bytes, expected
active bool
}{
// We cannot determine L2, so we go wide.
{ss, "Unknown label: wide", []string{
"L1 LDA L2-L1",
"L2 NOP",
}, nil, "ad0300ea", false},
// Sub-labels
{ss, "Sublabels", []string{
"L1 BEQ .1",
".1 NOP",
"L2 BEQ .2",
".1 NOP",
".2 NOP",
}, nil, "f000eaf001eaea", false},
// Includes: one level deep
{ss, "Include A", []string{
" BEQ OVER",
" .IN SUBFILE1",
"OVER NOP",
}, map[string][]string{
"SUBFILE1": {
" LDA #$2a",
},
}, "f002a92aea", false},
// Ifdefs: simple at first
{ss, "Ifdef A", []string{
"L1 .EQ $17",
" .DO L1>$16",
" LDA #$01",
" .ELSE",
" LDA #$02",
" .FIN",
" NOP",
}, nil, "a901ea", true},
// Ifdefs: else part
{ss, "Ifdef B", []string{
"L1 .EQ $16",
" .DO L1>$16",
" LDA #$01",
" .ELSE",
" LDA #$02",
" .FIN",
" NOP",
}, nil, "a902ea", true},
// Ifdefs: multiple else, true
{ss, "Ifdef C", []string{
"L1 .EQ $17",
" .DO L1>$16",
" LDA #$01",
" .ELSE",
" LDA #$02",
" .ELSE",
" LDA #$03",
" .ELSE",
" LDA #$04",
" .FIN",
" NOP",
}, nil, "a901a903ea", true},
// Ifdefs: multiple else, false
{ss, "Ifdef D", []string{
"L1 .EQ $16",
" .DO L1>$16",
" LDA #$01",
" .ELSE",
" LDA #$02",
" .ELSE",
" LDA #$03",
" .ELSE",
" LDA #$04",
" .FIN",
" NOP",
}, nil, "a902a904ea", true},
// Ifdef based on org, true
{ss, "Ifdef/org A", []string{
" .OR $1000",
" LDA #$01",
" .DO *=$1002",
" LDA #$02",
" .ELSE",
" LDA #$03",
" .FIN",
" NOP",
}, nil, "a901a902ea", true},
// Ifdef based on org, false
{ss, "Ifdef/org B", []string{
" .OR $1000",
" LDA #$01",
" .DO *=$1003",
" LDA #$02",
" .ELSE",
" LDA #$03",
" .FIN",
" NOP",
}, nil, "a901a903ea", true},
}
for i, tt := range tests {
if !tt.active {
continue
}
tt.a.Reset()
o.Clear()
o["TESTFILE"] = strings.Join(tt.i, "\n")
for k, v := range tt.ii {
o[k] = strings.Join(v, "\n")
}
if err := tt.a.Load("TESTFILE"); err != nil {
t.Errorf(`%d("%s"): tt.a.Load("TESTFILE") failed: %s`, i, tt.name, err)
continue
}
if _, err := tt.a.Pass(true, false); err != nil {
t.Errorf(`%d("%s"): tt.a.Pass(true, false) failed: %s`, i, tt.name, err)
continue
}
isFinal, err := tt.a.Pass(true, true)
if err != nil {
t.Errorf(`%d("%s"): tt.a.Pass(true, true) failed: %s`, i, tt.name, err)
continue
}
if !isFinal {
t.Errorf(`%d("%s"): tt.a.Pass(true, true) couldn't finalize`, i, tt.name)
continue
}
bb, err := tt.a.RawBytes()
if err != nil {
t.Errorf(`%d("%s"): tt.a.RawBytes() failed: %s`, i, tt.name, err)
continue
}
hx := hex.EncodeToString(bb)
if hx != tt.b {
t.Errorf(`%d("%s"): tt.a.RawBytes()=[%s]; want [%s]`, i, tt.name, hx, tt.b)
continue
}
}
}
func TestApplesoftBasic(t *testing.T) {
if err := os.Chdir("../../../goapple2/source/applesoft"); err != nil {
t.Fatal(err)
}
var o lines.OsOpener
ss := asm.NewAssembler(scma.New(), o)
ss.Reset()
if err := ss.Load("S.ACF"); err != nil {
t.Fatalf(`ss.Load("S.ACF") failed: %s`, err)
}
if _, err := ss.Pass(true, false); err != nil {
t.Fatalf(`ss.Pass(true, false) failed: %s`, err)
}
isFinal, err := ss.Pass(true, true)
if err != nil {
t.Fatalf(`ss.Pass(true, true) failed: %s`, err)
}
if !isFinal {
t.Fatalf(`ss.Pass(true, true) couldn't finalize`)
}
bb, err := ss.RawBytes()
if err != nil {
t.Fatalf(`ss.RawBytes() failed: %s`, err)
}
_ = bb
}

View File

@ -0,0 +1,142 @@
package common
import (
"fmt"
"github.com/zellyn/go6502/asm/context"
"github.com/zellyn/go6502/asm/inst"
"github.com/zellyn/go6502/opcodes"
)
// DecodeOp contains the common code that decodes an Opcode, once we
// have fully parsed it.
func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect bool, xy rune) (inst.I, error) {
i := inst.I{}
ex := in.Exprs[0]
// At this point we've parsed the Opcode. Let's see if it makes sense.
if indirect {
switch xy {
case 'x':
op, ok := summary.OpForMode(opcodes.MODE_INDIRECT_X)
if !ok {
return i, fmt.Errorf("%s doesn't support indexed indirect (addr,X) mode", in.Command)
}
in.Op = op.Byte
in.WidthKnown = true
in.MinWidth = 2
in.MaxWidth = 2
in.Mode = opcodes.MODE_INDIRECT_X
return in, nil
case 'y':
op, ok := summary.OpForMode(opcodes.MODE_INDIRECT_Y)
if !ok {
return i, fmt.Errorf("%s doesn't support indirect indexed (addr),Y mode", in.Command)
}
in.WidthKnown = true
in.MinWidth = 2
in.MaxWidth = 2
in.Mode = opcodes.MODE_INDIRECT_Y
in.Op = op.Byte
return in, nil
default:
op, ok := summary.OpForMode(opcodes.MODE_INDIRECT)
if !ok {
return i, fmt.Errorf("%s doesn't support indirect (addr) mode", in.Command)
}
in.Op = op.Byte
in.WidthKnown = true
in.MinWidth = 3
in.MaxWidth = 3
in.Mode = opcodes.MODE_INDIRECT
return in, nil
}
}
// Branch
if summary.Modes == opcodes.MODE_RELATIVE {
op, ok := summary.OpForMode(opcodes.MODE_RELATIVE)
if !ok {
panic(fmt.Sprintf("opcode error: %s has no MODE_RELATIVE opcode", in.Command))
}
in.Op = op.Byte
in.WidthKnown = true
in.MinWidth = 2
in.MaxWidth = 2
in.Mode = opcodes.MODE_RELATIVE
return in, nil
}
// No ,X or ,Y, and width is forced to 1-byte: immediate mode.
if xy == '-' && ex.Width() == 1 && summary.AnyModes(opcodes.MODE_IMMEDIATE) {
op, ok := summary.OpForMode(opcodes.MODE_IMMEDIATE)
if !ok {
panic(fmt.Sprintf("opcode error: %s has no MODE_IMMEDIATE opcode", in.Command))
}
in.Op = op.Byte
in.WidthKnown = true
in.MinWidth = 2
in.MaxWidth = 2
in.Mode = opcodes.MODE_IMMEDIATE
return in, nil
}
var zp, wide opcodes.AddressingMode
var zpS, wideS string
switch xy {
case 'x':
zp, wide = opcodes.MODE_ZP_X, opcodes.MODE_ABS_X
zpS, wideS = "ZeroPage,X", "Absolute,X"
case 'y':
zp, wide = opcodes.MODE_ZP_Y, opcodes.MODE_ABS_Y
zpS, wideS = "ZeroPage,Y", "Absolute,Y"
default:
zp, wide = opcodes.MODE_ZP, opcodes.MODE_ABSOLUTE
zpS, wideS = "ZeroPage", "Absolute"
}
opWide, wideOk := summary.OpForMode(wide)
opZp, zpOk := summary.OpForMode(zp)
if !summary.AnyModes(zp | wide) {
return i, fmt.Errorf("%s opcode doesn't support %s or %s modes.", zpS, wideS)
}
if !summary.AnyModes(zp) {
if !wideOk {
panic(fmt.Sprintf("opcode error: %s has no %s opcode", in.Command, wideS))
}
in.Op = opWide.Byte
in.WidthKnown = true
in.MinWidth = 3
in.MaxWidth = 3
in.Mode = wide
return in, nil
}
if !summary.AnyModes(wide) {
if !zpOk {
panic(fmt.Sprintf("opcode error: %s has no %s opcode", in.Command, zpS))
}
in.Op = opZp.Byte
in.WidthKnown = true
in.MinWidth = 2
in.MaxWidth = 2
in.Mode = zp
return in, nil
}
// Okay, we don't know whether it's wide or narrow: store enough info for either.
if !zpOk {
panic(fmt.Sprintf("opcode error: %s has no %s opcode", in.Command, zpS))
}
if !wideOk {
panic(fmt.Sprintf("opcode error: %s has no %s opcode", in.Command, wideS))
}
in.Op = opWide.Byte
in.ZeroOp = opZp.Byte
in.Mode = wide
in.ZeroMode = zp
in.MinWidth = 2
in.MaxWidth = 3
return in, nil
}

1
asm/flavors/flavors.go Normal file
View File

@ -0,0 +1 @@
package flavors

View File

@ -0,0 +1,34 @@
package merlin
import (
"errors"
"github.com/zellyn/go6502/asm/context"
"github.com/zellyn/go6502/asm/inst"
"github.com/zellyn/go6502/asm/lines"
)
// Merlin implements the Merlin-compatible assembler flavor.
// See http://en.wikipedia.org/wiki/Merlin_(assembler) and
// http://www.apple-iigs.info/doc/fichiers/merlin816.pdf
type Merlin struct {
context.SimpleContext
}
func New() *Merlin {
return &Merlin{}
}
// Parse an entire instruction, or return an appropriate error.
func (a *Merlin) ParseInstr(line lines.Line) (inst.I, error) {
return inst.I{}, nil
}
func (a *Merlin) Zero() (uint16, error) {
return 0, errors.New("Division by zero.")
}
func (a *Merlin) DefaultOrigin() (uint16, error) {
return 0x8000, nil
}

518
asm/flavors/scma/scma.go Normal file
View File

@ -0,0 +1,518 @@
package scma
import (
"encoding/hex"
"fmt"
"strconv"
"strings"
"github.com/zellyn/go6502/asm/context"
"github.com/zellyn/go6502/asm/expr"
"github.com/zellyn/go6502/asm/flavors/common"
"github.com/zellyn/go6502/asm/inst"
"github.com/zellyn/go6502/asm/lines"
"github.com/zellyn/go6502/opcodes"
)
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
const digits = "0123456789"
const hexdigits = digits + "abcdefABCDEF"
const labelChars = letters + digits + ".:"
const whitespace = " \t"
const cmdChars = letters + "."
const fileChars = letters + digits + "."
const operatorChars = "+-*/<>="
type directiveInfo struct {
Type inst.Type
Func func(inst.I, *lines.Parse) (inst.I, error)
}
// SCMA implements the S-C Macro Assembler-compatible assembler flavor.
// See http://www.txbobsc.com/scsc/ and http://stjarnhimlen.se/apple2/
type SCMA struct {
directives map[string]directiveInfo
operators map[string]expr.Operator
context.SimpleContext
}
func New() *SCMA {
s := &SCMA{}
s.directives = map[string]directiveInfo{
".IN": {inst.TypeInclude, s.parseInclude},
".OR": {inst.TypeOrg, s.parseAddress},
".TA": {inst.TypeTarget, s.parseNotImplemented},
".TF": {inst.TypeNone, nil},
".EN": {inst.TypeEnd, s.parseNoArgDir},
".EQ": {inst.TypeEqu, s.parseEquate},
".DA": {inst.TypeData, s.parseData},
".HS": {inst.TypeData, s.parseHexString},
".AS": {inst.TypeData, s.parseAscii},
".AT": {inst.TypeData, s.parseAscii},
".BS": {inst.TypeBlock, s.parseBlockStorage},
".TI": {inst.TypeNone, nil},
".LIST": {inst.TypeNone, nil},
".PG": {inst.TypeNone, nil},
".DO": {inst.TypeIfdef, s.parseDo},
".ELSE": {inst.TypeIfdefElse, s.parseNoArgDir},
".FIN": {inst.TypeIfdefEnd, s.parseNoArgDir},
".MA": {inst.TypeMacroStart, s.parseNoArgDir},
".EM": {inst.TypeMacroEnd, s.parseNoArgDir},
".US": {inst.TypeNone, s.parseNotImplemented},
}
s.operators = map[string]expr.Operator{
"*": expr.OpMul,
"/": expr.OpDiv,
"+": expr.OpPlus,
"-": expr.OpMinus,
"<": expr.OpLt,
">": expr.OpGt,
"=": expr.OpEq,
}
return s
}
// Parse an entire instruction, or return an appropriate error.
func (a *SCMA) ParseInstr(line lines.Line) (inst.I, error) {
lp := line.Parse
in := inst.I{Line: &line}
// Lines that start with a digit are considered to have a declared line number.
if lp.AcceptRun(digits) {
s := lp.Emit()
if len(s) != 4 {
return inst.I{}, fmt.Errorf("Line number must be exactly 4 digits: %s", s)
}
if !lp.Consume(" ") && lp.Peek() != lines.Eol {
return inst.I{}, fmt.Errorf("Line number (%s) followed by non-space", s)
}
i, err := strconv.ParseUint(s, 10, 16)
if err != nil {
return inst.I{}, fmt.Errorf("Invalid line number: %s: %s", s, err)
}
in.DeclaredLine = uint16(i)
}
// Empty line or comment
trimmed := strings.TrimSpace(lp.Rest())
if trimmed == "" || trimmed[0] == '*' {
in.Type = inst.TypeNone
return in, nil
}
// See if we have a label at the start
if lp.AcceptRun(labelChars) {
in.Label = lp.Emit()
}
// Ignore whitespace at the start or after the label.
lp.IgnoreRun(whitespace)
if lp.Peek() == lines.Eol {
in.Type = inst.TypeNone
return in, nil
}
return a.parseCmd(in, lp)
}
func (a *SCMA) Zero() (uint16, error) {
return uint16(0xffff), nil
}
func (a *SCMA) DefaultOrigin() (uint16, error) {
return 0x0800, nil
}
// parseCmd parses the "command" part of an instruction: we expect to be
// looking at a non-whitespace character.
func (a *SCMA) parseCmd(in inst.I, lp *lines.Parse) (inst.I, error) {
if lp.Consume(">") {
return a.parseMacroCall(in, lp)
}
if !lp.AcceptRun(cmdChars) {
c := lp.Next()
return inst.I{}, fmt.Errorf("Expecting instruction, found '%c' (%d)", c, c)
}
in.Command = lp.Emit()
if dir, ok := a.directives[in.Command]; ok {
in.Type = dir.Type
if dir.Func == nil {
return in, nil
}
return dir.Func(in, lp)
}
if summary, ok := opcodes.ByName[in.Command]; ok {
in.Type = inst.TypeOp
return a.parseOpArgs(in, lp, summary)
}
return inst.I{}, fmt.Errorf(`Not implemented: "%s": `, in.Command, in.Line)
}
// parseMacroCall parses a macro call. We expect to be looking at a the
// first character of the macro name.
func (a *SCMA) parseMacroCall(in inst.I, lp *lines.Parse) (inst.I, error) {
in.Type = inst.TypeMacroCall
if !lp.AcceptRun(cmdChars) {
c := lp.Next()
return inst.I{}, fmt.Errorf("Expecting macro name, found '%c' (%d)", c, c)
}
in.Command = lp.Emit()
lp.Consume(whitespace)
for {
s, err := a.parseMacroArg(lp)
if err != nil {
return inst.I{}, err
}
in.MacroArgs = append(in.MacroArgs, s)
if !lp.Consume(",") {
break
}
}
return in, nil
}
// parseMacroArg parses a single macro argument. We expect to be looking at the first
// character of a macro argument.
func (a *SCMA) parseMacroArg(lp *lines.Parse) (string, error) {
if lp.Peek() == '"' {
return a.parseQuoted(lp)
}
lp.AcceptUntil(whitespace + ",")
return lp.Emit(), nil
}
// parseQuoted parses a single quoted string macro argument. We expect
// to be looking at the first quote.
func (a *SCMA) parseQuoted(lp *lines.Parse) (string, error) {
if !lp.Consume(`"`) {
panic(fmt.Sprintf("parseQuoted called not looking at a quote"))
}
for {
lp.AcceptUntil(`"`)
// We're done, unless there's an escaped quote
if !lp.AcceptString(`""`) {
break
}
}
s := lp.Emit()
if !lp.Consume(`"`) {
c := lp.Peek()
return "", fmt.Errorf("Expected closing quote; got %s", c)
}
c := lp.Peek()
if c != ',' && c != ' ' && c != lines.Eol && c != '\t' {
return "", fmt.Errorf("Unexpected char after quoted string: '%s'", c)
}
return strings.Replace(s, `""`, `"`, -1), nil
}
// parseOpArgs parses the arguments to an assembly op. We expect to be looking at the first
// non-op character (probably whitespace)
func (a *SCMA) parseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary) (inst.I, error) {
i := inst.I{}
// MODE_IMPLIED: we don't really care what comes next: it's a comment.
if summary.Modes == opcodes.MODE_IMPLIED {
op := summary.Ops[0]
in.Data = []byte{op.Byte}
in.WidthKnown = true
in.MinWidth = 1
in.MaxWidth = 1
in.Final = true
in.Mode = opcodes.MODE_IMPLIED
return in, nil
}
// Nothing else on the line? Must be MODE_A
lp.Consume(whitespace)
if lp.Consume(whitespace) || lp.Peek() == lines.Eol {
if !summary.AnyModes(opcodes.MODE_A) {
return i, fmt.Errorf("%s with no arguments", in.Command)
}
op, ok := summary.OpForMode(opcodes.MODE_A)
if !ok {
panic(fmt.Sprintf("%s doesn't support accumulator mode", in.Command))
}
in.Data = []byte{op.Byte}
in.WidthKnown = true
in.MinWidth = 1
in.MaxWidth = 1
in.Final = true
in.Mode = opcodes.MODE_A
return in, nil
}
indirect := lp.Consume("(")
if indirect && !summary.AnyModes(opcodes.MODE_INDIRECT_ANY) {
return i, fmt.Errorf("%s doesn't support any indirect modes", in.Command)
}
xy := '-'
expr, err := a.parseExpression(lp)
if err != nil {
return i, err
}
in.Exprs = append(in.Exprs, expr)
comma := lp.Consume(",")
if comma {
if lp.Consume("xX") {
xy = 'x'
} else if lp.Consume("yY") {
if indirect {
return i, fmt.Errorf(",Y unexpected inside parens")
}
xy = 'y'
} else {
return i, fmt.Errorf("X or Y expected after comma")
}
}
comma2 := false
if indirect {
if !lp.Consume(")") {
return i, fmt.Errorf("Expected closing paren")
}
comma2 = lp.Consume(",")
if comma2 {
if comma {
return i, fmt.Errorf("Cannot have ,X or ,Y twice.")
}
if !lp.Consume("yY") {
return i, fmt.Errorf("Only ,Y can follow parens.")
}
xy = 'y'
}
}
return common.DecodeOp(a, in, summary, indirect, xy)
}
func (a *SCMA) parseAddress(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace)
expr, err := a.parseExpression(lp)
if err != nil {
return inst.I{}, err
}
in.Exprs = append(in.Exprs, expr)
in.WidthKnown = true
in.MinWidth = 0
in.MaxWidth = 0
in.Final = true
return in, nil
}
func (a *SCMA) parseAscii(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace)
var invert, invertLast byte
if lp.Consume("-") {
invert = 0x80
}
if in.Command == ".AT" {
invertLast = 0x80
}
delim := lp.Next()
if delim == lines.Eol || strings.IndexRune(whitespace, delim) >= 0 {
return inst.I{}, fmt.Errorf("%s expects delimeter, found '%s'", in.Command, delim)
}
lp.Ignore()
lp.AcceptUntil(string(delim))
delim2 := lp.Next()
if delim != delim2 {
return inst.I{}, fmt.Errorf("%s: expected closing delimeter '%s'; got '%s'", in.Command, delim, delim2)
}
lp.Backup()
in.Data = []byte(lp.Emit())
for i := range in.Data {
in.Data[i] ^= invert
if i == len(in.Data)-1 {
in.Data[i] ^= invertLast
}
}
return in, nil
}
func (a *SCMA) parseBlockStorage(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace)
ex, err := a.parseExpression(lp)
if err != nil {
return inst.I{}, err
}
in.Exprs = append(in.Exprs, ex)
return in, nil
}
func (a *SCMA) parseData(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace)
for {
ex, err := a.parseExpression(lp)
if err != nil {
return inst.I{}, err
}
in.Exprs = append(in.Exprs, ex)
if !lp.Consume(",") {
break
}
}
return in, nil
}
func (a *SCMA) parseDo(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace)
expr, err := a.parseExpression(lp)
if err != nil {
return inst.I{}, err
}
in.Exprs = append(in.Exprs, expr)
in.WidthKnown = true
in.MinWidth = 0
in.MaxWidth = 0
in.Final = true
return in, nil
}
func (a *SCMA) parseEquate(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace)
expr, err := a.parseExpression(lp)
if err != nil {
return inst.I{}, err
}
in.Exprs = append(in.Exprs, expr)
in.WidthKnown = true
in.MinWidth = 0
in.MaxWidth = 0
in.Final = true
return in, nil
}
func (a *SCMA) parseHexString(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace)
if !lp.AcceptRun(hexdigits) {
return inst.I{}, fmt.Errorf("%s expects hex digits; got '%s'", in.Command, lp.Next())
}
hs := lp.Emit()
if len(hs)%2 != 0 {
return inst.I{}, fmt.Errorf("%s expects pairs of hex digits; got %d", in.Command, len(hs))
}
var err error
if in.Data, err = hex.DecodeString(hs); err != nil {
return inst.I{}, fmt.Errorf("%s: error decoding hex string: %s", in.Command, err)
}
return in, nil
}
func (a *SCMA) parseInclude(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace)
if !lp.AcceptRun(fileChars) {
return inst.I{}, fmt.Errorf("Expecting filename, found '%c'", lp.Next())
}
in.TextArg = lp.Emit()
in.WidthKnown = true
in.MinWidth = 0
in.MaxWidth = 0
in.Final = true
return in, nil
}
func (a *SCMA) parseNoArgDir(in inst.I, lp *lines.Parse) (inst.I, error) {
in.WidthKnown = true
in.MinWidth = 0
in.MaxWidth = 0
in.Final = true
return in, nil
}
func (a *SCMA) parseNotImplemented(in inst.I, lp *lines.Parse) (inst.I, error) {
return inst.I{}, fmt.Errorf("Not implemented (yet?): %s", in.Command)
}
func (a *SCMA) parseExpression(lp *lines.Parse) (*expr.E, error) {
var outer *expr.E
if lp.Accept("#/") {
switch lp.Emit() {
case "#":
outer = &expr.E{Op: expr.OpLsb}
case "/":
outer = &expr.E{Op: expr.OpMsb}
}
}
tree, err := a.parseTerm(lp)
if err != nil {
return &expr.E{}, err
}
for lp.Accept(operatorChars) {
c := lp.Emit()
right, err := a.parseTerm(lp)
if err != nil {
return &expr.E{}, err
}
tree = &expr.E{Op: a.operators[c], Left: tree, Right: right}
}
if outer != nil {
outer.Left = tree
return outer, nil
}
return tree, nil
}
func (a *SCMA) parseTerm(lp *lines.Parse) (*expr.E, error) {
ex := &expr.E{}
top := ex
// Unary minus: just wrap the current expression
if lp.Consume("-") {
top = &expr.E{Op: expr.OpMinus, Left: ex}
}
// Current location
if lp.Consume("*") {
ex.Op = expr.OpLeaf
ex.Text = "*"
return top, nil
}
// Hex
if lp.Consume("$") {
if !lp.AcceptRun(hexdigits) {
c := lp.Next()
return &expr.E{}, fmt.Errorf("Expecting hex number, found '%c' (%d)", c, c)
}
s := lp.Emit()
i, err := strconv.ParseUint(s, 16, 16)
if err != nil {
return &expr.E{}, fmt.Errorf("Invalid hex number: %s: %s", s, err)
}
ex.Op = expr.OpLeaf
ex.Val = uint16(i)
return top, nil
}
// Decimal
if lp.AcceptRun(digits) {
s := lp.Emit()
i, err := strconv.ParseUint(s, 10, 16)
if err != nil {
return &expr.E{}, fmt.Errorf("Invalid number: %s: %s", s, err)
}
ex.Op = expr.OpLeaf
ex.Val = uint16(i)
return top, nil
}
// Label
if !lp.AcceptRun(labelChars) {
c := lp.Next()
return &expr.E{}, fmt.Errorf("Expecting *, (hex) number, or label; found '%c' (%d)", c, c)
}
ex.Op = expr.OpLeaf
ex.Text = lp.Emit()
return top, nil
}

View File

@ -0,0 +1,197 @@
package flavors
import (
"encoding/hex"
"testing"
"github.com/zellyn/go6502/asm"
"github.com/zellyn/go6502/asm/flavors/as65"
"github.com/zellyn/go6502/asm/flavors/merlin"
"github.com/zellyn/go6502/asm/flavors/scma"
"github.com/zellyn/go6502/asm/lines"
)
func TestSimpleCommonFunctions(t *testing.T) {
ss := scma.New()
aa := as65.New()
mm := merlin.New()
tests := []struct {
a asm.Flavor // assembler flavor
i string // input string
p string // printed instruction, expected
b string // bytes, expected
}{
{ss, "* Comment", "{-}", ""},
{ss, "* Comment", "{-}", ""},
{aa, "; Comment", "{-}", ""},
{mm, "* Comment", "{-}", ""},
{ss, "Label", "{- 'Label'}", ""},
{aa, "Label", "{- 'Label'}", ""},
{mm, "Label", "{- 'Label'}", ""},
{ss, " .IN FILE.NAME", "{inc 'FILE.NAME'}", ""},
{ss, " .IN S.DEFS", "{inc 'S.DEFS'}", ""},
{aa, ` include "FILE.NAME"`, "{inc 'FILE.NAME'}", ""},
{mm, " PUT !FILE.NAME", "{inc 'FILE.NAME'}", ""},
{ss, " .TI 76,Title here", "{-}", ""},
{aa, ` title "Title here"`, "{-}", ""},
{mm, ` TTL "Title here"`, "{-}", ""},
{ss, " .TF OUT.BIN", "{-}", ""},
{mm, " DSK OUTFILE", "{-}", ""},
{mm, " SAV OUTFILE", "{-}", ""},
{ss, " .OR $D000", "{org $d000}", ""},
{aa, " org $D000", "{org $d000}", ""},
{mm, " ORG $D000", "{org $d000}", ""},
// {ss, " .TA *-1234", "{target (- * $04d2)}", ""},
{ss, " .DA $1234", "{data $1234}", "3412"},
{aa, " dw $1234", "{data $1234}", "3412"},
{mm, " DW $1234", "{data $1234}", "3412"},
{ss, " .DA/$1234,#$1234,$1234", "{data (msb $1234),(lsb $1234),$1234}", "12343412"},
{ss, " ROL", "{ROL/a}", "2a"},
{aa, " rol a", "{ROL/a}", "2a"},
{mm, " ROL", "{ROL/a}", "2a"},
{ss, " ROL Comment after two spaces", "{ROL/a}", "2a"},
{ss, " ROL $1234", "{ROL/abs $1234}", "2e3412"},
{aa, " rol $1234", "{ROL/abs $1234}", "2e3412"},
{mm, " ROL $1234", "{ROL/abs $1234}", "2e3412"},
{ss, " ROL $12", "{ROL/zp $0012}", "2612"},
{aa, " rol $12", "{ROL/zp $0012}", "2612"},
{mm, " ROL $12", "{ROL/zp $0012}", "2612"},
{ss, " LDA #$12", "{LDA/imm (lsb $0012)}", "a912"},
{aa, " lda #$12", "{LDA/imm (lsb $0012)}", "a912"},
{mm, " LDA #$12", "{LDA/imm (lsb $0012)}", "a912"},
{ss, " JMP $1234", "{JMP/abs $1234}", "4c3412"},
{aa, " jmp $1234", "{JMP/abs $1234}", "4c3412"},
{mm, " JMP $1234", "{JMP/abs $1234}", "4c3412"},
{ss, " JMP ($1234)", "{JMP/ind $1234}", "6c3412"},
{aa, " jmp ($1234)", "{JMP/ind $1234}", "6c3412"},
{mm, " JMP ($1234)", "{JMP/ind $1234}", "6c3412"},
{ss, " BEQ $2345", "{BEQ/rel $2345}", "f0fe"},
{aa, " beq $2345", "{BEQ/rel $2345}", "f0fe"},
{mm, " BEQ $2345", "{BEQ/rel $2345}", "f0fe"},
{ss, " BEQ $2347", "{BEQ/rel $2347}", "f000"},
{aa, " beq $2347", "{BEQ/rel $2347}", "f000"},
{mm, " BEQ $2347", "{BEQ/rel $2347}", "f000"},
{ss, " BEQ $2343", "{BEQ/rel $2343}", "f0fc"},
{aa, " beq $2343", "{BEQ/rel $2343}", "f0fc"},
{mm, " BEQ $2343", "{BEQ/rel $2343}", "f0fc"},
{ss, " LDA $1234", "{LDA/abs $1234}", "ad3412"},
{aa, " lda $1234", "{LDA/abs $1234}", "ad3412"},
{mm, " LDA $1234", "{LDA/abs $1234}", "ad3412"},
{ss, " LDA $1234,X", "{LDA/absX $1234}", "bd3412"},
{aa, " lda $1234,x", "{LDA/absX $1234}", "bd3412"},
{mm, " LDA $1234,X", "{LDA/absX $1234}", "bd3412"},
{ss, " STA $1234,Y", "{STA/absY $1234}", "993412"},
{aa, " sta $1234,y", "{STA/absY $1234}", "993412"},
{mm, " STA $1234,Y", "{STA/absY $1234}", "993412"},
{ss, " LDA $12", "{LDA/zp $0012}", "a512"},
{aa, " lda $12", "{LDA/zp $0012}", "a512"},
{mm, " LDA $12", "{LDA/zp $0012}", "a512"},
{ss, " LDA $12,X", "{LDA/zpX $0012}", "b512"},
{aa, " lda $12,x", "{LDA/zpX $0012}", "b512"},
{mm, " LDA $12,X", "{LDA/zpX $0012}", "b512"},
{ss, " LDX $12,Y", "{LDX/zpY $0012}", "b612"},
{aa, " ldx $12,y", "{LDX/zpY $0012}", "b612"},
{mm, " LDX $12,Y", "{LDX/zpY $0012}", "b612"},
{ss, " LDA ($12),Y", "{LDA/indY $0012}", "b112"},
{aa, " lda ($12),y", "{LDA/indY $0012}", "b112"},
{mm, " LDA ($12),Y", "{LDA/indY $0012}", "b112"},
{ss, " LDA ($12,X)", "{LDA/indX $0012}", "a112"},
{aa, " lda ($12,x)", "{LDA/indX $0012}", "a112"},
{mm, " LDA ($12,X)", "{LDA/indX $0012}", "a112"},
{ss, ` .AS "ABC"`, "{data}", "414243"},
{ss, ` .AT "ABC"`, "{data}", "4142c3"},
{ss, ` .AS /ABC/`, "{data}", "414243"},
{ss, ` .AT /ABC/`, "{data}", "4142c3"},
{ss, ` .AS -"ABC"`, "{data}", "c1c2c3"},
{ss, ` .AT -"ABC"`, "{data}", "c1c243"},
{ss, ` .AS -dABCd`, "{data}", "c1c2c3"},
{ss, ` .AT -dABCd`, "{data}", "c1c243"},
{ss, " .HS 0001ffAb", "{data}", "0001ffab"},
{ss, "A.B .EQ *-C.D", "{= 'A.B' (- * C.D)}", ""},
{ss, " .BS $8", "{block $0008}", ""},
{ss, " .DO A<$3", "{if (< A $0003)}", ""},
{ss, " .ELSE", "{else}", ""},
{ss, " .FIN", "{endif}", ""},
{ss, "Label .MA", "{macro 'Label'}", ""},
{ss, " .EM", "{endm}", ""},
{ss, " .EN", "{end}", ""},
{ss, `>SAM AB,$12,"A B","A, B, "" C"`,
`{call SAM {"AB", "$12", "A B", "A, B, \" C"}}`, ""},
}
// TODO(zellyn): Add tests for finalization of four SCMA directives:
// "Labels used in operand expressions after .OR, TA, .BS,
// and .EQ directives must be defined prior to use (to prevent an
// undefined or ambiguous location counter)."
for i, tt := range tests {
// TODO(zellyn): Test AS65 and Merlin too.
if tt.a != ss {
continue
}
// Initialize to a known state for testing.
tt.a.Clear()
tt.a.SetAddr(0x2345)
tt.a.Set("A.B", 0x6789)
tt.a.Set("C.D", 0x789a)
inst, err := tt.a.ParseInstr(lines.NewSimple(tt.i))
if err != nil {
t.Errorf(`%d. %T.ParseInstr("%s") => error: %s`, i, tt.a, tt.i, err)
continue
}
if inst.Line.Parse == nil {
t.Errorf("Got empty inst.Line.Parse on input '%s'", tt.i)
}
_, err = inst.Compute(tt.a, true, true)
if err != nil {
t.Errorf(`%d. %s.Compute(tt.a, true, true) => error: %s`, i, inst, err)
continue
}
if inst.String() != tt.p {
t.Errorf(`%d. %T.ParseInstr("%s") = %s; want %s`, i, tt.a, tt.i, inst.String(), tt.p)
continue
}
if tt.b != "?" {
hx := hex.EncodeToString(inst.Data)
if hx != tt.b {
t.Fatalf(`%d. %T.ParseInstr("%s").Data = [%s]; want [%s]`, i, tt.a, tt.i, hx, tt.b)
}
}
}
}
func TestSimpleErrors(t *testing.T) {
ss := scma.New()
aa := as65.New()
mm := merlin.New()
tests := []struct {
a asm.Flavor // assembler flavor
i string // input string
}{
{ss, " LDA"}, // missing arg
{aa, " lda"}, //
{mm, " LDA"}, //
{aa, " rol"}, // missing arg (for assemblers that need "A")
{ss, " .DA $1234,"}, // data: trailing comma
{ss, `>MACRO "ABC`}, // macro: unclosed quote on arg
{ss, `>MACRO "ABC"$12`}, // macro: stuff after closing quote
}
for i, tt := range tests {
// TODO(zellyn): Test AS65 and Merlin too.
if tt.a != ss {
continue
}
inst, err := tt.a.ParseInstr(lines.NewSimple(tt.i))
if err == nil {
t.Errorf(`%d. %T.ParseInstr("%s") want err; got %s`, i, tt.a, tt.i, inst)
continue
}
}
}

3
asm/flavors/todos.org Normal file
View File

@ -0,0 +1,3 @@
* TODO only figure out what the opcode *looks* like in the assembler: more DRY
* TODO implement byte value check in simple instruction test
* TODO add line/character reporting to parse errors

405
asm/inst/instruction.go Normal file
View File

@ -0,0 +1,405 @@
package inst
import (
"errors"
"fmt"
"strings"
"github.com/zellyn/go6502/asm/context"
"github.com/zellyn/go6502/asm/expr"
"github.com/zellyn/go6502/asm/lines"
"github.com/zellyn/go6502/opcodes"
)
type Type int
const (
TypeUnknown Type = iota
TypeNone // No type (eg. just a label, just a comment, empty, ignored directive)
TypeMacroStart // Start of macro definition
TypeMacroEnd // End of macro definition
TypeMacroCall // Macro invocation
TypeMacroExit // Macro early exit
TypeIfdef // Ifdef start
TypeIfdefElse // Ifdef else block
TypeIfdefEnd // Ifdef end
TypeInclude // Include a file
TypeData // Data: hex, ascii, etc., etc.
TypeBlock // Block storage
TypeOrg // Where to store assembled code
TypeTarget // Target address to use for jumps, labels, etc.
TypeSegment // Segments: bss/code/data
TypeEqu // Equate
TypeOp // An actual asm opcode
TypeEnd // End assembly
)
type I struct {
Type Type // Type of instruction
Label string // Text of label part
MacroArgs []string // Macro args
Command string // Text of command part
TextArg string // Text of argument part
Exprs []*expr.E // Expression(s)
Data []byte // Actual bytes
WidthKnown bool // Do we know how many bytes this instruction takes yet?
MinWidth uint16 // minimum width in bytes
MaxWidth uint16 // maximum width in bytes
Final bool // Do we know the actual bytes yet?
Op byte // Opcode
Mode opcodes.AddressingMode // Opcode mode
ZeroMode opcodes.AddressingMode // Possible ZP-option mode
ZeroOp byte // Possible ZP-option Opcode
Value uint16 // For Equates, the value
DeclaredLine uint16
Line *lines.Line
}
func (i I) TypeString() string {
switch i.Type {
case TypeNone:
return "-"
case TypeMacroStart:
return "macro"
case TypeMacroEnd:
return "endm"
case TypeMacroCall:
return "call " + i.Command
case TypeMacroExit:
return "exitm"
case TypeIfdef:
return "if"
case TypeIfdefElse:
return "else"
case TypeIfdefEnd:
return "endif"
case TypeInclude:
return "inc"
case TypeData:
return "data"
case TypeBlock:
return "block"
case TypeOrg:
return "org"
case TypeTarget:
return "target"
case TypeSegment:
return "seg"
case TypeEqu:
return "="
case TypeEnd:
return "end"
case TypeOp:
modeStr := "?"
switch i.Mode {
case opcodes.MODE_IMPLIED:
modeStr = "imp"
case opcodes.MODE_ABSOLUTE:
modeStr = "abs"
case opcodes.MODE_INDIRECT:
modeStr = "ind"
case opcodes.MODE_RELATIVE:
modeStr = "rel"
case opcodes.MODE_IMMEDIATE:
modeStr = "imm"
case opcodes.MODE_ABS_X:
modeStr = "absX"
case opcodes.MODE_ABS_Y:
modeStr = "absY"
case opcodes.MODE_ZP:
modeStr = "zp"
case opcodes.MODE_ZP_X:
modeStr = "zpX"
case opcodes.MODE_ZP_Y:
modeStr = "zpY"
case opcodes.MODE_INDIRECT_Y:
modeStr = "indY"
case opcodes.MODE_INDIRECT_X:
modeStr = "indX"
case opcodes.MODE_A:
modeStr = "a"
}
return fmt.Sprintf("%s/%s", i.Command, modeStr)
}
return "?"
}
func (i I) String() string {
switch i.Type {
case TypeInclude:
return fmt.Sprintf("{inc '%s'}", i.TextArg)
}
s := "{" + i.TypeString()
if i.Label != "" {
s += fmt.Sprintf(" '%s'", i.Label)
}
if len(i.MacroArgs) > 0 {
ma := fmt.Sprintf("%#v", i.MacroArgs)[8:]
s += " " + ma
}
if len(i.Exprs) > 0 {
exprs := []string{}
for _, expr := range i.Exprs {
exprs = append(exprs, expr.String())
}
s += " " + strings.Join(exprs, ",")
}
return s + "}"
}
// Compute attempts to finalize the instruction.
func (i *I) Compute(c context.Context, setWidth bool, final bool) (bool, error) {
if i.Type == TypeEqu || i.Type == TypeTarget || i.Type == TypeOrg {
return i.computeMustKnow(c, setWidth, final)
}
if err := i.computeLabel(c, setWidth, final); err != nil {
return false, err
}
if i.Final {
return true, nil
}
switch i.Type {
case TypeOp:
return i.computeOp(c, setWidth, final)
case TypeData:
return i.computeData(c, setWidth, final)
case TypeBlock:
return i.computeBlock(c, setWidth, final)
}
// Everything else is zero-width
i.WidthKnown = true
i.MinWidth = 0
i.MaxWidth = 0
i.Final = true
return true, nil
}
// FixLabels attempts to turn .1 into LAST_LABEL.1
func (i *I) FixLabels(last string) error {
if i.Label != "" && i.Label[0] == '.' {
if last == "" {
return fmt.Errorf("Reference to sub-label '%s' before full label.", i.Label)
}
i.Label = last + i.Label
}
for _, e := range i.Exprs {
if err := e.FixLabels(last); err != nil {
return err
}
}
return nil
}
// computeLabel attempts to compute equates and label values.
func (i *I) computeLabel(c context.Context, setWidth bool, final bool) error {
if i.Label == "" {
return nil
}
if i.Type == TypeEqu {
panic("computeLabel should not be called for equates")
}
addr, aok := c.GetAddr()
if !aok {
return nil
}
lval, lok := c.Get(i.Label)
if lok && addr != lval {
return fmt.Errorf("Trying to set label '%s' to $%04x, but it already has value $%04x", i.Label, addr, lval)
}
c.Set(i.Label, addr)
return nil
}
func (i *I) computeData(c context.Context, setWidth bool, final bool) (bool, error) {
if len(i.Data) > 0 {
i.WidthKnown = true
i.MinWidth = uint16(len(i.Data))
i.MaxWidth = i.MinWidth
i.Final = true
return true, nil
}
allFinal := true
data := []byte{}
var width uint16
for _, e := range i.Exprs {
w := e.Width()
width += w
val, labelMissing, err := e.CheckedEval(c)
if err != nil && !labelMissing {
return false, err
}
if labelMissing {
allFinal = false
if final {
return false, err
}
}
switch w {
case 1:
data = append(data, byte(val))
case 2:
data = append(data, byte(val), byte(val>>8))
}
}
i.MinWidth = width
i.MaxWidth = width
i.WidthKnown = true
if allFinal {
i.Data = data
i.Final = true
}
return i.Final, nil
}
func (i *I) computeBlock(c context.Context, setWidth bool, final bool) (bool, error) {
val, err := i.Exprs[0].Eval(c)
if err == nil {
i.Value = val
i.WidthKnown = true
i.Final = true
i.MinWidth = val
i.MaxWidth = val
} else {
if setWidth || final {
return false, fmt.Errorf("block storage with unknown size")
}
}
return i.Final, nil
}
func (i *I) computeMustKnow(c context.Context, setWidth bool, final bool) (bool, error) {
i.WidthKnown = true
i.MinWidth = 0
i.MaxWidth = 0
i.Final = true
val, err := i.Exprs[0].Eval(c)
if err != nil {
return false, err
}
i.Value = val
switch i.Type {
case TypeTarget:
return false, errors.New("Target not implemented yet.")
case TypeOrg:
c.SetAddr(val)
case TypeEqu:
c.Set(i.Label, val)
// Don't handle labels.
return true, nil
}
if err := i.computeLabel(c, setWidth, final); err != nil {
return false, err
}
return true, nil
}
func (i *I) computeOp(c context.Context, setWidth bool, final bool) (bool, error) {
// If the width is not known, we better have a ZeroPage alternative.
if !i.WidthKnown && (i.ZeroOp == 0 || i.ZeroMode == 0) {
panic(fmt.Sprintf("Reached computeOp for '%s' with no ZeroPage alternative, i.Command"))
}
// An op with no args would be final already, so we must have an Expression.
if len(i.Exprs) == 0 {
panic(fmt.Sprintf("Reached computeOp for '%s' with no expressions", i.Command))
}
val, labelMissing, err := i.Exprs[0].CheckedEval(c)
// A real error
if !labelMissing && err != nil {
return false, err
}
if labelMissing {
// Don't know, do care.
if final {
return false, err
}
// Already know enough.
if i.WidthKnown {
return false, nil
}
// Do we know the width, even though the value is unknown?
if i.Exprs[0].Width() == 1 {
i.WidthKnown = true
i.MinWidth, i.MaxWidth = 2, 2
i.Op, i.Mode = i.ZeroOp, i.ZeroMode
i.ZeroOp, i.ZeroMode = 0, 0
return false, nil
}
// Don't know the width, but don't care on this pass.
if !setWidth {
return false, nil
}
// Okay, we have to set the width: since we don't know, go wide.
i.WidthKnown = true
i.MinWidth, i.MaxWidth = 3, 3
i.ZeroOp, i.ZeroMode = 0, 0
return false, nil
}
// If we got here, we got an actual value.
// We need to figure out the width
if !i.WidthKnown {
if val < 0x100 {
i.MinWidth = 2
i.MaxWidth = 2
i.Op = i.ZeroOp
i.Mode = i.ZeroMode
} else {
i.MinWidth = 3
i.MaxWidth = 3
}
}
// It's a branch
if i.Mode == opcodes.MODE_RELATIVE {
curr, ok := c.GetAddr()
if !ok {
if final {
return false, fmt.Errorf("Cannot determine current address for '%s'", i.Command)
}
return false, nil
}
// Found both current and target addresses
offset := int32(val) - (int32(curr) + 2)
if offset > 127 {
return false, fmt.Errorf("%s cannot jump forward %d (max 127)", i.Command, offset)
}
if offset < -128 {
return false, fmt.Errorf("%s cannot jump back %d (max -128)", i.Command, offset)
}
val = uint16(offset)
}
i.WidthKnown = true
i.Final = true
i.ZeroOp = 0
i.ZeroMode = 0
switch i.MinWidth {
case 2:
// TODO(zellyn): Warn if > 0xff
i.Data = []byte{i.Op, byte(val)}
case 3:
i.Data = []byte{i.Op, byte(val), byte(val >> 8)}
default:
panic(fmt.Sprintf("computeOp reached erroneously for '%s'", i.Command))
}
return true, nil
}

View File

@ -0,0 +1,82 @@
package inst
import (
"testing"
"github.com/zellyn/go6502/asm/context"
"github.com/zellyn/go6502/asm/expr"
)
func TestComputeLabel(t *testing.T) {
i := I{
Label: "L1",
}
c := &context.SimpleContext{}
i.computeLabel(c, false, false)
}
func TestWidthDoesNotChange(t *testing.T) {
i := I{
Type: TypeOp,
Command: "LDA",
Exprs: []*expr.E{
&expr.E{
Op: expr.OpMinus,
Left: &expr.E{Op: expr.OpLeaf, Text: "L1"},
Right: &expr.E{Op: expr.OpLeaf, Text: "L2"},
},
},
MinWidth: 0x2,
MaxWidth: 0x3,
Final: false,
Op: 0xad,
Mode: 0x2,
ZeroMode: 0x80,
ZeroOp: 0xa5,
}
c := &context.SimpleContext{}
c.Set("L1", 0x102)
final, err := i.Compute(c, false, false)
if err != nil {
t.Fatal(err)
}
if final {
t.Fatal("First pass shouldn't be able to finalize expression with unknown width")
}
final, err = i.Compute(c, true, false)
if err != nil {
t.Fatal(err)
}
if final {
t.Fatal("Second pass shouldn't be able to finalize expression with unknown variable.")
}
if !i.WidthKnown {
t.Fatal("Second pass should have set width.")
}
if i.MinWidth != i.MaxWidth {
t.Fatalf("i.WidthKnown, but i.MinWidth(%d) != i.MaxWidth(%d)", i.MinWidth, i.MaxWidth)
}
if i.MinWidth != 3 {
t.Fatalf("i.MinWidth should be 3; got %d", i.MinWidth)
}
c.Set("L2", 0x101)
final, err = i.Compute(c, true, true)
if err != nil {
t.Fatal(err)
}
if !final {
t.Fatal("Third pass should be able to finalize expression.")
}
if !i.WidthKnown {
t.Fatal("Third pass should left width unchanged.")
}
if i.MinWidth != i.MaxWidth {
t.Fatalf("i.WidthKnown, but i.MinWidth(%d) != i.MaxWidth(%d)", i.MinWidth, i.MaxWidth)
}
if i.MinWidth != 3 {
t.Fatalf("i.MinWidth should still be 3; got %d", i.MinWidth)
}
}

80
asm/lines/files.go Normal file
View File

@ -0,0 +1,80 @@
package lines
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
)
type Opener interface {
Open(filename string) (io.ReadCloser, error)
}
type OsOpener struct{}
func (o OsOpener) Open(filename string) (io.ReadCloser, error) {
return os.Open(filename)
}
type FileLineSource struct {
context Context
lines []string
size int
curr int
}
func (fls *FileLineSource) Next() (line Line, done bool, err error) {
if fls.curr >= fls.size {
return Line{}, true, nil
}
fls.curr++
return NewLine(fls.lines[fls.curr-1], fls.curr, &fls.context), false, nil
}
func (fls FileLineSource) Context() Context {
return fls.context
}
func NewFileLineSource(filename string, context Context, opener Opener) (LineSource, error) {
fls := &FileLineSource{context: context}
file, err := opener.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fls.lines = append(fls.lines, scanner.Text())
}
if err = scanner.Err(); err != nil {
return nil, err
}
fls.size = len(fls.lines)
return fls, nil
}
type TestOpener map[string]string
func (o TestOpener) Open(filename string) (io.ReadCloser, error) {
contents, ok := o[filename]
if ok {
return ioutil.NopCloser(strings.NewReader(contents)), nil
} else {
return nil, fmt.Errorf("File not found: %s", filename)
}
}
func (o TestOpener) Clear() {
for k := range o {
delete(o, k)
}
}
func NewTestOpener() TestOpener {
return make(TestOpener)
}

118
asm/lines/lineparse.go Normal file
View File

@ -0,0 +1,118 @@
package lines
import (
"strings"
"unicode"
"unicode/utf8"
)
const Eol rune = '\n'
/*
Parse is a struct representing the parse/lex of a single line. It's
based on the lexer presented in Rob Pike's "Lexical Scannign in Go"
talk: http://cuddle.googlecode.com/hg/talk/lex.html, but it's simpler,
because assembly files are entirely line-based.
*/
type Parse struct {
line string
start int
pos int
width int
}
func NewParse(text string) *Parse {
text = strings.TrimRightFunc(text, unicode.IsSpace)
return &Parse{line: text}
}
func (lp *Parse) Emit() string {
emitted := lp.line[lp.start:lp.pos]
lp.start = lp.pos
return emitted
}
func (lp *Parse) Ignore() {
lp.start = lp.pos
}
func (lp *Parse) Next() (c rune) {
if lp.pos >= len(lp.line) {
lp.width = 0
return Eol
}
c, lp.width = utf8.DecodeRuneInString(lp.line[lp.pos:])
lp.pos += lp.width
return c
}
func (lp *Parse) Backup() {
lp.pos -= lp.width
}
func (lp *Parse) Peek() rune {
c := lp.Next()
lp.Backup()
return c
}
func (lp *Parse) Accept(valid string) bool {
if strings.IndexRune(valid, lp.Next()) >= 0 {
return true
}
lp.Backup()
return false
}
// Consume accepts and ignores a single character of input.
func (lp *Parse) Consume(valid string) bool {
if strings.IndexRune(valid, lp.Next()) >= 0 {
lp.start = lp.pos
return true
}
lp.Backup()
return false
}
func (lp *Parse) AcceptRun(valid string) bool {
some := false
for strings.IndexRune(valid, lp.Next()) >= 0 {
some = true
}
lp.Backup()
return some
}
func (lp *Parse) AcceptUntil(until string) bool {
until += "\n"
some := false
for strings.IndexRune(until, lp.Next()) < 0 {
some = true
}
lp.Backup()
return some
}
func (lp *Parse) IgnoreRun(valid string) bool {
if lp.AcceptRun(valid) {
lp.Ignore()
return true
}
return false
}
func (lp *Parse) AcceptString(prefix string) bool {
if strings.HasPrefix(lp.line[lp.pos:], prefix) {
lp.pos += len(prefix)
return true
}
return false
}
func (lp *Parse) Rest() string {
return lp.line[lp.pos:]
}
func (lp *Parse) Text() string {
return lp.line
}

43
asm/lines/lines.go Normal file
View File

@ -0,0 +1,43 @@
package lines
import "fmt"
type Context struct {
Filename string // Pointer to the filename
Parent *Line // Pointer to parent line (eg. include, macro)
}
type Line struct {
LineNo int // Actual line number in file
DeclaredLineNo int // Declared line number in file, or 0
Context *Context // Pointer to the file/include/macro context
Parse *Parse // The lineparser
}
type LineSource interface {
Next() (line Line, done bool, err error)
Context() Context
}
func NewLine(s string, lineNo int, context *Context) Line {
l := Line{
LineNo: lineNo,
Context: context,
Parse: NewParse(s),
}
return l
}
var testFilename = "(test)"
func NewSimple(s string) Line {
return NewLine(s, 0, &Context{Filename: testFilename})
}
func (l Line) Text() string {
return l.Parse.Text()
}
func (l Line) String() string {
return fmt.Sprintf("%s:%d: %s", l.Context.Filename, l.LineNo, l.Parse.Text())
}

View File

@ -42,9 +42,7 @@ type Memory interface {
}
// Ticker interface, for keeping track of cycles.
type Ticker interface {
Tick()
}
type Ticker func()
// Interrupt vectors.
const (
@ -146,7 +144,7 @@ func (c *cpu) Step() error {
c.oldPC = c.r.PC
i := c.m.Read(c.r.PC)
c.r.PC++
c.t.Tick()
c.t()
if f, ok := Opcodes[i]; ok {
f(c)

View File

@ -24,7 +24,7 @@ func clearFlag(flag byte) func(*cpu) {
return func(c *cpu) {
c.r.P &^= flag
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
}
}
@ -34,7 +34,7 @@ func setFlag(flag byte) func(*cpu) {
return func(c *cpu) {
c.r.P |= flag
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
}
}
@ -45,12 +45,12 @@ func branch(mask, value byte) func(*cpu) {
// T1
offset := c.m.Read(c.r.PC)
c.r.PC++
c.t.Tick()
c.t()
// T2
oldPC := c.r.PC
if c.r.P&mask == value {
c.m.Read(oldPC)
c.t.Tick()
c.t()
// T3
c.r.PC = c.r.PC + uint16(offset)
if offset >= 128 {
@ -58,7 +58,7 @@ func branch(mask, value byte) func(*cpu) {
}
if !samePage(c.r.PC, oldPC) {
c.m.Read((oldPC & 0xFF00) | (c.r.PC & 0x00FF))
c.t.Tick()
c.t()
}
}
}
@ -121,7 +121,7 @@ func adc_d(c *cpu, value byte) {
c.r.P |= FLAG_Z
}
case VERSION_65C02:
c.t.Tick()
c.t()
c.setNZ(byte(a & 0xFF))
default:
panic("Unknown chip version")
@ -155,27 +155,27 @@ func brk(c *cpu) {
// T1
c.m.Read(c.r.PC)
c.r.PC++
c.t.Tick()
c.t()
// T2
c.m.Write(0x100+uint16(c.r.SP), byte(c.r.PC>>8))
c.r.SP--
c.t.Tick()
c.t()
// T3
c.m.Write(0x100+uint16(c.r.SP), byte(c.r.PC&0xff))
c.r.SP--
c.t.Tick()
c.t()
// T4
c.m.Write(0x100+uint16(c.r.SP), c.r.P|FLAG_B) // Set B flag
c.r.SP--
c.r.P |= FLAG_I // Disable interrupts
c.t.Tick()
c.t()
// T5
addr := uint16(c.m.Read(IRQ_VECTOR))
c.t.Tick()
c.t()
// T6
addr |= (uint16(c.m.Read(IRQ_VECTOR+1)) << 8)
c.r.PC = addr
c.t.Tick()
c.t()
}
func cmp(c *cpu, value byte) {
@ -215,14 +215,14 @@ func dex(c *cpu) {
c.r.X--
c.setNZ(c.r.X)
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
}
func dey(c *cpu) {
c.r.Y--
c.setNZ(c.r.Y)
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
}
func eor(c *cpu, value byte) {
@ -240,40 +240,40 @@ func inx(c *cpu) {
c.r.X++
c.setNZ(c.r.X)
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
}
func iny(c *cpu) {
c.r.Y++
c.setNZ(c.r.Y)
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
}
func jmpAbsolute(c *cpu) {
// T1
addr := uint16(c.m.Read(c.r.PC))
c.r.PC++
c.t.Tick()
c.t()
// T2
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
c.r.PC++
c.r.PC = addr
c.t.Tick()
c.t()
}
func jmpIndirect(c *cpu) {
// T1
iAddr := uint16(c.m.Read(c.r.PC))
c.r.PC++
c.t.Tick()
c.t()
// T2
iAddr |= (uint16(c.m.Read(c.r.PC)) << 8)
c.r.PC++
c.t.Tick()
c.t()
// T3
addr := uint16(c.m.Read(iAddr))
c.t.Tick()
c.t()
// T4
// 6502 jumps to (xxFF,xx00) instead of (xxFF,xxFF+1).
// See http://en.wikipedia.org/wiki/MOS_Technology_6502#Bugs_and_quirks
@ -290,29 +290,29 @@ func jmpIndirect(c *cpu) {
panic("Unknown chip version")
}
c.r.PC = addr
c.t.Tick()
c.t()
}
func jsr(c *cpu) {
// T1
addr := uint16(c.m.Read(c.r.PC)) // We actually push PC(next) - 1
c.r.PC++
c.t.Tick()
c.t()
// T2
c.m.Read(0x100 + uint16(c.r.SP)) // Ignored read on stack
c.t.Tick()
c.t()
// T3
c.m.Write(0x100+uint16(c.r.SP), byte(c.r.PC>>8)) // Write PC|hi to stack
c.r.SP--
c.t.Tick()
c.t()
// T4
c.m.Write(0x100+uint16(c.r.SP), byte(c.r.PC&0xff)) // Write PC|lo to stack
c.r.SP--
c.t.Tick()
c.t()
// T5
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
c.r.PC = addr
c.t.Tick()
c.t()
}
func lda(c *cpu, value byte) {
@ -344,44 +344,44 @@ func ora(c *cpu, value byte) {
func nop(c *cpu) {
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
}
func pha(c *cpu) {
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
c.m.Write(0x100+uint16(c.r.SP), c.r.A)
c.r.SP--
c.t.Tick()
c.t()
}
func pla(c *cpu) {
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
c.m.Read(0x100 + uint16(c.r.SP))
c.r.SP++
c.t.Tick()
c.t()
c.r.A = c.m.Read(0x100 + uint16(c.r.SP))
c.setNZ(c.r.A)
c.t.Tick()
c.t()
}
func php(c *cpu) {
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
c.m.Write(0x100+uint16(c.r.SP), c.r.P)
c.r.SP--
c.t.Tick()
c.t()
}
func plp(c *cpu) {
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
c.m.Read(0x100 + uint16(c.r.SP))
c.r.SP++
c.t.Tick()
c.t()
c.r.P = c.m.Read(0x100+uint16(c.r.SP)) | FLAG_UNUSED | FLAG_B
c.t.Tick()
c.t()
}
func rol(c *cpu, value byte) byte {
@ -401,43 +401,43 @@ func ror(c *cpu, value byte) byte {
func rts(c *cpu) {
// T1
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
// T2
c.m.Read(0x100 + uint16(c.r.SP))
c.r.SP++
c.t.Tick()
c.t()
// T3
addr := uint16(c.m.Read(0x100 + uint16(c.r.SP)))
c.r.SP++
c.t.Tick()
c.t()
// T4
addr |= (uint16(c.m.Read(0x100+uint16(c.r.SP))) << 8)
c.t.Tick()
c.t()
// T5
c.m.Read(addr)
c.r.PC = addr + 1 // Since we pushed PC(next) - 1
c.t.Tick()
c.t()
}
func rti(c *cpu) {
// T1
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
// T2
c.m.Read(0x100 + uint16(c.r.SP))
c.r.SP++
c.t.Tick()
c.t()
// T3
c.r.P = c.m.Read(0x100+uint16(c.r.SP)) | FLAG_UNUSED
c.r.SP++
// T4
addr := uint16(c.m.Read(0x100 + uint16(c.r.SP)))
c.r.SP++
c.t.Tick()
c.t()
// T5
addr |= (uint16(c.m.Read(0x100+uint16(c.r.SP))) << 8)
c.r.PC = addr
c.t.Tick()
c.t()
}
func sbc(c *cpu, value byte) {
@ -502,7 +502,7 @@ func sbc_d(c *cpu, value byte) {
}
// fmt.Printf(" a=$%04X ($%02X)\n", a, byte(a))
c.r.A = byte(a)
c.t.Tick()
c.t()
c.setNZ(c.r.A)
default:
panic("Unknown chip version")
@ -525,39 +525,39 @@ func tax(c *cpu) {
c.r.X = c.r.A
c.setNZ(c.r.X)
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
}
func tay(c *cpu) {
c.r.Y = c.r.A
c.setNZ(c.r.Y)
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
}
func tsx(c *cpu) {
c.r.X = c.r.SP
c.setNZ(c.r.X)
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
}
func txa(c *cpu) {
c.r.A = c.r.X
c.setNZ(c.r.A)
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
}
func txs(c *cpu) {
c.r.SP = c.r.X
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
}
func tya(c *cpu) {
c.r.A = c.r.Y
c.setNZ(c.r.A)
c.m.Read(c.r.PC)
c.t.Tick()
c.t()
}

View File

@ -19,7 +19,7 @@ func immediate2(f func(*cpu, byte)) func(*cpu) {
value := c.m.Read(c.r.PC)
c.r.PC++
f(c, value)
c.t.Tick()
c.t()
}
}
@ -29,15 +29,15 @@ func absolute4r(f func(*cpu, byte)) func(*cpu) {
// T1
addr := uint16(c.m.Read(c.r.PC))
c.r.PC++
c.t.Tick()
c.t()
// T2
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
c.r.PC++
c.t.Tick()
c.t()
// T3
value := c.m.Read(addr)
f(c, value)
c.t.Tick()
c.t()
}
}
@ -47,14 +47,14 @@ func absolute4w(f func(*cpu) byte) func(*cpu) {
// T1
addr := uint16(c.m.Read(c.r.PC))
c.r.PC++
c.t.Tick()
c.t()
// T2
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
c.r.PC++
c.t.Tick()
c.t()
// T3
c.m.Write(addr, f(c))
c.t.Tick()
c.t()
}
}
@ -64,11 +64,11 @@ func zp3r(f func(*cpu, byte)) func(*cpu) {
// T1
addr := uint16(c.m.Read(c.r.PC))
c.r.PC++
c.t.Tick()
c.t()
// T2
value := c.m.Read(addr)
f(c, value)
c.t.Tick()
c.t()
}
}
@ -78,10 +78,10 @@ func zp3w(f func(*cpu) byte) func(*cpu) {
// T1
addr := uint16(c.m.Read(c.r.PC))
c.r.PC++
c.t.Tick()
c.t()
// T2
c.m.Write(addr, f(c))
c.t.Tick()
c.t()
}
}
@ -91,21 +91,21 @@ func absx4r(f func(*cpu, byte)) func(*cpu) {
// T1
addr := uint16(c.m.Read(c.r.PC))
c.r.PC++
c.t.Tick()
c.t()
// T2
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
addrX := addr + uint16(c.r.X)
c.r.PC++
c.t.Tick()
c.t()
// T3
if !samePage(addr, addrX) {
c.m.Read(addrX - 0x100)
c.t.Tick()
c.t()
}
// T3(cotd.) or T4
value := c.m.Read(addrX)
f(c, value)
c.t.Tick()
c.t()
}
}
@ -115,21 +115,21 @@ func absy4r(f func(*cpu, byte)) func(*cpu) {
// T1
addr := uint16(c.m.Read(c.r.PC))
c.r.PC++
c.t.Tick()
c.t()
// T2
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
addrY := addr + uint16(c.r.Y)
c.r.PC++
c.t.Tick()
c.t()
// T3
if !samePage(addr, addrY) {
c.m.Read(addrY - 0x100)
c.t.Tick()
c.t()
}
// T3(cotd.) or T4
value := c.m.Read(addrY)
f(c, value)
c.t.Tick()
c.t()
}
}
@ -139,18 +139,18 @@ func absx5w(f func(*cpu) byte) func(*cpu) {
// T1
addr := uint16(c.m.Read(c.r.PC))
c.r.PC++
c.t.Tick()
c.t()
// T2
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
addrX := addr + uint16(c.r.X)
c.r.PC++
c.t.Tick()
c.t()
// T3
c.m.Read((addr & 0xFF00) | (addrX & 0x00FF))
c.t.Tick()
c.t()
// T4
c.m.Write(addrX, f(c))
c.t.Tick()
c.t()
}
}
@ -160,18 +160,18 @@ func absy5w(f func(*cpu) byte) func(*cpu) {
// T1
addr := uint16(c.m.Read(c.r.PC))
c.r.PC++
c.t.Tick()
c.t()
// T2
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
addrY := addr + uint16(c.r.Y)
c.r.PC++
c.t.Tick()
c.t()
// T3
c.m.Read((addr & 0xFF00) | (addrY & 0x00FF))
c.t.Tick()
c.t()
// T4
c.m.Write(addrY, f(c))
c.t.Tick()
c.t()
}
}
@ -182,14 +182,14 @@ func zpx4r(f func(*cpu, byte)) func(*cpu) {
addr := c.m.Read(c.r.PC)
addrX := uint16(addr + c.r.X)
c.r.PC++
c.t.Tick()
c.t()
// T2
c.m.Read(uint16(addr))
c.t.Tick()
c.t()
// T3
value := c.m.Read(addrX)
f(c, value)
c.t.Tick()
c.t()
}
}
@ -200,13 +200,13 @@ func zpx4w(f func(*cpu) byte) func(*cpu) {
addr := c.m.Read(c.r.PC)
addrX := uint16(addr + c.r.X)
c.r.PC++
c.t.Tick()
c.t()
// T2
c.m.Read(uint16(addr))
c.t.Tick()
c.t()
// T3
c.m.Write(uint16(addrX), f(c))
c.t.Tick()
c.t()
}
}
@ -217,14 +217,14 @@ func zpy4r(f func(*cpu, byte)) func(*cpu) {
addr := c.m.Read(c.r.PC)
addrY := uint16(addr + c.r.Y)
c.r.PC++
c.t.Tick()
c.t()
// T2
c.m.Read(uint16(addr))
c.t.Tick()
c.t()
// T3
value := c.m.Read(uint16(addrY))
f(c, value)
c.t.Tick()
c.t()
}
}
@ -235,13 +235,13 @@ func zpy4w(f func(*cpu) byte) func(*cpu) {
addr := c.m.Read(c.r.PC)
addrY := uint16(addr + c.r.Y)
c.r.PC++
c.t.Tick()
c.t()
// T2
c.m.Read(uint16(addr))
c.t.Tick()
c.t()
// T3
c.m.Write(addrY, f(c))
c.t.Tick()
c.t()
}
}
@ -251,23 +251,23 @@ func zpiy5r(f func(*cpu, byte)) func(*cpu) {
// T1
iAddr := c.m.Read(c.r.PC)
c.r.PC++
c.t.Tick()
c.t()
// T2
addr := uint16(c.m.Read(uint16(iAddr)))
c.t.Tick()
c.t()
// T3
addr |= (uint16(c.m.Read(uint16(iAddr+1))) << 8)
addrY := addr + uint16(c.r.Y)
c.t.Tick()
c.t()
// T4
if !samePage(addr, addrY) {
c.m.Read((addr & 0xFF00) | (addrY & 0x00FF))
c.t.Tick()
c.t()
}
// T4(cotd.) or T5
value := c.m.Read(addr + uint16(c.r.Y))
f(c, value)
c.t.Tick()
c.t()
}
}
@ -277,20 +277,20 @@ func zpiy6w(f func(*cpu) byte) func(*cpu) {
// T1
iAddr := c.m.Read(c.r.PC)
c.r.PC++
c.t.Tick()
c.t()
// T2
addr := uint16(uint16(c.m.Read(uint16(iAddr))))
c.t.Tick()
c.t()
// T3
addr |= (uint16(c.m.Read(uint16(iAddr+1))) << 8)
addrY := addr + uint16(c.r.Y)
c.t.Tick()
c.t()
// T4
c.m.Read((addr & 0xFF00) | (addrY & 0x00FF))
c.t.Tick()
c.t()
// T5
c.m.Write(addr+uint16(c.r.Y), f(c))
c.t.Tick()
c.t()
}
}
@ -300,20 +300,20 @@ func zpxi6r(f func(*cpu, byte)) func(*cpu) {
// T1
iAddr := c.m.Read(c.r.PC)
c.r.PC++
c.t.Tick()
c.t()
// T2
c.m.Read(uint16(iAddr))
c.t.Tick()
c.t()
// T3
addr := uint16(uint16(c.m.Read(uint16(iAddr + c.r.X))))
c.t.Tick()
c.t()
// T4
addr |= (uint16(c.m.Read(uint16(iAddr+c.r.X+1))) << 8)
c.t.Tick()
c.t()
// T5
value := c.m.Read(addr)
f(c, value)
c.t.Tick()
c.t()
}
}
@ -323,19 +323,19 @@ func zpxi6w(f func(*cpu) byte) func(*cpu) {
// T1
iAddr := c.m.Read(c.r.PC)
c.r.PC++
c.t.Tick()
c.t()
// T2
c.m.Read(uint16(iAddr))
c.t.Tick()
c.t()
// T3
addr := uint16(uint16(c.m.Read(uint16(iAddr + c.r.X))))
c.t.Tick()
c.t()
// T4
addr |= (uint16(c.m.Read(uint16(iAddr+c.r.X+1))) << 8)
c.t.Tick()
c.t()
// T5
c.m.Write(addr, f(c))
c.t.Tick()
c.t()
}
}
@ -345,7 +345,7 @@ func acc2rmw(f func(*cpu, byte) byte) func(*cpu) {
// T1
c.m.Read(c.r.PC)
c.r.A = f(c, c.r.A)
c.t.Tick()
c.t()
}
}
@ -355,16 +355,16 @@ func zp5rmw(f func(*cpu, byte) byte) func(*cpu) {
// T1
addr := uint16(c.m.Read(c.r.PC))
c.r.PC++
c.t.Tick()
c.t()
// T2
value := c.m.Read(addr)
c.t.Tick()
c.t()
// T3
c.m.Write(addr, value)
c.t.Tick()
c.t()
// T4
c.m.Write(addr, f(c, value))
c.t.Tick()
c.t()
}
}
@ -374,20 +374,20 @@ func abs6rmw(f func(*cpu, byte) byte) func(*cpu) {
// T1
addr := uint16(c.m.Read(c.r.PC))
c.r.PC++
c.t.Tick()
c.t()
// T2
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
c.r.PC++
c.t.Tick()
c.t()
// T3
value := c.m.Read(addr)
c.t.Tick()
c.t()
// T4
c.m.Write(addr, value) // Spurious write...
c.t.Tick()
c.t()
// T5
c.m.Write(addr, f(c, value))
c.t.Tick()
c.t()
}
}
@ -397,20 +397,20 @@ func zpx6rmw(f func(*cpu, byte) byte) func(*cpu) {
// T1
addr8 := c.m.Read(c.r.PC)
c.r.PC++
c.t.Tick()
c.t()
// T2
c.m.Read(uint16(addr8))
c.t.Tick()
c.t()
// T3
addr := uint16(addr8 + c.r.X)
value := c.m.Read(addr)
c.t.Tick()
c.t()
// T4
c.m.Write(addr, value)
c.t.Tick()
c.t()
// T5
c.m.Write(addr, f(c, value))
c.t.Tick()
c.t()
}
}
@ -420,23 +420,23 @@ func absx7rmw(f func(*cpu, byte) byte) func(*cpu) {
// T1
addr := uint16(c.m.Read(c.r.PC))
c.r.PC++
c.t.Tick()
c.t()
// T2
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
addr += uint16(c.r.X)
c.r.PC++
c.t.Tick()
c.t()
// T3
c.m.Read(addr)
c.t.Tick()
c.t()
// T4
value := c.m.Read(addr)
c.t.Tick()
c.t()
// T5
c.m.Write(addr, value) // Spurious write
c.t.Tick()
c.t()
// T6
c.m.Write(addr, f(c, value))
c.t.Tick()
c.t()
}
}

View File

@ -6,10 +6,12 @@ function tables.
package opcodes
// Opcode addressing modes.
type AddressingMode int
type AddressingMode uint
const MODE_UNKNOWN = 0
const (
MODE_IMPLIED AddressingMode = iota
MODE_IMPLIED AddressingMode = 1 << iota
MODE_ABSOLUTE
MODE_INDIRECT
MODE_RELATIVE
@ -24,6 +26,9 @@ const (
MODE_A
)
// Logical OR of the three indirect modes
const MODE_INDIRECT_ANY AddressingMode = MODE_INDIRECT | MODE_INDIRECT_X | MODE_INDIRECT_Y
// Opcode read/write semantics: does the opcode read, write, or
// rmw. Useful to distinguish between instructions further than just
// addressing mode.
@ -162,20 +167,20 @@ var Opcodes = map[byte]Opcode{
// 3-opcode, 4*-cycle abs,X/Y
0x1D: {"ORA", MODE_ABS_X, RW_R},
0x19: {"ORA", MODE_ABS_X, RW_R},
0x39: {"AND", MODE_ABS_X, RW_R},
0x19: {"ORA", MODE_ABS_Y, RW_R},
0x39: {"AND", MODE_ABS_Y, RW_R},
0x3D: {"AND", MODE_ABS_X, RW_R},
0x59: {"EOR", MODE_ABS_X, RW_R},
0x59: {"EOR", MODE_ABS_Y, RW_R},
0x5D: {"EOR", MODE_ABS_X, RW_R},
0x79: {"ADC", MODE_ABS_X, RW_R},
0x79: {"ADC", MODE_ABS_Y, RW_R},
0x7D: {"ADC", MODE_ABS_X, RW_R},
0xB9: {"LDA", MODE_ABS_Y, RW_R},
0xBD: {"LDA", MODE_ABS_X, RW_R},
0xB9: {"LDA", MODE_ABS_X, RW_R},
0xD9: {"CMP", MODE_ABS_X, RW_R},
0xD9: {"CMP", MODE_ABS_Y, RW_R},
0xDD: {"CMP", MODE_ABS_X, RW_R},
0xF9: {"SBC", MODE_ABS_X, RW_R},
0xF9: {"SBC", MODE_ABS_Y, RW_R},
0xFD: {"SBC", MODE_ABS_X, RW_R},
0xBE: {"LDX", MODE_ABS_X, RW_R},
0xBE: {"LDX", MODE_ABS_Y, RW_R},
0xBC: {"LDY", MODE_ABS_X, RW_R},
// 3-opcode, 5-cycle abs,X/Y
@ -254,3 +259,42 @@ var Opcodes = map[byte]Opcode{
0xDE: {"DEC", MODE_ABS_X, RW_RMW},
0xFE: {"INC", MODE_ABS_X, RW_RMW},
}
// Information for lookup of opcodes by name and mode -------------------------
type OpInfo struct {
Mode AddressingMode
Length int
Byte byte
}
type OpSummary struct {
Modes AddressingMode // bitmask of supported modes
Ops []OpInfo
}
var ByName map[string]OpSummary
func init() {
ByName = make(map[string]OpSummary)
for b, oc := range Opcodes {
info := OpInfo{oc.Mode, ModeLengths[oc.Mode], b}
summary := ByName[oc.Name]
summary.Modes |= oc.Mode
summary.Ops = append(summary.Ops, info)
ByName[oc.Name] = summary
}
}
func (s OpSummary) AnyModes(modes AddressingMode) bool {
return modes&s.Modes != 0
}
func (s OpSummary) OpForMode(mode AddressingMode) (OpInfo, bool) {
for _, o := range s.Ops {
if o.Mode == mode {
return o, true
}
}
return OpInfo{}, false
}

View File

@ -51,7 +51,7 @@ func TestFunctionalTestCompare(t *testing.T) {
}
var cc CycleCount
c := cpu.NewCPU(&m, &cc, cpu.VERSION_6502)
c := cpu.NewCPU(&m, cc.Tick, cpu.VERSION_6502)
c.Reset()
m.Reset(MODE_RECORD)

View File

@ -24,7 +24,7 @@ func (m *K64) Write(address uint16, value byte) {
m[address] = value
}
// Cycle counter for the tests. Satisfies the cpu.Ticker interface.
// Cycle counter for the tests.
type CycleCount uint64
func (c *CycleCount) Tick() {
@ -60,7 +60,7 @@ func TestFunctionalTestInstructions(t *testing.T) {
var cc CycleCount
OFFSET := 0xa
copy(m[OFFSET:len(bytes)+OFFSET], bytes)
c := cpu.NewCPU(&m, &cc, cpu.VERSION_6502)
c := cpu.NewCPU(&m, cc.Tick, cpu.VERSION_6502)
c.Reset()
c.SetPC(0x1000)
for {
@ -150,7 +150,7 @@ func TestDecimalMode6502(t *testing.T) {
OFFSET := 0x1000
copy(m[OFFSET:len(bytes)+OFFSET], bytes)
m[1] = 0 // 6502
c := cpu.NewCPU(&m, &cc, cpu.VERSION_6502)
c := cpu.NewCPU(&m, cc.Tick, cpu.VERSION_6502)
c.Reset()
c.SetPC(0x1000)
for {
@ -186,7 +186,7 @@ func TestDecimalMode65C02(t *testing.T) {
OFFSET := 0x1000
copy(m[OFFSET:len(bytes)+OFFSET], bytes)
m[1] = 1 // 65C02
c := cpu.NewCPU(&m, &cc, cpu.VERSION_65C02)
c := cpu.NewCPU(&m, cc.Tick, cpu.VERSION_65C02)
c.Reset()
c.SetPC(0x1000)
for {