Page 1 of 1
Assembler & multiple ROM banks
Posted: Fri Mar 16, 2007 6:53 pm
by tokumaru
How do people usually handle coding programs larger than 32KB? I mean, how would you would you handle the source files and assemble them?
I really do not know much about assemblers, but I want to code my game right (clean source code). I'm trying to figure some assemblers out, but I can't seem to find out how to have blocks of code that share the same memory area (different banks that will be mapped to the same memory area) and still have them all see the labels and variables from the hardwired bank and vice-versa.
Can anyone help me out with this? What would be the best assembler to work with multiple ROM banks? Thanks for the help.
Re: Assembler & multiple ROM banks
Posted: Fri Mar 16, 2007 8:15 pm
by AWal
tokumaru wrote:I can't seem to find out how to have blocks of code that share the same memory area...
To be able to switch banks of code, you will need to grasp what the cartridge you will be coding for is capable of and what limits your compiler has.
Taking in my example(...alone as it seems), using Telemark Assembler (TASM) will automatically organize my code into Kilobyte chunks depending on how I organize my data. e.g.:
Code: Select all
.org $C000
Start:
SEI
CLD
LDA #$00
LDX #$FF
TXS
INX
TAY
...
.org $FFFA
Vectors:
.dw ReturnIt
.dw Start
.dw ReturnIt
.end
When compiled that will automagically produce the correct size program rom for an NROM-128 board. (The 128 is kilobits)
Now, if I were to:
Code: Select all
...
.org $9FF8
Vectors:
.dw ReturnIt
.dw Start
.dw ReturnIt
.end
This would only produce a Kilobyte of code. I could then use a dos command "copy /b 1.bin+2.bin+3.bin+4.bin prgrom.bin" to build a complete program rom. Now of course there are tools to build these for you, but somtimes it better to see what's happening (a piece of code may have not compiled becuase of an error, for example).
tokumaru wrote:...still have them all see the labels and variables from the hardwired bank and vice-versa.
Most, if not, all compilers include an "include" function that allows the compiler to jump to another file and insert it into the currently compiling program. One feature of this caveat is that you can also define variables, macros, and constants from another file using this technique.
So, if you had a set of variables in RAM space, and you had them defined in your main.asm, but also needed them just about everywhre else, you can simply copy them to another file, and then include that file where you'd need it. This saves programmers lots of time and is very effective when working with well-managed code. e.g.:
Code: Select all
OK. Let's say I have these variables in my main.asm file, but need them in my world.asm file as well:
;Variables
Pad1Stat = $00
Pad2Stat = $01
Pad3Stat = $02
Pad4Stat = $03
Pad1Delta1 = $04
Pad1Delta2 = $05
Pad2Delta1 = $06
Pad2Delta2 = $07
BinaryRoll = $10
I could simply create a new file (In this case, i'll call it MAIN.h, since .h is generally used for include files among compilers, but anything will work with most compilers)and in it I'll place that code. Now in my main.asm I'd need to replace all that with a new line (example using TASM, may vary depending on compiler used):
#INCLUDE "MAIN.H"
Now my main.asm file will compile that main.h file (in it's entirety) before continuing back to it's own file. I'd also add that line in my world.asm file as well, so that i'll compile and recognise those variables as well.
tokumaru wrote:What would be the best assembler to work with multiple ROM banks? Thanks for the help.
All compilers are capable of multiple banks (some to an extent). Just go with whatever you feel comfortable using.
Posted: Fri Mar 16, 2007 8:50 pm
by doppelganger
Shouldn't those vectors start at $9ffa or $fffa instead of $9ff8 or $fff8?
Re: Assembler & multiple ROM banks
Posted: Fri Mar 16, 2007 8:57 pm
by tokumaru
AWal wrote:When compiled that will automagically produce the correct size program rom for an NROM-128 board. (The 128 is kilobits)
Yes, I'm used to working like this.
I could then use a dos command "copy /b 1.bin+2.bin+3.bin+4.bin prgrom.bin" to build a complete program rom.
That's precisely what I'm trying to avoid. Sure, I could assemble each piece individually, but then each piece of code would not be able to see variables and labels defined in the main PRG bank. Nor would it be able to see labels and variables of the other ones.
you can simply copy them to another file, and then include that file where you'd need it.
Sure, I have the intention to do this for global variables, but that still doesn't help with the labels.
All compilers are capable of multiple banks (some to an extent). Just go with whatever you feel comfortable using.
I'm trying to go with ca65...
My problem is that I have a hardwired bank, that can call subroutines in the lower, switchable bank. To call routines in different banks, I need the labels from different banbks to be avaliable to the hardwired bank.
I also want all switchable banks to be able to see the labels in the hardwired bank. This seems easier, 'cause I guess I could just include the code for the hardwired bank at the end when assembling all other banks but have the assembler output only the lower part.
I can't seem to figure out a way for the hardwired bank to see the labels of all others, though.
Posted: Fri Mar 16, 2007 9:43 pm
by AWal
doppelganger wrote:Shouldn't those vectors start at $9ffa or $fffa instead of $9ff8 or $fff8?
Yes, they should, thanks for noticing...I add a bit of text before my vector arrangement, so it's always like $xxxx-x for text.
tokumaru wrote:I need the labels from different banbks to be avaliable to the hardwired bank.
For the labels, there is another function known as a symbol table. e.g.:
Code: Select all
With TASM, in a case that'd make it easy to import existing labels form other code, I'd use the EXPORT directive
.org $C000
Start:
SEI
CLD
.org $E000
RetIt:
RET
.org $FFFA
;Vectors
.dw RetIt
.dw Reset
.dw RetIt
EXPORT Start, RetIt
.end
This would write an aditional file (with an .exp extension...I think...), that I'd include elsewhere, and I could use those labels freely as I'd need.
With TASM, there is both a symbol table and an export symbol table. The major difference between these two files is that the export table includes .equ or = directives so that it can be directly compiled. The symbol table is mainly designed for debugging, and can help out definining what rountines go where, especially when you have a lot of them.
I hope this is helping...and at this point I'm only assuming UNROM since you suggested hardwired bank + changing the lower one.
I seem to remember one of the megamans using a technique where all the code resided in the fixed bank, and it only used data from the others.
Also, one thing I forgot to mention is bus contention issues. Some game cartridges did not disable the program rom chip when writing data, so the cartridge may overpower the output. Check a pcb of the cartridge type you are programming for to see if there is an or gate (74x32). Usually that will conclude that it's okay to write whereever, otherwise you'll need to reserve a 256-Byte bank of numbers, in hexidecimal order, then write your commands as e.g.: STA WriteBank, X .
Posted: Fri Mar 16, 2007 9:58 pm
by tokumaru
AWal wrote:I'm only assuming UNROM
UOROM actually. It's the same thing, but with 256KB of ROM.
I seem to remember one of the megamans using a technique where all the code resided in the fixed bank, and it only used data from the others.
That'd be great, but 16KB would not be enough for all my code.
Also, one thing I forgot to mention is bus contention issues.
Yeah, I'm aware of all the hardware issues. My problem really is all about being able to see the labels and variables across the different banks. I'll try this IMPORT/EXPORT thing...
Posted: Fri Mar 16, 2007 10:51 pm
by tokumaru
I just realized I am really stupid and don't know how to work with assemblers at all.
So far, I've done all my NES development with the 6502 simulator, which would let me save the binary data assembled from my code directly to a file. As simple as that, I had my .nes file ready.
Now I'm trying to learn to use some actual assemblers, and can't even seem to go past the linking process. In fact, I don't think I understand this process at all.
So, I get this object file. What do I do with it? Link? How? What the hell does "linking" do?
So yeah, I can't seem to be able to use any assemblers, I'm that stupid. I'd appreciate any help here now. Thanks.
Posted: Fri Mar 16, 2007 11:16 pm
by Memblers
I think the object file has the code, but no addresses. Linking it fills out all the memory locations and stuff depending on the configuration. Something like that.
I had to really fight with LD65 to get it to pad the files. Here's my linker config for Garage Cart (using 32kB banks and really hacked together):
Code: Select all
MEMORY {
ZP: start = $00, size = $100, type = rw;
RAM: start = $200, size = $600, type = rw;
WRAM: start = $6000, size = $2000, type = rw;
PRG1A: start = $8000, size = $3F80, type = ro, file = "prg1a.prg";
PRG1B: start = $C000, size = $4000, type = ro, file = "prg1b.prg";
PRG2A: start = $8000, size = $3F80, type = ro, file = "prg2a.prg";
PRG2B: start = $C000, size = $4000, type = ro, file = "prg2b.prg";
BOOTROM: start = $BF6E, size = $092, type = ro, fill = $00, file = "boot.prg";
NSFHEAD: start = $7F80, size = $0080, type = ro, file = "nsfhead";
}
SEGMENTS {
NSFHEADER: load = NSFHEAD, type = ro, start = $7F80;
CODE: load = PRG1A, type = ro, start = $8000;
BOOTCODE: load = BOOTROM, type = ro, start = $BF6E;
SAMPLES: load = PRG1B, type = ro, start = $C000;
CODE2: load = PRG2A, type = ro, start = $8000;
SAMPLES2: load = PRG2B, type = ro, start = $C000;
DATA: load = RAM, type = bss;
WRAM: load = WRAM, type = rw;
ZEROPAGE: load = ZP, type = zp;
BSS: load = RAM, type = bss, define = yes;
VECTORS: load = PRG1B, type = ro, start = $FFFA;
VECTORS2: load = PRG2B, type = ro, start = $FFFA;
CRAP0: load = PRG1A, type = ro, start = $BF6D;
CRAP1: load = PRG2A, type = ro, start = $BF6D;
CRAP2: load = BOOTROM, type = ro, start = $BFFF;
}
It was just using different segments in one big source file (well, with other source files .included too). Also, on the memory config if you set the filename to be same for all of them, it'll just put it all together for you. But I had trouble getting it to pad at the end of the bank, so I had to set my bootcode origin manually (usually the vectors handles that).
Posted: Fri Mar 16, 2007 11:42 pm
by tokumaru
Thanks Memblers, but I'm still very confused... I can't see where I'd write stuff like that, how and why.
I guess I'll be writing my own assembler then... (a simple one, with just what I really need!)
Seriously, how lame it is that I am able to think in 6502 ASM all day long but am not able to assemble the simplest program?
I just don't get why you need to do something so complicated...! Start putting values by hand? What sense does it make?
Posted: Sat Mar 17, 2007 1:21 am
by Bregalad
There is basically 2 ways of doing it :
- Have your assembler decide wich piece of code does in which bank, and place assembler if directive and all that stuff to do proper bankswitching. Typically you'd check if your routine is in the same bank than the previous one, and if not do the bankswitching. I recommand this method if you want to go with full 32kb pages, because if doesn't really matter in which order the banks are, and you just want to follow your code exactly no matter in wich bank it is. Your assembler must support multiple NMI/IRQ/Reset vectors pointing to the same adress in multiple banks however (I think WLA-DX does). Also, WLA-DX assemble stuff in sections, so that you're sure the code in a given section will always get in the same bank.
- Have yourself manage which data is in wich bank and just tell the assembler to do so. Then you decide "My graphics are in banks 0 and 1, my sound code is in bank 2, my game engine is in bank 3, ennemy data is in bank 4, map data is in bank 5, and my main code is in bank 7 wich is hardwired and will always be present at $c000-$ffff, including interrupts, reset, PPU writes, etc...".
Then you just code your stuff and tell the assembler to do so. I recommand this for 16k/16k hardwired pages. The main advantage is that it is easier to manage, but however, you never know if suddently you need more banks for something you havent planned, or if you just waste bank with little data in them. A disavantage of this is that you are very succeptible to find your hardwired bank overflowing, and be embarassed because this will force you to move stuff in bankswitced areas, and you'll be screwed (32k banks doesn't have this problem).
- Of course, you can mix both methods, and just have two sections in your code : The hardwired one and the bankswitched one. And then you would bankwitch stuff at $8000-$bfff by testing the bank index of your labals in bankswitched sections your assembler gives to you (assuming it does, however).
Posted: Mon Mar 19, 2007 2:15 pm
by commodorejohn
Hah, I'm working on-and-off on a project that comes out to 520KB - it's a port of another game, and it breaks down to 8KB of code and game data, 8KB of VROM, and 504KB of speech samples =) (8-bit speech samples directly outputted, that is, not DMC samples. And I have to pause whatever else I'm doing when playing a speech sample, but thankfully that's about how it worked in the original game anyway.)
Anyway, I'm using NESASM for it. I know a lot of people here dislike it, and they have valid reasons for doing so, but its banking support is quite nice, thanks to the fact that it was originally designed for the TG16, which has bankswapping functionality built right in. The ROM is divided into 8KB pages (as in the TG16 hardware mapper,) which is the smallest page size for NES mappers anyway. Page boundaries can be specified manually in the code, or handled automatically by the assembler. Very nice.
Posted: Mon Mar 19, 2007 3:53 pm
by tepples
commodorejohn wrote:Hah, I'm working on-and-off on a project that comes out to 520KB - it's a port of another game, and it breaks down to 8KB of code and game data, 8KB of VROM, and 504KB of speech samples =) (8-bit speech samples directly outputted, that is, not DMC samples. And I have to pause whatever else I'm doing when playing a speech sample, but thankfully that's about how it worked in the original game anyway.)
Is there a reason that you're using 8-bit (really 7-bit) speech samples instead of, say, 4-bit speech samples? Have you considered some form of data compression like
Big Bird's Hide and Speak uses?
Posted: Mon Mar 19, 2007 4:16 pm
by never-obsolete
commodorejohn wrote:
Anyway, I'm using NESASM for it. I know a lot of people here dislike it, and they have valid reasons for doing so, but its banking support is quite nice, thanks to the fact that it was originally designed for the TG16, which has bankswapping functionality built right in. The ROM is divided into 8KB pages (as in the TG16 hardware mapper,) which is the smallest page size for NES mappers anyway. Page boundaries can be specified manually in the code, or handled automatically by the assembler. Very nice.
i couldn't figure out the banking system nesasm used until after i wrote an assmelber. i feel kinda dumb now...
my assembler uses 16K banks with ".low" and ".high" directives for splitting a bank into 2 seperate 8K banks. it seems to work quite nicely.
Posted: Mon Mar 19, 2007 5:34 pm
by commodorejohn
tepples wrote:commodorejohn wrote:Hah, I'm working on-and-off on a project that comes out to 520KB - it's a port of another game, and it breaks down to 8KB of code and game data, 8KB of VROM, and 504KB of speech samples =) (8-bit speech samples directly outputted, that is, not DMC samples. And I have to pause whatever else I'm doing when playing a speech sample, but thankfully that's about how it worked in the original game anyway.)
Is there a reason that you're using 8-bit (really 7-bit) speech samples instead of, say, 4-bit speech samples? Have you considered some form of data compression like
Big Bird's Hide and Speak uses?
Because everything fits nicely into the cartridge with a little room to spare, and the game itself is quite small. (The numbers I gave were approximate - it's actually 489KB of sample data and ~8KB of code and game data.) Besides, with the recording quality of the speech, 4-bit is not very intelligible - I tried.
Posted: Mon Mar 19, 2007 8:03 pm
by kyuusaku
Thought I'd just throw this out because I've never really understood (the elaborateness of) other assembler's banking/segment schemes:
My scheme creates a virtual 16bit address space for each bank(segment/whatever). I have a file output directive that lets you dump whatever banks to file(s) at the end of assembly. The banks are This allows me to have make generic banks/segments for RAM allocation, ROM or whatever but only output the ROM banks. The first instance of the bank directive dynamically creates it, the second instance just puts the location into the bank's address space which makes AOROM style banking really simple.