n65 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
+