[Project Log] Python on the 6502/C64, 8080, 6800, 6809 and AVR

I am now able to cross assemble most inherent 6800 instructions.

While 6800 binary files currently have no value on a 6502, this means that a large part of the fundamental infrastructure of the assembler is working.

Next up, evaluating expressions…

3 Likes

This project is now two months old.

The FLEX file system (File Management System or FMS) is still lacking the code for writing random access files until I can come up with a good way to test it. I may go ahead and write the code but leave it disconnected to get an idea how big it is likely to be.

The FLEX user interface portion is feature complete except for background printing. Again, I may write the code to find out how large it will be. The error message reporting mechanism is complete.

There was little progress on the Utility Command Set this month. The SAVE utility has been written, but not thoroughly tested.

The 6800 assembler now runs on the 6502 as a cross assembler. To help test it, I have started implementing the text editor. It can currently create a text file and make some simple edits to it. The editor is definitely more difficult code than the assembler has been.

The current system memory map is:

$0000…$00FF - Zero page, locations at $12 and above are free for application programs to use
$0100…$017F - FLEX line buffer
$0180…$01FF - Stack
$0200…$033F - FLEX entry points, variables and printer driver
$0340…$047F - System File Control Block
$0480…$0AFF - FLEX Utility Command Space
$0B00…MEMEND - User memory

Somewhere above that is about 6K of FLEX itself.

It is time to start trying to freeze the memory organization. I am considering making a somewhat radical change to move the public portions (entry points, variables, Utility Command Space) from $200 up to $2000. Why? Doing this will make it possible for a KIM-1 or clone with a expanded RAM and a serial port to run FLEX. The operating system itself can reside from $200 to $2000 on systems which allow that and at the top of user memory otherwise. Because the binary file format consists of a collection of separate chunks, pieces of the system can potentially be built to load into several disjoint places where memory is available.

Another decision is whether the system should reserve a part of the lower or upper end of the zero page.

The important part now is to determine where the public portions are located. This is where I need your input as to what your particular system requires and allows. It may not be possible to please everyone, but the main goal is to have one version of the utilities and application programs which work on all supported platforms.

2 Likes

The text editor code has been difficult; I can only stand to work on it a bit at a time.

The 6800 version keeps values in the X register for long stretches of code, sometimes across several subroutines. I am about to give up trying to keep it in 6502 registers even part of the time, but to declare RegX, a 2-byte variable in the zero page, and copy values into and out of it as the 6800 loads and stores X. Addresses already have to be stored in the zero page to access memory. It will not be as efficient, but the code will be somewhat clearer; this program really needs that.

So far, the following functionality is complete:

loading a file
saving a file
text buffer management
Print command
Insert command
Delete command
Renumber command
Overlay command
Find command

The Copy command has been coded, but it does not work. When that is done, the Move command should be easy as it does a copy followed by a delete. I would guess we are somewhat past the halfway mark.

During breaks, I have been working on assemblers.

First, I added the SET directive to the 6800 ASMB. It is like EQU, but a label may be assigned a value more than once.

I have always liked the local labels in RELASMB, the 6809 relocatable assembler, and wished that ASMB implemented them.

Local labels work like this: a one or two digit number in the label field is considered to be a local label. It is referenced by stating the number followed by a “b” or “f” to designate whether to search for the nearest occurrence of the number before or following the current line. Note that it is not possible to find a local label on the current line. For example:

2                       ; This is the target for "2b"

2       beq     target  ; The label for this line cannot be specified

2                       ; This is the target for "2f"

Advantages of a local label include doing away with the need to come up with meaningful and unique names for short, trivial branches and a local label uses less memory than a regular symbol.

A web search for local label uncovered no prevalent standard, but a number with a “b” or “f” suffix is a very common form.

I will put a seemingly arbitrary limit on local labels: the number may not consist of only “0” and “1” digits. Why? ASMB 6800 accepts binary numbers in both the %xx and xxb forms. Something like “1b” is ambiguous.

I had been initially tempted to limit the reference of local labels to relative branch instructions to prevent people writing FORTRANish code, but RELASMB does not have that restriction. And it is convenient to be able to do something like the following:

        ldx     #2f     ; Print error message
        jsr     PSTRNG
        rts

2       fcc     "Operand expected."
        fcb     4

I am currently implementing local labels in my 6502 cross assembler. Should that go well, the capability will be migrated to the 6800 and 6809 assemblers. Then I will implement it in ASMB 6800; ASMB 6502 will inherit that functionality.

3 Likes

I made a very interesting discovery this morning.

The FLEX Users Group has discussions using an e-mail distribution list. The message archive was lost last year in a malware attack.

Discussion ensued about reconstructing it from various archives kept by individual members. They are in various formats depending upon the client software used.

I was reminded of a research project I had started to build a tool for deleting attachments from the mail file while preserving the text content of the message. I am pretty sure that my notes and code were lost in a bad disk crash.

I looked for it anyway…

<serious tangent>

No sign of it, but I did find something I had started I dubbed Dallas Disk Jockey. It was a full screen virtual disk manipulation tool. I will see whether it can be modernized and completed. It was developed using the Turbo Vision library from Borland and originally targetted MS-DOS, but I understand it can now be built for other platforms, Win32, Linux and MacOS.

1 Like

Many moons ago, I started working on a compiler to turn programs for the TSC 6800 BASIC interpreter into native code. Not because I programmed in BASIC much, but because it seemed like an interesting challenge. And because BASIC appeared to be easier to compile than other languages.

The goal was not to produce the fastest programs as pretty much any halfway decent compiler can beat the interpreter. The main goal was to have programs behave as closely as possible to how they ran in the interpreter. A secondary goal was for the resultant binary files to be as small as possible.

The initial attempt was to only support integer and string variables as floating point is a large nut to crack. Things were going well at first. Error handling required some intense deep thought to implement; ON ERROR and RESUME were finally working, even with the complexities introduced with GOSUB and RETURN. DATA, READ and RESTORE also took a bit of care to implement.

The project hit the wall when it came time to compile FOR loops. The spaghetti bowl of unstructured BASIC allowed the corresponding NEXT statement to be anywhere. How was the compiler supposed to find the NEXT statement for any particular FOR loop?

I messed up by not doing research into how the FOR statement should operate and went on the assumption that it was a while loop instead of a do until loop.

The project was mothballed. Even so, I was able to implement a 6800 disassembler in compiled BASIC.

The idea for compiling BASIC came up again as I started feeling I may not have enough of an urge to implement the equivalent of the FLEX BASIC interpreter for the 6502.

It is clear now that if a FOR loop is always allowed to run once before the exit condition is tested, the problem of finding the bottom of the loop solves itself. I did not think to try this back then, but that is exactly how the FLEX interpreter behaved.

As for the goal of small executables, MK I of the compiler embedded the run-time library into the compiler in the form of small snippets of code which were selectively written as they were needed by the compiled program. That approach turned out to be a maintenance headache.

During one of my frequent breaks from implementing the 6502 text editor, I decided to try a different approach to the “poor man’s linker” problem.

The run-time library now resides on disk in a collection of short source files. The main run-time library is a single source file read by the MK II compiler which looks for tags where the generated code and data are to be inserted. These insertions reference the library routines using code like this:

ifdef __READ
lib read.bar
endif

The compiler generates dependencies in the form

__READ set 1

The compiler can currently convert the program

100 print “Hello world!”

into a binary image which fits easily within a single 252 byte sector.

An added benefit of the new approach is that the compiler is much smaller. It may be possible to create a version to run natively.

1 Like

Thanks to feedback on several forums, I have come to an understanding of how the FOR and NEXT statements commonly behave in interpreters so that they can finally be implemented in my compiler.

http://forum.6502.org/viewtopic.php?f=2&t=6182

The following code has been written to approximate what the compiler can be expected to generate for this program:

10 FOR I = 1 TO 3
20 PRINT I
30 NEXT I
                          00068 * 00010 FOR I = 1 TO 3
 010D                     00069 L00010
 010D BD 0248         [9] 00070          jsr    ForPush   ; Get new FOR context
                          00071
 0110 86 01           [2] 00072          ldaa   #T00001>>8 ; Store top of loop address
 0112 A7 04           [6] 00073          staa   4,X
 0114 86 26           [2] 00074          ldaa   #T00001&$FF
 0116 A7 05           [6] 00075          staa   5,X
                          00076
 0118 86 06           [2] 00077          ldaa   #I>>8     ; Store variable address
 011A A7 06           [6] 00078          staa   6,X
 011C 86 0F           [2] 00079          ldaa   #I&$FF
 011E A7 07           [6] 00080          staa   7,X
                          00081
 0120 86 00           [2] 00082          ldaa   #1>>8     ; Initialize variable
 0122 C6 01           [2] 00083          ldab   #1&$FF
                          00084
 0124 20 21 (0147)    [4] 00085          bra    T00003    ; Execute the body of the loop
                          00086
 0126                     00087 T00001
 0126 B6 060F         [4] 00088          ldaa   I         ; Load variable
 0129 F6 0610         [4] 00089          ldab   I+1
                          00090
 012C CB 01           [2] 00091          addb   #1&$FF    ; Add step
 012E 89 00           [2] 00092          adca   #1>>8
                          00093
 0130 81 00           [2] 00094          cmpa   #3>>8     ; Compare with limit
 0132 22 06 (013A)    [4] 00095          bhi    T00002    ; Higher, exit loop
 0134 25 11 (0147)    [4] 00096          blo    T00003    ; Lower, iterate loop
 0136 C1 03           [2] 00097          cmpb   #3&$FF
 0138 23 0D (0147)    [4] 00098          bls    T00003    ; Not higher, iterate loop
                          00099
 013A                     00100 T00002
 013A FE 0611         [5] 00101          ldx    ForTop    ; Address context
                          00102
 013D A6 09           [5] 00103          ldaa   9,X       ; Push address of bottom of loop
 013F 36              [4] 00104          psha
 0140 A6 08           [5] 00105          ldaa   8,X
 0142 36              [4] 00106          psha
                          00107
 0143 BD 027B         [9] 00108          jsr    ForPull   ; Remove FOR context
                          00109
 0146 39              [5] 00110          rts              ; "Jump" to bottom of loop
                          00111
 0147                     00112 T00003
 0147 B7 060F         [5] 00113          staa   I         ; Update variable
 014A F7 0610         [5] 00114          stab   I+1
                          00115
                          00116 * 00020 PRINT I
 014D                     00117 L00020
 014D FE 060F         [5] 00118          ldx    I
 0150 BD 0428         [9] 00119          jsr    PInt
                          00120
 0153 BD 04CB         [9] 00121          jsr    NewLine
                          00122
                          00123 * 00030 NEXT I
 0156                     00124 L00030
 0156 86 06           [2] 00125          ldaa   #I>>8     ; Address the variable
 0158 C6 0F           [2] 00126          ldab   #I&$FF
                          00127
 015A BD 0225         [9] 00128          jsr    Next
                          00129
 015D                     00130 End_

Three subroutines provide support for a FOR-NEXT loop:

ForPush - Create a new loop context
ForPull - Remove a loop context
Next - Process the bottom of a loop

Only Next is interesting; when the specified variable does not match the one at the top of the loop, contexts are discarded until a matching one is found or there are none left.

.                         00354 ******************************************************************************
.                         00355 *
.                         00356 * Next - Process NEXT
.                         00357 *
.                         00358 * Input:
.                         00359 *       A:B = variable address
.                         00360 *
.0225                     00361 Next
.0225 FE 0611         [5] 00362          ldx    ForTop    ; Point to current context
.0228 27 12 (023C)    [4] 00363          beq    Next1     ; Not within a FOR loop
.                         00364
.022A                     00365 Next0
.022A A1 06           [5] 00366          cmpa   6,X       ; Compare variable address
.022C 26 0E (023C)    [4] 00367          bne    Next1     ; Loop variable mismatch
.022E E1 07           [5] 00368          cmpb   7,X
.0230 26 0A (023C)    [4] 00369          bne    Next1
.                         00370
.0232 32              [4] 00371          pula             ; Get and save bottom of loop
.0233 A7 08           [6] 00372          staa   8,X
.0235 32              [4] 00373          pula
.0236 A7 09           [6] 00374          staa   9,X
.                         00375
.0238 EE 04           [6] 00376          ldx    4,X       ; Jump to top of loop
.023A 6E 00           [4] 00377          jmp    ,X
.                         00378
.023C                     00379 Next1
.023C EE 02           [6] 00380          ldx    2,X       ; Get previous context
.023E FF 0611         [6] 00381          stx    ForTop    ; Save it
.0241 26 E7 (022A)    [4] 00382          bne    Next0     ; Retry if valid
.                         00383
.0243 86 3E           [2] 00384          ldaa   #62       ; Report FOR-NEXT nesting error
.0245 7E 0284         [3] 00385          jmp    ErrH
4 Likes

Found a nasty bug in Next:

.                         00354 ******************************************************************************
.                         00355 *
.                         00356 * Next - Process NEXT
.                         00357 *
.                         00358 * Input:
.                         00359 *       A:B = variable address
.                         00360 *
.0225                     00361 Next
.0225 FE 0611         [5] 00362          ldx    ForTop    ; Point to current context
.0228 27 19 (0243)    [4] 00363          beq    Next2     ; Not within a FOR loop
.                         00364
.022A                     00365 Next0
.022A A1 06           [5] 00366          cmpa   6,X       ; Compare variable address
.022C 26 0E (023C)    [4] 00367          bne    Next1     ; Loop variable mismatch
.022E E1 07           [5] 00368          cmpb   7,X
.0230 26 0A (023C)    [4] 00369          bne    Next1
.                         00370
.0232 32              [4] 00371          pula             ; Get and save bottom of loop
.0233 A7 08           [6] 00372          staa   8,X
.0235 32              [4] 00373          pula
.0236 A7 09           [6] 00374          staa   9,X
.                         00375
.0238 EE 04           [6] 00376          ldx    4,X       ; Jump to top of loop
.023A 6E 00           [4] 00377          jmp    ,X
.                         00378
.023C                     00379 Next1
.023C EE 02           [6] 00380          ldx    2,X       ; Get previous context
.023E FF 0611         [6] 00381          stx    ForTop    ; Save it
.0241 26 E7 (022A)    [4] 00382          bne    Next0     ; Retry if valid
.                         00383 Next2
.0243 86 3E           [2] 00384          ldaa   #62       ; Report FOR-NEXT nesting error
.0245 7E 0284         [3] 00385          jmp    ErrH

Also, this

                          00107
 0143 BD 027B         [9] 00108          jsr    ForPull   ; Remove FOR context
                          00109
 0146 39              [5] 00110          rts              ; "Jump" to bottom of loop
                          00111

should be just a jump instead of a jsr followed by rts.

1 Like

From the school of Procrastination as a Valid Software Technique, the address of the bottom of the loop is kept on the stack until it is needed or discarded:

                          00068 * 00010 FOR I = 1 TO 3
 010D                     00069 L00010
 010D BD 023A         [9] 00070          jsr    ForPush   ; Get new FOR context
                          00071
 0110 86 01           [2] 00072          ldaa   #T00001>>8 ; Store top of loop address
 0112 A7 04           [6] 00073          staa   4,X
 0114 86 26           [2] 00074          ldaa   #T00001&$FF
 0116 A7 05           [6] 00075          staa   5,X
                          00076
 0118 86 06           [2] 00077          ldaa   #I>>8     ; Store variable address
 011A A7 06           [6] 00078          staa   6,X
 011C 86 01           [2] 00079          ldaa   #I&$FF
 011E A7 07           [6] 00080          staa   7,X
                          00081
 0120 86 00           [2] 00082          ldaa   #1>>8     ; Initialize variable
 0122 C6 01           [2] 00083          ldab   #1&$FF
                          00084
 0124 20 19 (013F)    [4] 00085          bra    T00004    ; Execute the body of the loop
                          00086
 0126                     00087 T00001
 0126 B6 0601         [4] 00088          ldaa   I         ; Load variable
 0129 F6 0602         [4] 00089          ldab   I+1
                          00090
 012C CB 01           [2] 00091          addb   #1&$FF    ; Add step
 012E 89 00           [2] 00092          adca   #1>>8
                          00093
 0130 81 00           [2] 00094          cmpa   #3>>8     ; Compare with limit
 0132 22 06 (013A)    [4] 00095          bhi    T00002    ; Higher, exit loop
 0134 25 07 (013D)    [4] 00096          blo    T00003    ; Lower, iterate loop
 0136 C1 03           [2] 00097	         cmpb   #3&$FF
 0138 23 03 (013D)    [4] 00098          bls    T00003    ; Not higher, iterate loop
                          00099
 013A                     00100 T00002
 013A 7E 026D         [3] 00101          jmp    ForPull   ; Remove FOR context and exit loop
                          00102
 013D                     00103 T00003
 013D 31              [4] 00104          ins              ; Discard return address from Next
 013E 31              [4] 00105          ins
                          00106
 013F                     00107 T00004
 013F B7 0601         [5] 00108          staa   I         ; Update variable
 0142 F7 0602         [5] 00109          stab   I+1
                          00110
                          00111 * 00020 PRINT I
 0145                     00112 L00020
 0145 FE 0601         [5] 00113          ldx    I
 0148 BD 041A         [9] 00114          jsr    PInt
                          00115
 014B BD 04BD         [9] 00116          jsr    NewLine
                          00117
                          00118 * 00030 NEXT I
 014E                     00119 L00030
 014E 86 06           [2] 00120          ldaa   #I>>8     ; Address the variable
 0150 C6 01           [2] 00121          ldab   #I&$FF
                          00122
 0152 BD 021D         [9] 00123          jsr    Next
                          00124
 0155                     00125 End_
.                         00348 ******************************************************************************
.                         00349 *
.                         00350 * Next - Process NEXT
.                         00351 *
.                         00352 * Input:
.                         00353 *       A:B = variable address
.                         00354 *
.021D                     00355 Next
.021D FE 0603         [5] 00356          ldx    ForTop    ; Point to current context
.0220 27 13 (0235)    [4] 00357          beq    Next2     ; Not within a FOR loop
.                         00358
.0222                     00359 Next0
.0222 A1 06           [5] 00360          cmpa   6,X       ; Compare variable address
.0224 26 08 (022E)    [4] 00361          bne    Next1     ; Loop variable mismatch
.0226 E1 07           [5] 00362          cmpb   7,X
.0228 26 04 (022E)    [4] 00363          bne    Next1
.                         00364
.022A EE 04           [6] 00365          ldx    4,X       ; Jump to top of loop
.022C 6E 00           [4] 00366          jmp    ,X
.                         00367
.022E                     00368 Next1
.022E EE 02           [6] 00369          ldx    2,X       ; Get previous context
.0230 FF 0603         [6] 00370          stx    ForTop    ; Save it
.0233 26 ED (0222)    [4] 00371          bne    Next0     ; Retry if valid
.                         00372
.0235                     00373 Next2
.0235 86 3E           [2] 00374          ldaa   #62       ; Report FOR-NEXT nesting error
.0237 7E 0276         [3] 00375          jmp    ErrH

OK, the integration is done and the compiler can handle FOR loops now, even the monstrosity in the following program:

100 n = 1
110 m = 3
120 for i = n+1 to m+1 step m-2
130 print i
140 next i

The FOR statement compiles into:

                          00088 * 120 for i = n+1 to m+1 step m-2
 0125                     00089 L00120
                          00090          ifdef  __TRACE
                          00091          ldx    #120
                          00092          jsr    Trace
                          00093          endif
                          00094          ifdef  __ATLIN
 0125 CE 0125         [3] 00095          ldx    #L00120
 0128 FF 0663         [6] 00096          stx    ResLn_
                          00097          endif
                          00098
 012B BD 0296         [9] 00099          jsr    ForEnter
                          00100
 012E 86 01           [2] 00101          ldaa   #T00000>>8
 0130 A7 04           [6] 00102          staa   4,X
 0132 86 49           [2] 00103          ldaa   #T00000&$FF
 0134 A7 05           [6] 00104          staa   5,X
                          00105
 0136 86 06           [2] 00106          ldaa   #I_>>8
 0138 A7 06           [6] 00107          staa   6,X
 013A 86 75           [2] 00108          ldaa   #I_&$FF
 013C A7 07           [6] 00109          staa   7,X
                          00110
 013E C6 01           [2] 00111          ldab   #1
 0140 4F              [2] 00112          clra
 0141 FB 0672         [4] 00113          addb   N_+1
 0144 B9 0671         [4] 00114          adca   N_
                          00115
 0147 20 46 (018F)    [4] 00116          bra    T00003
                          00117
 0149                     00118 T00000
 0149 C6 01           [2] 00119          ldab   #1
 014B 4F              [2] 00120          clra
 014C FB 0674         [4] 00121          addb   M_+1
 014F B9 0673         [4] 00122          adca   M_
 0152 F7 066E         [5] 00123          stab   ITp00_+1
 0155 B7 066D         [5] 00124          staa   ITp00_
                          00125
 0158 F6 0674         [4] 00126          ldab   M_+1
 015B B6 0673         [4] 00127          ldaa   M_
 015E C0 02           [2] 00128          subb   #2
 0160 82 00           [2] 00129          sbca   #0
 0162 B7 066F         [5] 00130          staa   ITp01_
                          00131
 0165 FB 0676         [4] 00132          addb   I_+1
 0168 B9 0675         [4] 00133          adca   I_
                          00134
 016B 7D 066F         [6] 00135          tst    ITp01_
 016E 2B 0E (017E)    [4] 00136          bmi    T00004
                          00137
 0170 B1 066D         [4] 00138          cmpa   ITp00_
 0173 22 15 (018A)    [4] 00139          bhi    T00001
 0175 25 16 (018D)    [4] 00140          blo    T00002
 0177 F1 066E         [4] 00141          cmpb   ITp00_+1
 017A 23 11 (018D)    [4] 00142          bls    T00002
 017C 20 0C (018A)    [4] 00143          bra    T00001
                          00144
 017E                     00145 T00004
 017E B1 066D         [4] 00146          cmpa   ITp00_
 0181 25 07 (018A)    [4] 00147          blo    T00001
 0183 22 08 (018D)    [4] 00148          bhi    T00002
 0185 F1 066E         [4] 00149          cmpb   ITp00_+1
 0188 24 03 (018D)    [4] 00150          bhs    T00002
                          00151
 018A                     00152 T00001
 018A 7E 02D3         [3] 00153          jmp    ForExit
                          00154
 018D                     00155 T00002
 018D 31              [4] 00156          ins
 018E 31              [4] 00157          ins
                          00158
 018F                     00159 T00003
 018F B7 0675         [5] 00160          staa   I_
 0192 F7 0676         [5] 00161          stab   I_+1
1 Like

Does anybody see anything noteworthy in the following code?

                          00075 ; 100 for i=1 to 3
 0B12                     00076 L00100:
                          00077          ifdef  __TRACE
                          00078          ldx    #<100
                          00079          lda    #>100
                          00080          jsr    Trace
                          00081          endif
                          00082          ifdef  __ATLIN
 0B12 A2 12           [2] 00083          ldx    #<L00100
 0B14 8E 11E4         [4] 00084          stx    ResLn_
 0B17 A2 0B           [2] 00085          ldx    #>L00100
 0B19 8E 11E5         [4] 00086          stx    ResLn_+1
                          00087          endif
                          00088
 0B1C 20 0CCC         [6] 00089          jsr    ForEnter
                          00090
 0B1F A0 04           [2] 00091          ldy    #4
 0B21 A9 3B           [2] 00092          lda    #<T00000
 0B23 91 1C           [6] 00093          sta    (Ptr0),Y
 0B25 C8              [2] 00094          iny
 0B26 A9 0B           [2] 00095          lda    #>T00000
 0B28 91 1C           [6] 00096          sta    (Ptr0),Y
                          00097
 0B2A C8              [2] 00098          iny
 0B2B A9 EE           [2] 00099          lda    #<I_
 0B2D 91 1C           [6] 00100          sta    (Ptr0),Y
 0B2F C8              [2] 00101          iny
 0B30 A9 11           [2] 00102          lda    #>I_
 0B32 91 1C           [6] 00103          sta    (Ptr0),Y
                          00104
 0B34 A2 01           [2] 00105          ldx    #<1
 0B36 A0 00           [2] 00106          ldy    #>1
                          00107
 0B38 4C 0B59         [3] 00108          jmp    T00003
                          00109
 0B3B                     00110 T00000:
 0B3B A9 01           [2] 00111          lda    #<1
 0B3D 18              [2] 00112          clc
 0B3E 6D 11EE         [4] 00113          adc    I_
 0B41 AA              [2] 00114          tax
 0B42 A9 00           [2] 00115          lda    #>1
 0B44 6D 11EF         [4] 00116          adc    I_+1
                          00117
 0B47 C9 00           [2] 00118          cmp    #>3
 0B49 90 0B (0B56)  [2/3] 00119          blo    T00002
 0B4B D0 06 (0B53)  [2/3] 00120          bne    T00001
                          00121
 0B4D E0 03           [2] 00122          cpx    #<3
 0B4F 90 05 (0B56)  [2/3] 00123          blo    T00002
 0B51 F0 03 (0B56)  [2/3] 00124          beq    T00002
                          00125
 0B53                     00126 T00001:
 0B53 4C 0D26         [3] 00127          jmp    ForExit
                          00128
 0B56                     00129 T00002:
 0B56 A8              [2] 00130          tay
 0B57 68              [4] 00131          pla
 0B58 68              [4] 00132          pla
                          00133
 0B59                     00134 T00003:
 0B59 8E 11EE         [4] 00135          stx    I_
 0B5C 8C 11EF         [4] 00136          sty    I_+1
                          00137
                          00138 ; 110 print i
 0B5F                     00139 L00110:
                          00140          ifdef  __TRACE
                          00141          ldx    #<110
                          00142          lda    #>110
                          00143          jsr    Trace
                          00144          endif
                          00145          ifdef  __ATLIN
 0B5F A2 5F           [2] 00146          ldx    #<L00110
 0B61 8E 11E4         [4] 00147          stx    ResLn_
 0B64 A2 0B           [2] 00148          ldx    #>L00110
 0B66 8E 11E5         [4] 00149          stx    ResLn_+1
                          00150          endif
                          00151
 0B69 AE 11EE         [4] 00152          ldx    I_
 0B6C AD 11EF         [4] 00153          lda    I_+1
 0B6F 20 0F5A         [6] 00154          jsr    PInt
                          00155
 0B72 20 1067         [6] 00156          jsr    NewLine
                          00157
                          00158 ; 120 next i
 0B75                     00159 L00120:
                          00160          ifdef  __TRACE
                          00161          ldx    #<120
                          00162          lda    #>120
                          00163          jsr    Trace
                          00164          endif
                          00165          ifdef  __ATLIN
 0B75 A2 75           [2] 00166          ldx    #<L00120
 0B77 8E 11E4         [4] 00167          stx    ResLn_
 0B7A A2 0B           [2] 00168          ldx    #>L00120
 0B7C 8E 11E5         [4] 00169	         stx    ResLn_+1
                          00170          endif
                          00171
 0B7F A2 EE           [2] 00172          ldx    #<I_
 0B81 A9 11           [2] 00173          lda    #>I_
 0B83 20 0C80         [6] 00174          jsr    ForNext
                          00175
 0B86                     00176 End_:

The Poor Man’s Linker experiment has gone well. The conversion of the run-time library to the new approach is done. In the foreseeable future, my Python library (currently 6502 only) will be converted in the same way; it really needs it.

The following program, the skeleton of a monitor or a debugger, compiles into a 6800 binary image a little under 2K bytes in size; it uses quite a bit but not all of the library code.

10 PRINT '? ';
20 INPUT LINE A$
30 GOSUB 1000:REM Strip leading spaces from A$
40 B$=LEFT$(A$,1):A$=MID$(A$,2):GOSUB 1000:GOSUB 1100
50 IF B$ = 'D' THEN 10000
60 IF B$ = 'U' THEN 12000
90 IF B$ = 'Q' THEN END
95 PRINT 'Unrecognized command.':GOTO 10
1000 REM Strip leading spaces from A$
1010 IF ASC(' ') = ASC(A$) THEN A$=MID$(A$,2) ELSE RETURN
1020 GOTO 1010
1100 REM Convert B$ to upper case
1110 IF ASC('a') <= ASC(B$) AND ASC('z') >= ASC(B$) THEN B$=CHR$(ASC(B$)-32)
1120 RETURN
10000 REM DUMP
10001 print "DUMP ";a$:goto 10
12000 REM UNASSEMBLE
12001 print "UNASSEMBLE ";a$:goto 10

You may have noticed the ordering of “IF ASC(’ ') = ASC(A$)” as a bit unusual. A single-pass compiler which generates code as it parses the source can do a better job if simple expressions which can be completely evaluated at compile time are on the left while things which must wait until run time are postponed as long as possible. The opposite “IF ASC(A$) = ASC(’ ')” gets the first character from the string, puts it into a temporary variable, then does the comparison because without lookahead, the compiler does not know that a constant follows.

Since what I thought was source code for the FLEX BASIC interpreter turned out to be something else, SWTPC BASIC instead of TSC BASIC, I will be building a version of this compiler to generate 6502 code for TSC BASIC programs. I am just starting to reverse engineer the FLEX BASIC and Extended BASIC interpreters and thus cannot commit to doing anything with them at this time.

For comparison, here is some code from the two compilers…

The 6502 code generated by the compiler:

                          00108 ; 120 C = A * (B + 1)
 0B26                     00109	L00120:
                          00110          ifdef  __TRACE
                          00111          ldx    #<120
                          00112          lda    #>120
                          00113          jsr    Trace
                          00114          endif
                          00115          ifdef  __ATLIN
                          00116          ldx    #<L00120
                          00117          stx    ResLn_
                          00118          ldx    #>L00120
                          00119          stx    ResLn_+1
                          00120          endif
                          00121
 0B26 18              [2] 00122          clc
 0B27 AD 0F39         [4] 00123          lda    B_
 0B2A 69 01           [2] 00124          adc    #<1
 0B2C AA              [2] 00125          tax
 0B2D AD 0F3A         [4] 00126          lda    B_+1
 0B30 69 00           [2] 00127          adc    #>1
 0B32 AC 0F37         [4] 00128          ldy    A_
 0B35 84 14           [3] 00129          sty    Int0
 0B37 AC 0F38         [4] 00130          ldy    A_+1
 0B3A 84 15           [3] 00131          sty    Int0+1
 0B3C 20 0D30         [6] 00132          jsr    IMul
 0B3F 8E 0F3B         [4] 00133          stx    C_
 0B42 8D 0F3C         [4] 00134          sta    C_+1

The 6800 code generated by the compiler:

                          00083 * 120 C = A * (B + 1)
 0119                     00084 L00120
                          00085          ifdef  __TRACE
                          00086          ldx    #120
                          00087          jsr    Trace
                          00088          endif
                          00089          ifdef  __ATLIN
                          00090          ldx    #L00120
                          00091          stx    ResLn_
                          00092          endif
                          00093
 0119 C6 01           [2] 00094          ldab   #1
 011B 4F              [2] 00095          clra
 011C FB 0435         [4] 00096          addb   B_+1
 011F B9 0434         [4] 00097          adca   B_
 0122 FE 0432         [5] 00098          ldx    A_
 0125 BD 0297         [9] 00099          jsr    IMul
 0128 F7 0437         [5] 00100          stab   C_+1
 012B B7 0436         [5] 00101          staa   C_

The 6502 multiply subroutine:

.                         00730 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.                         00731 ;
.                         00732 ; IMul - Multiply integers
.                         00733 ;
.                         00734 ; Input:
.                         00735 ;       A:X = one number
.                         00736 ;       Int0 = the other number
.                         00737 ;
.                         00738 ; Output:
.                         00739 ;       A:X = the product
.                         00740 ;
.                         00741
.0D2C                     00742 IMulB    rmb    2         ; Operand
.0D2E                     00743 IMulC    rmb    1         ; Bits left
.0D2F                     00744 IMulS    rmb    1         ; Sign of the product
.                         00745
.0D30                     00746 IMul:
.0D30 A0 00           [2] 00747          ldy    #0        ; Clear sign of product
.0D32 8C 0D2F         [4] 00748          sty    IMulS
.                         00749
.0D35 C9 00           [2] 00750          cmp    #0        ; Is first number negative?
.0D37 10 10 (0D49)  [2/3] 00751          bpl    IMul1     ; No
.                         00752
.0D39 EE 0D2F         [6] 00753          inc    IMulS     ; Update sign of product
.                         00754
.0D3C 49 FF           [2] 00755          eor    #$FF      ; Negate the number
.0D3E A8              [2] 00756          tay
.0D3F 8A              [2] 00757          txa
.0D40 49 FF           [2] 00758          eor    #$FF
.0D42 18              [2] 00759          clc
.0D43 69 01           [2] 00760          adc    #1
.0D45 AA              [2] 00761          tax
.0D46 98              [2] 00762          tya
.0D47 69 00           [2] 00763          adc    #0
.                         00764
.0D49                     00765 IMul1:
.0D49 8E 0D2C         [4] 00766          stx    IMulB     ; Save the number
.0D4C 8D 0D2D         [4] 00767          sta    IMulB+1
.                         00768
.0D4F A5 15           [3] 00769          lda    Int0+1    ; Is the other number negative?
.0D51 10 10 (0D63)  [2/3] 00770          bpl    IMul2     ; No
.                         00771
.0D53 EE 0D2F         [6] 00772          inc    IMulS     ; Update sign of product
.                         00773
.0D56 A9 00           [2] 00774          lda    #0        ; Negate the other number
.0D58 38              [2] 00775          sec
.0D59 E5 14           [3] 00776          sbc    Int0
.0D5B 85 14           [3] 00777          sta    Int0
.0D5D A9 00           [2] 00778          lda    #0
.0D5F E5 15           [3] 00779          sbc    Int0+1
.0D61 85 15           [3] 00780          sta    Int0+1
.                         00781
.0D63                     00782 IMul2:
.0D63 A9 10           [2] 00783          lda    #16       ; 16 bits to multiply
.0D65 8D 0D2E         [4] 00784          sta    IMulC
.                         00785
.0D68 A9 00           [2] 00786          lda    #0        ; Clear product
.0D6A A8              [2] 00787          tay
.                         00788
.0D6B                     00789 IMul3:
.0D6B 4E 0D2D         [6] 00790          lsr    IMulB+1   ; Shift number right
.0D6E 6E 0D2C         [6] 00791          ror    IMulB
.0D71 90 09 (0D7C)  [2/3] 00792          bcc    IMul4     ; Skip add if low bit was clear
.                         00793
.0D73 18              [2] 00794          clc              ; Add number to product
.0D74 65 14           [3] 00795          adc    Int0
.0D76 AA              [2] 00796          tax
.0D77 98              [2] 00797          tya
.0D78 65 15           [3] 00798          adc    Int0+1
.0D7A A8              [2] 00799          tay
.0D7B 8A              [2] 00800          txa
.                         00801
.0D7C                     00802 IMul4:
.0D7C 06 14           [5] 00803          asl    Int0      ; Shift the other number left
.0D7E 26 15           [5] 00804          rol    Int0+1
.                         00805
.0D80 CE 0D2E         [6] 00806          dec    IMulC     ; One fewer bit to do
.0D83 D0 E6 (0D6B)  [2/3] 00807          bne    IMul3     ; More?
.                         00808
.0D85 4E 0D2F         [6] 00809          lsr    IMulS     ; Is product negative?
.0D88 90 0E (0D98)  [2/3] 00810          bcc    IMul5     ; No
.                         00811
.0D8A 49 FF           [2] 00812          eor    #$FF      ; Negate the product
.0D8C 18              [2] 00813          clc
.0D8D 69 01           [2] 00814          adc    #1
.0D8F AA              [2] 00815          tax
.0D90 98              [2] 00816          tya
.0D91 49 FF           [2] 00817          eor    #$FF
.0D93 69 00           [2] 00818          adc    #0
.                         00819
.0D95 4C 0D9A         [3] 00820          jmp    IMul6
.                         00821
.0D98                     00822 IMul5:
.0D98 AA              [2] 00823          tax              ; Product goes in A:X
.0D99 98              [2] 00824          tya
.                         00825
.0D9A                     00826 IMul6:
.0D9A 60              [6] 00827          rts

The 6800 multiply subroutine:

.                         00560 ******************************************************************************
.                         00561 *
.                         00562	* IMul - Multiply integers
.                         00563 *
.                         00564 * Input:
.                         00565 *       A:B = one number
.                         00566 *       X = the other number
.                         00567 *
.                         00568 * Output:
.                         00569 *       A:B = the product
.                         00570 *
.0293                     00571 IMulB    rmb    2         ; Operand
.0295                     00572 IMulC    rmb    1         ; Bits left
.0296                     00573 IMulS    rmb    1         ; Sign of the product
.                         00574
.0297                     00575 IMul
.0297 7F 0296         [6] 00576          clr    IMulS     ; Clear sign of product
.                         00577
.029A DF 02           [5] 00578          stx    Int0      ; Save second number
.                         00579
.029C 4D              [2] 00580          tsta             ; Is first number negative
.029D 2A 07 (02A6)    [4] 00581          bpl    IMul1     ; No
.                         00582
.029F 7C 0296         [6] 00583          inc    IMulS     ; Update sign of product
.                         00584
.02A2 40              [2] 00585	         nega             ; Negate the number
.02A3 50              [2] 00586          negb
.02A4 82 00           [2] 00587          sbca   #0
.                         00588
.02A6                     00589 IMul1
.02A6 B7 0293         [5] 00590          staa   IMulB     ; Save the number
.02A9 F7 0294         [5] 00591          stab   IMulB+1
.                         00592
.02AC 96 02           [3] 00593          ldaa   Int0      ; Is the other number negative?
.02AE 2A 0E (02BE)    [4] 00594          bpl    IMul2     ; No
.                         00595
.02B0 7C 0296         [6] 00596          inc    IMulS     ; Update sign of product
.                         00597
.02B3 73 0002         [6] 00598          com    Int0      ; Negate the other number
.02B6 70 0003         [6] 00599          neg    Int0+1
.02B9 25 03 (02BE)    [4] 00600          bcs    IMul2
.02BB 7A 0002         [6] 00601          dec    Int0
.                         00602
.02BE                     00603 IMul2
.02BE 86 10           [2] 00604          ldaa   #16       ; 16 bits to multiply
.02C0 B7 0295         [5] 00605          staa   IMulC
.                         00606
.02C3 4F              [2] 00607          clra             ; Clear product
.02C4 5F              [2] 00608          clrb
.                         00609
.02C5                     00610 IMul3
.02C5 74 0293         [6] 00611          lsr    IMulB     ; Shift number right
.02C8 76 0294         [6] 00612          ror    IMulB+1
.02CB 24 04 (02D1)    [4] 00613          bcc    IMul4     ; Skip add if low bit was clear
.                         00614
.02CD DB 03           [3] 00615          addb   Int0+1    ; Add number to product
.02CF 99 02           [3] 00616          adca   Int0
.                         00617
.02D1                     00618 IMul4
.02D1 78 0003         [6] 00619          asl    Int0+1    ; Shift the other number left
.02D4 79 0002         [6] 00620          rol    Int0
.                         00621
.02D7 7A 0295         [6] 00622          dec    IMulC     ; One fewer bit to do
.02DA 26 E9 (02C5)    [4] 00623          bne    IMul3     ; More?
.                         00624
.02DC 74 0296         [6] 00625          lsr    IMulS     ; Is the product negative?
.02DF 24 04 (02E5)    [4] 00626          bcc    IMul5     ; No
.                         00627
.02E1 40              [2] 00628          nega             ; Negate the product
.02E2 50              [2] 00629          negb
.02E3 82 00           [2] 00630          sbca   #0
.                         00631
.02E5                     00632 IMul5
.02E5 39              [5] 00633          rts
2 Likes

I have been playing with my toy Pascal compilers. This is some of the generated code…

For the 6502:

                          00084 ; 00009     A := 1;
 0B09 A2 01           [2] 00085          ldx    #1
 0B0B A9 00           [2] 00086          lda    #0
 0B0D 8E 0E71         [4] 00087          stx    A_
 0B10 8D 0E72         [4] 00088          sta    A_+1
                          00089 ; 00010     B := A + 1;
 0B13 18              [2] 00090          clc
 0B14 AD 0E71         [4] 00091          lda    A_
 0B17 69 01           [2] 00092          adc    #1
 0B19 AA              [2] 00093          tax
 0B1A AD 0E72         [4] 00094          lda    A_+1
 0B1D 69 00           [2] 00095          adc    #0
 0B1F 8E 0E73         [4] 00096          stx    B_
 0B22 8D 0E74         [4] 00097          sta    B_+1
                          00098 ; 00011     A := B;
 0B25 AE 0E73         [4] 00099          ldx    B_
 0B28 AD 0E74         [4] 00100          lda    B_+1
 0B2B 8E 0E71         [4] 00101          stx    A_
 0B2E 8D 0E72         [4] 00102          sta    A_+1
                          00103 ; 00012     C := A + B;
 0B31 18              [2] 00104          clc
 0B32 AD 0E71         [4] 00105          lda    A_
 0B35 6D 0E73         [4] 00106          adc    B_
 0B38 AA              [2] 00107          tax
 0B39 AD 0E72         [4] 00108          lda    A_+1
 0B3C 6D 0E74         [4] 00109          adc    B_+1
 0B3F 8E 0E75         [4] 00110          stx    C_
 0B42 8D 0E76         [4] 00111          sta    C_+1
                          00112 ; 00013     writeln('Hello world.');
 0B45 A2 64           [2] 00113          ldx    #<S_00000
 0B47 A9 0E           [2] 00114          lda    #>S_00000
 0B49 20 0CBE         [6] 00115          jsr    WriteString
 0B4C 20 0CF8         [6] 00116          jsr    PCRLF

For the 8080:

                          00029 ; 00009     A := 1;
 0100 21 0001        [10] 00030         lxi     H,1
 0103 22 01F3        [16] 00031         shld    A_
                          00032 ; 00010     B := A + 1;
 0106 2A 01F3        [16] 00033         lhld    A_
 0109 11 0001        [10] 00034         lxi     D,1
 010C 19             [10] 00035         dad     D
 010D 22 01F5        [16] 00036         shld    B_
                          00037 ; 00011     A := B;
 0110 2A 01F5        [16] 00038         lhld    B_
 0113 22 01F3        [16] 00039         shld    A_
                          00040 ; 00012     C := A + B;
 0116 2A 01F3        [16] 00041         lhld    A_
 0119 EB              [4] 00042         xchg
 011A 2A 01F5        [16] 00043         lhld    B_
 011D 19             [10] 00044         dad     D
 011E 22 01F7        [16] 00045         shld    C_
                          00046 ; 00013     writeln('Hello world.');
 0121 21 01D6        [10] 00047         lxi     H,S_00000
 0124 CD 019C        [17] 00048         call    WriteString
 0127 CD 018D        [17] 00049         call    PCRLF

And for the 6800:

                          00068 ; 00009     A := 1;
 010A CE 0001         [3] 00069          ldx    #1
 010D FF 02FD         [6] 00070          stx    A_
                          00071 ; 00010     B := A + 1;
 0110 F6 02FE         [4] 00072          ldab   A_+1
 0113 CB 01           [2] 00073          addb   #1
 0115 B6 02FD         [4] 00074          ldaa   A_
 0118 89 00           [2] 00075          adca   #0
 011A F7 0300         [5] 00076          stab   B_+1
 011D B7 02FF         [5] 00077          staa   B_
                          00078 ; 00011     A := B;
 0120 FE 02FF         [5] 00079          ldx    B_
 0123 FF 02FD         [6] 00080          stx    A_
                          00081 ; 00012     C := A + B;
 0126 F6 02FE         [4] 00082          ldab   A_+1
 0129 FB 0300         [4] 00083          addb   B_+1
 012C B6 02FD         [4] 00084          ldaa   A_
 012F B9 02FF         [4] 00085          adca   B_
 0132 F7 0302         [5] 00086          stab   C_+1
 0135 B7 0301         [5] 00087          staa   C_
                          00088 ; 00013     writeln('Hello world.');
 0138 CE 02F0         [3] 00089          ldx    #S_00000
 013B BD 01A2         [9] 00090          jsr    WriteString
 013E BD 01CA         [9] 00091          jsr    PCRLF

There is no optimization at this time other than some simple things which can be done while parsing.

Obvious opportunities for

A + 1

are

ldx     A_
inx

for the 6800

and

lhld    A_
inx     H

for the 8080.

3 Likes

An implementation of the Double Dabble algorithm for converting a number to an ASCII decimal representation was previously disclosed.

It has since been streamlined and generalized to handle numbers in sizes of one, two and four bytes.

This is the code for the 6502:

.                         00199 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.                         00200 ;
.                         00201 ; Format - Convert a number to ASCII decimal
.                         00202 ;
.                         00203 ; Input:
.                         00204 ;       The first bytes of Dabble = the number to convert
.                         00205 ;       Byt1 = number of bits to convert
.                         00206 ;       Byt2 = number of bytes to convert
.                         00207 ;       Byt3 = number of packed BCD bytes in result
.                         00208 ;
.                         00209 ; Output:
.                         00210 ;       Output string with length byte starting at Out+1
.                         00211 ;
.                         00212 ; Uses:
.                         00213 ;       Byt0
.                         00214 ;       Byt4
.                         00215 ;
.                         00216 ; Note:
.                         00217 ;       Implemented with the Double Dabble algorithm:
.                         00218 ;
.                         00219 ;         https://en.wikipedia.org/wiki/Double_dabble
.                         00220 ;
.0C27                     00221 Format:
.0C27 A9 00           [2] 00222          lda    #0        ; Clear the BCD digits
.0C29 A6 24           [3] 00223          ldx    Byt2
.0C2B A4 25           [3] 00224          ldy    Byt3
.                         00225
.0C2D                     00226 Format0:
.0C2D 95 2B           [4] 00227          sta    Dabble,X
.0C2F E8              [2] 00228          inx
.0C30 88              [2] 00229          dey
.0C31 D0 FA (0C2D)  [2/3] 00230          bne    Format0
.                         00231
.0C33 18              [2] 00232          clc              ; Determine index of BCD digits
.0C34 A5 25           [3] 00233          lda    Byt3      ; BCD digits are big endian order
.0C36 65 24           [3] 00234          adc    Byt2      ; Index := Num number bytes + Num BCD bytes - 1
.0C38 E9 00           [2] 00235          sbc    #0
.0C3A 85 26           [3] 00236          sta    Byt4
.                         00237
.0C3C                     00238 Format1:
.0C3C A6 26           [3] 00239          ldx    Byt4      ; Index to BCD digits
.0C3E A4 25           [3] 00240          ldy    Byt3      ; Number of bytes in converted BCD
.                         00241
.0C40                     00242 Format2:
.0C40 B5 2B           [4] 00243          lda    Dabble,X  ; Isolate lower nybble
.0C42 29 0F           [2] 00244          and    #$F
.0C44 C9 05           [2] 00245          cmp    #4+1      ; If greater than 4, add 3
.0C46 90 03 (0C4B)  [2/3] 00246          blo    FormatLoNotGT4
.                         00247
.0C48 18              [2] 00248          clc
.0C49 69 03           [2] 00249          adc    #3
.                         00250
.0C4B                     00251 FormatLoNotGT4:
.0C4B 85 22           [3] 00252          sta    Byt0      ; Stash new lower nybble
.                         00253
.0C4D B5 2B           [4] 00254          lda    Dabble,X   ; Isolate upper nybble
.0C4F 29 F0           [2] 00255          and    #$F0
.0C51 C9 50           [2] 00256          cmp    #$40+$10  ; If greater than 4, add 3
.0C53 90 03 (0C58)  [2/3] 00257          blo    FormatHiNotGT4
.                         00258
.0C55 18              [2] 00259          clc
.0C56 69 30           [2] 00260          adc    #$30
.                         00261
.0C58                     00262 FormatHiNotGT4:
.0C58 05 22           [3] 00263          ora    Byt0      ; Combine nybbles
.0C5A 95 2B           [4] 00264          sta    Dabble,X
.                         00265
.0C5C CA              [2] 00266          dex
.0C5D 88              [2] 00267          dey              ; More BCD digits to check?
.0C5E D0 E0 (0C40)  [2/3] 00268          bne    Format2
.                         00269
.0C60 06 2B           [5] 00270          asl    Dabble    ; Shift the number left one bit
.0C62 A2 01           [2] 00271          ldx    #1
.0C64 A4 24           [3] 00272          ldy    Byt2
.0C66 88              [2] 00273          dey
.0C67 F0 06 (0C6F)  [2/3] 00274          beq    Format3   ; No additional bytes
.                         00275
.0C69                     00276 FormatShiftNumber:
.0C69 36 2B           [6] 00277          rol    Dabble,X  ; Shift the rest of the number
.0C6B E8              [2] 00278          inx
.0C6C 88              [2] 00279          dey
.0C6D D0 FA (0C69)  [2/3] 00280          bne    FormatShiftNumber
.                         00281
.0C6F                     00282 Format3:
.0C6F A6 26           [3] 00283          ldx    Byt4      ; Shift the BCD digits left one bit
.0C71 A4 25           [3] 00284          ldy    Byt3
.                         00285
.0C73                     00286 FormatShiftBCD:
.0C73 36 2B           [6] 00287          rol    Dabble,X
.0C75 CA              [2] 00288          dex
.0C76 88              [2] 00289          dey
.0C77 D0 FA (0C73)  [2/3] 00290          bne    FormatShiftBCD
.                         00291
.0C79 C6 23           [5] 00292          dec    Byt1      ; More bits to process?
.0C7B D0 BF (0C3C)  [2/3] 00293          bne    Format1
.                         00294
.0C7D A4 24           [3] 00295          ldy    Byt2      ; Index converted BCD
.0C7F A2 00           [2] 00296          ldx    #0        ; And the output string
.                         00297
.0C81                     00298 Format4:
.0C81 B9 002B       [4/5] 00299          lda    Dabble,Y  ; Isolate upper digit
.0C84 29 F0           [2] 00300          and    #$F0
.0C86 D0 04 (0C8C)  [2/3] 00301          bne    FormatEmitHi
.                         00302
.0C88 E0 00           [2] 00303          cpx    #0        ; Leading zero?
.0C8A F0 0A (0C96)  [2/3] 00304          beq    FormatSkipHi
.                         00305
.0C8C                     00306 FormatEmitHi:
.0C8C 4A              [2] 00307          lsr    A         ; Shift into lower nybble
.0C8D 4A              [2] 00308          lsr    A
.0C8E 4A              [2] 00309          lsr    A
.0C8F 4A              [2] 00310          lsr    A
.                         00311
.0C90 18              [2] 00312          clc              ; Convert to ASCII numeral
.0C91 69 30           [2] 00313          adc    #'0'
.0C93 E8              [2] 00314          inx
.0C94 95 29           [4] 00315          sta    Out+1,X
.                         00316
.0C96                     00317 FormatSkipHi:
.0C96 B9 002B       [4/5] 00318          lda    Dabble,Y  ; Isolate lower digit
.0C99 29 0F           [2] 00319          and    #$F
.0C9B D0 04 (0CA1)  [2/3] 00320          bne    FormatEmitLo
.                         00321
.0C9D E0 00           [2] 00322          cpx    #0        ; Leading zero?
.0C9F F0 06 (0CA7)  [2/3] 00323          beq    FormatSkipLo
.                         00324
.0CA1                     00325 FormatEmitLo:
.0CA1 18              [2] 00326          clc              ; Convert to ASCII numeral
.0CA2 69 30           [2] 00327          adc    #'0'
.0CA4 E8              [2] 00328          inx
.0CA5 95 29           [4] 00329          sta    Out+1,X
.                         00330
.0CA7                     00331 FormatSkipLo:
.0CA7 C8              [2] 00332          iny              ; Address next pair of digits
.                         00333
.0CA8 C6 25           [5] 00334          dec    Byt3      ; More digits?
.0CAA D0 D5 (0C81)  [2/3] 00335          bne    Format4
.                         00336
.0CAC 86 29           [3] 00337          stx    Out+1     ; Store length of result
.0CAE 8A              [2] 00338          txa
.0CAF D0 08 (0CB9)  [2/3] 00339          bne    FormatDone ; Check for all 0's
.                         00340
.0CB1 A9 01           [2] 00341          lda    #1        ; Default to "0"
.0CB3 85 29           [3] 00342          sta    Out+1
.0CB5 A9 30           [2] 00343          lda    #'0'
.0CB7 85 2A           [3] 00344          sta    Out+1+1
.                         00345
.0CB9                     00346 FormatDone:
.0CB9 60              [6] 00347          rts

For the 6800:

.                         00180 ******************************************************************************
.                         00181 *
.                         00182 * Format - Convert a number to ASCII decimal
.                         00183 *
.                         00184 * Input:
.                         00185 *       The first bytes of Dabble = the number to convert
.                         00186 *       Int0 = number of bytes to convert
.                         00187 *       Byt1 = number of bits to convert
.                         00188 *       Byt2 = number of packed BCD bytes in result
.                         00189 *
.                         00190 * Output:
.                         00191 *       Output string with length byte starting at Out+1
.                         00192 *
.                         00193 * Uses:
.                         00194 *       Int1
.                         00195 *       Byt0
.                         00196 *
.                         00197 * Note:
.                         00198 *       Implemented with the Double Dabble algorithm:
.                         00199 *
.                         00200 *         https://en.wikipedia.org/wiki/Double_dabble
.                         00201 *
.020B                     00202 Format
.020B 86 00           [2] 00203          ldaa   #0        ; Clear the BCD digits
.020D DE 02           [4] 00204          ldx    Int0
.020F D6 12           [3] 00205          ldab   Byt2
.                         00206
.0211                     00207 Format0
.0211 A7 19           [6] 00208          staa   Dabble,X
.0213 08              [4] 00209          inx
.0214 5A              [2] 00210          decb
.0215 26 FA (0211)    [4] 00211          bne    Format0
.                         00212
.0217 96 12           [3] 00213          ldaa   Byt2      ; Determine index of BCD digits
.0219 9B 03           [3] 00214          adda   Int0+1    ; BCD digits are big endian order
.021B 4A              [2] 00215          deca             ; Index := Num number bytes + Num BCD bytes - 1
.021C 97 05           [4] 00216          staa   Int1+1
.021E 7F 0004         [6] 00217          clr    Int1      ; Clear upper byte of index
.                         00218
.0221                     00219 Format1
.0221 DE 04           [4] 00220          ldx    Int1      ; Index to BCD digits
.0223 D6 12           [3] 00221          ldab   Byt2      ; Number of bytes in converted BCD
.                         00222
.0225                     00223 Format2
.0225 A6 19           [5] 00224          ldaa   Dabble,X  ; Isolate lower nybble
.0227 84 0F           [2] 00225          anda   #$F
.0229 81 05           [2] 00226          cmpa   #4+1      ; If greater than 4, add 3
.022B 25 02 (022F)    [4] 00227          blo    FormatLoNotGT4
.                         00228
.022D 8B 03           [2] 00229          adda   #3
.                         00230
.022F                     00231 FormatLoNotGT4
.022F 97 10           [4] 00232          staa   Byt0      ; Stash new lower nybble
.                         00233
.0231 A6 19           [5] 00234          ldaa   Dabble,X  ; Isolate upper nybble
.0233 84 F0           [2] 00235          anda   #$F0
.0235 81 50           [2] 00236          cmpa   #$40+$10  ; If greater than 4, add 3
.0237 25 02 (023B)    [4] 00237          blo    FormatHiNotGT4
.                         00238
.0239 8B 30           [2] 00239          adda   #$30
.                         00240
.023B                     00241 FormatHiNotGT4
.023B 9A 10           [3] 00242          oraa   Byt0      ; Combine nybbles
.023D A7 19           [6] 00243          staa   Dabble,X
.                         00244
.023F 09              [4] 00245          dex
.0240 5A              [2] 00246          decb             ; More BCD digits to check?
.0241 26 E2 (0225)    [4] 00247          bne    Format2
.                         00248
.0243 DE 02           [4] 00249          ldx    Int0      ; Address the number to convert
.0245 09              [4] 00250          dex
.                         00251
.0246 68 19           [7] 00252          asl    Dabble,X  ; Shift the number left one bit
.0248 D6 03           [3] 00253          ldab   Int0+1
.024A 5A              [2] 00254          decb
.024B 27 06 (0253)    [4] 00255          beq    Format3   ; No additional bytes
.                         00256
.024D                     00257 FormatShiftNumber
.024D 09              [4] 00258          dex
.024E 69 19           [7] 00259          rol    Dabble,X  ; Shift the rest of the number
.0250 5A              [2] 00260          decb
.0251 26 FA (024D)    [4] 00261          bne    FormatShiftNumber
.                         00262
.0253                     00263 Format3
.0253 DE 04           [4] 00264          ldx    Int1      ; Shift the BCD digits left one bit
.0255 D6 12           [3] 00265          ldab   Byt2
.                         00266
.0257                     00267 FormatShiftBCD
.0257 69 19           [7] 00268          rol    Dabble,X
.0259 09              [4] 00269          dex
.025A 5A              [2] 00270          decb
.025B 26 FA (0257)    [4] 00271          bne    FormatShiftBCD
.                         00272
.025D 7A 0011         [6] 00273          dec    Byt1      ; More bits to process?
.0260 26 BF (0221)    [4] 00274          bne    Format1
.                         00275
.0262 CE 0000         [3] 00276          ldx    #0        ; And the output string
.0265 DF 04           [5] 00277          stx    Int1
.                         00278
.0267                     00279 Format4
.0267 DE 02           [4] 00280          ldx    Int0      ; Index converted BCD
.0269 A6 19           [5] 00281          ldaa   Dabble,X  ; Isolate upper digit
.026B 84 F0           [2] 00282          anda   #$F0
.026D 26 05 (0274)    [4] 00283          bne    FormatEmitHi
.                         00284
.026F 7D 0005         [6] 00285          tst    Int1+1    ; Leading zero?
.0272 27 0F (0283)    [4] 00286          beq    FormatSkipHi
.                         00287
.0274                     00288 FormatEmitHi:
.0274 44              [2] 00289          lsra             ; Shift into lower nybble
.0275 44              [2] 00290          lsra
.0276 44              [2] 00291          lsra
.0277 44              [2] 00292          lsra
.                         00293
.0278 8B 30           [2] 00294          adda   #'0'      ; Convert to ASCII numeral
.027A DE 04           [4] 00295          ldx    Int1
.027C 08              [4] 00296          inx
.027D DF 04           [5] 00297          stx    Int1
.027F A7 17           [6] 00298          staa   Out+1,X
.0281 DE 02           [4] 00299          ldx    Int0      ; Index converted BCD
.                         00300
.0283                     00301 FormatSkipHi:
.0283 A6 19           [5] 00302          ldaa   Dabble,X  ; Isolate lower digit
.0285 84 0F           [2] 00303          anda   #$F
.0287 26 05 (028E)    [4] 00304          bne    FormatEmitLo
.                         00305
.0289 7D 0005         [6] 00306          tst    Int1+1    ; Leading zero?
.028C 27 0B (0299)    [4] 00307          beq    FormatSkipLo
.                         00308
.028E                     00309 FormatEmitLo:
.028E 8B 30           [2] 00310          adda   #'0'      ; Convert to ASCII numeral
.0290 DE 04           [4] 00311          ldx    Int1
.0292 08              [4] 00312          inx
.0293 DF 04           [5] 00313          stx    Int1
.0295 A7 17           [6] 00314          staa   Out+1,X
.0297 DE 02           [4] 00315          ldx    Int0      ; Index converted BCD
.                         00316
.0299                     00317 FormatSkipLo:
.0299 7C 0003         [6] 00318          inc    Int0+1    ; Address next pair of digits
.                         00319
.029C 7A 0012         [6] 00320          dec    Byt2      ; More digits?
.029F 26 C6 (0267)    [4] 00321          bne    Format4
.                         00322
.02A1 96 05           [3] 00323          ldaa   Int1+1    ; Store length of result
.02A3 97 17           [4] 00324          staa   Out+1
.02A5 26 08 (02AF)    [4] 00325          bne    FormatDone ; Check for all 0's
.                         00326
.02A7 86 01           [2] 00327          ldaa   #1        ; Default to "0"
.02A9 97 17           [4] 00328          staa   Out+1
.02AB 86 30           [2] 00329          ldaa   #'0'
.02AD 97 18           [4] 00330          staa   Out+1+1
.                         00331
.02AF                     00332 FormatDone
.02AF 39              [5] 00333          rts

And for the 8080:

.                         00148 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.                         00149 ;
.                         00150 ; Format - Convert a number to ASCII decimal
.                         00151 ;
.                         00152 ; Input:
.                         00153 ;       The first bytes of Dabble = the number to convert
.                         00154 ;       Int0 = number of bytes to convert
.                         00155 ;       Byt0 = number of bits to convert
.                         00156 ;       Byt1 = number of packed BCD bytes in result
.                         00157 ;
.                         00158 ; Output:
.                         00159 ;       Output string with length byte starting at Out+1
.                         00160 ;
.                         00161 ; Uses:
.                         00162 ;       Int1
.                         00163 ;
.                         00164 ; Note:
.                         00165 ;       Implemented with the Double Dabble algorithm:
.                         00166 ;
.                         00167 ;         https://en.wikipedia.org/wiki/Double_dabble
.                         00168 ;
.01F9                     00169 Format:
.01F9 3A 03B7        [13] 00170         lda     Byt1            ; Clear the BCD digits
.01FC 47              [5] 00171         mov     B,A
.01FD AF              [4] 00172         xra     A
.01FE 11 03BF        [10] 00173         lxi     D,Dabble
.0201 2A 03A8        [16] 00174         lhld    Int0
.0204 19             [10] 00175         dad     D
.                         00176
.0205                     00177 Format0:
.0205 77              [7] 00178         mov     M,A
.0206 23              [5] 00179         inx     H
.0207 05              [5] 00180         dcr     B
.0208 C2 0205        [10] 00181         jnz     Format0
.                         00182
.020B 3A 03B7        [13] 00183         lda     Byt1            ; Determine address of BCD digits
.020E 5F              [5] 00184         mov     E,A
.020F 16 00           [7] 00185         mvi     D,0
.0211 2A 03A8        [16] 00186         lhld    Int0            ; BCD digits are big endian order
.0214 19             [10] 00187         dad     D
.0215 11 03BE        [10] 00188         lxi     D,Dabble-1      ; Index := Num number bytes + Num BCD bytes - 1
.0218 19             [10] 00189         dad     D
.0219 EB              [4] 00190         xchg                    ; Keep it in DE
.                         00191
.021A                     00192 Format1:
.021A 3A 03B7        [13] 00193         lda     Byt1            ; Number of bytes in converted BCD
.021D 47              [5] 00194         mov     B,A
.                         00195
.021E 6B              [5] 00196         mov     L,E             ; Address lowest BCD byte
.021F 62              [5] 00197         mov     H,D
.                         00198
.0220                     00199 Format2:
.0220 7E              [7] 00200         mov     A,M             ; Isolate lower nybble
.0221 E6 0F           [7] 00201         ani     0Fh
.0223 FE 05           [7] 00202         cpi     4+1             ; If greater than 4, add 3
.0225 DA 022A        [10] 00203         jc      FormatLoNotGT4
.                         00204
.0228 C6 03           [7] 00205         adi     3
.                         00206
.022A                     00207 FormatLoNotGT4:
.022A 4F              [5] 00208         mov     C,A             ; Stash new lower nybble
.                         00209
.022B 7E              [7] 00210         mov     A,M             ; Isolate upper nybble
.022C E6 F0           [7] 00211         ani     0F0h
.022E FE 50           [7] 00212         cpi     40h+10h         ; If greater than 4, add 3
.0230 DA 0235        [10] 00213         jc      FormatHiNotGT4
.                         00214
.0233 C6 30           [7] 00215         adi     30h
.                         00216
.0235                     00217 FormatHiNotGT4:
.0235 B1              [4] 00218         ora     C               ; Combine nybbles
.0236 77              [7] 00219         mov     M,A
.                         00220
.0237 2B              [5] 00221         dcx     H
.0238 05              [5] 00222         dcr     B               ; More BCD digits to check?
.0239 C2 0220        [10] 00223         jnz     Format2
.                         00224
.023C 3A 03BF        [13] 00225         lda     Dabble          ; Shift the number left one bit
.023F 87              [4] 00226         add     A
.0240 32 03BF        [13] 00227         sta     Dabble
.                         00228
.0243 21 03C0        [10] 00229         lxi     H,Dabble+1
.0246 3A 03A8        [13] 00230         lda     Int0
.0249 47              [5] 00231         mov     B,A
.024A 05              [5] 00232         dcr     B
.024B CA 0256        [10] 00233         jz      Format3         ; No additional bytes
.                         00234
.024E                     00235 FormatShiftNumber:
.024E 7E              [7] 00236         mov     A,M             ; Shift the rest of the number
.024F 17              [4] 00237         ral
.0250 77              [7] 00238         mov     M,A
.0251 23              [5] 00239         inx     H
.0252 05              [5] 00240         dcr     B
.0253 C2 024E        [10] 00241         jnz     FormatShiftNumber
.                         00242
.0256                     00243 Format3:
.0256 3A 03B7        [13] 00244         lda     Byt1            ; Shift the BCD digits left one bit
.0259 47              [5] 00245         mov     B,A
.025A 6B              [5] 00246         mov     L,E
.025B 62              [5] 00247         mov     H,D
.                         00248
.025C                     00249 FormatShiftBCD:
.025C 7E              [7] 00250         mov     A,M
.025D 17              [4] 00251         ral
.025E 77              [7] 00252         mov     M,A
.025F 2B              [5] 00253         dcx     H
.0260 05              [5] 00254         dcr     B
.0261 C2 025C        [10] 00255         jnz     FormatShiftBCD
.                         00256
.0264 3A 03B6        [13] 00257         lda     Byt0            ; More bits to process?
.0267 3D              [5] 00258         dcr     A
.0268 32 03B6        [13] 00259         sta     Byt0
.026B C2 021A        [10] 00260         jnz     Format1
.                         00261
.026E 11 03BF        [10] 00262         lxi     D,Dabble
.0271 2A 03A8        [16] 00263         lhld    Int0
.0274 19             [10] 00264         dad     D
.0275 3A 03B7        [13] 00265         lda     Byt1
.0278 47              [5] 00266         mov     B,A
.                         00267
.0279 11 03BD        [10] 00268         lxi     D,Out+1         ; Address the output string
.                         00269
.027C 0E 01           [7] 00270         mvi     C,1
.                         00271
.027E                     00272 Format4:
.027E 7E              [7] 00273         mov     A,M             ; Isolate upper digit
.027F E6 F0           [7] 00274         ani     0F0h
.0281 C2 0288        [10] 00275         jnz     FormatEmitHi
.                         00276
.0284 0D              [5] 00277         dcr     C               ; Leading zero?
.0285 CA 0290        [10] 00278         jz      FormatSkipHi
.                         00279
.0288                     00280 FormatEmitHi:
.0288 0F              [4] 00281         rrc                     ; Shift into lower nybble
.0289 0F              [4] 00282         rrc
.028A 0F              [4] 00283         rrc
.028B 0F              [4] 00284         rrc
.                         00285
.028C C6 30           [7] 00286         adi     '0'             ; Convert to ASCII numeral
.028E 13              [5] 00287         inx     D
.028F 12              [7] 00288         stax    D
.                         00289
.0290                     00290 FormatSkipHi:
.0290 0C              [5] 00291         inr     C
.                         00292
.0291 7E              [7] 00293         mov     A,M             ; Isolate lower digit
.0292 E6 0F           [7] 00294         ani     0Fh
.0294 C2 029B        [10] 00295         jnz     FormatEmitLo
.                         00296
.0297 0D              [5] 00297         dcr     C               ; Leading zero?
.0298 CA 029F        [10] 00298         jz      FormatSkipLo
.                         00299
.029B                     00300 FormatEmitLo:
.029B C6 30           [7] 00301         adi     '0'             ; Convert to ASCII numeral
.029D 13              [5] 00302         inx     D
.029E 12              [7] 00303         stax    D
.                         00304
.029F                     00305 FormatSkipLo:
.029F 0C              [5] 00306         inr     C
.                         00307
.02A0 23              [5] 00308         inx     H               ; Address next pair of digits
.                         00309
.02A1 05              [5] 00310         dcr     B               ; More digits?
.02A2 C2 027E        [10] 00311         jnz     Format4
.                         00312
.02A5 79              [5] 00313         mov     A,C             ; Store length of result
.02A6 3D              [5] 00314         dcr     A
.02A7 C2 02B1        [10] 00315         jnz     FormatDone      ; Check for all 0's
.                         00316
.02AA 3E 30           [7] 00317         mvi     A,'0'           ; Default to "0"
.02AC 32 03BE        [13] 00318         sta     Out+1+1
.                         00319
.02AF 3E 01           [7] 00320         mvi     A,1
.                         00321
.02B1                     00322 FormatDone:
.02B1 32 03BD        [13] 00323         sta     Out+1
.                         00324
.02B4 C9             [10] 00325         ret
2 Likes

It took a bit of doing, but here is the Double Dabble implementation for the 9900…

.                         00112 ******************************************************************************
.                         00113 *
.                         00114 * Format - Convert a number to ASCII decimal
.                         00115 *
.                         00116 * Input:
.                         00117 *       The first bytes of Dabble = the number to convert
.                         00118 *       R1 = number of bits to convert
.                         00119 *       R2 = number of bytes to convert
.                         00120 *       R3 = number of packed BCD bytes in result
.                         00121 *
.                         00122 * Output:
.                         00123 *       Output string with length byte starting at Out+1
.                         00124 *
.                         00125 * Uses:
.                         00126 *       R4
.                         00127 *       R5
.                         00128 *       R6
.                         00129 *       R7
.                         00130 *
.                         00131 * Note:
.                         00132 *       Implemented with the Double Dabble algorithm:
.                         00133 *
.                         00134 *         https://en.wikipedia.org/wiki/Double_dabble
.                         00135 *
.0128                     00136 Format
.0128 064F                00137         dect    R15             ; Push return address
.012A C7CB                00138         mov     R11,*R15
.                         00139
.012C 04C0                00140         clr     R0              ; Clear the BCD digits
.012E C102                00141         mov     R2,R4           ; Point R4 to BCD digit
.0130 0224 03FC           00142         ai      R4,Dabble
.0134 C143                00143         mov     R3,R5
.                         00144
.0136                     00145 Format0
.0136 DD00                00146         movb    R0,*R4+
.0138 0605                00147         dec     R5
.013A 16FD (0136)         00148         jne     Format0
.                         00149
.013C C1C3                00150         mov     R3,R7           ; Index := Num number bytes + Num BCD bytes
.013E A1C2                00151         a       R2,R7           ; BCD digits are big endian order
.                         00152
.0140                     00153 Format1
.0140 C107                00154         mov     R7,R4           ; Index to BCD digits
.0142 C143                00155         mov     R3,R5           ; Number of bytes in converted BCD
.                         00156
.0144                     00157 Format2
.0144 D024 03FB           00158         movb    @Dabble-1(R4),R0
.0148 D180                00159         movb    R0,R6
.014A 0240 0F00           00160         andi    R0,>F00         ; Isolate lower nybble
.014E 0280 0400           00161         ci      R0,>400         ; If greater than 4, add 3
.0152 1202 (0158)         00162         jle     FormatLoNotGT4
.                         00163
.0154 0220 0300           00164         ai      R0,>300
.                         00165
.0158                     00166 FormatLoNotGT4
.0158 0246 F000           00167         andi    R6,>F000        ; Isolate upper nybble
.015C 0286 4000           00168         ci      R6,>4000        ; If greater than 4, add 3
.0160 1202 (0166)         00169         jle     FormatHiNotGT4
.                         00170
.0162 0226 3000           00171         ai      R6,>3000
.                         00172
.0166                     00173 FormatHiNotGT4
.0166 E006                00174         soc     R6,R0           ; Combine nybbles
.0168 D900 03FB           00175         movb    R0,@Dabble-1(R4)
.                         00176
.016C 0604                00177         dec     R4
.016E 0605                00178         dec     R5              ; More BCD digits to check?
.0170 16E9 (0144)         00179         jne     Format2
.                         00180
.0172 C102                00181         mov     R2,R4           ; Address the number to convert
.0174 04C6                00182         clr     R6              ; Start with no carry in
.                         00183
.0176                     00184 FormatShiftNumber
.0176 04C0                00185         clr     R0              ; Clear lower byte
.0178 D024 03FB           00186         movb    @Dabble-1(R4),R0
.017C 0A10                00187         sla     R0,1            ; Shift a byte of the number
.017E F006                00188         socb    R6,R0           ; Combine with carry
.0180 D900 03FB           00189         movb    R0,@Dabble-1(R4)
.0184 04C6                00190         clr     R6              ; Presume no carry
.0186 1702 (018C)         00191         jnc     FormatShiftNumberNoCarry
.                         00192
.0188 0206 0100           00193         li      R6,>100         ; Remember carry for next stage
.                         00194
.018C                     00195 FormatShiftNumberNoCarry
.018C 0604                00196         dec     R4
.018E 16F3 (0176)         00197         jne     FormatShiftNumber
.                         00198
.0190 C107                00199         mov     R7,R4           ; Shift the BCD digits left one bit
.0192 C143                00200         mov     R3,R5
.                         00201
.0194                     00202 FormatShiftBCD
.0194 04C0                00203         clr     R0              ; Clear lower byte
.0196 D024 03FB           00204         movb    @Dabble-1(R4),R0
.019A 0A10                00205         sla     R0,1
.019C F006                00206         socb    R6,R0           ; Combine with carry
.019E D900 03FB           00207         movb    R0,@Dabble-1(R4)
.01A2 04C6                00208         clr     R6              ; Presume no carry
.01A4 1702 (01AA)         00209         jnc     FormatShiftBCDNoCarry
.                         00210
.01A6 0206 0100           00211         li      R6,>100         ; Remember carry for next stage
.                         00212
.01AA                     00213 FormatShiftBCDNoCarry
.01AA 0604                00214         dec     R4
.01AC 0605                00215         dec     R5
.01AE 16F2 (0194)         00216         jne     FormatShiftBCD
.                         00217
.01B0 0601                00218         dec     R1              ; More bits to process?
.01B2 16C6 (0140)         00219         jne     Format1
.                         00220
.01B4 04C4                00221         clr     R4              ; Address the output string
.                         00222
.01B6                     00223 Format3
.01B6 D022 03FC           00224         movb    @Dabble(R2),R0
.01BA D180                00225         movb    R0,R6
.01BC 0240 F000           00226         andi    R0,>F000        ; Isolate upper digit
.01C0 1603 (01C8)         00227         jne     FormatEmitHi
.                         00228
.01C2 0284 0000           00229         ci      R4,0            ; Leading zero?
.01C6 1306 (01D4)         00230         jeq     FormatSkipHi
.                         00231
.01C8                     00232 FormatEmitHi
.01C8 0940                00233         srl     R0,4            ; Shift into lower nybble
.                         00234
.01CA 0220 3000           00235         ai      R0,'0'*256      ; Convert to ASCII numeral
.01CE 0584                00236         inc     R4
.01D0 D900 03F9           00237         movb    R0,@Out+1(R4)
.                         00238
.01D4                     00239 FormatSkipHi
.01D4 0246 0F00           00240         andi    R6,>F00         ; Isolate lower digit
.01D8 1603 (01E0)         00241         jne     FormatEmitLo
.                         00242
.01DA 0284 0000           00243         ci      R4,0            ; Leading zero?
.01DE 1305 (01EA)         00244         jeq     FormatSkipLo
.                         00245
.01E0                     00246 FormatEmitLo
.01E0 0226 3000           00247         ai      R6,'0'*256      ; Convert to ASCII numeral
.01E4 0584                00248         inc     R4
.01E6 D906 03F9           00249         movb    R6,@Out+1(R4)
.                         00250
.01EA                     00251 FormatSkipLo
.01EA 0582                00252         inc     R2              ; Address next pair of digits
.                         00253
.01EC 0603                00254         dec     R3              ; More digits?
.01EE 16E3 (01B6)         00255         jne     Format3
.                         00256
.01F0 06C4                00257         swpb    R4              ; Store length of result
.01F2 D804 03F9           00258         movb    R4,@Out+1
.                         00259
.01F6 1608 (0208)         00260         jne     FormatDone      ; Check for 0
.                         00261
.01F8 0200 0100           00262         li      R0,1*256        ; Default to "0"
.01FC D800 03F9           00263         movb    R0,@Out+1
.                         00264
.0200 0200 3000           00265         li      R0,'0'*256
.0204 D800 03FA           00266         movb    R0,@Out+1+1
.                         00267
.0208                     00268 FormatDone
.0208 C2FF                00269         mov     *R15+,R11       ; Pop return address
.020A 045B                00270         b       *R11

Reverse engineering of the TSC FLEX BASIC interpreters is not going well. I have real doubts about being able to build a compatible interpreter for the 6502 any time soon. Source code is available for the SWTPC interpreter, but it is not the same.

I was reminded of the Lucidata Pascal compiler. It generates an interpreted P-code instead of native machine code. The compiler is also provided in P-code form. What this means is that if a compatible interpreter can be implemented on the 6502, the compiler comes along for the ride. The interpreter is not very big, in fact, it is substantially smaller than BASIC. This is potentially going to be the next native language system to be available after the assembler.

Which brings up my toy Pascal compiler. I just did some work on the type system to allow single-byte variables, signed, unsigned and character. These are important for efficiency on an 8-bit system. The following is some code testing these features:

                          00080 ; 00001 program Test;
                          00081 ; 00002 
                          00082 ; 00003   var
                          00083 ; 00004     B                           : byte;
                          00084 ; 00005     B2                          : byte;
                          00085 ; 00006     Ch                          : char;
                          00086 ; 00007     Ch2                         : char;
                          00087 ; 00008     I                           : integer;
                          00088 ; 00009     S                           : shortint;
                          00089 ; 00010     S2                          : shortint;
                          00090 ; 00011     W                           : word;
                          00091 ; 00012 
                          00092 ; 00013   begin
                          00093 ; 00014     S := -1;
 0B09 A9 FF           [2] 00094          lda    #-1
 0B0B 8D 0F3C         [4] 00095          sta    S_
                          00096 ; 00015     I := S;
 0B0E AE 0F3C         [4] 00097          ldx    S_
 0B11 8A              [2] 00098          txa
 0B12 09 7F           [2] 00099          ora    #$7F
 0B14 30 02 (0B18)  [2/3] 00100          bmi    2f
 0B16 A9 00           [2] 00101          lda    #0
 0B18                     00102 2:
 0B18 8E 0F3A         [4] 00103          stx    I_
 0B1B 8D 0F3B         [4] 00104          sta    I_+1
                          00105 ; 00016     W := S;
 0B1E AE 0F3C         [4] 00106          ldx    S_
 0B21 8A              [2] 00107          txa
 0B22 09 7F           [2] 00108          ora    #$7F
 0B24 30 02 (0B28)  [2/3] 00109          bmi    2f
 0B26 A9 00           [2] 00110          lda    #0
 0B28                     00111 2:
 0B28 8E 0F3D         [4] 00112          stx    W_
 0B2B 8D 0F3E         [4] 00113          sta    W_+1
                          00114 ; 00017     writeln(I, ' ', W);
 0B2E AE 0F3A         [4] 00115          ldx    I_
 0B31 AD 0F3B         [4] 00116          lda    I_+1
 0B34 20 0D86         [6] 00117          jsr    WriteInteger
 0B37 A9 20           [2] 00118          lda    #32
 0B39 20 0DBF         [6] 00119          jsr    PUTCHR
 0B3C AE 0F3D         [4] 00120          ldx    W_
 0B3F AD 0F3E         [4] 00121          lda    W_+1
 0B42 20 0D8C         [6] 00122          jsr    WriteWord
 0B45 20 0DCC         [6] 00123          jsr    PCRLF
                          00124 ; 00018 
                          00125 ; 00019     B := S;
 0B48 AD 0F3C         [4] 00126          lda    S_
 0B4B 8D 0F39         [4] 00127          sta    B_
                          00128 ; 00020     I := B;
 0B4E AE 0F39         [4] 00129          ldx    B_
 0B51 A9 00           [2] 00130          lda    #0
 0B53 8E 0F3A         [4] 00131          stx    I_
 0B56 8D 0F3B         [4] 00132          sta    I_+1
                          00133 ; 00021     W := B;
 0B59 AE 0F39         [4] 00134          ldx    B_
 0B5C A9 00           [2] 00135          lda    #0
 0B5E 8E 0F3D         [4] 00136          stx    W_
 0B61 8D 0F3E         [4] 00137          sta    W_+1
                          00138 ; 00022     writeln(I, ' ', W);
 0B64 AE 0F3A         [4] 00139          ldx    I_
 0B67 AD 0F3B         [4] 00140          lda    I_+1
 0B6A 20 0D86         [6] 00141          jsr    WriteInteger
 0B6D A9 20           [2] 00142          lda    #32
 0B6F 20 0DBF         [6] 00143          jsr    PUTCHR
 0B72 AE 0F3D         [4] 00144          ldx    W_
 0B75 AD 0F3E         [4] 00145          lda    W_+1
 0B78 20 0D8C         [6] 00146          jsr    WriteWord
 0B7B 20 0DCC         [6] 00147          jsr    PCRLF
                          00148 ; 00023 
                          00149 ; 00024     S := 20;
 0B7E A9 14           [2] 00150          lda    #20
 0B80 8D 0F3C         [4] 00151          sta    S_
                          00152 ; 00025     S2 := S;
 0B83 AD 0F3C         [4] 00153          lda    S_
 0B86 8D 0F40         [4] 00154          sta    S2_
                          00155 ; 00026     S2 := S2 + S;
 0B89 18              [2] 00156          clc
 0B8A AD 0F40         [4] 00157          lda    S2_
 0B8D 6D 0F3C         [4] 00158          adc    S_
 0B90 8D 0F40         [4] 00159          sta    S2_
                          00160 ; 00027     writeln(S, ' ', S2);
 0B93 AD 0F3C         [4] 00161          lda    S_
 0B96 20 0D30         [6] 00162          jsr    WriteShortint
 0B99 A9 20           [2] 00163          lda    #32
 0B9B 20 0DBF         [6] 00164          jsr    PUTCHR
 0B9E AD 0F40         [4] 00165          lda    S2_
 0BA1 20 0D30         [6] 00166          jsr    WriteShortint
 0BA4 20 0DCC         [6] 00167          jsr    PCRLF
                          00168 ; 00028 
                          00169 ; 00029     B := 20;
 0BA7 A9 14           [2] 00170          lda    #20
 0BA9 8D 0F39         [4] 00171          sta    B_
                          00172 ; 00030     B2 := B;
 0BAC AD 0F39         [4] 00173          lda    B_
 0BAF 8D 0F38         [4] 00174          sta    B2_
                          00175 ; 00031     B2 := B2 + B;
 0BB2 18              [2] 00176          clc
 0BB3 AD 0F38         [4] 00177          lda    B2_
 0BB6 6D 0F39         [4] 00178          adc    B_
 0BB9 8D 0F38         [4] 00179          sta    B2_
                          00180 ; 00032     writeln(B, ' ', B2);
 0BBC AD 0F39         [4] 00181          lda    B_
 0BBF 20 0D36         [6] 00182          jsr    WriteByte
 0BC2 A9 20           [2] 00183          lda    #32
 0BC4 20 0DBF         [6] 00184          jsr    PUTCHR
 0BC7 AD 0F38         [4] 00185          lda    B2_
 0BCA 20 0D36         [6] 00186          jsr    WriteByte
 0BCD 20 0DCC         [6] 00187          jsr    PCRLF
                          00188 ; 00033 
                          00189 ; 00034     I := 32;
 0BD0 A2 20           [2] 00190          ldx    #32
 0BD2 A9 00           [2] 00191          lda    #0
 0BD4 8E 0F3A         [4] 00192          stx    I_
 0BD7 8D 0F3B         [4] 00193          sta    I_+1
                          00194 ; 00035     writeln(chr(I + I));
 0BDA 18              [2] 00195          clc
 0BDB AD 0F3A         [4] 00196          lda    I_
 0BDE 6D 0F3A         [4] 00197          adc    I_
 0BE1 20 0DBF         [6] 00198          jsr    PUTCHR
 0BE4 20 0DCC         [6] 00199          jsr    PCRLF
                          00200 ; 00036 
                          00201 ; 00037     Ch := 'A';
 0BE7 A9 41           [2] 00202          lda    #65
 0BE9 8D 0F41         [4] 00203          sta    CH_
                          00204 ; 00038     Ch2 := Ch;
 0BEC AD 0F41         [4] 00205          lda    CH_
 0BEF 8D 0F3F         [4] 00206          sta    CH2_
                          00207 ; 00039     writeln(Ch, chr(ord(Ch)+1))
 0BF2 AD 0F41         [4] 00208          lda    CH_
 0BF5 20 0DBF         [6] 00209          jsr    PUTCHR
 0BF8 18              [2] 00210          clc
 0BF9 AD 0F41         [4] 00211          lda    CH_
 0BFC 69 01           [2] 00212          adc    #1
 0BFE 20 0DBF         [6] 00213          jsr    PUTCHR
 0C01 20 0DCC         [6] 00214          jsr    PCRLF
                          00215 ; 00040   end.
1 Like

Another set of tests, this time of adding variables.

Note that a planned future enhancement will place as many variables as possible in the zero page.

The lesson is clear: for best results, use unsigned variables whenever possible.

                          00080 ; 00001 program Test;
                          00081 ; 00002 
                          00082 ; 00003   var
                          00083 ; 00004     B                           : byte;
                          00084 ; 00005     B2                          : byte;
                          00085 ; 00006     Ch                          : char;
                          00086 ; 00007     Ch2                         : char;
                          00087 ; 00008     I                           : integer;
                          00088 ; 00009     S                           : shortint;
                          00089 ; 00010     S2                          : shortint;
                          00090 ; 00011     W                           : word;
                          00091 ; 00012 
                          00092 ; 00013   begin
                          00093 ; 00014     W := W + S;
 0B09 18              [2] 00094          clc
 0B0A AD 10F9         [4] 00095          lda    W_
 0B0D 6D 10F8         [4] 00096          adc    S_
 0B10 AA              [2] 00097          tax
 0B11 AD 10F8         [4] 00098          lda    S_
 0B14 09 7F           [2] 00099          ora    #$7F
 0B16 30 02 (0B1A)  [2/3] 00100          bmi    2f
 0B18 A9 00           [2] 00101          lda    #0
 0B1A                     00102 2:
 0B1A 6D 10FA         [4] 00103          adc    W_+1
 0B1D 8E 10F9         [4] 00104          stx    W_
 0B20 8D 10FA         [4] 00105          sta    W_+1
                          00106 ; 00015     W := S + W;
 0B23 18              [2] 00107          clc
 0B24 AD 10F8         [4] 00108          lda    S_
 0B27 6D 10F9         [4] 00109          adc    W_
 0B2A AA              [2] 00110          tax
 0B2B AD 10F8         [4] 00111          lda    S_
 0B2E 09 7F           [2] 00112          ora    #$7F
 0B30 30 02 (0B34)  [2/3] 00113          bmi    2f
 0B32 A9 00           [2] 00114          lda    #0
 0B34                     00115 2:
 0B34 6D 10FA         [4] 00116          adc    W_+1
 0B37 8E 10F9         [4] 00117          stx    W_
 0B3A 8D 10FA         [4] 00118          sta    W_+1
                          00119 ; 00016     W := W + B;
 0B3D 18              [2] 00120          clc
 0B3E AD 10F9         [4] 00121          lda    W_
 0B41 6D 10F5         [4] 00122          adc    B_
 0B44 AA              [2] 00123          tax
 0B45 A9 00           [2] 00124          lda    #0
 0B47 6D 10FA         [4] 00125          adc    W_+1
 0B4A 8E 10F9         [4] 00126          stx    W_
 0B4D 8D 10FA         [4] 00127          sta    W_+1
                          00128 ; 00017     W := B + W;
 0B50 18              [2] 00129          clc
 0B51 AD 10F5         [4] 00130          lda    B_
 0B54 6D 10F9         [4] 00131          adc    W_
 0B57 AA              [2] 00132          tax
 0B58 A9 00           [2] 00133          lda    #0
 0B5A 6D 10FA         [4] 00134          adc    W_+1
 0B5D 8E 10F9         [4] 00135          stx    W_
 0B60 8D 10FA         [4] 00136          sta    W_+1
                          00137 ; 00018     W := B + B;
 0B63 18              [2] 00138          clc
 0B64 AD 10F5         [4] 00139          lda    B_
 0B67 6D 10F5         [4] 00140          adc    B_
 0B6A AA              [2] 00141          tax
 0B6B A9 00           [2] 00142          lda    #0
 0B6D 69 00           [2] 00143          adc    #0
 0B6F 8E 10F9         [4] 00144          stx    W_
 0B72 8D 10FA         [4] 00145          sta    W_+1
                          00146 ; 00019     W := S + B;
 0B75 18              [2] 00147          clc
 0B76 AD 10F8         [4] 00148          lda    S_
 0B79 6D 10F5         [4] 00149          adc    B_
 0B7C AA              [2] 00150          tax
 0B7D AD 10F8         [4] 00151          lda    S_
 0B80 09 7F           [2] 00152          ora    #$7F
 0B82 30 02 (0B86)  [2/3] 00153          bmi    2f
 0B84 A9 00           [2] 00154          lda    #0
 0B86                     00155 2:
 0B86 69 00           [2] 00156          adc    #0
 0B88 8E 10F9         [4] 00157          stx    W_
 0B8B 8D 10FA         [4] 00158          sta    W_+1
                          00159 ; 00020     W := B + S;
 0B8E 18              [2] 00160          clc
 0B8F AD 10F5         [4] 00161          lda    B_
 0B92 6D 10F8         [4] 00162          adc    S_
 0B95 AA              [2] 00163          tax
 0B96 AD 10F8         [4] 00164          lda    S_
 0B99 09 7F           [2] 00165          ora    #$7F
 0B9B 30 02 (0B9F)  [2/3] 00166          bmi    2f
 0B9D A9 00           [2] 00167          lda    #0
 0B9F                     00168 2:
 0B9F 69 00           [2] 00169          adc    #0
 0BA1 8E 10F9         [4] 00170          stx    W_
 0BA4 8D 10FA         [4] 00171          sta    W_+1
                          00172 ; 00021     W := S + S;
 0BA7 18              [2] 00173          clc
 0BA8 AD 10F8         [4] 00174          lda    S_
 0BAB 6D 10F8         [4] 00175          adc    S_
 0BAE AA              [2] 00176          tax
 0BAF A0 00           [2] 00177          ldy    #0
 0BB1 2C 10F8         [4] 00178          bit    S_
 0BB4 10 01 (0BB7)  [2/3] 00179          bpl    2f
 0BB6 88              [2] 00180          dey
 0BB7                     00181 2:
 0BB7 2C 10F8         [4] 00182          bit    S_
 0BBA 10 01 (0BBD)  [2/3] 00183          bpl    2f
 0BBC 88              [2] 00184          dey
 0BBD                     00185 2:
 0BBD 98              [2] 00186          tya
 0BBE 69 00           [2] 00187          adc    #0
 0BC0 8E 10F9         [4] 00188          stx    W_
 0BC3 8D 10FA         [4] 00189          sta    W_+1

Double Dabble for the Motorola 68000:

.                                 00113 ******************************************************************************
.                                 00114 *
.                                 00115 * Format - Convert a number to ASCII decimal
.                                 00116 *
.                                 00117 * Input:
.                                 00118 *       The first bytes of Dabble = the number to convert
.                                 00119 *       D1 = number of bits to convert
.                                 00120 *       D2 = number of bytes to convert
.                                 00121 *       D3 = number of packed BCD bytes in result
.                                 00122 *
.                                 00123 * Output:
.                                 00124 *       Output string with length byte starting at Out+1
.                                 00125 *
.                                 00126 * Uses:
.                                 00127 *       D0
.                                 00128 *       D4
.                                 00129 *       D5
.                                 00130 *       A0
.                                 00131 *       A1
.                                 00132 *       A2
.                                 00133 *
.                                 00134 * Note:
.                                 00135 *       Implemented with the Double Dabble algorithm:
.                                 00136 *
.                                 00137 *         https://en.wikipedia.org/wiki/Double_dabble
.                                 00138 *
.00000530                         00139 Format:
.00000530 207C 0000076A           00140         movea.l #Dabble,A0      ; Clear the BCD digits
.00000536 D0C2                    00141         adda.w  D2,A0
.00000538 1A03                    00142         move.b  D3,D5
.                                 00143
.0000053A                         00144 Format0:
.0000053A 4218                    00145         clr.b   (A0)+
.0000053C 5305                    00146         subq.b  #1,D5
.0000053E 66FA  (0000053A)        00147         bne     Format0
.                                 00148
.00000540 207C 00000769           00149         movea.l #Dabble-1,A0    ; Determine index of number to convert
.00000546 D0C2                    00150         adda.w  D2,A0           ; This is the LSB
.                                 00151
.00000548 2248                    00152         move.l  A0,A1           ; Determine index of BCD digits
.0000054A D2C3                    00153         adda.w  D3,A1           ; BCD digits are big endian order
.                                 00154                                 ; Index := Num number bytes + Num BCD bytes - 1
.                                 00155
.0000054C                         00156 Format1:
.0000054C 2449                    00157         move.l  A1,A2           ; Index to BCD digits
.0000054E 1A03                    00158         move.b  D3,D5           ; Number of bytes in converted BCD
.                                 00159
.00000550                         00160 Format2:
.00000550 1012                    00161         move.b  (A2),D0         ; Isolate lower nybble
.00000552 0240 000F               00162         andi    #$F,D0
.00000556 0C00 0005               00163         cmpi.b  #4+1,D0         ; If greater than 4, add 3
.0000055A 6500 0004  (00000560)   00164         blo     FormatLoNotGT4
.                                 00165
.0000055E 5600                    00166         addq.b  #3,D0
.                                 00167
.00000560                         00168 FormatLoNotGT4:
.00000560 1812                    00169         move.b  (A2),D4         ; Isolate upper nybble
.00000562 0244 00F0               00170         andi    #$F0,D4
.00000566 0C04 0050               00171         cmpi.b  #$40+$10,D4     ; If greater than 4, add 3
.0000056A 6500 0006  (00000572)   00172         blo     FormatHiNotGT4
.                                 00173
.0000056E 0604 0030               00174         addi.b  #$30,D4
.                                 00175
.00000572                         00176 FormatHiNotGT4:
.00000572 8004                    00177         or.b    D4,D0           ; Combine nybbles
.00000574 1480                    00178         move.b  D0,(A2)
.00000576 538A                    00179         subq.l  #1,A2
.00000578 5305                    00180         subq.b  #1,D5           ; More BCD digits to check?
.0000057A 66D4  (00000550)        00181         bne     Format2
.                                 00182
.0000057C 1012                    00183         move.b  (A2),D0         ; Shift the number left one bit
.0000057E E300                    00184         asl.b   #1,D0
.00000580 E314                    00185         roxl.b  #1,D4           ; Save X flag
.00000582 1480                    00186         move.b  D0,(A2)
.00000584 1A02                    00187         move.b  D2,D5
.00000586 5305                    00188         subq.b  #1,D5
.00000588 6700 0010  (0000059A)   00189         beq     Format3         ; No additional bytes
.                                 00190
.0000058C                         00191 FormatShiftNumber:
.0000058C 1022                    00192         move.b  -(A2),D0        ; Shift the rest of the number
.0000058E E20C                    00193         lsr.b   #1,D4           ; Recover X flag
.00000590 E310                    00194         roxl.b  #1,D0
.00000592 E314                    00195         roxl.b  #1,D4           ; Save X flag
.00000594 1480                    00196         move.b  D0,(A2)
.00000596 5305                    00197         subq.b  #1,D5
.00000598 66F2  (0000058C)        00198         bne     FormatShiftNumber
.                                 00199
.0000059A                         00200 Format3:
.0000059A 2449                    00201         movea.l A1,A2           ; Shift the BCD digits left one bit
.0000059C 1A03                    00202         move.b  D3,D5
.                                 00203
.0000059E                         00204 FormatShiftBCD:
.0000059E 1012                    00205         move.b  (A2),D0
.000005A0 E20C                    00206         lsr.b   #1,D4           ; Recover X flag
.000005A2 E310                    00207         roxl.b  #1,D0
.000005A4 E314                    00208         roxl.b  #1,D4           ; Save X flag
.000005A6 1480                    00209         move.b  D0,(A2)
.000005A8 538A                    00210         subq.l  #1,A2
.000005AA 5305                    00211         subq.b  #1,D5
.000005AC 66F0  (0000059E)        00212         bne     FormatShiftBCD
.                                 00213
.000005AE 5301                    00214         subq.b  #1,D1           ; More bits to process?
.000005B0 669A  (0000054C)        00215         bne     Format1
.                                 00216
.000005B2 227C 00000767           00217         movea.l #Out+1,A1       ; Address the output string
.000005B8 5288                    00218         addq.l  #1,A0           ; Address MSB of BCD digits
.000005BA 4204                    00219         clr.b   D4              ; Clear output digit count
.                                 00220
.000005BC                         00221 Format4:
.000005BC 1010                    00222         move.b  (A0),D0         ; Isolate upper digit
.000005BE 0200 00F0               00223         andi.b  #$F0,D0
.000005C2 6600 0008  (000005CC)   00224         bne     FormatEmitHi
.                                 00225
.000005C6 4A04                    00226         tst.b   D4              ; Leading zero?
.000005C8 6700 000E  (000005D8)   00227         beq     FormatSkipHi
.                                 00228
.000005CC                         00229 FormatEmitHi:
.000005CC E808                    00230         lsr.b   #4,D0           ; Shift into lower nybble
.                                 00231
.000005CE 0600 0030               00232         addi.b  #'0',D0         ; Convert to ASCII numeral
.000005D2 5289                    00233         addq.l  #1,A1
.000005D4 1280                    00234         move.b  D0,(A1)
.000005D6 5204                    00235         addq.b  #1,D4
.                                 00236
.000005D8                         00237 FormatSkipHi:
.000005D8 1018                    00238         move.b  (A0)+,D0        ; Isolate lower digit
.000005DA 0200 000F               00239         andi.b  #$F,D0
.000005DE 6600 0008  (000005E8)   00240         bne     FormatEmitLo
.                                 00241
.000005E2 4A04                    00242         tst.b   D4              ; Leading zero?
.000005E4 6700 000C  (000005F2)   00243         beq     FormatSkipLo
.                                 00244
.000005E8                         00245 FormatEmitLo:
.000005E8 0600 0030               00246         addi.b  #'0',D0         ; Convert to ASCII numeral
.000005EC 5289                    00247         addq.l  #1,A1
.000005EE 1280                    00248         move.b  D0,(A1)
.000005F0 5204                    00249         addq.b  #1,D4
.                                 00250
.000005F2                         00251 FormatSkipLo:
.000005F2 5303                    00252         subq.b  #1,D3           ; More digits?
.000005F4 66C6  (000005BC)        00253         bne     Format4
.                                 00254
.000005F6 11C4 0767               00255         move.b  D4,Out+1        ; Store length of result
.000005FA 6600 0012  (0000060E)   00256         bne     FormatDone      ; Check for all 0's
.                                 00257
.000005FE 103C 0001               00258         move.b  #1,D0           ; Default to "0"
.00000602 11C0 0767               00259         move.b  D0,Out+1
.00000606 103C 0030               00260         move.b  #'0',D0
.0000060A 11C0 0768               00261         move.b  D0,Out+1+1
.                                 00262
.0000060E                         00263 FormatDone:
.0000060E 4E75                    00264         rts

And for the Atmel AVR:

.                         00296 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.                         00297 ;
.                         00298 ; Format - Convert a number to ASCII decimal
.                         00299 ;
.                         00300 ; Input:
.                         00301 ;       The first bytes of Dabble = the number to convert
.                         00302 ;       R16 = number of bytes to convert
.                         00303 ;       R17 = number of bits to convert
.                         00304 ;       R18 = number of packed BCD bytes in result
.                         00305 ;
.                         00306 ; Output:
.                         00307 ;       Output string with length byte starting at Out+1
.                         00308 ;
.                         00309 ; Uses:
.                         00310 ;       R0
.                         00311 ;       R19
.                         00312 ;       R20
.                         00313 ;       R21
.                         00314 ;       R22
.                         00315 ;       R23
.                         00316 ;       R28
.                         00317 ;       R29
.                         00318 ;       R30
.                         00319 ;       R31
.                         00320 ;
.                         00321 ; Note:
.                         00322 ;       Implemented with the Double Dabble algorithm:
.                         00323 ;
.                         00324 ;         https://en.wikipedia.org/wiki/Double_dabble
.                         00325 ;
.0000ED                   00326 Format:
.0000ED E0EB          [1] 00327         ldi     R30,low(Dabble) ; Clear the BCD digits
.0000EE E0F1          [1] 00328         ldi     R31,high(Dabble)
.0000EF 2400          [1] 00329         clr     R0
.0000F0 0FE1          [1] 00330         add     R30,R17
.0000F1 1DF0          [1] 00331         adc     R31,R0
.0000F2 2F72          [1] 00332         mov     R23,R18
.                         00333
.0000F3                   00334 Format0:
.0000F3 9201          [2] 00335         st      Z+,R0
.0000F4 957A          [1] 00336         dec     R23
.0000F5 F7E9=0000F3 [1/2] 00337         brne    Format0
.                         00338
.0000F6 2F62          [1] 00339         mov     R22,R18         ; Determine address of BCD digits + 1
.0000F7 0F61          [1] 00340         add     R22,R17         ; BCD digits are big endian order
.0000F8 E04B          [1] 00341         ldi     R20,low(Dabble) ; Index := Num number bytes + Num BCD bytes
.0000F9 E051          [1] 00342         ldi     R21,high(Dabble)
.0000FA 0F46          [1] 00343         add     R20,R22
.0000FB 1D50          [1] 00344         adc     R21,R0
.                         00345
.0000FC                   00346 Format1:
.0000FC 2F32          [1] 00347         mov     R19,R18         ; Number of bytes in converted BCD
.                         00348
.0000FD 2FE4          [1] 00349         mov     R30,R20         ; Address lowest BCD byte
.0000FE 2FF5          [1] 00350         mov     R31,R21
.                         00351
.0000FF                   00352 Format2:
.0000FF 9162          [3] 00353         ld      R22,-Z          ; Isolate lower nybble
.000100 2F76          [1] 00354         mov     R23,R22
.000101 706F          [1] 00355         andi    R22,$F
.000102 3065          [1] 00356         cpi     R22,4+1         ; If greater than 4, add 3
.000103 F008=000105 [1/2] 00357         brlo    FormatLoNotGT4
.                         00358
.000104 5F6D          [1] 00359         subi    R22,$100-3      ; Add 3
.                         00360
.000105                   00361 FormatLoNotGT4:
.000105 7F70          [1] 00362         andi    R23,$F0         ; Isolate upper nybble
.000106 3570          [1] 00363         cpi     R23,$40+$10     ; If greater than 4, add 3
.000107 F008=000109 [1/2] 00364         brlo    FormatHiNotGT4
.                         00365
.000108 5D70          [1] 00366         subi    R23,$100-$30    ; Add $30
.                         00367
.000109                   00368 FormatHiNotGT4:
.000109 2B67          [1] 00369         or      R22,R23         ; Combine nybbles
.00010A 8360          [1] 00370         st      Z,R22
.                         00371
.00010B 953A          [1] 00372         dec     R19             ; More BCD digits to check?
.00010C F791=0000FF [1/2] 00373         brne    Format2
.                         00374
.00010D 9160 010B     [2] 00375         lds     R22,Dabble      ; Shift the number left one bit
.00010F 0F66          [1] 00376         add     R22,R22
.000110 9360 010B     [2] 00377         sts     Dabble,R22
.                         00378
.000112 E0EC          [1] 00379         ldi     R30,low(Dabble+1)
.000113 E0F1          [1] 00380         ldi     R31,high(Dabble+1)
.000114 2F31          [1] 00381         mov     R19,R17
.000115 953A          [1] 00382         dec     R19
.000116 F029=00011C [1/2] 00383         breq    Format3         ; No additional bytes
.                         00384
.000117                   00385 FormatShiftNumber:
.000117 8160          [1] 00386         ld      R22,Z           ; Shift the rest of the number
.000118 1F66          [1] 00387         rol     R22
.000119 9361          [2] 00388         st      Z+,R22
.00011A 953A          [1] 00389         dec     R19
.00011B F7D9=000117 [1/2] 00390         brne    FormatShiftNumber
.                         00391
.00011C                   00392 Format3:
.00011C 2F32          [1] 00393         mov     R19,R18         ; Shift the BCD digits left one bit
.00011D 2FE4          [1] 00394         mov     R30,R20
.00011E 2FF5          [1] 00395         mov     R31,R21
.                         00396
.00011F                   00397 FormatShiftBCD:
.00011F 9162          [3] 00398         ld      R22,-Z
.000120 1F66          [1] 00399         rol     R22
.000121 8360          [1] 00400         st      Z,R22
.000122 953A          [1] 00401         dec     R19
.000123 F7D9=00011F [1/2] 00402         brne    FormatShiftBCD
.                         00403
.000124 950A          [1] 00404         dec     R16             ; More bits to process?
.000125 F6B1=0000FC [1/2] 00405         brne    Format1
.                         00406
.000126 E0EB          [1] 00407         ldi     R30,low(Dabble) ; Address the packed BCD digits
.000127 E0F1          [1] 00408         ldi     R31,high(Dabble)
.000128 0FE1          [1] 00409         add     R30,R17
.000129 1DF0          [1] 00410         adc     R31,R0
.                         00411
.00012A E0CA          [1] 00412         ldi     R28,low(Out+1+1)        ; Address the output buffer
.00012B E0D1          [1] 00413         ldi     R29,high(Out+1+1)
.                         00414
.00012C                   00415 Format4:
.00012C 9161          [2] 00416         ld      R22,Z+          ; Isolate upper digit
.00012D 2F76          [1] 00417         mov     R23,R22
.00012E 7F60          [1] 00418         andi    R22,$F0
.00012F F411=000132 [1/2] 00419         brne    FormatEmitHi
.                         00420
.000130 2300          [1] 00421         tst     R16             ; Leading zero?
.000131 F021=000136 [1/2] 00422         breq    FormatSkipHi
.                         00423
.000132                   00424 FormatEmitHi:
.000132 9562          [1] 00425         swap    R22             ; Shift into lower nybble
.                         00426
.000133 5D60          [1] 00427         subi    R22,$100-'0'    ; Convert to ASCII numeral
.000134 9369          [2] 00428         st      Y+,R22
.                         00429
.000135 9503          [1] 00430         inc     R16
.                         00431
.000136                   00432 FormatSkipHi:
.000136 707F          [1] 00433         andi    R23,$F          ; Isolate lower digit
.000137 F411=00013A [1/2] 00434         brne    FormatEmitLo
.                         00435
.000138 2300          [1] 00436         tst     R16             ; Leading zero?
.000139 F019=00013D [1/2] 00437         breq    FormatSkipLo
.                         00438
.00013A                   00439 FormatEmitLo:
.00013A 5D70          [1] 00440         subi    R23,$100-'0'    ; Convert to ASCII numeral
.00013B 9379          [2] 00441         st      Y+,R23
.                         00442
.00013C 9503          [1] 00443         inc     R16
.                         00444
.00013D                   00445 FormatSkipLo:
.00013D 952A          [1] 00446         dec     R18             ; More digits?
.00013E F769=00012C [1/2] 00447         brne    Format4
.                         00448
.00013F 2300          [1] 00449         tst     R16             ; Store length of result
.000140 F421=000145 [1/2] 00450         brne    FormatDone      ; Check for all 0's
.                         00451
.000141 E360          [1] 00452         ldi     R22,'0'         ; Default to "0"
.000142 9360 010A     [2] 00453         sts     Out+1+1,R22
.                         00454
.000144 9503          [1] 00455         inc     R16
.                         00456
.000145                   00457 FormatDone:
.000145 9300 0109     [2] 00458         sts     Out+1,R16
1 Like

Early last year, I reported on some of my experiments with code generation. It is now being put to use in my Pascal compiler for the 6502.

This is a sample statement showing a straightforward translation:

                          00039 ; W0 := W0 + 1;
                          00040
                          00041 ;   ;  0 := v W0 -> 1
                          00042 ;   ;  1 L r 2
                          00043
                          00044 ;      ;  2 L v W0 -> 3
                          00045 ;      ;  3 + c 1
                          00046
                          00047
                          00048 ;  2 L v W0 -> 3
                          00049 ;  3 + c 1
 0034 18              [2] 00050          clc
 0035 A5 0D           [3] 00051          lda    W0
 0037 69 01           [2] 00052          adc    #1
 0039 AA              [2] 00053          tax
 003A A5 0E           [3] 00054          lda    W0+1
 003C 69 00           [2] 00055          adc    #0
                          00056 ;  1 L r 2
                          00057 ;  0 := v W0 -> 1
 003E 86 0D           [3] 00058          stx    W0
 0040 85 0E           [3] 00059          sta    W0+1

and a much better result when optimization is enabled:

                          00031 ; W0 := W0 + 1;
                          00032
                          00033 ;   ;  0 := v W0 -> 1
                          00034 ;   ;  1 L r 2
                          00035
                          00036 ;      ;  2 L v W0 -> 3
                          00037 ;      ;  3 + c 1
                          00038
                          00039
                          00040 ;  2 L v W0 -> 3
                          00041 ;  3 + c 1
 0029 E6 0D           [5] 00042          inc    W0
 002B D0 02 (002F)  [2/3] 00043          bne    2f
 002D E6 0E           [5] 00044          inc    W0+1
 002F                     00045 2:
                          00046 ;  1 L r 2
                          00047 ;  0 := v W0 -> 1

Surprisingly, an addition of a constant less than 256 can also benefit from a similar transformation:

                          00039 ; W0 := W0 + 2;
                          00040
                          00041 ;   ;  0 := v W0 -> 1
                          00042 ;   ;  1 L r 2
                          00043
                          00044 ;      ;  2 L v W0 -> 3
                          00045 ;      ;  3 + c 2
                          00046
                          00047
                          00048 ;  2 L v W0 -> 3
                          00049 ;  3 + c 2
 0034 18              [2] 00050          clc
 0035 A5 0D           [3] 00051          lda    W0
 0037 69 02           [2] 00052          adc    #2
 0039 AA              [2] 00053          tax
 003A A5 0E           [3] 00054          lda    W0+1
 003C 69 00           [2] 00055          adc    #0
                          00056 ;  1 L r 2
                          00057 ;  0 := v W0 -> 1
 003E 86 0D           [3] 00058          stx    W0
 0040 85 0E           [3] 00059          sta    W0+1

and the better result:

                          00031 ; W0 := W0 + 2;
                          00032
                          00033 ;   ;  0 := v W0 -> 1
                          00034 ;   ;  1 L r 2
                          00035
                          00036 ;      ;  2 L v W0 -> 3
                          00037 ;      ;  3 + c 2
                          00038
                          00039
                          00040 ;  2 L v W0 -> 3
                          00041 ;  3 + c 2
 0029 18              [2] 00042          clc
 002A A5 0D           [3] 00043          lda    W0
 002C 69 02           [2] 00044          adc    #2
 002E 85 0D           [3] 00045          sta    W0
 0030 90 02 (0034)  [2/3] 00046          bcc    2f
 0032 E6 0E           [5] 00047          inc    W0+1
 0034                     00048 2:
                          00049 ;  1 L r 2
                          00050 ;  0 := v W0 -> 1

The strange comments are diagnostics showing the data structures used by the code generator.

From the Kill Two Birds with One Stone department, the load of a signed number which has to be done anyway, is also used for sign extension, saving two bytes and three cycles.

This code to add a signed byte to a two-byte number:

                          00031 ; W0 := S1 + W2;
                          00032
                          00033 ;   ;  0 := v W0 -> 1
                          00034 ;   ;  1 L r 2
                          00035
                          00036 ;      ;  2 L v S1 -> 3
                          00037 ;      ;  3 + v W2
                          00038
                          00039
                          00040 ;  2 L v S1 -> 3
                          00041 ;  3 + v W2
 0029 18              [2] 00042          clc
 002A A5 16           [3] 00043          lda    S1
 002C 65 11           [3] 00044          adc    W2
 002E AA              [2] 00045          tax
 002F A5 16           [3] 00046          lda    S1
 0031 09 7F           [2] 00047          ora    #$7F
 0033 30 02 (0037)  [2/3] 00048          bmi    2f
 0035 A9 00           [2] 00049          lda    #0
 0037                     00050 2:
 0037 65 12           [3] 00051          adc    W2+1
                          00052 ;  1 L r 2
                          00053 ;  0 := v W0 -> 1
 0039 86 0D           [3] 00054          stx    W0
 003B 85 0E           [3] 00055          sta    W0+1

becomes:

 003D 18              [2] 00057          clc
 003E A0 00           [2] 00058          ldy    #0
 0040 A5 16           [3] 00059          lda    S1        ; Kill two birds with one stone
 0042 10 01 (0045)  [2/3] 00060          bpl    2f
 0044 88              [2] 00061          dey
 0045                     00062 2:
 0045 65 11           [3] 00063          adc    W2
 0047 AA              [2] 00064          tax
 0048 98              [2] 00065          tya
 0049 65 12           [3] 00066          adc    W2+1
 004B 86 0D           [3] 00067          stx    W0
 004D 85 0E           [3] 00068          sta    W0+1

And this code to add two signed bytes:

                          00031 ; W0 := S1 + S2;
                          00032
                          00033 ;   ;  0 := v W0 -> 1
                          00034 ;   ;  1 L r 2
                          00035
                          00036 ;      ;  2 L v S1 -> 3
                          00037 ;      ;  3 + v S2
                          00038
                          00039
                          00040 ;  2 L v S1 -> 3
                          00041 ;  3 + v S2
 0029 18              [2] 00042          clc
 002A A5 16           [3] 00043          lda    S1
 002C 65 17           [3] 00044          adc    S2
 002E AA              [2] 00045          tax
 002F A0 00           [2] 00046          ldy    #0
 0031 24 16           [3] 00047          bit    S1
 0033 10 01 (0036)  [2/3] 00048          bpl    2f
 0035 88              [2] 00049          dey
 0036                     00050 2:
 0036 24 17           [3] 00051          bit    S2
 0038 10 01 (003B)  [2/3] 00052          bpl    2f
 003A 88              [2] 00053          dey
 003B                     00054 2:
 003B 98              [2] 00055          tya
 003C 69 00           [2] 00056          adc    #0
                          00057 ;  1 L r 2
                          00058 ;  0 := v W0 -> 1
 003E 86 0D           [3] 00059          stx    W0
 0040 85 0E           [3] 00060          sta    W0+1

becomes:

 0042 18              [2] 00063          clc
 0043 A0 00           [2] 00064          ldy    #0
 0045 A5 16           [3] 00065          lda    S1        ; Kill two birds with one stone
 0047 10 01 (004A)  [2/3] 00066          bpl    2f
 0049 88              [2] 00067          dey
 004A                     00068 2:
 004A 65 17           [3] 00069          adc    S2
 004C AA              [2] 00070          tax
 004D 24 17           [3] 00071          bit    S2
 004F 10 01 (0052)  [2/3] 00072          bpl    2f
 0051 88              [2] 00073          dey
 0052                     00074 2:
 0052 98              [2] 00075          tya
 0053 69 00           [2] 00076          adc    #0
 0055 86 0D           [3] 00077          stx    W0
 0057 85 0E           [3] 00078          sta    W0+1

Edit: the branch in the first example should be bpl, not bmi.

2 Likes

More code generation, this time adding to a value already in registers:

                          00031 ; B0 := B1 + B2 + 2;
                          00032
                          00033 ;   ;  0 := v B0 -> 1
                          00034 ;   ;  1 L r 2
                          00035
                          00036 ;      ;  2 L v B1 -> 3
                          00037 ;      ;  3 + v B2 -> 4
                          00038 ;      ;  4 + c 2
                          00039
                          00040
                          00041 ;  1 L r 2
                          00042 ;  2 L v B1 -> 3
                          00043 ;  3 + v B2 -> 4
 0029 18              [2] 00044          clc
 002A A5 1A           [3] 00045          lda    B1
 002C 65 1B           [3] 00046          adc    B2
                          00047 ;  4 + c 2
 002E 18              [2] 00048          clc
 002F 69 02           [2] 00049          adc    #2
                          00050 ;  0 := v B0 -> 1
 0031 85 19           [3] 00051          sta    B0

It knows when not to bother to add:

                          00031 ; B0 := B1 + B2 + 512;
                          00032
                          00033 ;   ;  0 := v B0 -> 1
                          00034 ;   ;  1 L r 2
                          00035
                          00036 ;      ;  2 L v B1 -> 3
                          00037 ;      ;  3 + v B2 -> 4
                          00038 ;      ;  4 + c 512
                          00039
                          00040
                          00041 ;  1 L r 2
                          00042 ;  2 L v B1 -> 3
                          00043 ;  3 + v B2 -> 4
 0029 18              [2] 00044          clc
 002A A5 1A           [3] 00045          lda    B1
 002C 65 1B           [3] 00046          adc    B2
                          00047 ;  4 + c 512
                          00048 ;  0 := v B0 -> 1
 002E 85 19           [3] 00049          sta    B0

Likewise for two-byte values:

                          00031 ; W0 := W1 + W2 + 513;
                          00032
                          00033 ;   ;  0 := v W0 -> 1
                          00034 ;   ;  1 L r 2
                          00035
                          00036 ;      ;  2 L v W1 -> 3
                          00037 ;      ;  3 + v W2 -> 4
                          00038 ;      ;  4 + c 513
                          00039
                          00040
                          00041 ;  1 L r 2
                          00042 ;  2 L v W1 -> 3
                          00043 ;  3 + v W2 -> 4
 0029 18              [2] 00044          clc
 002A A5 0F           [3] 00045          lda    W1
 002C 65 11           [3] 00046          adc    W2
 002E AA              [2] 00047          tax
 002F A5 10           [3] 00048          lda    W1+1
 0031 65 12           [3] 00049          adc    W2+1
                          00050 ;  4 + c 513
 0033 18              [2] 00051          clc
 0034 A8              [2] 00052          tay
 0035 8A              [2] 00053          txa
 0036 69 01           [2] 00054          adc    #1
 0038 AA              [2] 00055          tax
 0039 98              [2] 00056          tya
 003A 69 02           [2] 00057          adc    #2
                          00058 ;  0 := v W0 -> 1
 003C 86 0D           [3] 00059          stx    W0
 003E 85 0E           [3] 00060          sta    W0+1

This is a major win:

                          00031 ; W0 := W1 + W2 + 512;
                          00032
                          00033 ;   ;  0 := v W0 -> 1
                          00034 ;   ;  1 L r 2
                          00035
                          00036 ;      ;  2 L v W1 -> 3
                          00037 ;      ;  3 + v W2 -> 4
                          00038 ;      ;  4 + c 512
                          00039
                          00040
                          00041 ;  1 L r 2
                          00042 ;  2 L v W1 -> 3
                          00043 ;  3 + v W2 -> 4
 0029 18              [2] 00044          clc
 002A A5 0F           [3] 00045          lda    W1
 002C 65 11           [3] 00046          adc    W2
 002E AA              [2] 00047          tax
 002F A5 10           [3] 00048          lda    W1+1
 0031 65 12           [3] 00049          adc    W2+1
                          00050 ;  4 + c 512
 0033 18              [2] 00051          clc
 0034 69 02           [2] 00052          adc    #2
                          00053 ;  0 := v W0 -> 1
 0036 86 0D           [3] 00054          stx    W0
 0038 85 0E           [3] 00055          sta    W0+1

This is ugly, but it has to be done. Can it be improved?

                          00031 ; W0 := W1 + W2 + S0;
                          00032
                          00033 ;   ;  0 := v W0 -> 1
                          00034 ;   ;  1 L r 2
                          00035
                          00036 ;      ;  2 L v W1 -> 3
                          00037 ;      ;  3 + v W2 -> 4
                          00038 ;      ;  4 + v S0
                          00039
                          00040
                          00041 ;  1 L r 2
                          00042 ;  2 L v W1 -> 3
                          00043 ;  3 + v W2 -> 4
 0029 18              [2] 00044          clc
 002A A5 0F           [3] 00045          lda    W1
 002C 65 11           [3] 00046          adc    W2
 002E AA              [2] 00047          tax
 002F A5 10           [3] 00048          lda    W1+1
 0031 65 12           [3] 00049          adc    W2+1
                          00050 ;  4 + v S0
 0033 18              [2] 00051          clc
 0034 85 25           [3] 00052          sta    BTp00_
 0036 8A              [2] 00053          txa
 0037 65 15           [3] 00054          adc    S0
 0039 AA              [2] 00055          tax
 003A A5 15           [3] 00056          lda    S0
 003C 09 7F           [2] 00057          ora    #$7F
 003E 30 02 (0042)  [2/3] 00058          bmi    2f
 0040 A9 00           [2] 00059          lda    #0
 0042                     00060 2:
 0042 65 25           [3] 00061          adc    BTp00_
                          00062 ;  0 := v W0 -> 1
 0044 86 0D           [3] 00063          stx    W0
 0046 85 0E           [3] 00064          sta    W0+1
2 Likes