What is (zp,X) actually useful for?

Discuss all aspects of programming here. From 8-bit through to modern architectures.
Post Reply
julie_m
Posts: 54
Joined: Wed Jul 24, 2019 8:53 pm
Location: Derby, UK
Contact:

What is (zp,X) actually useful for?

Post by julie_m » Sat Oct 12, 2019 4:48 pm

OK, moment of shame here :oops: In all the years I've spent programming the Beeb, I don't think I've ever used a (zp,X) instruction in anger.

I use the (zp),Y addressing mode all the time. I have a bunch of records, each a fixed length up to 256 bytes long. To select one of the records, I multiply its index number by the record length, add the address of the beginning of the list, store this address in two consecutive bytes in zero page such as &80+&81, load zero into the Y register and then step along the list using LDA(&80) and INY. Within the fixed-length record, there can be a pointer to, and the length of, another, variable-length record; which is just one of many, squashed into one contiguous list with all the other VLRs. (In BCP, a footprint record has a 23-byte fixed length part, and two variable-length parts: the co-ordinates of the vertices of the silkscreen outline; and the co-ordinates, pad styles and hole sizes of the connection pins.)

So much for that. But the 6502 has another indexed indirect addressing mode (zp,X) where X is added to the zero page address given to get the address of the pointer.

If I have a bunch of pointers in &70+&71, &72+&73, &74+&75 and so forth, I can select the contents of the address pointed to by any one of them with LDA(&70,X) where X=0, 2, 4 ...

But what next? If that's the beginning of a list, then the only way to step through the list is by altering the value in &70,X and &71,X. You can INCrease your way through the list, a byte at a time (there is even a "short" INC zp,X instruction that might have been made for this); but you can't so easily DECrease back down it, because there's no equivalent of the Z flag but that gets set when the ALU output is &FF as opposed to &00.

Also, each of the pointed-to lists -- if it's to be handled in a generic fashion that works irrespective of the value of X, using zp.X instructions to manipulate the pointer in situ -- has to contain records with the same data structure (or randomly-mixed data structures, with the handling code able to detect what each record is and deal with it accordingly).

But I don't really see how using (zp,X) to select your base address and then updating it every time is preferrable to something like

Code: Select all

LDA ptrs,X
STA &8E
LDA ptrs+1,X
STA &8F
LDY #0
.read_list
LDA (&8F),Y
\ ...
INY
CPY #rlen
BMI read_list
or even

Code: Select all

LDY &70 \ &70 does what X did before
LDA (&8C),Y
STA &8E
INY
LDA (&8C),Y
STA &8F \ now &8E+&8F is base of (&70)th list
INY
STY &70
LDY #0
.read_list
LDA (&8E),Y
All I can think is, the (zp,X) addressing mode is not meant for pointing to bases of lists, but for something else altogether ..... so what is it useful for for?

User avatar
tricky
Posts: 3735
Joined: Tue Jun 21, 2011 8:25 am
Contact:

Re: What is (zp,X) actually useful for?

Post by tricky » Sat Oct 12, 2019 9:29 pm

I have only used it a few times and probably half of those had X=0. There have been a couple of times when I have had to add to one of several lists and using (,x) and inc,x was more efficient.
I suspect that they may be more useful to a compiler or a virtual machine like sweet16.

julie_m
Posts: 54
Joined: Wed Jul 24, 2019 8:53 pm
Location: Derby, UK
Contact:

Re: What is (zp,X) actually useful for?

Post by julie_m » Sun Oct 13, 2019 12:11 pm

I suppose it would be useful if you wanted to sort an incoming data stream into separate "bins". (Building dictionaries for multi-strategy compression/decompression?)

It's just that advancing and retreating through those lists needs something like

Code: Select all

.adv_list \ 7 bytes, 15 or 20 cycles
INC &70,X \ Z gets set if we wrapped around
BNE adv_list1
INC &71,X \ increase high byte
.adv_list1
RTS
.ret_list \ 9 bytes, 19 or 24 cycles
LDA &70,X \ see if we are about to wrap around
BNE ret_list1
DEC &71,X \ decrease high byte
.ret_list1
DEC &70,X
RTS
which has to be done every single time to update the pointer in situ, rather than using an index into the list. Though I guess it's no less efficient when working with individual entries in different lists in turn, as opposed to multiple consecutive entries in the same list.

Maybe I'm just reading too much significance into it? It's just that with it being one of the "big eight" addressing modes (immediate, long, short, long,X, short,X, long,Y, (zp,Y), (zp),Y) that apply to eight instructions (LDA, STA, ADC, SBC, AND, ORA, EOR, CMP) accounting for a quarter (or even a third, since the internal logic of the 6502 is such that you can't have both bits 0 and 1 set) of all possible opcodes, it looks as though it should crop up more often in actual code .....

User avatar
BigEd
Posts: 2630
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: What is (zp,X) actually useful for?

Post by BigEd » Sun Oct 13, 2019 12:31 pm

I sometimes think of
(zp),Y as a pointer to struct (or pointer to array)
and
(zp,X) as an array of pointers

(But it would have to be a small array, as it's in zero page.)

I see three or four uses in the BBC OS and Basic, squinting at a quick and dirty disassembly, and in all cases (zp,X) is used when X is known to be zero (or one.)

User avatar
jgharston
Posts: 3696
Joined: Thu Sep 24, 2009 11:22 am
Location: Whitby/Sheffield
Contact:

Re: What is (zp,X) actually useful for?

Post by jgharston » Sun Oct 13, 2019 1:06 pm

I think the only case I've used (zp,X) is when comparing between to bits of data, such as a command lookup, something like:

LDX #0
LDA (src),Y
CMP (dst,X)

where the src and dst have to be stepped by different amounts, so can't share the same Y value. I think all my command lookups actually in live code have had a command table shorter than 256 bytes so have used CMP abs,X instead. I even rewrote the command lookup in the AP6/Plus1 ROM specifically to shrink the table so I could add more commands and keep it shorter than 256 bytes.

The BBC MOS only uses (zp,X) three times and each time it's because Y is being used to reference another block of data. The Master MOS replaces them with (zp). BASIC 2/3 only uses it once, and that's only because X is already &00 at that point and it saves a LDY #&00. I haven't checked, but I expect BASIC IV replaces that with (zp).

Code: Select all

$ bbcbasic
PDP11 BBC BASIC IV Version 0.25
(C) Copyright J.G.Harston 1989,2005-2015
>_

User avatar
Rich Talbot-Watkins
Posts: 1548
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: What is (zp,X) actually useful for?

Post by Rich Talbot-Watkins » Sun Oct 13, 2019 2:14 pm

Last time I used (zp, X) was in music playing code, which had to consume no more than a byte from a buffer for each channel, each update.

I think if you had to read from ptr+offset for a number of different ptrs, it would be better to copy the address into a known fixed zp pair and use (zp),Y instead.

And yeah, the only other times I used it was with X=0 (or some known fixed value that I could compensate for in the operand), if I needed to preserve Y.

jregel
Posts: 140
Joined: Fri Dec 20, 2013 6:39 pm
Location: Gloucestershire
Contact:

Re: What is (zp,X) actually useful for?

Post by jregel » Sun Oct 13, 2019 5:49 pm

I'm certainly not experienced enough in 6502 to add my own thoughts, but as I was recently reading "Advanced Machine Code Technique for the BBC Micro", I thought I'd post a bit of how Indexed Indirect Addressing is described:
"... indexed indirect addressing is not a commonly used mode. One area in which it is valuable is in handling peripheral interrupts. The course of a program can often depend on the particular peripheral which has requested interrupt. For example, the data sent to a printer will originate from a different area than the data send to a digital-to-analogue converter. Assuming there are two peripherals on line, then we can arrange to have two separate address pointers to service them, located in zero-page. Suppose these double-byte addresses occupy the four locations &72, &73 and &74, &75 and consider the following line:

STA (&70,X)

The value placed in X must be that which modifies the operand to locate the desired address pointer. Care should be taken when calculating the value of X. The indirect address pointer is a two-byte address, so X must be changed by two at a time, otherwise the instruction will define the high-byte instead of the low-byte. [example snipped].

Apart from handling peripherals, indexed indirect addressing can be used to simulate the CASE statement found in some of the structured languages or ON GOTO in BASIC. Control can be switched to separate machine code processes, each switched by a unique address pointer. The value in X determines which process is activated.
Can't vouch for how accurate/useful this is, but it's how that book described it.
BBC Master Turbo, Retroclinic External Datacentre, VideoNuLA, PiTubeDirect with Pi Zero, Gotek USB Floppy Emulator

User avatar
BigEd
Posts: 2630
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: What is (zp,X) actually useful for?

Post by BigEd » Mon Oct 14, 2019 8:27 am

By complete coincidence, Garth Wilson over on the 6502.org forum has just volunteered this:
Many users have wondered why the (zp,X) was even there, thinking it was a waste because it never gets used. Well, it does get used, constantly, in languages that use a data stack in ZP (which is separate from the hardware return stack in page 1), Forth being one of them.

User avatar
tricky
Posts: 3735
Joined: Tue Jun 21, 2011 8:25 am
Contact:

Re: What is (zp,X) actually useful for?

Post by tricky » Mon Oct 14, 2019 11:33 am

You would still expect X=0 or at least that the instruction to known which stack.
It feels like it looked useful and as noone else had programmed the 6502 it was a reasonable choice.
Shame they didn't extend it in the C02 to (zp) and (zp),x, like they did with store 0.

User avatar
BigEd
Posts: 2630
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: What is (zp,X) actually useful for?

Post by BigEd » Mon Oct 14, 2019 11:43 am

(With the data stack base pointer in ZP, you'd be using X as the stack pointer. It would most likely be a data stack, not a code stack for return values - that's still in page 1. Although, there might be interpreter designs where you manage your own return stack. In any case, it's at most a 256 byte stack, or it's a bother to adjust the stack pointer.)

User avatar
Rich Talbot-Watkins
Posts: 1548
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: What is (zp,X) actually useful for?

Post by Rich Talbot-Watkins » Mon Oct 14, 2019 12:37 pm

tricky wrote:
Mon Oct 14, 2019 11:33 am
You would still expect X=0 or at least that the instruction to known which stack.
It feels like it looked useful and as noone else had programmed the 6502 it was a reasonable choice.
Shame they didn't extend it in the C02 to (zp) and (zp),x, like they did with store 0.
The 65CE02 (as used in the unreleased Commodore 65, and very little else) added a Z register, which defaulted to 0. Then the LDA (zp) instructions of the 65C02 just became LDA (zp),Z. Had a really nice instruction set - shame it never really got off the ground.

Post Reply