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

Proper way of waiting for vblank without NMI

Proper way of waiting for vblank without NMI
by on (#233745)
As an offshoot of my Z-Machine idea, I'm writing terminal routines for the NES. From what I gather, I have to wait until VBLANK, turn off the display, update the nametable, and turn the PPU back on. I've noticed that most are using a NMI to update the screen, however, with text, I have the luxury of updating asynchronously.

They way the terminal works is that when a character needs to be printed, it's pushed to the stack and the output routine is called.

Here is how I will get it to work in theory....
In my output routine I'll loop and check bit 7 of PPU status. Once that goes high, I know I'm in vblank and I have to turn off the background and sprites using PPUMASK (bits 3 and 4)
Then I pop my character off the stack, and use PPUDATA to update the nametable and it will automatically advance to the next position
Then I turn on the background and sprites again and it should be good to go for the next frame, right?
Re: Proper way of waiting for vblank without NMI
by on (#233748)
halkun wrote:
I have to wait until VBLANK, turn off the display, update the nametable, and turn the PPU back on.
No. Vertical blanking is good enough. Access to the nametables (and everything else via $2007) is achievable during vertical or forced blanking.

If you don't use forced blanking, you have to be careful to never exceed the amount of timing available to you during vertical blanking.

If you do use forced blanking, you need to be careful about when you re-enable rendering to avoid incorrect scroll.
Re: Proper way of waiting for vblank without NMI
by on (#233749)
so I don't need to turn anything off, just wait for vblank and then push right into the nametable?

Code:
.proc nesout
   ;check to see if NES is in vblank
   lda PPUSTATUS
   and %1000000
   beq nesout
   ;pop the character off the stack
   pla
   ;write it to vram
   sta PPUDATA
   rts
.endproc

Re: Proper way of waiting for vblank without NMI
by on (#233751)
One thing with polling $2002 for vblank is if the write falls directly on the start of vblank it "cancels" it and returns 0, so you end up waiting an extra frame. So it's unfortunately not 100% reliable, though it's perfectly usable as long as it's acceptable to occasionally wait an extra frame.

That's kind of an unavoidable random behaviour, though you can reduce the impact of it if you make the polling loop longer. The more cycles you have in your polling loop, the less chance you have that the one cycle that reads $2002 will fall on the exact start of vblank and do a cancel.
Re: Proper way of waiting for vblank without NMI
by on (#233752)
halkun wrote:
so I don't need to turn anything off, just wait for vblank and then push right into the nametable? [code example]
Yes, but that code will end up only sending one byte every vblank. You really do want some kind of queuing system.
Re: Proper way of waiting for vblank without NMI
by on (#233754)
halkun wrote:
In my output routine I'll loop and check bit 7 of PPU status. Once that goes high, I know I'm in vblank and I have to turn off the background and sprites using PPUMASK (bits 3 and 4)
Then I pop my character off the stack, and use PPUDATA to update the nametable and it will automatically advance to the next position
Then I turn on the background and sprites again and it should be good to go for the next frame, right?

Any reason why you can't flush the buffer this way but in the NMI handler, instead of polling $2002? Like it's been pointed out, reading $2002 in a loop will cause some vblanks to be missed, which will affect music playback, animations, raster effects, and anything else that's timed off of vblank.

A very sensible approach would be to buffer characters whenever they're typed, and in the NMI handler you flush the buffer to VRAM whenever there's anything in it, while also updating the screen position. One thing you'd have to be careful about though is that there are two steps in buffering a character: 1 - writing the character to the buffer; 2 - updating the buffer's length. If the NMI happens to fire between these two steps, you might have problems. If the the length is updated first, and the NMI fires before the character is written to the buffer, a garbage character will be written to the screen. If the character is buffered first, and the NMI fires before the length is updated, two things might happen: if the length reads back as 0, the character won't be output this time but will be on the next vblank, which's fine; however, if there was already data in the buffer, that data will be output while the new character won't, and when the NMI returns, the new length will be set, and that will cause the characters that have already been output to be output again on the next vblank, along with the newest character and whatever may get buffered this frame.

A very simple way to solve this problem would be to zero out the length of the buffer before updating it (so that if the NMI fires it doesn't try to output anything), write the character to the buffer, and then set the new length. Most NES games have to do something along these lines, because an NMI might fire while the various buffers (background, sprites, etc.) are still being filled, and we definitely don't want partial updates to be sent to the PPU, we want to update only when the whole data is ready.
Re: Proper way of waiting for vblank without NMI
by on (#233769)
halkun wrote:
so I don't need to turn anything off, just wait for vblank and then push right into the nametable?
Code:
.proc nesout
   ;check to see if NES is in vblank
   lda PPUSTATUS
   and %1000000
   beq nesout
   ;pop the character off the stack
   pla
   ;write it to vram
   sta PPUDATA
   rts
.endproc

Somewhat like so, but I think there are 4 bugs in there
1. Your assembler will probably expect "#nn" for immediates to distinguish from "nn" address
2. The immediate used is testing bit6, not bit7
3. Pla and rts both read from stack, but not in the order that you seem to expect
4. You need to set the ppu address before writing ppu data, especially for the 1st byte after begin of vblank

The other issue is that vblank flag in bit gets cleared after reading ppustatus, that's why the function could output only one char per frame.
I would probably queue the outgoing data in a ring buffer, and then forward that data to vram in nmi handler, the advantage is that your main program won't need to pause until vblank.
Best way for ring buffers should be using separate write- and read-pointers. Write pointer getting incremented by the main program upon char output (unless the pointer would become equal, which would that ringbuffer is full). And read pointer getting incremented by the nmi handler when forwarding the chars to vram (until/unless the pointers are same, which would mean that the ringbuffer is empty).
Re: Proper way of waiting for vblank without NMI
by on (#233843)
Yea, I'm going to implement the NMI routine. Good catch on my missing # though :)
So I should set the nametable address every time? does it get corrupted when the PPC renders a frame? Also, I can't seem to count to 8 for some reason :P
Oh snap! JSR pushs on the stack too! I spaced that. I'll alter the code so that it's saved to the zero page instead

I'm porting something cool, but may look useless useless at first, but has lots of potential
I don't want to say until I have something that runs, but so far looks promising.

Also, is there a debugger that can take symbols from an ca65 object file? searching the ROM is kind of a pain.

==EDIT==
the NMI works in NO$NES, but not in FCEUX. Emulators can be tricky I guess.
I borrowed code from the SNROM template for PPU. Foes this seem correct?

Code:
.proc initnes
                  ;Stack was initialized before this call
  sei
  cld            ; disable decimal mode to help generic 6502 debuggers
 
  ; Acknowledge and disable interrupt sources during bootup
  ldx #0
  stx PPUCTRL    ; disable vblank NMI
  stx PPUMASK    ; disable rendering (and rendering-triggered mapper IRQ)
  lda #$40
  sta $4017      ; disable frame IRQ
  stx $4010      ; disable DPCM IRQ
  bit PPUSTATUS  ; ack vblank NMI
  bit $4015      ; ack DPCM IRQ

  ; Wait for the PPU to warm up (part 1 of 2)
vwait1:
  bit PPUSTATUS
  bpl vwait1

  ; While waiting for the PPU to finish warming up, we have about
  ; 29000 cycles to burn without touching the PPU.  So we have time
  ; to initialize some of RAM to known values.
  ; Ordinarily the "new game" initializes everything that the game
  ; itself needs, so we'll just do zero page and shadow OAM.
  ldy #$00
  lda #$F0
  ldx #$00
clear_zp:
  sty $00,x
  inx
  bne clear_zp
  ; the most basic sound engine possible
  lda #$0F
  sta $4015

  ; Wait for the PPU to warm up (part 2 of 2)
vwait2:
  bit PPUSTATUS
  bpl vwait2

  ; Initalize the screen
  jsr initscreen

  ; Turn screen on
  lda #0
  sta PPUSCROLL
  sta PPUSCROLL
  lda #VBLANK_NMI|BG_1000
  sta PPUCTRL
  lda #BG_ON
  sta PPUMASK

  rts

.endproc


.proc cls
  lda #VBLANK_NMI
  sta PPUCTRL
  lda #$20
  ldx #$00
  stx PPUMASK
  sta PPUADDR
  stx PPUADDR
  ldx #240
:
  sta PPUDATA
  sta PPUDATA
  sta PPUDATA
  sta PPUDATA
  dex
  bne :-
  ldx #64
  lda #0
:
  sta PPUDATA
  dex
  bne :-
 
  lda #$20
  sta PPUADDR
  lda #$62
  sta PPUADDR
 
  rts
.endproc

.proc initscreen
  jsr cls

  ; set monochrome palette
  lda #$3F
  sta PPUADDR
  lda #$00
  sta PPUADDR
  ldx #8
:
  lda #$02    <-- Palette
  sta PPUDATA
  lda #$30
  sta PPUDATA
  sta PPUDATA
  sta PPUDATA
  dex
  bne :-
  rts
.endproc


.proc nmi
   pha                  ;we destroy accumulator so save it first
   lda #0              ;locking the screen to upper left
   sta PPUSCROLL
   sta PPUSCROLL
   lda #$20
   sta PPUADDR
   lda #$62
   sta PPUADDR
   lda #$40             ;'@'
   sta PPUDATA
   pla
   rti
.endproc



FECUX doen't seem to want to reset the scroll position
Image
Re: Proper way of waiting for vblank without NMI
by on (#233852)
Mesen can use a .dbg file generated using the --dbgfile option.
Re: Proper way of waiting for vblank without NMI
by on (#233857)
I'm trying to make a debug file but it's not allowing me to...
Here is the command I'm using
cl65 -t none -C nes.ini -m map.txt -vm -o rom.nes main.s header.s

When I add a -g it says it doesn't know what to do with rom.nes

==EDIT==
Fixed scrolling problem Now works in FCEUX, N$NES, and Mensen :)
Re: Proper way of waiting for vblank without NMI
by on (#233859)
You didn't provide the actual command you used, just that you "added -g somewhere". Argument order matters, i.e. cl65 ... -g -o rom.nes ... is not the same thing as cl65 ... -o -g rom.nes .... I have no idea what you actually did so I can't tell you what's likely wrong. :-) --debug-info is also the same as -g if you prefer long/descriptive arguments.

Takeaway: Never ever append or insert flags/switches into a command-line if you aren't sure where they should go. Always refer to the usage documentation for what argument syntax is on any command-line program you use (on Windows, *IX systems, anything); -h or --help (a lot of things) or -? (some unique *IX programs) or /? (DOS/Windows) often display usage, but it varies per program; sometimes running a program with no arguments will show usage, but other programs might just sit there doing nothing (attempting to read from standard input).

In this case, this is the usage documentation, and this too which goes over nuances of argument order in cl65 / how the argument parser works (for the latter doc: read, don't skim).
Re: Proper way of waiting for vblank without NMI
by on (#233860)
halkun wrote:
So I should set the nametable address every time? does it get corrupted when the PPC renders a frame?

The PPU's address register is used internally for rendering, so yeah, you need to set the output address every time, as well as reset the scroll before rendering begins, every frame.

Quote:
Oh snap! JSR pushs on the stack too! I spaced that. I'll alter the code so that it's saved to the zero page instead

You can buffer things on the stack for later consumption, but since the CPU is always using the stack when calling subroutines or interrupt handlers, this normally requires you to manipulate the stack pointer, something that must be done with caution. Placing your buffers elsewhere is a better call if you don't absolutely need the speed/convenience of PLA/STA.

Quote:
the NMI works in NO$NES, but not in FCEUX. Emulators can be tricky I guess.

Emulators are not tricky for common stuff, but they can be when you're trying more advanced stuff that relies on perfect timing. If you're trying to use the console in a "standard" way, with no funny tricks, it should work all across the board without any trouble. If that's not the case, there's probably something wrong with the way you're structuring your code.

Quote:
FECUX doen't seem to want to reset the scroll position

Setting the scroll must be the last thing you do before trying starts. Like I said before, the PPU address registers is used by the programmer to update VRAM, but also by the PPU itself in order to render the picture.

In your code, you're setting the scroll (which prepares the PPU address register for rendering), and then clobbering it by using $2006 and $2007 to write to VRAM.
Re: Proper way of waiting for vblank without NMI
by on (#233861)
Yup: I fixed it up after I realized the PPU messes with it's own registers

I got cl65 dump a debug file, but sadly, Mesen doesn't like it very much

here's my final command line
cl65 -t none -C nes.ini -m map.txt -vm -o rom.nes -g main.s header.s -d -Ln rom.dbg
it generated a file and it's contents looks like this..
Code:
al 000300 .__BSS_LOAD__
al 000300 .__BSS_RUN__
al 000000 .__BSS_SIZE__
al 00E8D3 .nesin
al 00E8D2 .nesout
al 00E8AF .initscreen
al 00E877 .cls
al 00E85C .vwait2
al 00E852 .clear_zp
al 00E847 .vwait1
al 00E82F .initnes
....


When I load it in mesen I get an error
"import completed with 0 labels imported"
I feel like I'm not creating the debug file right
Re: Proper way of waiting for vblank without NMI
by on (#233862)
The option to generate the DBG file is --dbgfile. You've used -Ln which is not equivalent. (It's a VICE label file, which has a similar purpose, but it's not what Mesen uses.)

Also you should not use -d, I think that's for debugging the compiler itself, not your program, and probably slows down the process or adds unnecessary output. Only -g is important for what you want.

What -g does is generate more debug symbols to go in the DBG file (and label file). Without [b]-g[b], by default it doesn't keep track of much that it doesn't have to and these files will be fairly empty.
Re: Proper way of waiting for vblank without NMI
by on (#233863)
Yeah, it's sort of what I suspected, re: argument order is probably an issue. I mirror rainwarrior's sentiments. So, documentation references once more:

cl65 (compiler) flags: https://www.cc65.org/doc/cl65-2.html
cl65 (compiler) argument order: https://www.cc65.org/doc/cl65-3.html
ld65 (linker) flags: https://www.cc65.org/doc/ld65-2.html#ss2.1

And tips/hints:

1. You don't want -Ln {filename}, that generates a VICE label file, which is not what Mesen understands,
2. You don't want -d, which is "debug mode" for the compiler itself; not a common-used flag unless I think you're running into issues with cl65 behaviour itself,
3. --dbgfile {filename} is a linker flag, thus only available to ld65. You aren't calling ld65 natively, which means you need to use the -Wl {options} or --ld-args {options} flag of cl65 to pass arguments to ld65 (which its calling behind the scenes) (see below),
4. Strongly recommend putting your source filenames at the end of your command-line and stop appending arguments out of (I strongly suspect) laziness. See the "argument order" doc above for why.

In summary, your command should really be this (I have not tested this, I'm going purely off of documentation):

Code:
cl65 -t none -C nes.ini -m map.txt -vm -o rom.nes -g -Wl --dbgfile rom.dbg main.s header.s

If this doesn't work and ends up passing all the remaining arguments/files to the linker (I'm doubting this will happen; just be aware if you need to give more options to the linker, you separate them with commas), then #4 above is invalid and you should try this instead:
Code:
cl65 -t none -C nes.ini -m map.txt -vm -o rom.nes -g main.s header.s -Wl --dbgfile rom.dbg

Also note that the l of -Wl is a lowercase-ELL, not a one.

Alternately, you can try using cl65 on each source file separately with -c and then calling ld65 yourself to link together all the objects, e.g.:

Code:
cl65 -t none -g -c -o main.o main.s
cl65 -t none -g -c -o header.o header.s
ld65 -t none -C nes.ini -m map.txt -vm -Wl --dbgfile rom.dbg -o rom.nes main.o header.o

The latter is very much how most C software on *IX systems with GNU make work, using a Makefile to call the compiler repeatedly on several source files (example from BSD make, not GNU make), but make is a whole other complicated topic (and requires other software/binaries) that I don't want to get into.
Re: Proper way of waiting for vblank without NMI
by on (#233864)
Amending koitsu's last reply:

cc65 is the C compiler (C to assembly, or C to object)
ca65 is the assembler (assembly to object)
ld65 is the linker (objects to output)
cl65 is the combo "compile and link" (or assemble and link, in this case)

Also, the current documentation is here: https://cc65.github.io/doc/

(The old documents from several years ago that were left up for archival purposes are still unfortunately higher in google results. You should use the new ones.)


Though to be honest I avoid using cl65 in general and prefer to do separate steps for assemble and link. It may seem like an unnecessary step for a single file, but you can get better feedback about what's going on this way.
Re: Proper way of waiting for vblank without NMI
by on (#233865)
Got it! there is a comma I was forgetting
cl65 -t none -C nes.ini -m map.txt -vm -o rom.nes -g -Wl --dbgfile,rom.dbg main.s header.s
Thanks, I was really not getting what the switches do...
Re: Proper way of waiting for vblank without NMI
by on (#233867)
rainwarrior wrote:
--dbgfile is available to cl65. I don't think -Wl should be used in this case.

Hmm, are you sure? I don't see --dbgfile {filename} mentioned in that document (for cl65, cc65, or ca65 for that matter). I see --debug-info for cl65, which is the same as -g, and doesn't take a filename argument.

This is why I think -Wl needs to be used, since ld65 is what supports --dbgfile per documentation and this check I did that shows it only appears in the documentation for ld65:

Code:
~/work $ git clone git@github.com:cc65/cc65.git
Cloning into 'cc65'...
remote: Enumerating objects: 102, done.
remote: Counting objects: 100% (102/102), done.
remote: Compressing objects: 100% (75/75), done.
remote: Total 59174 (delta 40), reused 43 (delta 27), pack-reused 59072
Receiving objects: 100% (59174/59174), 21.67 MiB | 3.29 MiB/s, done.
Resolving deltas: 100% (37081/37081), done.
~/work $ grep dbgfile cc65/doc/*.sgml
cc65/doc/ld65.sgml:  --dbgfile name        Generate debug information
cc65/doc/ld65.sgml:  <label id="option--dbgfile">
cc65/doc/ld65.sgml:  <tag><tt>--dbgfile name</tt></tag>

It's super easy for me to mix up ca65 vs. cc65 vs. cl65 vs. ld65 so it's very possible I got something wrong.
Re: Proper way of waiting for vblank without NMI
by on (#233869)
No, I was wrong about that. Was reading from the wrong tab, I suppose? I'd edited it already before you replied... but you manged to get a quote in. :S

Would recommend that you stop using those outdated documentation links, though. The current stuff is here, and a lot has changed in the past several years since those were frozen:
https://cc65.github.io/doc/
Re: Proper way of waiting for vblank without NMI
by on (#233870)
Though also in your multiple-lines example at the end, if you're doing that you should use ca65 and not cl65 for the assemble step. You get better and more direct options and output that way.

Also the -t none is a workaround for cl65 which for some reason defaults to C64 instead of none. You can omit that from a ca65/ld65 invocation and have a simpler command line. (Actually if you're using -C with the linker, -t is invalid anyway.)

That's part of my advice in general for not using cl65. It's kind of a weird black sheep in this toolset, and it probably seems like it's simplifying things by removing a step, but I think we just proved otherwise with that awful command line jockeying we just had to do.
Re: Proper way of waiting for vblank without NMI
by on (#233873)
halkun wrote:
Got it! there is a comma I was forgetting
cl65 -t none -C nes.ini -m map.txt -vm -o rom.nes -g -Wl --dbgfile,rom.dbg main.s header.s
Thanks, I was really not getting what the switches do...

Ah, yes! I wasn't sure if the comma was needed since there's a substantial difference between spawning conventions, argument-wise, when it comes to arguments given to one utility that are "passed on" to another. It all has to do with how argv[] gets parsed (by cl65) and what gets passed on to the spawned utility (ld65). The comma-delimited portions are internally handled by cl65 to ensure that they're "passed on properly" to ld65, rather than parsed natively by cl65 itself (hope that makes sense!). In short, there's a substantial difference between these 3 (from the perspective of ld65's argv parser):

Code:
/* probably what happened with cl65 ... -Wl --dbgfile rom.dbg ... */
argv[0] "ld65",
argv[1]: "--dbgfile",
argv[2]: "main.s"

/* likely what would happen on *IX systems if doing cl65 ... -Wl "--dbgfile rom.dbg" ...
 * Note the quotes.  Probably would end up passing the entire string as a single argument to ld65 */
argv[0]: "ld65",
argv[1]: "--dbgfile rom.dbg",
argv[2]: "main.s"

/* probably what happened with cl65 ... -Wl --dbgfile,rom.dbg ..., which understandably works */
argv[0]: "ld65",
argv[1]: "--dbgfile",
argv[2]: "rom.dbg",
argv[3]: "main.s"

All of this (and more) is why I prefer just calling the compiler/assembler directly, and then the linker directly at the very end to link everything together, something rainwarrior advocates as well. cl65 does seem to be a "black sheep" of sorts; sticking to invocations of cc65/ca65/ld65 directly seems to be a better choice.
Re: Proper way of waiting for vblank without NMI
by on (#233874)
rainwarrior wrote:
Would recommend that you stop using those outdated documentation links, though. The current stuff is here, and a lot has changed in the past several years since those were frozen:
https://cc65.github.io/doc/

Yeah, I think I've been lectured about this more times than I can count. :-) The reason I get them wrong has to do with the bloody search results in Google or other SEs. Searching for "cc65 documentation" gets you these (order may vary per person, based on Google's weighting algorithms):

Code:
cc65 Documentation Overview
https://www.cc65.org/doc/

cc65 Users Guide: Usage
https://www.cc65.org/doc/cc65-2.html

GitHub - cc65/doc: the official cc65 documentation —
https://github.com/cc65/doc

cc65 Users Guide
https://cc65.github.io/doc/cc65.html

cc65 - a freeware C compiler for 6502 based systems
https://cc65.github.io/

ca65 Users Guide - cc65
https://cc65.github.io/doc/ca65.html

And this is just for cc65 -- but if you look very closely, the last result is actually for ca65 (the assembler), but says "cc65" in the title hence it comes back in the search results.

This same situation applies (though slightly varied) to ca65 and ld65. For cl65 and da65 (disassembler), the situation is much more clear: you only get 2 results: the http://www.cc65.org version and the cc65.github.io version.

github.io (a.k.a. "GitHub Pages") is the "web content" part of github.com, i.e. refers to content in your GitHub repository that you can change/manage with commits like normal, as long as its maintained either via static files or dynamically by GitHub via Jekyll. (I always forget this "sub-feature" of GitHub exists, as I prefer native Markdown documents so that I don't have "multiple websites" showing essentially the same stuff (github.com vs. github.io) -- just put all the content on github.com, which renders Markdown itself, and be done with it)

In short: I wish whoever maintains cc65.org/www.cc65.org would take the time to set up proper HTTP 3xx redirects from old document links to cc65.github.io and relieve half of this confusion. Users are almost certainly going to pick a website based on the software's name over something on GitHub. The actual project maintainers have obviously revamped documentation in a good way (ex. there is no more "cc65-2.html", they just put it all into one document and used anchors), but there's "old stuff lingering" that continues to take priority search-result-wise. :-(
Re: Proper way of waiting for vblank without NMI
by on (#233877)
koitsu wrote:
I wish whoever maintains cc65.org/www.cc65.org would take the time to set up proper HTTP 3xx redirects from old document links to cc65.github.io and relieve half of this confusion

I raised the issue twice in the past, and then gave up. Feel free to give it a try yourself.

Otherwise all I can do is point out that you keep linking the old documents, and offer a link to the better ones.