Joined: Fri Aug 30, 2002 1:09 am Posts: 8546 Location: Southern California
|
In the topic "Dead CBM PET 3016 help !!" under "General discussions," BigDumbDinosaur wrote:
Quote: Hence my work on a custom assembler. What do you have in mind?
One thing I would really like that I have not seen in an assembler so far is to go beyond assembler variables (like EQUates that can be reassigned as many times as you want, which is nothing new), and also have assembler arrays and the ability to use a variable as a pointer into an array. What I would like to do with them is to make one or more stacks in the assembler so I can make assembly program structures in macros, without having to make sure assembler variables used by one structure don't step on those used by other ones that are nested. Being able to write in assembly with nested program structure macros would sure take the confusion out of a lot of pieces of code that are normally written as a pile of spaghetti with gobs of branches to labels. I translated one of Bruce Clark's memory-block-moving routines at http://6502.org/source/general/memory_move.html below. I know it's not the best example, but it should give the idea:
Code: ┌───────────────────╖ │ Regular version ║ ╘═══════════════════╝
; Move memory down ; ; FROM = source start address ; TO = destination start address ; SIZE = number of bytes to move ; MOVEDOWN LDY #0 LDX SIZEH BEQ MD2 MD1 LDA (FROM),Y ; move a page at a time STA (TO),Y INY BNE MD1 INC FROM+1 INC TO+1 DEX BNE MD1 MD2 LDX SIZEL BEQ MD4 MD3 LDA (FROM),Y ; move the remaining bytes STA (TO),Y INY DEX BNE MD3 MD4 RTS
┌──────────────────────╖ │ Structured version ║ ╘══════════════════════╝
MOVEDOWN: LDY #0
LDX SIZEH ; Get the high byte of the size of block to move. IF_NEQ ; Do this 1st part if there's at least one full page to move. BEGIN ; Do this loop once for each full page to move. BEGIN ; Do this loop once for each byte in the page. LDA (FROM),Y STA (TO),Y INY UNTIL_EQ ; UNTIL_EQ assembles the BNE up to the BEGIN four lines up. INC FROM+1 ; Increment the high byte of the source INC TO+1 ; and destination addresses, and DEX ; decrement the number of full pages left to do. UNTIL_EQ ; UNTIL_EQ assembles the BNE up to the corresponding BEGIN. END_IF ; END_IF puts the branch distance in the BEQ assembled by the ; IF_NEQ above, whose operand's addr was on the assembler stack.
LDX SIZEL ; After all full pages have been moved, see if there's part of IF_NEQ ; one left to do. If there is, do the following. BEGIN ; Do this loop once for each byte left. LDA (FROM),Y STA (TO),Y ; After transferring each byte, INY ; increment the index, DEX ; and decrement the number of bytes left to do. UNTIL_EQ ; UNTIL_EQ assembles the BNE up to the BEGIN 5 lines up. END_IF ; END_IF puts the branch distance in the BEQ assembled ; by the IF_NEQ above, so a branch taken goes to the RTS below. RTS The two versions lay down exactly the same machine code. [Edit, years later:] I implemented structure macros and documented them at http://wilsonminesco.com/StructureMacros/index.html , adding also FOR...NEXT loops which are more suitable than the BEGIN...UNTIL structures above as they set up the applicable index register and do the incrementing or decrementing and the comparison in a more integrated way:
Code: MOVEDOWN: LDY #0
LDX SIZEH ; Get the high byte of the size of block to move. IF_NOT_ZERO ; Do this 1st part if there's at least one full page to move. FOR_X X_REG, DOWN_TO, 0 ; Do this loop once for each full page to move. Start w/ current X contents. FOR_Y Y_REG, UP_TO, 0 ; Do this loop once for each byte in the page. Start w/ current Y contents. LDA (FROM),Y STA (TO),Y NEXT_Y ; NEXT_Y assembles the BNE up to the LDA (FROM),Y two lines up. INC FROM+1 ; Increment the high byte of the source and INC TO+1 ; destination addresses. In next line, decr the number of full pages left to do. NEXT_X ; NEXT_X does the DEX, and assembles a BNE up to the first line after FOR_X above. END_IF ; END_IF puts the branch distance in the BEQ assembled by the ; IF_NOT_ZERO above, whose operand's addr was on the macro stack.
LDX SIZEL ; After all full pages have been moved, see if there's _part_ IF_NOT_ZERO ; of one left to do. If there is, do the following. FOR_X X_REG, DOWN_TO, 0 ; Do this loop once for each byte left. LDA (FROM),Y STA (TO),Y ; After transferring each byte, INY ; increment the index. In next line, decr the number of bytes left to do. NEXT_X ; NEXT_Y does the DEX, then assembles the BNE up to the first line after FOR_X above. END_IF ; END_IF puts the branch distance in the BEQ assembled ; by the IF_NOT_ZERO above, so a branch taken goes to the RTS below. RTS ;----------------
The resulting machine code laid down remains the same. [End edit]
The IF_NEQ (ie, "if not equal," meaning "if the Z flag is not set") macro assembled the BEQ instruction but with an invalid operand because it doesn't know yet how far down it will have to branch. Then it puts the address of that operand on the stack we formed in the assembler, so that the END_IF macro can fill it in. The same kind of thing will happen with IF...ELSE...END_IF, BEGIN...WHILE...REPEAT, FOR...NEXT (or DO...[LEAVE]...LOOP), CASE...CASE_OF...END_OF...END_CASE, and any other structure you might want.
Since it's a stack, any number of program structures can be nested without interfering with each other. No labels necessary. The macros will also use the stack to pass structure-security values for error-checking. This will alert you to occurrences of, for example, pairing a NEXT with a CASE because you accidentally left something out and had an incomplete structure.
A CASE structure might look like this:
Code: CASE A CASE_OF $0A actions ; ┐ Do this stuff only actions ; │ if accum was equal actions ; ┘ to $0A (linefeed char) END_OF
CASE_OF $0D actions ; ┐ Do this stuff only actions ; │ if accum was equal actions ; ┘ to $0D (CR char) END_OF
CASE_OF $08 actions ; ┐ Do this stuff only actions ; │ if accum was equal actions ; ┘ to $08 (backspace char) END_OF
actions ; code to execute actions ; for all other cases
END_CASE
(Substitute appropriate assembly code for "actions.") CASE_OF $0A assembles CMP #$0A, BNE __ , with the BNE operand invalid until the corresponding END_OF fills it in, making the BNE go down to the next part, CMP #$0D, BNE... The END_OFs also lay down an instruction to jump down to the code following END_CASE, and the operands get filled in by the END_CASE macro, without requiring a second pass. After all three particular tests are done (ie, testing for $0A, $0D, and $08), any bytes that did not get caught by those tests will get handled in the code between the last END_OF and the END_CASE. Regardless of what the accumulator contents were initially, after each case is handled, execution normally resumes immediately after END_CASE.
These macros of course are not part of the assembler, but merely show what can be done if the assembler supports assembler arrays so the use can make stacks and form the structure macros. Actually what I showed above is nowhere near exhaustive as far as listing what could be done with them goes. This stuff really is just some Forth internal stuff, which is why I might sound like I've done it before. I have.
The C32 assembler I use from Universal Cross-Assemblers only has one bug that I know of and can think of at the moment: A comma in a quoted string used as a macro parameter will get treated as the delimiter leading to the next macro parameter, even though it's before the double quote that marks the end of the string.
I sure liked the ANSI [Edit: that should say IBM437] graphics characters used by DOS (which I still use in my assembly). They're very useful for drawing diagrams and tables and framing titles in your code. My programmer's text editor that runs under DOS makes it very easy to draw these, almost like it was CAD. I just found out these characters can be had in GUIs also, but probably not in text files that only have 8 bits per character. I drew the shadowed boxes around the titles in the code above this way, but you can do far more with them. That's mostly a function of the text editor of course, but the assembler has to be able to tolerate the special characters without trouble.
I would like to have more freedom in naming labels than most assemblers give. This might require separating arithmetic and logical operators with spaces around them so you could for example have a routine called Read_A/D (read analog-to-digital converter). Without the spaces around the slash, the assembler would not try to actually divide the non-existent "Read_A" by "D". Also, using special characters allows you to have for example ±½°, 100Ω, φ2, or µF.
[Edit, Oct 2014:] I should have added that I don't want labels to be required to start in column 1 (nor do I want them to require their own line). If you can start labels of only local significance in column 2 for example, they will not show up in a text editor's condensed display. This makes it easier to find things, because the condensed display is not cluttered with those. Additionally, I don't want assembler directives (or anything else for the matter) to be required to start with a dot. When things start in the same column for vertical alignment for visual factoring, it looks messy when some lines start with a dot. It makes it look like you tried to align them and were just really sloppy. I want the option to have case sensitivity.
Somewhere I wrote a wish list for an assembler, but I can't find it at the moment, so I'll probably have more ideas trickling in. I'm thinking more seriously again about starting to make my next computer, using the '816, and then I should have enough memory to run an assembler that is much more suited for large projects than what I have on the current workbench computer. Ideally it would no longer be dependent on the PC for development. The assembler I have in ROM in my current workbench computer is minimal and is really only for assembling primitives for Forth, runtimes, subroutines, ISRs, etc., but not a whole application. It does however allow putting more than one instruction on a line, which I like. You can for example do:
Code: LDA_ZP,X 0 CMP_ZP,X 4 BNE 1$ LDA_ZP,X 1 CMP_ZP,X 5 BNE 1$ LDA_ZP,X 2 CMP_ZP,X 6 BEQ 1$ LDA_ZP,X 3 CMP_ZP,X 7 BEQ 1$
LDA(ZP) IP PHA INY LDA(ZP),Y IP STA_ZP IP+1 PLA STA_ZP IP INX INX JMP POP3 (This is a slight modification of a portion of actual working code.) To straight-line it would take 23 lines.
I know BigDumbDinosaur was probably talking about making an assembler just for his own use, but I thought it would be a good idea to discuss desirable assembler features anyway. BDD, if you do make your assembler available to others, I trust it will be for Linux, even if you do a separate Windoze version.
_________________ 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?
|
|