Rdcpu16 0.0.3.1 → 0.0.4.alpha
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/dcpu16.rb +32 -0
- data/lib/dcpu16/assembler.rb +260 -18
- data/lib/dcpu16/{instruction.rb → cpu/instruction.rb} +32 -30
- data/lib/dcpu16/cpu/operand.rb +43 -0
- data/lib/dcpu16/cpu/register.rb +37 -0
- data/lib/dcpu16/cpu.rb +25 -17
- data/lib/dcpu16/debugger.rb +31 -0
- data/lib/dcpu16/memory/word.rb +22 -0
- data/lib/dcpu16/memory.rb +6 -5
- data/lib/dcpu16/screen.rb +155 -0
- data/lib/dcpu16/version.rb +1 -1
- data/lib/dcpu16/version.rb~ +1 -1
- data/lib/dcpu16.rb +4 -0
- data/spec/fixtures/example.dasm16 +20 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/unit/assembler_spec.rb +14 -0
- data/spec/unit/{dcpu16_cpu_spec.rb → cpu/cpu_spec.rb} +4 -4
- data/spec/unit/{dcpu16_instruction_spec.rb → cpu/instruction_spec.rb} +7 -7
- data/spec/unit/{dcpu16_operand_spec.rb → cpu/operand_spec.rb} +14 -14
- data/spec/unit/cpu/register_spec.rb +6 -0
- data/spec/unit/{dcpu16_memory_spec.rb → memory/memory_spec.rb} +1 -1
- data/spec/unit/{dcpu16_word_spec.rb → memory/word_spec.rb} +3 -3
- data/spec/unit/screen_spec.rb +51 -0
- data/spec/unit/support/screen_observer.rb +13 -0
- metadata +36 -29
- data/lib/dcpu16/operand.rb +0 -61
- data/lib/dcpu16/register.rb +0 -35
- data/lib/dcpu16/word.rb +0 -20
- data/spec/fixtures/example.asm +0 -29
- data/spec/unit/dcpu16_assembler_spec.rb +0 -10
- data/spec/unit/dcpu16_register_spec.rb +0 -6
- /data/spec/unit/{dcpu16_instructions_spec.rb → cpu/instructions_spec.rb} +0 -0
data/bin/dcpu16.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'dcpu16'
|
5
|
+
rescue LoadError
|
6
|
+
$LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__))
|
7
|
+
require 'dcpu16'
|
8
|
+
end
|
9
|
+
|
10
|
+
if ARGV.length == 1
|
11
|
+
filename = ARGV[0]
|
12
|
+
if filename.end_with?(".o")
|
13
|
+
dump = File.open(filename, "rb").read.unpack("S>*")
|
14
|
+
else
|
15
|
+
require 'tempfile'
|
16
|
+
file = File.open(filename)
|
17
|
+
assembler = DCPU16::Assembler.new(file.read)
|
18
|
+
tmp_file = Tempfile.new("obj")
|
19
|
+
assembler.write(tmp_file.path)
|
20
|
+
dump = tmp_file.read.unpack("S>*")
|
21
|
+
end
|
22
|
+
debugger = DCPU16::Debugger.new(dump)
|
23
|
+
debugger.run
|
24
|
+
elsif ARGV.length == 2
|
25
|
+
file = File.open(ARGV[0])
|
26
|
+
assembler = DCPU16::Assembler.new(file.read)
|
27
|
+
assembler.write(ARGV[1])
|
28
|
+
else
|
29
|
+
puts "Usage: dcpu16 <input> <output>"
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
data/lib/dcpu16/assembler.rb
CHANGED
@@ -1,33 +1,275 @@
|
|
1
1
|
# WIP
|
2
|
+
# Taken from: https://github.com/dcpu16/dcpu16-rb/blob/master/asm.rb
|
2
3
|
module DCPU16
|
3
4
|
class Assembler
|
5
|
+
class Instruction
|
6
|
+
attr_accessor :inst, :a, :b
|
7
|
+
attr_reader :num, :line
|
8
|
+
|
9
|
+
def initialize(num, line)
|
10
|
+
@num, @line = num, line
|
11
|
+
@inst, @a, @b = 0, 0, 0
|
12
|
+
@extensions = []
|
13
|
+
@references = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def extend(value)
|
17
|
+
# Check value against max int
|
18
|
+
@extensions << value
|
19
|
+
end
|
20
|
+
|
21
|
+
def reference(label)
|
22
|
+
@references[@extensions.size] = label
|
23
|
+
@extensions << 0 # Placeholder
|
24
|
+
end
|
25
|
+
|
26
|
+
def resolve(labels)
|
27
|
+
@references.each_with_index do |label, i|
|
28
|
+
next unless label
|
29
|
+
location = labels[label]
|
30
|
+
error("Cannot find label #{label} in #{labels.keys.join(", ")}") unless location
|
31
|
+
@extensions[i] = location
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def word
|
36
|
+
@inst + (@a << 4) + (@b << 10)
|
37
|
+
end
|
38
|
+
|
39
|
+
def words
|
40
|
+
[word] + @extensions.dup
|
41
|
+
end
|
42
|
+
|
43
|
+
def bytes
|
44
|
+
words.map{|w| [w].pack('n') }.join("")
|
45
|
+
end
|
46
|
+
|
47
|
+
def size
|
48
|
+
@extensions.size + 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def hex(w)
|
52
|
+
"%04x" % w
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
"#{@num}: #{words.map{|w| hex(w)}.join(" ")}" # ; #{line}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def error(message)
|
60
|
+
raise Exception.new("Line #{num}: #{message}\n#{line}")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
def self.declare(map, start, string)
|
67
|
+
count = start
|
68
|
+
string.split(" ").each do |token|
|
69
|
+
map[token] = count
|
70
|
+
count += 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
HEX_RE = /^0x[0-9a-fA-F]+$/
|
75
|
+
INT_RE = /^\d+$/
|
76
|
+
REG_RE = /^[A-Z]+$/
|
77
|
+
LABEL_RE = /^[a-z_]+$/
|
78
|
+
INDIRECT_RE = /^\[.+\]/
|
79
|
+
INDIRECT_OFFSET_RE = /^[^+]+\+[^+]+$/
|
80
|
+
|
81
|
+
EXT_PREFIX = 0
|
82
|
+
|
83
|
+
INDIRECT = 0x08
|
84
|
+
INDIRECT_OFFSET = 0x10
|
85
|
+
INDIRECT_NEXT = 0x1e
|
86
|
+
NEXT = 0x1f
|
87
|
+
LITERAL = 0x20
|
88
|
+
|
89
|
+
INSTRUCTIONS = {}
|
90
|
+
EXTENDED_INSTRUCTIONS = {}
|
91
|
+
VALUES = {}
|
92
|
+
|
93
|
+
declare(INSTRUCTIONS, 1, "SET ADD SUB MUL DIV MOD SHL SHR AND BOR XOR IFE IFN IFG IFB")
|
94
|
+
declare(EXTENDED_INSTRUCTIONS, 1, "JSR")
|
95
|
+
declare(VALUES, 0, "A B C X Y Z I J")
|
96
|
+
declare(VALUES, 0x18, "POP PEEK PUSH SP PC O")
|
97
|
+
|
4
98
|
attr_reader :input
|
5
|
-
def initialize(
|
6
|
-
@
|
99
|
+
def initialize(input)
|
100
|
+
@body = []
|
101
|
+
@label_these = []
|
102
|
+
@input = input
|
103
|
+
self.assemble
|
104
|
+
end
|
105
|
+
|
106
|
+
def clean(line)
|
107
|
+
line.gsub(/;.*/, "").gsub(/,/, " ").gsub(/\s+/, " ").strip
|
108
|
+
end
|
109
|
+
|
110
|
+
def dehex(token)
|
111
|
+
return token.hex.to_s if HEX_RE === token
|
112
|
+
token
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_value(token, op)
|
116
|
+
token = dehex(token)
|
117
|
+
|
118
|
+
case token
|
119
|
+
when INT_RE
|
120
|
+
value = token.to_i
|
121
|
+
return LITERAL + value if value <= 31
|
122
|
+
op.extend value
|
123
|
+
return NEXT
|
124
|
+
|
125
|
+
when REG_RE
|
126
|
+
return VALUES[token]
|
127
|
+
|
128
|
+
when LABEL_RE
|
129
|
+
op.reference(token)
|
130
|
+
return NEXT
|
131
|
+
|
132
|
+
when INDIRECT_RE
|
133
|
+
inner = dehex(token[1..-2])
|
134
|
+
case inner
|
135
|
+
when INT_RE
|
136
|
+
value = inner.to_i
|
137
|
+
op.extend value
|
138
|
+
return INDIRECT_NEXT
|
139
|
+
|
140
|
+
when REG_RE
|
141
|
+
reg = VALUES[inner]
|
142
|
+
op.error("Can't use indirect addressing on non-basic reg #{reg}") unless reg <= VALUES["J"]
|
143
|
+
return INDIRECT + reg
|
144
|
+
|
145
|
+
when LABEL_RE
|
146
|
+
op.reference(inner)
|
147
|
+
return INDIRECT_NEXT
|
148
|
+
|
149
|
+
when INDIRECT_OFFSET_RE
|
150
|
+
offset, reg = inner.split("+").map{|x| x.strip }
|
151
|
+
offset = dehex(offset)
|
152
|
+
|
153
|
+
op.error("Malformed indirect offset value #{inner}") unless INT_RE === offset && REG_RE === reg
|
154
|
+
value = offset.to_i# + VALUES[reg]
|
155
|
+
op.extend value
|
156
|
+
|
157
|
+
return INDIRECT_OFFSET + VALUES[reg]
|
158
|
+
end
|
159
|
+
else
|
160
|
+
op.error("Unrecognized value #{token}")
|
161
|
+
end
|
7
162
|
end
|
8
163
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
164
|
+
def parse_op(op, tokens)
|
165
|
+
inst_name = tokens.shift
|
166
|
+
|
167
|
+
inst_code = INSTRUCTIONS[inst_name]
|
168
|
+
if inst_code
|
169
|
+
op.inst = inst_code
|
170
|
+
op.a = parse_value(tokens.shift, op)
|
171
|
+
op.b = parse_value(tokens.shift, op)
|
172
|
+
return
|
173
|
+
end
|
174
|
+
|
175
|
+
inst_code = EXTENDED_INSTRUCTIONS[inst_name]
|
176
|
+
if inst_code
|
177
|
+
op.inst = EXT_PREFIX
|
178
|
+
op.a = inst_code
|
179
|
+
op.b = parse_value(tokens.shift, op)
|
180
|
+
end
|
181
|
+
|
182
|
+
raise Exception.new("No such instruction: #{inst_name}") unless inst_code
|
183
|
+
end
|
184
|
+
|
185
|
+
def assemble#(text)
|
186
|
+
text = @input
|
187
|
+
labels = {}
|
188
|
+
|
189
|
+
num = 0
|
190
|
+
location = 0
|
191
|
+
text.each_line do |line|
|
192
|
+
num += 1
|
193
|
+
op = Instruction.new(num, line)
|
194
|
+
|
195
|
+
cleaned = clean(line)
|
196
|
+
next if cleaned.empty?
|
197
|
+
|
198
|
+
tokens = cleaned.split(/\s/)
|
199
|
+
op.error("Wrong number of tokens - #{tokens}") unless (2..4) === tokens.size
|
200
|
+
|
201
|
+
labels[tokens.shift[1..-1]] = location if tokens[0].start_with?(":")
|
202
|
+
parse_op(op, tokens)
|
203
|
+
|
204
|
+
@body << op
|
205
|
+
location += op.size
|
206
|
+
end
|
207
|
+
|
208
|
+
@body.each {|op| op.resolve(labels) }
|
209
|
+
|
210
|
+
#display
|
211
|
+
end
|
15
212
|
|
16
|
-
|
213
|
+
def display
|
214
|
+
@body.each { |op| puts op }
|
215
|
+
end
|
17
216
|
|
18
|
-
line.gsub!(/^\s*([^;]*).*$/) { $1 } # Strip some spaces
|
19
217
|
|
20
|
-
|
21
|
-
|
218
|
+
def dump#(filename)
|
219
|
+
@body
|
220
|
+
# File.open(filename, "w") do |file|
|
221
|
+
# @body.each {|inst| file.write(inst.bytes) }
|
222
|
+
# end
|
223
|
+
end
|
22
224
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
:b => match[4] }
|
27
|
-
puts line
|
28
|
-
lines << line
|
225
|
+
def write(filename)
|
226
|
+
File.open(filename, "w") do |file|
|
227
|
+
@body.each {|inst| file.write(inst.bytes) }
|
29
228
|
end
|
30
229
|
end
|
31
230
|
end
|
32
231
|
end
|
33
232
|
|
233
|
+
|
234
|
+
#if __FILE__ == $PROGRAM_NAME
|
235
|
+
# asm = Assembler.new
|
236
|
+
# filename = "#{ARGV.first || "out.s"}.o"
|
237
|
+
# asm.assemble ARGF
|
238
|
+
# asm.dump filename
|
239
|
+
#end
|
240
|
+
|
241
|
+
|
242
|
+
|
243
|
+
|
244
|
+
|
245
|
+
|
246
|
+
|
247
|
+
|
248
|
+
|
249
|
+
#attr_reader :input
|
250
|
+
#def initialize(text)
|
251
|
+
# @input = text
|
252
|
+
#end
|
253
|
+
|
254
|
+
#def dump
|
255
|
+
# lines = []
|
256
|
+
# @input.each_line do |line|
|
257
|
+
# empty = (line =~ /^\s*$/)
|
258
|
+
# comment = (line =~ /^\s*;+.*$/)
|
259
|
+
|
260
|
+
# next if empty || comment
|
261
|
+
|
262
|
+
# line.gsub!(/^\s*([^;]*).*$/) { $1 } # Strip some spaces
|
263
|
+
|
264
|
+
# regex = /^(:\w*)?\s*(\w*)\s*([^,\s]*),?\s?([^\s]*).*$/
|
265
|
+
# match = line.match(regex)
|
266
|
+
|
267
|
+
# line = { :label => match[1],
|
268
|
+
# :op => match[2],
|
269
|
+
# :a => match[3],
|
270
|
+
# :b => match[4] }
|
271
|
+
# puts line
|
272
|
+
# lines << line
|
273
|
+
# end
|
274
|
+
#end
|
275
|
+
|
@@ -19,38 +19,40 @@
|
|
19
19
|
# 1111 1100 0000 0000 -> 0xfc00
|
20
20
|
|
21
21
|
module DCPU16
|
22
|
-
class
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
@
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
22
|
+
class CPU
|
23
|
+
class Instruction
|
24
|
+
INSTRUCTIONS = [
|
25
|
+
:reserved,
|
26
|
+
:set, :add, :sub, :mul, :div, :mod,
|
27
|
+
:shl, :shr,
|
28
|
+
:and, :bor, :xor,
|
29
|
+
:ife, :ifn, :ifg, :ifb
|
30
|
+
]
|
31
|
+
|
32
|
+
NON_BASIC_INSTRUCTIONS = [
|
33
|
+
:reserved,
|
34
|
+
:jsr
|
35
|
+
]
|
36
|
+
|
37
|
+
attr_reader :opcode, :a, :b, :word
|
38
|
+
|
39
|
+
def initialize(word = nil)
|
40
|
+
@word = word.value
|
41
|
+
@opcode = @word & 0x000F
|
42
|
+
|
43
|
+
if @opcode == 0x0
|
44
|
+
@non_basic = true
|
45
|
+
@opcode = (@word >> 4) & 0x3f
|
46
|
+
@a = @word >> 10
|
47
|
+
else
|
48
|
+
@a = (@word >> 4) & 0x3f
|
49
|
+
@b = @word >> 10
|
50
|
+
end
|
49
51
|
end
|
50
|
-
end
|
51
52
|
|
52
|
-
|
53
|
-
|
53
|
+
def op
|
54
|
+
@non_basic ? NON_BASIC_INSTRUCTIONS[@opcode] : INSTRUCTIONS[@opcode]
|
55
|
+
end
|
54
56
|
end
|
55
57
|
end
|
56
58
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# TODO: Test && Refacoring
|
2
|
+
module DCPU16
|
3
|
+
module Operand
|
4
|
+
class NotFound < StandardError; end
|
5
|
+
|
6
|
+
def get_operand(value)
|
7
|
+
if (0x00..0x07).include?(value) # register (A, B, C, X, Y, Z, I or J, in that order)
|
8
|
+
@registers[value]
|
9
|
+
elsif (0x08..0x0f).include?(value) # [register]
|
10
|
+
register = @registers[value - 0x08]
|
11
|
+
@memory[register]
|
12
|
+
elsif (0x10..0x17).include?(value)
|
13
|
+
@cycle += 1
|
14
|
+
offset = @memory[@PC].value + @registers[value - 0x10].value
|
15
|
+
@memory[offset].tap { @PC += 1 }
|
16
|
+
elsif value == 0x18 # POP / [@SP++]
|
17
|
+
@memory[@SP].tap { @SP += 1 }
|
18
|
+
elsif value == 0x19 # PEEK / [@SP]
|
19
|
+
@memory[@SP]
|
20
|
+
elsif value == 0x1a # PUSH / [--@SP]
|
21
|
+
@SP -= 1
|
22
|
+
@memory[@SP]
|
23
|
+
elsif value == 0x1b # @SP
|
24
|
+
@SP
|
25
|
+
elsif value == 0x1c # @PC
|
26
|
+
@PC
|
27
|
+
elsif value == 0x1d # O
|
28
|
+
@O
|
29
|
+
elsif value == 0x1e # [next word]
|
30
|
+
@cycle += 1
|
31
|
+
@memory[@memory[@PC]].tap { @PC += 1 }
|
32
|
+
elsif value == 0x1f # next word (literal)
|
33
|
+
@cycle += 1
|
34
|
+
@memory[@PC].tap { @PC += 1 }
|
35
|
+
elsif (0x20..0x3f).include?(value) # literal value 0x00-0x1f (literal)
|
36
|
+
DCPU16::Literal.new(value - 0x20)
|
37
|
+
else
|
38
|
+
raise NotFound
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module DCPU16
|
2
|
+
class CPU
|
3
|
+
class Register
|
4
|
+
attr_reader :value
|
5
|
+
|
6
|
+
def initialize(value = 0x0)
|
7
|
+
@default_value = value
|
8
|
+
reset
|
9
|
+
end
|
10
|
+
|
11
|
+
def value
|
12
|
+
warn "[Register #{self}] No Value defined" unless @value
|
13
|
+
@value
|
14
|
+
end
|
15
|
+
alias_method :read, :value
|
16
|
+
|
17
|
+
def +(value)
|
18
|
+
@value += value
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def -(value)
|
23
|
+
@value -= value
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def write(value)
|
28
|
+
@value = value
|
29
|
+
end
|
30
|
+
|
31
|
+
def reset
|
32
|
+
@value = @default_value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
data/lib/dcpu16/cpu.rb
CHANGED
@@ -1,18 +1,16 @@
|
|
1
|
+
require "observer"
|
1
2
|
require 'dcpu16/support/debug'
|
2
3
|
|
4
|
+
require 'dcpu16/cpu/instruction'
|
3
5
|
require 'dcpu16/cpu/instructions'
|
6
|
+
require 'dcpu16/cpu/operand'
|
7
|
+
require 'dcpu16/cpu/register'
|
4
8
|
|
5
|
-
require 'dcpu16/instruction'
|
6
|
-
require 'dcpu16/literal'
|
7
|
-
require 'dcpu16/memory'
|
8
|
-
require 'dcpu16/operand'
|
9
|
-
require 'dcpu16/register'
|
10
|
-
|
11
|
-
require "observer"
|
12
9
|
|
13
10
|
module DCPU16
|
14
11
|
class CPU
|
15
12
|
include DCPU16::Debug
|
13
|
+
include DCPU16::Operand
|
16
14
|
include DCPU16::Instructions
|
17
15
|
include Observable
|
18
16
|
|
@@ -34,10 +32,10 @@ module DCPU16
|
|
34
32
|
def initialize(memory = [])
|
35
33
|
@cycle = 0
|
36
34
|
@memory = DCPU16::Memory.new(memory)
|
37
|
-
@registers = Array.new(REGISTERS_COUNT) {
|
38
|
-
@PC =
|
39
|
-
@SP =
|
40
|
-
@O =
|
35
|
+
@registers = Array.new(REGISTERS_COUNT) { Register.new }
|
36
|
+
@PC = Register.new(0x0)
|
37
|
+
@SP = Register.new(0xFFFF)
|
38
|
+
@O = Register.new(0x0)
|
41
39
|
|
42
40
|
@clock_cycle = CLOCK_CYCLE
|
43
41
|
@debug = true
|
@@ -85,7 +83,7 @@ module DCPU16
|
|
85
83
|
end
|
86
84
|
|
87
85
|
# Perform a single step
|
88
|
-
# TODO: Refacor if/else/if/else ...
|
86
|
+
# TODO: Refacor if/else/if/else ... hacky mess here
|
89
87
|
def step
|
90
88
|
@instruction = Instruction.new(@memory.read(@PC))
|
91
89
|
@PC += 1
|
@@ -97,22 +95,32 @@ module DCPU16
|
|
97
95
|
if @skip
|
98
96
|
@skip = false
|
99
97
|
else
|
100
|
-
if b
|
98
|
+
if b # Basic Instruction
|
101
99
|
result = self.send(op, a.value, b.value)
|
102
|
-
else
|
100
|
+
else # Non-Basic Instruction
|
103
101
|
result = self.send(op, a.value)
|
104
102
|
end
|
105
103
|
a.write(result) if result
|
106
104
|
end
|
107
105
|
|
106
|
+
# Notify observers
|
108
107
|
changed
|
109
108
|
notify_observers(self)
|
110
109
|
end
|
111
110
|
|
112
|
-
|
113
|
-
|
114
|
-
|
111
|
+
def to_s
|
112
|
+
<<EOF
|
113
|
+
###########
|
114
|
+
### CPU ###
|
115
|
+
|
116
|
+
* Cycle: #{cycle}
|
117
|
+
# Clock: #{clock_cycle}
|
118
|
+
* HZ: #{hz}
|
119
|
+
* Last: #{last_instruction.inspect}
|
120
|
+
* Reg.: #{registers.inspect}
|
121
|
+
EOF
|
115
122
|
end
|
123
|
+
|
116
124
|
end
|
117
125
|
end
|
118
126
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module DCPU16
|
2
|
+
class Debugger
|
3
|
+
attr_reader :cpu, :screen
|
4
|
+
def initialize(dump = [])
|
5
|
+
@cpu = DCPU16::CPU.new(dump)
|
6
|
+
@screen = DCPU16::Screen.new(@cpu.memory)
|
7
|
+
|
8
|
+
@cpu.add_observer(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
clear_screen
|
13
|
+
begin
|
14
|
+
@cpu.run
|
15
|
+
rescue DCPU16::Instructions::Reserved => e
|
16
|
+
puts @cpu.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Observer
|
21
|
+
def update(cpu)
|
22
|
+
#cpu
|
23
|
+
end
|
24
|
+
|
25
|
+
# Clears screen for output
|
26
|
+
def clear_screen
|
27
|
+
print @screen.to_s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module DCPU16
|
2
|
+
class Memory
|
3
|
+
class Word
|
4
|
+
def initialize(value, memory = nil, offset = nil)
|
5
|
+
@value = value
|
6
|
+
@memory = memory
|
7
|
+
@offset = offset
|
8
|
+
end
|
9
|
+
|
10
|
+
def value
|
11
|
+
@value
|
12
|
+
end
|
13
|
+
alias_method :read, :value
|
14
|
+
|
15
|
+
def write(value)
|
16
|
+
@value = value
|
17
|
+
@memory.write(@offset, value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
data/lib/dcpu16/memory.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'dcpu16/word'
|
1
|
+
require 'dcpu16/memory/word'
|
2
2
|
require "observer"
|
3
3
|
|
4
4
|
module DCPU16
|
@@ -21,7 +21,7 @@ module DCPU16
|
|
21
21
|
# HACK: so we can just pass a Fixnum or a Register
|
22
22
|
offset = offset.value if offset.respond_to? :value
|
23
23
|
|
24
|
-
|
24
|
+
Word.new(@memory[offset], self, offset)
|
25
25
|
end
|
26
26
|
|
27
27
|
def write(offset, value)
|
@@ -29,6 +29,8 @@ module DCPU16
|
|
29
29
|
offset = offset.value if offset.respond_to? :value
|
30
30
|
|
31
31
|
@memory[offset] = value
|
32
|
+
|
33
|
+
# Notify observers
|
32
34
|
changed
|
33
35
|
notify_observers(offset, value)
|
34
36
|
end
|
@@ -40,13 +42,12 @@ module DCPU16
|
|
40
42
|
@memory.length
|
41
43
|
end
|
42
44
|
|
43
|
-
private
|
44
45
|
def [](key)
|
45
|
-
|
46
|
+
read(key)
|
46
47
|
end
|
47
48
|
|
48
49
|
def []=(key, value)
|
49
|
-
|
50
|
+
write(key, value)
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|