6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Nov 10, 2024 8:39 pm

All times are UTC




Post new topic Reply to topic  [ 15 posts ] 
Author Message
PostPosted: Mon Aug 19, 2024 1:41 pm 
Offline
User avatar

Joined: Wed Aug 17, 2005 12:07 am
Posts: 1250
Location: Soddy-Daisy, TN USA
I'm trying to iterate through two different sections of memory (for C64).

Let's say for example that I have:

Code:

lda #<BUFFER
sta ZP10
lda #>BUFFER
sta ZP11

lda #<SCREEN
sta ZP12
lda #>SCREEN
sta ZP13

ldx #00
ldy #40

lda (ZP10), y    // get the 40th character from buffer
sta (ZP12), x    // store at position 0 on the screen



It seems you cannot use X as an indirect offset like you can Y.
However, my assembler (KickAssembler) compiles this just fine. It even runs. But it obviously doesn't work.

The only way I can get indirect addressing to work that way is to always use Y. Which can be tedious sometimes.
Does anyone know why this is?

BTW, if anyone is curious, this is my *nearly* full screen scrolling (not smooth) with color. As you can see, I don't use "indirect X".

Code:
    .align $100
    * = * "Map.DrawMap"
    DrawMap: {
            .label COL = ZP.TEMP1
            lda #$00
            sta COL

            ldx #<MAP_BUFFER        // X contains the LOW byte.  Character column.
            ldy #>MAP_BUFFER        // Y contains the HIGH byte of the MAP_BUFFER (character row)
            stx ZP.MapTemp          // ZP.MapTemp contains MAP_BUFFER address
            sty ZP.MapTemp + 1

            ldx #$00
            ldy ZP.MapX
        !loop:
            lda #>MAP_BUFFER
            sta ZP.MapTemp + 1

            .for(var i = 0; i < 18; i++) {
                lda (ZP.MapTemp), y
                sta SCR_BUFFER + (i * 40), x
                sty ZP.TEMP1
                tay
                lda ATTR_DATA, y
                sta VIC.COLOR_RAM + (i * 40), x
                ldy ZP.TEMP1
                inc ZP.MapTemp + 1
            }
            inx
            iny
            cpx #40
            beq !exit+
            jmp !loop-
        !exit:
            rts
    }

_________________
Cat; the other white meat.


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 19, 2024 1:59 pm 
Online

Joined: Tue Sep 03, 2002 12:58 pm
Posts: 336
cbmeeks wrote:
Code:
lda (ZP10), y    // get the 40th character from buffer
sta (ZP12), x    // store at position 0 on the screen


It seems you cannot use X as an indirect offset like you can Y.
However, my assembler (KickAssembler) compiles this just fine. It even runs. But it obviously doesn't work.

The only way I can get indirect addressing to work that way is to always use Y. Which can be tedious sometimes.
Does anyone know why this is?


The 6502 has two addressing modes that involve zero-page indirection: (zp, X) and (zp), Y. They differ in when the indexing takes place: before or after reading the address from zero page. (65C02 adds (zp) with no indexing, but that's not relevant to the C64).

Why is that? I don't think any of us know. We can speculate, but only the original designers would know (and they may have forgotten). You can use pre-indexing with X or post-indexing with Y, and that's all. I'm not sure I've ever used (zp, X).

If your assembler is accepting STA (zp), X and producing code for it, that's a bug in the assembler. What opcode does it produce for that line?


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 19, 2024 2:15 pm 
Offline
User avatar

Joined: Wed Aug 17, 2005 12:07 am
Posts: 1250
Location: Soddy-Daisy, TN USA
I didn't think to check the opcode.

It's producing $95 which is Zero Page, X.

Code:
STA (STore Accumulator)

Affects Flags: none

MODE           SYNTAX       HEX LEN TIM
Zero Page     STA $44       $85  2   3
Zero Page,X   STA $44,X     $95  2   4
Absolute      STA $4400     $8D  3   4
Absolute,X    STA $4400,X   $9D  3   5
Absolute,Y    STA $4400,Y   $99  3   5
Indirect,X    STA ($44,X)   $81  2   6
Indirect,Y    STA ($44),Y   $91  2   6


Honestly, this isn't the first time I forgot that you cannot use X like I am wanting to. lol

_________________
Cat; the other white meat.


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 19, 2024 2:54 pm 
Online

Joined: Tue Sep 03, 2002 12:58 pm
Posts: 336
Oh. Yes. That's not a bug then, but an extremely confusing ambiguity. I ran into it myself with my own assembler.

It's good for an assembler to accept general expressions wherever it needs a value. That way we can write things like
Code:
    LDA buffer + (something+5)*3, Y

Expressions are made of subexpressions: if A and B are expressions, then A + B is too. We want to be able to control precedence with parentheses, so an expression inside ( ) is also an expression.
Code:
    LDA (buffer + (something+5)*3), Y


Unfortunately that's identical to the syntax for indirect addressing, and there's no way to tell which was intended. I don't remember the solution I went with (in practice it has been working well enough, whatever it was). If I was writing it again now I'd probably say parentheses can only be used in subexpressions. You can't have the whole expression wrapped in ( ), because that's reserved for indirection.

In that case
Code:
    STA (ZP12), x

would be an error, because there is no (zp), X addressing mode, and putting the whole expression inside ( ) cannot be anything but indirection.


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 19, 2024 3:45 pm 
Online

Joined: Mon Jan 19, 2004 12:49 pm
Posts: 957
Location: Potsdam, DE
That suggests that an assembler should be looking for a ( immediately (ignoring spaces) after the instruction and treating whatever follows that as an expression; you shouldn't need enclosing braces so the final ,X) or ),Y can be unambiguous.

Of course, since there's never any in-software evaluation, you can define the whole thing externally to your actual instruction:
Code:
ptr equ (buffer + (something+5)*3)
;or ptr equ buffer + (something+5) * 3
    LDA (ptr),Y

which ought to be acceptable to the assembler?

Neil


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 19, 2024 4:31 pm 
Offline

Joined: Thu Mar 12, 2020 10:04 pm
Posts: 704
Location: North Tejas
If X always contains zero,

sta (ZP12,X)

does what

sta (ZP12), x

appears to do.

If you have to vary the index, you will have to

- ping pong between the indices using Y

or

- modify ZP12 to incorporate the index

Edit: <RANT>

This is why I prefer that the assembler always require a delimiter like ';' before the comment field. The other extreme, what your assembler does, is that any text after the addressing mode is parsed is considered to be a comment. If ';' is required, ", X" will be flagged as a comment without a leading ';'

</RANT>


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 24, 2024 12:04 am 
Offline

Joined: Sun Feb 22, 2004 9:01 pm
Posts: 104
John West wrote:
You can use pre-indexing with X or post-indexing with Y, and that's all. I'm not sure I've ever used (zp, X).
I've used it to optimise code, to save the use of another register, save some opcode bytes, and get a bit more speed:
Code:
.bytelp
LDX #8                       :\ Prepare to rotate CRC 8 bits
LDA (addr-8 AND &FF,X)       :\ Fetch byte from memory
:

_________________
--
JGH - http://mdfs.net


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 24, 2024 12:40 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8539
Location: Southern California
John West wrote:
The 6502 has two addressing modes that involve zero-page indirection: (zp, X) and (zp), Y.  They differ in when the indexing takes place: before or after reading the address from zero page.  (65C02 adds (zp) with no indexing, but that's not relevant to the C64).

Why is that?  I don't think any of us know.  We can speculate, but only the original designers would know (and they may have forgotten).  You can use pre-indexing with X or post-indexing with Y, and that's all.  I'm not sure I've ever used (zp, X).

I suppose it'd be nice to be able to do either operation with either index register; but that would take up more places in the op code table, and maybe the reason they didn't do that is that they were keeping those places open for other future enhancements, and/or that it would require more silicon space in a die they wanted to keep the price down on.  At least we do have those functions though, only restricting which index register can be used for each.  So many people have wondered why the (ZP,X) addressing mode even existed, thinking it was useless.  The secret is to think of its use not for a table, but rather for a data stack in ZP.  The Forth programming language uses it constantly, and I have heard that other languages that use a ZP stack (although they might hide it from the user) also use it.

BillG wrote:
This is why I prefer that the assembler always require a delimiter like ';' before the comment field.  The other extreme, what your assembler does, is that any text after the addressing mode is parsed is considered to be a comment.  If ';' is required, ", X" will be flagged as a comment without a leading ';'

That's a good consideration that I'm not sure I had ever thought of.  It's similar to the use of a colon (":") after a label, which I always recommend even if the assembler does not require it.  It makes searches easier, because otherwise if you're looking for the particular routine, a search without the colon may turn up many, many references to the label before you find the label itself.  I've also had assembly errors, even in the last month, resulting from violating this good practice.

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


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 24, 2024 12:59 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8481
Location: Midwestern USA
John West wrote:
I'm not sure I've ever used (zp, X).

The driver code I wrote for the virtual QUART in my POC unit makes extensive use of (<dp>,X) addressing to select the chip registers and serial I/O queues being manipulated.  The alternative would have been to set up and take down pointers each time a DUART or queue had to be accessed, which, in particular, would have inflated the size of the IRQ handler that processes DUART interrupts.  It came down to counting cycles, and use of (<dp>,X) was significantly faster, even though it eats up a fair amount of direct page space (less a problem with the 65C816 than the 65C02, of course).

GARTHWILSON wrote:
I suppose it'd be nice to be able to do either operation with either index register; but that would take up more places in the op code table, and maybe the reason they didn't do that is that they were keeping those places open for other future enhancements, and/or that it would require more silicon space in a die they wanted to keep the price down on.

Also, the originally-intended application for the 6502, as was the case for the other early microprocessors, was in machine controllers and the like, not general-purpose computing.  As a goal of Chuck Peddle and his posse was to undercut Intel’s and Motorola’s position in that market, a lot of the 6502’s refinement was focused on the cost angle, which entailed, among other things, excising instructions that weren’t critical to the application.  Something such as (<zp>,X),Y would have been useful, but not essential.  Their (MOS’) success in that area arguably is what accelerated the homebrew computer hobby, since a 6502 was a *lot* cheaper than an 8080 or 6800...and as it turned out, just as capable.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 24, 2024 6:09 pm 
Offline

Joined: Sun Nov 08, 2009 1:56 am
Posts: 410
Location: Minnesota
Quote:
Unfortunately that's identical to the syntax for indirect addressing, and there's no way to tell which was intended. I don't remember the solution I went with (in practice it has been working well enough, whatever it was). If I was writing it again now I'd probably say parentheses can only be used in subexpressions. You can't have the whole expression wrapped in ( ), because that's reserved for indirection.


I'm not sure about that. The value of an expression wrapped in parentheses is no different than the value of the same expression not wrapped in parentheses. So that in itself is not a problem.

What matters for indicating indirection, particularly post-indexed Y, is that the parentheses are properly balanced at the beginning and end of of the expression. If you add one to counter when encountering an open parenthesis and subtract one for a close parenthesis, indirection is flagged if the counter value reaches zero at the final close parenthesis at the end of the expression. If you reach zero before then, then it's not indirection, it's just parentheses indicating order of evaluation.

So this:

Code:
lda ((expr1) + (expr2)),y


is indirection, but this:

Code:
lda (expr1) + (expr2),y


is not, even though both lines have an opening parenthesis at the start and a close parenthesis at the end.


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 24, 2024 7:01 pm 
Online

Joined: Tue Sep 03, 2002 12:58 pm
Posts: 336
teamtempest wrote:
So this:
Code:
lda ((expr1) + (expr2)),y

is indirection, but this:
Code:
lda (expr1) + (expr2),y

is not, even though both lines have an opening parenthesis at the start and a close parenthesis at the end.


That's what I'm saying. The second is an expression containing two sub-expressions (each of which are in parentheses) added together. It is not in parentheses itself.

Although it starts and ends with ( ), they aren't paired. The first ( goes with the first ) making a pair around expr1. The second ( goes with the second ) making a pair around expr2. There's nothing around the whole expression. My assembler has a proper recursive expression parser with precedence, so this falls out naturally.


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 24, 2024 7:42 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8481
Location: Midwestern USA
teamtempest wrote:
Code:
lda ((expr1) + (expr2)),y

is indirection, but this:

Code:
lda (expr1) + (expr2),y

is not, even though both lines have an opening parenthesis at the start and a close parenthesis at the end.

That last bit of code, in most assemblers that can evaluate parenthesized expressions, would effectively be...

Code:
lda expr,y

...where EXPR is the sum of EXPR1 and EXPR2.

On the other hand, in older assemblers that could not evaluate an operand in that fashion, the result would be a syntax error, due to the parentheses being seen solely as indications of indirection.  That was the behavior of Commodore’s MADS assembler for the C-64.

In the Kowalski assembler, expression grouping is done with curly braces ({ }), which is a little confusing until one gets used to it.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 26, 2024 4:55 am 
Offline

Joined: Tue Jul 05, 2005 7:08 pm
Posts: 1042
Location: near Heidelberg, Germany
When I need to use a calculation with brackets in a normal addressing mode, I just use something like
Code:
    LDA 0+(....),Y


That tricks the assembler into absolute addressing mode

André

_________________
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 26, 2024 6:45 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8481
Location: Midwestern USA
fachat wrote:
When I need to use a calculation with brackets in a normal addressing mode, I just use something like

Code:
LDA 0+(....),Y

That tricks the assembler into absolute addressing mode

Due to the variations in how assemblers handle expressions (and some can’t do algebraic evaluation at all), I’ve generally avoided doing such evaluation in operands and instead evaluate earlier in the source code, assign the result to a symbol and use the symbol as the operand.

In the Kowalski assembler, there is no confusion over use of parentheses, since curly brackets are used to set precedence, e.g....

Code:
XYZ = {2+3}*{8-1}

I could, however, use the above expression as an operand without trouble:

Code:
LDA ({2+3}*{8-1},X)

...but I prefer to keep such clutter out of instructions.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 26, 2024 8:29 pm 
Offline
User avatar

Joined: Wed Aug 17, 2005 12:07 am
Posts: 1250
Location: Soddy-Daisy, TN USA
I do enjoy Kick Assembler. If I'm not mistaken, you can use square brackets in place of parenthesis so that evaluations don't mess up the assembled output.

Also, I usually keep complex evaluations down to a minimum. The original issue I was having on this thread was solved using the following code.

Code:
    * = * "Map.DrawMap_DBL_BUFFER_Top"
    DrawMap_DBL_BUFFER_Top: {
            ldx #$00
            stx ZP.MapTemp
            ldy ZP.MapX
        !loop:
            lda #>MAP_BUFFER
            sta ZP.MapTemp + 1

            .for(var i = 0; i < 9; i++) {
                lda (ZP.MapTemp), y                 // A has CHAR from MAP_BUFFER
                sta DBL_BUFFER + (i * 40), x        // Blit the CHAR
                sty ZP.TEMP1                        // Copy Y position (X position in MAP_BUFFER)
                tay                                 // Move the CHAR to Y
                lda ATTR_DATA, y                    // Lookup color/attr for the CHAR
                sta VIC.COLOR_RAM + (i * 40), x     // Blit the color value
                ldy ZP.TEMP1                        // Restore Y
                inc ZP.MapTemp + 1                  // Increment HIGH byte of MAP_BUFFER ptr
            }
            inx                                     // Advance to next CHAR column in MAP_BUFFER
            iny                                     // Move Y
            cpx #40
            beq !exit+
            jmp !loop-
        !exit:
            rts
    }

_________________
Cat; the other white meat.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 15 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 18 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: