Rdcpu16 0.0.4.beta → 0.0.4.0
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 +8 -1
- data/lib/dcpu16/assembler.rb +55 -242
- data/lib/dcpu16/assembler/constants.rb +48 -0
- data/lib/dcpu16/assembler/instruction.rb +235 -0
- data/lib/dcpu16/assembler/line.rb +16 -0
- data/lib/dcpu16/cpu.rb +33 -23
- data/lib/dcpu16/cpu/instruction.rb +1 -0
- data/lib/dcpu16/cpu/register.rb +15 -2
- data/lib/dcpu16/debugger.rb +31 -5
- data/lib/dcpu16/memory.rb +1 -0
- data/lib/dcpu16/memory/word.rb +1 -0
- data/lib/dcpu16/screen.rb +8 -23
- data/lib/dcpu16/version.rb +1 -1
- data/lib/dcpu16/version.rb~ +1 -1
- data/spec/fixtures/example.dasm16 +2 -0
- data/spec/unit/assembler_spec.rb +4 -3
- data/spec/unit/screen_spec.rb +1 -1
- metadata +10 -7
data/bin/dcpu16
CHANGED
|
@@ -19,12 +19,19 @@ if ARGV.length == 1
|
|
|
19
19
|
assembler.write(tmp_file.path)
|
|
20
20
|
dump = tmp_file.read.unpack("S>*")
|
|
21
21
|
end
|
|
22
|
-
|
|
22
|
+
|
|
23
|
+
# TODO: get args from command line
|
|
24
|
+
debugger = DCPU16::Debugger.new(dump, :update_every => 10000, :step_mode => false)
|
|
23
25
|
debugger.run
|
|
24
26
|
elsif ARGV.length == 2
|
|
25
27
|
file = File.open(ARGV[0])
|
|
26
28
|
assembler = DCPU16::Assembler.new(file.read)
|
|
27
29
|
assembler.write(ARGV[1])
|
|
30
|
+
|
|
31
|
+
File.open(ARGV[1] + ".hex", "w") do |file|
|
|
32
|
+
file.write(assembler.to_s)
|
|
33
|
+
end
|
|
34
|
+
|
|
28
35
|
else
|
|
29
36
|
puts "Usage: dcpu16 <input> <output>"
|
|
30
37
|
exit
|
data/lib/dcpu16/assembler.rb
CHANGED
|
@@ -1,275 +1,88 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
|
1
|
+
require 'dcpu16/assembler/constants'
|
|
2
|
+
require 'dcpu16/assembler/instruction'
|
|
3
|
+
require 'dcpu16/assembler/line'
|
|
34
4
|
|
|
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
5
|
|
|
6
|
+
module DCPU16
|
|
7
|
+
class Assembler
|
|
98
8
|
attr_reader :input
|
|
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
9
|
|
|
110
|
-
def
|
|
111
|
-
|
|
112
|
-
|
|
10
|
+
def initialize(text)
|
|
11
|
+
@input = text
|
|
12
|
+
assemble
|
|
113
13
|
end
|
|
114
14
|
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
162
|
-
end
|
|
163
|
-
|
|
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
|
|
15
|
+
# Assemble the given input.
|
|
16
|
+
def assemble
|
|
190
17
|
location = 0
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
op = Instruction.new(num, line)
|
|
194
|
-
|
|
195
|
-
cleaned = clean(line)
|
|
196
|
-
next if cleaned.empty?
|
|
18
|
+
@labels = {}
|
|
19
|
+
@body = []
|
|
197
20
|
|
|
198
|
-
|
|
199
|
-
|
|
21
|
+
lines.each do |line|
|
|
22
|
+
# Set label location
|
|
23
|
+
@labels[line.label] = location if line.label
|
|
200
24
|
|
|
201
|
-
|
|
202
|
-
|
|
25
|
+
# Skip when no op
|
|
26
|
+
next if line.op.empty?
|
|
203
27
|
|
|
28
|
+
op = Instruction.create(line.op, line.args, location)
|
|
204
29
|
@body << op
|
|
205
30
|
location += op.size
|
|
206
31
|
end
|
|
207
32
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
33
|
+
# Apply labels
|
|
34
|
+
begin
|
|
35
|
+
@body.each { |op| op.apply_labels(@labels) }
|
|
36
|
+
rescue Exception => e
|
|
37
|
+
puts @labels.inspect
|
|
38
|
+
raise e
|
|
39
|
+
end
|
|
211
40
|
end
|
|
212
41
|
|
|
213
|
-
|
|
214
|
-
|
|
42
|
+
# Returns assembled bytes
|
|
43
|
+
def bytes
|
|
44
|
+
@body.map { |op| op.bytes }.join
|
|
215
45
|
end
|
|
216
46
|
|
|
47
|
+
def to_s
|
|
48
|
+
@body.join("\n")
|
|
49
|
+
end
|
|
217
50
|
|
|
218
|
-
def
|
|
219
|
-
@
|
|
220
|
-
# File.open(filename, "w") do |file|
|
|
221
|
-
# @body.each {|inst| file.write(inst.bytes) }
|
|
222
|
-
# end
|
|
51
|
+
def lines
|
|
52
|
+
@lines ||= read_lines
|
|
223
53
|
end
|
|
224
54
|
|
|
55
|
+
# Write bytes to file
|
|
225
56
|
def write(filename)
|
|
226
|
-
File.open(filename, "w")
|
|
227
|
-
@body.each {|inst| file.write(inst.bytes) }
|
|
228
|
-
end
|
|
57
|
+
File.open(filename, "w") { |file| file.write(bytes) }
|
|
229
58
|
end
|
|
230
|
-
end
|
|
231
|
-
end
|
|
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
59
|
|
|
241
60
|
|
|
242
61
|
|
|
62
|
+
private
|
|
243
63
|
|
|
64
|
+
def read_lines
|
|
65
|
+
@lines = []
|
|
66
|
+
@input.each_line do |line|
|
|
67
|
+
empty = (line =~ RE_LINE_EMPTY)
|
|
68
|
+
comment = (line =~ RE_LINE_COMMENT)
|
|
244
69
|
|
|
70
|
+
next if empty || comment
|
|
245
71
|
|
|
72
|
+
line.gsub!(RE_LINE_CLEAN) { $1 }
|
|
246
73
|
|
|
74
|
+
match = line.match(RE_LINE)
|
|
75
|
+
label = match[1]
|
|
76
|
+
op = match[2]
|
|
77
|
+
args = match[3]
|
|
78
|
+
args = args.scan( RE_ARGS ).flatten.compact
|
|
247
79
|
|
|
80
|
+
line = Line.new(label, op, args)
|
|
81
|
+
@lines << line
|
|
82
|
+
end
|
|
248
83
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
84
|
+
return @lines
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
275
88
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module DCPU16
|
|
2
|
+
class Assembler
|
|
3
|
+
RE_LINE_EMPTY = /^\s*$/
|
|
4
|
+
RE_LINE_COMMENT = /^\s*;+.*$/
|
|
5
|
+
|
|
6
|
+
#RE_LINE_CLEAN = /^\s*([^;]*).*$/
|
|
7
|
+
# "asd" | 'asd' | [^"';"]
|
|
8
|
+
RE_LINE_CLEAN = /^\s*((?:(?:"[^"]*")|(?:'[^']*')|(?:[^;'"]*))*).*$/
|
|
9
|
+
|
|
10
|
+
# Parsing the line
|
|
11
|
+
RE_LINE = /\A
|
|
12
|
+
(:\w*)? # label
|
|
13
|
+
\s*
|
|
14
|
+
(\w*) # op
|
|
15
|
+
\s*
|
|
16
|
+
(.*?) # args
|
|
17
|
+
\s*
|
|
18
|
+
\Z/x
|
|
19
|
+
|
|
20
|
+
# Parsing the arguments
|
|
21
|
+
# TODO: no comma needed atm, fix this
|
|
22
|
+
RE_ARGS = /([^'",\s]+)|("[^"]+")|('[^']+')/
|
|
23
|
+
# args.scan( RE_ARGS ).flatten.compact
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Regex against a op: 1: label | 2: OP | 3: A | 4: b
|
|
27
|
+
#RE_OP = /^(:\w*)?\s*(\w*)\s*([^,\s]*),?\s?([^\s]*).*$/
|
|
28
|
+
RE_OP = /^(:\w*)?\s*(\w*)\s*([^,\n]*),?\s?([^\s]*).*$/
|
|
29
|
+
|
|
30
|
+
REGISTER = {
|
|
31
|
+
:A => 0x0,
|
|
32
|
+
:B => 0x1,
|
|
33
|
+
:C => 0x2,
|
|
34
|
+
:X => 0x3,
|
|
35
|
+
:Y => 0x4,
|
|
36
|
+
:Z => 0x5,
|
|
37
|
+
:I => 0x6,
|
|
38
|
+
:J => 0x7,
|
|
39
|
+
:POP => 0x18,
|
|
40
|
+
:PEEK => 0x19,
|
|
41
|
+
:PUSH => 0x1a,
|
|
42
|
+
:SP => 0x1b,
|
|
43
|
+
:PC => 0x1c,
|
|
44
|
+
:O => 0x1d
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
module DCPU16
|
|
2
|
+
class Assembler
|
|
3
|
+
|
|
4
|
+
BASIC_INSTRUCTIONS = {
|
|
5
|
+
:SET => 0x1,
|
|
6
|
+
:ADD => 0x2,
|
|
7
|
+
:SUB => 0x3,
|
|
8
|
+
:MUL => 0x4,
|
|
9
|
+
:DIV => 0x5,
|
|
10
|
+
:MOD => 0x6,
|
|
11
|
+
:SHL => 0x7,
|
|
12
|
+
:SHR => 0x8,
|
|
13
|
+
:AND => 0x9,
|
|
14
|
+
:BOR => 0xa,
|
|
15
|
+
:XOR => 0xb,
|
|
16
|
+
:IFE => 0xc,
|
|
17
|
+
:IFN => 0xd,
|
|
18
|
+
:IFG => 0xe,
|
|
19
|
+
:IFB => 0xf
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
NON_BASIC_INSTRUCTIONS = {
|
|
23
|
+
:JSR => 0x01
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
INSTRUCTIONS = BASIC_INSTRUCTIONS.merge(NON_BASIC_INSTRUCTIONS)
|
|
27
|
+
# INDIRECT: [x+y] =>
|
|
28
|
+
|
|
29
|
+
# + 0x00-0x07: register (A, B, C, X, Y, Z, I or J, in that order) SET A 0x0001
|
|
30
|
+
# I 0x08-0x0f: [register] SET [A] 0x????
|
|
31
|
+
# I 0x10-0x17: [next word + register] SET [A+0x1000] 0x???? 0x????
|
|
32
|
+
# + 0x18: POP / [SP++]
|
|
33
|
+
# + 0x19: PEEK / [SP]
|
|
34
|
+
# + 0x1a: PUSH / [--SP]
|
|
35
|
+
# + 0x1b: SP
|
|
36
|
+
# + 0x1c: PC
|
|
37
|
+
# + 0x1d: O
|
|
38
|
+
# I 0x1e: [next word] SET [0x1000] 0x???? 0x????
|
|
39
|
+
# V 0x1f: next word (literal) SET 0x1000 0x???? 0x????
|
|
40
|
+
# V 0x20-0x3f: literal value 0x00-0x1f (literal) SET 0x???? 0x????
|
|
41
|
+
class Instruction; end;
|
|
42
|
+
|
|
43
|
+
# BasicInstruction like: SET, ADD, ...
|
|
44
|
+
class BasicInstruction < Instruction
|
|
45
|
+
def initialize(op, a, b, location)
|
|
46
|
+
super(op, location)
|
|
47
|
+
@a = value(a)
|
|
48
|
+
@b = value(b)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def word
|
|
52
|
+
@op_value + (@a << 4) + (@b << 10)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# NonBasicInstruction like: JSR
|
|
57
|
+
class NonBasicInstruction < Instruction
|
|
58
|
+
def initialize(op, a, location)
|
|
59
|
+
super(op, location)
|
|
60
|
+
@a = value(a)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def word
|
|
66
|
+
(@op_value << 4) + (@a << 10)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# DatInstruction: dat "foobar"
|
|
71
|
+
class DatInstruction < Instruction
|
|
72
|
+
def initialize(op, args, location)
|
|
73
|
+
super(op, location)
|
|
74
|
+
|
|
75
|
+
args.each do |arg|
|
|
76
|
+
# TODO: shitty string detection here, refactor please
|
|
77
|
+
re_string = /^"([^"]*)"|'([^']*)'$/
|
|
78
|
+
match = arg.match(re_string)
|
|
79
|
+
if match
|
|
80
|
+
s = match[1] || match[2]
|
|
81
|
+
s.each_byte { |b| @words << b } # Add chars
|
|
82
|
+
else
|
|
83
|
+
@words << dehex(arg) # No string given, assume value/label here
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def words
|
|
89
|
+
@words
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def size
|
|
93
|
+
@words.size
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Instruction #< Struct.new(:op, :args, :location)
|
|
101
|
+
RE_INDIRECT = /^\[([^+]+)\+?([^+]+)?\]$/
|
|
102
|
+
RE_HEX = /^0[xX][0-9a-fA-F]{1,4}$/
|
|
103
|
+
RE_INT = /^\d{1,5}$/
|
|
104
|
+
LITERALS = (0x00..0x1f)
|
|
105
|
+
|
|
106
|
+
attr_accessor :a, :b, :location, :op
|
|
107
|
+
|
|
108
|
+
class << self
|
|
109
|
+
def instruction_type(op)
|
|
110
|
+
return :basic if BASIC_INSTRUCTIONS[op]
|
|
111
|
+
return :non_basic if NON_BASIC_INSTRUCTIONS[op]
|
|
112
|
+
return op
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# InstructionFactory
|
|
116
|
+
def create(op, args, location)
|
|
117
|
+
@op = op.upcase.intern
|
|
118
|
+
|
|
119
|
+
case instruction_type(@op)
|
|
120
|
+
when :basic
|
|
121
|
+
BasicInstruction.new(@op, args[0], args[1], location)
|
|
122
|
+
when :non_basic
|
|
123
|
+
NonBasicInstruction.new(@op, args[0], location)
|
|
124
|
+
when :DAT
|
|
125
|
+
DatInstruction.new(@op, args, location)
|
|
126
|
+
else
|
|
127
|
+
raise "Instruction not found: #{@op}"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def initialize(op, location)
|
|
134
|
+
@words = []
|
|
135
|
+
@op = op
|
|
136
|
+
@location = location
|
|
137
|
+
@op_value = INSTRUCTIONS[@op]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def add_word(value)
|
|
142
|
+
raise "NilValue" unless value
|
|
143
|
+
@words << value
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def value(operand)
|
|
147
|
+
operand = operand.to_s
|
|
148
|
+
|
|
149
|
+
register = REGISTER[operand.upcase.intern]
|
|
150
|
+
return register if register
|
|
151
|
+
|
|
152
|
+
# Is a indirect: [A], [0x8000], [0x8000 + A], [0x30]
|
|
153
|
+
indirect = operand.match(RE_INDIRECT)
|
|
154
|
+
if indirect
|
|
155
|
+
if indirect[2]
|
|
156
|
+
# hacky implementation
|
|
157
|
+
r = REGISTER[indirect[2].upcase.intern]
|
|
158
|
+
i = 1
|
|
159
|
+
if !r
|
|
160
|
+
r = REGISTER[indirect[1].upcase.intern]
|
|
161
|
+
i = 2
|
|
162
|
+
end
|
|
163
|
+
raise "Something went wrong (yeah, its a stupid message right here) [#{operand}]" unless r
|
|
164
|
+
add_word(dehex(indirect[i]))
|
|
165
|
+
|
|
166
|
+
# 0x10-0x17: [next word + register]
|
|
167
|
+
return (r + 0x10)
|
|
168
|
+
else
|
|
169
|
+
r = REGISTER[indirect[1].upcase.intern]
|
|
170
|
+
if r
|
|
171
|
+
# 0x08-0x0f: [register]
|
|
172
|
+
return (r + 0x08)
|
|
173
|
+
else
|
|
174
|
+
# 0x1e: [next word]
|
|
175
|
+
add_word(dehex(indirect[1]))
|
|
176
|
+
return 0x1e
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
value = dehex(operand)
|
|
182
|
+
if (0x00..0x1f).include?(value)
|
|
183
|
+
# 0x20-0x3f: literal value 0x00-0x1f (literal)
|
|
184
|
+
return (value + 0x20)
|
|
185
|
+
else
|
|
186
|
+
# 0x1f: next word (literal) || next word (label)
|
|
187
|
+
add_word(value)
|
|
188
|
+
return 0x1f
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def word
|
|
193
|
+
raise "Must be implemented by #{self.class.name}!"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Returns size of Instruction in bytes
|
|
197
|
+
def size
|
|
198
|
+
@words.size + 1
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Add Labels to values
|
|
202
|
+
def apply_labels(labels = {})
|
|
203
|
+
@words.map! { |word| labels[word] || dehex(word, :raise => true) }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def words
|
|
207
|
+
[word] + @words
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def bytes
|
|
211
|
+
words.map { |w| [w].pack('n') }.join("")
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def to_s(options = {:line_number => false})
|
|
215
|
+
raw = words.map{|w| hex(w)}.join(" ")
|
|
216
|
+
options[:line_number] ? "#{location.to_s(16)}:\t#{raw}" : raw
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Convert string to hex or dec value; if none of them return input
|
|
220
|
+
# Raise error if option enabled and no hex/dec value
|
|
221
|
+
def dehex(v, options = {:raise => false})
|
|
222
|
+
v = v.to_s
|
|
223
|
+
return v.hex if v.match(RE_HEX)
|
|
224
|
+
return v.to_i if v.match(RE_INT)
|
|
225
|
+
raise "'#{v}' is no Hex or Int (label not found?) [#{@words.inspect}]\n" if options[:raise]
|
|
226
|
+
return v.downcase
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def hex(w)
|
|
230
|
+
"%04x" % w
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module DCPU16
|
|
2
|
+
class Assembler
|
|
3
|
+
class Line < Struct.new(:raw_label, :op, :args)
|
|
4
|
+
# TODO: refactor
|
|
5
|
+
def label
|
|
6
|
+
return @label if @label || raw_label.nil?
|
|
7
|
+
|
|
8
|
+
@label = raw_label.downcase
|
|
9
|
+
@label = @label[1, @label.length] if @label.start_with?(":")
|
|
10
|
+
|
|
11
|
+
return @label
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
data/lib/dcpu16/cpu.rb
CHANGED
|
@@ -32,7 +32,9 @@ module DCPU16
|
|
|
32
32
|
def initialize(memory = [])
|
|
33
33
|
@cycle = 0
|
|
34
34
|
@memory = DCPU16::Memory.new(memory)
|
|
35
|
-
@registers =
|
|
35
|
+
@registers = []
|
|
36
|
+
[:A, :B, :C, :X, :Y, :Z, :I, :J].each { |r| @registers << Register.new(0x0, r) }
|
|
37
|
+
|
|
36
38
|
@PC = Register.new(0x0)
|
|
37
39
|
@SP = Register.new(0xFFFF)
|
|
38
40
|
@O = Register.new(0x0)
|
|
@@ -62,7 +64,9 @@ module DCPU16
|
|
|
62
64
|
|
|
63
65
|
# Current clock_cycle
|
|
64
66
|
def hz
|
|
65
|
-
|
|
67
|
+
now = Time.now
|
|
68
|
+
@started_at ||= now
|
|
69
|
+
diff = now - @started_at
|
|
66
70
|
diff = 1 if diff == 0
|
|
67
71
|
@cycle / diff
|
|
68
72
|
end
|
|
@@ -84,28 +88,30 @@ module DCPU16
|
|
|
84
88
|
|
|
85
89
|
# Perform a single step
|
|
86
90
|
# TODO: Refacor if/else/if/else ... hacky mess here
|
|
87
|
-
def step
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
@skip
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
91
|
+
def step(steps = 1)
|
|
92
|
+
steps.times do
|
|
93
|
+
@instruction = Instruction.new(@memory.read(@PC))
|
|
94
|
+
@PC += 1
|
|
95
|
+
|
|
96
|
+
op = @instruction.op
|
|
97
|
+
a = get_operand(@instruction.a)
|
|
98
|
+
b = get_operand(@instruction.b) if @instruction.b
|
|
99
|
+
|
|
100
|
+
if @skip
|
|
101
|
+
@skip = false
|
|
102
|
+
else
|
|
103
|
+
if b # Basic Instruction
|
|
104
|
+
result = self.send(op, a.value, b.value)
|
|
105
|
+
else # Non-Basic Instruction
|
|
106
|
+
result = self.send(op, a.value)
|
|
107
|
+
end
|
|
108
|
+
a.write(result) if result
|
|
102
109
|
end
|
|
103
|
-
a.write(result) if result
|
|
104
|
-
end
|
|
105
110
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
# Notify observers
|
|
112
|
+
changed
|
|
113
|
+
notify_observers(self)
|
|
114
|
+
end
|
|
109
115
|
end
|
|
110
116
|
|
|
111
117
|
def to_s
|
|
@@ -117,7 +123,11 @@ module DCPU16
|
|
|
117
123
|
# Clock: #{clock_cycle}
|
|
118
124
|
* HZ: #{hz}
|
|
119
125
|
* Last: #{last_instruction.inspect}
|
|
120
|
-
* Reg.: #{registers.
|
|
126
|
+
* Reg.: #{registers.join("\n ")}
|
|
127
|
+
* SP: #{@SP}
|
|
128
|
+
* PC: #{@PC}
|
|
129
|
+
* O: #{@O}
|
|
130
|
+
0x8000: #{memory[0x8000].value.to_s(16)}
|
|
121
131
|
EOF
|
|
122
132
|
end
|
|
123
133
|
|
data/lib/dcpu16/cpu/register.rb
CHANGED
|
@@ -3,8 +3,9 @@ module DCPU16
|
|
|
3
3
|
class Register
|
|
4
4
|
attr_reader :value
|
|
5
5
|
|
|
6
|
-
def initialize(value = 0x0)
|
|
6
|
+
def initialize(value = 0x0, name = nil)
|
|
7
7
|
@default_value = value
|
|
8
|
+
@name = name
|
|
8
9
|
reset
|
|
9
10
|
end
|
|
10
11
|
|
|
@@ -16,21 +17,33 @@ module DCPU16
|
|
|
16
17
|
|
|
17
18
|
def +(value)
|
|
18
19
|
@value += value
|
|
20
|
+
@value &= 0xFFFF
|
|
19
21
|
self
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def -(value)
|
|
23
25
|
@value -= value
|
|
26
|
+
@value &= 0xFFFF
|
|
24
27
|
self
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
def write(value)
|
|
28
|
-
@value = value
|
|
31
|
+
@value = value & 0xFFFF
|
|
29
32
|
end
|
|
30
33
|
|
|
31
34
|
def reset
|
|
32
35
|
@value = @default_value
|
|
33
36
|
end
|
|
37
|
+
|
|
38
|
+
def inspect
|
|
39
|
+
{ :name => @name, :value => @value, :default_value => @default_value }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_s
|
|
43
|
+
vh = ""
|
|
44
|
+
dh =
|
|
45
|
+
"name: #{@name}\tvalue: 0x%04x(#{@value})\tdefault: 0x%04x#{@default_value}" % [@value, @default_value]
|
|
46
|
+
end
|
|
34
47
|
end
|
|
35
48
|
end
|
|
36
49
|
end
|
data/lib/dcpu16/debugger.rb
CHANGED
|
@@ -1,25 +1,51 @@
|
|
|
1
1
|
module DCPU16
|
|
2
2
|
class Debugger
|
|
3
3
|
attr_reader :cpu, :screen
|
|
4
|
-
def initialize(dump = [])
|
|
4
|
+
def initialize(dump = [], options = {})
|
|
5
5
|
@cpu = DCPU16::CPU.new(dump)
|
|
6
6
|
@screen = DCPU16::Screen.new(@cpu.memory)
|
|
7
|
+
@update_every = options[:update_every] || 100000
|
|
8
|
+
@step_mode = false || options[:step_mode]
|
|
7
9
|
|
|
8
10
|
@cpu.add_observer(self)
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
def run
|
|
12
|
-
|
|
14
|
+
at_exit do
|
|
15
|
+
print @screen
|
|
16
|
+
puts @cpu.to_s
|
|
17
|
+
end
|
|
18
|
+
|
|
13
19
|
begin
|
|
14
|
-
@
|
|
20
|
+
if @step_mode
|
|
21
|
+
@update_every = nil
|
|
22
|
+
while a = $stdin.gets
|
|
23
|
+
a = a.to_i
|
|
24
|
+
a = (@last_step_count || 1) if a < 1
|
|
25
|
+
@last_step_count = a
|
|
26
|
+
|
|
27
|
+
@cpu.step(a)
|
|
28
|
+
print @screen
|
|
29
|
+
print @cpu.to_s
|
|
30
|
+
end
|
|
31
|
+
else
|
|
32
|
+
clear_screen
|
|
33
|
+
@cpu.run
|
|
34
|
+
end
|
|
15
35
|
rescue DCPU16::Instructions::Reserved => e
|
|
16
|
-
|
|
36
|
+
print @cpu.to_s
|
|
17
37
|
end
|
|
18
38
|
end
|
|
19
39
|
|
|
20
40
|
# Observer
|
|
21
41
|
def update(cpu)
|
|
22
|
-
|
|
42
|
+
@counter ||= 0
|
|
43
|
+
if @update_every && @counter > @update_every
|
|
44
|
+
@counter = 0
|
|
45
|
+
clear_screen
|
|
46
|
+
print @cpu.to_s
|
|
47
|
+
end
|
|
48
|
+
@counter += 1
|
|
23
49
|
end
|
|
24
50
|
|
|
25
51
|
# Clears screen for output
|
data/lib/dcpu16/memory.rb
CHANGED
data/lib/dcpu16/memory/word.rb
CHANGED
data/lib/dcpu16/screen.rb
CHANGED
|
@@ -39,6 +39,7 @@ module DCPU16
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
# Initial Screen dump
|
|
42
|
+
# CHECK: might be buggy
|
|
42
43
|
@chars = []
|
|
43
44
|
@height.times do |h|
|
|
44
45
|
@width.times do |w|
|
|
@@ -57,14 +58,14 @@ module DCPU16
|
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
def memory_offset_end
|
|
60
|
-
@memory_offset_end ||=
|
|
61
|
+
@memory_offset_end ||= @memory_offset + size - 1
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def to_s
|
|
64
65
|
return @to_s if @to_s
|
|
65
66
|
|
|
66
67
|
@to_s = "\e[?1049h\e[17;1H"
|
|
67
|
-
@to_s << chars.join
|
|
68
|
+
@to_s << @chars.join
|
|
68
69
|
@to_s << frame
|
|
69
70
|
end
|
|
70
71
|
|
|
@@ -93,16 +94,16 @@ module DCPU16
|
|
|
93
94
|
# Callback from observed memory
|
|
94
95
|
def update(offset, value)
|
|
95
96
|
return unless (memory_offset..memory_offset_end).include?(offset)
|
|
96
|
-
|
|
97
|
+
@to_s = nil
|
|
97
98
|
|
|
98
|
-
diff =
|
|
99
|
+
diff = offset - @memory_offset
|
|
99
100
|
h = diff / @width
|
|
100
101
|
w = diff % @width
|
|
101
102
|
@chars[diff] = Char.new(value, w + @x_offset, h + @y_offset)
|
|
102
103
|
print @chars[diff]
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
changed
|
|
105
|
+
notify_observers(self)
|
|
106
|
+
print @chars[diff]
|
|
106
107
|
end
|
|
107
108
|
end
|
|
108
109
|
|
|
@@ -137,19 +138,3 @@ module DCPU16
|
|
|
137
138
|
end
|
|
138
139
|
end
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
#The high 8 bits determine the color; the highest 4 are the foreground and the lowest 4 are the background
|
|
143
|
-
|
|
144
|
-
#args = []
|
|
145
|
-
#args << (value >> 15)
|
|
146
|
-
#if value > 0x7F
|
|
147
|
-
# args << color_to_ansi(value >> 12) + 30
|
|
148
|
-
# args << color_to_ansi(value >> 8) + 40
|
|
149
|
-
#end
|
|
150
|
-
|
|
151
|
-
#char = " " if char.ord.zero?
|
|
152
|
-
|
|
153
|
-
#color = "\e[#{args*';'}m"
|
|
154
|
-
#print "\e7\e[#{rows+1};#{cols+1}H#{color}#{char}\e8"
|
|
155
|
-
|
data/lib/dcpu16/version.rb
CHANGED
data/lib/dcpu16/version.rb~
CHANGED
data/spec/unit/assembler_spec.rb
CHANGED
|
@@ -5,10 +5,11 @@ describe DCPU16::Assembler do
|
|
|
5
5
|
subject { DCPU16::Assembler.new(asm) }
|
|
6
6
|
|
|
7
7
|
its(:input) { should == asm }
|
|
8
|
-
its(:
|
|
8
|
+
its(:lines) { should be_a_kind_of(Array) }
|
|
9
9
|
specify do
|
|
10
|
-
#
|
|
11
|
-
|
|
10
|
+
# subject.lines
|
|
11
|
+
subject.assemble
|
|
12
|
+
#assert false
|
|
12
13
|
end
|
|
13
14
|
end
|
|
14
15
|
|
data/spec/unit/screen_spec.rb
CHANGED
|
@@ -39,7 +39,7 @@ describe DCPU16::Screen do
|
|
|
39
39
|
specify { subject.should_receive(:update).tap { memory.write(0x8000, 40) } }
|
|
40
40
|
context "memory outside of screen changed" do
|
|
41
41
|
specify { observer.should_not_receive(:update).tap { memory.write(0x7FFF, 40) } }
|
|
42
|
-
specify { observer.should_not_receive(:update).tap { memory.write(0x8000 +
|
|
42
|
+
specify { observer.should_not_receive(:update).tap { memory.write(0x8000 + 0x300, 40) } }
|
|
43
43
|
end
|
|
44
44
|
context "memory of screen changed" do
|
|
45
45
|
pending "Disabled Observer right now!"
|
metadata
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: Rdcpu16
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.4.
|
|
5
|
-
prerelease:
|
|
4
|
+
version: 0.0.4.0
|
|
5
|
+
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
8
8
|
- Patrick Helm
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2012-04-
|
|
12
|
+
date: 2012-04-19 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rspec
|
|
16
|
-
requirement: &
|
|
16
|
+
requirement: &84851380 !ruby/object:Gem::Requirement
|
|
17
17
|
none: false
|
|
18
18
|
requirements:
|
|
19
19
|
- - ~>
|
|
@@ -21,7 +21,7 @@ dependencies:
|
|
|
21
21
|
version: '2.6'
|
|
22
22
|
type: :development
|
|
23
23
|
prerelease: false
|
|
24
|
-
version_requirements: *
|
|
24
|
+
version_requirements: *84851380
|
|
25
25
|
description: This is a simple Ruby port of the fictive 16-bit-cpu DCPU16.
|
|
26
26
|
email:
|
|
27
27
|
- deradon87@gmail.com
|
|
@@ -35,6 +35,9 @@ files:
|
|
|
35
35
|
- lib/dcpu16/assembler.rb
|
|
36
36
|
- lib/dcpu16/memory.rb
|
|
37
37
|
- lib/dcpu16/version.rb
|
|
38
|
+
- lib/dcpu16/assembler/line.rb
|
|
39
|
+
- lib/dcpu16/assembler/instruction.rb
|
|
40
|
+
- lib/dcpu16/assembler/constants.rb
|
|
38
41
|
- lib/dcpu16/memory/word.rb
|
|
39
42
|
- lib/dcpu16/cpu/operand.rb
|
|
40
43
|
- lib/dcpu16/cpu/instruction.rb
|
|
@@ -79,9 +82,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
79
82
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
83
|
none: false
|
|
81
84
|
requirements:
|
|
82
|
-
- - ! '
|
|
85
|
+
- - ! '>='
|
|
83
86
|
- !ruby/object:Gem::Version
|
|
84
|
-
version:
|
|
87
|
+
version: '0'
|
|
85
88
|
requirements: []
|
|
86
89
|
rubyforge_project:
|
|
87
90
|
rubygems_version: 1.8.10
|