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.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/LICENSE +340 -0
- data/README.md +126 -0
- data/Rakefile +2 -0
- data/bin/n65 +11 -0
- data/data/opcodes.yaml +1030 -0
- data/examples/beep.asm +24 -0
- data/examples/mario2.asm +260 -0
- data/examples/mario2.char +0 -0
- data/examples/music_driver.asm +202 -0
- data/examples/noise.asm +93 -0
- data/examples/pulse_chord.asm +213 -0
- data/images/assembler_demo.png +0 -0
- data/lib/n65.rb +243 -0
- data/lib/n65/directives/ascii.rb +42 -0
- data/lib/n65/directives/bytes.rb +102 -0
- data/lib/n65/directives/dw.rb +86 -0
- data/lib/n65/directives/enter_scope.rb +55 -0
- data/lib/n65/directives/exit_scope.rb +35 -0
- data/lib/n65/directives/inc.rb +67 -0
- data/lib/n65/directives/incbin.rb +51 -0
- data/lib/n65/directives/ines_header.rb +53 -0
- data/lib/n65/directives/label.rb +46 -0
- data/lib/n65/directives/org.rb +47 -0
- data/lib/n65/directives/segment.rb +45 -0
- data/lib/n65/directives/space.rb +46 -0
- data/lib/n65/front_end.rb +90 -0
- data/lib/n65/instruction.rb +308 -0
- data/lib/n65/instruction_base.rb +29 -0
- data/lib/n65/memory_space.rb +150 -0
- data/lib/n65/opcodes.rb +9 -0
- data/lib/n65/parser.rb +85 -0
- data/lib/n65/regexes.rb +33 -0
- data/lib/n65/symbol_table.rb +198 -0
- data/lib/n65/version.rb +3 -0
- data/n65.gemspec +23 -0
- data/nes_lib/nes.sym +105 -0
- data/test/test_memory_space.rb +82 -0
- data/test/test_symbol_table.rb +238 -0
- data/utils/midi/Makefile +3 -0
- 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 +93 -0
- data/utils/midi/include/file.h +57 -0
- data/utils/midi/include/helpers.h +14 -0
- data/utils/midi/include/track.h +45 -0
- data/utils/midi/lil_melody.mid +0 -0
- data/utils/midi/mi_feabhra.mid +0 -0
- data/utils/midi/midi_to_nes.rb +204 -0
- data/utils/midi/source/convert.cpp +16 -0
- data/utils/midi/source/event.cpp +96 -0
- data/utils/midi/source/file.cpp +37 -0
- data/utils/midi/source/helpers.cpp +46 -0
- data/utils/midi/source/track.cpp +37 -0
- data/utils/opcode_table_to_yaml.rb +91 -0
- metadata +133 -0
data/lib/n65/version.rb
ADDED
data/n65.gemspec
ADDED
@@ -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
|
data/nes_lib/nes.sym
ADDED
@@ -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
|
+
|