User Tools

Site Tools


introduction_to_programming_the_beamracer

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
introduction_to_programming_the_beamracer [2020/05/27 19:53] laubzegaintroduction_to_programming_the_beamracer [2021/09/10 01:49] (current) – [Initialization] silverdr
Line 20: Line 20:
 ==== Initialization ==== ==== Initialization ====
  
-In order to maintain a high level of compatibility with existing C64 software, BeamRacer remains hidden on power on, and the computer behaves as if it was not there. That is, register writes are ignored, and reads report the same values as those in a vanilla C64. +In order to maintain a high level of compatibility with existing C64 software, BeamRacer remains hidden on power up, and the computer behaves as if it was not there. That is, register writes are ignored, and reads report the same values as those in a vanilla C64. 
-It is necessary to perform so-called “register knocking” for the board to reveal its presence. This is achieved by writing a sequence $42, $52  (screen codes for “BR”) to register $D030. To check if the board is indeed there, register $D030 can be then read and compared with “0”.+It is necessary to perform so-called “register knocking” for the board to reveal its presence. This is achieved by writing a sequence $42, $52  (screen codes for “BR”) to register $D031. To check if the board is indeed there, register $D031 can then be read and verifiedValue of $FF means that BeamRacer was NOT activated.((Please note that early revisions of this chapter, and the code snippet below, advocated checking for $00 as an indicator that the BeamRacer was activated. This is now deprecated and may lead to "false negative" results. The inverted logic, i.e. checking that the value is NOT $FF, is currently the recommended method.))
  
 <code  [enable_line_numbers="false"]> <code  [enable_line_numbers="false"]>
- LDX $D030+VREG_CONTROL = $D031 
 + 
 + LDX VREG_CONTROL
  INX  INX
  BNE BEAMRACER_ALREADY_ACTIVE  BNE BEAMRACER_ALREADY_ACTIVE
  LDX #$42  LDX #$42
- STX $D030+ STX VREG_CONTROL
  LDX #$52  LDX #$52
- STX $D030 + STX VREG_CONTROL 
- LDX $D030 + LDX VREG_CONTROL 
- BEQ BEAMRACER_FOUND_AND_ACTIVATED+ INX 
 + BNE BEAMRACER_FOUND_AND_ACTIVATED
  RTS ; sadly, no BeamRacer...  RTS ; sadly, no BeamRacer...
 </code> </code>
Line 39: Line 42:
 ==== Local Memory ==== ==== Local Memory ====
  
-For VASYL to execute a display list, it first needs to be placed in its local memory (LRAM). BeamRacer provides VASYL with 512KiB of LRAM, organized in 8 banks of 64KiB each. The 6510 can put data into LRAM using two one-byte wide ports. Each port is built out of five registers:+For VASYL to execute a display list, it first needs to be placed in its local memory (LRAM). BeamRacer provides VASYL with eight banks of 64KiB of LRAM each. The 6510 can put data into LRAM using two one-byte wide ports. Each port is built out of five registers:
  
   * [[registers#ADR0L|ADRL]]   * [[registers#ADR0L|ADRL]]
Line 47: Line 50:
   * [[registers#REP0|REP]]   * [[registers#REP0|REP]]
  
-ADRL/ADRH are respectively the LSB and MSB of a 16-bit address in a LRAM memory bank. Together they determine the location where data will be written to (or read from), while register PORT is used to transfer the actual value. A following code+ADRL/ADRH are respectively the LO and HI bytes of a 16-bit address in a LRAM memory bank. Together they determine the location where data will be written to (or read from), while register PORT is used to transfer the actual value. A following code
  
 <code> <code>
Line 65: Line 68:
  LDA #$20  LDA #$20
  STA VREG_ADR0H  STA VREG_ADR0H
- LDA #$01 ; advance LRAM pointer by one on every transfer+ LDA #$01 ; advance LRAM pointer by one after every transfer
  STA VREG_STEP0  STA VREG_STEP0
  LDX #0  LDX #0
Line 75: Line 78:
 </code> </code>
  
-On some occasions you may also want to read from LRAM with the CPU. There is an extra step involved, as many 6510 addressing modes result in a bus READ access before the requested WRITE occurs, which could lead to confusing results if not used with care. Register CTRL1 has a bit named PORT_READ_ENABLE, which is used to enable reading from both ports. To read every 4th byte of a 256-byte long sequence starting from LRAM location 0, the following code would be used+On some occasions you may also want to read from LRAM with the CPU. There is an extra step involved, as many 6510 addressing modes result in a bus READ access before the requested WRITE occurs, which could lead to confusing results if not used with care. Register CONTROL has a bit named PORT_READ_ENABLE, which is used to enable reading from both ports. To read every 4th byte of a 256-byte long sequence starting from LRAM location 0, the following code would be used
  
 <code> <code>
- LDA VREG_CTRL1+ LDA VREG_CONTROL
  ORA #CTRL_PORT_READ_ENABLE  ORA #CTRL_PORT_READ_ENABLE
- STA VREG_CTRL1+ STA VREG_CONTROL
    
  LDA #$00  LDA #$00
Line 131: Line 134:
 the second instruction will be ignored and act as a NOP (no-operation). the second instruction will be ignored and act as a NOP (no-operation).
  
-[[registers#MOV|MOV]] is an instruction used to transfer a value to a VIC-II or VASYL register. For example+[[registers#MOV|MOV]] is an instruction used to transfer a value to a VIC-II or VASYL register. The first argument is the number of the destination register (where 0 corresponds to $D000 in regular memory space), while the second argument is a byte value to be stored in the register. For example
  
 <code vasyl> <code vasyl>
Line 142: Line 145:
  
 <code vasyl> <code vasyl>
-dlist: 
     WAIT  48,13     WAIT  48,13
     MOV $20, 1     MOV $20, 1
Line 149: Line 151:
     MOV $20, 0     MOV $20, 0
     END         ; this is a handy alias for instruction "WAIT 511,63"     END         ; this is a handy alias for instruction "WAIT 511,63"
-dlend: 
 </code> </code>
  
Line 161: Line 162:
  
 <code vasyl> <code vasyl>
-dlist: 
     WAIT  48, 0     WAIT  48, 0
     MOV $20,15     MOV $20,15
Line 169: Line 169:
     MOV $20,15     MOV $20,15
     END     END
-dlend: 
 </code> </code>
  
Line 176: Line 175:
  
 <code vasyl> <code vasyl>
-dlist: 
     WAIT    48, 0 ; starting line     WAIT    48, 0 ; starting line
     MOV    $20,15     MOV    $20,15
Line 184: Line 182:
     MOV    $20,15     MOV    $20,15
     END     END
-dlend: 
 </code> </code>
  
Line 210: Line 207:
  
 <code vasyl> <code vasyl>
-dlist: 
     WAIT    48, 0 ; starting line     WAIT    48, 0 ; starting line
 loop:     loop:    
Line 223: Line 219:
     BRA    loop         ; loop endlessly     BRA    loop         ; loop endlessly
     END                 ; never reached     END                 ; never reached
-dlend: 
 </code> </code>
  
Line 234: Line 229:
 While this nice feature of VASYL makes infinite loops practical, in many situations we want to have more control over the number of loop repetitions. Let's close this intro chapter with two more instructions useful for exactly that. While this nice feature of VASYL makes infinite loops practical, in many situations we want to have more control over the number of loop repetitions. Let's close this intro chapter with two more instructions useful for exactly that.
  
-=== CSET and CDEC ===+=== SET and DEC ===
  
-VASYL contains two internal 8-bit counters designed to make controlled looping easy. This is how it works.+VASYL contains two internal 8-bit counters called A and B, which are intended to make controlled looping easy. This is how it works.
  
-Instruction "[[isa#CSET|CSET]] C, N" loads value N (ranging from 0 to 255) to counter number C. For instance+Instruction "[[isa#SETA|SETA]] N" loads value N (ranging from 0 to 255) to counter A. Instruction "[[isa#SETB|SETB]] N" does the same for counter B. For instance
  
 <code vasyl> <code vasyl>
-    CSET 0,  3        ; load 3 to counter 0 +    SETA   3        ; set counter A to 3 
-    CSET 1,255        ; load 255 to counter 1+    SETB 255        ; set counter B to 255
 </code> </code>
  
-To make a counter run, we now need to use instruction "[[isa#CDEC|CDEC]] C". What it does is a bit more complicated - first, it checks if the value held by counter is equal to "0", and if it is, it skips the next two bytes of the display list (which usually is how much space the next instruction occupies). If the value in the counter is anything else than zero, CDEC decrements it by one, and then continues normally. \\ This is best illustrated building on the previous example, with newly added lines highlighted.+To make a counter run, we now need to use instruction "[[isa#DECA|DECA]]" for counter A, or "[[isa#DECB|DECB]]" for counter B. What it does is a bit more complicated - first, it checks if the value held by respective counter is equal to "0", and if it is, it skips the next two bytes of the display list (which usually is how much space the next instruction occupies). If the value in the counter is anything else than zero, the instruction decrements it by one, and then continues normally. \\ This is best illustrated building on the previous example, with newly added lines highlighted.
  
-<code vasyl [highlight_lines_extra="3,13"]> +<code vasyl [highlight_lines_extra="2,12"]>
-dlist:+
     WAIT    48, 0 ; starting line     WAIT    48, 0 ; starting line
-    CSET     0,       ; load 3 to counter 0+    SETA     3          ; load 3 to counter 0
 loop:     loop:    
     MOV    $20, 8     MOV    $20, 8
Line 260: Line 254:
     MOV    $20, 0     MOV    $20, 0
     DELAYV  10          ; a larger gap between individual rasterbars     DELAYV  10          ; a larger gap between individual rasterbars
-    CDEC              ; zero? skip the next instruction. not zero? decrement+    DECA                ; zero? skip the next instruction. not zero? decrement
     BRA    loop         ; will be skipped when counter 0 reaches 0     BRA    loop         ; will be skipped when counter 0 reaches 0
     END     END
-dlend: 
 </code> </code>
  
Line 270: Line 263:
 {{:result_3.png?600|}} {{:result_3.png?600|}}
  
-What happened here? On the first iteration through the loop, the counter holds value 3. This is clearly different from 0, so all CDEC does is to decrement it by one. On the next iteration (when the second rasterbar is drawn) it holds value 2. On the third (third rasterbar) - the value is 1. Finally, on the fourth iteration (fourth rasterbar), the counter holds the value of 0. When we get to execute CDEC this time, it does things differently. Since counter "0" has ran out, it skips the next instruction (BRA). Display list then reaches instruction END, which makes sure that nothing more happens in this frame. And the next frame, the whole cycle repeats, since CSET reinitializes the counter to 3.+What happened here? On the first iteration through the loop, the counter holds value 3. This is clearly different from 0, so all DECA does is to decrement it by one. On the next iteration (when the second rasterbar is drawn) it holds value 2. On the third (third rasterbar) - the value is 1. Finally, on the fourth iteration (fourth rasterbar), the counter holds the value of 0. When we get to execute DECA this time, it does things differently. Since counter has ran out, DECA skips the next instruction (BRA). Display list then reaches instruction END, which makes sure that nothing more happens in this frame. And the next frame, the whole cycle repeats, since SETA reinitializes the counter to 3.
  
 A good thing about having two counters is that they can be used to construct nested loops. How about drawing three groups of four rasterbars each, where space between groups is twice the size of space between individual bars? Please give it a try! A good thing about having two counters is that they can be used to construct nested loops. How about drawing three groups of four rasterbars each, where space between groups is twice the size of space between individual bars? Please give it a try!
  
  
-Finally, be aware that when CDEC decides to skip, it has no idea how many bytes the next instruction occupies - one or two - so it always skips two. Instructions used for jumping (of which BRA is one example) take two bytes, so this fits nicely. But once you start composing more advanced display lists and discover other uses for CDEC, be sure to verify the number of bytes used by the next instruction (in [[isa|this table]]), and if happens to take just a single byte, simply pad it with a NOP.+Finally, be aware that when DECA or DECB decides to skip, it has no idea how many bytes the next instruction occupies - one or two - so it always skips two. Instructions used for jumping (of which BRA is one example) take two bytes, so this fits nicely. But once you start composing more advanced display lists and discover other uses for DEC instructions, be sure to verify the number of bytes used by the next instruction (in [[isa|this table]]), and if happens to take just a single byte, simply pad it with a NOP.
  
 This concludes our introduction to BeamRacer programming. Please practice what you have learned here before moving on to the next chapter. This concludes our introduction to BeamRacer programming. Please practice what you have learned here before moving on to the next chapter.
  
introduction_to_programming_the_beamracer.1590634429.txt.gz · Last modified: 2020/05/27 19:53 by laubzega