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 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