Close

Can't Get Away From BASIC

A project log for Model 100 Assembler

Development of an assembler for the Tandy Model 100 portable computer.

clintrclintr 09/23/2014 at 04:090 Comments

My next assembler is written and I'm working on testing it - hopefully I will be posting it here soon.  It will have 2 parts: one part (RUNASM.BA) written in BASIC, and another part (ASM.CO) written in assembly.  The reason is that for some tasks, it makes more sense to use BASIC, and there is no (easy/documented) way to call into the BASIC code from a machine language program, whereas there is a way to call machine language from BASIC.  RUNASM.BA will be listed below.

I need RUNASM.BA to deal with getting input from the user, displaying messages to the user, and handling the input file being assembled.  Most of the tasks will only be required once or twice - except #1 (line 100 - get the next line from the input file); and #9 (line 900 - used for testing/debugging).  If the assembler is too slow, I will suspect task #1 first.

The main reason that I want to use BASIC to handle the input file is that, as well as files in RAM, it can read from cassette, the serial port, and ** a diskette drive ** if you have one and have set BASIC up to use it.  (For example, there was available the "TANDY Portable Disk Drive" and a program called TS-DOS which could be hooked into BASIC - see the links on the left for more info.)   Note, though, that for now I'm only testing with RAM files.

Aside: I figure I'd better assert copyright on this stuff, SO

All code below copyright (C) 2014 Clinton Reddekop.

You can redistribute and/or modify this code

under the terms of the GNU General Public License as published by

the Free Software Foundation, either version 3 of the License, or

(at your option) any later version.

This code is distributed in the hope that it will be useful,

but WITHOUT ANY WARRANTY; without even the implied warranty of

MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

GNU General Public License (available at <http://www.gnu.org/licenses/gpl-3.0.html>) for details.

Now here's how this works.  RUNASM.BA loops, repeatedly:

- calling ASM.CO

- checking a byte ("inst") in RAM which corresponds to a task that ASM.CO wants ASM.BA to perform

- performing the requested task

Each call into ASM.CO sets the A register to 0 (or 1 if there has been an error - don't worry about that yet...) and the HL register pair to any result needed from the previous task.

Here is RUNASM.BA:

5 ON ERROR GOTO6000
10 LOADM"asm.co"
20 CLEAR:LN$="":HL!=0:CO!=57345
30 CALLCO!,0,HL!
40 HL!=0
50 SW!=PEEK(CO!+3)
60 ONSW!GOTO100,200,300,400,500,600,700,800,900
70 IFSW!<>0THENGOTO5000
80 CLEAR
90 GOTO7000
100 LINEINPUT#1,LN$
110 HL!=VARPTR(LN$)
120 GOTO30
200 OPENFN$FORINPUTAS1
210 GOTO30
300 CLOSE1
310 GOTO30
400 NM!=PEEK(CO!+8)+256*PEEK(CO!+9)
410 ?"error near input file line:";NM!
420 GOT030
500 INPUT"input file";FN$
510 GOTO30
600 L!=PEEK(CO!+4)+256*PEEK(CO!+5)
610 H!=PEEK(CO!+6)+256*PEEK(CO!+7)
620 IFHIMEM>L!THENGOTO660
630 IFL!>H!THENGOTO660
640 IFH!>=MAXRAMTHENGOTO660
650 GOTO30
660 HL!=1:?"bounds error"
670 GOTO30
700 L!=PEEK(CO!+4)+256*PEEK(CO!+5)
710 H!=PEEK(CO!+6)+256*PEEK(CO!+7)
720 ?"LO =";L!,"HI =";H!
730 GOTO30
800 ?"success"
810 GOTO30
900 STOP
910 GOTO30
5000 ?"error bad instruction"
5010 CALLCO!,1,0
5020 GOTO7000
6000 CALLCO!,1,0
6010 ERROR ERR
7000 ?"assembler exit"
7010 END

CO! is the location in ASM.CO which is called from BASIC

CO! + 3 is the address of the "inst" byte

CO! + 4 and CO! + 6 are the addresses of the (16-bit) lowest and highest addresses that the program being assembled will be written to

CO! + 8 is the (16-bit) line number of the line most recently read from the input file of the program being assembled

Here is the machine code that runs first when ASM.CO is called:

; BASIC calls always go through here ;
entry:
    ;if a is nonzero, restore to
    ; uncalled state
    ana a
    jnz restore
    ; save BASIC sp, use local stack ;
    shld hlin ; save hl param
    lxi h,0h
    dad sp
    shld spbasic
    lhld splocal
    sphl
    lhld hlin ; restore hl param
    ; tos has address to return to ;
    ; (on first entry this is main) ;
    ret


This switches from BASIC's stack to another one created for use in ASM.CO.  The word on the top of the local stack is always the address of the code we want to run next; we get there via the 'ret' instruction.

Here is an example showing how ASM.CO tells BASIC to do something:


;func to print "success"
;modifies all regs
;stack usage 2 words
reportsuccess:
    mvi a,8h ; instruction to
    sta inst ; print "success"
    lxi h,printedsuccess
    push h
    jmp backtobasic
printedsuccess:
    call printbounds
    ret

It stores the instruction it wants performed in 'inst', pushes the 'printedsuccess' address onto the top of the local stack, and jumps to backtobasic (below).  Having 'printedsuccess' on the top of the local stack means that when BASIC calls entry (above) again, it will return to 'printedsuccess'.

This is the backtobasic code:

backtobasic:
    ; save local sp, use BASIC stack ;
    lxi h,0h
    dad sp
    shld splocal
    lhld spbasic
    sphl
    ret

It switches back to the BASIC stack and returns to BASIC, which will then handle the instruction in 'inst'.

Discussions