It is currently Thu Sep 19, 2019 11:41 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 23 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Tue Feb 05, 2019 6:49 pm 
Offline

Joined: Mon Jul 18, 2011 10:04 pm
Posts: 45
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?


Top
 Profile  
 
PostPosted: Tue Feb 05, 2019 7:13 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 8559
Location: Seattle
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.


Top
 Profile  
 
PostPosted: Tue Feb 05, 2019 7:17 pm 
Offline

Joined: Mon Jul 18, 2011 10:04 pm
Posts: 45
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



Top
 Profile  
 
PostPosted: Tue Feb 05, 2019 7:30 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 7582
Location: Canada
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.


Top
 Profile  
 
PostPosted: Tue Feb 05, 2019 7:37 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 8559
Location: Seattle
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.


Top
 Profile  
 
PostPosted: Tue Feb 05, 2019 10:03 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11412
Location: Rio de Janeiro - Brazil
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.


Top
 Profile  
 
PostPosted: Wed Feb 06, 2019 2:29 am 
Offline

Joined: Fri Feb 24, 2012 12:09 pm
Posts: 1005
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).


Top
 Profile  
 
PostPosted: Wed Feb 06, 2019 7:59 pm 
Offline

Joined: Mon Jul 18, 2011 10:04 pm
Posts: 45
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


Top
 Profile  
 
PostPosted: Wed Feb 06, 2019 9:10 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21592
Location: NE Indiana, USA (NTSC)
Mesen can use a .dbg file generated using the --dbgfile option.

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Wed Feb 06, 2019 10:55 pm 
Offline

Joined: Mon Jul 18, 2011 10:04 pm
Posts: 45
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 :)


Top
 Profile  
 
PostPosted: Wed Feb 06, 2019 11:34 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4208
Location: A world gone mad
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).


Top
 Profile  
 
PostPosted: Wed Feb 06, 2019 11:54 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11412
Location: Rio de Janeiro - Brazil
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.


Top
 Profile  
 
PostPosted: Thu Feb 07, 2019 12:07 am 
Offline

Joined: Mon Jul 18, 2011 10:04 pm
Posts: 45
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


Top
 Profile  
 
PostPosted: Thu Feb 07, 2019 12:17 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 7582
Location: Canada
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.


Top
 Profile  
 
PostPosted: Thu Feb 07, 2019 12:35 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4208
Location: A world gone mad
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.


Last edited by koitsu on Thu Feb 07, 2019 12:43 am, edited 1 time in total.

Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 23 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: Google [Bot], Google Adsense [Bot] and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group