n65 0.5.0 → 1.0.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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +28 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +125 -0
- data/Gemfile +3 -1
- data/README.md +3 -20
- data/Rakefile +15 -1
- data/bin/n65 +2 -0
- data/data/opcodes.yaml +39 -39
- data/everdrive_transfer/everdrive.rb +175 -0
- data/examples/pulse_chord.asm +1 -1
- data/examples/scales.asm +182 -0
- data/lib/n65/directives/ascii.rb +4 -19
- data/lib/n65/directives/bytes.rb +20 -35
- data/lib/n65/directives/dw.rb +22 -36
- data/lib/n65/directives/enter_scope.rb +14 -30
- data/lib/n65/directives/exit_scope.rb +7 -17
- data/lib/n65/directives/inc.rb +14 -30
- data/lib/n65/directives/incbin.rb +6 -24
- data/lib/n65/directives/ines_header.rb +66 -27
- data/lib/n65/directives/label.rb +8 -21
- data/lib/n65/directives/org.rb +9 -19
- data/lib/n65/directives/segment.rb +5 -19
- data/lib/n65/directives/space.rb +6 -18
- data/lib/n65/front_end.rb +36 -39
- data/lib/n65/instruction.rb +123 -159
- data/lib/n65/instruction_base.rb +6 -18
- data/lib/n65/memory_space.rb +51 -71
- data/lib/n65/opcodes.rb +3 -5
- data/lib/n65/parser.rb +20 -38
- data/lib/n65/regexes.rb +20 -21
- data/lib/n65/symbol_table.rb +58 -89
- data/lib/n65/version.rb +3 -1
- data/lib/n65.rb +120 -121
- data/n65.gemspec +17 -12
- data/nes_lib/nes.sym +2 -2
- data/spec/.rubocop.yml +4 -0
- data/spec/assembler_spec.rb +84 -0
- data/spec/lib/n65/memory_space_spec.rb +147 -0
- data/spec/lib/n65/symbol_table_spec.rb +291 -0
- data/utils/opcode_table_to_yaml.rb +65 -67
- data.tar.gz.sig +0 -0
- metadata +84 -41
- metadata.gz.sig +0 -0
- data/examples/music_driver.asm +0 -202
- data/test/test_memory_space.rb +0 -82
- data/test/test_symbol_table.rb +0 -238
- data/utils/midi/Makefile +0 -3
- data/utils/midi/c_scale.mid +0 -0
- data/utils/midi/convert +0 -0
- data/utils/midi/guitar.mid +0 -0
- data/utils/midi/include/event.h +0 -93
- data/utils/midi/include/file.h +0 -57
- data/utils/midi/include/helpers.h +0 -14
- data/utils/midi/include/track.h +0 -45
- data/utils/midi/lil_melody.mid +0 -0
- data/utils/midi/mi_feabhra.mid +0 -0
- data/utils/midi/midi_to_nes.rb +0 -204
- data/utils/midi/source/convert.cpp +0 -16
- data/utils/midi/source/event.cpp +0 -96
- data/utils/midi/source/file.cpp +0 -37
- data/utils/midi/source/helpers.cpp +0 -46
- data/utils/midi/source/track.cpp +0 -37
data/lib/n65/instruction.rb
CHANGED
|
@@ -1,153 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'opcodes'
|
|
2
4
|
require_relative 'regexes'
|
|
3
5
|
|
|
4
6
|
module N65
|
|
5
|
-
|
|
6
|
-
####
|
|
7
|
-
## Represents a single 6502 Instruction
|
|
7
|
+
# Represents a single 6502 Instruction
|
|
8
8
|
class Instruction
|
|
9
|
-
attr_reader :op, :arg, :mode, :hex, :description, :length, :
|
|
9
|
+
attr_reader :op, :arg, :mode, :hex, :description, :length, :cycles, :boundry_add, :flags, :address
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# Custom Exceptions
|
|
12
12
|
class InvalidInstruction < StandardError; end
|
|
13
13
|
class UnresolvedSymbols < StandardError; end
|
|
14
14
|
class InvalidAddressingMode < StandardError; end
|
|
15
15
|
class AddressOutOfRange < StandardError; end
|
|
16
16
|
class ArgumentTooLarge < StandardError; end
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
# Include Regexes
|
|
19
19
|
include Regexes
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
:
|
|
23
|
-
:
|
|
24
|
-
:
|
|
25
|
-
:
|
|
26
|
-
:
|
|
21
|
+
ADDRESSING_MODES = {
|
|
22
|
+
relative: {
|
|
23
|
+
example: 'B** my_label',
|
|
24
|
+
display: '%s $%.4X',
|
|
25
|
+
regex: /$^/i,
|
|
26
|
+
regex_label: /^#{Branches}\s+#{Sym}$/
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
:
|
|
30
|
-
:
|
|
31
|
-
:
|
|
32
|
-
:
|
|
33
|
-
:
|
|
29
|
+
immediate: {
|
|
30
|
+
example: 'AAA #$FF',
|
|
31
|
+
display: '%s #$%.2X',
|
|
32
|
+
regex: /^#{Mnemonic}\s+#{Immediate}$/,
|
|
33
|
+
regex_label: /^#{Mnemonic}\s+#(<|>)#{Sym}$/
|
|
34
34
|
},
|
|
35
35
|
|
|
36
|
-
:
|
|
37
|
-
:
|
|
38
|
-
:
|
|
39
|
-
:
|
|
36
|
+
implied: {
|
|
37
|
+
example: 'AAA',
|
|
38
|
+
display: '%s',
|
|
39
|
+
regex: /^#{Mnemonic}$/
|
|
40
40
|
},
|
|
41
41
|
|
|
42
|
-
:
|
|
43
|
-
:
|
|
44
|
-
:
|
|
45
|
-
:
|
|
46
|
-
:
|
|
42
|
+
zero_page: {
|
|
43
|
+
example: 'AAA $FF',
|
|
44
|
+
display: '%s $%.2X',
|
|
45
|
+
regex: /^#{Mnemonic}\s+#{Num8}$/,
|
|
46
|
+
regex_label: /^#{Mnemonic}\s+#{Sym}\s+zp$/
|
|
47
47
|
},
|
|
48
48
|
|
|
49
|
-
:
|
|
50
|
-
:
|
|
51
|
-
:
|
|
52
|
-
:
|
|
53
|
-
:
|
|
49
|
+
zero_page_x: {
|
|
50
|
+
example: 'AAA $FF, X',
|
|
51
|
+
display: '%s $%.2X, X',
|
|
52
|
+
regex: /^#{Mnemonic}\s+#{Num8}\s?,\s?#{XReg}$/,
|
|
53
|
+
regex_label: /^#{Mnemonic}\s+#{Sym}\s?,\s?#{XReg}\s+zp$/
|
|
54
54
|
},
|
|
55
55
|
|
|
56
|
-
:
|
|
57
|
-
:
|
|
58
|
-
:
|
|
59
|
-
:
|
|
60
|
-
:
|
|
56
|
+
zero_page_y: {
|
|
57
|
+
example: 'AAA $FF, Y',
|
|
58
|
+
display: '%s $%.2X, Y',
|
|
59
|
+
regex: /^#{Mnemonic}\s+#{Num8}\s?,\s?#{YReg}$/,
|
|
60
|
+
regex_label: /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg}\s+zp$/
|
|
61
61
|
},
|
|
62
62
|
|
|
63
|
-
:
|
|
64
|
-
:
|
|
65
|
-
:
|
|
66
|
-
:
|
|
67
|
-
:
|
|
63
|
+
absolute: {
|
|
64
|
+
example: 'AAA $FFFF',
|
|
65
|
+
display: '%s $%.4X',
|
|
66
|
+
regex: /^#{Mnemonic}\s+#{Num16}$/,
|
|
67
|
+
regex_label: /^#{Mnemonic}\s+#{Sym}$/
|
|
68
68
|
},
|
|
69
69
|
|
|
70
|
-
:
|
|
71
|
-
:
|
|
72
|
-
:
|
|
73
|
-
:
|
|
74
|
-
:
|
|
70
|
+
absolute_x: {
|
|
71
|
+
example: 'AAA $FFFF, X',
|
|
72
|
+
display: '%s $%.4X, X',
|
|
73
|
+
regex: /^#{Mnemonic}\s+#{Num16}\s?,\s?#{XReg}$/,
|
|
74
|
+
regex_label: /^#{Mnemonic}\s+#{Sym}\s?,\s?#{XReg}$/
|
|
75
75
|
},
|
|
76
76
|
|
|
77
|
-
:
|
|
78
|
-
:
|
|
79
|
-
:
|
|
80
|
-
:
|
|
81
|
-
:
|
|
77
|
+
absolute_y: {
|
|
78
|
+
example: 'AAA $FFFF, Y',
|
|
79
|
+
display: '%s $%.4X, Y',
|
|
80
|
+
regex: /^#{Mnemonic}\s+#{Num16}\s?,\s?#{YReg}$/,
|
|
81
|
+
regex_label: /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg}$/
|
|
82
82
|
},
|
|
83
83
|
|
|
84
|
-
:
|
|
85
|
-
:
|
|
86
|
-
:
|
|
87
|
-
:
|
|
88
|
-
:
|
|
84
|
+
indirect: {
|
|
85
|
+
example: 'AAA ($FFFF)',
|
|
86
|
+
display: '%s ($%.4X)',
|
|
87
|
+
regex: /^#{Mnemonic}\s+\(#{Num16}\)$/,
|
|
88
|
+
regex_label: /^#{Mnemonic}\s+\(#{Sym}\)$/
|
|
89
89
|
},
|
|
90
90
|
|
|
91
|
-
:
|
|
92
|
-
:
|
|
93
|
-
:
|
|
94
|
-
:
|
|
95
|
-
:
|
|
91
|
+
indirect_x: {
|
|
92
|
+
example: 'AAA ($FF, X)',
|
|
93
|
+
display: '%s ($%.2X, X)',
|
|
94
|
+
regex: /^#{Mnemonic}\s+\(#{Num8}\s?,\s?#{XReg}\)$/,
|
|
95
|
+
regex_label: /^#{Mnemonic}\s+\(#{Sym}\s?,\s?#{XReg}\)$/
|
|
96
96
|
},
|
|
97
97
|
|
|
98
|
-
:
|
|
99
|
-
:
|
|
100
|
-
:
|
|
101
|
-
:
|
|
102
|
-
:
|
|
98
|
+
indirect_y: {
|
|
99
|
+
example: 'AAA ($FF), Y)',
|
|
100
|
+
display: '%s ($%.2X), Y',
|
|
101
|
+
regex: /^#{Mnemonic}\s+\(#{Num8}\)\s?,\s?#{YReg}$/,
|
|
102
|
+
regex_label: /^#{Mnemonic}\s+\(#{Sym}\)\s?,\s?#{YReg}$/
|
|
103
103
|
}
|
|
104
|
-
}
|
|
104
|
+
}.freeze
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
## Raises SyntaxError if the line is malformed in some way
|
|
106
|
+
# Parse one line of assembly, returns nil if the line
|
|
107
|
+
# is ultimately empty of asm instructions
|
|
108
|
+
# Raises SyntaxError if the line is malformed in some way
|
|
110
109
|
def self.parse(line)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
## We have regexes that match each addressing mode
|
|
110
|
+
# Try to parse this line in each addressing mode
|
|
111
|
+
ADDRESSING_MODES.each do |mode, parse_info|
|
|
112
|
+
# We have regexes that match each addressing mode
|
|
116
113
|
match_data = parse_info[:regex].match(line)
|
|
117
114
|
|
|
118
115
|
unless match_data.nil?
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
# We must have a straight instruction without symbols, construct
|
|
117
|
+
# an Instruction from the match_data, and return it
|
|
121
118
|
_, op, arg_hex, arg_bin = match_data.to_a
|
|
122
119
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if arg_hex
|
|
120
|
+
# Until I think of something better, it seems that the union regex
|
|
121
|
+
# puts a hexidecimal argument in one capture, and a binary in the next
|
|
122
|
+
# This is annoying, but still not as annoying as using Treetop to parse
|
|
123
|
+
if !arg_hex.nil?
|
|
127
124
|
return Instruction.new(op, arg_hex.to_i(16), mode)
|
|
128
|
-
elsif arg_bin
|
|
125
|
+
elsif !arg_bin.nil?
|
|
129
126
|
return Instruction.new(op, arg_bin.to_i(2), mode)
|
|
130
127
|
else
|
|
131
128
|
return Instruction.new(op, nil, mode)
|
|
132
129
|
end
|
|
133
130
|
|
|
134
131
|
else
|
|
135
|
-
|
|
132
|
+
# Can this addressing mode even use labels?
|
|
136
133
|
unless parse_info[:regex_label].nil?
|
|
137
134
|
|
|
138
|
-
|
|
135
|
+
# See if it does in fact have a symbolic argument
|
|
139
136
|
match_data = parse_info[:regex_label].match(line)
|
|
140
137
|
|
|
141
138
|
unless match_data.nil?
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
# We have found an assembly instruction containing a symbolic
|
|
140
|
+
# argument. We can resolve this symbol later by looking at the
|
|
141
|
+
# symbol table in the #exec method
|
|
145
142
|
match_array = match_data.to_a
|
|
146
143
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
144
|
+
# If we have a 4 element array, this means we matched something
|
|
145
|
+
# like LDA #<label, which is a legal immediate one byte value
|
|
146
|
+
# by taking the msb. We need to make that distinction in the
|
|
147
|
+
# Instruction, by passing an extra argument
|
|
151
148
|
if match_array.size == 4
|
|
152
149
|
_, op, byte_selector, arg = match_array
|
|
153
150
|
return Instruction.new(op, arg, mode, byte_selector.to_sym)
|
|
@@ -160,88 +157,75 @@ module N65
|
|
|
160
157
|
end
|
|
161
158
|
end
|
|
162
159
|
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
# We just don't recognize this line of asm, it must be a Syntax Error
|
|
161
|
+
raise(SyntaxError, line)
|
|
165
162
|
end
|
|
166
163
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
## because that can later be used to index into our opcodes hash in OpCodes
|
|
171
|
-
## OpCodes contains the definitions of each OpCode
|
|
164
|
+
# Create an instruction. Having the instruction op a downcased symbol is nice
|
|
165
|
+
# because that can later be used to index into our opcodes hash in OpCodes
|
|
166
|
+
# OpCodes contains the definitions of each OpCode
|
|
172
167
|
def initialize(op, arg, mode, byte_selector = nil)
|
|
173
|
-
|
|
174
|
-
## Lookup the definition of this opcode, otherwise it is an invalid instruction
|
|
175
168
|
@byte_selector = byte_selector.nil? ? nil : byte_selector.to_sym
|
|
176
|
-
|
|
169
|
+
raise(InvalidInstruction, "Bad Byte selector: #{byte_selector}") unless [:>, :<, nil].include?(@byte_selector)
|
|
177
170
|
|
|
171
|
+
## Lookup the definition of this opcode, otherwise it is an invalid instruction
|
|
178
172
|
@op = op.downcase.to_sym
|
|
179
173
|
definition = OpCodes[@op]
|
|
180
|
-
|
|
174
|
+
raise(InvalidInstruction, op) if definition.nil?
|
|
181
175
|
|
|
182
176
|
@arg = arg
|
|
183
177
|
|
|
184
|
-
|
|
178
|
+
# Be sure the mode is an actually supported mode.
|
|
185
179
|
@mode = mode.to_sym
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if definition[@mode].nil?
|
|
189
|
-
fail(InvalidInstruction, "#{op} cannot be used in #{mode} mode")
|
|
190
|
-
end
|
|
180
|
+
raise(InvalidAddressingMode, mode) unless ADDRESSING_MODES.key?(@mode)
|
|
181
|
+
raise(InvalidInstruction, "#{op} cannot be used in #{mode} mode") if definition[@mode].nil?
|
|
191
182
|
|
|
192
183
|
@description, @flags = definition.values_at(:description, :flags)
|
|
193
184
|
@hex, @length, @cycles, @boundry_add = definition[@mode].values_at(:hex, :len, :cycles, :boundry_add)
|
|
194
185
|
end
|
|
195
186
|
|
|
196
|
-
|
|
197
|
-
####
|
|
198
|
-
## Return if this instruction is a zero page instruction
|
|
187
|
+
# Is this instruction a zero page instruction?
|
|
199
188
|
def zero_page_instruction?
|
|
200
|
-
[
|
|
189
|
+
%i[zero_page zero_page_x zero_page_y].include?(@mode)
|
|
201
190
|
end
|
|
202
191
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
## If there is a symbolic argument, we can try to resolve it now, or
|
|
207
|
-
## promise to resolve it later.
|
|
192
|
+
# Execute writes the emitted bytes to virtual memory, and updates PC
|
|
193
|
+
# If there is a symbolic argument, we can try to resolve it now, or
|
|
194
|
+
# promise to resolve it later.
|
|
208
195
|
def exec(assembler)
|
|
209
|
-
|
|
210
196
|
promise = assembler.with_saved_state do |saved_assembler|
|
|
211
197
|
@arg = saved_assembler.symbol_table.resolve_symbol(@arg)
|
|
212
198
|
|
|
213
|
-
|
|
199
|
+
# If the instruction uses a byte selector, we need to apply that.
|
|
214
200
|
@arg = apply_byte_selector(@byte_selector, @arg)
|
|
215
201
|
|
|
216
|
-
|
|
202
|
+
# If the instruction is relative we need to work out how far away it is
|
|
217
203
|
@arg = @arg - saved_assembler.program_counter - 2 if @mode == :relative
|
|
218
204
|
|
|
219
205
|
saved_assembler.write_memory(emit_bytes)
|
|
220
206
|
end
|
|
221
207
|
|
|
222
208
|
case @arg
|
|
223
|
-
when
|
|
209
|
+
when Integer, NilClass
|
|
224
210
|
assembler.write_memory(emit_bytes)
|
|
225
211
|
when String
|
|
226
212
|
begin
|
|
227
|
-
|
|
228
|
-
## I think it always resolves every symbol later.
|
|
213
|
+
# This works correctly now :)
|
|
229
214
|
promise.call
|
|
230
215
|
rescue SymbolTable::UndefinedSymbol
|
|
231
216
|
placeholder = [@hex, 0xDE, 0xAD][0...@length]
|
|
232
|
-
|
|
233
|
-
|
|
217
|
+
# I still have to write a placeholder instruction of the right
|
|
218
|
+
# length. The promise will come back and resolve the address.
|
|
234
219
|
assembler.write_memory(placeholder)
|
|
235
|
-
|
|
220
|
+
promise
|
|
236
221
|
end
|
|
237
222
|
end
|
|
238
223
|
end
|
|
239
224
|
|
|
240
|
-
|
|
241
|
-
####
|
|
242
|
-
## Apply a byte selector to an argument
|
|
225
|
+
# Apply a byte selector to an argument
|
|
243
226
|
def apply_byte_selector(byte_selector, value)
|
|
244
227
|
return value if byte_selector.nil?
|
|
228
|
+
|
|
245
229
|
case byte_selector
|
|
246
230
|
when :>
|
|
247
231
|
high_byte(value)
|
|
@@ -250,59 +234,39 @@ module N65
|
|
|
250
234
|
end
|
|
251
235
|
end
|
|
252
236
|
|
|
253
|
-
|
|
254
|
-
####
|
|
255
|
-
## Emit bytes from asm structure
|
|
237
|
+
# Emit bytes from asm structure
|
|
256
238
|
def emit_bytes
|
|
257
239
|
case @length
|
|
258
240
|
when 1
|
|
259
241
|
[@hex]
|
|
260
242
|
when 2
|
|
261
|
-
if zero_page_instruction? && @arg
|
|
262
|
-
|
|
243
|
+
if zero_page_instruction? && @arg.negative? || @arg > 0xff
|
|
244
|
+
raise(ArgumentTooLarge, "For #{@op} in #{@mode} mode, only 8-bit values are allowed")
|
|
263
245
|
end
|
|
246
|
+
|
|
264
247
|
[@hex, @arg]
|
|
265
248
|
when 3
|
|
266
249
|
[@hex] + break_16(@arg)
|
|
267
250
|
else
|
|
268
|
-
|
|
251
|
+
raise("Can't handle instructions > 3 bytes")
|
|
269
252
|
end
|
|
270
253
|
end
|
|
271
254
|
|
|
272
|
-
|
|
273
|
-
####
|
|
274
|
-
## Pretty Print
|
|
275
|
-
def to_s
|
|
276
|
-
#display = AddressingModes[@mode][:display]
|
|
277
|
-
#if @arg.kind_of?(String)
|
|
278
|
-
#sprintf("#{display} (#{@mode}, #{@arg})", @op, 0x0)
|
|
279
|
-
#else
|
|
280
|
-
#sprintf("#{display} (#{@mode})", @op, @arg)
|
|
281
|
-
#end
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
|
|
285
255
|
private
|
|
286
|
-
|
|
287
|
-
|
|
256
|
+
|
|
257
|
+
# Break an integer into two 8-bit parts
|
|
288
258
|
def break_16(integer)
|
|
289
259
|
[integer & 0x00FF, (integer & 0xFF00) >> 8]
|
|
290
260
|
end
|
|
291
261
|
|
|
292
|
-
|
|
293
|
-
####
|
|
294
|
-
## Take the high byte of a 16-bit integer
|
|
262
|
+
# Take the high byte of a 16-bit integer
|
|
295
263
|
def high_byte(word)
|
|
296
264
|
(word & 0xFF00) >> 8
|
|
297
265
|
end
|
|
298
266
|
|
|
299
|
-
|
|
300
|
-
####
|
|
301
|
-
## Take the low byte of a 16-bit integer
|
|
267
|
+
# Take the low byte of a 16-bit integer
|
|
302
268
|
def low_byte(word)
|
|
303
269
|
word & 0xFF
|
|
304
270
|
end
|
|
305
|
-
|
|
306
271
|
end
|
|
307
|
-
|
|
308
272
|
end
|
data/lib/n65/instruction_base.rb
CHANGED
|
@@ -1,29 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
|
|
2
3
|
module N65
|
|
3
|
-
|
|
4
4
|
class InstructionBase
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
#####
|
|
8
|
-
## Sort of a "pure virtual" class method, not really tho.
|
|
9
|
-
def self.parse(line)
|
|
10
|
-
fail(NotImplementedError, "#{self.class.name} must implement self.parse")
|
|
5
|
+
def self.parse(_line)
|
|
6
|
+
raise(NotImplementedError, "#{self.class.name} must implement #{__method__}")
|
|
11
7
|
end
|
|
12
8
|
|
|
9
|
+
def exec(_assembler)
|
|
10
|
+
raise(NotImplementedError, "#{self.class.name} must implement #{__method__}")
|
|
11
|
+
end
|
|
13
12
|
|
|
14
|
-
####
|
|
15
|
-
## Does this instruction have unresolved symbols?
|
|
16
13
|
def unresolved_symbols?
|
|
17
14
|
false
|
|
18
15
|
end
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
####
|
|
22
|
-
## Another method subclasses will be expected to implement
|
|
23
|
-
def exec(assembler)
|
|
24
|
-
fail(NotImplementedError, "#{self.class.name} must implement exec")
|
|
25
|
-
end
|
|
26
|
-
|
|
27
16
|
end
|
|
28
|
-
|
|
29
17
|
end
|
data/lib/n65/memory_space.rb
CHANGED
|
@@ -1,58 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
|
|
2
3
|
module N65
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## It can also be used to create arbitrary sized spaces
|
|
8
|
-
## for example to build the final binary ROM in.
|
|
4
|
+
# Let's use this to simulate a virtual address space
|
|
5
|
+
# Either a 16kb prog rom or 8kb char rom space.
|
|
6
|
+
# It can also be used to create arbitrary sized spaces
|
|
7
|
+
# for example to build the final binary ROM in.
|
|
9
8
|
class MemorySpace
|
|
10
|
-
|
|
11
|
-
#### Custom exceptions
|
|
12
9
|
class AccessOutsideProgRom < StandardError; end
|
|
13
10
|
class AccessOutsideCharRom < StandardError; end
|
|
14
11
|
class AccessOutOfBounds < StandardError; end
|
|
15
12
|
|
|
13
|
+
# Some constants, the size of PROG and CHAR ROM
|
|
14
|
+
BANK_SIZES = {
|
|
15
|
+
ines: 0x10,
|
|
16
|
+
prog: 0x4000,
|
|
17
|
+
char: 0x2000
|
|
18
|
+
}.freeze
|
|
16
19
|
|
|
17
|
-
#### Some constants, the size of PROG and CHAR ROM
|
|
18
|
-
BankSizes = {
|
|
19
|
-
:ines => 0x10, # 16b
|
|
20
|
-
:prog => 0x4000, # 16kb
|
|
21
|
-
:char => 0x2000, # 8kb
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
####
|
|
26
|
-
## Create a new PROG ROM
|
|
27
20
|
def self.create_prog_rom
|
|
28
|
-
|
|
21
|
+
create_bank(:prog)
|
|
29
22
|
end
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
####
|
|
33
|
-
## Create a new CHAR ROM
|
|
34
24
|
def self.create_char_rom
|
|
35
|
-
|
|
25
|
+
create_bank(:char)
|
|
36
26
|
end
|
|
37
27
|
|
|
38
|
-
|
|
39
|
-
####
|
|
40
|
-
## Create a new bank
|
|
41
28
|
def self.create_bank(type)
|
|
42
|
-
|
|
29
|
+
new(BANK_SIZES[type], type)
|
|
43
30
|
end
|
|
44
31
|
|
|
45
|
-
|
|
46
|
-
####
|
|
47
|
-
## Create a completely zeroed memory space
|
|
32
|
+
# Create a completely zeroed memory space
|
|
48
33
|
def initialize(size, type)
|
|
49
34
|
@type = type
|
|
50
35
|
@memory = Array.new(size, 0x0)
|
|
36
|
+
@bytes_written = 0
|
|
51
37
|
end
|
|
52
38
|
|
|
53
|
-
|
|
54
|
-
####
|
|
55
|
-
## Normalized read from memory
|
|
39
|
+
# Normalized read from memory
|
|
56
40
|
def read(address, count)
|
|
57
41
|
from_normalized = normalize_address(address)
|
|
58
42
|
to_normalized = normalize_address(address + (count - 1))
|
|
@@ -61,9 +45,7 @@ module N65
|
|
|
61
45
|
@memory[from_normalized..to_normalized]
|
|
62
46
|
end
|
|
63
47
|
|
|
64
|
-
|
|
65
|
-
####
|
|
66
|
-
## Normalized write to memory
|
|
48
|
+
# Normalized write to memory
|
|
67
49
|
def write(address, bytes)
|
|
68
50
|
from_normalized = normalize_address(address)
|
|
69
51
|
to_normalized = normalize_address(address + bytes.size - 1)
|
|
@@ -71,80 +53,78 @@ module N65
|
|
|
71
53
|
|
|
72
54
|
bytes.each_with_index do |byte, index|
|
|
73
55
|
@memory[from_normalized + index] = byte
|
|
56
|
+
@bytes_written += 1
|
|
74
57
|
end
|
|
75
58
|
bytes.size
|
|
76
59
|
end
|
|
77
60
|
|
|
78
|
-
|
|
79
|
-
####
|
|
80
|
-
## Return the memory as an array of bytes to write to disk
|
|
61
|
+
# Return the memory as an array of bytes to write to disk
|
|
81
62
|
def emit_bytes
|
|
82
63
|
@memory
|
|
83
64
|
end
|
|
84
65
|
|
|
66
|
+
# Bank Usage information
|
|
67
|
+
def usage_info
|
|
68
|
+
percent_used = @bytes_written / @memory.size.to_f * 100
|
|
69
|
+
percent_string = format('%0.2f', percent_used)
|
|
70
|
+
bytes_written_hex = format('$%04x', @bytes_written)
|
|
71
|
+
memory_size_hex = format('$%04x', @memory.size)
|
|
72
|
+
"(#{bytes_written_hex} / #{memory_size_hex}) #{percent_string}%"
|
|
73
|
+
end
|
|
85
74
|
|
|
86
75
|
private
|
|
87
76
|
|
|
88
|
-
|
|
89
|
-
## Are the given addresses in bounds? If not blow up.
|
|
77
|
+
# Are the given addresses in bounds? If not blow up.
|
|
90
78
|
def ensure_addresses_in_bounds!(addresses)
|
|
91
79
|
addresses.each do |address|
|
|
92
80
|
unless address >= 0 && address < @memory.size
|
|
93
|
-
|
|
81
|
+
raise(AccessOutOfBounds, format("Address $%.4X is out of bounds in this #{@type} bank"))
|
|
94
82
|
end
|
|
95
83
|
end
|
|
96
84
|
true
|
|
97
85
|
end
|
|
98
86
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
## in those segments. Char rom doesn't need this. This will also
|
|
105
|
-
## fail if you are accessing outside of the address space.
|
|
87
|
+
# Since prog rom can be loaded at either 0x8000 or 0xC000
|
|
88
|
+
# We should normalize the addresses to fit properly into
|
|
89
|
+
# these banks, basically it acts like it is mirroring addresses
|
|
90
|
+
# in those segments. Char rom doesn't need this. This will also
|
|
91
|
+
# fail if you are accessing outside of the address space.
|
|
106
92
|
def normalize_address(address)
|
|
107
93
|
case @type
|
|
108
94
|
when :prog
|
|
109
|
-
|
|
110
|
-
return address - 0x8000
|
|
111
|
-
end
|
|
112
|
-
if address_inside_prog_rom2?(address)
|
|
113
|
-
return address - 0xC000
|
|
114
|
-
end
|
|
115
|
-
fail(AccessOutsideProgRom, sprintf("Address $%.4X is outside PROG ROM", address))
|
|
95
|
+
normalize_prog_rom_address(address)
|
|
116
96
|
when :char
|
|
117
|
-
|
|
118
|
-
fail(AccessOutsideCharRom, sprintf("Address $%.4X is outside CHAR ROM", address))
|
|
119
|
-
end
|
|
120
|
-
address
|
|
97
|
+
normalize_char_rom_address(address)
|
|
121
98
|
else
|
|
122
99
|
address
|
|
123
100
|
end
|
|
124
101
|
end
|
|
125
102
|
|
|
103
|
+
def normalize_prog_rom_address(address)
|
|
104
|
+
return (address - 0x8000) if address_inside_prog_rom1?(address)
|
|
105
|
+
return (address - 0xC000) if address_inside_prog_rom2?(address)
|
|
106
|
+
|
|
107
|
+
message = 'Address $%.4X is outside PROG ROM'
|
|
108
|
+
raise(AccessOutsideProgRom, format(message, address))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def normalize_char_rom_address(address)
|
|
112
|
+
return address if address_inside_char_rom?(address)
|
|
113
|
+
|
|
114
|
+
message = 'Address $%.4X is outside CHAR ROM'
|
|
115
|
+
raise(AccessOutsideCharRom, format(message, address))
|
|
116
|
+
end
|
|
126
117
|
|
|
127
|
-
####
|
|
128
|
-
## Is this address inside the prog rom 1 area?
|
|
129
118
|
def address_inside_prog_rom1?(address)
|
|
130
119
|
address >= 0x8000 && address < 0xC000
|
|
131
120
|
end
|
|
132
121
|
|
|
133
|
-
|
|
134
|
-
####
|
|
135
|
-
## Is this address inside the prog rom 2 area?
|
|
136
122
|
def address_inside_prog_rom2?(address)
|
|
137
123
|
address >= 0xC000 && address <= 0xffff
|
|
138
124
|
end
|
|
139
125
|
|
|
140
|
-
|
|
141
|
-
####
|
|
142
|
-
## Is this address inside the char rom area?
|
|
143
126
|
def address_inside_char_rom?(address)
|
|
144
127
|
address >= 0x0000 && address <= 0x1fff
|
|
145
128
|
end
|
|
146
|
-
|
|
147
129
|
end
|
|
148
|
-
|
|
149
130
|
end
|
|
150
|
-
|
data/lib/n65/opcodes.rb
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'yaml'
|
|
2
4
|
|
|
3
5
|
module N65
|
|
4
|
-
|
|
5
|
-
## Load OpCode definitions into this module
|
|
6
|
-
MyDirectory = File.expand_path(File.dirname(__FILE__))
|
|
7
|
-
OpCodes = YAML.load_file("#{MyDirectory}/../../data/opcodes.yaml")
|
|
8
|
-
|
|
6
|
+
OpCodes = YAML.load_file(File.join(__dir__, '../../data/opcodes.yaml'))
|
|
9
7
|
end
|