n65 0.5.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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +340 -0
  5. data/README.md +126 -0
  6. data/Rakefile +2 -0
  7. data/bin/n65 +11 -0
  8. data/data/opcodes.yaml +1030 -0
  9. data/examples/beep.asm +24 -0
  10. data/examples/mario2.asm +260 -0
  11. data/examples/mario2.char +0 -0
  12. data/examples/music_driver.asm +202 -0
  13. data/examples/noise.asm +93 -0
  14. data/examples/pulse_chord.asm +213 -0
  15. data/images/assembler_demo.png +0 -0
  16. data/lib/n65.rb +243 -0
  17. data/lib/n65/directives/ascii.rb +42 -0
  18. data/lib/n65/directives/bytes.rb +102 -0
  19. data/lib/n65/directives/dw.rb +86 -0
  20. data/lib/n65/directives/enter_scope.rb +55 -0
  21. data/lib/n65/directives/exit_scope.rb +35 -0
  22. data/lib/n65/directives/inc.rb +67 -0
  23. data/lib/n65/directives/incbin.rb +51 -0
  24. data/lib/n65/directives/ines_header.rb +53 -0
  25. data/lib/n65/directives/label.rb +46 -0
  26. data/lib/n65/directives/org.rb +47 -0
  27. data/lib/n65/directives/segment.rb +45 -0
  28. data/lib/n65/directives/space.rb +46 -0
  29. data/lib/n65/front_end.rb +90 -0
  30. data/lib/n65/instruction.rb +308 -0
  31. data/lib/n65/instruction_base.rb +29 -0
  32. data/lib/n65/memory_space.rb +150 -0
  33. data/lib/n65/opcodes.rb +9 -0
  34. data/lib/n65/parser.rb +85 -0
  35. data/lib/n65/regexes.rb +33 -0
  36. data/lib/n65/symbol_table.rb +198 -0
  37. data/lib/n65/version.rb +3 -0
  38. data/n65.gemspec +23 -0
  39. data/nes_lib/nes.sym +105 -0
  40. data/test/test_memory_space.rb +82 -0
  41. data/test/test_symbol_table.rb +238 -0
  42. data/utils/midi/Makefile +3 -0
  43. data/utils/midi/c_scale.mid +0 -0
  44. data/utils/midi/convert +0 -0
  45. data/utils/midi/guitar.mid +0 -0
  46. data/utils/midi/include/event.h +93 -0
  47. data/utils/midi/include/file.h +57 -0
  48. data/utils/midi/include/helpers.h +14 -0
  49. data/utils/midi/include/track.h +45 -0
  50. data/utils/midi/lil_melody.mid +0 -0
  51. data/utils/midi/mi_feabhra.mid +0 -0
  52. data/utils/midi/midi_to_nes.rb +204 -0
  53. data/utils/midi/source/convert.cpp +16 -0
  54. data/utils/midi/source/event.cpp +96 -0
  55. data/utils/midi/source/file.cpp +37 -0
  56. data/utils/midi/source/helpers.cpp +46 -0
  57. data/utils/midi/source/track.cpp +37 -0
  58. data/utils/opcode_table_to_yaml.rb +91 -0
  59. metadata +133 -0
@@ -0,0 +1,3 @@
1
+ module N65
2
+ VERSION = "0.5.0"
3
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'n65/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "n65"
8
+ spec.version = N65::VERSION
9
+ spec.authors = ["Safiire"]
10
+ spec.email = ["safiire@irkenkitties.com"]
11
+ spec.summary = %q{An NES assembler for the 6502 microprocessor written in Ruby}
12
+ spec.description = %q{An NES assembler for the 6502 microprocessor written in Ruby}
13
+ spec.homepage = "http://github.com/safiire/n65"
14
+ spec.license = "GPL2"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ end
@@ -0,0 +1,105 @@
1
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2
+ ; Let's start a little library for naming parts of memory in the NES
3
+ ;
4
+ ; Including this file will not emit any instructions or data into your binary
5
+ ; It only defines symbols in the symbol table to name memory addresses
6
+ ;
7
+ ; Author: Saf Allen 2015
8
+ ; I picked up some extra register names for mappers etc here:
9
+ ; http://wiki.nesdev.com/w/index.php/Registers
10
+ ; Some different common names from here:
11
+ ; http://wiki.nesdev.com/w/index.php/PPU_registers
12
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
13
+
14
+ .org $0000
15
+ .scope nes
16
+ .scope ppu
17
+ .org $2000
18
+ .space control 1 ; $2000
19
+ .space mask 1 ; $2001
20
+ .space status 1 ; $2002
21
+ .org $2005
22
+ .space scroll 1 ; $2005
23
+ .
24
+ .org $2003
25
+ .scope oam
26
+ .space address 1 ; $2003
27
+ .space io 1 ; $2004
28
+ .org $4014
29
+ .space dma 1 ; $4014
30
+ .
31
+ .org $2006
32
+ .scope vram
33
+ .space address 1 ; $2006
34
+ .space io 1 ; $2007
35
+ .
36
+
37
+ ; Now let's do the APU registers
38
+ .org $4000
39
+ .scope apu
40
+ .scope pulse1
41
+ .space control 1 ; $4000
42
+ .space ramp_control 1 ; $4001
43
+ .space ft 1 ; $4002
44
+ .space ct 1 ; $4003
45
+ .
46
+ .scope pulse2
47
+ .space control 1 ; $4004
48
+ .space ramp_control 1 ; $4005
49
+ .space ft 1 ; $4006
50
+ .space ct 1 ; $4007
51
+ .
52
+ .scope triangle
53
+ .space control1 1 ; $4008
54
+ .space control2 1 ; $4009
55
+ .space freq1 1 ; $400A
56
+ .space freq2 1 ; $400B
57
+ .
58
+ .scope noise
59
+ .space control1 1 ; $400C
60
+ .space control2 1 ; $400D
61
+ .space freq1 1 ; $400E
62
+ .space freq2 1 ; $400F
63
+ .
64
+ .scope dmc
65
+ .space control 1 ; $4010
66
+ .space da 1 ; $4011
67
+ .space address 1 ; $4012
68
+ .space dl 1 ; $4013
69
+ .
70
+ .org $4015
71
+ .space channel_enable 1 ; $4015
72
+ .
73
+ .org $4016
74
+ .space controller1 1 ; $4016
75
+ .space controller2 1 ; $4017
76
+ .scope mapper
77
+ .org $8000
78
+ .space unrom 1
79
+ .org $8000
80
+ .space cnrom 1
81
+ .org $8000
82
+ .space mmc1_control 1
83
+ .org $A000
84
+ .space mmc1_vrom_low 1
85
+ .org $C000
86
+ .space mmc1_vrom_high 1
87
+ .org $E000
88
+ .space mmc1_prog 1
89
+ .org $8000
90
+ .space mmc3_command 1
91
+ .space mmc3_page 1
92
+ .org $A000
93
+ .space mmc3_mirror 1
94
+ .space mmc3_sram 1
95
+ .org $C000
96
+ .space mmc3_clock 1
97
+ .space mmc3_latch 1
98
+ .org $E000
99
+ .space mmc3_clock_off 1
100
+ .space mmc3_clock_on 1
101
+ .
102
+ .
103
+
104
+ ; Let's put PC back somewhere sane
105
+ .org $0000
@@ -0,0 +1,82 @@
1
+ gem 'minitest'
2
+ require 'minitest/autorun'
3
+ require 'minitest/unit'
4
+
5
+ require_relative '../lib/n65/memory_space.rb'
6
+
7
+
8
+ class TestMemorySpace < MiniTest::Test
9
+ include N65
10
+
11
+ def _test_create_prog_rom
12
+ ## First just try to read alll of it
13
+ space = MemorySpace.create_prog_rom
14
+ contents = space.read(0x8000, 0x4000)
15
+ assert_equal(contents.size, 0x4000)
16
+ assert(contents.all?{|byte| byte.zero?})
17
+
18
+ ## It is mirrored so this should also work
19
+ space = MemorySpace.create_prog_rom
20
+ contents = space.read(0xC000, 0x4000)
21
+ assert_equal(contents.size, 0x4000)
22
+ assert(contents.all?{|byte| byte.zero?})
23
+ end
24
+
25
+
26
+ def _test_writing
27
+ ## Write some bytes into prog 2 area
28
+ space = MemorySpace.create_prog_rom
29
+ space.write(0xC000, "hi there".bytes)
30
+
31
+ ## Read them back..
32
+ contents = space.read(0xC000, 8)
33
+ assert_equal('hi there', contents.pack('C*'))
34
+
35
+ ## Should be mirrored in prog 1
36
+ contents = space.read(0x8000, 8)
37
+ assert_equal('hi there', contents.pack('C*'))
38
+ end
39
+
40
+
41
+ def _test_reading_out_of_bounds
42
+ space = MemorySpace.create_prog_rom
43
+ assert_raises(MemorySpace::AccessOutsideProgRom) do
44
+ space.read(0x200, 10)
45
+ end
46
+
47
+ ## But that is valid char rom area, so no explody
48
+ space = MemorySpace.create_char_rom
49
+ space.read(0x200, 10)
50
+
51
+ ## But something like this should explode
52
+ space = MemorySpace.create_char_rom
53
+ assert_raises(MemorySpace::AccessOutsideCharRom) do
54
+ space.read(0x8001, 10)
55
+ end
56
+ end
57
+
58
+
59
+ ####
60
+ ## There seem to be problems writing bytes right to
61
+ ## the end of the memory map, specifically where the
62
+ ## vector table is in prog rom, so let's test that.
63
+ def test_writing_to_end
64
+ space = MemorySpace.create_prog_rom
65
+ bytes = [0xDE, 0xAD]
66
+
67
+ ## Write the NMI address to FFFA
68
+ space.write(0xFFFA, bytes)
69
+
70
+ ## Write the entry point to FFFC
71
+ space.write(0xFFFC, bytes)
72
+
73
+ ## Write the irq to FFFE, and this fails, saying
74
+ ## I'm trying to write to $10000 for some reason.
75
+ space.write(0xFFFE, bytes)
76
+
77
+ ## Write to the very first
78
+ space.write(0x8000, bytes)
79
+ end
80
+
81
+ end
82
+
@@ -0,0 +1,238 @@
1
+ gem 'minitest'
2
+ require 'minitest/autorun'
3
+ require 'minitest/unit'
4
+
5
+ require_relative '../lib/n65/symbol_table.rb'
6
+ require_relative '../lib/n65.rb'
7
+
8
+
9
+ class TestSymbolTable < MiniTest::Test
10
+ include N65
11
+
12
+ ####
13
+ ## Test that we can make simple global symbols
14
+ def test_define_global_symbols
15
+ st = SymbolTable.new
16
+ st.define_symbol('dog', 'woof')
17
+ assert_equal('woof', st.resolve_symbol('dog'))
18
+ end
19
+
20
+
21
+ ####
22
+ ## Test entering into a sub scope, and setting and retrieving values
23
+ def test_enter_scope
24
+ st = SymbolTable.new
25
+ st.enter_scope('animals')
26
+ st.define_symbol('dog', 'woof')
27
+ assert_equal('woof', st.resolve_symbol('dog'))
28
+ end
29
+
30
+
31
+ ####
32
+ ## Access something from an outer scope without dot syntax
33
+ def test_outer_scope
34
+ st = SymbolTable.new
35
+ st.enter_scope('outer')
36
+ st.define_symbol('dog', 'woof')
37
+ st.enter_scope('inner')
38
+ st.define_symbol('pig', 'oink')
39
+ assert_equal('woof', st.resolve_symbol('dog'))
40
+ end
41
+
42
+
43
+ ####
44
+ ## Access something from an outer scope without dot syntax
45
+ def test_shadow
46
+ st = SymbolTable.new
47
+ st.enter_scope('outer')
48
+ st.define_symbol('dog', 'woof')
49
+ st.enter_scope('inner')
50
+ st.define_symbol('dog', 'bark')
51
+ assert_equal('bark', st.resolve_symbol('dog'))
52
+ assert_equal('woof', st.resolve_symbol('outer.dog'))
53
+ st.exit_scope
54
+ st.exit_scope
55
+ assert_equal('bark', st.resolve_symbol('outer.inner.dog'))
56
+ end
57
+
58
+
59
+ ####
60
+ ## Test exiting a sub scope, and seeing that the variable is unavailable by simple name
61
+ def test_exit_scope
62
+ st = SymbolTable.new
63
+ st.enter_scope('animals')
64
+ st.define_symbol('dog', 'woof')
65
+ assert_equal('woof', st.resolve_symbol('dog'))
66
+
67
+ st.exit_scope
68
+
69
+ assert_raises(SymbolTable::UndefinedSymbol) do
70
+ assert_equal('woof', st.resolve_symbol('dog'))
71
+ end
72
+ end
73
+
74
+
75
+ ####
76
+ ## Test exiting a sub scope, and being able to access a symbol through a full path
77
+ def test_exit_scope
78
+ st = SymbolTable.new
79
+ st.enter_scope('animals')
80
+ st.define_symbol('dog', 'woof')
81
+ assert_equal('woof', st.resolve_symbol('dog'))
82
+
83
+ st.exit_scope
84
+
85
+ assert_equal('woof', st.resolve_symbol('animals.dog'))
86
+ end
87
+
88
+
89
+ ####
90
+ ## Have two symbols that are the same but are in different scopes
91
+ def test_two_scopes_same_symbol
92
+ st = SymbolTable.new
93
+ st.define_symbol('dog', 'woof')
94
+ assert_equal('woof', st.resolve_symbol('dog'))
95
+
96
+ st.enter_scope('animals')
97
+
98
+ st.define_symbol('dog', 'woofwoof')
99
+ assert_equal('woofwoof', st.resolve_symbol('dog'))
100
+
101
+ st.exit_scope
102
+
103
+ assert_equal('woof', st.resolve_symbol('dog'))
104
+ assert_equal('woofwoof', st.resolve_symbol('animals.dog'))
105
+ end
106
+
107
+
108
+ ####
109
+ ## How do you get stuff out of the global scope when you are in
110
+ ## a sub scope?
111
+ def test_access_global_scope
112
+ st = SymbolTable.new
113
+ st.define_symbol('dog', 'woof')
114
+ assert_equal('woof', st.resolve_symbol('dog'))
115
+
116
+ st.enter_scope('animals')
117
+ st.define_symbol('pig', 'oink')
118
+ assert_equal('oink', st.resolve_symbol('pig'))
119
+
120
+ ## Ok, now I want to access global.dog basically from the previous scope
121
+ assert_equal('woof', st.resolve_symbol('global.dog'))
122
+ end
123
+
124
+
125
+ ####
126
+ ## Now I want to just test making an anonymous scope
127
+ def test_anonymous_scope
128
+ st = SymbolTable.new
129
+ st.define_symbol('dog', 'woof')
130
+ assert_equal('woof', st.resolve_symbol('dog'))
131
+
132
+ st.enter_scope
133
+ st.define_symbol('pig', 'oink')
134
+ assert_equal('oink', st.resolve_symbol('pig'))
135
+
136
+ ## Ok, now I want to access global.dog basically from the previous scope
137
+ assert_equal('woof', st.resolve_symbol('global.dog'))
138
+
139
+ ## Now exit the anonymous scope and get dog
140
+ st.exit_scope
141
+ assert_equal('woof', st.resolve_symbol('global.dog'))
142
+ assert_equal('woof', st.resolve_symbol('dog'))
143
+ end
144
+
145
+
146
+ ####
147
+ ## Now I want to test that I cannot exist the outer-most
148
+ ## global scope by mistake
149
+ def test_cant_exit_global
150
+ st = SymbolTable.new
151
+ assert_raises(SymbolTable::CantExitScope) do
152
+ st.exit_scope
153
+ end
154
+ end
155
+
156
+
157
+ ####
158
+ ## I would like the name of the scope to take on the
159
+ ## value of the program counter at that location.
160
+ def test_scope_as_symbol
161
+ program = <<-ASM
162
+ .ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 0}
163
+ .org $8000
164
+ .segment prog 0
165
+ .scope main
166
+ sei
167
+ cld
168
+ lda \#$00
169
+ jmp main
170
+ .
171
+ jmp main
172
+ jmp global.main
173
+ ASM
174
+
175
+ #### There really should be an evaluate string method
176
+ assembler = Assembler.new
177
+ program.split(/\n/).each do |line|
178
+ assembler.assemble_one_line(line)
179
+ end
180
+ assembler.fulfill_promises
181
+ assert_equal(0x8000, assembler.symbol_table.resolve_symbol('global.main'))
182
+ end
183
+
184
+
185
+ ####
186
+ ## Fix a bug where we can't see a forward declared symbol in a scope
187
+ def test_foward_declaration_in_scope
188
+ program = <<-ASM
189
+ ;;;;
190
+ ; Create an iNES header
191
+ .ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 0}
192
+
193
+ ;;;;
194
+ ; Try to expose a problem we have with scopes
195
+ ; We don't seem to be able to branch to a forward
196
+ ; declared symbol within a scope
197
+ .org $8000
198
+ .scope main
199
+ sei
200
+ cld
201
+ lda #\$00
202
+ bne forward_symbol
203
+ nop
204
+ nop
205
+ nop
206
+ forward_symbol:
207
+ rts
208
+ .
209
+ ASM
210
+
211
+ #### There really should be an evaluate string method
212
+ assembler = Assembler.new
213
+ program.split(/\n/).each do |line|
214
+ assembler.assemble_one_line(line)
215
+ end
216
+ puts YAML.dump(assembler.symbol_table)
217
+ assembler.fulfill_promises
218
+
219
+ #### The forward symbol should have been resolved to +3, and the ROM should look like this:
220
+ correct_rom = [0x4e, 0x45, 0x53, 0x1a, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
221
+ 0x78, # SEI
222
+ 0xd8, # CLD
223
+ 0xa9, 0x0, # LDA immediate 0
224
+ 0xd0, 0x3, # BNE +3
225
+ 0xea, # NOP
226
+ 0xea, # NOP
227
+ 0xea, # NOP
228
+ 0x60 # RTS forward_symbol
229
+ ]
230
+
231
+ #### Grab the first 26 bytes of the rom and make sure they assemble to the above
232
+ emitted_rom = assembler.emit_binary_rom.bytes[0...26]
233
+ assert_equal(correct_rom, emitted_rom)
234
+ #### Yup it is fixed now.
235
+ end
236
+
237
+ end
238
+