6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Wed Jul 03, 2024 11:18 am

All times are UTC




Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Fri Jun 12, 2015 6:15 pm 
Offline

Joined: Mon Jan 07, 2013 2:42 pm
Posts: 576
Location: Just outside Berlin, Germany
After the first few days of working on the Crude Emulator for the 65816 (https://github.com/scotws/crude65816), I remember why I hated assembling stuff by hand. This will not do. So I've started adapting the 65c02 assember I wrote (http://forum.6502.org/viewtopic.php?f=2&t=3141 , more background there) for the 65816. It's currently in ALPHA -- some of the easy stuff works, but long branches and jumps, for instance, do not: https://github.com/scotws/tasm65816

Like the version for the 65c02, this is a "Typist's Assembler" in Forth, which means it uses a different syntax. The new variants so far are:
Code:
    MODE                      WDC SYNTAX       TYPIST'S SYNTAX

    absolute long             jmp $101000     101000 jmp.l    (65816)
    absolute long x indexed   jmp $101000,x   101000 jmp.lx   (65816)
    absolute indirect long    jmp [$1000]       1000 jmp.il   (65816)
    dp indirect long          lda [$10]           10 lda.dil  (65816)
    dp indirect long y index  lda [$10],y         10 lda.dily (65816)
    relative long             brl $20f000     20f000 bra.l    (65816)
    stack relative            lda 3,S              3 lda.s    (65816)
    stack rel ind y indexed   lda (3,S),y          3 lda.siy  (65816)
    block move                mvp 0,0            0 0 mvp      (65816)
I've also taken the liberty of changing some opcodes that don't fit the system or I find illogical, though there will be synonyms:
Code:
        WDC             Systematic      Synonym
        Code            version

        BRL             bra.l           brl
        JML             jmp.l           jml
        JSL             jsr.l           jsl
        PEA             phe.#           pea
        PEI             phe.di          pei
        PER             phe.r           per
        RTL             rts.l           rtl
(PEA $0000 really, really annoys me). As for the assembler commands, stuff is still in flux. However, so far "emulation mode" encodes SEC XCE, and "a:8" the SEP $20 sequence. Put together, this is what the example on page 92 of the WDC Programming Manual ends up as:
Code:
      MACHINE CODE      WDC ASSEMBLER        TYPIST'S ASSEMBLER

         E2 20          SEP #$00100000            a:8
                        LONGA OFF
         A9 3F          LDA #$3F               3f lda.#
      8D 00 B1          STA $B100            b100 sta
("a8" would have been shorter, but could be confused with a hex number). Note that "a:8" includes the functionality of LONGA OFF. I'm sure some of this will change: Because I don't really like any of the assemblers/emulators available for the 65816, I'll be dogfooding both programs far more than with the 65c02, where it was more proof of concept and a Forth learning project. Because it's more serious, I've started a primitive test suite this time. Priority will be on the assembler first now, I guess.

As always, any comments and suggestions are most welcome.


Top
 Profile  
Reply with quote  
PostPosted: Sat Jun 20, 2015 12:08 am 
Offline

Joined: Mon Jan 07, 2013 2:42 pm
Posts: 576
Location: Just outside Berlin, Germany
I'm having a devil of a time testing the BRL instruction -- not only do I not have a working 65816 assembler I trust yet, there are also no examples online because it seems everybody is following BDD's advice and using JMP instead. I have this little test routine:
Code:
toplbl  NOP
        BRL toplbl
        BRL botlbl
botlbl  NOP
to check forward and backward label references. When assembled at 00e000, it currently gives me
Code:
00e000  EA
00e001  82 FC FF
00e004  82 01 FF   ; WTF?!
00e007  EA
Now, if I understand how this works, the first BRL would seem to be right: Start with the address of the last byte of the instruction (00e003), add the operand (fffc) and then add one because of (waves hands) the subtraction math. Wrapped, this gives us 00e000, which is where we want to go.

However, I'm really not sure if the second BRL is correct -- I'd expect the result to be 82 00 00 the same way that BRA would give me 80 00 here. I'm assuming this is a problem with the way unresolved forward references are handled, which would not be surprising as there is trickery involved at the best of times even with one byte. Solving this might involve actual thinking, so I was hoping somebody could just confirm I have the basic logic right, if not the code, before I do something extreme. Thanks!

(The actual assembly code in Typist's Assembler, just in case anybody is interested, is
Code:
0e000 origin
-> toplink         nop
           toplink bra.l 
     b> bottomlink bra.l
-> bottomlink      nop
end
BRA.L replaces BRL. The B> handles unresolved branches, and I'm assuming I'm going to need to tweak that code for long references somehow.)


Top
 Profile  
Reply with quote  
PostPosted: Sat Jun 20, 2015 1:30 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1938
Location: Sacramento, CA, USA
Do you have your assembler source in some form fit for public review yet? Maybe someone here could help you find that nasty little bug. I agree that your unresolved branch handler should be one of the first places to look.

Mike B.

[Edit: Oh, I see it's linked in your top post! Duh!]


Last edited by barrym95838 on Sat Jun 20, 2015 1:31 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Sat Jun 20, 2015 1:31 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8462
Location: Southern California
scotws wrote:
However, I'm really not sure if the second BRL is correct -- I'd expect the result to be 82 00 00 the same way that BRA would give me 80 00 here. I'm assuming this is a problem with the way unresolved forward references are handled, which would not be surprising as there is trickery involved at the best of times even with one byte. Solving this might involve actual thinking, so I was hoping somebody could just confirm I have the basic logic right, if not the code, before I do something extreme. Thanks!

I just tried 82 00 00 just to be extra sure, and it works as expected.

_________________
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 Jun 20, 2015 2:01 am 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3366
Location: Ontario, Canada
scotws wrote:
Start with the address of the last byte of the instruction (00e003), add the operand (fffc) and then add one because of (waves hands) the subtraction math.
There's no hand-waving if you start with the first byte of the following instruction. Just as with conditional branches -- another case of relative addressing -- you base the calculation starting from where you'd go if the branch isn't taken.

Dunno if that helps at all, Scot, or seems more plausible to you. Heck, I half suspect you prefer to have a little bit of hand waving! :wink: But evidently it's agreeable to the processor pipeline, since that's how they chose to design it.

BTW & FWIW, the PER instruction is another case involving relative addressing, and again the same logic applies. The value pushed is relative to the first byte of the following instruction.

-- Jeff

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Top
 Profile  
Reply with quote  
PostPosted: Sat Jun 20, 2015 2:29 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8239
Location: Midwestern USA
Dr Jefyll wrote:
There's no hand-waving if you start with the first byte of the following instruction. Just as with conditional branches -- another case of relative addressing -- you base the calculation starting from where you'd go if the branch isn't taken...the PER instruction is another case involving relative addressing, and again the same logic applies. The value pushed is relative to the first byte of the following instruction.

Indeed. In Supermon 816, a (gasp!) subroutine handles relative addressing calculations, since all Bxx branch instructions, as well as PER, need it. Computation, as Jeff notes, is zero-based to the instruction immediately following the Bxx or PER instruction. It's actually quite simple, especially with the '816 doing 16 bit arithmetic in single "gulps."

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


Top
 Profile  
Reply with quote  
PostPosted: Sat Jun 20, 2015 2:41 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8239
Location: Midwestern USA
scotws wrote:
I'm having a devil of a time testing the BRL instruction -- not only do I not have a working 65816 assembler I trust yet, there are also no examples online because it seems everybody is following BDD's advice and using JMP instead.

Huh? :shock: What did I do? 8)

I don't remember advising anyone against use of BRL. I only noted that unless one is writing relocatable code BRL confers no advantages over JMP, and the latter is faster by one clock cycle.

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


Top
 Profile  
Reply with quote  
PostPosted: Sat Jun 20, 2015 6:20 am 
Offline

Joined: Mon Jan 07, 2013 2:42 pm
Posts: 576
Location: Just outside Berlin, Germany
Thanks, guys. That helped a lot. I'll take a look at the code for unresolved labels then, and see about generalizing the relative branch in a second step -- haven't gotten to PER (phe.r in this assembler) yet, so hadn't seen that.

BDD, there is this quote from you at http://www.6502.org/tutorials/65c816interrupts.html :
Quote:
Don't use the BRL instruction unless you are writing relocatable code: As mentioned earlier, BRA and JMP take three clock cycles to complete, whereas BRL consumes four cycles.
It looks like not too many people are writing relocatable code, I can't seem to find BRL examples online with assembled code. As long as you are in the same bank, you can use JMP absolute anyway ...

Thanks again!


Top
 Profile  
Reply with quote  
PostPosted: Sat Jun 20, 2015 10:02 am 
Offline
User avatar

Joined: Tue Mar 02, 2004 8:55 am
Posts: 996
Location: Berkshire, UK
Your forward relative offset calculation is wrong. My assembler generates this
Code:
                                            .ORG    $E000
                                           
00E000  EA                : toplbl          NOP
00E001  82FCFF            :                 BRL     toplbl
00E004  820000            :                 BRL     botlbl
00E007  EA                : botlbl          NOP
                                           

_________________
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  
PostPosted: Sat Jun 20, 2015 8:41 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8239
Location: Midwestern USA
BitWise wrote:
Your forward relative offset calculation is wrong. My assembler generates this
Code:
                                            .ORG    $E000
                                           
00E000  EA                : toplbl          NOP
00E001  82FCFF            :                 BRL     toplbl
00E004  820000            :                 BRL     botlbl
00E007  EA                : botlbl          NOP
                                           

I assembled the following in the machine language monitor in POC:
Attachment:
File comment: Relative Addressing Example
relative_addressing.jpg
relative_addressing.jpg [ 825.66 KiB | Viewed 2283 times ]

I started at $008000 because ROM is at $00E000. Up to the PER instruction it is the same as in Andrew's example.

All branch calculations are basic arithmetic, with the starting address for computation purposes being that of the instruction immediately following the Bxx or PER instruction—consider that following instruction to be the "base" instruction. The offset is then:

    OFFSET = TARGET - BASE

where TARGET is the address of a Bxx or PER instruction's target. OFFSET becomes the operand of the Bxx or PER instruction. You can see this in Andrew's example by counting backwards from the instruction at $00E004. Similarly, if you count backwards from the BRK instruction at $00800B in my example you will see how the reverse offset was computed.

The 65C816 makes this quite easy, as a single 16 bit subtraction is all that is required to generate the offset. In the case of any of the Bxx instructions other than BRL, you'd have to verify that the difference doesn't exceed $007F going forward or $FF80 going backward. For BRL and PER, your range check is $7FFF going forward and $8000 going backward.

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


Last edited by BigDumbDinosaur on Sun Jun 21, 2015 7:11 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Sat Jun 20, 2015 9:00 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8239
Location: Midwestern USA
scotws wrote:
BDD, there is this quote from you at http://www.6502.org/tutorials/65c816interrupts.html :
Quote:
Don't use the BRL instruction unless you are writing relocatable code: As mentioned earlier, BRA and JMP take three clock cycles to complete, whereas BRL consumes four cycles.

Yes, I did write that in a section on how to optimize code within an interrupt handler, where execution speed is always a major priority. You'd need to quote the entire section of the article in which that is located to complete the picture. :)

Quote:
It looks like not too many people are writing relocatable code, I can't seem to find BRL examples online with assembled code. As long as you are in the same bank, you can use JMP absolute anyway...

I have yet to run into a situation where I have needed relocatable code. Although the 65C816 makes writing such code much easier than it would be with its eight bit siblings, it's hardly a cake-walk. For one thing, there is no native BSR instruction, so one has to be synthesized via a mixture of pushes (e.g., PER) and long branches, or short ones if the target subroutine is close enough (I have a macro that synthesizes BSR). String and data table handling can get interesting as well if those items are part of the program—usage of PER would be necessary to generate appropriate run-time addresses. All of this, of course, can be buried in macros.

If the 65C816 hardware supports virtual addressing via some sort of MMU device then relocatable code is technically unnecessary, since the MMU address translation functions would always make the load address appear to be a fixed value (e.g., $xx0100). Programs would be assembled to that load address and hence could use static addresses to access subroutines and initialized data structures.

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


Top
 Profile  
Reply with quote  
PostPosted: Sun Jun 21, 2015 10:01 pm 
Offline

Joined: Mon Jan 07, 2013 2:42 pm
Posts: 576
Location: Just outside Berlin, Germany
Fixed it, thanks again for the help. Not happy with the code, though, because this is how it works now:

The assembler source code is actually a Forth program, and as such single pass by definition. This is fine when we define labels before we use them:
Code:
-> mylabel
is simply a variable with the right address, which "->" defines (if you're new to Forth, yes, that's a legal instruction, because "special characters" aren't special). But when we use an undefined forward references, the Forth interpreter has no way of knowing that "mylabel" is a label, and throws and error when it can't find an instruction with that name in the dictionary.

So we need a way to tell the assembler that this is a label, but we don't know where it will go, and to have it reserve some space for it, depending what instruction we're using. The classic single-pass assembler way of doing this is by link lists, one for each unknown label, that are later resolved when the label is finally defined.

Right now, this works by having a special instruction for every kind of branch or jump.
Code:
 
    b>  bottomlink bra
    j>  bottomlink jmp
    j>  bottomlink jsr
    jl> bottomlink jmp.l      \ JML
    jl> bottomlink jsr.l      \ JSL
    bl> bottomlink bra.l      \ BRL       

   -> bottomlink   nop
For instance, B> triggers a common routine ("addlabel") that either creates a new list of unresolved references or adds a new link to an existing one. It also saves a link to the word ("subroutine") that will later replace a dummy value in the list with the real address ("dummy>rel" in this case). The BRA instruction saves its own opcode and reserves one byte for the offset, while JMP.L (JML) saves three bytes along with its opcode. When "->" finally comes around with the actual label, each dummy replacement word is triggered triggered and the address or offset calculated.

(Actually, the assembler currently it doesn't save a link to the replacement routines but an offset to a jump table with the addresses of those routines, which is an extra level of indirection that obviously has to go but was useful while I was figuring out what the hell I was doing.)

These "label prefixes" were only mildly annoying when it was just B> and J> for the 65c02. Now that we have the 65816 with JL> and BL> as well, this is getting to be a bit much. Also, we're defining the type of branch or jump twice: BRA.L (BRL) should know that it is a long branch without any "BL>". The current setup violates DRY - "don't repeat yourself".

So I'm considering a different solution. First, add a "reserve this label" instruction so that the assembler knows the next name is a label that is going to be used later.
Code:
   reserve bottomlink     

        bottomlink bra
        bottomlink jmp
        bottomlink jsr
        bottomlink jmp.l      \ JML
        bottomlink jsr.l      \ JSL
        bottomlink bra.l      \ BRL       

   -> bottomlink   nop
(PRELABEL, ANTICIPATE, AWAIT or STASH might be better than RESERVE -- EXPECT is used by Gforth, unfortunately -- but that's details) This will also get rid of the pesky ">" character, which is a pain to type. Then, when it comes time to replace the dummy values with the actual offset or address, have the assembler go back one character from the gap we left and re-exame the opcode to find out what kind of branch/jump instruction this is. If it's $82, we have BRA.L (BRL), and we need to call the routine that calculates a 16-bit offset, if it's $4C, we're dealing with a vanilla jump instruction. We can do this without a second pass.

I'm sure there's a more hardcore Forth way of doing this -- maybe when a word is not found in the dictionary, interrupt the error sequence, check if the next word is something like BRA, BRA.L, or JMP, and if yes, define it as a new label, for instance. This seems a bit too clever at the moment for my skill level, though :D . If anybody sees a different solution, I'd be most grateful.


Top
 Profile  
Reply with quote  
PostPosted: Mon Jun 22, 2015 2:06 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1938
Location: Sacramento, CA, USA
scotws wrote:
... The assembler source code is actually a Forth program, and as such single pass by definition ...

Single-pass assemblers and compilers have always had touchy issues with forward references, and much cleverer minds than mine have come up with techniques to overcome them. The only thing you would be able to do to get rid of these issues completely would be to figure out a way to make your assembler multi-pass. I have no idea how to do that in Forth, if it's even possible at all.

http://stackoverflow.com/questions/1024 ... -the-futur

Mike B.


Top
 Profile  
Reply with quote  
PostPosted: Mon Jun 22, 2015 2:31 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8462
Location: Southern California
What kind of material do you want to assemble?

My first project to learn Forth, in 1991, was to write a general-purpose 6502 assembler, on my HP-71 hand-held computer. The way I did some things was a pain to use, like requiring that labels to start in column 1, mnemonics to start in column 9, etc., as I did not realize how easy it would have been to make it more free-form. However, I did make it build a symbol table (as a file) on first pass, and resolve the forward references on second pass, after it knew all labels' names and addresses from having been through the whole source code the first time.

The teensy assembler in my Forth kernel however is just for small things like primitives, runtimes, and subroutines which max out at a few dozen lines. You don't leave it in assembly for thousands of lines at a time. Long, descriptive assembly labels then are not necessary, so I did the forward references like BNE 4$, then when it gets to label 4$: (with the colon), it now has the info to fill in the offset at the branch operand, and 4$ can be re-used after that. This does mean that if multiple things branch forward to the same place, it will need multiple labels, like 1$: 2$:... 0$ through 9$ has always been enough labels for any routine I've written with this, partly because of the re-usability of labels after a forward reference has been resolved.

To branch backwards, I use HERE to put a marker on the stack where I want it to branch to, then, having the desired marker at the top of the stack, do BNE GO_BACK. In that situation there's no real label at all. If I want multiple places to branch back to it, all but the last one will do some sort of duplicating operation first, like DUP GO_BACK or OVER GO_BACK.

_________________
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 Jun 22, 2015 3:23 am 
Offline

Joined: Sun Jul 28, 2013 12:59 am
Posts: 235
I wouldn't even bother with the labels: I have a data stack! For a backwards reference, well, take the target address with HERE, and resolve it appropriately at branch time. For a forward reference, take the address of the branch offset, assemble in a dummy offset value, and backpatch it with the appropriate offset once you get to the branch target. And then wrap the whole thing up with some structured conditionals.

For x86, for example, I have the ability to assemble "<>, IF, ... THEN," which does a conditional forward branch if the Z flag is set (thus executing the code in the ellipses if Z is clear / the last comparison was not equal). Like Garth's system, it's intended for small things, like a single Forth primitive or a short routine or the like.

I don't yet have a 65c02 or 65816 assembler, but I rather expect that I'll continue the same philosophy when I do sit down to write one.


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

All times are UTC


Who is online

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