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:

Re: Neolithic Tiny Basic

Post by barnacle »

barrym95838 wrote:

Code: Select all

isalpha:
    pha             ; (optional)
    ora  #$20       ; xlate upper to lower
    clc
    adc  #$85       ; 'z' should be $ff now
    cmp  #$e6       ; only 7-bit ASCII alphas should set carry
    pla             ; (optional)
    rts
Kudos, Mike, looks like you nailed it. That may be the canonical 'isalpha' - may I steal it please?

Thanks,

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

Re: Neolithic Tiny Basic

Post by barrym95838 »

Absolutely! Everything I post here is free to any good home that'll take it.
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 »

My thanks!

Once I've got the current issues with for/next sorted (can't decide whether searching for a matching next has any variables it needs to keep between recursive iterations) I'll drop it into the code and test it for real.

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

Re: Neolithic Tiny Basic

Post by barnacle »

Dropped it in, it doesn't appear to have broken anything!

Meanwhile, a little macro abuse. For reasons I don't know, my assembler doesn't have an override (that I can find) that will let it generate three-byte absolute,x instructions when the address is less than $100. Since I use the x register as a pointer to the local stack, I need that ability whenever I store local variables on the stack.

In most cases, zero page addresses can be used, or shared, for local variables without overlap, but there are half a dozen routines which need to be called recursively and have more than one local 16-bit variable. Pushing and popping works well enough for a single variable, but is a pain once you get past there. It's also handy to be able to refer to a named variable... hence:

Code: Select all

; stack looks like:
;	caller param 1 hi	; 0x0105,x
;	caller param 2 lo	; 0x0104,x
;	return address hi
;	return address lo
;	pushed x			; stack pointer copied to x
;	param 1 hi			; 0x0100,x
;	param 1 lo			; 0x00ff,x
;	param 2 hi			; 0x00fe,x
;	param 2 lo			; 0x00fd,x
;	...					...

LDAZXHI	macro	p1
	db	0xbd
	db lo (0x100 - ((p1 - 1) * 2))
	db hi (0x100 - ((p1 - 1) * 2))
	endm
		
LDAZXLO	macro	p1
	db	0xbd
	db lo (0xff - ((p1 - 1) * 2))
	db hi (0xff - ((p1 - 1) * 2))
	endm

STAZXHI	macro	p1
	db	0x9d
	db lo (0x100 - ((p1 - 1) * 2))
	db hi (0x100 - ((p1 - 1) * 2))
	endm
		
STAZXLO	macro	p1
	db	0x9d
	db lo (0xff - ((p1 - 1) * 2))
	db hi (0xff - ((p1 - 1) * 2))
	endm
And in use:

Code: Select all

	PUSH $1234 			; caller private
	PUSH $5678			; caller parameter
	
	jsr test
	pla
	pla
	pla
	pla
	
test:
first_p		equ 1
second_p	equ 2
	phx
	tsx
	PUSH $9ABC			; first_p
	PUSH $DEF0			; second_p
	lda CALLER_LO,x
	lda CALLER_HI,x
	LDAZXHI first_p
	LDAZXLO first_p
	LDAZXHI second_p
	LDAZXLO second_p
	lda #0
	STAZXHI first_p
	STAZXLO first_p
	STAZXHI second_p
	STAZXLO second_p
	plx
	plx
	plx
	plx	
	plx
	rts
And a chunk of generated code

Code: Select all

                        	PUSH $DEF0			; second_p
ec0d : a9de            >			lda # hi $DEF0			
ec0f : 48              >			pha
ec10 : a9f0            >			lda # lo $DEF0			
ec12 : 48              >			pha                        
ec13 : bd0401           	lda CALLER_LO,x
ec16 : bd0501           	lda CALLER_HI,x
                        	LDAZXHI first_p
ec19 : bd              >	db	0xbd
ec1a : 00              >	db lo (0x100 - ((first_p - 1) * 2))
ec1b : 01              >	db hi (0x100 - ((first_p - 1) * 2))                        
                        	LDAZXLO first_p
ec1c : bd              >	db	0xbd
ec1d : ff              >	db lo (0xff - ((first_p - 1) * 2))
ec1e : 00              >	db hi (0xff - ((first_p - 1) * 2))                        
                        	LDAZXHI second_p
ec1f : bd              >	db	0xbd
ec20 : fe              >	db lo (0x100 - ((second_p - 1) * 2))
ec21 : 00              >	db hi (0x100 - ((second_p - 1) * 2))                        
                        	LDAZXLO second_p
ec22 : bd              >	db	0xbd
ec23 : fd              >	db lo (0xff - ((second_p - 1) * 2))
ec24 : 00              >	db hi (0xff - ((second_p - 1) * 2))                        
ec25 : a900             	lda #0
                        	STAZXHI first_p
ec27 : 9d              >	db	0x9d
ec28 : 00              >	db lo (0x100 - ((first_p - 1) * 2))
ec29 : 01              >	db hi (0x100 - ((first_p - 1) * 2))
(Just a chunk from the middle, for brevity, but to show all accesses)

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

Re: Neolithic Tiny Basic

Post by barnacle »

Hmm, dunno why I called it LDAZXHI/LO. Perhaps LDAAXHI/LO...

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

Re: Neolithic Tiny Basic

Post by barrym95838 »

Using "negative" stack addressing can be useful but should be done with care, lest an untimely interrupt fires to stomp on your unprotected parameters.
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 »

Yeah, I know. There aren't any interrupts running on this system, but if there were I don't think it's an issue: the local variables are all pushed onto the stack and popped off on exit, so (a) calls from within the routine work and (b) an interrupt happening at any time can use the stack safely; things carry on as normal when it returns.

Code: Select all

for:
	; recursive, so allocate local storage on stack
paramto		equ 1
for_name	equ 2		; only one byte but allocate two
this_line	equ 3
this_where	equ 4	
where		equ 5

	phx					; save incoming stack frame
	tsx					; grab the new stack frame
;	int16_t paramto;	// to
	phx
	phx
;	char name;			// and which variable is the counter?
	phx
	phx
;	uint16_t this_line;	// the line we start at
	phx
	phx
;	char * this_where;	// the place we start at
	phx
	phx
;	where -= 4;
	phy					; where is in y:a on entry
	pha
	sec
	LDAZXLO where
	sbc #4
	STAZXLO where
	bcs for_1
	INCZXHI where	
for_1:	...
And a big pile of plx to clean the stack up on exit.

The issue in general is that using the stack this way temporarily uses a huge amount of it; I may have to start again and use separate call and variable stacks. We'll see how deep the stack gets in practice.

As an aside: when this runs, I'm going to have to go through it line by line and rationalise the variable naming. I had hoped that this method would allow me to use the same variable name in different places by re-using equates, but nope, they can only be used once. Hmm... must not get distracted by writing an assembler :mrgreen:

Neil
User avatar
GARTHWILSON
Forum Moderator
Posts: 8773
Joined: 30 Aug 2002
Location: Southern California
Contact:

Re: Neolithic Tiny Basic

Post by GARTHWILSON »

It can be done, even re-using the same variable names for local variables.  I tell about it in the middle of the page at http://wilsonminesco.com/stacks/loc_vars.html .
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Ah, I had thought from the documentation (AS65: http://www.kingswood-consulting.co.uk/assemblers/) that 'equ' and 'set' did the same thing. But they don't...

Code: Select all

where		equ 5
where		set 6
complains about a redefinition, while

Code: Select all

where		set 5
where		set 6
doesn't, and it appears to generate the expected code. Thanks for that!

The list of macros is increasing: I have a feeling I may rewrite (once it's working) so that many or most of the common usages use macros rather than discrete code. It's easier to maintain the 1:1 comparison with the C code that way, though it will likely need another pass to optimise, which I'm doing as I go along at the moment.

Code: Select all

; high and low byte versions of
    LDAZXHI	
    STAZXHI	
    STYZXHI	
    ADCZXHI	
    SBCZXHI	
    STZZXHI	
    INCZXHI	
    DECZXHI	
    CMPZXHI	
Neil

edit: I think there could still be issues if a global variable has the same name as a local, though that's a potential wherever and generally to be frowned upon.
User avatar
BigDumbDinosaur
Posts: 9425
Joined: 28 May 2009
Location: Midwestern USA (JB Pritzker’s dystopia)
Contact:

Re: Neolithic Tiny Basic

Post by BigDumbDinosaur »

barrym95838 wrote:
Using "negative" stack addressing can be useful but should be done with care, lest an untimely interrupt fires to stomp on your unprotected parameters.

All the more reason to move up to the 65C816:D  Stack (and direct page) contretemps arising from an “untimely” interrupt are readily avoided when operating the 816 in native mode.  Using the stack as a scratchpad is far easier, especially if direct page is temporarily relocated to the stack.  In other words, why shovel snow by hand when a snowplow is readily available?
x86?  We ain't got no x86.  We don't NEED no stinking x86!
User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Neolithic Tiny Basic

Post by barrym95838 »

If you are honestly asking "Why", then you're missing a key reason for the existence of this forum, BDD. I could try to explain in further detail, but your well-worn preference to frequently promote an antiquated MPU over a slightly more antiquated MPU is beyond this youngster's ability to influence. The entire thread (up to now) has been exclusively about a 65C02 tiny BASIC, and your well-intentioned opinions are not universally unwelcome, but are also (sadly) not universally prescriptive.
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 »

Well, why spend all that time trying to figure out the crossword when you could have all the answers tomorrow anyway?

Yes, I have railed against the 65c02's lack of a stack frame but that is not in itself sufficient to move to a different processor. I want to know whether _I_ can solve the problems in implementing my program using the hardware available to me in the 65c02. Converting my (working) C code using a 16-bit wide processor would, I am certain, be a lot simpler than it is with the 65c02 but that's not the issue at hand.

On the other hand, it's nice to know what options are available if I ever decide to use the 65c816 but it is not currently on my roadmap. At current progress rates, I can see it being at least a year before this project is finished, if ever.

Also: I don't see it as being difficult, or indeed risky, to use the stack for local storage. The need for the macros was to handle an assembler deficiency (they all produce similar three-byte instructions which can't otherwise be generated) and the benefit is that I can access named variables on the stack.

I don't see a real risk from interrupts of this approach. At all times the stack pointer is below the data in use, whether it's positioned by simply pushing (as I do here) or by manipulating the stack pointer directly (which is shorter than pushing only when you need more than four 16-bit variables).

The issue isn't one of 8-bit versus 16-bit, but the lack a stack frame pointer which could be indexed from. Allocate space for the variables, copy the stack pointer to the frame pointer (having saved the frame pointer because something above you might be using it) and all the variables, including those passed from the caller, are positive offsets from the frame pointer. But it doesn't have it, so the fun is working around that. Which is what I'm doing with X - not the only approach, but it works.

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

Re: Neolithic Tiny Basic

Post by barnacle »

Oh bother! I have been deceived by my assembler... it thinks there's an STY abs,x instruction and assembles it without warning:

Code: Select all

ece0 : 9c0001               sty $100,x
which is of course the opcode for STY abs... a minor rethink of my plans is in order!

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

Re: Neolithic Tiny Basic

Post by barnacle »

Screenshot from 2025-02-20 10-37-05.png
Well we're getting closer, but not quite there yet :mrgreen:

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

Re: Neolithic Tiny Basic

Post by barnacle »

Finally... nested for-next.
Screenshot from 2025-02-23 21-45-59.png
Unfortunately it revealed a couple of further errors:
  • print with no parameters should just print an empty line; it doesn't
  • expression, as used by print, should be able to parse '(q*10)+r' but it throws an error (or print isn't dealing correctly with the open bracket)
Slowly, we progress...

Neil
Post Reply