6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Nov 23, 2024 9:30 pm

All times are UTC




Post new topic Reply to topic  [ 65 posts ]  Go to page 1, 2, 3, 4, 5  Next
Author Message
PostPosted: Sun Oct 13, 2019 7:07 am 
Offline

Joined: Sun Oct 13, 2019 6:20 am
Posts: 36
Location: Pennsylvania, USA
Hello everyone! I'm very very very new to 6502 Assembler. Aw heck, to coding in general and I've been working on tiny simple coding projects because you can't learn how to walk until you crawl.

I'm still learning how to wrap my head around handling High Bytes and Low Bytes. I swear, making 16-bit values with only 8-bit registers is like trying to drive a car that can only turn right. If you want to turn left, you need to find away to safely turn the car right three times without crashing.

I'm working with a generic virtual machine and the memory addresses for the display data is between $0200 and $05ff.

My plan is to make an incrementing 16 bit number starting at 0200 and increments until it hits 05ff. Every time it increments, the high byte and low byte will be stored in the zero page and the program will read the high and low bytes to see where the display data needs to be written in that loop. Basically, I want to use this pointer to "sweep the display" with a single color.

This program is designed to be run in the dirt simple Easy6502 Virtual Machine

https://skilldrick.github.io/easy6502/

LDA #$01 ;Initial values
LDX #$00
LDY #$02
STX $00 ;low byte
STY $01 ;high byte

mainloop: ;placeholder program loop
JSR counter ;increment pointer
LDY $01 ;checking high byte
CPY #$05
BNE mainloop ;loop again
LDX $00 ;checking low byte
CPX #$ff
BNE mainloop ;loop again
BRK ;when value is 05ff, end


counter: ;incrementing subroutine

l_up: ;increment low byte
LDX $00
INC $00
CPX #$ff ;check if high byte needs to be incremented
BEQ h_up
RTS ;if not, return to main loop

h_up: ;increment high byte and return to main loop
INC $01
RTS

The snag I'm getting into is how to get the program to read the recorded values. Then treating it as a display data pointer, so I can load color values onto the display.

I feel like it involves Indexed Indirect Addressing somehow, but I for the life of me can't figure it out. Can someone here please tell me the super simple and obvious thing im completely over looking? Because I'm stumped.

Thanks for humoring me. :D


Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 13, 2019 8:00 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8546
Location: Southern California
Welcome.

It sounds like you want to write the same value to every location from $200 to $5FF. Is that right? How about the following. Note that there are no subroutine calls, and the loop usually only needs to execute three instructions.
Code:
                          ; (Set up a variable however your assembler does it.  Here I just
POINTER: EQU 0            ; made it address 0; but normally you'll tell the assembler where
                          ; to start, then let it decide the addresses as you pile on more.

FOOBAR:  STZ  POINTER     ; (Enter here with A holding the number you want to store.)  Zero
         LDY  #2          ; the pointer's low byte,
         STY  POINTER+1   ; then make the high byte to be 2 (without overwriting A).

         LDY  #0          ; Init the index value which will be added to the indirect addr.
 loop:   STA  (POINTER),Y ; Store your value.
         INY              ; Increment the index.
         BNE  loop        ; As long as it's not 0, loop again.

         INC  POINTER+1   ; But if it _is_ 0, increment the high byte of the pointer.
         LDX  POINTER+1
         CPX  #6          ; Did that turn it into a 6?
         BNE  loop        ; If not, you're not done, so keep looping.  (Y is 0 again, so
                          ; you don't have to re-initialze it.)
 done:

So the first four program lines (including the LDY #0) are just setup. The next three are executed for each byte of each page. The last four are executed only four time, once for each time you finish a page and are getting ready to start a new page. I didn't test it, but it's not too complicated, so hopefully I didn't make an embarrassing mistake.

_________________
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: Sun Oct 13, 2019 8:14 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8546
Location: Southern California
If I can be naughty and whet your appetite to step up to the 65816 after you get your feet wet, for the 65816, you don't even need a variable, and the whole thing is only the following. (Start with A in 8-bit mode and index registers in 16-bit mode.)

Code:
FOOBAR: LDX  #$3FF
 loop:  STA  $200,X
        DEX
        BPL  loop

_________________
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: Sun Oct 13, 2019 8:50 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10986
Location: England
Welcome EvilSandwich! Using easy6502 should be a good way to get started. You can single-step your program and watch memory changing, and you'll find that can be a lot more illuminating than running the whole program and seeing the end result.

I thought I saw an obvious error or two in your incrementing routine, but on closer inspection the error is only in my head. It's true that your routine doesn't proceed the way I expected, but that's perhaps a later question about the various idioms which make efficient use of the 6502. (Hint: you can increment your zero page values without needing to use an additional register, in your case X.)

Indeed, in your main program you're using X and Y where you don't strictly need to: that's normal for a beginner, but it has given you the impression that there's lots of shuffling to be done. Don't worry - you'll get there!

So, by reading your program, it looks to me like it should increment your two byte pointer at 00, as you want it. But as you note, you're not yet making use of that pointer to write to the display.

And yes, normally the way to do that is indirect addressing. (Another way is to modify code: write bytes to update an absolute address that's part of your program. This isn't recommended, for a number of reasons, although sometimes it's the right thing to do. Think of it as a very sharp knife or a power tool without a safety guard.)

So, the simple way is like so:
LDA #colour
LDY #0
STA (screenpointer),Y
where I've used symbolic names to make things easier.

Of course, using Y just to always hold a zero value isn't always the most efficient and expert way, but it works.

I think it would be great for you to keep working on your original code, and modify it until it works. There are, as always, many ways to solve this problem. With respect to Garth, I don't recommend you just paste in some working code. Nor do I recommend you start thinking about alternative CPUs or different emulators. You're nearly there - persevere!

It is always worthwhile reading through code, whether because it's good code and you want to understand it, or because it's not working and you need to fix it. Initially you might want to step through with pencil and paper, updating A, X, Y and the various status bits on the paper. Eventually, you can do that in your head.


Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 13, 2019 3:16 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1488
Location: Scotland
Hi,

So here is the issue ... ask a dozen programmers, get a bakers dozen answers...

A few things here and one is picking a different way to do it.

So the issue is to fill a section of RAM with a fixed value. For your range: $200 through $5FF, personally, I'd un-wind the loop and not use a zero page pointer at all. This is at the expense of code size, but the advantage is speed. Much faster, so:

Code:
; fill:
;       Value passed in A

fill:
        ldy     #0
fill1:
        sta     $0200,y
        sta     $0300,y
        sta     $0400,y
        sta     $0500,y
        iny
        bne     fill1
        rts


Actually, looking at that now, it might even be smaller than a conventional pointer based fill + increment + compare type loop.

Tackling the original scenario, break it down, so the increment:

Code:
; inc16:
;       Add 1 to a 16-bit pointer in zero page

inc16:
        inc     ptr
        bne     :+
        inc     ptr+1
:       rts


You can see there that there is no need to do a compare to handle the wrap round because the 6502 explicitly sets condition flags based on the last operation. In this scenario, when we increment 'ptr' (the low byte) when it increments from $FF through to $00, then the Zero flag will be set, so we can simply test for it.

The compare part (in the original scenario) can simply be to test against $600 - this is easy (a single byte compare) but needs the loop to work like:

Code:
mainFill:
        ldy     #$02
        sty     ptr+1   ; High byte
        ldy     #$00
        sty     ptr     ; Low byte (leaves Y=0)
mainFill1:
        sta     (ptr),y
        jsr     inc16
        lda     ptr+1   ; High byte
        cmp     #$06
        bne     mainFill1
        rts


If you are using as 65C02 then this can be further optimised, but on simple way would be to in-line the call to inc16 - that'll save a jsr/rts, but will add a few more bytes to the code. Also look at Garths example above where he uses Y as an index in conjunction with the pointer. (Told you there was more than one way to do it ;-) It may not be as fast as the original one I write though, but might give you some ideas of different ways to accomplish the same thing.

Finally - do note that if you're looking at a lot of old 6502 code, then it's often been written to optimise for space rather than speed. Mask programmable ROMs were expensive back then...

Cheers,

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 13, 2019 8:11 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8546
Location: Southern California
Gordon and Ed are of course right. After a few episodes like this, you'd think I'd learn not to post late at night. Fortunately most of the members here are pretty gracious.

We often think of "unrolling the loop" as being for trading memory efficiency for speed; but Gordon shows how it can actually be more memory-efficient as well. It works especially well when you want to treat the same number of bytes in every page. I think I'll add it to the programming tips page of the 6502 primer.

Quote:
If you are using a 65C02 then this can be further optimised,

...which I always recommend it for new builds. If you're writing code for something like the Commodore 64 which never had the CMOS version available for it, you'll have to stay with the NMOS 6502's more-limited set of instructions and addressing modes. Otherwise, make life easier for yourself and use the CMOS. (I have a comparison here .)

Quote:
(Another way is to modify code: write bytes to update an absolute address that's part of your program. This isn't recommended, for a number of reasons, although sometimes it's the right thing to do. Think of it as a very sharp knife or a power tool without a safety guard.)

Ah yes, self-modifying code (SMC) is easy to do on the '02, and is a good solution to certain programming problems. As Ed says though, it requires more care so you don't wind up with bugs that are hard to figure out.

_________________
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: Sun Oct 13, 2019 8:20 pm 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1949
Location: Sacramento, CA, USA
drogon wrote:
Hi,

So here is the issue ... ask a dozen programmers, get a bakers dozen answers...

A few things here and one is picking a different way to do it.

So the issue is to fill a section of RAM with a fixed value. For your range: $200 through $5FF, personally, I'd un-wind the loop and not use a zero page pointer at all. This is at the expense of code size, but the advantage is speed. Much faster, so:

Code:
; fill:
;       Value passed in A

fill:
        ldy     #0
fill1:
        sta     $0200,y
        sta     $0300,y
        sta     $0400,y
        sta     $0500,y
        iny
        bne     fill1
        rts


Actually, looking at that now, it might even be smaller than a conventional pointer based fill + increment + compare type loop.

This is my personal favorite of the bunch so far. It's simpler, faster and smaller than any others I can imagine at the moment, and it faithfully embodies the 6502 idiom ... win-win!

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


Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 13, 2019 9:20 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10986
Location: England
Nice to see various solutions to the screen-filling problem, but it'd be great to help EvilSandwich get their code working. That's the real problem: how to learn how to get the 6502 to do things, or how to teach how to get the 6502 to do things.

Just as many people have problems understanding pointers in C, it's likely that many people have problems getting to grips with the indirect addressing of the 6502. (Especially as both the available modes are indexed - we don't have an unindexed mode, on the 6502. That comes in later revisions.)


Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 13, 2019 9:37 pm 
Offline

Joined: Sun Oct 13, 2019 6:20 am
Posts: 36
Location: Pennsylvania, USA
Thanks for the help guys!

I haven't had this many people ready and willing to assist me on a public forum since I was looking for help on a Mint community message board. Those Linux guys are so happy for new blood, they'll roll out the red carpet no matter how dumb my question was. haha

I'll need time to unpack everything you guys gave me, but since I need to leave for work in 15 minutes (the downside of only being on the hobby level of coding, you still need to keep the lights on with a boring 9 to 5).

GARTHWILSON wrote:
If I can be naughty and whet your appetite to step up to the 65816 after you get your feet wet, for the 65816, you don't even need a variable, and the whole thing is only the following. (Start with A in 8-bit mode and index registers in 16-bit mode.)

Code:
FOOBAR: LDX  #$3FF
 loop:  STA  $200,X
        DEX
        BPL  loop


Ooof, I've been staring at all the pretty features of the objectively better 65816 as I've been learning. With its more robust opcode list, 16-bit index registers and TWO Accumulators. <3

Unfortunately, I've been mostly learning the 6502 in order to work with the Ricoh 2A03 on the NES, which is pretty much chained down to the basic 6502 design. Another reason why I've been trying to optimize my code for being as compact as possible. With the C64, you have over 38K of RAM to work with. The nintendo... not so much. lol

BigEd wrote:
Nice to see various solutions to the screen-filling problem, but it'd be great to help EvilSandwich get their code working. That's the real problem: how to learn how to get the 6502 to do things, or how to teach how to get the 6502 to do things.

Just as many people have problems understanding pointers in C, it's likely that many people have problems getting to grips with the indirect addressing of the 6502. (Especially as both the available modes are indexed - we don't have an unindexed mode, on the 6502. That comes in later revisions.)


Yeah, I'm more focused on just getting the indirect addressing to work first, before I go about making the code less amateur hour. Pretty much all these projects are strictly for teaching me how to get used to certain features. If you're wondering why I put a JSR/RTS in there for no reason, it just because i'm trying to get used to making modular subroutines that can be inserted into different programs later.

I'll get to all of you later, because you guys have been really helpful. (I didn't even know the "STA address +1" was something you could even do).

For now, off to the boring job.


Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 13, 2019 11:14 pm 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
The '816 doesn't really have two accumulators. It has one accumulator that can be in 8 or 16 bit mode (like the index registers), and the bytes of which can be swapped over. Alas, WDC's official documentation can be very confusing on certain subtleties; it seems like they tried to borrow some Motorola terminology and failed.

The undeniable extra power of the new addressing modes is countered somewhat by the poor match of the old 6502 assembly syntax to the new paradigm. There are four different address *spaces* (Direct, Absolute, Long, and Program) which overlap each other to some degree, depending on the values of bank and pointer registers, but in which address 0 can refer to four completely different places. So almost the first thing you have to learn is how your particular assembler disambiguates those cases, and I guarantee it'll give you a headache.

Anyway, to answer the original question, this is how I would increment a 16-bit value on the 6502:
Code:
  INC v+0
  BNE :+
  INC v+1
: ; target of BNE


Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 13, 2019 11:24 pm 
Offline
User avatar

Joined: Tue Mar 05, 2013 4:31 am
Posts: 1385
As noted already, multiple ways to skin a cat (with code) for this kind of stuff. In addition to incrementing a 16-bit pointer (in page zero), there's also the typical need to decrement the same 16-bit pointer. Here's a couple routines I used to increment and decrement a 16-bit pointer which is used as an INDEX for memory ops within a monitor program. I simply call the page zero locations INDEXL and INDEXH, for the low order and high order bytes of the INDEX. You can them anything you like of course... and I have multiple sets of 16-bit pointers within my Monitor code.

Code:
;DECINDEX subroutine: decrement 16 bit variable INDEXL/INDEXH
DECINDEX        LDA     INDEXL          ;Get index low byte
                BNE     SKP_IDXH        ;Test for INDEXL = zero
                DEC     INDEXH          ;Decrement index high byte
SKP_IDXH        DEC     INDEXL          ;Decrement index low byte
                RTS                     ;Return to caller
;
;INCINDEX subroutine: increment 16 bit variable INDEXL/INDEXH
INCINDEX        INC     INDEXL          ;Increment index low byte
                BNE     SKP_IDX         ;If not zero, skip high byte
                INC     INDEXH          ;Increment index high byte
SKP_IDX         RTS                     ;Return to caller
;

_________________
Regards, KM
https://github.com/floobydust


Top
 Profile  
Reply with quote  
PostPosted: Mon Oct 14, 2019 12:09 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8546
Location: Southern California
Quote:
Just as many people have problems understanding pointers in C, it's likely that many people have problems getting to grips with the indirect addressing of the 6502. (Especially as both the available modes are indexed - we don't have an unindexed mode, on the 6502. That comes in later revisions.)

Yep, that came on the 65C02 (CMOS), which can do non-indexed indirects. EvilSandwich, the thing to remember is that what's inside the parentheses is what gets used to point to an address.

  • In the case of (zp,X) indexing, the X always goes inside the parentheses, so X gets added to the operand before trotting off to look up the content of the resulting address to act upon.

  • In the case of (zp),Y indexing, the processor first looks up the content of the address pointed to by the ZP location, and then adds Y to that resulting address to get the final address to act upon.

Hopefully that clarifies it, rather than adding to the confusion.

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.

Quote:
(I didn't even know the "STA address +1" was something you could even do).

That's something done in the assembler itself. It's not an addressing mode in the '02.

_________________
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: Mon Oct 14, 2019 1:00 am 
Offline

Joined: Sun Oct 13, 2019 6:20 am
Posts: 36
Location: Pennsylvania, USA
drogon wrote:
Hi,

So here is the issue ... ask a dozen programmers, get a bakers dozen answers...

A few things here and one is picking a different way to do it.

So the issue is to fill a section of RAM with a fixed value. For your range: $200 through $5FF, personally, I'd un-wind the loop and not use a zero page pointer at all. This is at the expense of code size, but the advantage is speed. Much faster, so:

Code:
; fill:
;       Value passed in A

fill:
        ldy     #0
fill1:
        sta     $0200,y
        sta     $0300,y
        sta     $0400,y
        sta     $0500,y
        iny
        bne     fill1
        rts


Actually, looking at that now, it might even be smaller than a conventional pointer based fill + increment + compare type loop.

That's actually very close to my first version of the code. The only reason I abandoned it was because the "Top to Bottom" fill was a specific effect that I was going for and this will fill the four horizontal sections at the same time.

Your way is better and faster, but the top to bottom sweep was something I did to force me to consider incrementing the location on a 16-bit level. In case I was ever in that situation.

GARTHWILSON wrote:
Quote:
Quote:
(I didn't even know the "STA address +1" was something you could even do).

That's something done in the assembler itself. It's not an addressing mode in the '02.


That would explain why the javascript assembler spat an error message at me when I tried it out. I think I'm finally starting to reach the limits of what Nick Morgan's helpful little virtual machine can do. lol

Are there any virtual machine assemblers you can recommend that would be able to handle commands like that?


Top
 Profile  
Reply with quote  
PostPosted: Mon Oct 14, 2019 3:23 am 
Offline

Joined: Tue Jul 24, 2012 2:27 am
Posts: 679
Just to be silly...
Code:
 ldy #0
:sta $0200,y
 iny
 bne :-
:sta $0300,y
 iny
 bne :-
:sta $0400,y
 iny
 bne :-
:sta $0500,y
 iny
 bne :-

:P

_________________
WFDis Interactive 6502 Disassembler
AcheronVM: A Reconfigurable 16-bit Virtual CPU for the 6502 Microprocessor


Top
 Profile  
Reply with quote  
PostPosted: Mon Oct 14, 2019 3:45 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1949
Location: Sacramento, CA, USA
EvilSandwich wrote:
That's actually very close to my first version of the code. The only reason I abandoned it was because the "Top to Bottom" fill was a specific effect that I was going for and this will fill the four horizontal sections at the same time.

With a 1 MHz clock drogon's code will execute in 6.4 msec ... blink and you miss it. But I get your point about learning to master different techniques to achieve the desired effects.

I have never heard of a symbolic assembler that didn't allow + or - in its operands. That would be a crippling omission IMO.

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


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 65 posts ]  Go to page 1, 2, 3, 4, 5  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 19 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: