Permalink
Browse files

first draft of working emulator

  • Loading branch information...
Deradon committed Apr 11, 2012
1 parent 90f5f28 commit 24416dd5fdfa27c38b089e87af5dca534c355b6b
View
11 TODO
@@ -11,3 +11,14 @@
Register, Word and Literal are all ducktyped to behave similiar,
check if we can clean it up.
+1.4 DualScreen (*MultiScreen)
+require 'dcpu16'
+cpu = DCPU16::CPU.new
+screen = DCPU16::Screen.new(cpu.memory)
+screen2 = DCPU16::Screen.new(cpu.memory, :x_offset => 40, :memory_offset => 0x9000)
+cpu.memory.write(0x8000, 0x30)
+cpu.memory.write(0x9000, 0x30)
+
+1.5 Screen
+ * RefreshRate
+
View
@@ -0,0 +1,32 @@
+#!/usr/bin/env ruby
+
+begin
+ require 'dcpu16'
+rescue LoadError
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__))
+ require 'dcpu16'
+end
+
+if ARGV.length == 1
+ filename = ARGV[0]
+ if filename.end_with?(".o")
+ dump = File.open(filename, "rb").read.unpack("S>*")
+ else
+ require 'tempfile'
+ file = File.open(filename)
+ assembler = DCPU16::Assembler.new(file.read)
+ tmp_file = Tempfile.new("obj")
+ assembler.write(tmp_file.path)
+ dump = tmp_file.read.unpack("S>*")
+ end
+ debugger = DCPU16::Debugger.new(dump)
+ debugger.run
+elsif ARGV.length == 2
+ file = File.open(ARGV[0])
+ assembler = DCPU16::Assembler.new(file.read)
+ assembler.write(ARGV[1])
+else
+ puts "Usage: dcpu16 <input> <output>"
+ exit
+end
+
@@ -0,0 +1,2 @@
+[0x7c21, 0x0030, 0x8011, 0x0911, 0x8000, 0x8412, 0x05fe, 0x0300, 0x7dc1, 0x0003, 0x8422, 0x09fe, 0x007b, 0x7dc1, 0x0002,0000]
+
@@ -0,0 +1,20 @@
+; Patrick Helm, deradon87@gmail.com
+;
+; Flash Screen with chars (Benchmark)
+;
+; Output:
+; * Cycle: 519002 (Cycle needed to finish)
+; * Clock: 100000 (Clock-Speed of CPU [defined])
+; * HZ: 99999.02 (Clock-Speed of CPU [real])
+
+SET C, 0x30 ; Init OuterLoop
+:outer_loop SET B, 0 ; Init InnerLoop
+:inner_loop SET [0x8000+B], C ;draw
+ADD B, 1
+IFG 0x0300, B
+SET PC, inner_loop ;jmp inner_loop
+
+ADD C,1
+IFG 0x7B, C
+SET PC, outer_loop ;jmp outer_loop
+
View
@@ -1,7 +1,9 @@
require 'dcpu16/assembler'
require 'dcpu16/cpu'
+require 'dcpu16/debugger'
require 'dcpu16/literal'
require 'dcpu16/memory'
+require 'dcpu16/screen'
require 'dcpu16/version'
module DCPU16
View
@@ -1,33 +1,275 @@
# WIP
+# Taken from: https://github.com/dcpu16/dcpu16-rb/blob/master/asm.rb
module DCPU16
class Assembler
+ class Instruction
+ attr_accessor :inst, :a, :b
+ attr_reader :num, :line
+
+ def initialize(num, line)
+ @num, @line = num, line
+ @inst, @a, @b = 0, 0, 0
+ @extensions = []
+ @references = []
+ end
+
+ def extend(value)
+ # Check value against max int
+ @extensions << value
+ end
+
+ def reference(label)
+ @references[@extensions.size] = label
+ @extensions << 0 # Placeholder
+ end
+
+ def resolve(labels)
+ @references.each_with_index do |label, i|
+ next unless label
+ location = labels[label]
+ error("Cannot find label #{label} in #{labels.keys.join(", ")}") unless location
+ @extensions[i] = location
+ end
+ end
+
+ def word
+ @inst + (@a << 4) + (@b << 10)
+ end
+
+ def words
+ [word] + @extensions.dup
+ end
+
+ def bytes
+ words.map{|w| [w].pack('n') }.join("")
+ end
+
+ def size
+ @extensions.size + 1
+ end
+
+ def hex(w)
+ "%04x" % w
+ end
+
+ def to_s
+ "#{@num}: #{words.map{|w| hex(w)}.join(" ")}" # ; #{line}"
+ end
+
+ def error(message)
+ raise Exception.new("Line #{num}: #{message}\n#{line}")
+ end
+ end
+
+
+
+ def self.declare(map, start, string)
+ count = start
+ string.split(" ").each do |token|
+ map[token] = count
+ count += 1
+ end
+ end
+
+ HEX_RE = /^0x[0-9a-fA-F]+$/
+ INT_RE = /^\d+$/
+ REG_RE = /^[A-Z]+$/
+ LABEL_RE = /^[a-z_]+$/
+ INDIRECT_RE = /^\[.+\]/
+ INDIRECT_OFFSET_RE = /^[^+]+\+[^+]+$/
+
+ EXT_PREFIX = 0
+
+ INDIRECT = 0x08
+ INDIRECT_OFFSET = 0x10
+ INDIRECT_NEXT = 0x1e
+ NEXT = 0x1f
+ LITERAL = 0x20
+
+ INSTRUCTIONS = {}
+ EXTENDED_INSTRUCTIONS = {}
+ VALUES = {}
+
+ declare(INSTRUCTIONS, 1, "SET ADD SUB MUL DIV MOD SHL SHR AND BOR XOR IFE IFN IFG IFB")
+ declare(EXTENDED_INSTRUCTIONS, 1, "JSR")
+ declare(VALUES, 0, "A B C X Y Z I J")
+ declare(VALUES, 0x18, "POP PEEK PUSH SP PC O")
+
attr_reader :input
- def initialize(text)
- @input = text
+ def initialize(input)
+ @body = []
+ @label_these = []
+ @input = input
+ self.assemble
+ end
+
+ def clean(line)
+ line.gsub(/;.*/, "").gsub(/,/, " ").gsub(/\s+/, " ").strip
+ end
+
+ def dehex(token)
+ return token.hex.to_s if HEX_RE === token
+ token
+ end
+
+ def parse_value(token, op)
+ token = dehex(token)
+
+ case token
+ when INT_RE
+ value = token.to_i
+ return LITERAL + value if value <= 31
+ op.extend value
+ return NEXT
+
+ when REG_RE
+ return VALUES[token]
+
+ when LABEL_RE
+ op.reference(token)
+ return NEXT
+
+ when INDIRECT_RE
+ inner = dehex(token[1..-2])
+ case inner
+ when INT_RE
+ value = inner.to_i
+ op.extend value
+ return INDIRECT_NEXT
+
+ when REG_RE
+ reg = VALUES[inner]
+ op.error("Can't use indirect addressing on non-basic reg #{reg}") unless reg <= VALUES["J"]
+ return INDIRECT + reg
+
+ when LABEL_RE
+ op.reference(inner)
+ return INDIRECT_NEXT
+
+ when INDIRECT_OFFSET_RE
+ offset, reg = inner.split("+").map{|x| x.strip }
+ offset = dehex(offset)
+
+ op.error("Malformed indirect offset value #{inner}") unless INT_RE === offset && REG_RE === reg
+ value = offset.to_i# + VALUES[reg]
+ op.extend value
+
+ return INDIRECT_OFFSET + VALUES[reg]
+ end
+ else
+ op.error("Unrecognized value #{token}")
+ end
end
- def dump
- raise "TODO #{caller.first}"
- lines = []
- @input.each_line do |line|
- empty = (line =~ /^\s*$/)
- comment = (line =~ /^\s*;+.*$/)
+ def parse_op(op, tokens)
+ inst_name = tokens.shift
+
+ inst_code = INSTRUCTIONS[inst_name]
+ if inst_code
+ op.inst = inst_code
+ op.a = parse_value(tokens.shift, op)
+ op.b = parse_value(tokens.shift, op)
+ return
+ end
+
+ inst_code = EXTENDED_INSTRUCTIONS[inst_name]
+ if inst_code
+ op.inst = EXT_PREFIX
+ op.a = inst_code
+ op.b = parse_value(tokens.shift, op)
+ end
+
+ raise Exception.new("No such instruction: #{inst_name}") unless inst_code
+ end
+
+ def assemble#(text)
+ text = @input
+ labels = {}
+
+ num = 0
+ location = 0
+ text.each_line do |line|
+ num += 1
+ op = Instruction.new(num, line)
+
+ cleaned = clean(line)
+ next if cleaned.empty?
+
+ tokens = cleaned.split(/\s/)
+ op.error("Wrong number of tokens - #{tokens}") unless (2..4) === tokens.size
+
+ labels[tokens.shift[1..-1]] = location if tokens[0].start_with?(":")
+ parse_op(op, tokens)
+
+ @body << op
+ location += op.size
+ end
+
+ @body.each {|op| op.resolve(labels) }
+
+ #display
+ end
- next if empty || comment
+ def display
+ @body.each { |op| puts op }
+ end
- line.gsub!(/^\s*([^;]*).*$/) { $1 } # Strip some spaces
- regex = /^(:\w*)?\s*(\w*)\s*([^,\s]*),?\s?([^\s]*).*$/
- match = line.match(regex)
+ def dump#(filename)
+ @body
+# File.open(filename, "w") do |file|
+# @body.each {|inst| file.write(inst.bytes) }
+# end
+ end
- line = { :label => match[1],
- :op => match[2],
- :a => match[3],
- :b => match[4] }
- puts line
- lines << line
+ def write(filename)
+ File.open(filename, "w") do |file|
+ @body.each {|inst| file.write(inst.bytes) }
end
end
end
end
+
+#if __FILE__ == $PROGRAM_NAME
+# asm = Assembler.new
+# filename = "#{ARGV.first || "out.s"}.o"
+# asm.assemble ARGF
+# asm.dump filename
+#end
+
+
+
+
+
+
+
+
+
+#attr_reader :input
+#def initialize(text)
+# @input = text
+#end
+
+#def dump
+# lines = []
+# @input.each_line do |line|
+# empty = (line =~ /^\s*$/)
+# comment = (line =~ /^\s*;+.*$/)
+
+# next if empty || comment
+
+# line.gsub!(/^\s*([^;]*).*$/) { $1 } # Strip some spaces
+
+# regex = /^(:\w*)?\s*(\w*)\s*([^,\s]*),?\s?([^\s]*).*$/
+# match = line.match(regex)
+
+# line = { :label => match[1],
+# :op => match[2],
+# :a => match[3],
+# :b => match[4] }
+# puts line
+# lines << line
+# end
+#end
+
View
@@ -108,6 +108,19 @@ def step
notify_observers(self)
end
+ def to_s
+ <<EOF
+###########
+### CPU ###
+
+* Cycle: #{cycle}
+# Clock: #{clock_cycle}
+* HZ: #{hz}
+* Last: #{last_instruction.inspect}
+* Reg.: #{registers.inspect}
+EOF
+ end
+
end
end
Oops, something went wrong.

0 comments on commit 24416dd

Please sign in to comment.