ROM calls and other tricks

Basic and Machine Language

Moderator: Moderators

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

PRIMM

Post by Mike »

I just dug this out from the depths of my HD:

It's a small (30 bytes) and relocatable PRIMM routine. The data loader below allows the ZP address be freely chosen, where PRIMM keeps the pointer to the next char to be printed:

Code: Select all

1 AD=673:ZP=251
2 FORT=0TO29:READA
3 IFA<0THENA=ZP-A-1
4 POKEAD+T,A:NEXT
5 :
6 DATA 104,133,-1,104,133,-2,230,-1,208,2,230,-2,160,0,177
7 DATA -1,240,5,32,210,255,144,239,165,-2,72,165,-1,72,96
The routine does not bother to stack the registers. Here's the source code:

Code: Select all

.Primm
 PLA
 STA zp
 PLA
 STA zp+1
.Primm_00
 INC zp
 BNE Primm_01
 INC zp+1
.Primm_01
 LDY #0
 LDA (zp),Y
 BEQ Primm_02
 JSR $FFD2
 BCC Primm_00 
.Primm_02
 LDA zp+1
 PHA
 LDA zp
 PHA
 RTS
User avatar
Wilson
Vic 20 Enthusiast
Posts: 196
Joined: Mon Sep 28, 2009 7:19 am
Location: Brooklyn, NY

Post by Wilson »

$E5BB (58811): The KERNAL's routine to initialize the VIC registers ($9000-$900F)
$E5B5 (58805): Sets the cursor to the home position and calls $E5BB.
wimoos
Vic 20 Afficionado
Posts: 352
Joined: Tue Apr 14, 2009 8:15 am
Website: http://wimbasic.webs.com
Location: Netherlands
Occupation: farmer

Compare by using EOR

Post by wimoos »

Comparing A to a memory location is normally done using CMP. CMP leaves the value of A intact and sets the N, Z and C-flag according to the result of the comparison.

Sometimes:
- you need to compare for equality and
- you want to keep the C-flag and/or
- need to clear some memory location or register after the comparison.
- and don't care about A no more.

The way to do this is using EOR (and maybe followed STA, TAY or TAX), fast and efficiently.

Sometimes, when you DO care about A, it is even quicker to EOR again, to recover the lost value.


Example from WimBasic where the '.' is handled as replacement for current line:

Code: Select all


(A contains first character after GOTO/LIST/etc and P holds status after JSR $0073)

LA673	JSR  LA679     	 ; handle possible '.' processing
	     JMP  $C8A3     	 

LA679	EOR  #'.'	; save carry while comparing to period
	     BNE  LA690     	 
	     LDA  $39       	 
	     LDX  $3A       	 
	     CPX  #$FF      	 
	     BNE  LA689     	 
	     LDA  $3B       	 
	     LDX  $3C       	 
LA689	STA  $14       	 
	     STX  $15       	 
        JMP  $0073  ; swallow the dot    	 

LA690	EOR  #'.'      	 ; recover character and perform goto linenumber (saves doing another JSR $0079)
        JMP  $C96B     	 


Another example from WimBasic where SuperNumbers are handled:

Code: Select all


LGETVADR	EOR  #$5C   ; pound sign
	     BEQ  LGTSNADR
	     JMP  $D08B  ; this routine will do a JSR $0079 again

LGTSNADR	STA  $0D ; A holds zero here, clear these locations to signify a float variable
	     STA  $0E
	     JSR  LA46F  ; calculate address of SuperNumber
	     JMP  $D18F  ; store it like $D08B does

Regards,

Wim.
VICE; selfwritten 65asmgen; tasm; maintainer of WimBasic
wimoos
Vic 20 Afficionado
Posts: 352
Joined: Tue Apr 14, 2009 8:15 am
Website: http://wimbasic.webs.com
Location: Netherlands
Occupation: farmer

ML Optimization tips and tricks

Post by wimoos »

If you're in for ML optimization, then the following may help you. These hints are geared towards optimization in size. This generally speeds up processing the code as well, but not always with dramatic effect.
  • Remove NOP’s
    In general, remove code that does not do anything, like SEC when you know the carry is already set or loading a register with a value you know is already in there.
    Remove code that is never accessed
    Remove data that is not touched
    Change JSRs, followed by RTS into JMPs
    Rearrange code in order to remove JMPs or branches on a known condition
    Change JMPs into branches on a known condition
    Change CPX #$FF, CPX #$01, CPY #$FF, CPY #$01 followed by BEQ/BNE into INX, DEX, INY, DEY respectively, when the value in X or Y respectively is no longer necessary (carry will not be affected !)
    Change JMP or branch on a known condition over a single or double-byte instruction into a construction with BIT $zp or BIT $absolute instruction.
    A BEQ-branch taken after a CMP, CPY or CPX, will have the carry set, so no need to set it again.
    A comparison using EOR affects A and Z, but not the carry !
    A BEQ-branch taken after an EOR will have 0 in A: useful when X, Y or another memory location needs to be filled with 0 using TAX, TAY or STA.
    A subroutine that is called from only one location, may be moved inline to the calling code.
    Checking for a value of $80 or higher in A, X or Y can be done by TAX/TAY, TXA, TYA followed by BMI, provided that the value in the destination register is no longer necessary.
    Checking for a value of $00 in A, X or Y can be done by TAX/TAY, TXA, TYA followed by BEQ, provided that the value in the destination register is no longer necessary
    An ASL/ROL may help to save b7 (negative) a little longer in the carry.
    An LSR/ROR may help to save b0 a little longer in the carry
    Use the BIT instruction to test the status of a b6 or b7 in memory, instead of LDA, optionally followed by AND.
    Gather pieces of code together into subroutines.
The following ROM subroutines have the following known exit status:
  • $D391 (word to float) / $D3A2 (byte to float): V=0, Y=0
    $D7F7: V=0, C=0
    $D7EB / $D79B / $D79E: Y=0
    $D1B2 / $D1B5 / $D1B8: Y=0
    $DBA6 (float mem to acc#1): Y=0
    $C8C7: C=1
    $C8FB: Z=0
    $DDDD / $DDDF: A=0, Y=1
    $DDCD: C=1, Z=1
    $C96B: X=0, C=1
    $C660: Z=1
    $C8A3: Z=0
    $DCCC: A=contents of $61 (exp of acc#1)
    $CD9E: A,Z,N=contents of $61 (exp of acc#1), X=0, Y=$FF
    $DBC7: Y=0
    $DB0F: Y=0
There's many, many more..

Regards,

Wim.
Last edited by wimoos on Mon Dec 18, 2017 5:55 am, edited 2 times in total.
VICE; selfwritten 65asmgen; tasm; maintainer of WimBasic
metalfoot76
Vic 20 Amateur
Posts: 63
Joined: Fri May 31, 2013 10:37 pm

Re:

Post by metalfoot76 »

Mike wrote:Small routines for saving and loading blocks of memory:

SAVE memory block:

Code: Select all

SYS57809(N$),8,1:POKE193,start_lo:POKE194,start_hi
POKE780,193:POKE781,end_lo:POKE782,end_hi:SYS65496
'start' is inclusive, 'end' is exclusive. 'N$' contains the file name. Other device numbers than 8 can be used (also 1 for tape), but the additional ',1' must be kept.

LOAD memory block:

Code: Select all

SYS57809(N$),8,1:POKE780,0:SYS65493
Loads the file 'N$' to its correct address without restarting the BASIC program.

Michael

Edit: Removed unnecessary POKE147,0 instruction in LOAD memory block.
For the load block, does it work like the C64 equivalent routine, where you can force the exact load location by passing parameters through 781/782 (lb/hb)?
User avatar
Mike
Herr VC
Posts: 4888
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

SAVEing and LOADing memory blocks

Post by Mike »

metalfoot76 wrote:For the load block, does it work like the C64 equivalent routine, where you can force the exact load location by passing parameters through 781/782 (lb/hb)?
Yes. The KERNAL jump table of VIC-20 and C64 was made for exactly this purpose. Even though the routines in ROM might be placed at slightly different addresses, the jump instructions in this table point to their place.

I found though there's a different behaviour of KERNAL for tape and disk, so it might be sensible to reprise this tip a bit and include the effect of different secondary addresses:


SAVE memory block:

Code: Select all

SYS57809(N$),<device>:POKE193,<start_lo>:POKE194,<start_hi>
POKE780,193:POKE781,<end_lo>:POKE782,<end_hi>:SYS65496
or 'SYS57809(N$),<device>,0'. <start> is inclusive, <end> is exclusive, N$ contains the file name. This should actually be the standard method for saving memory blocks. For disk, it makes no difference to the following method, but on tape only this one allows to load the block back to a different address it was saved from.


SAVE memory block (and force load address on tape):

Code: Select all

SYS57809(N$),<device>,1:POKE193,<start_lo>:POKE194,<start_hi>
POKE780,193:POKE781,<end_lo>:POKE782,<end_hi>:SYS65496
For disk, effect is same as above. For tape, this forces to load back the back to the same address it was saved from, regardless what secondary address is given with the load command!


LOAD memory block ('relative' or 'relocating' load):

Code: Select all

SYS57809(N$),<device>:POKE780,0:POKE781,<start_lo>:POKE782,<start_hi>:SYS65493
or 'SYS57809(N$),<device>,0'. Load file to address <start>. This only works as intended unless the file had been saved on tape with 'force load address'. In that case, the load address on tape takes precedence!


LOAD memory block ('absolute' load):

Code: Select all

SYS57809(N$),<device>,1:POKE780,0:SYS65493
Load the file to the same address it was saved from.


Greetings,

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

Comparing two registers

Post by Mike »

Normally, comparing two registers would involve (usually a zero-page) temporary:

Code: Select all

 STA aa
 CPX aa
... which does a "CPX A" or "CXA" (as in: calculate X-A, discard the result, and just reflect the result in the flags).

If you don't want to "spoil" a ZP address, but OTOH can afford a 1 byte longer and self-modifying code, the store instruction can instead write to the operand field of a CMP/CPX/CPY #imm instruction that follows:

Code: Select all

 STA compare+1
.compare
 CPX #$00
This also takes the same number of cycles (4+2 vs. 3+3) than the zp-temporary version.
User avatar
Mike
Herr VC
Posts: 4888
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Divide 16-bit value by 8-bit constant

Post by Mike »

During a WIP, I (re-)discovered this handy routine to do an unsigned division of a 16-bit value by an 8-bit constant:

Code: Select all

 LDA #0
 LDX #16
.loop
 ASL zp
 ROL zp+1
 ROL A
 BCS subtract
 CMP #xx
 BCC skip
.subtract
 SBC #xx
 INC zp
.skip
 DEX
 BNE loop
zp and zp+1 contain the 16-bit value to be divided, and the result afterwards. The 8-bit divisor constant is encoded in the immediate fields of CMP #xx and SBC #xx. The remainder ends up in the Accumulator.

Of course, for divisors that are powers of 2, shifting/masking is shorter and faster. :)

The routine includes the bugfix suggested by J.E.E.K. for divisors >= $80 (see discussion). Constant divisors < $80 can omit the two lines "BCS subtract" and ".subtract".
User avatar
R'zo
Vic 20 Nerd
Posts: 514
Joined: Fri Jan 16, 2015 11:48 pm

Re: SAVEing and LOADing memory blocks

Post by R'zo »

Mike wrote:SAVE memory block:

Code: Select all

SYS57809(N$),<device>:POKE193,<start_lo>:POKE194,<start_hi>
POKE780,193:POKE781,<end_lo>:POKE782,<end_hi>:SYS65496
[...]
Is there any way to scratch or overwrite these files? Specifically using string input from user for the filename.
Last edited by Mike on Sat Jun 13, 2020 12:31 pm, edited 1 time in total.
Reason: quote shortened - please refer to the original post in the thread here for details
R'zo
I do not believe in obsolete...
User avatar
Mike
Herr VC
Posts: 4888
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: SAVEing and LOADing memory blocks

Post by Mike »

R'zo wrote:Is there any way to scratch or overwrite these files? Specifically using string input from user for the filename.
You'd use the standard CBM DOS commands for this, e.g.:

OPEN15,8,15,"S0:"+N$:CLOSE15

deletes file N$ on disk.

You should *not* use the save-and-replace variant of the filename, i.e. prepend "@" or "@0:", as the implementation of this function is bugged on many 15xx disk drives and can corrupt the disk contents.
User avatar
Mike
Herr VC
Posts: 4888
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

BIT #imm using the VIC-20 BASIC ROM

Post by Mike »

Hi, y'all!

As yet another follow-up to a discussion concerning the BIT instruction I wrote this small program to find the first occurence of each byte value in the VIC-20 BASIC ROM:

Code: Select all

1 DIMS(255):R=49152:FORT=0TO8191:A=PEEK(R+T):IFS(A)=0THENS(A)=R+T
2 NEXT:OPEN2,8,2,"BIT.TXT,S,W":FORA=0TO255:B=A:GOSUB4:P$="$"+H$:B=INT(S(A)/256):GOSUB4
3 P$=P$+"  $"+H$:B=S(A)-256*B:GOSUB4:P$=P$+H$:PRINT#2,P$:NEXT:CLOSE2:END
4 H$="":P=INT(B/16):GOSUB5:P=B-16*P
5 H$=H$+CHR$(48+P-7*(P>9)):RETURN
This now allows to rewrite a BIT #imm instruction (which is not available on the NMOS 6502!) as BIT ABS instruction pointing into the BASIC ROM. The BIT instruction sets the Z flag corresponding to the AND result (and N, V directly from bits 7 and 6 of the tested address), but leaves the accumulator unaltered, which allows to perform several bit tests on a given value in A without the necessity to restore A in-between!

The most interesting values are probably the powers of 2, they're given here:

Code: Select all

$01  $C390
$02  $C39B
$04  $C3E7
$08  $C3B9
$10  $C338
$20  $C1A1
$40  $CB5A
$80  $C018
The whole list comprises:

Code: Select all

$00  $C058
$01  $C390
$02  $C39B
$03  $C3A0
$04  $C3E7
$05  $C01A
$06  $C42D
$07  $C3A8
$08  $C3B9
$09  $C414
$0A  $C377
$0B  $C06C
$0C  $C634
$0D  $C06E
$0E  $C35C
$0F  $C563
$10  $C338
$11  $C08A
$12  $C3B3
$13  $C44D
$14  $C50E
$15  $C09C
$16  $C67D
$17  $C572
$18  $C3B1
$19  $C67B
$1A  $C85A
$1B  $C9C5
$1C  $C024
$1D  $C010
$1E  $C35E
$1F  $C620
$20  $C1A1
$21  $C395
$22  $C3C5
$23  $C03A
$24  $C360
$25  $C33A
$26  $C048
$27  $C022
$28  $C40B
$29  $C459
$2A  $C087
$2B  $C534
$2C  $C030
$2D  $C4B0
$2E  $C02C
$2F  $C670
$30  $C00C
$31  $C3BC
$32  $C3BE
$33  $C40F
$34  $C409
$35  $C33C
$36  $C5D2
$37  $C07E
$38  $C3BF
$39  $C052
$3A  $C02A
$3B  $C33E
$3C  $C5A9
$3D  $C7BB
$3E  $C3FD
$3F  $C59D
$40  $CB5A
$41  $C008
$42  $C005
$43  $C004
$44  $C0A8
$45  $C09E
$46  $C092
$47  $C0C1
$48  $C136
$49  $C00A
$4A  $C02E
$4B  $C0FC
$4C  $C0BE
$4D  $C006
$4E  $C09F
$4F  $C0A2
$50  $C08F
$51  $C160
$52  $C034
$53  $C009
$54  $C0AA
$55  $C0AF
$56  $C040
$57  $C0E5
$58  $C056
$59  $C118
$5A  $C098
$5B  $C3C8
$5C  $DBC8
$5D  $C044
$5E  $CAB4
$5F  $C3C3
$60  $C3B7
$61  $C036
$62  $C77D
$63  $CA17
$64  $C032
$65  $C072
$66  $C779
$67  $C002
$68  $C068
$69  $C081
$6A  $C344
$6B  $C49D
$6C  $C437
$6D  $CE78
$6E  $CE7B
$6F  $CA5E
$70  $C020
$71  $C05E
$72  $C346
$73  $C48B
$74  $C715
$75  $CA57
$76  $C475
$77  $CDE0
$78  $C000
$79  $C080
$7A  $C04E
$7B  $C086
$7C  $C070
$7D  $C05A
$7E  $CA2A
$7F  $C03C
$80  $C018
$81  $C393
$82  $C026
$83  $C362
$84  $C3BD
$85  $C046
$86  $C486
$87  $CAC0
$88  $C3EC
$89  $C92F
$8A  $C3B0
$8B  $C076
$8C  $C514
$8D  $C511
$8E  $C65A
$8F  $CFAE
$90  $C34A
$91  $C3EA
$92  $D3EB
$93  $D377
$94  $C060
$95  $C422
$96  $DF1C
$97  $C881
$98  $C3CD
$99  $C5A1
$9A  $C683
$9B  $C042
$9C  $DF31
$9D  $C34C
$9E  $C05C
$9F  $C01E
$A0  $C389
$A1  $CEAC
$A2  $C413
$A3  $C0B1
$A4  $C014
$A5  $C396
$A6  $C57C
$A7  $C933
$A8  $C12C
$A9  $C44A
$AA  $C142
$AB  $C140
$AC  $C32A
$AD  $C074
$AE  $C7EB
$AF  $C143
$B0  $C3D7
$B1  $C06A
$B2  $C038
$B3  $C096
$B4  $C603
$B5  $C32C
$B6  $D51B
$B7  $CBDE
$B8  $C50B
$B9  $C522
$BA  $C04A
$BB  $C93F
$BC  $C14C
$BD  $C14B
$BE  $C016
$BF  $C9C8
$C0  $C5BE
$C1  $C0AB
$C2  $C0D5
$C3  $C04C
$C4  $C0A0
$C5  $C0D0
$C6  $C043
$C7  $C00F
$C8  $C00D
$C9  $C01D
$CA  $C03D
$CB  $C015
$CC  $C01B
$CD  $C011
$CE  $C09A
$CF  $C091
$D0  $C019
$D1  $C028
$D2  $C0A3
$D3  $C039
$D4  $C073
$D5  $C354
$D6  $C079
$D7  $C071
$D8  $C031
$D9  $C063
$DA  $C088
$DB  $C08B
$DC  $C053
$DD  $C3A4
$DE  $C144
$DF  $C05F
$E0  $C061
$E1  $C033
$E2  $C067
$E3  $C001
$E4  $C003
$E5  $C093
$E6  $C4E6
$E7  $C661
$E8  $C090
$E9  $C5D4
$EA  $C062
$EB  $CFDC
$EC  $C078
$ED  $C064
$EE  $C968
$EF  $C95D
$F0  $C334
$F1  $C4BA
$F2  $C3F9
$F3  $C925
$F4  $C461
$F5  $C5C0
$F6  $D0B9
$F7  $C012
$F8  $C529
$F9  $C3EE
$FA  $C41B
$FB  $C548
$FC  $C523
$FD  $C60A
$FE  $C512
$FF  $C336
$00 is probably not that useful, but has been included for completeness. :wink:

PAL and NTSC VIC-20s share the same BASIC ROM, so this method works for both types of VIC-20s. The list is also valid for the bug-fixed BASIC ROM that I devised a few years ago (see here).

Greetings,

Michael

(mod: follow-up discussion split off into own thread.)
User avatar
J.E.E.K.
Vic 20 Drifter
Posts: 23
Joined: Wed Jan 25, 2017 12:31 pm
Website: http://klasek.at/8bit
Location: AT

Re: ML Optimization tips and tricks

Post by J.E.E.K. »

wimoos wrote: Wed Dec 12, 2012 4:27 amRemove NOP’s
[...]
This list could be extended by
  • Hold flag in bit 7 of a location, so you can check the flag by "BIT flag" followed by "BMI/BPL destination" keeping A untouched, clear the flag with "LSR flag". If carry is set by an condition before, use "ROR flag" to set the flag.
  • Operations without temporarily save the accumulator to memory:
    • To subtract the value in A from location's value ( (mem)-A ) use EOR #$FF; SEC; ADC mem (negate A with two's complement and simply add).
    • Merge some bits from A into a memory location using EOR mem; AND #mask_bits_taken_from_acc; EOR mem; STA mem.
Last edited by Mike on Sat Oct 24, 2020 2:34 pm, edited 1 time in total.
Reason: (near) full-quote shortened. Please refer to the OP by following the blue arrow link.
User avatar
bjonte
Vic 20 Hobbyist
Posts: 110
Joined: Sun Jan 22, 2017 5:47 am
Location: Gothenburg

Handy toggle

Post by bjonte »

Sometimes you want to toggle a memory location between two values. This can be done using EOR. The value to use for EOR can be constructed with eor in the assembler (operator ^ in this example).

Code: Select all

// constants to toggle between
const value1 = 55
const value2 = 170

// set initial value
lda #value1
sta memaddr

// toggle
lda memaddr
eor #value1 ^ value2
sta memaddr
User avatar
MrSterlingBS
Vic 20 Enthusiast
Posts: 195
Joined: Tue Jan 31, 2023 2:56 am
Location: Germany,Braunschweig

Re: ROM calls and other tricks

Post by MrSterlingBS »

Sometimes I used

STY $ZP
… some code
LDY $ZP

instead of
TYA
PHA
… some code
PLA
TAY

A little bit faster and shorter code if you have some ZP registers free.
wimoos
Vic 20 Afficionado
Posts: 352
Joined: Tue Apr 14, 2009 8:15 am
Website: http://wimbasic.webs.com
Location: Netherlands
Occupation: farmer

Re: Handy toggle

Post by wimoos »

bjonte wrote: Mon Jul 04, 2022 2:34 pm Sometimes you want to toggle a memory location between two values. This can be done using EOR. The value to use for EOR can be constructed with eor in the assembler (operator ^ in this example)

To elaborate on this, consider the following code snippet that I used in WimBasic:

Code: Select all

	EOR  #$82      	 ; token NEXT ?
	BEQ  LA3D6     	 
	EOR  #$9C ^ $82  ; token CLR
	BEQ  LA3F3     	 
	EOR  #$8E ^ $9C  ; token RETURN
	BEQ  LA46C     	 
	JMP  $CF08 ; ?SYNTAX ERROR
;
LA3D6	TAY ; clear Y
		...
		
LA46C	
The code is entered with a tokenized value in A, that needs to be checked against three possible values. The code, following the selection benefits from having a value of zero in A (or could benefit from the carry-flag not having been changed).

Regards,

Wim.
VICE; selfwritten 65asmgen; tasm; maintainer of WimBasic
Post Reply