6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Nov 23, 2024 12:14 am

All times are UTC




Post new topic Reply to topic  [ 48 posts ]  Go to page 1, 2, 3, 4  Next
Author Message
PostPosted: Wed Apr 01, 2020 12:31 am 
Offline

Joined: Fri Nov 16, 2018 8:55 pm
Posts: 71
Okay, so I'm working my way through C64 Machine Language for Beginners by Danny Davis, Melbourne House (1984) as a 6502 primer. On the whole I really like the book. As a rule it's not moving too fast for and it pretty darn through about explaining itself the vast majority of the time.

I'm working through this text in parallel with my efforts to dissassemble a C64 Cartridge and convert it to working fully commented code. Doing these two things at the same time has been very helpful. As I'm disassembling entirely by hand (no automatic disassembly) I'm only about 10% done. My progress in the book has been quite solid, I'm about 60% done. The only "hiccup" up to this point was 2's compliment arithmetic when performing branching instructions. From what I understand, that confuses everyone the first time around.

Unfortunately, I've hit one of those places where the sample program isn't fully intelligible and the book's explanation... isn't what it could be and I'm just not 100% following. Googling around hasn't helped much. I did check my favorite 6502 Instruction Set Reference site before posting.

In summary: the point of the program is to look for the value $a9 in memory and (exit?) when it encounters the 4th occurrence. I think it just exits if it wraps around at $ff. Here is the code, with my perhaps questionable comments after the semi-colon. This is on page 59:

Code:
        ldx #$00        ; X is a counter, it increments upon encountering $a9
        ldy #$00        ; Y is an index into memory a memory location.
        lda #$a9        ; A has the value to be compared.
L40     cmp $f000,y     ; Compare somewhere in RAM mapped to KERNAL ROM? Y indexed memory.
        beq L90         ; Why am I checking Z and jumping forward? Is this spaghetti code?
L60     iny             ; Y++ and Y indexed memory is offset by the new Y value.
        bne L40         ; Z!=0? What's setting Z? Is this effectively a greater than statement? If so, why?
        stx $0334       ; X is stored here -- as incremented when $a9 is encountered.
        rts             ; Exit to BASIC?
L90     inx             ; X++
        cpx #$04        ; X != 4?
        bne L60         ; If not, branch to L60 and continue.
        stx $0334       ; X is stored here, the same as 5 lines up. Why is this here twice?
        rts             ; Exit to BASIC?


Any help more completely grasping what I'm seeing would be helpful. I don't want to move on to chapter 8 until I get this.


Top
 Profile  
Reply with quote  
PostPosted: Wed Apr 01, 2020 1:04 am 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
Okay, I think I have this. In logical order to aid understanding:

Quote:
Compare somewhere in RAM mapped to KERNAL ROM?

I think it's using KERNAL as a source of example data. There should be plenty of $A9 bytes in there, as that's the LDA immediate opcode.

Quote:
Why am I checking Z and jumping forward?

The Z and N flags are set by most instructions that change a register value or perform read-modify-write on memory. In this case CMP-BEQ is a common idiom meaning "if(A == M) then goto …"

Quote:
Z!=0? What's setting Z?

The INY did. If Z is set, then it just wrapped around from 255 to 0. If that didn't happen, then we loop back to test the next byte.

Quote:
stx $0334 (twice)

I think this is a bug. It should be STY, not STX, so that the location where the fourth $A9 was found is stored. Since the routine is supposed to be looking for the fourth $A9, it would be logical to store that. And if you get zero back (after INY wrapped around), you know that there weren't as many as four of them in that page.

Quote:
Exit to BASIC?

Strictly speaking, this returns to whatever called this subroutine. It might be used by a larger machine-language program, rather than being a toy example called directly from BASIC.


Top
 Profile  
Reply with quote  
PostPosted: Wed Apr 01, 2020 2:32 am 
Offline

Joined: Thu Mar 12, 2020 10:04 pm
Posts: 704
Location: North Tejas
load81 wrote:
Code:
L60     iny             ; Y++ and Y indexed memory is offset by the new Y value.
        bne L40         ; Z!=0? What's setting Z? Is this effectively a greater than statement? If so, why?



The INY instruction sets the Z flag if incrementing register Y results in a zero value, otherwise Z is cleared.

I found the book and the program is looking for $A9 within 255 bytes of the given address.

Their program actually contains a bug - it actually looks for $A9 within 256 bytes of $F000!

I am not a C64 expert and will have to research the meaning of storing a value at $0334. It is probably a way to get a result back to a BASIC program.


Top
 Profile  
Reply with quote  
PostPosted: Wed Apr 01, 2020 2:38 am 
Offline

Joined: Thu Mar 12, 2020 10:04 pm
Posts: 704
Location: North Tejas
Chromatix wrote:
Quote:
stx $0334 (twice)

I think this is a bug. It should be STY, not STX, so that the location where the fourth $A9 was found is stored. Since the routine is supposed to be looking for the fourth $A9, it would be logical to store that. And if you get zero back (after INY wrapped around), you know that there weren't as many as four of them in that page.



No, it is not a bug.

There are two ways to return from that subroutine: one when $A9 is found four times and one when Y wraps around. In both places, X contains the number of times $A9 has been found.


Top
 Profile  
Reply with quote  
PostPosted: Wed Apr 01, 2020 2:45 am 
Offline

Joined: Fri Nov 16, 2018 8:55 pm
Posts: 71
BillG wrote:
I am not a C64 expert and will have to research the meaning of storing a value at $0334. It is probably a way to get a result back to a BASIC program.


If my memory isn't playing tricks on me, I think that's somewhere in the cassette tape buffer. In which case, it's just a convenient place to store data that isn't likely to be overwritten while using their ML input program. I don't think BASIC has any special access into this area of memory.


Top
 Profile  
Reply with quote  
PostPosted: Wed Apr 01, 2020 2:48 am 
Offline

Joined: Thu Mar 12, 2020 10:04 pm
Posts: 704
Location: North Tejas
If you will look on page 147, location $0334 is unused, so it is a safe place to pass a value back without crashing BASIC.


Top
 Profile  
Reply with quote  
PostPosted: Wed Apr 01, 2020 2:50 am 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
The book happens to be online at archive.org.

I found the relevant program on page 57, not 59. There is a comment in the text that changing STX to STY does indeed store the address of the fourth $A9, rather than the count. They're using a WATCH command to monitor the contents of that address, rather than attempting to examine the 6502 registers themselves.


Top
 Profile  
Reply with quote  
PostPosted: Wed Apr 01, 2020 2:52 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8544
Location: Southern California
There are lots of simple programming tricks, with links to more, on the 6502 primer's programming tips page, at http://wilsonminesco.com/6502primer/PgmTips.html . These may answer many of the questions you come up with regarding why various things were done the way they were like your INY, BNE.

_________________
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: Wed Apr 01, 2020 2:56 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1949
Location: Sacramento, CA, USA
In my own experience, non-trivial assembly language examples have a natural tendency to resemble spaghetti code, unless some more advanced assembler features like macros and local labels are brought into play, and even then it's probably not going to look much like PASCAL. That being said, it looks like this particular example is a bit more "spaghettiish" than necessary ... I'll abstain from any further unprompted criticism, because doing so would have questionable educational value. Have fun and keep learning! :)

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


Last edited by barrym95838 on Wed Apr 01, 2020 3:03 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Wed Apr 01, 2020 2:59 am 
Offline

Joined: Fri Nov 16, 2018 8:55 pm
Posts: 71
BillG wrote:
Their program actually contains a bug - it actually looks for $A9 within 256 bytes of $F000!


Wait... are you saying the BNE and the INY should be swapped to eliminate an off-by-one error? If so, can you spell it out for me as to why this is true?


Top
 Profile  
Reply with quote  
PostPosted: Wed Apr 01, 2020 3:20 am 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
Let me be more precise. The program checks a range of 256 bytes, at offsets 0 to 255 inclusive from the base address, $F000. So in fact it *does* cover "within 255 bytes" of $F000. There is no bug in that respect.

You may find it helpful to work through the CPU's operation by hand, step by step, and pay attention to what happens when.


Top
 Profile  
Reply with quote  
PostPosted: Wed Apr 01, 2020 3:37 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1949
Location: Sacramento, CA, USA
Reiterating what Chromatix said, a significant percentage of 65xx instructions have condition flag side-effects, perhaps a higher percentage than any other microprocessor I can recall. Getting these side-effects to work for you rather than against you is one of the keys to writing (and understanding) efficient code, and requires some practice and careful thought.

_________________
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: Wed Apr 01, 2020 6:31 am 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 730
Location: Tokyo, Japan
Just a few addenda to all the other good comments:

First, until you fully memorize which instructions affect which flags (which will come naturally over time), carefully check a reference sheet for that information for the instruction preceeding a branch and further instructions back if necessary. That information is on the Masswerk sheet you linked, but you may find the Obelisk Instruction Reference easier to read, at least at first.

So when we look at code like this:
Code:
        lda #$a9        ; A has the value to be compared.
L40     cmp $f000,y     ; Compare somewhere in RAM mapped to KERNAL ROM? Y indexed memory.
        beq L90         ; Why am I checking Z and jumping forward? Is this spaghetti code?

we can analyze it as follows:

  • Looking up BEQ, we see that it branches if the Z (zero) flag is set and continues forward if the Z flag is clear. (Essentially, it's asking if some result of the last instruction to set it was equal to zero.)
  • Looking backwards at CMP, we see that it indeed sets/clears the zero flag based on its result. CMP subtracts memory from the accumulator, in this case A - ($F000+Y), reading the parens as "the contents of." Since if A is $A9 and the contents of the memory location is $A9, and $A9-$A9 = $00, that would set the zero flag and we now see why the branch instruction is named BEQ. The negative and carry flags will be set as appropriate as well, but the result will be thrown away, rather than being stored somewhere.
  • But what number are we comparing? If I hadn't inserted the spoiler above, you'd look back at the previous instruction yet and see that it's a LDA #$A9, so now we know what was in the A register when the CMP instruction was executed.

As for spaghetti code, well, the standard in 6502 assembler for that is pretty high: remember, all we have is conditional GOTO, so the only result of a test is either to continue forward or jump to somewhere else. But in this case, we have a loop with an exception condition in it that we jump out to from the middle, and when done with that jump back into the middle of the loop, so it is a bit confusing. But having to do this is not infrequent in assembler.

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Thu Apr 02, 2020 8:00 pm 
Offline

Joined: Sun Nov 08, 2009 1:56 am
Posts: 411
Location: Minnesota
If you prefer, the example program can be re-written in any number of ways. Here's one:

Code:
         ldx #0         ; we're going to count to four
         ldy #0         ; we're going to index off an absolute address
         lda #a9        ; this is the byte we're looking for
L1       cmp $f000, y   ; subtract the value at $f000+y from the accumulator, throw the result away but do set the processor flags
         bne  L2        ; branch if no match
         inx            ; found another one
         cpx #4         ; subtract the value 4 from the x-register, throw the result away but do set the processor flags
         beq L3         ; branch if we found all we're looking for
L2       iny            ; index to the next memory location and set the z-flag if we've wrapped the full range of the y-register
         bne L1         ; branch if we haven't looked at the full page yet ($F000 - $F0FF)
L3       stx $0334      ; save however many we actually found
         rts            ; return to caller


Top
 Profile  
Reply with quote  
PostPosted: Thu Apr 02, 2020 8:39 pm 
Offline
User avatar

Joined: Tue Mar 02, 2004 8:55 am
Posts: 996
Location: Berkshire, UK
It can even be structured...
Code:
00:0300  A200              :                 ldx     #0              ; we're going to count to four
00:0302  A000              :                 ldy     #0              ; we're going to index off an absolute address
00:0304  A9A9              :                 lda     #$a9            ; this is the byte we're looking for
                                             repeat
00:0306  D900F0            :                  cmp    $f000,y         ; subtract the value at $f000+y from the accumulator, throw the result away but do set the processor flags
00:0309  D005              :                  if eq
00:030B  E8                :                   inx                   ; found another one
00:030C  E004              :                   cpx #4                ; subtract the value 4 from the x-register, throw the result away but do set the processor flags
00:030E  F003              :                   break eq              ; branch if we found all we're looking for
                                              endif
00:0310  C8                :                  iny                    ; index to the next memory location and set the z-flag if we've wrapped the full range of the y-register
00:0311  D0F3              :                 until eq                ; branch if we haven't looked at the full page yet ($F000 - $F0FF)
00:0313  8E3403            :                 stx     $0334           ; save however many we actually found
00:0316  60                :                 rts                     ; return to caller

.. which removes the need for labels.

_________________
Andrew Jacobs
6502 & PIC Stuff - http://www.obelisk.me.uk/
Cross-Platform 6502/65C02/65816 Macro Assembler - http://www.obelisk.me.uk/dev65/
Open Source Projects - https://github.com/andrew-jacobs


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

All times are UTC


Who is online

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