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