Neolithic Tiny Basic

Programming the 6502 microprocessor and its relatives in assembly and other languages.
Post Reply
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Neolithic Tiny Basic

Post by barnacle »

The steps that led to the C version of NTB are documented in the Crenshaw thread here: viewtopic.php?f=2&t=8175
This thread is to document the conversion of that C code to a generic 65c02 build, so the details don't get lost. I'll be posting snippets of code and eventually a complete listing here; please feel free to jump in if I'm doing anything silly or looking lost. I believe that once I have a basic structure it should drop together pretty easily.

The code won't be the fastest, but I'm aiming for simplicity; it can be optimised later.

The assembler I am using is as65 from http://www.kingswood-consulting.co.uk/assemblers/ and I invoke it: as65 tiny.asm -h0 -ltiny.lst -s2 -x to produce a listing, which I find handy to check what code is actually being generated; as65 has an automatic internal optimiser which for example will replace 'jmp' with 'bra' if the target is in range, and a few other things.

API
Because I want to copy the C program, I feel it will be easiest to use a c-style API for calling functiond. It makes heavy use of the stack, but I don't think too heavy, so I don't have a separate stack to manage (at the moment; that's always subject to change)
  • Functions can take one or more eight or sixteen bit arguments, and return zero or one eight or sixteen bit results in A or Y:A.
  • Function arguments are counted from the left:

    Code: Select all

    result = divide (first, second)    ; divide first by second
    
  • Sixteen bit arguments are passed in Y:A and on the stack if necessary; if there is more than one argument they are pushed on the stack from the left, with the final argument being placed in Y:A.
  • All functions must preserve the X register; it is used as a stack frame pointer for the calling routine. Other registers may be thumped as required.
  • Local variables are pushed as required to the stack, generally as sixteen bit words.
  • The X register is used as a frame pointer; constant offsets are provided for convenient reference
A couple of short examples:

Code: Select all

c16c :                  libadd16:				; add TOS to y:a
c16c : da               	phx					; save caller's stack frame
c16d : ba               	tsx					; set local frame pointer
c16e : 8556             	sta maths1			; we hold the high byte in y
c170 : 18               	clc
c171 : 7d0401           	adc CALLER_LO,x
c174 : 8556             	sta maths1
c176 : 98               	tya
c177 : 7d0501           	adc CALLER_HI,x
c17a : a8               	tay					; high result back in y
c17b : a556             	lda maths1			; collect low result
c17d : fa               	plx					; clean the stack
c17e : 60               	rts
                        
c17f :                  libsub16:				; subtract y:a from TOS
c17f : da               	phx
c180 : ba               	tsx
c181 : 5a               	phy
c182 : 48               	pha					; build stack frame
c183 : 38               	sec
c184 : bd0401           	lda CALLER_LO,x		; we need to subtract LOCAL from CAL
c187 : f5ff             	sbc LOCAL_LO,x		
c189 : 95ff             	sta LOCAL_LO,x
c18b : bd0501           	lda CALLER_HI,x
c18e : fd0001           	sbc LOCAL_HI,x
c191 : 9d0001           	sta LOCAL_HI,x
c194 : 68               	pla					; load y:a and restore stack
c195 : 7a               	ply
c196 : fa               	plx
c197 : 60               	rts
As you can see from these examples, the stack looks like this once we're in them:

Code: Select all

    second argument (2)
    return address  (2)
    x register      (1)
    local argument  (2)
stack pointer here --->
and the convenience definitions

Code: Select all

00ff =                  LOCAL_LO	equ 0xff
0100 =                  LOCAL_HI	equ 0x100
0104 =                  CALLER_LO	equ 0x104
0105 =                  CALLER_HI	equ 0x105

provide an x offset to the argument. Note that the function can see (and directly alter) variables in the caller's stack; I strongly caution against allowing it to do so! Further local variables can be accessed at 0xfe,x, 0xfd,x and so on. I wish WDC had included a stack-pointer-relative addressing mode!

Which brings me to the first question: does an zero page x-indexed memory wrap around, or go where I expect it to? I note that the optimiser has substituted a zero page instruction in the examples above...

Neil :mrgreen:
John West
Posts: 383
Joined: 03 Sep 2002

Re: Neolithic Tiny Basic

Post by John West »

barnacle wrote:
Which brings me to the first question: does an zero page x-indexed memory wrap around, or go where I expect it to?
Zero page indexing wraps:

Code: Select all

LDX #$FF
LDA $80, X
will read from $007f, not $017f.
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Thanks, that was a good catch; I need to tell the assembler not to optimise, then.

It's very inconvenient of it :mrgreen:

Neil
User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Neolithic Tiny Basic

Post by barrym95838 »

When Woz wanted to use "negative" stack indexing with X he had to "hard-code" abs,X opcodes, because it's not generally easy to convince most assemblers to avoid zp,X when they see a base address less than $0100.

If you have room in zero-page, my first recommendation in the optimization process would be to move the formal argument stack there and keep the call stack in page one clean for addresses and local temporaries.

Example (untested):

Code: Select all

libsub16:             ; subtract y:a from TOS (27 bytes)
    phx
    tsx
    phy
    pha               ; build stack frame
    sec
    lda CALLER_LO,x   ; we need to subtract LOCAL from CAL
    sbc LOCAL_LO,x      
    sta LOCAL_LO,x
    lda CALLER_HI,x
    sbc LOCAL_HI,x
    sta LOCAL_HI,x
    pla               ; load y:a and restore stack
    ply
    plx
    rts
becomes

Code: Select all


libsub16:             ; subtract y:a from TOS (17 bytes)
    sec               ; we're going to add the two's complement
    pha               ; save a (strictly necessary?)
    eor #$ff
    adc 0,x           ; x points to top of zp argument stack
    sta 0,x
    tya
    eor #$ff
    adc 1,x
    sta 1,x
    pla               ; restore a (strictly necessary?)
    rts
More trickery would be to place libsub16 directly before libadd16. libsub16 would set carry and one's complement Y:A, then BRA or employ a naked BIT opcode to enter libadd16 after the CLC. This optimization assumes that you don't need to preserve Y:A, in which case you don't need as many PHA/PLA pairs, saving several more bytes:

Code: Select all

libsub16:             ; subtract y:a from TOS (10 bytes)
    sec               ; we're going to add the two's complement
    pha               ; save low byte
    tya
    eor #$ff          ; complement high byte
    tay
    pla               ; restore low byte
    eor #$ff          ; complement low byte
    dcb $89           ; naked BIT# opcode to skip over the clc
libadd16:             ; add y:a to TOS (11 bytes)
    clc
    adc 0,x
    sta 0,x
    tya
    adc 1,x
    sta 1,x
    rts
Last edited by barrym95838 on Sun Nov 10, 2024 7:25 pm, edited 3 times in total.
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

It's a thought, Mike, but I need to think about how much stack I'm going to need. For the moment, I think I'll hard-code the 16-bit opcode where necessary (i.e. everywhere there's a local variable) - this assembler doesn't appear to have a word override. I'd like to try and keep the function API constant for everything, if possible.

There are obvious places where one can select the right order of functions to save bytes, and as you point out the subtract routine can be shorter, but... let's see. (A thought: since I need an invert routine to make divide work, I can use that to invert the subtrahend and use add instead...)

Neil

edit: aka first make it work, then make it quick/small :mrgreen:
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Ah, this ought to work; the subtract routine is now three bytes long...

Code: Select all

                        ;negate: change the sign of an int16_t by inverting th
                        ; adding one. The variable arrives and returns in Y:A
c174 :                  negate:	
c174 : 18               	clc
c175 : 49ff             	eor #0xff
c177 : 6901             	adc #1
c179 : 48               	pha
c17a : 98               	tya
c17b : 49ff             	eor #0xff
c17d : 6900             	adc #0
c17f : a8               	tay
c180 : 68               	pla
c181 : 60               	rts
                        	
                        ;libsub16: minuend pushed to stack, subtrahend in y:a,
                        ; we subtract by negating y:a and then adding it to TO
c182 :                  libsub16:				; subtract y:a from TOS
c182 : 2074c1           	jsr negate
                        	
                        ; libadd16: augand pushed to stack, addend in y:a, => 
c185 :                  libadd16:				; add TOS to y:a
c185 : da               	phx					; save caller's stack frame
c186 : ba               	tsx					; set local frame pointer
c187 : 8556             	sta maths1			; we hold the high byte in y
c189 : 18               	clc
c18a : 7d0401           	adc CALLER_LO,x
c18d : 8556             	sta maths1
c18f : 98               	tya
c190 : 7d0501           	adc CALLER_HI,x
c193 : a8               	tay					; high result back in y
c194 : a556             	lda maths1			; collect low result
c196 : fa               	plx					; clean the stack
c197 : 60               	rts
Neil
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Another chunk of code; divide the TOS by the int16_t y:a; result in y:a and remainder in zero page 'remainder' (I need the remainder later).
Also uses four other zero page bytes (as two words): dividend (that which will be divided) and divisor (that by which we will divide). Those bytes are shared with the other arithmetic routines but given different names.

Code: Select all

                        ;libdiv16: dividend on stack, divisor in y:a, result i
                        ; in remainder
                        ; NOTE: if either (or both) parameter is negative, we 
                        ; we do the division; if just one is negative, we inve
c24a :                  libdiv16:
c24a : da               	phx					; caller stack frame
c24b : ba               	tsx	
c24c : 645c             	stz sign			; assume both arguments are positive
c24e : 8556             	sta divisor			; save the low byte
c250 : 98               	tya					; but wait: is it negative? 
c251 : 100b             	bpl ldv_01			; positive, so skip
c253 : a556             		lda divisor		; else negate it; y still holds high by
c255 : 20f1c1           		jsr negate		
c258 : 8556             		sta divisor		; save negated low byte
c25a : a980             		lda #0x80
c25c : 855c             		sta sign		; flag that we've inverted
c25e :                  ldv_01:
c25e : 8457             	sty divisor+1		; save the divisor
c260 : bd0401           	lda CALLER_LO,x
c263 : 8558             	sta dividend
c265 : bc0501           	ldy CALLER_HI,x
c268 : 98               	tya					; check for sign of this argument
c269 : 100d             	bpl ldv_02
c26b : a558             		lda dividend
c26d : 20f1c1           		jsr negate
c270 : 8558             		sta dividend	; negated low byte
c272 : a55c             		lda sign
c274 : 4980             		eor #0x80		; sign is now 0x80 if just one arg is neg
c276 : 855c             		sta sign		; but 0x00 if both were same sign
c278 :                  ldv_02:
c278 : 8459             	sty dividend+1		; and dividend
                        	
                        	; core routine taken from https://www.llx.com/Neil/a2
                        	; divides dividend by divisor, leaves result in divid
                        	; remainder in remainder
                        	; dividend and divisor are both positive
c27a : 645a             	stz remainder
c27c : 645b             	stz remainder+1		; remainder initialised to zero
c27e : a210             	ldx #16				; bit counter
c280 :                  ldv_1:
c280 : 0658             		asl dividend
c282 : 2659             		rol	dividend+1
c284 : 265a             		rol remainder
c286 : 265b             		rol remainder+1		; move high bit of dividend into re
c288 : a55a             		lda remainder
c28a : 38               		sec					
c28b : e556             		sbc divisor
c28d : a8               		tay					; trial subtraction
c28e : a55b             		lda remainder+1
c290 : e557             		sbc divisor+1
c292 : 9006             		bcc ldv_2
c294 : 855b             			sta remainder+1		; and if it worked, save it
c296 : 845a             			sty remainder
c298 : e658             			inc dividend		; slide the result in as the low bit 
                        								; of dividend
c29a :                  ldv_2:
c29a : ca               		dex			
c29b : d0e3             		bne ldv_1			; and back sixteen times
c29d : a558             	lda dividend
c29f : a459             	ldy dividend+1		; return the result in y:a
c2a1 : a65c             	ldx sign			; check the sign of the result
c2a3 : 1003             	bpl ldv_exit
c2a5 : 20f1c1           		jsr negate
c2a8 :                  ldv_exit:
c2a8 : fa               	plx					; restore stack frame
c2a9 : 60               	rts
I'm not currently able to test this (my test board is for some reason having hiccups, and I don't know whether that's because I've broken it or I've just forgotten a step in loading it) and I can't find an online emulator that can do 65c02. And of course my own 6502 emulator from five years back is NMOS only...

Neil
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

And a partial (unfinished) chunk: to output y:a as a signed decimal string. This uses a block of seven bytes in zero page; the first holds flags to indicate whether the number is negative, whether it is right or left justified, and the current digit position. Digits slot in from numbuf[5] to numbuf[1] with a terminating /0 at numbuf[6].

Code: Select all

c160 :                  putn:					; output y:a as a string with leading zeroes
                        						; suppressed. Right justified if carry set
                        						; The number is generated by dividing the arg
                        						; by ten repeatedly, filling in numbuf from the
                        						; right as we go, and finally adding the minus
                        						; sign as required
c160 : da               	phx					; save caller stack frame
c161 : 5a               	phy
c162 : 48               	pha					; save arg as local variable
c163 : a906             	lda #6
c165 : 855d             	sta numbuf			; use buffer[0] for args and char positi
c167 : a900             	lda #0
c169 : 8563             	sta numbuf+6		; mark the end of the string
c16b : 9006             	bcc putn_1
c16d : a55d             		lda numbuf
c16f : 0940             		ora #0x40
c171 : e65d             		inc numbuf		; bit 6 set = right justify
c173 :                  putn_1:
c173 : aa               	tax
c174 : 98               	tya					; check high byte of arg
c175 : 100e             	bpl putn_2			; if arg is negative
c177 : a55d             		lda numbuf
c179 : 0980             		ora #0x80		
c17b : 855d             		sta numbuf		; bit 7 set = negative number
c17d : 8a               		txa
c17e : 20f1c1           		jsr negate		; and make the arg positive
c181 : fa               		plx
c182 : fa               		plx
c183 : 5a               		phy
c184 : 48               		pha				; replace arg on stack with positive version
c185 :                  putn_2:
c185 : a000             		ldy #0
c187 : a90a             		lda #10
c189 : 204ac2           		jsr libdiv16	; we want to divide the argument by ten
c18c : fa               		plx
c18d : fa               		plx
c18e : 5a               		phy
c18f : 48               		pha				; replace arg by arg/10
c190 : a55a             		lda remainder	; we only care about the low byte
c192 : 0930             		ora #'0'		; make it ascii
c194 : a8               		tay				; keep it handy
c195 : a55d             		lda numbuf		; get the char count
c197 : 3a               		dec a
c198 : 855d             		sta numbuf		; decrement the char position
c19a : 2907             		and #0x7		; isolate the count bits
c19c : aa               		tax
c19d : 98               		tya				; get the digit back
c19e : 955d             		sta numbuf,x	; put it in the buffer from the right
c1a0 : e002             		cpx #2			; we count 6-5-4-3-2
c1a2 : d0e1             		bne putn_2		; so loop if we're not there yet
                        	; now we have to consider the leading zeros, the left
                        	; alignment, and the sign of the output
Neil
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Testing basic arithmetic, now I've got enough of a framework to see what's happening... obviously lots more testing required, but so far we have proved that

Code: Select all

6 + 3 = 9
6 - 3 = 3
6 * 3 = 18 (0x12)
Haven't quite dared do the divide yet :mrgreen:

Neil

edit: this is running on my Neo version of Grant Searle's minimal design, which means I have to pop an eeprom between it and a homebrew programmer and back for each iteration; it's getting a bit tedious but for some reason the Romless with the LV5183 chips doesn't want to talk to me at the moment and I have no idea why.

edit 2:

Code: Select all

6 / 3 = 2
Amazing!
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic - putn

Post by barnacle »

We progress: this is the output from (lots of) calls to puts, which outputs a signed 16-bit integer in decimal, either left or right justified; the latter in a six-wide cell. Both formats include a trailing space, to match the C version.

Code: Select all

Neolithic Tiny Basic                                                
                                                                    
1                                                                   
-1                                                                  
12                                                                  
-12                                                                 
123                                                                 
-123                                                                
1234                                                                
-1234                                                               
12345                                                               
-12345                                                              
     1                                                              
    -1                                                              
    12                                                              
   -12                                                              
   123                                                              
  -123                                                              
  1234                                                              
 -1234                                                              
 12345                                                              
-12345                                                              
The code itself is reasonably compact - 119 bytes, perhaps that can be improved - and relies on the division and negate routines in the arithmetic library (I'll post that soon).

Code: Select all

                        ;-----------------------------------------------------
c27b :                  putn:					; output y:a as a string with leading zeroes
                        						; suppressed. Right justified if carry set.
                        						; The number is generated by dividing the arg
                        						; by ten repeatedly, filling in numbuf from the
                        						; right as we go, and finally adding the minus
                        						; sign as required.
                        						; Irrespective of justification, we follow the 
                        						; number with a space; we always print at least
                        						; one digit.
c27b : da               	phx					; save caller stack frame
c27c : 5a               	phy
c27d : 48               	pha					; save arg as local variable
c27e : aa               	tax					; and temporarily in x
c27f : 6464             	stz numbuf+7		; mark the end of the string
c281 : a920             	lda #' '
c283 : 8563             	sta numbuf+6
c285 : a906             	lda #6
c287 : 9002             	bcc putn_1
c289 : 0980             		ora #0x80		; right justified
c28b :                  putn_1:
c28b : 855d             	sta numbuf			; use buffer[0] for args and char positi
c28d : 98               	tya					; check high byte of arg
c28e : 100e             	bpl putn_2			; if arg is negative
c290 : a55d             		lda numbuf
c292 : 0940             		ora #0x40		
c294 : 855d             		sta numbuf		; bit 6 set = negative number
c296 : 8a               		txa
c297 : 2036c3           		jsr negate		; and make the arg positive
c29a : fa               		plx
c29b : fa               		plx
c29c : 5a               		phy
c29d : 48               		pha				; replace arg on stack with positive version
c29e :                  putn_2:
c29e : a000             		ldy #0
c2a0 : a90a             		lda #10
c2a2 : 208ec3           		jsr libdiv16	; we want to divide the argument by ten
c2a5 : fa               		plx
c2a6 : fa               		plx
c2a7 : 5a               		phy
c2a8 : 48               		pha				; replace arg by arg/10
c2a9 : a55a             		lda remainder	; we only care about the low byte
c2ab : 0930             		ora #'0'		; make it ascii
c2ad : a8               		tay				; keep it handy
c2ae : a55d             		lda numbuf		; get the char count
c2b0 : 3a               		dec a
c2b1 : 855d             		sta numbuf		; decrement the char position
c2b3 : 2907             		and #0x7		; isolate the count bits
c2b5 : aa               		tax
c2b6 : 98               		tya				; get the digit back
c2b7 : 955d             		sta numbuf,x	; put it in the buffer from the right
c2b9 : e001             		cpx #1			; we count 6-5-4-3-2
c2bb : d0e1             		bne putn_2		; so loop if we're not there yet
                        	; now we have to consider the leading zeros, the left
                        	; alignment, and the sign of the output
c2bd : a201             	ldx #1				; blank the leading zeros
c2bf :                  putn_3:					
c2bf : b55d             	lda numbuf,x
c2c1 : c930             	cmp #'0'			; are we looking at a zero?
c2c3 : d009             	bne putn_4			; no, we're done
c2c5 : a920             		lda #' '		; replace with a space
c2c7 : 955d             		sta numbuf,x
c2c9 : e8               		inx
c2ca : e005             		cpx #5			; don't eat the last zero!
c2cc : d0f1             		bne putn_3
c2ce :                  putn_4:
c2ce : 065d             	asl numbuf			; save the justification bit in carry
                        						; so now the sign bit is in bit 7
c2d0 : ca               	dex					; x was pointing to the last space; now it mi
                        						; be pointing at the flags byte
c2d1 : a55d             	lda numbuf
c2d3 : a020             	ldy #' '			; we're done with the flags but might need
c2d5 : 845d             	sty numbuf			; space for a minus sign
c2d7 : 2980             	and #0x80				
c2d9 : f005             	beq putn_5			; no negative sign required
c2db : a92d             		lda #'-'
c2dd : 955d             		sta numbuf,x	; else insert one
c2df : ca               		dex
c2e0 :                  putn_5:
                        	LYA numbuf			; point to the start of the buffer
c2e0 : a95d            >			lda # lo numbuf			
c2e2 : a000            >			ldy # hi numbuf			
                        
c2e4 : b005             	bcs putn_8			; right justified
                        		; unfortunately, I can't add x to a but I note that 
                        		; page addresses for puts aren't used until we call 
                        		; end. So... for left justified
c2e6 : e8               		inx				; correct for the start of numbuf
c2e7 : 8652             		stx put_ptr		; naughty naughty, not my variable!
c2e9 : 6552             		adc put_ptr		; carry is already clear
                        						; and we're pointing at the right place
c2eb :                  putn_8:
c2eb : 203ec2           	jsr puts
c2ee : fa               	plx
c2ef : fa               	plx
c2f0 : fa               	plx
c2f1 : 60               	rts

Neil

edit: corrected to make it work with a zero! (I forgot to test that)
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Here are the arithmetic routines: 183 bytes covering the usual four operators, and negation. As always, parameters are passed both on the stack before calling, with the second in Y:A, and the result returned in Y:A (for division, a zero page word 'remainder' is also used). For speed and simplicity, these routines share some zero page space.

Code: Select all

                        ;-----------------------------------------------------
                        ;
                        ; arithmetic and logic
                        
                        ;negate: change the sign of an int16_t by inverting th
                        ; adding one. The variable arrives and returns in Y:A
c336 :                  negate:	
c336 : 18               	clc
c337 : 49ff             	eor #0xff			; this doesn't affect carry
c339 : 6901             	adc #1				; but this does...
c33b : 48               	pha
c33c : 98               	tya
c33d : 49ff             	eor #0xff
c33f : 6900             	adc #0				; and this uses the carry from above
c341 : a8               	tay
c342 : 68               	pla
c343 : 60               	rts
                        	
                        ;libsub16: minuend pushed to stack, subtrahend in y:a,
                        ; we subtract by negating y:a and then adding it to TO
c344 :                  libsub16:				; subtract y:a from TOS
c344 : 2036c3           	jsr negate			; negate and drop through to libadd16
                        	
                        ; libadd16: augand pushed to stack, addend in y:a, => 
c347 :                  libadd16:				; add TOS to y:a
c347 : da               	phx					; save caller's stack frame
c348 : ba               	tsx					; set local frame pointer
c349 : 8556             	sta maths1			; we hold the high byte in y
c34b : 18               	clc
c34c : 7d0401           	adc CALLER_LO,x
c34f : 8556             	sta maths1
c351 : 98               	tya
c352 : 7d0501           	adc CALLER_HI,x
c355 : a8               	tay					; high result back in y
c356 : a556             	lda maths1			; collect low result
c358 : fa               	plx					; clean the stack
c359 : 60               	rts
                        
                        ;libmul16: multiplicand on the stack, multiplier in y:
                        ; This code is taken from the example p206 of the West
                        ; Programming Manual, and uses two zero page words (fo
                        
c35a :                  libmul16:				; signed multiply
c35a : da               	phx					; caller stack frame
c35b : ba               	tsx	
c35c : 8556             	sta maths1
c35e : 8457             	sty maths1+1		; save the multiplier
c360 : bd0401           	lda CALLER_LO,x
c363 : 8558             	sta maths2
c365 : bd0501           	lda CALLER_HI,x
c368 : 8559             	sta maths2+1		; and multiplicand
                        	
c36a : a200             	ldx #0
c36c : a000             	ldy #0				; clear the result
c36e :                  lml_m1:
c36e : a556             	lda maths1
c370 : 0557             	ora maths1+1		; is the operand zero? We're done...
c372 : f015             	beq lml_exit
c374 : 4657             		lsr maths1+1	; get lowest bit into carry
c376 : 6656             		ror maths1		
c378 : 9009             		bcc lml_m2		; nothing to do if it's zero
c37a : 18               			clc			; else add maths2 to result
c37b : 98               			tya
c37c : 6558             			adc maths2
c37e : a8               			tay
c37f : 8a               			txa
c380 : 6559             			adc maths2+1
c382 : aa               			tax
c383 :                  lml_m2:
c383 : 0658             		asl maths2		; multiply maths2 by two
c385 : 2659             		rol maths2+1
c387 : 80e5             		jmp lml_m1		; and back for the next bit
                        		
c389 :                  lml_exit:
                        	; result is in x:y which isn't where we want it...
c389 : 98               	tya					; the low byte
c38a : da               	phx
c38b : 7a               	ply					; and the high
c38c : fa               	plx					; restore stack frame
c38d : 60               	rts
                        
                        ;libdiv16: dividend on stack, divisor in y:a, result i
                        ; in remainder
                        ; NOTE: if either (or both) parameter is negative, we 
                        ; we do the division; if just one is negative, we inve
c38e :                  libdiv16:
c38e : da               	phx					; caller stack frame
c38f : ba               	tsx	
c390 : 645c             	stz sign			; assume both arguments are positive
c392 : 8556             	sta divisor			; save the low byte
c394 : 98               	tya					; but wait: is it negative? 
c395 : 100b             	bpl ldv_01			; positive, so skip
c397 : a556             		lda divisor		; else negate it; y still holds high by
c399 : 2036c3           		jsr negate		
c39c : 8556             		sta divisor		; save negated low byte
c39e : a980             		lda #0x80
c3a0 : 855c             		sta sign		; flag that we've inverted
c3a2 :                  ldv_01:
c3a2 : 8457             	sty divisor+1		; save the divisor
c3a4 : bd0401           	lda CALLER_LO,x
c3a7 : 8558             	sta dividend
c3a9 : bc0501           	ldy CALLER_HI,x
c3ac : 98               	tya					; check for sign of this argument
c3ad : 100d             	bpl ldv_02
c3af : a558             		lda dividend
c3b1 : 2036c3           		jsr negate
c3b4 : 8558             		sta dividend	; negated low byte
c3b6 : a55c             		lda sign
c3b8 : 4980             		eor #0x80		; sign is now 0x80 if just one arg is neg
c3ba : 855c             		sta sign		; but 0x00 if both were same sign
c3bc :                  ldv_02:
c3bc : 8459             	sty dividend+1		; and dividend
                        	
                        	; core routine taken from https://www.llx.com/Neil/a2
                        	; divides dividend by divisor, leaves result in divid
                        	; remainder in remainder
                        	; dividend and divisor are both positive
c3be : 645a             	stz remainder
c3c0 : 645b             	stz remainder+1		; remainder initialised to zero
c3c2 : a210             	ldx #16				; bit counter
c3c4 :                  ldv_1:
c3c4 : 0658             		asl dividend
c3c6 : 2659             		rol	dividend+1
c3c8 : 265a             		rol remainder
c3ca : 265b             		rol remainder+1		; move high bit of dividend into re
c3cc : a55a             		lda remainder
c3ce : 38               		sec					
c3cf : e556             		sbc divisor
c3d1 : a8               		tay					; trial subtraction
c3d2 : a55b             		lda remainder+1
c3d4 : e557             		sbc divisor+1
c3d6 : 9006             		bcc ldv_2
c3d8 : 855b             			sta remainder+1		; and if it worked, save it
c3da : 845a             			sty remainder
c3dc : e658             			inc dividend		; slide the result in as the low bit 
                        								; of dividend
c3de :                  ldv_2:
c3de : ca               		dex			
c3df : d0e3             		bne ldv_1			; and back sixteen times
c3e1 : a558             	lda dividend
c3e3 : a459             	ldy dividend+1		; return the result in y:a
c3e5 : a65c             	ldx sign			; check the sign of the result
c3e7 : 1003             	bpl ldv_exit
c3e9 : 2036c3           		jsr negate
c3ec :                  ldv_exit:
c3ec : fa               	plx					; restore stack frame
c3ed : 60               	rts
Neil
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Apologies for the truncated comments in the listing above. It's down to the assembler listing chopping them according to no obvious rule I can discern. Better to perhaps provide the source rather than the listing?
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Gradually working my way up the ladder: the expression parser can now cope with addition and subtraction; multiplication and division aren't far away (the code is very similar) and recursive brackets will follow.

I am deliberately keeping the code as close to the C code as I can; later I can go and prune unnecessary loads of the accumulator, for example. It's not going to be the fastest code ever, but it's amazing how easy it is to translate...

Neil
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

The expression parser appears to be complete (at least for now; I can't check the variables until I have an assignment function written).

Factor, Term, and Expression are just 202 bytes, not counting the support stuff like the arithmetic routines.

Code: Select all

Neolithic Tiny Basic                                              
                                                                  
2*2*2*4*355/(3*113)=    33                                        
1+2*3=     7                                                      
(1+2)*3=     9    
(just test code, nothing fancy yet!).

Next step is to enter program text into the 65c02 memory space, with tokenisation. Should be fun - I need to find something small to behave like strncasecmp()...

Neil
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Wow, that took longer than it should have done.

Squish_buffer() has to tokenise the input stream (but not between quotes, of course) and required the generation of a token matching routine (reasonably straightforward), a routine to test _all_ tokens against text, memmove, and strlen equivalents.

I think they're all working now.

So now I have 'just' to get the text into memory... at least I can continue to follow the working example of the C code.

Neil (just under 2k at the moment, but there's at least 1/4k of debug which will come out at the end.)
Post Reply