Navigation Menu

Skip to content

Commit

Permalink
first draft of working emulator
Browse files Browse the repository at this point in the history
  • Loading branch information
Deradon committed Apr 11, 2012
1 parent 90f5f28 commit 24416dd
Show file tree
Hide file tree
Showing 14 changed files with 621 additions and 20 deletions.
11 changes: 11 additions & 0 deletions TODO
Expand Up @@ -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

32 changes: 32 additions & 0 deletions bin/dcpu16.rb
@@ -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

2 changes: 2 additions & 0 deletions examples/array/screen.arr
@@ -0,0 +1,2 @@
[0x7c21, 0x0030, 0x8011, 0x0911, 0x8000, 0x8412, 0x05fe, 0x0300, 0x7dc1, 0x0003, 0x8422, 0x09fe, 0x007b, 0x7dc1, 0x0002,0000]

20 changes: 20 additions & 0 deletions examples/dasm16/screen.dasm16
@@ -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

2 changes: 2 additions & 0 deletions lib/dcpu16.rb
@@ -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
Expand Down
278 changes: 260 additions & 18 deletions lib/dcpu16/assembler.rb
@@ -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

13 changes: 13 additions & 0 deletions lib/dcpu16/cpu.rb
Expand Up @@ -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

0 comments on commit 24416dd

Please sign in to comment.