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 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
+
@@ -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(text)
6
- @input = text
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 dump
10
- raise "TODO #{caller.first}"
11
- lines = []
12
- @input.each_line do |line|
13
- empty = (line =~ /^\s*$/)
14
- comment = (line =~ /^\s*;+.*$/)
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
- next if empty || comment
213
+ def display
214
+ @body.each { |op| puts op }
215
+ end
17
216
 
18
- line.gsub!(/^\s*([^;]*).*$/) { $1 } # Strip some spaces
19
217
 
20
- regex = /^(:\w*)?\s*(\w*)\s*([^,\s]*),?\s?([^\s]*).*$/
21
- match = line.match(regex)
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
- line = { :label => match[1],
24
- :op => match[2],
25
- :a => match[3],
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 Instruction
23
- INSTRUCTIONS = [
24
- :reserved,
25
- :set, :add, :sub, :mul, :div, :mod,
26
- :shl, :shr,
27
- :and, :bor, :xor,
28
- :ife, :ifn, :ifg, :ifb
29
- ]
30
-
31
- NON_BASIC_INSTRUCTIONS = [
32
- :reserved,
33
- :jsr
34
- ]
35
-
36
- attr_reader :opcode, :a, :b, :word
37
-
38
- def initialize(word = nil)
39
- @word = word.value
40
- @opcode = @word & 0x000F
41
-
42
- if @opcode == 0x0
43
- @non_basic = true
44
- @opcode = (@word >> 4) & 0x3f
45
- @a = @word >> 10
46
- else
47
- @a = (@word >> 4) & 0x3f
48
- @b = @word >> 10
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
- def op
53
- @non_basic ? NON_BASIC_INSTRUCTIONS[@opcode] : INSTRUCTIONS[@opcode]
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) { DCPU16::Register.new }
38
- @PC = DCPU16::Register.new(0x0)
39
- @SP = DCPU16::Register.new(0xFFFF)
40
- @O = DCPU16::Register.new(0x0)
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
- # TODO: May be removed
113
- def get_operand(value)
114
- DCPU16::Operand.new(self, value)
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
- DCPU16::Word.new(@memory[offset], self, offset)
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
- super(key)
46
+ read(key)
46
47
  end
47
48
 
48
49
  def []=(key, value)
49
- super(key, value)
50
+ write(key, value)
50
51
  end
51
52
  end
52
53
  end