• Please review our updated Terms and Rules here

6502 Assembly Language Question.

Hugo Holden

Veteran Member
Joined
Dec 23, 2015
Messages
4,750
Location
Australia
I'm trying to write an assembly language program which can enter or read values to memory from page 04 upwards and I don't want to use any page 0 locations for the program or data. The program will sit just at the top of page 03.

I can easily write to a page of memory, for example with STA $0400,X and increment the x register from 00 to FF, so easy to write, to up to address $04FF. But at that point I have not found a way yet to auto increment the high byte 04 to the next page at 05.

I have tried using the indirect indexed format for example STA (ADDR),X where ADDR was the label as an address with a .WORD (& I tried a byte) assembler directive, and holds two bytes (which I could modify to change the address), but this address method only appears to work for page 0 addresses. And I get assembly errors when I try it.

( When I was doing some 8080 assembly, I didn't have this issue, because to generate an address that could be incremented or decremented across the span $0000 to $FFFF, it would work with the H-L "register pair" and was dead easy)

So the question is; what is the better method (routine) to generate sequential 16 bit addresses across the full range of say $0400 to $FFFF in 6502 assembly language, where the total address value can be incremented or decremented, that will work with LDA and STA to read and write from & to memory and doesn't use page 0 memory ?
 
Without page zero? I think the only way left is self-modifying code. Use a LDA $xx00,X instruction and keep changing the third byte.
 
Correct.

You either must use page 0 or self modifying code. Those are your only two options I am afraid.

Dave
 
There is one other option, you could set up a series of LDA/STA instructions for each page and just run through them or use them as subroutines as appropriate.
Can you show me how to do this without having to have hard coded byte values to code for each page , in this case from page 04 to 7F so as to encode the addresses?
 
Last edited:
It looks like it is time for me to venture into self modifying code. I have not done this before. Mainly because the books seem to advise against it and my coding skills are limited, but if it is the only way , without having to hard code a large array of bytes, then it cannot be all that bad and I am bound to learn something from it. I will report back if I succeed.
 
Self-modifying code is the spawn of the devil and should be avoided at all costs! However, if you must do it, you must do it...

I suppose my dumb question would be "why not use page 0"? That is what it is there for...

If you must use self modifying code, try and use a 'generic' subroutine to do the Harry Potter magic - and that should be less hassle in the long run!

Try something like this...

Code:
        ORG     $0380
      
        LDX     #$08    ; Byte offset.
        LDA     #$55    ; Value to store.
        LDY     #$07    ; Page number.
        JSR     WRITE   ; Do the write.
        LDA     #$00    ; Zap out accumulator A.
        JSR     READ    ; Do the read.
        BRK             ; Kill and go back to TIM/MLM.
      
        ; Subroutines.
      
WRITE:
        STY     SMCW    ; Modify the page number of the next instruction.
        STA     $FF00,X ; Store A away in a defined memory page (Y) offset by X.
SMCW    EQU     $-1     ; This makes the offset a nice symbol.
        RTS             ; Return to caller.
      
READ: 
        STY     SMCR    ; Modify the page number of the next instruction.
        LDA     $FF00,X ; Load A from a defined memory page (Y) offset by X.
SMCR    EQU     $-1     ; This makes the offset a nice symbol.
        RTS             ; Return to caller.
      
        END

Dave
 
Last edited:
Yea, honestly, "not using page zero" is right up there with "not using the Accumulator".

If it's just an exercise, then, great. Have at it. But if it's for some other reason, at worst you can preserve the ZP locations on the stack and restore them when you're done.

My first machine code was self modifying, just more manifest of not know what I was doing than anything else.
 
Yea, honestly, "not using page zero" is right up there with "not using the Accumulator".

If it's just an exercise, then, great. Have at it. But if it's for some other reason, at worst you can preserve the ZP locations on the stack and restore them when you're done.

My first machine code was self modifying, just more manifest of not know what I was doing than anything else.
There was a reason for it, to avoid page 0.

In the PET, BASIC extensively uses page 0 and I wanted to go to an assembly language subroutine in the spare area in the high end of page 03 where the cassette buffers are, so as to write a program there to check DRAM memory from page 04 upwards, and return to BASIC afterwards, without upsetting or corrupting BASIC.
 
Self-modifying code is the spawn of the devil and should be avoided at all costs! However, if you must do it, you must do it...

I suppose my dumb question would be "why not use page 0"? That is what it is there for...

If you must use self modifying code, try and use a 'generic' subroutine to do the Harry Potter magic - and that should be less hassle in the long run!

Try something like this...

Code:
        ORG     $0380
     
        LDX     #$08    ; Byte offset.
        LDA     #$55    ; Value to store.
        LDY     #$07    ; Page number.
        JSR     WRITE   ; Do the write.
        LDA     #$00    ; Zap out accumulator A.
        JSR     READ    ; Do the read.
        BRK             ; Kill and go back to TIM/MLM.
     
        ; Subroutines.
     
WRITE:
        STY     SMCW    ; Modify the page number of the next instruction.
        STA     $FF00,X ; Store A away in a defined memory page (Y) offset by X.
SMCW    EQU     $-1     ; This makes the offset a nice symbol.
        RTS             ; Return to caller.
     
READ:
        STY     SMCR    ; Modify the page number of the next instruction.
        LDA     $FF00,X ; Load A from a defined memory page (Y) offset by X.
SMCR    EQU     $-1     ; This makes the offset a nice symbol.
        RTS             ; Return to caller.
     
        END

Dave
Thanks for that example.
 
Hugo,

There is guaranteed spare page 0 memory for user-written code.

I can’t quite remember where it starts (at the moment) but I can find out later for you.

Page 0 becomes a problem (as you are aware of with the AIM-65) when the user ‘expands’ their machine with optional software/firmware.

This is one reason why MAE has a ‘relocatable’ object code format. You can specify (for example) the start of the code, data and page 0 variables without reassembling (from source code) the application. If the developer released the relocatable object code, then the user could perform this relocation step by themselves.

Dave
 
There was a reason for it, to avoid page 0.

In the PET, BASIC extensively uses page 0 and I wanted to go to an assembly language subroutine in the spare area in the high end of page 03 where the cassette buffers are, so as to write a program there to check DRAM memory from page 04 upwards, and return to BASIC afterwards, without upsetting or corrupting BASIC.
If that is your only reason then you are in luck. There's lots of areas that you can use in zero page without worrying BASIC. I'm not as familiar with the PET, but I'm pretty sure you could use unused areas such as: $00A2 and $00FF. You could also use areas that are only used temporarily by BASIC such as the floating point areas, some of the cassette tape areas, etc.
 
If that is your only reason then you are in luck. There's lots of areas that you can use in zero page without worrying BASIC. I'm not as familiar with the PET, but I'm pretty sure you could use unused areas such as: $00A2 and $00FF. You could also use areas that are only used temporarily by BASIC such as the floating point areas, some of the cassette tape areas, etc.
Yes, I am already using the spare cassette tape areas for the program. A book I have which shows the usage of page 0 by BASIC, shows it is very heavily used, and to be 100% sure not to mess it up, I decided to simply stay away from memory below $0400, except for the cassette areas on page 03 where the program sits. In the end this program will be in ROM with a hardware circuit on the expansion connector that also dynamically deactivates the existing DRAM memory below $0400 and electrically replaces it with ROM & SRAM and it tests the remaining DRAM to the very near end of page 7F.

One memory test program I have seen started at $0484. I also found that writing to the BASIC program area immediately above $0400 can sometimes upset BASIC, even without a saved or executed program, but just some immediate commands. So despite being said that $0400 to $7FFF is user memory, it is a little misleading. Experimenting at least, BASIC is never affected by any forced memory changes from page 05 to page 7E.

One interesting thing, is that BASIC appears to use the top 15 bytes on page 7F (in a 32k PET), if these get altered it sometimes corrupts BASIC, but at this point I'm not sure why unless it is something to do with the way BASIC assesses the value of high memory, but that would only likely be used at boot up, so that would not explain it. I also learnt very quickly to preserve and restore registers around the assembly language routine or things also get corrupted after the return.

I have got the self modifying code working now. It was not too difficult, though I had to assemble it once first to figure out where the byte was I needed to change as I could not trust myself to count it from the start from the expected code byte usage and not make a mistake.

The current DRAM in my 32k PET is defective, there are 3 and sometimes 4 bytes in high page 06 that are faulty, but here is the interesting thing: if I write to those locations (whether with BASIC pokes or my assembly language routine) and immediately return the value just after writing it, for a test, it looks ok. If I write there and read sometime later, after I have filled the near entire memory, the bytes in that location have changed. If I check with the PETTESTER it passes. Either there is something wrong with the retention of data just in those 4 locations, or writing to other locations in memory has caused them to change when they should not have. So I am trying to make my memory test program pick up this sort of odd DRAM memory problem.
 
My PETTESTER uses the 'standard' MARCH-C DRAM memory test, so should detect writing to one address affecting another.

The more likely possibility is data retention issues. MARCH-C doesn't test that as standard - but the refresh cycles are outside of my (software) control...

Dave
 
In the PET, BASIC extensively uses page 0
Sure. To be fair, my first 6502 code of note was on a PET, and it did use self modifying code.

That said, even with BASICs heavy use of ZP, as long as you don't pick locations that are used by some interrupt routine (and thus potentially changed behind your back), you can simply preserve any ZP locations you wish to use at the start or your routine and restore them when you're done with BASIC not being the wiser for it.
 
My PETTESTER uses the 'standard' MARCH-C DRAM memory test, so should detect writing to one address affecting another.

The more likely possibility is data retention issues. MARCH-C doesn't test that as standard - but the refresh cycles are outside of my (software) control...

Dave
I bought a stand alone 4116 DRAM tester.This tester is supposed to have a protocol that tests the data retention. I have not individually tested the DRAMs in my pet with it yet, but later if I can perfect this memory test program to detect this problem, I will run these IC's through on this tester and see if it can detect faults in any of them. The refresh cycles, might be borderline too far apart for some DRAMS as they are failing.

Makes me prefer SRAM. I have found it is very easy to deactivate the entire DRAM reads in the PET just by taking one connection with a pullup resistor low. And it is dead easy to put SRAM on the expansion connector that is active with an address decoder to activate it from $0000 to $7FFF. So the dynamic PET can easily run SRAM instead and avoid those refresh cycle issues, but then it couldn't rightly be called a "dynamic PET" could it ?
 
So the dynamic PET can easily run SRAM instead and avoid those refresh cycle issues, but then it couldn't rightly be called a "dynamic PET" could it ?

If keeping it original isn’t a concern there are certainly many ways to skin the cat of putting SRAM in there in place of the DRAM. (Under the CPU daughterboards, mostly, but the expansion connector certainly works if you disable the onboard memory select.) There are several flavors of those solutions out there for sale.

I don’t know if what you’re seeing could be related, but I actually have two Dynamic boards that have an intermittent *video* memory corruption issue; run something that updates the screen constantly, like a video game, and the occasional write will get corrupted or misplaced. The SRAM isn’t the problem, I can swap it into another board I have and it’s fine. My suspicion is that there’s something wonky happening with the address multiplexer circuit that flips the address presented to the vram between the CPU and the video address generator circuit, and it might be an “analog” problem because some aging component is messing with signal timing. (And I somehow have managed to leave figuring this out on my to-do list for almost a decade at this point.)

Anyway, it wouldn’t surprise me at all if some similar subtle issue could randomly happen in the address multiplexing circuitry that drives the DRAM in these things, that stuff isn’t any younger and timing is probably even more critical.
 
Stupid question, I know--but if you have spare RAM in other areas, why not save the cells you need in page 0 elsewhere and then restore them when you're done?
 
Back
Top