Page 2 of 2

Posted: Mon Jun 02, 2008 12:48 am
by Mike
A replacement for INPUT#, especially useable for reading records in REL files. Here's an example.



Code: Select all


; 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 ...
 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
 JSR $E10F       ; CHRIN
 CMP #$0D
 BEQ GetLine_01  ; exit loop, if CR
 STA ($37),Y     ; store into buffer
 CPY #$FF        ; maximum length reached?
 BNE GetLine_00  ; no
 JSR $FFCC       ; restore I/O
 JMP $D3A2       ; return string length in FAC#1
 JMP $D248       ; flag error

; ... and is completed with:
 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
 LDA ($14),Y
 PHA             ; push low byte
 LDA ($14),Y
 STA $15         ; store high byte
 STA $14         ; store low byte
 STA $FE         ; store length
 LDY #$00
 BEQ Assign_01
 LDA ($37),Y     ; copy from buffer to string
 STA ($14),Y
 BNE Assign_00
 RTS             ; scary, but it works.

Posted: Wed Apr 15, 2009 6:34 pm
by Ghislain
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: ... 19228.html

(which refers to BC5B in the C64 BASIC ROM, but the VIC-20 location for this is DC5B)

Coding small time-delays

Posted: Mon Oct 19, 2009 3:16 am
by Mike
This small sub-routine may be useful for coding a small time delay up to 4 seconds:

Code: Select all

 LDA $A2
 LDA $A2
 CMP #xx
 BCC Delay_00

... 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.

CHRIN patch sub-routine

Posted: Thu Dec 31, 2009 3:06 pm
by Mike
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

 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


Posted: Wed May 25, 2011 2:39 pm
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
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

 STA zp
 STA zp+1
 INC zp
 BNE Primm_01
 INC zp+1
 LDY #0
 LDA (zp),Y
 BEQ Primm_02
 BCC Primm_00
 LDA zp+1
 LDA zp

Posted: Fri Aug 19, 2011 10:59 pm
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.

Compare by using EOR

Posted: Mon Oct 22, 2012 7:26 am
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.

- 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
        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



ML Optimization tips and tricks

Posted: Wed Dec 12, 2012 4:27 am
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..




Posted: Sat Mar 22, 2014 8:05 pm
by metalfoot76
Mike wrote:Small routines for saving and loading blocks of memory:

SAVE memory block:

Code: Select all


'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


Loads the file 'N$' to its correct address without restarting the BASIC program.


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)?

SAVEing and LOADing memory blocks

Posted: Sun Mar 23, 2014 4:39 am
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


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


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


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


Load the file to the same address it was saved from.



Divide 16-bit value by 8-bit constant

Posted: Wed Jan 03, 2018 6:36 am
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
 ASL zp
 ROL zp+1
 CMP #xx
 BCC skip
 SBC #xx
 INC zp
 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. :)