Rdcpu16 0.0.4.beta → 0.0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|