6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Oct 05, 2024 12:19 pm

All times are UTC




Post new topic Reply to topic  [ 26 posts ]  Go to page Previous  1, 2
Author Message
PostPosted: Tue Dec 03, 2019 9:31 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8411
Location: Midwestern USA
GARTHWILSON wrote:
In assembly language, you get control over every teensy detail...After you get your feet wet, you can start venturing into macros, and do things like
Code:
       DISPLAY_IMM    "Press CONTINUE when ready"
       WAIT_FOR_KEY   CONT_KEY

Yep! My source code is liberally peppered with macros. sprints string is certainly less typing than:

Code:
         pea #stgaddr          ;string address
         pea #chan             ;channel number
         pea #sprints          ;function number
         cop #0                ;call function
         bcs error

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


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 04, 2019 2:57 am 
Offline

Joined: Wed Oct 16, 2019 11:17 pm
Posts: 34
BigDumbDinosaur wrote:
Code:
         ldx #<stgaddr         ;string address LSB
         ldy #>stgaddr         ;string address MSB
         stx zpptr             ;ZP pointer LSB
         sty zpptr+1           ;ZP pointer MSB
         ldy #0                ;starting index
;
L0000010 lda (zpptr),y         ;get from string
         beq DONE              ;end of string
;
         jsr putchr            ;write to output
         iny
         bne L0000010          ;do next char
;
DONE     RTS                   ;exit
[/size]
The above uses MOS Technology/WDC syntax (the assembly language standard for the 6502 family) and assumes the string is terminated with a null ($00). Maximum string length is 255 bytes and two locations on zero page are required for the string pointer. Also, it is assumed your "character out" subroutine preserves at least the .Y register.


This looks awesome! Thank you! I'll have to look at this tomorrow when I'm a little more awake.


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 04, 2019 3:07 am 
Offline

Joined: Wed Oct 16, 2019 11:17 pm
Posts: 34
So here's what I've done for now (before I saw BigDumbDinosaur's post).

I defined a string at the end of my file:

Code:
   *=$A000
row_text !text "row: ",$FF," col: ",$EE, $00


The plan being that I would use $FF and $EE as markers for substituting values later on....

Then I wrote a macro:

Code:
!macro show_row .x,.y {
   ldx #$00
ROW_START
   lda row_text,x
   beq ROW_END
   cmp #$FF
   beq ROW_X
   cmp #$EE
   beq ROW_Y
   sta TEXT_START,x
   inx
   jmp ROW_START
ROW_X
   lda .x
   jmp ROW_GO
ROW_Y
   lda .y
ROW_GO
   adc #$2F
   sta TEXT_START,x
   inx
   jmp ROW_START
ROW_END
   lda #$00
   sta TEXT_START,x
   jsr OUT_TEXT
}


You can see how I'm looking for the $FF and $EE in there.

Then I called the macro:

Code:
; accumulator holds the row
   ; .col is address that holds the column
   ; .row is address that will hold the row
   sta .row
   +show_row .row,.col


Then I called my lcd display code and I had a proper string! It worked great.

I have two things I don't quite get, though. First, you can see in my macro I'm adding #$2F to the values in the .row and .col memory addresses. If .row holds #$01, and I add #$2F to it, I should get #$30, which would be ASCII for "0", but "1" displays. I'm not sure why. Second, just before I call the macro, I have one of the values in the accumulator. I store it in a memory location and then call the macro with that memory location. So I'm actually calling the macro with two memory locations. Not that I need to, but I'm curious if there is any way I can call the macro in one line that passes the value in the accumulator and the value in the memory location. I don't think there is, but I wonder about it.

I really can't thank all of you enough. You've been so helpful. I really do appreciate it.


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 04, 2019 4:37 am 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
BigEd wrote:
Many C compilers can also output the machine code they generate: it might help to compile a few fragments and study the code, if there's a simple CPU you're familiar with and a compiler for it. Matt Godbolt has an online demo of such a thing, with many compilers and many targets, at godbolt.org


I'm not at all familiar with the code generated by cc65, but I've often found code from compilers to be much more difficult to understand than hand-written assembly code. Still, it's worth a try if you're interested.

Another source of code examples is Lance Leventhal's 6502 Assembly Language Subroutines, which also includes in the first few chapters a quick but deep introduction to 6502 assembler. That seems to be aimed at people who already know another assembly language, but it might be worth looking through to see how much you understand.

But it kinda feels like you're at a point where you need to make a choice here: do you want to just get your routine done and be finished with all this, or do you want to put in a bit more time and actually learn assembly? I'd strongly recommend the latter; assembly is one of those languages that gives you a lot of insight into all the other programming you do. In which case you probably need to sit down and spend a dozen or two hours with a good book to take you through it from the start. Maybe something like Roger Wagner's Assembly Lines?

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


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 04, 2019 8:38 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10947
Location: England
rickseiden wrote:
If .row holds #$01, and I add #$2F to it, I should get #$30, which would be ASCII for "0", but "1" displays. I'm not sure why.

Most likely you didn't clear the carry before the ADC? Always something worth checking.


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 04, 2019 5:26 pm 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
BigEd wrote:
rickseiden wrote:
If .row holds #$01, and I add #$2F to it, I should get #$30, which would be ASCII for "0", but "1" displays. I'm not sure why.

Most likely you didn't clear the carry before the ADC? Always something worth checking.

This is exactly the problem.

If you look back through the instruction sequence that executed before the ADC instruction, it was as follows for the $EE case. (I've picked this case because it's shorter, but the $FF case is similar.)

Code:
    cmp #$EE
    beq ROW_Y
    lda .y
    adc #$2F

CMP affects the following flags:
  • Z (zero): set if A = operand, otherwise reset. (This is what you're using for the BEQ.)
  • C (carry): set if A >= operand, otherwise reset.
  • N (negative): set if bit 7 of the result is set, otherwise reset.

Since the BEQ was taken, the zero flag must have been set, which means the carry flag must also have been set. Thus your ADC adds $2F from the operand + $01 from the carry, or $30, to the $01 in A, giving you $31.

The easy fix, which you should do now, is to do a CLC before the ADC. You know you want to add exactly $2F and don't want whatever's in the carry bit. (You should generally always do a CLC before an ADC, and correspondingly an SEC before an SBC, unless you know you want to propagate the carry from a previous add or subtract.)

The optimized fix is to use ADC #$2E instead. The reasoning behind this is:
  1. The only way you can enter ROW_GO is from ROW_X or ROW_Y.
  2. The only way you can enter ROW_X or ROW_Y is from the two BEQs in ROW_START.
  3. In both those cases, the BEQ is after a CMP, so if the BEQ is taken the carry flag will always be set, as described above.
  4. Thus, carry is always set when ROW_GO's ADC is executed, so you can save yourself the CLC instruction by taking into account that set carry when doing the ADC, compensating by subtracting one from the constant value you're adding.

Of course, you need to keep in mind that this is happening whenever you change the code, so that you don't accidentally make a change that then invalidates the above reasoning. (This is why you generally leave microoptimizations like this for later, once the code has settled.)

And that's the joy of assembler that other languages can't give you! (Or if a program in another language does give you that kind of joy, you usually fix the program. :-))

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


Top
 Profile  
Reply with quote  
PostPosted: Wed Dec 04, 2019 10:30 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8411
Location: Midwestern USA
rickseiden wrote:
This looks awesome! Thank you! I'll have to look at this tomorrow when I'm a little more awake.

Yer welcome. It's actually pretty standard code.

The illustrated method by which a 16-bit address is passed to and used within a subroutine is a common 6502 idiom due to the lack of 16-bit registers. Also, use of zero page to point to objects (data, string constants, etc.) is another basic 6502 idiom. These two sequences are so common in 6502 assembly language programs of any significant size I wrote macros to handle them:

Code:
;load .X & .Y immediate with object pointer
;
lxyi     .macro .addr          ;'lxyi <object>'
         ldx #<.addr           ;LSB
         ldy #>.addr           ;MSB
         .endm                 ;end of macro

and

Code:
;set up ZP pointer, address in .X (LSB) & .Y (MSB)
;
szptr    .macro .zpptr         ;'szptr <zpptr>'
         stx .zpptr            ;set address LSB
         sty .zpptr+1          ;set address MSB
         .endm                 ;end of macro

Those two, in turn, can be wrapped in another macro that sets up a zero page pointer when given the object and the zero page location as arguments:

Code:
;set up a ZP pointer
;
suzptr   .macro .zpptr,.obj    ;'szptr <zpptr>,<obj>'
         lxyi .obj             ;get object address &...
         szptr .zpptr          ;set it
         .endm                 ;end of macro

The above examples are in the style of the macro language implemented in the Kowalski editor/assembler/simulator. If you have access to a Windows machine or can run WINE on Linux you can play around with this simulator and learn the 6502 assembly language in an interactive way.

As your assembly language skills advance you'll find that wrapping function (subroutine) calls and certain kinds of logic in macros will make your code easier to understand and maintain. I've written a 64-bit integer math library for the 65C816, in which the functions are called with macros. Due to the heavy use of the stack to pass parameters, the raw assembly language required to call a math function is sufficiently obtuse to make mistakes the rule, rather than the exception. Burying the code in macros all but eliminates errors and lends a higher level feel to the code without sacrificing the performance inherent in assembly language.

Also, it would be worthwhile to visit Garth Wilson's website, on which he has demonstrated how to use macros to add some structure to an assembly language program. It's still assembly language, but the logic flow is more readily apparent when reading the source code.

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


Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 02, 2020 8:20 am 
Offline
User avatar

Joined: Thu Jan 02, 2020 7:45 am
Posts: 3
Location: Columbus
rickseiden wrote:
I'm familiar with zp,Y addressing, although I've never used it. I'm not sure how I could take two 8 bit values from the a and y registers and combine them into a 16 bit address, though.

BigDumbDinosaur wrote:
Code:
lda (zpptr),y



Isn't that actually the ind,Y addressing mode?

_________________
Retro 6k


Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 02, 2020 9:36 am 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
vtk wrote:
rickseiden wrote:
I'm familiar with zp,Y addressing, although I've never used it. I'm not sure how I could take two 8 bit values from the a and y registers and combine them into a 16 bit address, though.

BigDumbDinosaur wrote:
Code:
lda (zpptr),y


Isn't that actually the ind,Y addressing mode?

Right. There is no zp,Y addressing mode, just zp,X, abs,Y and (zp),Y. The post to which rickseiden was replying said "(zp),Y".

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


Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 02, 2020 9:42 am 
Offline
User avatar

Joined: Thu Jan 02, 2020 7:45 am
Posts: 3
Location: Columbus
cjs wrote:
There is no zp,Y addressing mode


There is for LDX and STX, according to Masswerk

_________________
Retro 6k


Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 02, 2020 10:41 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10947
Location: England
(hey, welcome vtk!)


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 26 posts ]  Go to page Previous  1, 2

All times are UTC


Who is online

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