Search code examples
assemblyc646510

Stable raster on C64


Using 6510 assembly on the Commodore 64, I am trying to create a stable raster effect. Using the double IRQ principle I draw some raster lines on the screen. I pad with NOPs to match 63 cycles for every normal scanline, and to 23 cycles for every badline. I realise that there is a specific start line I need to set, in order to match my 8th iteration with a badline, but no matter on what line I put the first line or what combination of NOPs I use, I can't get the timing right. I want complete lines that are not "broken". Can anyone see what I am doing wrong? Code is in Kick Assembler format. And here is a screenshot:

Screenshot

.pc = $0801 "Basic upstart"
:BasicUpstart($8000)

.pc = $8000 "Program"

  jsr $ff81

  sei
  lda #$35
  sta $01

  jsr setupInterrupts
  cli

  jmp *

setupInterrupts:
  lda #<int1
  ldy #>int1
  sta $fffe
  sty $ffff

  lda #$01
  sta $d01a
  lda #$7f
  sta $dc0d
  sta $dd0d
  lda $dc0d  
  lda $dd0d
  lda #$1b
  sta $d011
  lda #$01
  sta $d019

  lda start
  sta $d012

  rts

start:
  .byte 56

int1:
  pha txa pha tya pha

  :STABILIZE()

.for (var i=0; i<7; i++) {
  inc $d020   // 6 cycles
  inc $d021   // 6 cycles
  nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop // 24*2=48 cycles
  bit $ea     // 3 cycles
              // = 63 cycles
}
  inc $d020   // 6 cycles
  inc $d021   // 6 cycles
  nop nop nop nop // 4*2=8 cycles
  bit $ea     // 3 cycles
              // = 23 cycles (badline)

  lda #$00
  sta $d020
  sta $d021

  lda start
  sta $d012

  lda #<int1 
  ldy #>int1 
  sta $fffe
  sty $ffff

  lda #$01
  sta $d019

  pla tay pla tax pla

  rti


.macro STABILIZE() {

  lda #<nextRasterLineIRQ
  sta $fffe
  lda #>nextRasterLineIRQ
  sta $ffff   

  inc $d012

  lda #$01
  sta $d019

  tsx

  cli

  nop nop nop nop nop nop nop nop

nextRasterLineIRQ:
  txs

  ldx #$08
  dex
  bne *-1
  bit $00

  lda $d012
  cmp $d012

  beq *+2      
}

Solution

  • As i understand you, your problem isn't that your raster bars are flickering (i.e. your raster interrupt is stable), but that the second line of the raster-bar you are drawing on the screen isn't fully red.

    Your problem is bad lines. (See [1])

    After you have stabilized your raster interrupt, with the code you posted, your "actual code" will start running at cycle 4 of rasterline $3A.

    The second line of your raster-bar, where you want to the background color and the border color to be red, is a bad line. (It is raster-line $3B. Since $D011 = $1B, this is a bad-line, since the lower 3 bits of $D011 and $D012 are the same)

    On this bad line, the first INC (INC $D020) manages to run, so the border color becomes red. Then the second INC (INC $D021) starts running, but the VIC takes over before it completes, and your INC $D021 is thus not completed until after the VIC has given the bus back. (This is 43 cycles later - i.e. setting the background-color to red is delayed by 43 cycles).

    You almost had it, but the badline was on a different raster-line than what your code expected and you needed to "push a few cycles" so that both INCs would be executed on badlines before being interrupted by the VIC. (It is a bit too late to start executing the two INCs at cycle 4 of a badline, if you want both of them to be executed before the VIC takes over)

    Updated Example:

    Try replacing this section of your code:

    .for (var i=0; i<7; i++) {
      inc $d020   // 6 cycles
      inc $d021   // 6 cycles
      nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop // 24*2=48 cycles
      bit $ea     // 3 cycles
                  // = 63 cycles
    }
    
      inc $d020   // 6 cycles
      inc $d021   // 6 cycles
      nop nop nop nop // 4*2=8 cycles
      bit $ea     // 3 cycles
                  // = 23 cycles (badline)
    

    With this:

    // a delay to get to some cycle at the end of the raster-line, so we have time to execute both inc's on 
    // each successive raster-line - in particular on the badlines before the VIC takes over the bus.
    .for (var i=0; i<28; i++) nop
    
    // just for illustrative purposes - not cool code :)
    .for (var i=0; i<8*6; i++) {
      inc $d020   // 6 cycles
      inc $d021   // 6 cycles
      .if ([i & %111] == 0) {
          // badline
          nop nop nop nop // 4*2=8 cycles
      } else {
          // non-badline
          nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop // 24*2=48 cycles
          bit $ea     // 3 cycles
                      // = 63 cycles
      }
    }
    

    (Warning: This code is quite wasteful memory-wise - the same effect could easily be made with a normal loop) (Instead of varying the delay, you could alternatively push the bad-lines away, by modifying $D011, if you don't plan to display character graphics)

    Try checking out the machine-code monitor in the HOXS64 emulator. It is perfect for debugging timing related issues. It shows you which cycle of which raster-line you are on at any given time (+it can break on interrupt taken).

    Hope this helped :)


    Note, i haven't throughly looked through your stable-raster routine for pitfalls, but it seems ok - the approach is correct and you don't have any flickering. If you start getting flickering raster-bars, you know what to fix. ;)


    In case someone reading this does not know what badlines are:

    Cool References:

    [1]: Read more about "bad lines" in the vic-article (or "the vic-bible" as it deserves to be called): http://csdb.dk/release/?id=44685 (PDF) or http://vice-emu.sourceforge.net/plain/VIC-Article.txt (TXT). Also see the addendum: http://vice-emu.sourceforge.net/plain/VIC-Addendum.txt

    Basically, when the VIC-chip starts drawing the first raster-line of a text-line, it steals 40-43 cycles from the CPU (see below for why not always 43). These raster-lines are called "bad lines". On a bad line there are thus only 20-23 cycles available, instead of 63.

    (To be more precise, a badline occurs when the 3 lowermost bits of $D011 equals the 3 lowermost bits of $D012 (and we're not in the border and the screen haven't been "switched off" by bit 4 of $D011))

    The VIC-chip uses the last 40 of these 43 cycles to read the 40 characters which is to be displayed on the text-line. The CPU can't execute any instructions during these 40 cycles.

    During the first 3 cycles of these 43 cycles, however, the CPU can actually execute "write cycles" of its instructions - but only write cycles, not read cycles. (See [2]) So if you time your opcodes correctly, you can execute some of the cycles of your instructions during these 3 cycles. (Note: the only instruction with 3 write-cycles is "brk", which is usually useless, so in practice you will only be able to use at most 2 of these 3 cycles for something useful).

    Note that in addition to stealing cycles on badlines, the VIC will also steal cycles from the CPU on raster-lines with sprites.

    [2]: See "64doc" to learn which cycles of the C64's different instructions are write-cycles: http://vice-emu.sourceforge.net/plain/64doc.txt
    (Write-cycles are marked as "W" in the tables, and read-cycles are marked as "R")

    [X]: ...And there are lots of good articles at http://codebase64.org