Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: judofyr/rcpu
base: master
...
head fork: rlane/rcpu
compare: instruction-decoder
Checking mergeability… Don't worry, you can still create the pull request.
  • 4 commits
  • 7 files changed
  • 0 commit comments
  • 1 contributor
View
88 bin/rcpu-disas
@@ -23,20 +23,13 @@ input_filename = ARGV[0]
data = File.read(input_filename).unpack('v*')
$emu = RCPU::Emulator.new(data)
-# TODO refactor emulator to make this easier
$decoded = {}
+$decoder = RCPU::InstructionDecoder.new $emu.memory
def decode pc
+ fail unless pc.is_a? Fixnum
return *$decoded[pc] if $decoded.member? pc
- $emu[:PC] = pc
- _, inst = $emu.next_instruction
- new_pc = $emu[:PC]
- $emu.instance_variable_set :@next_instruction, nil
- if inst.name
- $decoded[pc] = [new_pc, inst]
- return new_pc, inst
- else
- return nil
- end
+ inst, new_pc, cycles = $decoder.decode pc
+ $decoded[pc] = [inst, new_pc]
end
def mklabel pc
@@ -55,50 +48,55 @@ while not queue.empty?
pc = queue.shift
next if seen_pcs.member? pc
seen_pcs << pc
- new_pc, inst = decode pc
- if inst then
- valid_pcs << pc
- case inst.name
- when :SET, :ADD, :SUB
- if inst.a.is_a? Register and inst.a.name == :PC
- if inst.b.is_a? Literal
- target = if inst.name == :SET
- inst.b.value
- elsif inst.name == :ADD
- new_pc + inst.b.value
- elsif inst.name == :SUB
- new_pc - inst.b.value
+ begin
+ inst, new_pc = decode pc
+
+ if inst then
+ valid_pcs << pc
+ case inst.name
+ when :SET, :ADD, :SUB
+ if inst.a.is_a? Register and inst.a.name == :PC
+ if inst.b.is_a? Literal
+ target = if inst.name == :SET
+ inst.b.value
+ elsif inst.name == :ADD
+ new_pc + inst.b.value
+ elsif inst.name == :SUB
+ new_pc - inst.b.value
+ end
+ queue << target
+ branch_targets << target
+ inst.b = Label.new(mklabel(target))
+ elsif inst.b.is_a? Register and inst.b.name == :POP
+ # Function return, covered by JSR case.
+ else
+ $stderr.puts "unsupported branch: #{inst}"
end
+ else
+ queue << new_pc
+ end
+ when :JSR
+ queue << new_pc
+ if inst.a.is_a? Literal
+ target = inst.a.value
queue << target
branch_targets << target
- inst.b = Label.new(mklabel(target))
- elsif inst.b.is_a? Register and inst.b.name == :POP
- # Function return, covered by JSR case.
+ inst.a = Label.new(mklabel(target))
else
$stderr.puts "unsupported branch: #{inst}"
end
- else
+ when :IFE, :IFN, :IFG, :IFB
+ _, skip_pc = decode new_pc
queue << new_pc
- end
- when :JSR
- queue << new_pc
- if inst.a.is_a? Literal
- target = inst.a.value
- queue << target
- branch_targets << target
- inst.a = Label.new(mklabel(target))
+ queue << skip_pc
+ skippable_pcs << new_pc
else
- $stderr.puts "unsupported branch: #{inst}"
+ queue << new_pc
end
- when :IFE, :IFN, :IFG, :IFB
- skip_pc, _ = decode new_pc
- queue << new_pc
- queue << skip_pc
- skippable_pcs << new_pc
- else
- queue << new_pc
end
+ rescue RCPU::DecoderError
+ puts "decoder error at #{pc}: #{$!.message}"
end
end
@@ -112,7 +110,7 @@ while pc < data.length
print " "
end
if valid_pcs.member? pc
- new_pc, inst = decode pc
+ inst, new_pc = decode pc
print inst
pc = new_pc
else
View
4 examples/fib.rcpu
@@ -2,7 +2,7 @@
block :fib do
fun do |n|
IFG 2, n
- jmp :base
+ SET pc, :base
locals do |first_result, saved_n|
SET saved_n, n
@@ -14,7 +14,7 @@ block :fib do
call :_fib, n
ADD a, first_result
end
- jmp :out
+ SET pc, :out
label :base
fail unless a == n
View
78 lib/rcpu.rb
@@ -16,6 +16,70 @@ module RCPU
VERSION = "0.1.0"
class AssemblerError < StandardError; end
+ class DecoderError < StandardError; end
+
+ class InstructionDecoder
+ def initialize memory
+ @memory = memory
+ end
+
+ def decode pc
+ @pc = pc
+ @cycles = 1
+ ins = next_word
+ op = ins & 0xF
+ a = (ins >> 4) & 0x3F
+ b = (ins >> 10) & 0x3F
+ inst = if op == 0
+ NonBasicInstruction.from_code(a, value(b))
+ else
+ BasicInstruction.from_code(op, value(a), value(b))
+ end
+ return inst, @pc, @cycles
+ end
+
+ def next_word
+ @memory[@pc.tap do
+ @pc = (@pc + 1) & 0xFFFF;
+ end]
+ end
+
+ def value(v)
+ case v
+ when 0x00..0x07 # register
+ Register.from_code(v)
+ when 0x08..0x0f # [register]
+ reg = Register.from_code(v - 0x08)
+ Indirection.new(reg)
+ when 0x10..0x17 # [register + next word]
+ @cycles += 1
+ reg = Register.from_code(v - 0x10)
+ PlusRegister.new(reg, next_word)
+ when 0x18 # POP
+ Register.new(:POP)
+ when 0x19 # PEEK
+ Register.new(:PEEK)
+ when 0x1A
+ Register.new(:PUSH)
+ when 0x1B
+ Register.new(:SP)
+ when 0x1C
+ Register.new(:PC)
+ when 0x1D
+ Register.new(:O)
+ when 0x1E
+ @cycles += 1
+ Indirection.new(Literal.new(next_word))
+ when 0x1F
+ @cycles += 1
+ Literal.new(next_word)
+ when 0x20..0x3F
+ Literal.new(v - 0x20)
+ else
+ raise DecoderError.new("Missing value: 0x#{v.to_s(16)}")
+ end
+ end
+ end
class BasicInstruction < Struct.new(:name, :a, :b)
ALL = %w[SET ADD SUB MUL DIV MOD SHL SHR AND BOR XOR IFE IFN IFG IFB].map(&:to_sym)
@@ -37,6 +101,7 @@ def to_machine(mem = [])
end
def self.from_code(code, a, b)
+ raise DecoderError.new("Invalid basic opcode #{code}") if code < 1 or code > ALL.size
new(ALL[code-1], a, b)
end
@@ -145,6 +210,7 @@ def to_machine(mem = [])
end
def self.from_code(code, a)
+ raise DecoderError.new("Invalid nonbasic opcode #{code}") if code < 1 or code > ALL.size
new(ALL[code - 1], a)
end
@@ -190,6 +256,10 @@ def +(n)
PlusRegister.new(self, n)
end
+ def -(n)
+ self + -n
+ end
+
def execute(emu)
case name
when *REAL
@@ -227,7 +297,13 @@ def code
class PlusRegister < Struct.new(:register, :value)
def to_s
- "[#{register}+#{value}]"
+ v = if value.is_a? Fixnum
+ # Sign extend from 16 bits
+ value | (-value[15] & ~0xFFFF)
+ else
+ value
+ end
+ "[%s%+d]" % [register, v]
end
def code
View
79 lib/rcpu/emulator.rb
@@ -54,7 +54,7 @@ def []=(key, value)
def slice(*a) @array.slice(*a) end
end
- attr_reader :memory, :registers, :next_instruction
+ attr_reader :memory, :registers
attr_accessor :cycle
class Immediate < Struct.new(:value)
@@ -63,6 +63,7 @@ class Immediate < Struct.new(:value)
def initialize(program)
@size = program.size
@memory = Memory.new(program)
+ @decoder = InstructionDecoder.new(@memory)
@registers = Hash.new(0)
@cycle = 0
@instruction_count = 0
@@ -96,41 +97,17 @@ def dump
puts " Inst: #{@instruction_count}"
end
- def next_instruction
- @next_instruction ||= [self[:PC], send(*dispatch)]
- end
-
- def next_word
- @registers[:PC].tap do
- @registers[:PC] = (@registers[:PC] + 1) & 0xFFFF;
- end
- end
-
- def dispatch
- current = next_word
-
- ins = @memory[current]
- if (op = ins & 0xF).zero?
- op = (ins >> 4) & 0x3F
- a = (ins >> 10) & 0x3F
- [:non_basic, op, value(a)]
- else
- a = (ins >> 4) & 0x3F
- b = (ins >> 10) & 0x3F
- [:basic, op, value(a), value(b)]
- end
- end
-
# Run one instruction
def tick
@instruction_count += 1
- next_instruction[1].execute(self)
- @next_instruction = nil
+ inst, @registers[:PC], cycles = @decoder.decode @registers[:PC]
+ inst.execute(self)
+ @cycle += cycles
end
# Skip one instruction
def skip
- dispatch
+ _, @registers[:PC], _ = @decoder.decode @registers[:PC]
end
def run
@@ -144,14 +121,6 @@ def run_forever
tick while true
end
- def basic(op, a, b)
- BasicInstruction.from_code(op, a, b)
- end
-
- def non_basic(op, a)
- NonBasicInstruction.from_code(op, a)
- end
-
def [](k)
case k
when Register
@@ -188,42 +157,6 @@ def []=(k, v)
raise "Missing set: #{k}"
end
end
-
- def value(v)
- case v
- when 0x00..0x07 # register
- Register.from_code(v)
- when 0x08..0x0f # [register]
- reg = Register.from_code(v - 0x08)
- Indirection.new(reg)
- when 0x10..0x17 # [register + next word]
- self.cycle += 1
- reg = Register.from_code(v - 0x10)
- PlusRegister.new(reg, @memory[next_word])
- when 0x18 # POP
- Register.new(:POP)
- when 0x19 # PEEK
- Register.new(:PEEK)
- when 0x1A
- Register.new(:PUSH)
- when 0x1B
- Register.new(:SP)
- when 0x1C
- Register.new(:PC)
- when 0x1D
- Register.new(:O)
- when 0x1E
- self.cycle += 1
- Indirection.new(Literal.new(@memory[next_word]))
- when 0x1F
- self.cycle += 1
- Literal.new(@memory[next_word])
- when 0x20..0x3F
- Literal.new(v - 0x20)
- else
- raise "Missing value: 0x#{v.to_s(16)}"
- end
- end
end
end
View
1  test/test_assembler.rb
@@ -100,6 +100,5 @@ def test_data_label
assert_equal 0x2, memory[0x1]
assert_equal 0x2, memory[0x1+512+0x1]
end
-
end
end
View
51 test/test_decoder.rb
@@ -0,0 +1,51 @@
+require 'helper'
+
+module RCPU
+ class TestInstructionDecoder < TestCase
+ def check words, asm, expected_cycles
+ lib = Library.new
+ ASMParser.new(lib, asm).parse
+ expected_inst = lib.blocks[:main].ins[0]
+ decoder = InstructionDecoder.new words
+ inst, new_pc, cycles = decoder.decode 0
+ assert_equal expected_inst, inst
+ assert_equal words.size, new_pc
+ assert_equal expected_cycles, cycles
+ end
+
+ def test_invalid_instruction
+ decoder = InstructionDecoder.new [0x00, 0x20, 0x3f0]
+
+ (0...3).each do |pc|
+ assert_raises DecoderError do
+ inst, new_pc, cycles = decoder.decode pc
+ end
+ end
+ end
+
+ def test_basic_instructions
+ check [0x2401], 'SET a, [b]', 1
+ check [0x18a2], 'ADD [c], i', 1
+ check [0x8573, 0x0001], 'SUB [1+j], 1', 2
+ check [0x6584], 'MUL pop, peek', 1
+ check [0x6995], 'DIV peek, push', 1
+ check [0x61a6], 'MOD push, pop', 1
+ check [0x7db7, 0xffff], 'SHL SP, 0xFFFF', 2
+ check [0x71f8, 0xffff], 'SHR 0xFFFF, PC', 2
+ check [0xfdd9], 'AND O, 31', 1
+ check [0x79fa, 0x0020, 0x0000], 'BOR 32, [0]', 3
+ check [0x0deb, 0xffff], 'XOR [0xFFFF], x', 2
+ check [0x7c4c, 0x7fff], 'IFE y, 0x7FFF', 2
+ check [0x795d, 0xffff, 0xffff], 'IFN [0xFFFF+z], [0xFFFF]', 3
+ check [0x545e, 0x0], 'IFG z, [0+z]', 2
+ check [0x75df], 'IFB O, O', 1
+ end
+
+ def test_nonbasic_instructions
+ check [0x0010], 'JSR a', 1
+ check [0xfc10], 'JSR 31', 1
+ check [0x7c10, 0x20], 'JSR 32', 2
+ check [0x5410, 0x0a], 'JSR [10+z]', 2
+ end
+ end
+end
View
12 test/test_emulator.rb
@@ -34,8 +34,9 @@ def test_parse
end
end
+ decoder = InstructionDecoder.new @emu.memory
until ins.empty?
- assert_equal ins.shift, @emu.next_instruction[1]
+ assert_equal ins.shift, decoder.decode(@emu[:PC])[0]
@emu.tick
end
end
@@ -276,6 +277,8 @@ def test_format
block :main do
SET a, 1
ADD [a], 0x1000
+ ADD [a+0xFFFF], 0x1000
+ ADD [a-2], 0x1000
SUB [a+1], [0x10]
SET push, o
SET x, pop
@@ -290,17 +293,20 @@ def test_format
res = [
"SET a, 0x1",
"ADD [a], 0x1000",
+ "ADD [a-1], 0x1000",
+ "ADD [a-2], 0x1000",
"SUB [a+1], [0x10]",
"SET push, o",
"SET x, pop",
"SET x, peek",
"SET x, pc",
- "JSR 0xC",
+ "JSR 0x12",
"SUB pc, 0x1"
]
+ decoder = InstructionDecoder.new @emu.memory
until res.empty?
- assert_equal res.shift, @emu.next_instruction[1].to_s
+ assert_equal res.shift, decoder.decode(@emu[:PC])[0].to_s
@emu.tick
end
end

No commit comments for this range

Something went wrong with that request. Please try again.