User Tools

Site Tools


vbasic_tutorial

VBASIC tutorial

Foreword

In this document we don't want to introduce you to BASIC programming language. We assume that you know it well enough to write more sophisticated programs than the immortal

10 PRINT “HELLO, WORLD!”
20 GOTO 10

first programming experience of every BASIC programmer. With this assumption in mind we want to give you a peek into areas where VBASIC can quickly become one of your closest friends:

  • general BASIC programming at somewhat lower level1), where the built-in programming aids like full support for haxadecimal numbers, 16-bit PEEKs and POKEs, as well as LO/HI functions come in very much handy. Even without BeamRacer installed
  • adding stunning visual effects to BASIC programs thanks to full VASYL assembly support2)
  • rapid prototyping of timing-sensitive visual effects for award winning demos or games, testing which would normally require a lot of boilerplate code
  • verification and debugging of VASYL programs thanks to built-in VASYL memory monitor/disassembler

Whichever is your cup of tea - create and enjoy!

Structuring your programs

In most cases the way programs are structured is a result of the programmer's style and his way of thinking/tackling the problem. BASIC as a programming language does not impose many strict rules on how programs have to be structured so we only give you a few hints, which we consider to be good practices.

Initialisation

Assuming that the machine, with all its components is in a particular state when starting your program is one of the common bad programming practices in general. Unexpected results, which may stem from this assumption are often hard to explain and debug. Do yourself a favour and initialise BeamRacer to a known state at the very beginning of your program:

10 RACER 0: RACER 2

makes sure that BeamRacer hardware is reinitialised into a known, default state and will behave the same every time you RUN your program.

But why not to simply issue a DLOFF in the first line instead?

Please note that the DLOFF/DLON combination does not reinitialise all of the chip's internal structures. DLOFF is best suited to temporarily stop already running displaylist processing. Like when we need to change displaylists at runtime. Obviously DLON alone is also indispensable once everything is ready to roll.

Parallelism

You didn't envision yourself having to deal with parallel computing problems on a C64, did you? ;-) Things changed. Now you have to make sure that both processors (the veteran 6510 and the new kid on the block VASYL) work together as a team rather than against each other.

  • make sure that whatever you write to from BASIC/6510 is not a subject to writes by VASYL:
    • VASYL memory
    • VASYL registers
    • VIC registers
  • turn off displaylist processing with a DLOFF statement whenever you need to modify displaylists, which may also be modified by VASYL from within the displaylist itself
  • keep a close eye on potential parallel use of VASYL PORTx (and related ADRxL/H, STEPx) registers! Neglecting this can lead your program into lalaland in no time!

The least error-prone approach is to set the displaylist and other VASYL memory data in their entireties, before starting displaylist processing with DLON statement. But we know that it is often unfeasible so having the above points in mind may save you lots of debugging time.

Displaylist programming

Another common, yet easy to avoid pitfall is incorrect opening/closing displaylist programming sections. The author's preferred way is to

  • always configure VBASIC “conveyor” with appropriate VCFG statement
  • always close displaylist off with a VEND statement
  • start displaylist
  • END the program

before writing a single line of the actual code:

10 RACER 0:RACER 2
100 VCFG 0,0,1
999 VEND
1000 DLON
1999 END

is a typical starting skeleton of a small to medium-sized VBASIC program. In this approach lines

  • 10-99 are used for initialisations / GOSUBs into subroutines delivering whatever may be needed to get pre-initialised, pre-calculated or pre-loaded from external storage
  • 100-999 carry displaylist assembly
  • 1000-1999 hold code that is executed in parallel while the displaylist is already running
  • 2000- get filled with subroutines as needed

This lines numbering scheme is of course just an example of the author's proven preference. The real takeaway here is:

  1. initialise things upfront
  2. never forget to configure the “conveyor(s)” you're going to use
  3. never forget to close the displaylist off with a VEND
  4. actually starting displaylist processing with a DLON may help in some “nothing works” cases ;-)

Common patterns

Transferring displaylists

The regular, most common way of storing displaylist programs in VASYL memory is to use the built-in displaylist assembler in conjunction with a preconfigured conveyor pointing to the memory address of choice. In a simple example this will look like:

10 RACER 0:RACER 2
100 VCFG 0,0,1
110 VWAIT $32,0
120 VMOV $20,15
130 VDELAYV 1
140 VMOV $20,6
150 VDELAYV 200
160 VMOV $20,15
170 VDELAYV 1
180 VMOV $20,14
999 VEND
1000 DLON
1999 END

Here above lines 110 to 999, when executed, will assemble and store our displaylist in VASYL memory starting at address $0000. The same approach works well for much larger programs too but displaylist programs can also be prepared separately and stored/loaded as needed from storage medium like disk drive for example. We'll cover these more advanced techniques later.

Transferring VASYL data

The VBASIC built-in displaylist assembler is great for inline assembling and storing even relatively large VASYL programs. But what about other types of data?

Transferring the non-executable parts of data to VASYL memory can be tackled in a few ways. The choice of the most suitable one depends on the amount of data, which needs to be transferred.

Direct VPOKE

Small data sets can be easily VPOKEd into VASYL memory

1000 REM ALL 16 COLOURS TO $0100 IN ORDER
1010 FOR I=0 TO 15
1020 VPOKE $100+I,I
1030 NEXT I

or

1000 REM ALL 16 COLOURS TO $0100 OUT OF ORDER
1010 FOR I=0 TO 15
1020 READ C
1030 VPOKE $100+I,C
1040 NEXT I
2000 DATA 1,4,3,6,12,2,15,14
2010 DATA 7,9,13,10,0,11,8,5

But you will quickly notice that the larger the dataset, the more visible lack of efficiency of this approach becomes.

VDATA

Step up a notch in efficiency is the use of VDATA. This keyword along with machine code behind it was designed specifically to be far more efficient in transferring medium-sized datasets.

1000 VCFG0,$100
1010 VDATA 0,127,0,1,255,192,3,255,224,3,231,224
1020 VDATA 7,217,240,7,223,240,7,217,240,3,231,224
1030 VDATA 3,255,224,3,255,224,2,255,160,1,127,64
1040 VDATA 1,62,64,0,156,128,0,156,128,0,73,0,0,73,0
1050 VDATA 0,62,0,0,62,0,0,62,0,0,28,0

Transfers quickly the Commodore Air Force One Airship to $0100 in VASYL RAM, while:

5320 VCFG 1,$1F00,1
5330 VDATA "CACACACBCCCDCDCDCDCCCBCA"
5340 VDATA "CACACACBCCCDCDCDCDCCCBCB"
5350 VDATA "CACACACACBCCCDCDCDCDCCCB"
5360 VDATA "CACACACACBCCCDCDCDCDCDCC"
5370 VDATA "CBCACACACACBCCCDCDCDCDCC"
5380 VDATA "CBCACACACACBCCCCCDCDCDCD"
5390 VDATA "CCCBCACACACACBCCCDCD"

sends another dataset $1F00 thereto. In order to make VDATA more versatile than regular DATA, we made it accept binary values in three forms:

  • decimal numbers ranging from 0 to 255
  • hexadecimal numbers ranging from $00 to $FF
  • LE strings of hexadecimal values (as seen in the last example above)

Using VDATA is an order of magnitude faster than plain DATA READing and VPOKE'ing. It is best suited for up to medium-sized datasets. OTOH there are times when you want to send larger sets of data to VASYL RAM. Typical example would be graphics/bitmap data, which needs to be placed in VASYL memory before it can be used as source for the Programmable Bitmap Sequencer for instance. Storing all those bits in ASCII encoded form of a BASIC program might be3) possible but surely a far cry from efficient use of available resources. Chiefly BASIC RAM but also LOADing and processing time4).

LOAD and COPY

Another notch up, especially useful for dealing with large amounts of data - is to LOAD the data into a memory buffer available within the main CPU address range and then COPY it from there to VASYL RAM. This requires more thorough knowledge of the Commodore 64 memory layouts as well as a bit of extra work needed to handle LOADing5) behaviour when called from within a running BASIC program. Specifically the fact that BASIC does not continue execution of current program once LOADing finishes but returns to its beginning and continues from there. The old, plain BASIC pattern for handling this behaviour remains valid with VBASIC:

  • set a LOAD tracking variable up
  • increment it before each LOAD
  • skip LOADing code sections according to the value of said variable (IF LD=… THEN … or ON LD GOTO/GOSUB …) where LD is the name of the tracking variable
10 RACER 0:RACER 2 : REM REINIT VASYL
20 DV=PEEK($BA)
30 REM ***************************
40 REM ***    LOAD GRAPHICS    ***
50 REM ***************************
60 IF LD=0 THEN GOSUB 5000 : REM LOAD
61 PRINT TAB(25);"DONE"
70 IF LD=1 THEN GOSUB 5100 : REM XFER
71 PRINT TAB(25);"DONE"
[...]

then in the subroutines starting at 5000 and 5100 we load and transfer data respectively:

5000 REM
5001 REM ****************************
5002 REM ***  LOAD LOGO TO $C000  ***
5003 REM ****************************
5010 PRINT "LOADING HIRES BITMAP...";
5020 LD=1 : LOAD"SDLOGO",DV,1
5100 REM
5101 REM ****************************
5102 REM ** XFER LOGO TO VASYL RAM **
5103 REM ****************************
5110 PRINT "SENDING TO VASYL RAM...";
5120 LD=2 : REM MARK XFER TO VASYL
5130 DS=DPEEK($AE) - 49152 : REM SIZE
5140 VCFG 2,49152 : REM LOADED ADDRESS
5150 VCFG 0,$1000 : REM DESTINATION
5160 COPY #0,#2,DS
5170 RETURN

Easy? I think so! There are a few caveats though.

Memory buffer

First you need to make sure that you have an available memory buffer of the required size. There is 4 KiB of RAM in Commodore 64, which is not normally used by BASIC. This relatively small memory area starting at 49152 ($c000) has been the favourite place for storing transient data or machine language subroutines for decades now. It looks like the first candidate for our buffer

Files preparation

Then all your data file need to be of a PRG type with two leading bytes pointing to a correct memory address it is expected to LOAD into. Since we use the above mentioned four KiB of RAM to LOAD as the buffer, the first two bytes of the file need to be $00 and $c06). Make sure that this is in fact the case or you risk crashing the machine. Make also sure that the file is not larger than 4KiB or you will crash the machine.

OK but what if my data is larger than the 4KiB available at 49152??

The answer is either you find a buffer large enough to hold all your data at once or you need to LOAD it in chunks not larger than the available buffer. I would strongly recommend the latter approach - split the file into as many smaller ones as needed and then LOAD and COPY them one by one. This will always work regardless of the size of your BASIC program.

But wouldn't it be much better to simply OPEN the file and read its content byte by byte and use VPOKE to send it to VASYL?

No. This looks like an elegant approach at first but in virtually all cases7) it will be unbearably slow to read a sizeable amount of data this way. And for small datasets it's easier and faster to use other methods presented above.

Takeaways:

  1. adapt the way you handle data transfer to VASYL memory to the amount of data you need to move there
  2. it does not pay to LOAD a few bytes. Using DATA or VDATA will be more efficient and less error-prone
  3. when dealing with large amounts of data, the most efficient way is to LOAD it in chunks not larger than the available buffer size. Up to 4KiB in practice
  4. steer clear of using memory areas other than the 4KiB of free RAM available at $c000. You'll thank me once we meet :-)
  5. reading data from sequential file is not really an option

Moving data around

We talked about moving data into VASYL accessible memory and outlined a few methods for doing it. How about moving some data within/into the computer's main RAM? An efficient way of doing this might become handy in various situations

  • moving graphics data (hires, charset, sprite, …) into their expected locations
  • clearing/filling memory areas (again - usually graphics or colour RAM related) with predefined values
  • utilising VASYL RAM as “swap” memory
  • copying ROM data to RAM for patching
  • […]

Whoever tried to fill even text screen with a predefined value(s) knows that this is not something Commodore BASIC excels at. OF course instead of clearing the screeen with POKEs we can issue a PRINT “{CLEAR}” statement and have the screen cleared8) in an instant. But what if we want to fill it with different value(s)?

Try the following to have a fresh impression:

100 FOR I=0 TO 999
110 POKE 1024+I,0
120 NEXT I

and now try this for a quick comparison:

100 VPOKE $FFFF,0
110 VCFG 1,$FFFF,0:VCFG 2,1024,1
120 COPY #2,#1,1000

The same technique can be used to fill required Colour RAM areas. Example:

5200 REM
5201 REM ****************************
5202 REM *** FAST COLOUR RAM FILL ***
5203 REM ****************************
5210 PRINT "UPDATING COLOUR RAM...";
5220 VPOKE $0FFF,14
5230 VCFG 1,$0FFF,0
5240 VCFG 2,$DAA8,1
5250 COPY #2,#1,320 : REM LAST 8 ROWS
5260 RETURN

Copying KERNAL ROM to RAM for patching uses a variation of this technique:

5600 REM
5601 REM ****************************
5602 REM *** PATCH KERNAL 17 ROWS ***
5603 REM ****************************
5610 PRINT "PATCHING KERNAL...";
5620 VCFG 1,$E000,1 : REM $E000
5630 VCFG 2,$E000,1 : REM KERNAL ROM
5640 COPY #1,#2,$2000
5650 VCFG 1,$E000,1 : REM $E000
5660 VCFG 2,$E000,1 : REM KERNAL ROM
5670 COPY #2,#1,$2000
5680 POKE $E882,$11 : REM PATCH SCROLL
5690 POKE 1,53: REM SWITCH TO RAM KERNAL
5700 RETURN

It won't be as fast as an optimised machine code (here we do actually two transfers) but surely you can spare yourself checking how long would this take using plain BASIC.

VDATA revisited

In previous sections we discussed how easy and efficient it is to configure a “conveyor” and then use VDATA statements to quickly move data into VASYL RAM. What we didn't mention though is the fact that we can use the conveyor #2 to make VDATA (and many other “V” statements) talk to computer's main memory instead. Remember this famous example from page 71 of Commodore 64 Users Guide? Yeah - who can forget it, right?

1 REM UP, UP, AND AWAY!
5 PRINT "{CLEAR}"
10 V=53248 : REM START OF DISPLAY CHIP
11 POKE V+21,4 : REM ENABLE SPRITE 2
12 POKE 2042,13 : REM SPRITE 2 DATA FROM 13TH BLK
20 FOR N = 0 TO 62: READ Q : POKE 832 + N, Q : NEXT
30 FOR X = 0 TO 200
40 POKE V+4,X: REM UPDATE X COORDINATES
50 POKE V+5,X: REM UPDATE Y COORDINATES
60 NEXT X
70 GOTO 30
200 DATA 0,127,0,1,255,192,3,255,224,3,231,224
210 DATA 7,217,240,7,223,240,7,217,240,3,231,224
220 DATA 3,255,224,3,255,224,2,255,160,1,127,64
230 DATA 1,62,64,0,156,128,0,156,128,0,73,0,0,73,0
240 DATA 0,62,0,0,62,0,0,62,0,0,28,0

Let's quickly and effortlessly make it use the much faster VDATA instead

1 REM UP, UP, AND VDATA!
5 PRINT "{CLEAR}"
10 V=53248 : REM START OF DISPLAY CHIP
11 POKE V+21,4 : REM ENABLE SPRITE 2
12 POKE 2042,13 : REM SPRITE 2 DATA FROM 13TH BLK
20 GOSUB 200
30 FOR X = 0 TO 200
40 POKE V+4,X: REM UPDATE X COORDINATES
50 POKE V+5,X: REM UPDATE Y COORDINATES
60 NEXT X
70 GOTO 30
200 VCFG 2,832,1
210 VDATA 0,127,0,1,255,192,3,255,224,3,231,224
220 VDATA 7,217,240,7,223,240,7,217,240,3,231,224
230 VDATA 3,255,224,3,255,224,2,255,160,1,127,64
240 VDATA 1,62,64,0,156,128,0,156,128,0,73,0,0,73,0
250 VDATA 0,62,0,0,62,0,0,62,0,0,28,0
260 RETURN

Here we have a relatively small dataset so the difference is adequately small too. But the more data you need to process, the more clearly pronounced the difference becomes.

TBC

1)
Mandatory for sound and graphics
2)
Obviously requires BeamRacer expansion installed in order to see them in action
3)
Size dependent, of course
4)
ASCII encoded binary will be over twice the actual binary size in the best case
5)
as opposed to opening the file manually and reading data from it byte by byte
6)
There are ways to LOAD files to another address than the one specified by its first two-byte pointer value but we don't go into this territory here
7)
Unless your machine configuration includes a very efficient “speeder”, which accelerates sequential reading by a large factor. In such case though, LOADing will be most probably accelerated by an even larger factor!
8)
technically filled with bytes having their value corresponding to ASCII code for the invisible SPACE character
vbasic_tutorial.txt · Last modified: 2022/12/04 12:01 by silverdr