Skip to content

Commit

Permalink
Sorted out addressing modes.
Browse files Browse the repository at this point in the history
  • Loading branch information
ExpandingMan committed May 7, 2017
1 parent 22adee9 commit 0d3fd33
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 45 deletions.
6 changes: 6 additions & 0 deletions src/Sim6502.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
__precompile__(true)

module Sim6502

using MacroTools

import Base.fetch
import Base.pointer
import Base: +, -

include("utils.jl")
include("cpu.jl")
include("memory.jl")
include("boilerplate.jl")
include("instructions.jl")

end # module
4 changes: 4 additions & 0 deletions src/boilerplate.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#===================================================================================================
Here we keep some macros for doing boilerplate code generation...
===================================================================================================#

2 changes: 1 addition & 1 deletion src/cpu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fetch(c::CPU, reg::Symbol) = getfield(c, reg)
store!(c::CPU, reg::Symbol, val::Integer) = setfield!(c, reg, val)
export fetch, store!

counter!(c::CPU, ℓ::UInt8) = (c.PC += ℓ)
counter!(c::CPU, ℓ::Unsigned) = (c.PC += ℓ)
export counter!


Expand Down
82 changes: 61 additions & 21 deletions src/instructions.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,44 @@


#=====================================================================================================
Some conventions:
All instructions are based on methods where arguments are passed explicitly (rather than from
the stack). These base methods affect the registers (or memory) as they should, but do not
affect the program counter as they are not reading from the stack.
Zero-argument instruction methods read memory from the stack, and affect the PC register as
they should.
=====================================================================================================#

# these can be passed to instructions to specify addressing modes when appropriate
abstract type AddressingMode end
abstract type DirectMode <: AddressingMode end
abstract type IndirectMode <: AddressingMode end
struct Direct <: DirectMode end
struct DirectX <: DirectMode end
struct DirectY <: DirectMode end
struct IndirectX <: IndirectMode end
struct IndirectY <: IndirectMode end

export AddressingMode, DirectMode, IndirectMode, Direct, DirectX, DirectY, IndirectX, IndirectY


# utility functions used frequently in instructions
checkNflag!(c::CPU, val::UInt8) = val 0x80 ? status!(c, :N, true) : status!(c, :N, false)
checkZflag!(c::CPU, val::UInt8) = val == 0x00 ? status!(c, :Z, true) : status!(c, :Z, false)

# pointers that occur in different addressing modes
pointer(::Type{Direct}, ptr:, c::CPU, m::Memory) = ptr
pointer(::Type{DirectX}, ptr:, c::CPU, m::Memory) = ptr + c.X
pointer(::Type{DirectY}, ptr:, c::CPU, m::Memory) = ptr + c.Y
pointer(::Type{IndirectX}, ptr:, c::CPU, m::Memory) = Π(deref(UInt16, ptr + c.X, m))
pointer(::Type{IndirectY}, ptr:, c::CPU, m::Memory) = Π(deref(UInt16, ptr, m) + c.Y)

# these dereference in a way appropriate for Indirect
deref{T<:AddressingMode}(::Type{T}, ptr:, c::CPU, m::Memory) = deref(pointer(T, ptr, c, m), m)

function store!{T<:AddressingMode}(::Type{T}, ptr:, c::CPU, m::Memory, val::UInt8)
store!(m, pointer(T, ptr, c, m), val)
end


#===================================================================================================
<load, store instructions>
Expand All @@ -13,40 +49,44 @@ function ld!(c::CPU, reg::Symbol, val::UInt8)
v = store!(c, reg, val)
checkNflag!(c, v)
checkZflag!(c, v)
counter!(c, 0x02)
v
end

# immediate mode
function ld!{T<:AddressingMode}(c::CPU, m::Memory, reg::Symbol, ::Type{T}, ptr:)
ld!(c, reg, deref(T, ptr, c, m))
end
ld!(c::CPU, m::Memory, reg::Symbol, ptr:) = ld!(c, m, reg, Direct, ptr)

# TODO get rid of boilerplate stuff
lda!(c::CPU, val::UInt8) = ld!(c, :A, val)
ldx!(c::CPU, val::UInt8) = ld!(c, :X, val)
ldy!(c::CPU, val::UInt8) = ld!(c, :Y, val)

# template for ld instructions in zero page or absolute mode
ld!(c::CPU, m::Memory, reg::Symbol, ptr:) = ld!(c, reg, ptr m)

lda!(c::CPU, m::Memory, ptr:) = ld!(c, m, :A, ptr)
ldx!(c::CPU, m::Memory, ptr:) = ld!(c, m, :X, ptr)
ldy!(c::CPU, m::Memory, ptr:) = ld!(c, m, :Y, ptr)
lda!{T<:AddressingMode}(c::CPU, m::Memory, ::Type{T}, ptr:) = ld!(c, m, :A, T, ptr)
ldx!{T<:AddressingMode}(c::CPU, m::Memory, ::Type{T}, ptr:) = ld!(c, m, :X, T, ptr)
ldy!{T<:AddressingMode}(c::CPU, m::Memory, ::Type{T}, ptr:) = ld!(c, m, :Y, T, ptr)

# zero page,X,Y and absolute,X,Y; note this also works with A, even though that's not a real instruction!
ld!(c::CPU, m::Memory, reg::Symbol, ptr:, idx::Symbol) = ld!(c, m, reg, (ptr + fetch(c,idx)) m)

lda!(c::CPU, m::Memory, ptr:, idx::Symbol) = ld!(c, m, :A, ptr, idx)
ldx!(c::CPU, m::Memory, ptr:, idx::Symbol) = ld!(c, m, :X, ptr, idx)
ldy!(c::CPU, m::Memory, ptr:, idx::Symbol) = ld!(c, m, :Y, ptr, idx)
lda!(c::CPU, m::Memory, ptr:) = ld!(c, m, :A, Direct, ptr)
ldx!(c::CPU, m::Memory, ptr:) = ld!(c, m, :X, Direct, ptr)
ldy!(c::CPU, m::Memory, ptr:) = ld!(c, m, :Y, Direct, ptr)

export ld!, lda!, ldx!, ldy!


#---------------------STA, STX, STY------------------------------------------------
# template for st in zero page or absolute mode
st!(c::CPU, m::Memory, reg::Symbol, ptr:) = store!(m, ptr, fetch(c, reg))
function st!(c::CPU, m::Memory, reg::Symbol, ::Type{T}, ptr:) where T<:AddressingMode
store!(m, pointer(T, ptr, c, m), fetch(c, reg))
end
st!(c::CPU, m::Memory, reg::Symbol, ptr:) = st!(c, m, reg, Direct, ptr)

sta!{T<:AddressingMode}(c::CPU, m::Memory, ::Type{T}, ptr:) = st!(c, m, :A, T, ptr)
stx!{T<:AddressingMode}(c::CPU, m::Memory, ::Type{T}, ptr:) = st!(c, m, :X, T, ptr)
sty!{T<:AddressingMode}(c::CPU, m::Memory, ::Type{T}, ptr:) = st!(c, m, :Y, T, ptr)

# zero page or absolute mode
sta!(c::CPU, m::Memory, ptr:) = st!(c, m, :A, ptr)
stx!(c::CPU, m::Memory, ptr:) = st!(c, m, :X, ptr)
sty!(c::CPU, m::Memory, ptr:) = st!(c, m, :Y, ptr)
sta!(c::CPU, m::Memory, ptr:) = st!(c, m, :A, Direct, ptr)
stx!(c::CPU, m::Memory, ptr:) = st!(c, m, :X, Direct, ptr)
sty!(c::CPU, m::Memory, ptr:) = st!(c, m, :Y, Direct, ptr)

export st!, sta!, stx!, sty!
#===================================================================================================
Expand Down
39 changes: 35 additions & 4 deletions src/memory.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,71 @@ struct Memory

Memory(ℓ::Integer=65536) = Memory(zeros(UInt8, ℓ))
end
export Memory

reset!(m::Memory) = (m.v .= zeros(UInt8, length(m.v)))
export reset!

fetch(m::Memory, idx::T) where T<:Unsigned = m.v[idx+one(T)]
fetch(m::Memory, idx::AbstractVector{T}) where T<:Unsigned = m.v[idx+one(T)]

Base.getindex(m::Memory, T) = fetch(m, T)

Base.setindex!(m::Memory, val::UInt8, idx::T) where T<:Unsigned = (m.v[idx+one(T)] = val)


#===================================================================================================
<pointers>
===================================================================================================#
abstract type AbstractΠ end # uses capital Π

# note that, somewhat confusingly, T is the type of the pointer, not the value being pointed to
type Π{T<:Unsigned} <: AbstractΠ
addr::T
addr::UInt16

Π{T}(addr::Unsigned) where T<:Unsigned = new(addr)
end
export Π

Π(addr::T) where T<:Unsigned = Π{T}(addr)

const Π8 = Π{UInt8} # zero page pointer
const Π16 = Π{UInt16} # standard 6502 memory pointer
export Π8, Π16

dereference{T}(ptr:{T}, m::Memory) = m.v[ptr.addr+one(T)]
deref(ptr:, m::Memory) = dereference(ptr, m)
(ptr:, m::Memory) = dereference(ptr, m) # this symbol is \mapsto
()(ptr:, m::Memory) = dereference(ptr, m) # this symbol is \mapsto

dereference(::Type{UInt8}, ptr:, m::Memory)::UInt8 = dereference(ptr, Π)
deref(::Type{UInt8}, ptr:, m::Memory) = dereference(ptr, Π)

# these are for dereferencing length 2 arrays into single UInt16's
function dereference{T}(::Type{UInt16}, ptr:{T}, m::Memory)::UInt16
least_sig = deref(ptr, m)
most_sig = deref(ptr + one(T), m)
(convert(UInt16, most_sig) << 8) + least_sig
end
deref(::Type{UInt16}, ptr:, m::Memory) = dereference(UInt16, ptr, m)
()(ptr:, m::Memory) = dereference(UInt16, ptr, m) # this symbol is \rightarrowtriangle

# dereference arrays
function dereference{T}(ptr:{T}, m::Memory, ℓ::Integer)
start_idx = ptr.addr + one(T)
end_idx = start_idx +- 1
m.v[start_idx:end_idx]
end
deref(ptr:, m::Memory, ℓ::Integer) = dereference(ptr, m, ℓ)

store!(m::Memory, ptr:{T}, val::UInt8) = (m.v[ptr.addr+one(T)] = val)
store!{T}(m::Memory, ptr:{T}, val::UInt8) = (m.v[ptr.addr+one(T)] = val)

(+){T}(ptr1:{T}, ptr2:{T}) = Π{T}(ptr1.addr + ptr2.addr)
(-){T}(ptr1:{T}, ptr2:{T}) = Π{T}(ptr1.addr - ptr2.addr)

(+){T}(ptr:{T}, val::Unsigned) = Π{T}(ptr.addr + convert(T, val))
(-){T}(ptr:{T}, val::Unsigned) = Π{T}(ptr.addr - convert(T, val))

export dereference, deref, , store!, +, -
export dereference, deref, , , store!, +, -
#===================================================================================================
</pointers>
===================================================================================================#
Expand Down
32 changes: 13 additions & 19 deletions test/instructions.jl
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
using Sim6502
using BenchmarkTools

c = CPU()
m = Memory()

macro p(expr)
esc(:($expr; println(c)))
end

tick!(c::CPU) = (adc!(c, 0xff); c)

println(c)
adc!(c, 0xff)
println(c)
adc!(c, 0x01)
println(c)
adc!(c, 0xff)
println(c)
adc!(c, 0xff)
println(c)


lda!(c, 0xa0)
println(c)
ldx!(c, 0x01)
println(c)
ldy!(c, 0x02)
println(c)
@p ldx!(c, 0x01)
@p lda!(c, 0x05)
@p sta!(c, m, Π(0x01))
@p lda!(c, 0x06)
@p sta!(c, m, Π(0x02))
@p ldy!(c, 0x0a)
@p sty!(c, m, Π(0x0605))
@p lda!(c, m, IndirectX, Π(0x00))

0 comments on commit 0d3fd33

Please sign in to comment.