ROM calls and other tricks

Basic and Machine Language

Moderator: Moderators

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

Postby Mike » Mon Jun 02, 2008 12:48 am

A replacement for INPUT#, especially useable for reading records in REL files. Here's an example.

Greetings,

Michael

Code: Select all

*=$033c

; REL file READ RECORD written 2008 by Michael Kircher

; writing a record into a REL file with PRINT# is okay, but reading it
; back with INPUT# is a bit of a nuisance, since INPUT# is restricted to
; 88 (on the 64: 80) chars, and also barfs at anything unexpected, like
; commas, quotes, and colons.

; this pair of routines reads in a complete record up to (but not including)
; the terminating CR, and assigns it to a string, without any those problems.

; - if the USR vector is not at 828, lower RAMTOP with POKE56,PEEK(56)-1:CLR
; - prepare Z$ once: Z$="I'M 17 CHARS LONG":Z$=Z$+Z$+Z$+Z$+Z$:Z$=Z$+Z$+Z$
; - POKE the following code to address 828
; - set up the USR vector with: POKE1,60:POKE2,3

; INPUT#channel,R$ is now replaced by: R$=LEFT$(Z$,USR(channel)):SYS873,R$

; which calls ...
.GetLine
 JSR $D7F7       ; FAC#1 to Integer in $14/$15
 LDA $15
 BNE GetLine_02  ; sec. address >= 256 -> error
 LDX $14
 JSR $E11B       ; CHKIN for channel in $14
 LDY #$00
 STY $FE         ; reset buffer pointer
.GetLine_00
 JSR $E10F       ; CHRIN
 CMP #$0D
 BEQ GetLine_01  ; exit loop, if CR
 LDY $FE
 STA ($37),Y     ; store into buffer
 INY
 STY $FE
 CPY #$FF        ; maximum length reached?
 BNE GetLine_00  ; no
.GetLine_01
 JSR $FFCC       ; restore I/O
 LDY $FE
 JMP $D3A2       ; return string length in FAC#1
.GetLine_02
 JMP $D248       ; flag error

; ... and is completed with:
.Assign
 JSR $CEFD       ; check for ','
 JSR $D08B       ; get variable address
 STA $14         ; which is the descriptor
 STY $15         ; for a string
 LDY #$00
 LDA ($14),Y
 PHA             ; push length
 INY
 LDA ($14),Y
 PHA             ; push low byte
 INY
 LDA ($14),Y
 STA $15         ; store high byte
 PLA
 STA $14         ; store low byte
 PLA
 STA $FE         ; store length
 LDY #$00
.Assign_00
 CPY $FE
 BEQ Assign_01
 LDA ($37),Y     ; copy from buffer to string
 STA ($14),Y
 INY
 BNE Assign_00
.Assign_01
 RTS             ; scary, but it works.

User avatar
Ghislain
Realms of Quest
Posts: 1064
Joined: Sun Aug 08, 2004 12:54 am

Postby Ghislain » Wed Apr 15, 2009 6:34 pm

To do < = > you can do the following:

JSR $DC5B ;compare floating point pointed to by A:Y with FAC1; if FAC1=mem then A=0, if FAC1<mem then A=255, if FAC1>mem then A=1

Source:

http://www.experts-exchange.com/Program ... 19228.html

(which refers to BC5B in the C64 BASIC ROM, but the VIC-20 location for this is DC5B)
"A slave is one who waits for someone to come and free him." -- Ezra Pound

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

Coding small time-delays

Postby Mike » Mon Oct 19, 2009 3:16 am

This small sub-routine may be useful for coding a small time delay up to 4 seconds:

Code: Select all

.Delay
 LDA $A2
 STA $FB
.Delay_00
 SEC
 LDA $A2
 SBC $FB
 CMP #xx
 BCC Delay_00
 RTS

... where xx gives the delay in 1/60 seconds, and $FB could also be another free (ZP) address. There's a small inaccuracy of up to 1/60 second, depending on whether the jiffy-clock has been updated right before, or is after the first LDA $A2 - but this shouldn't be important, if a delay of half a second (xx=30) or more is specified.

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

CHRIN patch sub-routine

Postby Mike » Thu Dec 31, 2009 3:06 pm

Normally, CHRIN is supposed to set the C flag when that operation didn't succeed. However, I found with serial transfers the routine would always return with C cleared. But at least the status byte ST at $90 is set correctly, and I designed this patch sub-routine to get the desired behaviour:

Code: Select all

.GetByte
 JSR $FFCF ; call CHRIN
 PHA
 LDA $90
 CMP #$01  ; set C, if ST >= 1 (i.e. ST != 0)
 PLA       ; restore Accu, keep C flag, and set Z flag, if A=0
 RTS

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

PRIMM

Postby Mike » Wed May 25, 2011 2:39 pm

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: 156
Joined: Mon Sep 28, 2009 7:19 am

Postby Wilson » Fri Aug 19, 2011 10:59 pm

$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 Devotee
Posts: 237
Joined: Tue Apr 14, 2009 8:15 am
Website: http://wimbasic.webs.com
Location: Netherlands
Occupation: farmer

Compare by using EOR

Postby wimoos » Mon Oct 22, 2012 7:26 am

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.
PAL, two-prong VIC20 on 65C02 with 3k RAM expansion internal, 32k NOVRAM expansion external, DS1307 RTC and S-Video mod; 64HDD in FreeDOS on a thin-client; selfwritten 65asmgen; tasm; maintainer of WimBasic

wimoos
Vic 20 Devotee
Posts: 237
Joined: Tue Apr 14, 2009 8:15 am
Website: http://wimbasic.webs.com
Location: Netherlands
Occupation: farmer

ML Optimization tips and tricks

Postby wimoos » Wed Dec 12, 2012 4:27 am

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
    $DC1B: A=contents of $61 (exp of acc#1)
    $DCCC: A=contents of $61 (exp of acc#1)
    $CD9E: A,Z,N=contents of $61 (exp of acc#1), X=0, Y=$FF


There's many, many more..

Regards,

Wim.
PAL, two-prong VIC20 on 65C02 with 3k RAM expansion internal, 32k NOVRAM expansion external, DS1307 RTC and S-Video mod; 64HDD in FreeDOS on a thin-client; selfwritten 65asmgen; tasm; maintainer of WimBasic

metalfoot76
Vic 20 Amateur
Posts: 63
Joined: Fri May 31, 2013 10:37 pm

Re:

Postby metalfoot76 » Sat Mar 22, 2014 8:05 pm

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: 2951
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

SAVEing and LOADing memory blocks

Postby Mike » Sun Mar 23, 2014 4:39 am

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


Return to “Programming”

Who is online

Users browsing this forum: No registered users and 1 guest