Rdcpu16 0.0.3.1 → 0.0.4.alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|