This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Obligatory newbie questions

Obligatory newbie questions
by on (#79395)
Hiya. I ran into this site awhile back and really got interested in learning 6502 asm. I've written a little C++ and a little Java and Python, but nothing too big. I've been reading a book here and a tutorial there and now I've given myself a nice big headache. :lol:

This low level stuff is seriously complicated! It seems to be a very different process from writing in a higher language, and I'm having trouble penetrating all the new vocabulary and hardware concepts.

Not at all helping the problem is that different tools seem to handle things radically different. Do I use macros? Labels? What's the syntax? Are there pre-existing libraries I can load and use? Continue ad infinitum. There's almost too much 6502 info out there and I'm having trouble finding a place to start.

I'm thinking the wisest thing to do is use the CC65 assembler, and I have CC65 installed and working. It's convenient that it compiles to any of the 6502 platforms. However, I don't know the peculiarities of that assembler, and I'm not sure where to find them.

Right now, I'm basically stuck using the 6502 simulator writing very simple programs that don't do anything but store and add numbers at some arbitrary address. That's after several days. I figure by now it's time to ask for some help. :P

by on (#79396)
Macros are not unlike inline functions in C++. Labels are like function names or variable names. The syntax for both depends on the assembler: asm6 does it one way and ca65 does it another. You mentioned that you use ca65, the assembler in the cc65 distribution; reading its manual will probably be worth your while.

There aren't a lot of NES-specific reusable libraries, but I've written libraries for controller reading, pseudorandom number generation, multiplication, division, basic trig (enough for aiming in a video game), number printing, and sound playback that I'll let you use. PM me for details.

by on (#79397)
Here's a good Quick Reference Summary of the 6502.
Re: Obligatory newbie questions
by on (#79398)
Bellum wrote:
Right now, I'm basically stuck using the 6502 simulator writing very simple programs that don't do anything but store and add numbers at some arbitrary address. That's after several days. I figure by now it's time to ask for some help. :P

That's the best way to start IMO, and I suggest you continue doing that for a while longer, until you're more comfortable with the 6502. If you try to code something for the NES knowing little more than how to load and store values you might even succeed (after all, all it takes to display something on the NES is a few loads and stores), but because you'll be dealing not only with the CPU but the PPU as well the concepts might end up getting mixed in your head.

I suggest that you gradually increase the complexity of the programs you are writing in the simulator. Even if you have pointless goals like filling a page of RAM with numbers from 1 to 255, you'll end up mastering loops, subroutines, addressing modes, and other essential concepts of 6502 assembly. You can also try making subroutines for useful tasks, like multiplying and dividing, moving blocks of memory, compressing and decompressing RLE... That way the learning will not feel like a waste of time, because you'll be able to use the code later in actual projects.

by on (#79399)
tepples wrote:
Macros are not unlike inline functions in C++. Labels are like function names or variable names. The syntax for both depends on the assembler: asm6 does it one way and ca65 does it another. You mentioned that you use ca65, the assembler in the cc65 distribution; reading its manual will probably be worth your while.

There aren't a lot of NES-specific reusable libraries, but I've written libraries for controller reading, pseudorandom number generation, multiplication, division, basic trig (enough for aiming in a video game), number printing, and sound playback that I'll let you use. PM me for details.


Ah. Much thanks. I saw the manual for compiling C to ASM, but missed that. Thanks. The way things are going, it'll probably be awhile before I'm skilled enough to do anything practical, but I'll keep that stuff in mind! Are there any libraries that make manipulating characters easier? Do I seriously have to encode every character by hand? xD

Also, thanks, Dwedit. That'll come in handy. A big list of three letter acronyms is a bit hard to remember, so I imagine I'll be using it a lot. :P
Re: Obligatory newbie questions
by on (#79400)
tokumaru wrote:
Bellum wrote:
Right now, I'm basically stuck using the 6502 simulator writing very simple programs that don't do anything but store and add numbers at some arbitrary address. That's after several days. I figure by now it's time to ask for some help. :P

That's the best way to start IMO, and I suggest you continue doing that for a while longer, until you're more comfortable with the 6502. If you try to code something for the NES knowing little more than how to load and store values you might even succeed (after all, all it takes to display something on the NES is a few loads and stores), but because you'll be dealing not only with the CPU but the PPU as well the concepts might end up getting mixed in your head.

I suggest that you gradually increase the complexity of the programs you are writing in the simulator. Even if you have pointless goals like filling a page of RAM with numbers from 1 to 255, you'll end up mastering loops, subroutines, addressing modes, and other essential concepts of 6502 assembly. You can also try making subroutines for useful tasks, like multiplying and dividing, moving blocks of memory, compressing and decompressing RLE... That way the learning will not feel like a waste of time, because you'll be able to use the code later in actual projects.


Ah, thanks. I was feeling kind of stupid, as slowly as I was progressing. I definitely intend on continuing to use the simulator, it's really nice for getting quick results.

by on (#79401)
About encoding characters: NES graphics works much like text mode on an old 8-bit microcomputer such as a Commodore 64 or MSX. The background can be thought of as a 32x30-cell grid of character cells. If you draw tiles 32 to 95 in ASCII order, you can have the assembler do your text encoding for you:
Code:
  .byt "Hello World"


As an intermediate step between the simulator and actual NES programming, you can treat the Windows version of FCEUX as a simulator. Make an iNES program that puts its variables in $0000-$07FF, and then examine the memory in the hex editor.

by on (#79405)
Thanks for the tip! Is FCEUX more accurate than Nestopia, I wonder?

I wont be able to test anything I write on a real NES anyway; haven't had one for a looong time. I think it's broken by now, anyway. The machines, alas, are not as indestructible as the SNES. Especially when forced to deal with kids. :P

by on (#79409)
Bellum wrote:
Thanks for the tip! Is FCEUX more accurate than Nestopia, I wonder?

No way! AFAIK, Nestopia and Nintendulator are currently the most accurate emulators. FCEUX is fairly inaccurate actually... It just happens to have the best debugging features. Nintendulator has a few debugging features too.

by on (#79411)
I'd start reading Nerdy Nights (HERE), start at beginners, despite your knowledge. It should help you through and clear up the confusion of instructions, and explain them in more detail. You'll get the hang of it.

Also, those tutorials use NESASM3, it's a windows appliction ran from the command prompt [Batch file] so you'll have to download that and set that up, too. Just post if you have more problems and questions. To find NESASM3, just google it.

by on (#79421)
3gengames wrote:
To find NESASM3, just google it.

The guy said he already has ca65 working, please don't make him downgrade to NESASM! Nerdy Nights is indeed a decent tutorial, but if you can follow it and use another assembler that would be better.

by on (#79427)
tepples wrote:
Code:
  .byt "Hello World"



Can you loop through this somehow to store each character in memory? I think I've got the looping syntax down ok, but I can't store a string into a,x,y (Is there a practical difference between these?) if it's larger than one character.

by on (#79428)
I'll work with you on this example. Let me know each thing you don't understand.
Code:
PPUADDR = $2006  ; PPU video memory address port
PPUDATA = $2007  ; PPU video memory data port

.segment "RODATA"
  .byt "HELLO WORLD",0
  ; the 0 at the end is a NUL terminator, denoting the end of the string

.segment "CODE"
say_hello:

  ; point the video memory address at a nametable location
  ; near the center of the screen
  lda #$21
  sta PPUADDR
  lda #$CA
  sta PPUADDR

  ; loop over the characters in the string, indexed by Y
  ldy #0
loop:
  lda hello_str,y  ; load a character
  beq done  ; if we loaded a NUL terminator, stop writing characters
  sta PPUDATA  ; write the character to video memory
  iny  ; go to the next character in the string
  bne loop
done:
  rts

Now about how to allocate registers, here are rules of thumb:
  • A is best for arithmetic and bitwise logic operations. Because load/store instructions LDA/STA support more addressing modes than LDX/STX and LDY/STY, it's also good for data transfer.
  • X is useful for indexing through an array, and works with a few more instructions than Y, such as the read-modify-write instructions (DEC, INC, ASL, LSR, ROL, ROR).
  • Y is better when a string can start at any of several addresses or is bigger than 256 bytes, as it allows for storing the starting address in a 2-byte variable on zero page and then using the (d),y indirect indexed addressing mode to step through the array.

by on (#79432)
Hmm, not sure what segment does at all, actually. :P

I understand what say_hello does, but I don't understand why you store the numbers $21 and $CA in memory.

I think I'm pretty clear on how the main loop works, wooo!

So, for instance, I could say...

Code:
astring            .byt "This is a string!"
                  LDA astring,0            ;loads astring[0] into a


Except with correct formatting because it somehow messed up my notepad++ example. :/

EDIT:
Oh. I think I may have to use x or y directly for indexing?

by on (#79434)
tepples wrote:
If you draw tiles 32 to 95 in ASCII order, you can have the assembler do your text encoding for you:
Code:
  .byt "Hello World"

I'll just add that CA65 also allows using custom character maps with the .charmap control command.

by on (#79436)
Bellum wrote:
Hmm, not sure what segment does at all, actually. :P

Please see segments in ca65 Users Guide. If you don't understand something in that section, please ask.

Quote:
I understand what say_hello does, but I don't understand why you store the numbers $21 and $CA in memory.

That's something you'll learn more about once you start to learn about the PPU. For now, suffice it to say that VRAM addresses are formed $2000 + $20*y + x, and $21CA represents (x=10, y=14).

Quote:
So, for instance, I could say...

Code:
astring            .byt "This is a string!"
                  LDA astring,0            ;loads astring[0] into a

No, that's a syntax error. You must use comma only for index registers (e.g. astring,x or astring,y); you can use + for constant offsets from the start of an array (e.g. astring+0 or astring+3).


thefox: In which version of ca65 was .charmap introduced?

by on (#79438)
Just to clarify what tepples said, you're actually feeding an address to the PPU one byte at a time via $2006.

Thus

Code:
lda #$21
sta PPUADDR
lda #$08
sta PPUADDR


is saying, 'Hey PPU, I'm setting the VRAM address to $2108.' Then you loop data into that address via $2007.

See the relevant wiki section for reference:

http://wiki.nesdev.com/w/index.php/PPU_registers

The trickier part of learning the NES is not ASM, but how its registers are mapped to its various hardware components.

by on (#79439)
noattack wrote:
The trickier part of learning the NES is not ASM, but how its registers are mapped to its various hardware components.

Which is precisely why I said he should get more comfortable with the CPU before trying to mess with the PPU.

by on (#79440)
tepples wrote:
thefox: In which version of ca65 was .charmap introduced?

Not sure, but it's in the current release version. I remember using it 4 years ago. :)

by on (#79441)
tokumaru wrote:
3gengames wrote:
To find NESASM3, just google it.

The guy said he already has ca65 working, please don't make him downgrade to NESASM!


NESASM is by far the easiest assembler to use out there. I think it's one of the top 2-3. Why not use it? Please give details on why it's such of a downgrade.

by on (#79442)
tokumaru wrote:
noattack wrote:
The trickier part of learning the NES is not ASM, but how its registers are mapped to its various hardware components.

Which is precisely why I said he should get more comfortable with the CPU before trying to mess with the PPU.

tokumaru: The NES PPU and the Apple II Monitor ROM are the only character outputs on 6502-based systems that I'm familiar with.

Bellum: Which 6502 simulator are you using, so that I can tailor my examples to its specific method of character output?

by on (#79444)
http://home.pacbell.net/michal_k/6502.html

Also downloaded the samples last night, which I somehow missed before. I understand how they make use of the terminal. Kinda. Sorta. If I'm looking at the examples for reference. :P

EDIT:
Ok, so is .segment a location in memory designated for specific tasks that the assembler understands?

by on (#79449)
3gengames wrote:
Please give details on why it's such of a downgrade.

There have been several cases of people showing up here with problems that ended up being NESASM's fault. Line length issues, generation of corrupt binaries, absurd lack of error report (it generated bad binaries instead of telling you your source code had errors!), things like that. I believe some of those problems were already fixed, but the fact it uses non-standard syntax for some 6502 instructions/addressing modes is still really annoying. Also, in spite of its name, it was originally made for the PC Engine I believe, and there are a few leftovers from that platform that are not necessarily relevant to the NES, such as the obligatory bank size of 8KB.

by on (#79450)
In case you (Bellum) want to stick with ca65 and still read and use the Nerdy Nights tutorials which I highly recommend, I'd say check this out:
https://bitbucket.org/ddribin/nerdy-nights/src

It might not be perfect, but it's a good way to see how ca65 can be used while you're learning how to actually program for the NES.
It helped me a lot and I'm very happy now that I chose to switch to ca65 after using nesasm for a short time.
I agree, it can be easy to start with, but there is nothing really "hard" about ca65 if you ask me.
Today, I would say it is a lot more comvenient to use than nesasm, but that is a personal preference.

by on (#79469)
Thanks, Grumskiz. I've read the first few NN tutorials so far and they seem helpful. :)

Can I get you guys to look at this example for looping through strings?
Code:
      *= $1000

stLength   = $2000      ;Pointer to $2000 for length of stStore string argument
initString = $F000      ;Pointer to beginning of Strings
curString  = $F000      ;Pointer to last character in array
            ;In this case, the array is dynamic with absolutely no regard to what it may overwrite.
            ;LIVE ON THE WILD SIDE




stStore      .MACRO st      ;Macro stStore - Store a string in memory - Param: string
            LDY #0            ;y = 0
stLoop      LDA %st, y      ;a = st[y]
            BEQ stReturn   ;if st[y] == null: return
            INY               ;y++
            STA curString   ;put a at curString
curString = curString + $08   ;increment curString 1 byte
            BNE stLoop      ;goto stLoop
stReturn
      .ENDM

      
      
main
stringOne   .BYTE "This is the first string.",0
stringTwo   .BYTE "This is the second string.",0
stringThree   .BYTE "This is the last string.",0

            stStore stringOne
            stStore stringTwo
            stStore stringThree
            .END


It wont let me pass stringOne to stStore. Is this just a syntax error or something else?

EDIT: yeeegh, the formatting for the macro was way off. What gives, code tag?
EDIT2: Hm. Reformatting it in notepad++ seems to have helped a little, this time.

by on (#79470)
You lost me at the first 3 equates, not sure what $2000, $F000, and $F000 are doing here. Are those just for the 6502 simulator?

by on (#79471)
Yeah, those are just some meaningless addresses just so I have someplace to put things.

EDIT:
Actually, the first one isn't even used anymore. :P

by on (#79744)
Coming back to this (I've got a few other projects sucking up big chunks of my time), I think I've come up with something very slightly less terrible. :P

Labels are constants? (like #define?) It makes sense, but for some reason it hadn't occurred to me before. I keep wanting to translate this into something a little higher level and more manageable.

My rewrite is very similar, but it assembles past the first call to stStore. The problem is, in the second call, it apparently tries to re-define stLoop, which doesn't work. Is there a better way to go about loops in macros, or should I be using jumping to a "subroutine" instead?
Code:
      *=1000
      
stLength = $2000   ;Store the length of the string for indexing
initString = $2500   ;The beginning of the string


stStore      .MACRO st       ;stStore - store a string in memory
            LDX stLength      ;put the length of string array in x
            LDY #0            ;init y as index of st
stLoop      LDA st,y         ;store st[y] in a
            STA initString,x   ;Store a in initString+x
            INX
            INY               ;Increment Y and X
            BNE stLoop
            STX stLength      ;Store new array size at stLength
            .ENDM
      
      
      

stringA      .BYTE "This is a string."
stringB      .BYTE "This is another string."

            stStore stringA
            stStore stringB
            .END

by on (#79749)
What you will need to do is use a local label in your macro. Usually this is done by beginning the label with at (@), but some assemblers use a period (.). Otherwise it is global.

by on (#79751)
Thanks, that assembled. Now to see if it actually does anything remotely similar to what I think it's supposed to do. :lol:

by on (#79753)
Bellum wrote:
or should I be using jumping to a "subroutine" instead?

In my opinion it makes more sense to have a subroutine that writes strings rather than a macro, but both should work. The code you posted takes around 18 bytes, which means that for each string you print you are wasting 18 bytes (in the case of a small string like "hello word" the printing code is larger than the string itself!). As a subroutine the code would be a bit larger (because you'd have to process the arguments), but the same code would be used for ALL strings.

by on (#79754)
Woo, it worked! Sort of. The second time it looped infinitely for some reason and started storing garbage, so I'll have to work that out. :)

tokumaru wrote:
Bellum wrote:
or should I be using jumping to a "subroutine" instead?

In my opinion it makes more sense to have a subroutine that writes strings rather than a macro, but both should work. The code you posted takes around 18 bytes, which means that for each string you print you are wasting 18 bytes (in the case of a small string like "hello word" the printing code is larger than the string itself!). As a subroutine the code would be a bit larger (because you'd have to process the arguments), but the same code would be used for ALL strings.


That makes a lot of sense. What sort of thing would macros be better used for?

by on (#79757)
Bellum wrote:
What sort of thing would macros be better used for?

Hum... the most obvious I can think of are 16-bit math operations, because we need them often and they are so small that a subroutine would offer little (if any) gain of ROM space and would be much slower. Another thing I can think of are mapper writes, if they are short enough.

I don't see much sense in having large macros, unless they are meant to be used across different programs. If a piece of code is reasonably large and you have to use it in several places of the same program, it makes more sense to use a subroutine.

by on (#79758)
Quote:
a subroutine would offer little (if any) gain of ROM space and would be much slower.


Are JMP statements particularly slow?

by on (#79759)
Macros are needed when speed is imporant. Say, you have code like this:

Code:
loop
 jsr a short subroutine
 ..
 jsr a short subroutine again
 ..

 go loop


If there is not much code in the loop and the subroutine, you can spend much time on jsr/rts. If you replace the subroutine with a macro, you'll save this time. It could be important for VRAM update code, when you have to fit many things into ~2200 CPU clocks, for example. One subroutine call is 12 extra clocks, one macro usage is 0 extra clocks, in exchange for larger code size.

by on (#79760)
Bellum wrote:
Are JMP statements particularly slow?


Go here: http://www.obelisk.demon.co.uk/6502/reference.html

It has the cycle counts for every instruction. jmp is fairly fast at 3 cycles. The fastest instructions take 2 cycles. The slowest instructions take 7.

Both jsr and rts are 6 cycles which means every normal subroutine call* will take at least 12 cycles.

For reference, I believe an NTSC NES' frame is 29830 CPU cycles long.

*i.e. one that doesn't pull the rts location from the stack and return some different way or something crazy.

by on (#79762)
Of course, a macro can also be handy to set things up for the JSR, with less typing.
Code:
.macro writestring addr
  lda #<addr
  sta vram_addr_lo
  lda #>addr
  sta vram_addr_hi
  jsr string
.endmacro


And I use one like this all the time:
Code:
.macro pointer addr1_zp,addr2
  lda #<addr2
  sta addr1_zp
  lda #>addr2
  sta addr1_zp+1
.endmacro

Using macros inside of macros can be fun too. :)
Code:
.macro writestring addr
  pointer vram_addr_lo,addr
  jsr string
.endmacro


more examples of what I thought macros were useful for, in general use:
http://www.parodius.com/~memblers/nes/macros.s
(the controller one is probably my favorite for making my programs readable)

by on (#79782)
Bellum wrote:
Are JMP statements particularly slow?

JMP (3 cycles) isn't slow, but JSR (6 cycles) is. JMP is not used for subroutines, because it doesn't save the return address. JSR saves the return address, so that you can RTS (also takes 6 cycles) when the subroutine is done. Since it takes 12 cycles to get in and out of a subroutine, it doesn't make sense to use one for very small and quick tasks.

by on (#79793)
Thanks, all. That clears up a lot. :)