A sample programming session in VICMON

Basic and Machine Language

Moderator: Moderators

User avatar
Mike
Herr VC
Posts: 4831
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

A sample programming session in VICMON

Post by Mike »

Hi, y'all!

Let's just pretend we got a VICMON cartridge for Christmas. How about translating the well known "Coloured Birds" example of the VIC User's Manual (see p. 41) to machine language? :mrgreen:

Code: Select all

1 A$="{BLK,WHT,RED,CYN,PUR,GRN,BLU,YEL}"
2 N=INT(RND(1)*8)+1
3 B$=MID$(A$,N,1)
4 PRINTB$"{SHIFT-U,SHIFT-Q,SHIFT-I,SHIFT-J,SHIFT-Q,SHIFT-K}";
5 GOTO2
Here we go:

Image

At first, we need to take care of the two strings present in the program: unfortunately, VICMON and it's brethren do not feature an easy method to directly enter PETSCII strings into memory - unlike the ":"-command in the output of "M" or the ","-command in the output of "D", the "´"-output of the "I"nterpret command is not reversible.

It's really a nuisance to look up all the PETSCII codes, but the H-command comes to the rescue! There, the single quote (´) allows to search for a given plain text string, and we just let H search in $0000 to $03FF where it keeps that string to compare it with memory. As evident, VICMON stores that string from $0031 onwards, and for the string with the control characters, we leave out the double quote ("). Note: other monitors most probably use a different position for that buffer!

So two T commands transfer the 8 byte long control character string from $0032..$0039 to $1800..$1807 and the 6 byte long 'birds' string from $0031..$0036 to $1808..$180D. The program is now supposed to start at $180E.

We now need a random number between 0 and 7 (both inclusive) to index into the control characters. For our application, we take the lowest 3 bits of the VIA #2 timer 1 low byte in $9124. In principle, that's a bad (pseudo) random number generator, but it's good enough for our purpose. :)

The instructions from $180E..$1817 output the random colour code, and the loop in $181A..$1825 outputs the "birds" string, with the KERNAL CHROUT routine at $FFD2:

Image

Before we jump back to the start of the program, we check for the STOP key with JSR $FFE1. When it is not pressed, BNE $180E loops. Otherwise, the program is halted with the BRK instruction and returns to the monitor:

Image

Not to forget: save the program before you start it! Tape users will of course use "01" instead of "08". ;)

Now, go for "G 180E" to let the birds fly! And wee, it's noticably faster than its BASIC counterpart!

Image

Finally, the STOP key halts the program, as was intended. If you get a white prompt, just press Ctrl-6 to return to a blue cursor. With R, you can re-display the register contents in that case.

Image

A Happy Boxing Day! :D

Michael
User avatar
Mike
Herr VC
Posts: 4831
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample programming session in VICMON

Post by Mike »

... the saga continues ...

Having watched this colourful flock of birds for some time now, we get a little more adventurous. There's that little animation on page 64 of the User's manual, which sends a ball on diagonal paths around the screen to bounce off the border. However, it looks quite flickery, something which ML could surely improve upon (I set the border colour to blue instead of white, and corrected the typo in line 140):

Code: Select all

10 PRINT "{CLR}"
20 POKE 36879, 14
30 POKE 36878, 15
40 X = 1
50 Y = 1
60 DX = 1
70 DY = 1
80 POKE 7680 + X + 22*Y, 81
90 FOR T=1 TO 10: NEXT
100 POKE 7680 + X + 22*Y, 32
110 X = X + DX
120 IF X=0 OR X=21 THEN DX=-DX: POKE 36876, 220
130 Y = Y + DY
140 IF Y=0 OR Y=22 THEN DY=-DY: POKE 36876, 230
150 POKE 36876,0
160 GOTO 80
Lines 80 and 100 don't look especially promising though. Our little, small 6502 in the VIC-20 isn't all that good at multiplying numbers. :?

Adding up 16-bit numbers isn't that hard though - so we build a table that contains the addresses of all screen lines. Separated into low- and high-bytes! Starting up VICMON, the program again goes to $1800, the low-bytes of the screen line addresses go to $1600 and the high-bytes go to $1700. That means we can use a single value of the X register to index into *both* parts of the table!

Image

We obtain the next entry in the table by adding 22 = $0016 to the previous value. As soon X reaches 22, there have 23 entries (0..22) been written, the table then is complete. First, a check whether the table is built as wanted - the BRK re-enters VICMON:

Image

That looks good! Combining the low- and high-bytes of both table parts we get $1E00, $1E16, $1E2C, ... up to $1FE4. Let's check: 7680 + 22*22 = 8164 = $1FE4. Exactly what was expected!

Image

That table being prepared, we can start with the actual translation of the original BASIC program. We continue at $1822, overwriting the BRK instruction there. Now, what's the PETSCII code of 'clear screen'? Easy. We once ask again the H command and find $93 in the pattern buffer ($22 is the double quote). The POKEs in the lines 20 and 30 are as easy as pie. And the variables X, Y, DX and DY go into the zeropage. $F7..$FA are normally used for RS232 operations, but we don't do any of that. So $F7..$FA are free for our purpose:

Image

Of course there's no need to reload A with 1 for each store. We're quite lucky here. :)

Remember address $183B. That is the start address of the main loop!

The index registers now have their roles reversed somewhat: The X register is loaded from $F8 (which is defined to hold the Y co-ordinate!) and indexes into the line address table. The instructions at $183D..$1845 place the line address into a pointer at $FB, and use the (indirect),Y address mode to index into the line. As a consequence, Y is loaded from $F7, and holds the value of the X co-ordinate. Still not confused? :wink:

Image

LDA #$51 and STA ($FB),Y truly put the ball into place.

In line 90, the BASIC program now calls for a FOR loop to keep the ball at that place for a short time. No use to do this the same way in machine code. This would be way too fast.

Instead, we call the VIC chip for help. One of its registers contains the number of the current screen line, and the instructions at $184F..$1855 loop until the first raster lines in the top border are drawn on screen. Plenty of time before the screen window is scanned by the electron beam of the CRT to do all the following actions, and finally place the ball at the next position.

First, the 'old' ball is erased. We also move the "sound off" POKE 36876,0 from the end of the loop to here, so any sound blip is at least heard 1/60 or 1/50 of a second. The instructions at $185F..$1864 correspond to line 110 (X=X+DX). The next line of the BASIC program uses IF to check, whether the ball has touched the left or right border, and we need to resolve the forward references of two branch instructions (the branch 'targets' $1866 and $186A are just placeholders!):

Image

After $1864, no need for a CMP #$00. We've just found out, where the BEQ has to go into the "THEN" clause: at $186C!

Image

There we go.

The THEN clause inverts the sign of DX. That's actually the same as if we'd subtract the original value from zero, and that's how we do it: DX = -DX (= 0 - DX!). Let's not forget the POKE into 36876:

Image

STA $900C is the last instruction of the THEN clause. When the IF condition of line 110 isn't true, the branch at $186A should continue at $1878. So, we go a few lines up and correct also that BNE:

Image

Lines 130 and 140, which handle the Y co-ordinate, are translated quite the same. Here's how it looks after those two branches also have been corrected:

Image

Line 150 (the "sound off" POKE) has already been handled.

Instead of just reentering the main loop at $183B (you remember?) we check for the STOP key, with JSR $FFE1. When STOP is pressed, the BRK instruction at $1896 returns us to VICMON. Before you start the program with G 1800, save the hard work:

Image

And there it is! :mrgreen:

Image

Blip - blop - blip - blop - ...

... actually, the program as shown has a small bug, which however only manifests on NTSC. Can you spot it? We'll take a look into this, and fix that bug. :)
User avatar
Wilson
Vic 20 Enthusiast
Posts: 190
Joined: Mon Sep 28, 2009 7:19 am
Location: Brooklyn, NY

Re: A sample programming session in VICMON

Post by Wilson »

I missed the original post here. I love the colored birds example (as my avatar suggests ;))! Nothing more fun than doing some MONitor coding. Fun ideas! I still think a monitor is the most accessible and best way to start learning ML.
wimoos
Vic 20 Afficionado
Posts: 346
Joined: Tue Apr 14, 2009 8:15 am
Website: http://wimbasic.webs.com
Location: Netherlands
Occupation: farmer

Re: A sample programming session in VICMON

Post by wimoos »

Mike,

There is a handy table at $EDFD and another one at $D9, why not use those to determine the screenaddress ?

Regards,

Wim.
VICE; selfwritten 65asmgen; tasm; maintainer of WimBasic
User avatar
Mike
Herr VC
Posts: 4831
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample programming session in VICMON

Post by Mike »

wimoos wrote:There is a handy table at $EDFD and another one at $D9, why not use those to determine the screenaddress?
The program isn't exactly hard pressed for memory, and then I prefer a more general approach that can easily be adapted when the screen is resized. :)
User avatar
vicist
Vic 20 Afficionado
Posts: 352
Joined: Tue Oct 09, 2012 5:26 am
Location: Sheffield, UK

Re: A sample programming session in VICMON

Post by vicist »

I follow this with interest as I am keen to understand a bit more about mc on the vic. This is a great primer series and I look forward to more examples of turning basic progs into mc.
I typed all the listings carefully into 'cbm prg studio' and ran them. The birds worked perfectly but the ball goes a little haywire after it hits the first wall.
A disassembly in the vice monitor seems to compare ok with your listing.

Code: Select all

[...]
Is there anything wrong here?

(mod: disassembly listing removed as the actual type-in work is part of the exercise. The issue at hand has been sorted out.)
User avatar
Mike
Herr VC
Posts: 4831
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample programming session in VICMON

Post by Mike »

vicist wrote:Is there anything wrong here?
The instruction at $186D mistakenly uses ZP address mode, and should be ".186D LDA #$00" instead. :)
User avatar
vicist
Vic 20 Afficionado
Posts: 352
Joined: Tue Oct 09, 2012 5:26 am
Location: Sheffield, UK

Re: A sample programming session in VICMON

Post by vicist »

Thanks Mike.
I must have checked through that code at least 10 times and still missed that.

Off to the opticians me thinks. :shock:
User avatar
Mike
Herr VC
Posts: 4831
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample programming session in VICMON

Post by Mike »

vicist wrote:I must have checked through that code at least 10 times and still missed that.

Off to the opticians me thinks. :shock:
It's quite a common programming error on the 65xx, so nothing to be too concerned about. Though it's a good idea to visit the eye doctor at least every two years, especially for those with myopia. ;)
Mike wrote:[...] the program as shown has a small bug, which however only manifests on NTSC. Can you spot it?
The busy wait for "raster line equal to 1" in VIC register $9004 (see the loop at $1850) doesn't always work as intended.

...

As a matter of fact, the fastest code path through the main loop just needs around 110 cycles. The VIC register $9004 counts "pairs" of rasters with a single value, and those pairs have 130 cycles with NTSC and 142 cycles in PAL. So, when the check for "raster line = 1" succeeds, and the ball was moved one position, chances are the check succeeds for a second time - and then the ball is advanced two steps in a single frame. This is not what we want!

Now, why does it *seem* to work on PAL nonetheless, and only sometimes fail (in quite regular time intervals though) on NTSC?

It's the "jiffy timer and keyscan" IRQ we need to take into account!

The wait loop runs with disabled interrupts (see the SEI in $184F!) and, as soon as the register is not anymore 1, it "collects" a pending IRQ. On PAL, this always happens (at least after the second ball move), as the frame rate is 50 per second and the IRQ is due 60 times per second. So there's then a pending IRQ, which is executed as soon as interrupts are re-enabled with CLI. The extra time spent in the interrupt processing slows down the main loop so the VIC register indeed isn't anymore 1, when the ball has been moved and the main loop restarts.

On NTSC, the jiffy clock also runs at 60 times per second, but it is *slightly* slower than the frame frequency. Sometimes, the loop won't "collect" a pending IRQ. In that case, the main loop will be executed twice in a frame.


The solution is to add another busy loop before that, which spins while the VIC register $9005 has the value 1.


We will apply the bug fix to the saved "BALLPING" program (you *did* save it, didn't you? ;)):

Image

The original busy loop is 5 bytes in size, and we shift the program 5 bytes to a higher address, beginning with the CMP instruction of the loop. There are now two copies of that busy loop, and we "just" need to change the first BNE to a BEQ:

Image

Within the moved part of the program, thankfully there are no absolute addresses which we need to fix. Also, the (relative) jump distances of most branches are still valid (that means, those of the IF..THEN constructions) ...

Image

... but the branch of the main loop will need to be corrected to point to $183B again!

Image

Finally, we add an extra "POKE36876,0" at the end. This will silence a blip in the rare case where we manage to stop the program while the ball bounces off the border:

Image

SAVE and RUN. :mrgreen:

See you next time! :)
JavaJack
Vic 20 Drifter
Posts: 39
Joined: Fri May 30, 2014 10:19 am

Re: A sample programming session in VICMON

Post by JavaJack »

vicist wrote:

Code: Select all

.C:1800   LDA #$00
.C:1802   STA $1600
.C:1805   LDA #$1E
.C:1807   STA $1700
.C:180a   LDX #$00
.C:180c   CLC
What is ".C"? I copy/pasted this into CBM Prg Studio and it built and ran, but I'm not accustomed to seeing this in assembly source code.
User avatar
vicist
Vic 20 Afficionado
Posts: 352
Joined: Tue Oct 09, 2012 5:26 am
Location: Sheffield, UK

Re: A sample programming session in VICMON

Post by vicist »

.C is the letter that the 'vice emulator' monitor gives before the disassembly listing.

>C goes before a memory listing.
Vic20-Ian
Vic 20 Scientist
Posts: 1214
Joined: Sun Aug 24, 2008 1:58 pm

Re: A sample programming session in VICMON

Post by Vic20-Ian »

Nice thread Mike, well done.

Could you perhaps produce a short guide to using the monitor in Vice to accomplish something.

e.g. locate the code that decrements lives or alter the number of lives in Star Defence ;-)

I would like to see the special waves at 10, 15, 20 etc.
Vic20-Ian

The best things in life are Vic-20

Upgrade all new gadgets and mobiles to 3583 Bytes Free today! Ready
User avatar
Mike
Herr VC
Posts: 4831
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample programming session in VICMON

Post by Mike »

vicist wrote:.C is the letter that the 'vice emulator' monitor gives before the disassembly listing.

>C goes before a memory listing.
To be more precise: "." or ".," are the standard prompts for monitor disassembly output so these lines can easily be (re-)assembled in a full screen editor by overwriting the instruction and operands. In the monitor built into VICE, the in-between "C:" signifies that you are currently disassembling the Computer's memory. With the command "device" you can switch to one of the drives 8..11 or back to the computer with C like thus: "device 8:" or "device C:" (without the surrounding quotes). Then, the disassembly output changes to ".8: <hex dump> <instruction> [<operands>]".

Please note: despite the similar spelling, VICMON is not the monitor built into VICE. For any further questions regarding the VICE monitor, the section "Emulation and Cross Development" is a more appropriate place. Some of the techniques I already described here (and what I've already prepared for further posts) transfer well to the VICE monitor for sure and ...
Vic20-Ian wrote:Could you perhaps produce a short guide to using the monitor in Vice to accomplish something.
... if you stay tuned, you'll certainly acquire the necessary set of skills to do this on your own. On a real VIC-20 even, without the need for VICE on a PC.
20questions
Vic 20 Hobbyist
Posts: 114
Joined: Sun Mar 10, 2019 7:39 pm
Location: lodi california
Occupation: student

Re: A sample programming session in VICMON

Post by 20questions »

Mike wrote:Hi, y'all!

Let's just pretend we got a VICMON cartridge for Christmas. How about translating the well known "Coloured Birds" example of the VIC User's Manual (see p. 41) to machine language? :mrgreen:

Code: Select all

1 A$="{BLK,WHT,RED,CYN,PUR,GRN,BLU,YEL}"
2 N=INT(RND(1)*8)+1
3 B$=MID$(A$,N,1)
4 PRINTB$"{SHIFT-U,SHIFT-Q,SHIFT-I,SHIFT-J,SHIFT-Q,SHIFT-K}"
5 GOTO2
Here we go:

[...]

A Happy Boxing Day! :D

Michael
i am honestly blown away. phenomenal job

(mod: (near) full quote of OP reduced to a sensible size)
Bedroom coder=rock star
modern coder= pop star
i'd rather be a rock star than a pop start 8)
User avatar
Mike
Herr VC
Posts: 4831
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample programming session in VICMON

Post by Mike »

We continue the series with a small utility program known as 'cat' in Unix (or 'type' in DOS), which displays file contents on screen. Here's the original BASIC program:

Code: Select all

1 INPUT"FILE";N$
2 DN=PEEK(186)
3 OPEN1,DN,0,N$
4 GET#1,A$
5 PRINTA$;
6 IFST=0THEN4
7 CLOSE1
This is a slightly enhanced version of a program that's shown on page 112 of the User's Manual, and which reads in the last used device from zeropage into the variable DN and uses that device number instead of being fixed to tape use. This example shows, that BASIC shields us in a positive way from the bare metal - the translation to machine code is much more verbose, but also tells us what really happens under the hood. :)


First, we translate the INPUT prompt in line 1. The string is translated with the "H+T technique" shown in earlier posts of this thread, with a placeholder ("X") that gets changed into a carriage return code:

Image

$02B1 has just been changed from $58 to $0D. Including the CR code is necessary, otherwise the prompt would 'stick' to the end of the program start invocation ("G ..." or "SYS ..."). The string "{CR}FILE? " is placed backwards, so the print-out loop can also loop backwards (which saves us a CPX instruction), and we have register X equal to 0 on exit of the loop ...

Image

... which is truly used to clear the I/O status register ST in $90. This happens automatically, when you start a BASIC program with RUN, but here we have to do that ourselves. X=0 is also quite convenient for the file name read-in loop:

Image

The KERNAL routine CHRIN at $FFCF enables the cursor and stops our program when input is on the keyboard! Only when the user presses [RETURN] our program continues, CHRIN then reads the current line under the cursor and returns the characters. The file name is stored at a convenient place in memory, at $0140. One nice thing: CHRIN automatically knows(!) that there had been a prompt printed beforehand - we do not need to remove "FILE? " from the input!

When CHRIN returns the carriage return code, the input loop is finished - we've just found out, where the BEQ at $02C4 is supposed to branch to:

Image

Having A=13 now is quite useful to print out a carriage return, so the output of the file isn't directly appended to the file name. This is what the JSR $FFD2 instruction at $02CC is used for.

Now we set up the equivalent of the OPEN command in machine code. This involves three KERNAL routines: SETNAM ($FFBD), SETLFS ($FFBA) and OPEN ($FFC0). SETNAM expects the file name length in A, and a pointer to the file name in Y/X (Y: high byte, X: low byte), see $02CF..$02D4. SETLFS expects the logical file number in A, the device number in X and the secondary address in Y, see $02D7..$02DD. Funnily enough, line 2 of the BASIC source ends up as the single instruction LDX $BA in $02D9! With SETNAM and SETLFS being prepared, JSR $FFC0 opens the file:

Image

Input for CHRIN now is redirected to the file. This is done with the KERNAL routine CHKIN ($FFC6). KERNAL I/O redirection is half-duplex only: we can either change input or output, but not both - JSR $FFD2 still writes to screen. The small loop in $02E8..$02F5 now displays the entire file until ST<>0 ...

Image

... or the STOP key has been pressed. We've just found out, where the bail-out branch for STOP at $02F1 has to go:

Image

Unlike the BASIC original, we cleanly close the file even when the program has been prematurely stopped by user interaction. :) Before the call to CLOSE ($FFC3) at $02FC, input (and output) are restored to default with CLRCHN ($FFCC). The BRK instruction at $02FF takes us back to the monitor prompt. If you want to use "cat.prg" from BASIC, with SYS 690, replace BRK with RTS.

Image

For testing purposes, we simply let "cat" cat itself: :mrgreen:

Image

The tool can be entered in quite the same fashion with MINIMON. Just the buffer of the H command now resides at $0248 - you'll find two hits: one from the command input buffer at $020x, and one from the pattern buffer at $0248, and it's the latter we'd want to use.

Cat also works on the C64, without any changes.
Post Reply