Skip to content

Commit

Permalink
refactor for multi cpu architecture support (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
cornelk authored Dec 20, 2024
1 parent 2ef1c56 commit 78bc92b
Show file tree
Hide file tree
Showing 46 changed files with 637 additions and 457 deletions.
69 changes: 54 additions & 15 deletions arch/arch.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,65 @@
package arch

import (
"github.com/retroenv/retrogolib/arch/cpu/m6502"
"github.com/retroenv/retroasm/lexer/token"
"github.com/retroenv/retroasm/parser/ast"
)

// Architecture contains architecture specific information.
type Architecture struct {
// AddressWidth describes the memory address widths of the CPU.
AddressWidth int
type Architecture[T any] interface {
// AddressWidth returns the address width of the architecture in bits.
AddressWidth() int
// AssignInstructionAddress assigns an address to the instruction.
AssignInstructionAddress(assigner AddressAssigner, ins Instruction) (uint64, error)
// GenerateInstructionOpcode generates the instruction opcode based on the instruction base opcode,
// its addressing mode and parameters.
GenerateInstructionOpcode(assigner AddressAssigner, ins Instruction) error
// Instruction returns the instruction with the given name.
Instruction(name string) (T, bool)
// ParseIdentifier parses an identifier and returns the corresponding node.
ParseIdentifier(p Parser, ins T) (ast.Node, error)
}

// BranchingInstructions contains all CPU branching instruction names.
BranchingInstructions map[string]struct{}
// Parser processes an input stream and parses its token to produce an abstract syntax tree (AST) as output.
type Parser interface {
// AddressWidth returns the address width of the architecture in bits.
AddressWidth() int
// AdvanceReadPosition advances the token read position.
AdvanceReadPosition(offset int)
// NextToken returns the current or a following token with the given offset from current token parse position.
// If the offset exceeds the available tokens, a token of type EOF is returned.
NextToken(offset int) token.Token
}

// Instructions maps instruction names to CPU instruction information.
Instructions map[string]*m6502.Instruction
type AddressAssigner interface {
// ArgumentValue returns the value of an instruction argument, either a number or a symbol value.
ArgumentValue(argument any) (uint64, error)
// RelativeOffset returns the relative offset between two addresses.
RelativeOffset(destination, addressAfterInstruction uint64) (byte, error)
// ProgramCounter returns the current program counter.
ProgramCounter() uint64
}

// NewNES returns a new NES architecture instance.
func NewNES() Architecture {
return Architecture{
AddressWidth: 16,
BranchingInstructions: m6502.BranchingInstructions,
Instructions: m6502.Instructions,
}
type Instruction interface {
// Address returns the assigned start address of the instruction.
Address() uint64
// Addressing returns the addressing mode of the instruction.
Addressing() int
// Argument returns the instruction argument.
Argument() any
// Name returns the instruction name.
Name() string
// Opcodes returns the instruction opcodes.
Opcodes() []byte
// Size returns the size of the instruction in bytes.
Size() int

// SetAddress sets the assigned start address of the instruction.
SetAddress(uint64)
// SetAddressing sets the addressing mode of the instruction.
SetAddressing(int)
// SetOpcodes sets the instruction opcodes.
SetOpcodes([]byte)
// SetSize sets the size of the instruction in bytes.
SetSize(int)
}
61 changes: 61 additions & 0 deletions arch/m6502/assembler/address_assigning_step.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Package assembler implements the architecture specific assembler functionality.
package assembler

import (
"fmt"
"math"

"github.com/retroenv/retroasm/arch"
"github.com/retroenv/retroasm/arch/m6502/parser"
"github.com/retroenv/retrogolib/arch/cpu/m6502"
)

func AssignInstructionAddress(assigner arch.AddressAssigner, ins arch.Instruction) (uint64, error) {
pc := assigner.ProgramCounter()
ins.SetAddress(pc)

name := ins.Name()
insDetails, ok := m6502.Instructions[name]
if !ok {
return 0, fmt.Errorf("unsupported instruction '%s'", name)
}

addressing := m6502.AddressingMode(ins.Addressing())

// handle disambiguous addressing mode to reduce absolute addressings to
// zeropage ones if the used address value fits into byte
switch addressing {
case parser.XAddressing:
argument := ins.Argument()
value, err := assigner.ArgumentValue(argument)
if err != nil {
return 0, fmt.Errorf("getting instruction argument: %w", err)
}
if value > math.MaxUint8 {
ins.SetAddressing(int(m6502.AbsoluteXAddressing))
} else {
ins.SetAddressing(int(m6502.ZeroPageXAddressing))
}

case parser.YAddressing:
argument := ins.Argument()
value, err := assigner.ArgumentValue(argument)
if err != nil {
return 0, fmt.Errorf("getting instruction argument: %w", err)
}
if value > math.MaxUint8 {
ins.SetAddressing(int(m6502.AbsoluteYAddressing))
} else {
ins.SetAddressing(int(m6502.ZeroPageYAddressing))
}
}

addressing = m6502.AddressingMode(ins.Addressing())
addressingInfo, ok := insDetails.Addressing[addressing]
if !ok {
return 0, fmt.Errorf("unsupported instruction '%s' addressing %d", name, addressing)
}

programCounter := pc + uint64(addressingInfo.Size)
return programCounter, nil
}
108 changes: 108 additions & 0 deletions arch/m6502/assembler/generate_opcode_step.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package assembler

import (
"encoding/binary"
"fmt"
"math"

"github.com/retroenv/retroasm/arch"
"github.com/retroenv/retrogolib/arch/cpu/m6502"
)

// GenerateInstructionOpcode generates the instruction opcode based on the instruction base opcode,
// its addressing mode and parameters.
func GenerateInstructionOpcode(assigner arch.AddressAssigner, ins arch.Instruction) error {
instructionInfo := m6502.Instructions[ins.Name()]
addressing := m6502.AddressingMode(ins.Addressing())
addressingInfo := instructionInfo.Addressing[addressing]
ins.SetOpcodes([]byte{addressingInfo.Opcode})
ins.SetSize(int(addressingInfo.Size))

switch addressing {
case m6502.ImpliedAddressing, m6502.AccumulatorAddressing:

case m6502.ImmediateAddressing:
if err := generateImmediateAddressingOpcode(assigner, ins); err != nil {
return fmt.Errorf("generating opcode: %w", err)
}

case m6502.AbsoluteAddressing, m6502.AbsoluteXAddressing, m6502.AbsoluteYAddressing,
m6502.IndirectAddressing, m6502.IndirectXAddressing, m6502.IndirectYAddressing:
if err := generateAbsoluteIndirectAddressingOpcode(assigner, ins); err != nil {
return fmt.Errorf("generating opcode: %w", err)
}

case m6502.ZeroPageAddressing, m6502.ZeroPageXAddressing, m6502.ZeroPageYAddressing:
if err := generateZeroPageAddressingOpcode(assigner, ins); err != nil {
return fmt.Errorf("generating opcode: %w", err)
}

case m6502.RelativeAddressing:
if err := generateRelativeAddressingOpcode(assigner, ins); err != nil {
return fmt.Errorf("generating opcode: %w", err)
}

default:
return fmt.Errorf("unsupported instruction addressing %d", addressing)
}

return nil
}

func generateAbsoluteIndirectAddressingOpcode(assigner arch.AddressAssigner, ins arch.Instruction) error {
value, err := assigner.ArgumentValue(ins.Argument())
if err != nil {
return fmt.Errorf("getting instruction argument: %w", err)
}
if value > math.MaxUint16 {
return fmt.Errorf("value %d exceeds word", value)
}

opcodes := binary.LittleEndian.AppendUint16(ins.Opcodes(), uint16(value))
ins.SetOpcodes(opcodes)
return nil
}

func generateZeroPageAddressingOpcode(assigner arch.AddressAssigner, ins arch.Instruction) error {
value, err := assigner.ArgumentValue(ins.Argument())
if err != nil {
return fmt.Errorf("getting instruction argument: %w", err)
}
if value > math.MaxUint8 {
return fmt.Errorf("value %d exceeds byte", value)
}

opcodes := append(ins.Opcodes(), byte(value))
ins.SetOpcodes(opcodes)
return nil
}

func generateImmediateAddressingOpcode(assigner arch.AddressAssigner, ins arch.Instruction) error {
value, err := assigner.ArgumentValue(ins.Argument())
if err != nil {
return fmt.Errorf("getting instruction argument: %w", err)
}
if value > math.MaxUint8 {
return fmt.Errorf("value %d exceeds byte", value)
}

opcodes := append(ins.Opcodes(), byte(value))
ins.SetOpcodes(opcodes)
return nil
}

func generateRelativeAddressingOpcode(assigner arch.AddressAssigner, ins arch.Instruction) error {
value, err := assigner.ArgumentValue(ins.Argument())
if err != nil {
return fmt.Errorf("getting instruction argument: %w", err)
}

b, err := assigner.RelativeOffset(value, ins.Address()+uint64(ins.Size()))
if err != nil {
return fmt.Errorf("value %d exceeds byte", value)
}

opcodes := append(ins.Opcodes(), b)
ins.SetOpcodes(opcodes)
return nil
}
47 changes: 47 additions & 0 deletions arch/m6502/m6502.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Package m6502 provides a 6502 architecture specific assembler code.
package m6502

import (
"github.com/retroenv/retroasm/arch"
"github.com/retroenv/retroasm/arch/m6502/assembler"
"github.com/retroenv/retroasm/arch/m6502/parser"
"github.com/retroenv/retroasm/assembler/config"
"github.com/retroenv/retroasm/parser/ast"
"github.com/retroenv/retrogolib/arch/cpu/m6502"
)

// New returns a new 6502 architecture configuration.
func New() *config.Config[*m6502.Instruction] {
p := &arch6502[*m6502.Instruction]{}
cfg := &config.Config[*m6502.Instruction]{
Arch: p,
}
return cfg
}

type arch6502[T any] struct {
}

func (_ *arch6502[T]) AddressWidth() int {
return 16
}

func (_ *arch6502[T]) Instruction(name string) (*m6502.Instruction, bool) {
ins, ok := m6502.Instructions[name]
return ins, ok
}

// nolint: wrapcheck
func (_ *arch6502[T]) ParseIdentifier(p arch.Parser, ins *m6502.Instruction) (ast.Node, error) {
return parser.ParseIdentifier(p, ins)
}

// nolint: wrapcheck
func (_ *arch6502[T]) AssignInstructionAddress(assigner arch.AddressAssigner, ins arch.Instruction) (uint64, error) {
return assembler.AssignInstructionAddress(assigner, ins)
}

// nolint: wrapcheck
func (_ *arch6502[T]) GenerateInstructionOpcode(assigner arch.AddressAssigner, ins arch.Instruction) error {
return assembler.GenerateInstructionOpcode(assigner, ins)
}
15 changes: 11 additions & 4 deletions parser/addressing.go → arch/m6502/parser/addressing.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Package parser implements the architecture specific parser functionality.
package parser

import (
"fmt"

"github.com/retroenv/retroasm/arch"
"github.com/retroenv/retroasm/lexer/token"
"github.com/retroenv/retrogolib/arch/cpu/m6502"
)
Expand All @@ -15,16 +17,21 @@ const (
addressingZeroPage
)

const (
XAddressing = m6502.AbsoluteXAddressing | m6502.ZeroPageXAddressing
YAddressing = m6502.AbsoluteYAddressing | m6502.ZeroPageYAddressing
)

// parseAddressSize returns the addressing mode used for an instruction based on the following
// tokens.
func (p *Parser) parseAddressSize(ins *m6502.Instruction) (addressingSize, error) {
tok := p.NextToken(0)
func parseAddressSize(parser arch.Parser, ins *m6502.Instruction) (addressingSize, error) {
tok := parser.NextToken(0)
if tok.Type != token.Identifier && tok.Type != token.EOL {
return addressingDefault, nil
}

accumulatorAddressing := ins.HasAddressing(m6502.AccumulatorAddressing)
next1 := p.NextToken(1)
next1 := parser.NextToken(1)

if accumulatorAddressing && (tok.Type == token.EOL || next1.Type != token.Colon) {
return addressingDefault, nil
Expand All @@ -45,7 +52,7 @@ func (p *Parser) parseAddressSize(ins *m6502.Instruction) (addressingSize, error
return addressingDefault, nil

case token.Colon:
p.readPosition += 2
parser.AdvanceReadPosition(2)
return addrSize, nil

default:
Expand Down
Loading

0 comments on commit 78bc92b

Please sign in to comment.