diff --git a/examples/scales.asm b/examples/scales.asm new file mode 100644 index 0000000..123e7d1 --- /dev/null +++ b/examples/scales.asm @@ -0,0 +1,182 @@ +.ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 0} +.inc + +.segment prog 0 + +;; SRAM Variables +.org $0000 +.scope audio + .space timer 1 + .space next_note 1 + .space note_frequency 2 +. + +;; Interrupt vectors +.org $FFFA +.dw vblank +.dw main +.dw irq + + +.org $C000 +.scope main + sei + cld + + ;; Setup the stack + ldx #$ff + txs + + ;; Disable rendering, reset APU + ldx #$00 + stx nes.ppu.control + stx nes.ppu.mask + jsr zero_apu + + .scope + wait_vblank: + bit nes.ppu.status + bpl wait_vblank + . + + clear_ram: + lda #$00 + sta $00, x + sta $100, x + sta $300, x + sta $400, x + sta $500, x + sta $600, x + sta $700, x + lda #$ff + sta $200, x + inx + bne clear_ram + + .scope + wait_vblank: + bit nes.ppu.status + bpl wait_vblank + . + + jsr initialize + + forever: + jmp forever + rti +. + + +;; Zero the APU +.scope zero_apu + lda #$00 + ldx #$00 + loop: + sta $4000, x + inx + cpx $18 + bne loop + rts +. + + +;; Initialize PPU and APU +.scope initialize + lda #%00000011 + sta nes.apu.channel_enable + + ; Reenable interrupts, Turn Vblank back on + lda #%10000000 + sta nes.ppu.control + + ; Initialize the audio structure + lda #$00 + sta audio.timer zp + lda #$30 + sta audio.next_note zp + + cli + rts +. + + +;; Keep time via 60fps vblank +.scope vblank + ; Update the audio timer so it resets every 64 frames + ldx audio.timer zp + inx + txa + and #%00000011 + sta audio.timer zp + bne return + + ; Play the next note on reset + lda audio.next_note zp + cmp #$80 + bmi continue + lda #$30 + + continue: + jsr play_note + sta audio.next_note zp + inc audio.next_note zp + + return: + rti +. + + +;; Hi and lo byte tables for note frequencies +.scope midi_notes + .scope hi + .bytes $35, $32, $2f, $2c, $2a, $28, $25, $23, $21, $1f, $1d, $1c, $1a, $19, $17, $16 + .bytes $15, $14, $12, $11, $10, $0f, $0e, $0e, $0d, $0c, $0b, $0b, $0a, $0a, $09, $08 + .bytes $08, $07, $07, $07, $06, $06, $05, $05, $05, $05, $04, $04, $04, $03, $03, $03 + .bytes $03, $03, $02, $02, $02, $02, $02, $02, $02, $01, $01, $01, $01, $01, $01, $01 + .bytes $01, $01, $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .bytes $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .bytes $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .bytes $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + . + .scope lo + .bytes $71, $71, $9c, $f0, $6a, $09, $ca, $ab, $aa, $c6, $fe, $4f, $b8, $38, $ce, $78 + .bytes $35, $04, $e4, $d5, $d5, $e3, $fe, $27, $5b, $9c, $e6, $3b, $9a, $01, $72, $ea + .bytes $6a, $f1, $7f, $13, $ad, $4d, $f3, $9d, $4c, $00, $b8, $74, $34, $f8, $bf, $89 + .bytes $56, $26, $f9, $ce, $a6, $80, $5c, $3a, $1a, $fb, $df, $c4, $ab, $93, $7c, $67 + .bytes $52, $3f, $2d, $1c, $0c, $fd, $ef, $e1, $d5, $c9, $bd, $b3, $a9, $9f, $96, $8e + .bytes $86, $7e, $77, $70, $6a, $64, $5e, $59, $54, $4f, $4b, $46, $42, $3f, $3b, $38 + .bytes $34, $31, $2f, $2c, $29, $27, $25, $23, $21, $1f, $1d, $1b, $1a, $18, $17, $15 + .bytes $14, $13, $12, $11, $10, $0f, $0e, $0d, $0c, $0c, $0b, $0a, $0a, $09, $08, $08 + . +. + + +;; Play midi note held in A +.scope play_note + pha + tax + lda #%10011111 + sta nes.apu.pulse1.control + + ; Get the low byte of the timer + ldy midi_notes.lo, x + sty nes.apu.pulse1.ft + sty audio.note_frequency+1 zp + + ; Get the high 3 bits of the timer + ldy midi_notes.hi, x + tya + and #%00000111 + ora #%11111000 + sta nes.apu.pulse1.ct + sta audio.note_frequency zp + + pla + rts +. + + +;; IRQ, we are not using +.scope irq + rti +. diff --git a/lib/n65.rb b/lib/n65.rb index 5eef5b1..70fda67 100644 --- a/lib/n65.rb +++ b/lib/n65.rb @@ -18,13 +18,14 @@ module N65 #### ## Assemble from an asm file to a nes ROM - def self.from_file(infile, outfile) + def self.from_file(infile, options) fail(FileNotFound, infile) unless File.exists?(infile) assembler = self.new program = File.read(infile) + output_file = options[:output_file] - puts "Building #{infile}" + puts "Building #{infile}" unless options[:quiet] ## Process each line in the file program.split(/\n/).each_with_index do |line, line_number| begin @@ -33,28 +34,35 @@ module N65 STDERR.puts("\n\n#{e.class}\n#{line}\n#{e}\nOn line #{line_number}") exit(1) end - print '.' + print '.' unless options[:quiet] end - puts + puts unless options[:quiet] ## Second pass to resolve any missing symbols. - print "Second pass, resolving symbols..." + print "Second pass, resolving symbols..." unless options[:quiet] assembler.fulfill_promises - puts " Done." + puts " Done." unless options[:quiet] - ## Let's not export the symbol table to a file anymore - ## Will add an option for this later. - #print "Writing symbol table to #{outfile}.yaml..." - #File.open("#{outfile}.yaml", 'w') do |fp| - #fp.write(assembler.symbol_table.export_to_yaml) - #end - #puts "Done." + if options[:write_symbol_table] + print "Writing symbol table to #{output_file}.yaml..." unless options[:quiet] + File.open("#{output_file}.yaml", 'w') do |fp| + fp.write(assembler.symbol_table.export_to_yaml) + end + puts "Done." unless options[:quiet] + end ## Emit the complete binary ROM - File.open(outfile, 'w') do |fp| - fp.write(assembler.emit_binary_rom) + rom = assembler.emit_binary_rom + File.open(output_file, 'w') do |fp| + fp.write(rom) + end + + unless options[:quiet] + rom_size = rom.size + rom_size_hex = "%x" % rom_size + assembler.print_bank_usage + puts "Total size: $#{rom_size_hex}, #{rom_size} bytes" end - puts "All Done :)" end @@ -190,15 +198,11 @@ module N65 def emit_binary_rom progs = @virtual_memory[:prog] chars = @virtual_memory[:char] - puts "iNES Header" - puts "+ #{progs.size} PROG ROM bank#{progs.size != 1 ? 's' : ''}" - puts "+ #{chars.size} CHAR ROM bank#{chars.size != 1 ? 's' : ''}" rom_size = 0x10 rom_size += MemorySpace::BankSizes[:prog] * progs.size rom_size += MemorySpace::BankSizes[:char] * chars.size - puts "= Output ROM will be #{rom_size} bytes" rom = MemorySpace.new(rom_size, :rom) offset = 0x0 @@ -215,6 +219,24 @@ module N65 end + #### + ## Display information about the bank sizes and total usage + def print_bank_usage + puts + puts "ROM Structure {" + puts " iNES 1.0 Header: $10 bytes" + + @virtual_memory[:prog].each_with_index do |prog_rom, bank_number| + puts " PROG ROM bank #{bank_number}: #{prog_rom.usage_info}" + end + + @virtual_memory[:char].each_with_index do |char_rom, bank_number| + puts " CHAR ROM bank #{bank_number}: #{char_rom.usage_info}" + end + puts "}" + end + + private diff --git a/lib/n65/front_end.rb b/lib/n65/front_end.rb index 195f38e..6801bff 100644 --- a/lib/n65/front_end.rb +++ b/lib/n65/front_end.rb @@ -11,7 +11,7 @@ module N65 #### ## Initialize with ARGV commandline def initialize(argv) - @options = {:output_file => nil} + @options = {output_file: nil, write_symbol_table: false, quiet: false} @argv = argv.dup end @@ -31,7 +31,7 @@ module N65 ## Only can assemble one file at once for now if @argv.size != 1 - STDERR.puts "Can only assemble one input file at once :(" + STDERR.puts "Can only assemble one input file at once, but you can use .inc and .incbin directives" exit(1) end @@ -54,7 +54,7 @@ module N65 exit(1) end - N65::Assembler.from_file(input_file, @options[:output_file]) + N65::Assembler.from_file(input_file, @options) end private @@ -69,12 +69,19 @@ module N65 @options[:output_file] = output_file; end + opts.on('-s', '--symbols', 'Outputs a symbol map') do + @options[:write_symbol_table] = true + end + + opts.on('-q', '--quiet', 'No output on success') do + @options[:quiet] = true + end + opts.on('-v', '--version', 'Displays Version') do puts "N65 Assembler Version #{N65::VERSION}" exit end - opts.on('-h', '--help', 'Displays Help') do puts opts exit diff --git a/lib/n65/memory_space.rb b/lib/n65/memory_space.rb index fb260ca..4b0f595 100644 --- a/lib/n65/memory_space.rb +++ b/lib/n65/memory_space.rb @@ -48,6 +48,7 @@ module N65 def initialize(size, type) @type = type @memory = Array.new(size, 0x0) + @bytes_written = 0 end @@ -71,6 +72,7 @@ module N65 bytes.each_with_index do |byte, index| @memory[from_normalized + index] = byte + @bytes_written += 1 end bytes.size end @@ -83,6 +85,17 @@ module N65 end + #### + ## Bank Usage information + def usage_info + percent_used = @bytes_written / @memory.size.to_f * 100 + percent_string = "%0.2f" % percent_used + bytes_written_hex = "$%04x" % @bytes_written + memory_size_hex = "$%04x" % @memory.size + "(#{bytes_written_hex} / #{memory_size_hex}) #{percent_string}%" + end + + private #### diff --git a/lib/n65/version.rb b/lib/n65/version.rb index 57f4638..0736b5e 100644 --- a/lib/n65/version.rb +++ b/lib/n65/version.rb @@ -1,3 +1,3 @@ module N65 - VERSION ||= "1.5.0" + VERSION ||= "1.5.2" end