So recently I got bored with the good ol' Sega 8 bit systems, and decided to try out the NES.
When first walking in, there was an unfortunately debatable topic: which assembler to use.
So I not only created a hello world for one assembler, I made one for them all. By 'all' I mean asm6, ca65, nesasm and wla-dx.
The download link is below. It gives you a little taste of each one (except for possibly ca65, because for all I know that thing could have every function in the world I could have used).
Got feedback on the code itself? I'd love it.
I'm not sure if this is even a good section for the subject, but here's my thoughts.
asm6
Easy to use. Simple and compatible. I've only ever seen two complaints with it, one of which was somewhat outlandish and the other one I'm not concerned with. Overall my favourite.
ca65
Nice and all, but a bit heavy for my tastes. What with the separate cfg file being required for all your projects (unless portability isn't a concern). And I couldn't figure out how to have ascii text, but eh, that's not really the main subject. Certainly a second fave.
wla-dx
My god, it's so underrated. The documentation may not be so great, but once you figure it out it can do plenty. Its main downside is its inability to decide on whether to put in addresses as 8 or 16 bit (you have to specify with .b or .w). I could have some bias since this is the assembler of choice in the z80 community, so.
nesasm
Eh. Not into it. Directives need to be indented, making me wonder why do they even start with a period (which I would think lets the assembler detect if it's a directive). No anonymous labels. Somewhat picky syntax. I'd say the pre-generated headers aren't worth having to live with the rest of the BS.
Anyway, what are your thoughts on these? Tell meh.
In CA65 you can put string literals in a .byte directive. You can also interleave number literals with it.
Code:
.byte "MY NAME IS",0
.byte "A",13,"B",$42
If you want to get more advanced, it has directives to remap characters too, so that the encoding for string literals can map to something other than ASCII.
Also, you should wait for two vblanks to pass before writing to the PPU registers. Move that second jsr vblank_cycle before your stx $2000. (Won't cause a problem on emulators, or with powerpak, but if you built a cartidge for it you might see problems if you're using the PPU before it's warmed up.)
Secondly, you enable sprite rendering but you've never initialized the OAM data for displaying sprites. You're likely to see weird random sprites on the screen if you try it on real hardware. Emulators will probably just show a pile of sprite tile $00 in the top left corner.
I don't know about asm6, but I wholeheartedly recommend ca65 over wla-dx (I think you all know my opinion on that one...) and nesasm. I remember when I first wanted to do SNES programming, I found that I had a hard time finding anything, and I kept hearing about something called "nerdy nights" which was basically a tutorial for NES programming. I figured that maybe some of the knowledge that I'd acquire there would help me on the SNES, (luckily, it did a bit) and I remember them using nesasm as the assembler. I remember hating every second of it. It was only until I found bazz's tutorials on superfamicom.org and this website that I had any clue as to what I was doing. (I really don't think nerdy nights was the best tutorial either.)
I use wla-dx for master system programming so it made sense to also use it for nes.
I just had a big fiasco where it decided to use 8 bit addressing instead of 16 bit and it screwed me over, so I vowed never to use it again. I thought I was going crazy because I kept looking over the code I made and the SNES kept crashing, but it wasn't actually my fault.
Espozo wrote:
I just had a big fiasco where it decided to use 8 bit addressing instead of 16 bit and it screwed me over, so I vowed never to use it again. I thought I was going crazy because I kept looking over the code I made and the SNES kept crashing, but it wasn't actually my fault.
Like I said, that can be fixed by adding a ".w" but it looks ugly and shouldn't be necessary.
Prime wrote:
I use wla-dx for master system programming so it made sense to also use it for nes.
Yup, that's my exact situation. It just doesn't seem as good for the 6502.
rainwarrior, thanks for the suggestions. Fix'd it up. What are the directives to remap characters though?
EDIT: Added new version to this post on accident.
nicklausw wrote:
Like I said, that can be fixed by adding a ".w" but it looks ugly and shouldn't be necessary.
Nobody told me that... Anyway, the only reason you have to do that is that it's too stupid to realize if you want 8 bit or 16 bit addressing. It's not like any of the other assemblers have that problem.
Espozo wrote:
nicklausw wrote:
Like I said, that can be fixed by adding a ".w" but it looks ugly and shouldn't be necessary.
Nobody told me that...
Wait, seriously? People around here don't seem to have ANY idea how to use WLA. Perhaps it needs some better examples?
nicklausw wrote:
What are the directives to remap characters though?
The relevant control command is
.charmapYou can find all this stuff in the documentation:
http://www.cc65.org/doc/ca65.html(You might look at the entry for
.byte to see an example for using strings, too.)
rainwarrior wrote:
nicklausw wrote:
What are the directives to remap characters though?
The relevant control command is
.charmapYou can find all this stuff in the documentation:
http://www.cc65.org/doc/ca65.html(You might look at the entry for
.byte to see an example for using strings, too.)
I tried to set A-Z to their specific characters, and found that the only good way to do so is through a loop. Is this really the best way?
Code:
charmap1 .set $41
charmap2 .set $b
.repeat 26
.charmap charmap1,charmap2
charmap1 .set charmap1+1
charmap2 .set charmap2+1
.endrep
.charmap $20,$00 ; space
More up-to-date documentation is here:
http://cc65.github.io/doc/ca65.htmlMost recent win32 binary (If you don't want to build your own):
http://sourceforge.net/projects/cc65/For charsets, maybe take a look at this post by thefox:
viewtopic.php?p=129836#p129836
Thanks Movax, I was able to set that up accordingly. The multi-line thing didn't work though...
Anyway 0.3 is up (updates so fast I'm like Ville Helin XD). Fixed the NESASM example (forgot to remove the part enabling sprites,) added the charmap to the ca65 example and changed how the print screen function works. At first I had the idea that this lets you print to infinity, but that's not the case.
So far nesasm is the only assembler that I couldn't set up strings with. Any ideas?
Just one more thing about the startup code. I think what you've got probably works, but for good practice you should add one extra read of $2002 before your 2 vblank loops, or alternatively just do 3 vblank loops.
The reasons are outlined here:
http://forums.nesdev.com/viewtopic.php?f=2&t=3958I'd forgotten about this because it's been a long time since I wrote new startup code, but basically a lot of commercial NES games don't do this, and the popular Nerdy Nights tutorial doesn't either, but if you start writing to the PPU too soon after that second vblank loop, it can fail.
Because you initialize RAM and stuff afterwards, the cycles spent doing that should actually cover the problematic window of time, but I'm just letting you know it's a probably a good idea to clear $2002 anyway. (Example:
http://wiki.nesdev.com/w/index.php/Init_code)
rainwarrior wrote:
Just one more thing about the startup code. I think what you've got probably works, but for good practice you should add one extra read of $2002 before your 2 vblank loops, or alternatively just do 3 vblank loops.
The reasons are outlined here:
http://forums.nesdev.com/viewtopic.php?f=2&t=3958I'd forgotten about this because it's been a long time since I wrote new startup code, but basically a lot of commercial NES games don't do this, and the popular Nerdy Nights tutorial doesn't either, but if you start writing to the PPU too soon after that second vblank loop, it can fail.
Because you initialize RAM and stuff afterwards, the cycles spent doing that should actually cover the problematic window of time, but I'm just letting you know it's a probably a good idea to clear $2002 anyway. (Example:
http://wiki.nesdev.com/w/index.php/Init_code)
Alright, made that one last change. Didn't increment the version because it's...not really all that necessary.
Espozo: I opened an issue on GitHub about how you have to use .w for all 16-bit addresses, and apparently Ville is looking into it. How long has this problem been around, anyway...?
nicklausw wrote:
nesasm
Eh. Not into it. Directives need to be indented, making me wonder why do they even start with a period (which I would think lets the assembler detect if it's a directive). No anonymous labels. Somewhat picky syntax. I'd say the pre-generated headers aren't worth having to live with the rest of the BS.
Directives in NESASM do not require the period; it is optional. I like the "picky" syntax of NESASM, and I have added support for anonymous labels (but this is one of the few of the features I have added that I do not use myself (I have added various other features too, but which I seem to be the only one to use them); I added anonymous labels for benefit of other people).
Another limit of NESASM though is that the ROM data is limited to 1MB. (My own version though allows larger ROM data but if you do so, the extra data must be external and you have to use a custom output routine.)
Throwing in my two cents: charmap in ca65 is stupid and badly-implemented. Compare its ridiculousness to how
x816 implemented it (see
.ASCTABLE and
.ASC).
koitsu wrote:
Throwing in my two cents: charmap in ca65 is stupid and badly-implemented. Compare its ridiculousness to how
x816 implemented it (see
.ASCTABLE and
.ASC).
That's exactly how WLA goes about it, something that I've always loved.
nicklausw wrote:
Thanks Movax, I was able to set that up accordingly. The multi-line thing didn't work though...
Line continuation in ca65 needs to enabled with the ".linecont +" control command. I guess I forgot to mention that in the original post. BTW you don't need the .concat in there if it's a single line.
nicklausw wrote:
I tried to set A-Z to their specific characters, and found that the only good way to do so is through a loop. Is this really the best way?
Code:
charmap1 .set $41
charmap2 .set $b
.repeat 26
.charmap charmap1,charmap2
charmap1 .set charmap1+1
charmap2 .set charmap2+1
.endrep
.charmap $20,$00 ; space
This simplifies down to:
Code:
.repeat 26, i
.charmap $41+i, $b+i
.endrepeat
.charmap $20,$00 ; space
koitsu wrote:
Throwing in my two cents: charmap in ca65 is stupid and badly-implemented. Compare its ridiculousness to how
x816 implemented it (see
.ASCTABLE and
.ASC).
I agree that it's suboptimal. However most of the shortcomings can be worked around with macros (you could implement quite easily a macro that allows you to map ranges). The biggest problem I feel is the lack of ability to push/pop/reset the character sets, which can be really problematic e.g. for macros/files that expect the character set to be set up in a certain way. This, too, can be somewhat worked around with macro trickery but it's not pleasant.
Assembler-wise, I think I'll stick with asm6. ca65 and wla-dx are tied for a second, and nesasm takes the spot for last.
With nesasm, it's nice that someone made a better version on their own, but I'm not into the idea of having to say "make sure you use the version from THIS PERSON, not the STANDARD ONE!"
nicklausw wrote:
With nesasm, it's nice that someone made a better version on their own, but I'm not into the idea of having to say "make sure you use the version from THIS PERSON, not the STANDARD ONE!"
That's a big problem with assemblers in general. There's no standard to conform to, just a bunch of conventions implementers optionally use.
As for whether .charmap is fussy to use, I don't really see the problem for a small table generating code that you only have to write once, and that you could easily shuffle off into a header file that you .include where needed. How often are you changing character sets that it really feels like an inconvenience?
rainwarrior wrote:
(Won't cause a problem on emulators, or with powerpak, but if you built a cartidge for it you might see problems if you're using the PPU before it's warmed up.)
Not waiting
will cause a problem on PowerPak when you press the Reset button on the Control Deck after the game starts. So if you test on PowerPak, don't forget to try a reset every once in a while to see if your program still works.
I don't want to be nitpicking.. but I have some suggestions.
From the asm6 source
Code:
-: lda message, x
cmp $ff ; <----- don't you mean #$ ?
beq load_attr
sta PPU_VRAM_IO
inx
jmp -
(..)
message:
.db "MY FIRST NES ROM"-$36,$ff
The program works as intended because memory from the end of your code is all zeroes and returns true from your
Code:
cmp $ff
It's a simple thing to mess up.
Personally I really love to
Code:
pad $fffa,$ea
so I know for certain things don't mess up anything.
Code:
.org $fffa
.dw 0, Reset, 0
Not fond of this..
I would atleast put some dummy labels there just in case.
...aw crap, it seems I did screw up that loop. I'll get on my pc and fix that.
I don't see how padding AND filling with $ea is better than just orging (which is padding) and taking the default filler of 0.
Edit: I see watcha mean now, but still don't think it's necessary.
And I guess I can add a dummy label to an rti.
Alrighty then, fixed some of that up and tried cleaning up a few things. Note that other than asm6, I can't guarantee that any of these will work (my previous set-up went with my hard drive) so yeah, just go with my blessing. XD
EDIT: Broke ca65 on accident.
I don't really understand the point of filling with $EA. If you end up running code there it'll just NOP-slide all the way to the vectors, and then try to execute those as code, then if it's not dead yet, wrap around to the ZP and continue... what advantage is there to that?
I fill with BRK (which happens to be 0 as well), which means the IRQ routine gets executed if I ever accidentally start executing fill. Since I'm not using the IRQ for anything else, I put an error diagnostic screen in my IRQ handler. Helps me debug if I ever make a mistake (usually to do with a missing stack push/pop or something).
A lot of original NES games seem to fill with $FF. I don't know if there's any software advantage to this, but an "empty" EPROM has all bits set, so maybe this was an advantage during testing.
rainwarrior wrote:
I don't really understand the point of filling with $EA. If you end up running code there it'll just NOP-slide all the way to the vectors, and then try to execute those as code, then if it's not dead yet, wrap around to the ZP and continue... what advantage is there to that?
Technically there is none and I do some what agree with you on this. I totally see your point. If you also happen to have an irq/brk handler it would be really nice to use when ever you go out of boundry. However, as often as a nop slide can occur and wrap around, a brk might also end up - such as in this case - on zp and be equally confusing for someone not fully understanding how things work, which I assume a "Hello World" program is ment for. Setting all vectors, albeit to dummy values/labels, seem like a good idea to me atleast. Maybe I am looking too deep into something really simple here
Yes, BRK is obviously not much use if you don't have something in your IRQ handler to catch it.
If you put a jmp ($FFFC) instruction just before your vectors (at $FFF7), an $EA fill NOP-slide will cause a soft-reset, which might be nicer than it going rogue? I actually did this for a while until I thought of using BRK instead.
rainwarrior wrote:
Yes, BRK is obviously not much use if you don't have something in your IRQ handler to catch it.
If you put a jmp ($FFFC) instruction just before your vectors (at $FFF7), an $EA fill NOP-slide will cause a soft-reset, which might be nicer than it going rogue? I actually did this for a while until I thought of using BRK instead.
Is that why, iirc in the standard nes.cfg file, the segment VECTORS starts 3 bytes before $fffa?
No. The
nes.cfg that comes with CC65 does not specify a fill value, and the "VECTORS" segment starts at $FFF6. The contents are defined in
crt0.s, which show that it's just 3 more pointers that I guess are used internally by the CC65 libraries. They all point to the same empty IRQ handler (just an RTI instruction).
I don't recommend using that, though. It's better to use libraries directly oriented toward gaming, like
the one Shiru made, or perhaps look at the source code for
this music project of mine for another example.
There were a number of other that-era microcontrollers that used a 6502 core with extra vectors, such as Mitsubishi's M50734SP (which is in an old dot matrix printer I had).
Why the original NES build scripts thought it had those extra vectors is a good question, and not one explained in cc65's commit history...
This is probably a really dumb question, but how did you rewrite assembly for different assemblers?
Most tutorials are written for cc65, but I'd like to use the example code with asm6
Learn how their syntaxes are different and change the code. It's usually very cosmetic changes, like using [] instead of (), or a slightly different way to creating anonymous labels. Some of it you could do with a quick regex search and replace. Sometimes you can't, especailly if it's using a feature specific to the assembler (e.g. ca65's extensive macro system). A program that could automatically convert, reliably, would be as complex as an assembler itself.
(Actually one way to convert is just to assemble it, and then use a disassembler on the result.)
If you're going through a tutorial, you might as well just read the sample code and convert it manually as you're typing it in to your own code, instead of just trying to copy-paste-regex it into shape. That way you'll at least get used to the language a little bit?
...or just learn ca65 and join the dark side! JOIN USSSSSSSSSSSSS... HISSSSSSS...
rainwarrior wrote:
A program that could automatically convert, reliably, would be as complex as an assembler itself.
I wrote such a program as part of the Action 53 project It acts a filter converting a subset of NESASM/UMK syntax to cc65 syntax. I used it to reassemble LAN Master and Super PakPak. Want me to dig it up?
This is probably not of interest to the original poster, but I'll mention it anyways:
One way to convert stuff from ca65 to other assemblers is to generate an o65 relocatable binary file from the source, and then run that through the co65 tool to get an expanded source file which has all of the code (including macros) expanded into a set of .byte and .word commands that are very easy to adapt to other assemblers in an automated way. The nice thing is that this kind of converted source code is relocatable. This is mostly useful if you want to distribute a relocatable, statically linkable library for multiple different assemblers (I used it in my MUSE sound library). It doesn't help if you want to actually be able to modify the source code after translation, as it's practically indecipherable.
BTW: I remember Shiru had some sort of automated conversion tool included with his FamiTone2 library. I think the source format was NESASM in that one, and it had some limitations, but you might want to check it out anyways.
owin wrote:
This is probably a really dumb question, but how did you rewrite assembly for different assemblers?
Most tutorials are written for cc65, but I'd like to use the example code with asm6
I don't really know the best way to explain ca65 and asm6's differences to you. Study the two respective hello worlds, I guess?
rainwarrior wrote:
...or just learn ca65 and join the dark side! JOIN USSSSSSSSSSSSS... HISSSSSSS...
I'm starting to think that this would be a good idea, to just get beginners right on the big stuff. The only problem is that ca65 in specific kind of has a weird learning curve to it. Maybe giving asm6 to beginners and ca65 to people with previous experience would work? XD