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.
Files changed (64) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +28 -0
  4. data/.gitignore +1 -1
  5. data/.rubocop.yml +125 -0
  6. data/Gemfile +3 -1
  7. data/README.md +3 -20
  8. data/Rakefile +15 -1
  9. data/bin/n65 +2 -0
  10. data/data/opcodes.yaml +39 -39
  11. data/everdrive_transfer/everdrive.rb +175 -0
  12. data/examples/pulse_chord.asm +1 -1
  13. data/examples/scales.asm +182 -0
  14. data/lib/n65/directives/ascii.rb +4 -19
  15. data/lib/n65/directives/bytes.rb +20 -35
  16. data/lib/n65/directives/dw.rb +22 -36
  17. data/lib/n65/directives/enter_scope.rb +14 -30
  18. data/lib/n65/directives/exit_scope.rb +7 -17
  19. data/lib/n65/directives/inc.rb +14 -30
  20. data/lib/n65/directives/incbin.rb +6 -24
  21. data/lib/n65/directives/ines_header.rb +66 -27
  22. data/lib/n65/directives/label.rb +8 -21
  23. data/lib/n65/directives/org.rb +9 -19
  24. data/lib/n65/directives/segment.rb +5 -19
  25. data/lib/n65/directives/space.rb +6 -18
  26. data/lib/n65/front_end.rb +36 -39
  27. data/lib/n65/instruction.rb +123 -159
  28. data/lib/n65/instruction_base.rb +6 -18
  29. data/lib/n65/memory_space.rb +51 -71
  30. data/lib/n65/opcodes.rb +3 -5
  31. data/lib/n65/parser.rb +20 -38
  32. data/lib/n65/regexes.rb +20 -21
  33. data/lib/n65/symbol_table.rb +58 -89
  34. data/lib/n65/version.rb +3 -1
  35. data/lib/n65.rb +120 -121
  36. data/n65.gemspec +17 -12
  37. data/nes_lib/nes.sym +2 -2
  38. data/spec/.rubocop.yml +4 -0
  39. data/spec/assembler_spec.rb +84 -0
  40. data/spec/lib/n65/memory_space_spec.rb +147 -0
  41. data/spec/lib/n65/symbol_table_spec.rb +291 -0
  42. data/utils/opcode_table_to_yaml.rb +65 -67
  43. data.tar.gz.sig +0 -0
  44. metadata +84 -41
  45. metadata.gz.sig +0 -0
  46. data/examples/music_driver.asm +0 -202
  47. data/test/test_memory_space.rb +0 -82
  48. data/test/test_symbol_table.rb +0 -238
  49. data/utils/midi/Makefile +0 -3
  50. data/utils/midi/c_scale.mid +0 -0
  51. data/utils/midi/convert +0 -0
  52. data/utils/midi/guitar.mid +0 -0
  53. data/utils/midi/include/event.h +0 -93
  54. data/utils/midi/include/file.h +0 -57
  55. data/utils/midi/include/helpers.h +0 -14
  56. data/utils/midi/include/track.h +0 -45
  57. data/utils/midi/lil_melody.mid +0 -0
  58. data/utils/midi/mi_feabhra.mid +0 -0
  59. data/utils/midi/midi_to_nes.rb +0 -204
  60. data/utils/midi/source/convert.cpp +0 -16
  61. data/utils/midi/source/event.cpp +0 -96
  62. data/utils/midi/source/file.cpp +0 -37
  63. data/utils/midi/source/helpers.cpp +0 -46
  64. data/utils/midi/source/track.cpp +0 -37
@@ -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, :cycle, :boundry_add, :flags, :address
9
+ attr_reader :op, :arg, :mode, :hex, :description, :length, :cycles, :boundry_add, :flags, :address
10
10
 
11
- ## Custom Exceptions
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
- ## Include Regexes
18
+ # Include Regexes
19
19
  include Regexes
20
20
 
21
- AddressingModes = {
22
- :relative => {
23
- :example => 'B** my_label',
24
- :display => '%s $%.4X',
25
- :regex => /$^/i, # Will never match this one
26
- :regex_label => /^#{Branches}\s+#{Sym}$/
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
- :immediate => {
30
- :example => 'AAA #$FF',
31
- :display => '%s #$%.2X',
32
- :regex => /^#{Mnemonic}\s+#{Immediate}$/,
33
- :regex_label => /^#{Mnemonic}\s+#(<|>)#{Sym}$/
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
- :implied => {
37
- :example => 'AAA',
38
- :display => '%s',
39
- :regex => /^#{Mnemonic}$/
36
+ implied: {
37
+ example: 'AAA',
38
+ display: '%s',
39
+ regex: /^#{Mnemonic}$/
40
40
  },
41
41
 
42
- :zero_page => {
43
- :example => 'AAA $FF',
44
- :display => '%s $%.2X',
45
- :regex => /^#{Mnemonic}\s+#{Num8}$/,
46
- :regex_label => /^#{Mnemonic}\s+#{Sym}\s+zp$/
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
- :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$/
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
- :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} zp$/
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
- :absolute => {
64
- :example => 'AAA $FFFF',
65
- :display => '%s $%.4X',
66
- :regex => /^#{Mnemonic}\s+#{Num16}$/,
67
- :regex_label => /^#{Mnemonic}\s+#{Sym}$/
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
- :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}$/
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
- :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}$/
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
- :indirect => {
85
- :example => 'AAA ($FFFF)',
86
- :display => '%s ($%.4X)',
87
- :regex => /^#{Mnemonic}\s+\(#{Num16}\)$/,
88
- :regex_label => /^#{Mnemonic}\s+\(#{Sym}\)$/
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
- :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}\)$/
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
- :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}$/
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
- ## Parse one line of assembly, returns nil if the line
108
- ## is ultimately empty of asm instructions
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
- ## Try to parse this line in each addressing mode
113
- AddressingModes.each do |mode, parse_info|
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
- ## We must have a straight instruction without symbols, construct
120
- ## an Instruction from the match_data, and return it
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
- ## Until I think of something better, it seems that the union regex
124
- ## puts a hexidecimal argument in one capture, and a binary in the next
125
- ## This is annoying, but still not as annoying as using Treetop to parse
126
- if arg_hex != nil
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 != nil
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
- ## Can this addressing mode even use labels?
132
+ # Can this addressing mode even use labels?
136
133
  unless parse_info[:regex_label].nil?
137
134
 
138
- ## See if it does in fact have a symbolic argument
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
- ## We have found an assembly instruction containing a symbolic
143
- ## argument. We can resolve this symbol later by looking at the
144
- ## symbol table in the #exec method
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
- ## If we have a 4 element array, this means we matched something
148
- ## like LDA #<label, which is a legal immediate one byte value
149
- ## by taking the msb. We need to make that distinction in the
150
- ## Instruction, by passing an extra argument
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
- ## We just don't recognize this line of asm, it must be a Syntax Error
164
- fail(SyntaxError, line)
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
- ## Create an instruction. Having the instruction op a downcased symbol is nice
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
- fail(InvalidInstruction, "Bad Byte selector: #{byte_selector}") unless [:>, :<, nil].include?(@byte_selector)
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
- fail(InvalidInstruction, op) if definition.nil?
174
+ raise(InvalidInstruction, op) if definition.nil?
181
175
 
182
176
  @arg = arg
183
177
 
184
- ## Be sure the mode is an actually supported mode.
178
+ # Be sure the mode is an actually supported mode.
185
179
  @mode = mode.to_sym
186
- fail(InvalidAddressingMode, mode) unless AddressingModes.has_key?(@mode)
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
- [:zero_page, :zero_page_x, :zero_page_y].include?(@mode)
189
+ %i[zero_page zero_page_x zero_page_y].include?(@mode)
201
190
  end
202
191
 
203
-
204
- ####
205
- ## Execute writes the emitted bytes to virtual memory, and updates PC
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
- ## If the instruction uses a byte selector, we need to apply that.
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
- ## If the instruction is relative we need to work out how far away it is
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 Fixnum, NilClass
209
+ when Integer, NilClass
224
210
  assembler.write_memory(emit_bytes)
225
211
  when String
226
212
  begin
227
- ## This is a bug, I don't believe it will ever get here.
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
- ## I still have to write a placeholder instruction of the right
233
- ## length. The promise will come back and resolve the address.
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
- return promise
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 < 0 || @arg > 0xff
262
- fail(ArgumentTooLarge, "For #{@op} in #{@mode} mode, only 8-bit values are allowed")
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
- fail("Can't handle instructions > 3 bytes")
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
- ## Break an integer into two 8-bit parts
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
@@ -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
@@ -1,58 +1,42 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module N65
3
-
4
- ####
5
- ## Let's use this to simulate a virtual address space
6
- ## Either a 16kb prog rom or 8kb char rom space.
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
- self.create_bank(:prog)
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
- self.create_bank(:char)
25
+ create_bank(:char)
36
26
  end
37
27
 
38
-
39
- ####
40
- ## Create a new bank
41
28
  def self.create_bank(type)
42
- self.new(BankSizes[type], type)
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
- fail(AccessOutOfBounds, sprintf("Address $%.4X is out of bounds in this #{@type} bank"))
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
- ## Since prog rom can be loaded at either 0x8000 or 0xC000
102
- ## We should normalize the addresses to fit properly into
103
- ## these banks, basically it acts like it is mirroring addresses
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
- if address_inside_prog_rom1?(address)
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
- unless address_inside_char_rom?(address)
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