-
Assembly Assembler v1.0
10/08/2014 at 03:40 • 0 commentsThe first assembly assembler is ready. Below I will paste the code and explain how to assemble it with the BASIC assembler. First some comments, though.
I have been using the following book as a reference:
"8080/8085 ASSEMBLY LANGUAGE PROGRAMMING MANUAL", 1977, 1978 Intel Corporation, 3065 Bowers Avenue, Santa Clara, California 95051
The assembler I'm posting here behaves like the one described in the book, except:
- no macros
- no EQU or SET directives (yet?)
- binary numbers are not understood; i.e. "101110B" is not valid
- multiple labels may start a line
- negative numbers are not understood (yet)
- the data list of a DB or DW directive may end with a comma; i.e. "db 0, 1, 'hello', 0dfH," (as a line) is not an error, and is equivalent to "db 0, 1, 'hello', 0dfH"
- no expression evaluation (yet)
- lines can be up to 255 characters long, I think, but don't do that
- labels can be up to 254 characters long
- some other stuff I don't remember right now?
I'm still using the "Virtual T" Model 100 emulator for development, and the instructions below are meant for that emulator, with 32kbyte of RAM. If you are using an actual Model 100, you will need to have 32kbyte of RAM, and some way of moving files into and out of its RAM file system.
The Code
Cut and paste the following into file "asmrun.ba":
1 'Copyright (C) 2014 Clinton Reddekop.
2 'You can redistribute and/or modify
3 'this program under the terms of the
4 'GNU General Public License as
5 ' published by the Free Software
6 'Foundation, either version 3 of the
7 'License, or (at your option) any
8 'later version.
9 'This program is distributed in the
10 'hope that it will be useful, but
11 'WITHOUT ANY WARRANTY; without even
12 'the implied warranty of
13 'MERCHANTABILITY or FITNESS FOR A
14 'PARTICULAR PURPOSE. See the GNU
15 'General Public License
16 '(available at
17 '<http://www.gnu.org/licenses/gpl-3.0.html>)
18 'for details.
19 '
20 ON ERROR GOTO6000
21 LOADM"ASM.CO"
22 CLEAR512:LN$="":HL!=0
25 CO!=58253+2:' ADX IN ASM.CO TO CALL
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 GOTO30
500 INPUT"input file";FN$
510 GOTO30
600 L!=PEEK(CO!+4)+256*PEEK(CO!+5)
605 H!=PEEK(CO!+6)+256*PEEK(CO!+7)
610 AL!=CO!-2
615 AH!=PEEK(CO!+10)+256*PEEK(CO!+11)
620 IFHIMEM>L!THENGOTO680
625 IFL!>H!THENGOTO680
630 IFH!>=MAXRAMTHENGOTO680
635 IFL!<AL!ANDH!>=AL!THENGOTO680
640 IFL!>=AL!ANDL!<AH!THENGOTO680
670 GOTO30
680 HL!=1:?"bounds error"
690 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 ENDCut and paste the following into file "asm.do":
; asmasm v1.0
;An assembler program for the TRS-80
;Model 100 computer
; Copyright (C) 2014 Clinton Reddekop.
; You can redistribute and/or modify
; this program 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 program 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.
;Reference:
; "8080/8085 ASSEMBLY LANGUAGE
; PROGRAMMING MANUAL",
; 1977, 1978 Intel Corporation
; 3065 Bowers Avenue, Santa Clara,
; California 95051
;NOTE for any function:
; I always allow and assume that the
; a register and flags may be modified,
; unless noted otherwise.
; Any registers listed as outpars
; are modified by the function.
; Otherwise registers are preserved,
; unless noted otherwise.
;NOTE below, "ASM" means this program,
; as machine language LOADM'ed to the
; correct location in RAM.
;This program has been designed to be
; run from a companion BASIC program.
;The BASIC program, in a loop, keeps
; calling calladdr in ASM. Every time
; ASM returns to the BASIC program,
; BASIC checks the byte at inst (below)
; and performs the job corresponding to
; the inst value.
;BASIC normally calls ASM with
; a=0. BASIC may call ASM
; with a=1 instead, to tell ASM to put
; itself back in its initial state.
org 0e38dh
ret ;prevent running w/o companion
;BASIC prog
db 0h ;this byte wasted
; address 0e002h - call to this address
; from the companion BASIC program
calladdr:
jmp entry
; GLOBALS
;instruction byte back to BASIC
;instructions are:
; 0: end
; 1: get next line from input file
; into BASIC variable LN$
; (on next call, in hl, BASIC passes
; back a ptr to LN$ in its var tbl)
; 2: open input file
; 3: close input file
; 4: print error msg w/ line number
; taken from linenum (below)
; 5: get input filename
; 6: check for bounds error
; 7: print bounds (loaddr and hiaddr)
; 8: print "success"
;
; (DO 5 before 2, 2 before 1 before 3)
;
; NOTE inst 6: the bounds in loaddr,
; hiaddr are checked to make sure the
; program we are assembling will fit
; in [HIMEM, MAXRAM), and also not
; overwrite ASM - if so, the
; next call into ASM will have
; hl=0, and if not, BASIC will print
; an error message and call this prog
; with hl=1.
;
;calladdr+3 (BASIC PEEKS THIS)
inst:
db 0h
;calladdr+4 (BASIC PEEKS THIS)
loaddr: dw 0h ;lowest address of prog
;calladdr+6 (BASIC PEEKS THIS)
hiaddr: dw 0h ;highest address of prog
;calladdr+8 (BASIC PEEKS THIS)
linenum: dw 0h ;line number in input
;file of curline
;calladdr+10 (BASIC PEEKS THIS)
dw asmend
hlin: dw 0h
spbasic: dw 0h ;for saving BASIC's sp
splocal: dw endstack ;initial sp
loccntr: dw 0h ;location counter
;;; for testing
; dw 0ffh
;svaf: dw 0fafh
;svbc: dw 0cbch
;svde: dw 0edeh
;svhl: dw 1414h
;from: dw 0h
;local stack
;;;stack: ds 30h
;;;endstack: dw main ;go here on 1st entry
stack:
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach
endstack: dw main ;go here on 1st entry
db 0fh,0fh,0fh,0fh,0fh,0fh,0fh,0fh
;workspace
curtoken: ds 100h ;current token
curline: ds 100h ; current line
; from input file
labelmap: ds 800h ;label map storage
endlabelmap:
clpos: dw 0h ;position in curline
mapused: dw 0h ;lbl map amount used
; BASIC INTERFACE CODE
; 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
; final return to BASIC when ending ;
exit:
xra a
sta inst ; end instruction
lxi h,main
shld endstack
lxi sp,endstack
; fall through... ;
; we return to BASIC through here ;
backtobasic:
; save local sp, use BASIC stack ;
lxi h,0h
dad sp
shld splocal
lhld spbasic
sphl
ret
; reinitialize stack when BASIC tells ;
; us an error occurred ;
restore:
lxi h,main
shld endstack
lxi h,endstack
shld splocal
xra a
sta inst
ret
; MORE GLOBALS
;strings
strsp: db 73h,70h,0h
strpsw: db 70h,73h,77h,0h
;when this is set, looking up a label
; that doesn't exist doesn't cause an
; error:
inel: db 1h
;FUNCTIONS
;main
;does a first pass of the input file to
; make sure that:
; - there are no syntax errors,
; - the file is formatted correctly,
; - the memory to be written is
; within [HIMEM,MAXRAM-1]
; and to determine values of all labels
;if no problems with first pass, does
; a second pass to write the program
; into memory.
;NOTE:
; You probably want to read this one
; last; it won't make sense unless you
; know what the other functions do.
;stack usage 0 words ** return address
; and regs are saved on BASIC stack,
; we are not counting words on it
main:
; *** first pass *** ;
; get input file name ;
call getinfilename
; open input file ;
call openinputfile
; init globals for first pass ;
lxi h,0ffffh
shld loaddr
lxi h,0h
shld hiaddr
shld loccntr
shld linenum
shld labelmap ;null marks end
inx h
shld mapused
; set up functions for first pass ;
lxi h,pokefirst
shld pokeaddr
mvi a,1h
sta inel
; run first pass ;
call singlepass
; close input file ;
call closeinputfile
; check hiaddr,loaddr ;
call checkbounds
; *** second pass *** ;
; open input file ;
call openinputfile
; init globals for second pass ;
lxi h,0h
shld loccntr
shld linenum
; set up funcs for second pass ;
lxi h,labeldef
mvi m,0c9h ; opcode for ret
lxi h,pokesecond
shld pokeaddr
xra a
sta inel
; run second pass ;
call singlepass
; close input file ;
call closeinputfile
; report success ;
call reportsuccess
jmp exit
;handle an error in currently-open
; input file - prints line number as
; part of error message
;modifies all regs
;stack usage 2 words
errorinfile:
mvi a,4h ; instruction to
sta inst ; print error w/line num
lxi h,reportederror
push h
jmp backtobasic
reportederror:
jmp exit
;func to get the input file name
;(BASIC keeps it for use later)
;modifies all regs
;stack usage 1 words
getinfilename:
mvi a,5h ; instruction to
sta inst ; get input file name
jmp backtobasic
;func to open the input file
;modifies all regs
;stack usage 1 words
openinputfile:
mvi a,2h ; instruction to
sta inst ; open input file
jmp backtobasic
;func to close the input file
;modifies all regs
;stack usage 1 words
closeinputfile:
mvi a,3h ; instruction to
sta inst ; open input file
jmp backtobasic
;func to print bounds (loaddr, hiaddr)
;modifies all regs
;stack usage 1 words
printbounds:
mvi a,7h ; instruction to
sta inst ; print bounds
jmp backtobasic
;;; FOR TESTING
;basicpause:
; shld svhl
; pop h
; push h
; shld from
; xchg
; shld svde
; push psw
; pop h
; shld svaf
; push b
; pop h
; shld svbc
; mvi a,9h
; sta inst
; lxi h,paused
; push h
; jmp backtobasic
;paused:
; lhld svbc
; push h
; pop b
; lhld svaf
; push h
; pop psw
; lhld svde
; xchg
; lhld svhl
; ret
;func to check bounds (loaddr, hiaddr)
; to make sure the code will fit in
; [HIMEM, MAXRAM)
; and print "bounds error" if not
;modifies all regs
;stack usage 2 words
checkbounds:
mvi a,6h ; instruction to
sta inst ; check bounds
lxi h,checkedbounds
push h
jmp backtobasic
checkedbounds:
;BASIC passes nonzero in hl if
; bounds error found
mov a,h
ora l
rz
call printbounds
jmp exit
;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
;func to get the next line of the input
; file and put it in curline, 0-term'd
;also sets clpos to point at the first
; char of the line
;if got a line, cy=1 on return;
;else some error and cy=0 on return.
;; CORRECTION might get a BASIC
;; error and never return
;stack usage 5 words
getnextline:
push b
push d
push h
; get line into BASIC var LN$ ;
mvi a,1h ; instruction to
sta inst ; get line from file
lxi h,gotline
push h
jmp backtobasic
gotline:
;now hl points to the variable
;entry in the variable table
; copy LN$ to curline ;
mov b,m ; length
inx h
mov e,m ; string start lsB
inx h
mov d,m ; string start msB
lxi h,curline
call memcopy
mvi m,0h ; 0-terminate
; set clpos to start of line ;
lxi h,curline
shld clpos
; update line number ;
lhld linenum
inx h
shld linenum
pop h
pop d
pop b
stc
ret
;func to copy a block of mem to another
; inpars: src block ptr in de,
; dst block ptr in hl, length in b
; modifies b, advances both hl and de
; past their respective blocks
memcopy:
xra a
cmp b
rz
memcopyloop:
ldax d
mov m,a
inx d
inx h
dcr b
jnz memcopyloop
ret
;func to compare 2 strings upto first
; 0-terminator or max 255 characters
;inpars: str1 in de, str2 in hl,
;result is in z, cy flags on return
;stack usage 2 words
strcmp:
push b
mvi b,0ffh
call strncmp
pop b
ret
;func to compare 2 strings upto first
; 0-terminator or a max length
;*** NOTE that msb of first byte of
; str1 is ignored in comparison - see
; comment in code
;inpars: str1 in de, str2 in hl,
; max length in b
;result is in z, cy flags on return
;stack usage 3 words
strncmp:
push b
push d
push h
inr b
; clear msb of first byte in str1 ;
; we use this to flag assembler ;
; directives in opcode table ;
ldax d ; a = *str1
ani 7fh
strncmploop:
; check for max length
dcr b
jz retstrncmp
; compare next char
cmp m ; none = a - *str2
jnz retstrncmp
inx d ; str1++
inx h ; str2++
; check for 0-terminator
ana a
ldax d ; a = *str1
jnz strncmploop
retstrncmp:
pop h
pop d
pop b
ret
;func to find length of 0-term'd string
;inpar: str in hl
;outpar: length in bc
;stack usage 2 words
strlen:
push h
lxi b,0h
xra a
strlenloop:
cmp m
jz retstrlen
inx h
inx b
jmp strlenloop
retstrlen:
pop h
ret
;func to search for given instruct'n in
; the opcode table - uses binary search
;the string must have been converted
; to lowercase
;inpar: str in hl - mnemonic to find
;outpars:
; if str found, cy clear, else cy set
; if str found, bc gets last 2 bytes
; of entry
; if str found and the entry in the
; opcode table is marked as an
; assembly directive, then z clear
; else z set
;stack usage 5 words
getopc:
push d
push h
;; if (strlen(str)>4) { return(fail); }
call strlen
mov a,b
ana a
jnz retgetopcfail
mvi a,4h
cmp c
jc retgetopcfail
;; bottom = 0; top = 0x55;
mvi b,0h ;idx of first table entry
mvi c,55h ;idx one past last entry
;; while ((top-bottom) != 1) {
getopcloop:
mov a,c
sub b
dcr a
mov a,b
jz checkbottom
; if str is in the table, index is
; in [b,c)
;;mid = (bottom + top) / 2;
;;switch(strncmp(&table[mid],str,4){
add c ; note cy is clear here
push b ; save bottom and top
rar ; now a = (b+c)/2
mov c,a ; save mid for later in c
ana a ; clear cy
ral ; now a = ((b+c)/2)*2
push h ; save str
mov l,a
mvi h,0h
mov d,h
mov e,l
dad d
dad d ; now hl has offset of entry
; to check = 6*((b+c)/2)
lxi d,opctblstart
dad d
xchg ; de is adx of entry to check
; = &table[mid]
pop h ; hl is str
mvi b,4h
call strncmp
mov a,c ; a = mid
pop b ; b = bottom, c = top
;; case 0: return (success); break;
;; case -1: bottom = mid; break;
;; case +1: top = mid; break;}
;; } // while
;; return (fail);
jz retgetopcpass
jc movebottomup
mov c,a
jmp getopcloop
movebottomup:
mov b,a
jmp getopcloop
checkbottom:
;if bottom still zero, haven't
; checked table[0] yet
;(bottom is still in a)
;; if (bottom == 0) {
;; ** can use strcmp b/c btm table **
;; ** entry has a null-term'd mnem. **
;; if (strcmp(&table[0],str)==0){
;; return (success);
;; }
;; }
ana a
jnz retgetopcfail
lxi d,opctblstart
call strcmp
jz retgetopcpass
retgetopcfail:
stc
jmp retgetopc
retgetopcpass:
; check if entry is an assembler
; directive
ldax d
ani 80h
mov h,a ; save directive flag
; put last 2 bytes of entry in bc
inx d
inx d
inx d
inx d
ldax d
mov b,a
inx d
ldax d
mov c,a
mov a,h ; ret directive flag in a
ana a ; cy=0, set/reset z
retgetopc:
pop h
pop d
ret
;func to skip past whitespace in string
;whitespace: chars in [1h,20h]
;inpar: str in hl
;outpar: ptr to first non-ws char in hl
;stack usage 2 words
skipws:
push b
mvi c,21h
skipwsloop:
mov a,m
ana a
jz retskipws
cmp c
jnc retskipws
inx h
jmp skipwsloop
retskipws:
pop b
ret
;func to convert a string to lowercase
; in-place
;inpar: str in hl
;stack usage 4 words
tolower:
push b
push d
push h
mvi c,20h ; 'a'-'A'
mvi d,41h ; 'A'
mvi e,5bh ; 'Z'+1
tolowerloop:
mov a,m
ana a
jz rettolower ; end of str
cmp d
jc contlowerloop ; a < 'A'
cmp e
jnc contlowerloop ; a > 'Z'
add c ; convert to lowercase
mov m,a
contlowerloop:
inx h
jmp tolowerloop
rettolower:
pop h
pop d
pop b
ret
;func to check if the char in a is
; alphanumeric
;iff so, cy=1 on return
;preserves all registers
;stack usage 1 words
isalphanum:
cpi 30h ; '0'
jc failalphanum
cpi 3ah ; '9'+1
rc ; a is ['0','9']
cpi 41h ; 'A'
jc failalphanum
cpi 5bh ; 'Z'+1
rc ; a is ['A','Z']
cpi 61h ; 'a'
jc failalphanum
cpi 7bh ; 'z'+1
rc ; a is ['a','z']
failalphanum:
ana a ; clear cy
ret
;func to get next token from curline
; (starting at clpos) and put it in
; curtoken, advancing clpos past the
; token in the line
;comments (starting with ';') are
; skipped
;the token put in curtoken is always
; 0-terminated
;valid tokens are:
; - "" (indicates end of line reached)
; - ","
; - "'"
; - an alphanumeric string
; - an alphanumeric string followed
; immediately by ':'
; if the token ends with ':' the ':'
; is not copied into curtoken
; on return, z=0 if the token ended
; with ':', else z=1
;stack usage 4 words
getnexttoken:
push b
push d
push h
mvi b,0h
lxi d,curtoken ; de points to token
lhld clpos ; hl points to line
call skipws
; get first char of token
mov a,m
ana a ; end of line?
jz retgetnexttoken
cpi 2ch ; ','
jz onechartoken
cpi 27h ; '\''
jz onechartoken
cpi 3bh ; ';'
jz retgetnexttoken ; ignore comment
; if we get here token must start
; with an alphanumeric char
call isalphanum
jnc errorinfile
getnexttokenloop:
stax d
inx d
inx h
mov a,m
call isalphanum
jc getnexttokenloop
; token may end with ':'
cpi 3ah ; ':'
jnz retgetnexttoken
mov b,a ; make b nonzero
inx h
jmp retgetnexttoken
onechartoken:
stax d
inx d
inx h
retgetnexttoken:
shld clpos
xra a
stax d ; 0-terminate the token
lxi h,curtoken
call tolower
mov a,b
ana a ; return z=0/1
pop h
pop d
pop b
ret
;same as above, but error if token
; is label
getnexttokennolabel:
call getnexttoken
jnz errorinfile
ret
;fnc to do a single pass of the input
; file
;loops reading and processing lines
; from the input file
;no inpars or return value
;stack usage 4 words
singlepass:
push b
push d
push h
jmp mainloopnoemptycheck
mainloop:
; make sure rest of line empty
call getnexttoken
lda curtoken
ana a
jnz errorinfile
mainloopnoemptycheck:
; handle next line
call getnextline
jnc errorinfile ;no end directive
mainloopuserestofline:
call getnexttoken
jz notlabel
; make sure label not a keyword ;
call getopc
jnc errorinfile
; store the label/value ;
lhld loccntr
call labeldef
jmp mainloopuserestofline
notlabel:
lxi h,curtoken
xra a
cmp m
jz mainloopnoemptycheck ;empty line
call getopc
jc errorinfile
jnz handledirective
; now b=opcode, c=fmt
; handle operands as given by fmt
mov a,c
ral
mov c,a
cc dorm543
mov a,c
ral
mov c,a
cc dorp
mov a,c
ral
mov c,a
cc dorbd
mov a,c
ral
mov c,a
cc dorw
mov a,c
ral
mov c,a
cc docomma
mov a,c
ral
mov c,a
cc dorm210
mov a,b
call poke ; write opcode to mem
mov a,c
ral
mov c,a
cc do1byte
mov a,c
ral
mov c,a
cc do2byte
jmp mainloop
handledirective:
; jump to cb
mov h,c
mov l,b
pchl
dorst:
call getnexttokennolabel
call getconstant
mov a,h
ana a
jnz errorinfile ; too big
mov a,l
cpi 8h
jnc errorinfile ; too big
ana a ; clear cy
ral ; shift code up
ral
ral
adi 0c7h ; add opcode into code
call poke ; write opcode to mem
jmp mainloop
dodb:
; get next byte(s), write to mem ;
call getnexttokennolabel
lda curtoken
; allow line to end before any ;
; data, or immediately after ',' ;
ana a
jz mainloop
; not line end ;
cpi 27h ; '\''
jz strconstant
call getconstant
mov a,h
ana a
jnz errorinfile ; const too big
mov a,l
call poke
jmp endstrconstant
strconstant:
mvi b,27h ; '\''
lhld clpos
strconstantloop:
mov a,m
inx h
cmp b
jz endstrconstantloop
call poke
jmp strconstantloop
endstrconstantloop:
shld clpos
endstrconstant:
; get comma or line end ;
call getnexttoken
lda curtoken
cpi 2ch ; ','
jz dodb
ana a
jz mainloop
jmp errorinfile
dodw:
; get next word, write to mem ;
call getnexttokennolabel
; allow line to end before any ;
; data, or immediately after ',' ;
lda curtoken
ana a
jz mainloop
; must be a constant ;
call getconstant
mov a,l
call poke
mov a,h
call poke
; get comma or line end ;
call getnexttoken
lda curtoken
cpi 2ch ; ','
jz dodw
ana a
jz mainloop
jmp errorinfile
dods:
call getnexttokennolabel
lda inel ;we need to know the val
mov b,a ; to set loccntr to in
xra a ; BOTH passes, so set
sta inel ; inel to 0 here
call getconstant
mov a,b
sta inel ;restore inel
xchg
; poke 0 de times ;
dsloop:
mov a,d
ora e
jz mainloop
xra a
call poke
dcx d
jmp dsloop
doorg:
call getnexttokennolabel
lda inel ;we need to know the val
mov b,a ; to set loccntr to in
xra a ; BOTH passes, so set
sta inel ; inel to 0 here
call getconstant
mov a,b
sta inel ;restore inel
shld loccntr
jmp mainloop
doend:
;TODO def of start address?
;fall through;
retsinglepass:
pop h
pop d
pop b
ret
;func to read the next token from
; curline, expecting the name of an
; 8-bit reg or "m".
;puts the corresponding 3-bit
;code into opcode[2,1,0]
;inpars: opcode in b
;outpars: modified opcode in b
;stack usage 2 words
dorm210:
push d
call helprm
mov a,d
add b
mov b,a
pop d
ret
;func to read the next token from
; curline, expecting the name of an
; 8-bit reg or "m".
;outpar: the corresponding 3-bit code
;in d
;stack usage 2 words
helprm:
push h
call getnexttoken
lxi h,curtoken
mov a,m
cpi 61h ; 'a'
mvi d,7h
jz rethelprm
cpi 62h ; 'b'
mvi d,0h
jz rethelprm
cpi 63h ; 'c'
mvi d,1h
jz rethelprm
cpi 64h ; 'd'
mvi d,2h
jz rethelprm
cpi 65h ; 'e'
mvi d,3h
jz rethelprm
cpi 68h ; 'h'
mvi d,4h
jz rethelprm
cpi 6ch ; 'l'
mvi d,5h
jz rethelprm
cpi 6dh ; 'm'
mvi d,6h
jnz errorinfile
rethelprm:
;check that token length is 1
inx h
xra a
cmp m
jnz errorinfile
pop h
ret
;func to read the next token from
; curline, expecting name of an rp:
; "b", "d", "h" or "sp".
;puts the corresponding 2-bit
;code into opcode[5,4]
;inpars: opcode in b
;outpars: modified opcode in b
;stack usage 2 words
dorp:
push d
call helprbdh
mov a,e ; don't allow "psw"
cpi 70h
jz errorinfile
mov a,d
add b
mov b,a
pop d
ret
;func to read the next token from
; curline, expecting "b", "d", "h",
; "sp" or "psw".
;outpars:
; puts the corresponding code for bits
; [5,4] into d, and the first char of
; the token into e
;stack usage 2 words
helprbdh:
push h
call getnexttoken
lxi h,curtoken
mov a,m
mov e,a
cpi 62h ; 'b'
mvi d,0h
jz rethelprbdh
cpi 64h ; 'd'
mvi d,10h
jz rethelprbdh
cpi 68h ; 'h'
mvi d,20h
jz rethelprbdh
lxi d,strsp
call strcmp
mvi d,30h
mvi e,73h
jz rethelprbdhnolenchk
lxi d,strpsw
call strcmp
mvi d,30h
mvi e,70h
jz rethelprbdhnolenchk
jmp errorinfile
rethelprbdh:
;check that token length is 1
inx h
xra a
cmp m
jnz errorinfile
rethelprbdhnolenchk:
pop h
ret
;func to read the next token from
; curline, expecting name of an rbd:
; "b" or "d".
;puts the corresponding 1-bit
;code into opcode[4]
;inpars: opcode in b
;outpars: modified opcode in b
;stack usage 2 words
dorbd:
push d
call helprbdh
mov a,d
cpi 11h ; only allow "b" or "d"
jnc errorinfile
add b
mov b,a
pop d
ret
;func to read the next token from
; curline, expecting name of an rw:
; "b", "d", "h" or "psw".
;puts the corresponding 2-bit
;code into opcode[5,4]
;inpars: opcode in b
;outpars: modified opcode in b
;stack usage 2 words
dorw:
push d
call helprbdh
mov a,e ; don't allow "sp"
cpi 73h
jz errorinfile
mov a,d
add b
mov b,a
pop d
ret
;func to read the next token from
; curline, expecting ","
;stack usage 1 words
docomma:
;note no need to check length -
; getnexttoken never returns any
; token starting w/',' other than
; ","
call getnexttoken
lda curtoken
cpi 2ch
jnz errorinfile
ret
;func to read the next token from
; curline, expecting the name of an
; 8-bit reg or "m".
;puts the corresponding 3-bit
;code into opcode[5,4,3]
;inpars: opcode in b
;outpars: modified opcode in b
;stack usage 2 words
dorm543:
push d
call helprm
mov a,d
ana a ; clear cy
ral
ral
ral
add b
mov b,a
pop d
ret
;func to read the next token from
; curline, expecting a constant (either
; literal or named) in range [0h,ffh]
; which it writes in a single byte to
; mem at the current location counter,
; advancing the location counter past
; the write
;modifies h, l
;stack usage 1 words
do1byte:
call getnexttokennolabel
call getconstant
mov a,h
ana a
jnz errorinfile ; > ffh
mov a,l
call poke
ret
;func to read the next token from
; curline, expecting a constant (either
; literal or named) in range [0h,ffffh]
; which it writes in a 2-byte word to
; mem at the current location counter,
; advancing the location counter past
; the write
;modifies h, l
;stack usage 1 words
do2byte:
call getnexttokennolabel
call getconstant
mov a,l
call poke
mov a,h
call poke
ret
;func to write the byte in a to mem at
; the current location counter, and
; advance the location counter past the
; write
;stack usage 3 words
poke:
db 0c3h ; opcode for jmp
;addr of pokefirst or pokesecond
; will be put here, depending on
; which pass:
pokeaddr:
dw 0h
pokefirst: ;does this in first pass
;keep track of lowest and highest
; addresses of program, but don't
; write memory yet
push h
push d
lhld loaddr
xchg
lhld loccntr
mov a,l
sub e
mov a,h
sbb d
jnc checkhiaddr
shld loaddr
checkhiaddr:
xchg ;loccntr in de
lhld hiaddr
mov a,l
sub e
mov a,h
sbb d
xchg ;loccntr in hl
jnc retpokefirst
shld hiaddr
retpokefirst:
;advance loccntr
inx h
shld loccntr
pop d
pop h
ret
pokesecond: ;does this in second pass
;do the byte write
push h
lhld loccntr
mov m,a
inx h
shld loccntr
pop h
ret
;func to read a constant (literal or
; named) from curtoken
;no inpars
;outpar: the number in hl
;stack usage 3 words
getconstant:
push b
push d
lda curtoken
ana a
jz errorinfile
checkforcharliteral:
cpi 27h ; '\''
jnz checkfordigit
; token was '\'' - get rest of
; char lit. from clpos in curline
lhld clpos
mov a,m
ana a
jz errorinfile
cpi 27h ; '\''
jz errorinfile
mov b,a ; save value in b
; check for closing '\''
inx h
mov a,m
cpi 27h ; '\''
jnz errorinfile
inx h
shld clpos
; put value in hl
mvi h,0h
mov l,b
jmp retgetconstant
checkfordigit:
cpi 30h ; '0'
jc errorinfile
cpi 3ah ; '9'+1
jnc checkforlabel
call getnumfromtoken
jmp retgetconstant
checkforlabel:
cpi 61h ; 'a'
jc errorinfile
cpi 7bh ; 'z'+1
jnc errorinfile
call labellookup
retgetconstant:
pop d
pop b
ret
;func to read a literal number from
; curtoken
;curtoken MUST start w/ decimal digit -
; this is not checked
;number must fit in 16 bits or you'll
; get (number mod 65536)
;no inpars
;outpar: the number in hl
;stack usage 4 words
getnumfromtoken:
push b
push d
lxi h,curtoken
call strlen
dcx b
dad b ;now hl points to last char
mov a,m
mvi m,0h
; hex numbers end in 'h' ;
cpi 68h ; 'h'
jz gethexnum
; decimal numbers may end in 'd' ;
cpi 64h ; 'd'
jz getdecnum
; no end letter is also decimal ;
mov m,a
getdecnum:
lxi h,curtoken
lxi b,0h ;bc will accumulate number
decnumloop:
; get value of next digit ;
mov a,m
inx h
ana a
jz retgetnumfromtoken
cpi 3ah ; '9'+1
jnc errorinfile
cpi 30h ; '0'
jc errorinfile
sbi 30h ; now next digit val is in a
; multiply bc by 10 and add a ;
push h
mov h,b
mov l,c ;hl = bc
dad h ;hl = 2bc
dad h ;hl = 4bc
dad h ;hl = 8bc
dad b ;hl = 9bc
dad b ;hl = 10bc
add l ;a += l
mov c,a
mov b,h
pop h
jnc decnumloop
inr b
jmp decnumloop
gethexnum:
lxi h,curtoken
lxi b,0h ;bc will accumulate number
hexnumloop:
; get value of next digit ;
mov a,m
inx h
ana a
jz retgetnumfromtoken
cpi 3ah ; '9'+1
jnc chkatof
cpi 30h ; '0'
jc errorinfile
sbi 30h ; now next digit val is in a
jmp hexnummult
chkatof:
cpi 67h ; 'f'+1
jnc errorinfile
cpi 61h ; 'a'
jc errorinfile
sbi 57h ; now next digit val is in a
hexnummult:
; multiply bc by 16 and add a ;
push h
mov h,b
mov l,c ;hl = bc
dad h ;hl = 2bc
dad h ;hl = 4bc
dad h ;hl = 8bc
dad h ;hl = 16bc
add l ;a += l
mov c,a
mov b,h
pop h
jnc hexnumloop
inr b
jmp hexnumloop
retgetnumfromtoken:
mov h,b
mov l,c
pop d
pop b
ret
;next function might be too SLOW
; consider better implementation later
; if necessary
;label map entry is just label string
; (0-terminated)followed immediately
; by 2-byte value of label
;the label map is a running list of
; these entries i.e.
; [key1][val1][key2][val2]...
; in memory, not sorted in any way
;end of list is indicated by empty str
;helper for labellookup and labeldef
;finds curtoken in labelmap if there
;on return:
; if label found, cy=1 and hl points
; to corresponding value
; else cy=0 and hl points to null that
; marks end of labelmap
;modifies d,e,h,l
;stack usage 1 words
labelfind:
lxi h,labelmap
labelsearch:
xra a
cmp m
rz ;with cy=0
lxi d,curtoken
labelcmp:
ldax d
cmp m
jnz nextlabel
inx d
inx h
ana a
jnz labelcmp
; label matches key
stc
ret
nextlabel:
mov a,m
ana a
inx h
jnz nextlabel
inx h
inx h
jmp labelsearch
;func which:
; (inel==0): gets the value associated
; with the key in curtoken, reporting
; error if not found
; (inel==1): gets 0
;outpar: the value in hl
;stack usage 2 words
labellookup:
push d
lda inel
ana a
jz gofindlabel
lxi h,0h
jmp retlabellookup
gofindlabel:
call labelfind
jnc errorinfile
mov e,m
inx h
mov h,m
mov l,e
retlabellookup:
pop d
ret
;func to associate a value with a key
; in the constmap
;if key already there, it is an error
;inpars: value in hl, key in curtoken
;modifies h, l
;stack usage 4 words
labeldef:
push d
push h ;save value
; check if curtoken already there ;
call labelfind
jc errorinfile
push h ;save ptr to labelmap end
; check if there is room for new ;
; label in labelmap ;
lxi h,curtoken
call strlen
lhld mapused
dad b
inx h
inx h
inx h
;hl now contains space needed
;compare to max labelmap size
ana a ;clear cy
;;mov a,l
;;sbi 0h
mov a,h
sbi 8h
jnc errorinfile
shld mapused ;update used space
; put the new label in the map ;
pop h ;ptr to labelmap end
lxi d,curtoken
writelabelloop:
ldax d
mov m,a
inx h
inx d
ana a
jnz writelabelloop
pop d ;value
mov m,e
inx h
mov m,d
inx h
xra a
mov m,a ;null marks labelmap end
pop d
ret
;table to look up opcodes by mnemonic
; and assembly directives
;** EXCEPTION rst is treated as a
; directive
;entries are 6 bytes each
;table is in alph. order of mnem./dir'v
;entry format - opcode:
; 4-byte mnemonic, rt-pad w/ 0
; 1-byte opcode
; 1 format byte:
; bit:meaning if set
; 7:opcode[2,1,0] gets an rm operand
; 6:opcode[5,4] gets an rp operand
; 5:opcode[4] gets an rbd operand
; 4:opcode[5,4] gets an rw operand
; 3:instruction has 2 operands
; 2:opcode[5,4,3] gets an rm operand
; 1:instruction has 2 bytes
; 0:instruction has 3 bytes
;entry format - directive:
; 4-byte directive, rt-pad w/ 0
; 2-byte address of part of getopc that
; handles this directive
opctblstart:
db 61h,63h,69h,00h,0ceh,02h
db 61h,64h,63h,00h,088h,04h
db 61h,64h,64h,00h,080h,04h
db 61h,64h,69h,00h,0c6h,02h
db 61h,6eh,61h,00h,0a0h,04h
db 61h,6eh,69h,00h,0e6h,02h
db 63h,61h,6ch,6ch,0cdh,01h
db 63h,63h,00h,00h,0dch,01h
db 63h,6dh,00h,00h,0fch,01h
db 63h,6dh,61h,00h,02fh,00h
db 63h,6dh,63h,00h,03fh,00h
db 63h,6dh,70h,00h,0b8h,04h
db 63h,6eh,63h,00h,0d4h,01h
db 63h,6eh,7ah,00h,0c4h,01h
db 63h,70h,00h,00h,0f4h,01h
db 63h,70h,65h,00h,0ech,01h
db 63h,70h,69h,00h,0feh,02h
db 63h,70h,6fh,00h,0e4h,01h
db 63h,7ah,00h,00h,0cch,01h
db 64h,61h,61h,00h,027h,00h
db 64h,61h,64h,00h,009h,40h
db 0e4h,62h,0h,0h
dw dodb
db 64h,63h,72h,00h,005h,80h
db 64h,63h,78h,00h,00bh,40h
db 64h,69h,00h,00h,0f3h,00h
db 0e4h,73h,0h,0h
dw dods
db 0e4h,77h,0h,0h
dw dodw
db 65h,69h,00h,00h,0fbh,00h
db 0e5h,6eh,64h,0h
dw doend
db 68h,6ch,74h,00h,076h,00h
db 69h,6eh,00h,00h,0dbh,02h
db 69h,6eh,72h,00h,004h,80h
db 69h,6eh,78h,00h,003h,40h
db 6ah,63h,00h,00h,0dah,01h
db 6ah,6dh,00h,00h,0fah,01h
db 6ah,6dh,70h,00h,0c3h,01h
db 6ah,6eh,63h,00h,0d2h,01h
db 6ah,6eh,7ah,00h,0c2h,01h
db 6ah,70h,00h,00h,0f2h,01h
db 6ah,70h,65h,00h,0eah,01h
db 6ah,70h,6fh,00h,0e2h,01h
db 6ah,7ah,00h,00h,0cah,01h
db 6ch,64h,61h,00h,03ah,01h
db 6ch,64h,61h,78h,00ah,20h
db 6ch,68h,6ch,64h,02ah,01h
db 6ch,78h,69h,00h,001h,49h
db 6dh,6fh,76h,00h,040h,8ch
db 6dh,76h,69h,00h,006h,8ah
db 6eh,6fh,70h,00h,000h,00h
db 6fh,72h,61h,00h,0b0h,04h
db 0efh,72h,67h,0h
dw doorg
db 6fh,72h,69h,00h,0f6h,02h
db 6fh,75h,74h,00h,0d3h,02h
db 70h,63h,68h,6ch,0e9h,00h
db 70h,6fh,70h,00h,0c1h,10h
db 70h,75h,73h,68h,0c5h,10h
db 72h,61h,6ch,00h,017h,00h
db 72h,61h,72h,00h,01fh,00h
db 72h,63h,00h,00h,0d8h,00h
db 72h,65h,74h,00h,0c9h,00h
db 72h,69h,6dh,00h,020h,00h
db 72h,6ch,63h,00h,007h,00h
db 72h,6dh,00h,00h,0f8h,00h
db 72h,6eh,63h,00h,0d0h,00h
db 72h,6eh,7ah,00h,0c0h,00h
db 72h,70h,00h,00h,0f0h,00h
db 72h,70h,65h,00h,0e8h,00h
db 72h,70h,6fh,00h,0e0h,00h
db 72h,72h,63h,00h,00fh,00h
db 0f2h,73h,74h,0h
dw dorst
db 72h,7ah,00h,00h,0c8h,00h
db 73h,62h,62h,00h,098h,04h
db 73h,62h,69h,00h,0deh,02h
db 73h,68h,6ch,64h,022h,01h
db 73h,69h,6dh,00h,030h,00h
db 73h,70h,68h,6ch,0f9h,00h
db 73h,74h,61h,00h,032h,01h
db 73h,74h,61h,78h,002h,20h
db 73h,74h,63h,00h,037h,00h
db 73h,75h,62h,00h,090h,04h
db 73h,75h,69h,00h,0d6h,02h
db 78h,63h,68h,67h,0ebh,00h
db 78h,72h,61h,00h,0a8h,04h
db 78h,72h,69h,00h,0eeh,02h
db 78h,74h,68h,6ch,0e3h,00h
asmend:
endCut and paste the following into file "asmsml.do":
; asmasm v1.0
;An assembler program for the TRS-80
;Model 100 computer
; Copyright (C) 2014 Clinton Reddekop.
; You can redistribute and/or modify
; this program 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 program 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.
org 0e38dh
ret
db 0h
calladdr:
jmp entry
inst:
db 0h
loaddr: dw 0h
hiaddr: dw 0h
linenum: dw 0h
dw asmend
hlin: dw 0h
spbasic: dw 0h
splocal: dw endstack
loccntr: dw 0h
stack:
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach,0ach,0ach
db 0ach,0ach,0ach,0ach
endstack: dw main
db 0fh,0fh,0fh,0fh,0fh,0fh,0fh,0fh
curtoken: ds 100h
curline: ds 100h
labelmap: ds 800h
endlabelmap:
clpos: dw 0h
mapused: dw 0h
entry:
ana a
jnz restore
shld hlin
lxi h,0h
dad sp
shld spbasic
lhld splocal
sphl
lhld hlin
ret
exit:
xra a
sta inst
lxi h,main
shld endstack
lxi sp,endstack
backtobasic:
lxi h,0h
dad sp
shld splocal
lhld spbasic
sphl
ret
restore:
lxi h,main
shld endstack
lxi h,endstack
shld splocal
xra a
sta inst
ret
strsp: db 73h,70h,0h
strpsw: db 70h,73h,77h,0h
inel: db 1h
main:
call getinfilename
call openinputfile
lxi h,0ffffh
shld loaddr
lxi h,0h
shld hiaddr
shld loccntr
shld linenum
shld labelmap
inx h
shld mapused
lxi h,pokefirst
shld pokeaddr
mvi a,1h
sta inel
call singlepass
call closeinputfile
call checkbounds
call openinputfile
lxi h,0h
shld loccntr
shld linenum
lxi h,labeldef
mvi m,0c9h
lxi h,pokesecond
shld pokeaddr
xra a
sta inel
call singlepass
call closeinputfile
call reportsuccess
jmp exit
errorinfile:
mvi a,4h
sta inst
lxi h,reportederror
push h
jmp backtobasic
reportederror:
jmp exit
getinfilename:
mvi a,5h
sta inst
jmp backtobasic
openinputfile:
mvi a,2h
sta inst
jmp backtobasic
closeinputfile:
mvi a,3h
sta inst
jmp backtobasic
printbounds:
mvi a,7h
sta inst
jmp backtobasic
checkbounds:
mvi a,6h
sta inst
lxi h,checkedbounds
push h
jmp backtobasic
checkedbounds:
mov a,h
ora l
rz
call printbounds
jmp exit
reportsuccess:
mvi a,8h
sta inst
lxi h,printedsuccess
push h
jmp backtobasic
printedsuccess:
call printbounds
ret
getnextline:
push b
push d
push h
mvi a,1h
sta inst
lxi h,gotline
push h
jmp backtobasic
gotline:
mov b,m
inx h
mov e,m
inx h
mov d,m
lxi h,curline
call memcopy
mvi m,0h
lxi h,curline
shld clpos
lhld linenum
inx h
shld linenum
pop h
pop d
pop b
stc
ret
memcopy:
xra a
cmp b
rz
memcopyloop:
ldax d
mov m,a
inx d
inx h
dcr b
jnz memcopyloop
ret
strcmp:
push b
mvi b,0ffh
call strncmp
pop b
ret
strncmp:
push b
push d
push h
inr b
ldax d
ani 7fh
strncmploop:
dcr b
jz retstrncmp
cmp m
jnz retstrncmp
inx d
inx h
ana a
ldax d
jnz strncmploop
retstrncmp:
pop h
pop d
pop b
ret
strlen:
push h
lxi b,0h
xra a
strlenloop:
cmp m
jz retstrlen
inx h
inx b
jmp strlenloop
retstrlen:
pop h
ret
getopc:
push d
push h
call strlen
mov a,b
ana a
jnz retgetopcfail
mvi a,4h
cmp c
jc retgetopcfail
mvi b,0h
mvi c,55h
getopcloop:
mov a,c
sub b
dcr a
mov a,b
jz checkbottom
add c
push b
rar
mov c,a
ana a
ral
push h
mov l,a
mvi h,0h
mov d,h
mov e,l
dad d
dad d
lxi d,opctblstart
dad d
xchg
pop h
mvi b,4h
call strncmp
mov a,c
pop b
jz retgetopcpass
jc movebottomup
mov c,a
jmp getopcloop
movebottomup:
mov b,a
jmp getopcloop
checkbottom:
ana a
jnz retgetopcfail
lxi d,opctblstart
call strcmp
jz retgetopcpass
retgetopcfail:
stc
jmp retgetopc
retgetopcpass:
ldax d
ani 80h
mov h,a
inx d
inx d
inx d
inx d
ldax d
mov b,a
inx d
ldax d
mov c,a
mov a,h
ana a
retgetopc:
pop h
pop d
ret
skipws:
push b
mvi c,21h
skipwsloop:
mov a,m
ana a
jz retskipws
cmp c
jnc retskipws
inx h
jmp skipwsloop
retskipws:
pop b
ret
tolower:
push b
push d
push h
mvi c,20h
mvi d,41h
mvi e,5bh
tolowerloop:
mov a,m
ana a
jz rettolower
cmp d
jc contlowerloop
cmp e
jnc contlowerloop
add c
mov m,a
contlowerloop:
inx h
jmp tolowerloop
rettolower:
pop h
pop d
pop b
ret
isalphanum:
cpi 30h
jc failalphanum
cpi 3ah
rc
cpi 41h
jc failalphanum
cpi 5bh
rc
cpi 61h
jc failalphanum
cpi 7bh
rc
failalphanum:
ana a
ret
getnexttoken:
push b
push d
push h
mvi b,0h
lxi d,curtoken
lhld clpos
call skipws
mov a,m
ana a
jz retgetnexttoken
cpi 2ch
jz onechartoken
cpi 27h
jz onechartoken
cpi 3bh
jz retgetnexttoken
call isalphanum
jnc errorinfile
getnexttokenloop:
stax d
inx d
inx h
mov a,m
call isalphanum
jc getnexttokenloop
cpi 3ah
jnz retgetnexttoken
mov b,a
inx h
jmp retgetnexttoken
onechartoken:
stax d
inx d
inx h
retgetnexttoken:
shld clpos
xra a
stax d
lxi h,curtoken
call tolower
mov a,b
ana a
pop h
pop d
pop b
ret
getnexttokennolabel:
call getnexttoken
jnz errorinfile
ret
singlepass:
push b
push d
push h
jmp mainloopnoemptycheck
mainloop:
call getnexttoken
lda curtoken
ana a
jnz errorinfile
mainloopnoemptycheck:
call getnextline
jnc errorinfile
mainloopuserestofline:
call getnexttoken
jz notlabel
call getopc
jnc errorinfile
lhld loccntr
call labeldef
jmp mainloopuserestofline
notlabel:
lxi h,curtoken
xra a
cmp m
jz mainloopnoemptycheck
call getopc
jc errorinfile
jnz handledirective
mov a,c
ral
mov c,a
cc dorm543
mov a,c
ral
mov c,a
cc dorp
mov a,c
ral
mov c,a
cc dorbd
mov a,c
ral
mov c,a
cc dorw
mov a,c
ral
mov c,a
cc docomma
mov a,c
ral
mov c,a
cc dorm210
mov a,b
call poke
mov a,c
ral
mov c,a
cc do1byte
mov a,c
ral
mov c,a
cc do2byte
jmp mainloop
handledirective:
mov h,c
mov l,b
pchl
dorst:
call getnexttokennolabel
call getconstant
mov a,h
ana a
jnz errorinfile
mov a,l
cpi 8h
jnc errorinfile
ana a
ral
ral
ral
adi 0c7h
call poke
jmp mainloop
dodb:
call getnexttokennolabel
lda curtoken
ana a
jz mainloop
cpi 27h
jz strconstant
call getconstant
mov a,h
ana a
jnz errorinfile
mov a,l
call poke
jmp endstrconstant
strconstant:
mvi b,27h
lhld clpos
strconstantloop:
mov a,m
inx h
cmp b
jz endstrconstantloop
call poke
jmp strconstantloop
endstrconstantloop:
shld clpos
endstrconstant:
call getnexttoken
lda curtoken
cpi 2ch
jz dodb
ana a
jz mainloop
jmp errorinfile
dodw:
call getnexttokennolabel
lda curtoken
ana a
jz mainloop
call getconstant
mov a,l
call poke
mov a,h
call poke
call getnexttoken
lda curtoken
cpi 2ch
jz dodw
ana a
jz mainloop
jmp errorinfile
dods:
call getnexttokennolabel
lda inel
mov b,a
xra a
sta inel
call getconstant
mov a,b
sta inel
xchg
dsloop:
mov a,d
ora e
jz mainloop
xra a
call poke
dcx d
jmp dsloop
doorg:
call getnexttokennolabel
lda inel
mov b,a
xra a
sta inel
call getconstant
mov a,b
sta inel
shld loccntr
jmp mainloop
doend:
retsinglepass:
pop h
pop d
pop b
ret
dorm210:
push d
call helprm
mov a,d
add b
mov b,a
pop d
ret
helprm:
push h
call getnexttoken
lxi h,curtoken
mov a,m
cpi 61h
mvi d,7h
jz rethelprm
cpi 62h
mvi d,0h
jz rethelprm
cpi 63h
mvi d,1h
jz rethelprm
cpi 64h
mvi d,2h
jz rethelprm
cpi 65h
mvi d,3h
jz rethelprm
cpi 68h
mvi d,4h
jz rethelprm
cpi 6ch
mvi d,5h
jz rethelprm
cpi 6dh
mvi d,6h
jnz errorinfile
rethelprm:
inx h
xra a
cmp m
jnz errorinfile
pop h
ret
dorp:
push d
call helprbdh
mov a,e
cpi 70h
jz errorinfile
mov a,d
add b
mov b,a
pop d
ret
helprbdh:
push h
call getnexttoken
lxi h,curtoken
mov a,m
mov e,a
cpi 62h
mvi d,0h
jz rethelprbdh
cpi 64h
mvi d,10h
jz rethelprbdh
cpi 68h
mvi d,20h
jz rethelprbdh
lxi d,strsp
call strcmp
mvi d,30h
mvi e,73h
jz rethelprbdhnolenchk
lxi d,strpsw
call strcmp
mvi d,30h
mvi e,70h
jz rethelprbdhnolenchk
jmp errorinfile
rethelprbdh:
inx h
xra a
cmp m
jnz errorinfile
rethelprbdhnolenchk:
pop h
ret
dorbd:
push d
call helprbdh
mov a,d
cpi 11h
jnc errorinfile
add b
mov b,a
pop d
ret
dorw:
push d
call helprbdh
mov a,e
cpi 73h
jz errorinfile
mov a,d
add b
mov b,a
pop d
ret
docomma:
call getnexttoken
lda curtoken
cpi 2ch
jnz errorinfile
ret
dorm543:
push d
call helprm
mov a,d
ana a
ral
ral
ral
add b
mov b,a
pop d
ret
do1byte:
call getnexttokennolabel
call getconstant
mov a,h
ana a
jnz errorinfile
mov a,l
call poke
ret
do2byte:
call getnexttokennolabel
call getconstant
mov a,l
call poke
mov a,h
call poke
ret
poke:
db 0c3h
pokeaddr:
dw 0h
pokefirst:
push h
push d
lhld loaddr
xchg
lhld loccntr
mov a,l
sub e
mov a,h
sbb d
jnc checkhiaddr
shld loaddr
checkhiaddr:
xchg
lhld hiaddr
mov a,l
sub e
mov a,h
sbb d
xchg
jnc retpokefirst
shld hiaddr
retpokefirst:
inx h
shld loccntr
pop d
pop h
ret
pokesecond:
push h
lhld loccntr
mov m,a
inx h
shld loccntr
pop h
ret
getconstant:
push b
push d
lda curtoken
ana a
jz errorinfile
checkforcharliteral:
cpi 27h
jnz checkfordigit
lhld clpos
mov a,m
ana a
jz errorinfile
cpi 27h
jz errorinfile
mov b,a
inx h
mov a,m
cpi 27h
jnz errorinfile
inx h
shld clpos
mvi h,0h
mov l,b
jmp retgetconstant
checkfordigit:
cpi 30h
jc errorinfile
cpi 3ah
jnc checkforlabel
call getnumfromtoken
jmp retgetconstant
checkforlabel:
cpi 61h
jc errorinfile
cpi 7bh
jnc errorinfile
call labellookup
retgetconstant:
pop d
pop b
ret
getnumfromtoken:
push b
push d
lxi h,curtoken
call strlen
dcx b
dad b
mov a,m
mvi m,0h
cpi 68h
jz gethexnum
cpi 64h
jz getdecnum
mov m,a
getdecnum:
lxi h,curtoken
lxi b,0h
decnumloop:
mov a,m
inx h
ana a
jz retgetnumfromtoken
cpi 3ah
jnc errorinfile
cpi 30h
jc errorinfile
sbi 30h
push h
mov h,b
mov l,c
dad h
dad h
dad h
dad b
dad b
add l
mov c,a
mov b,h
pop h
jnc decnumloop
inr b
jmp decnumloop
gethexnum:
lxi h,curtoken
lxi b,0h
hexnumloop:
mov a,m
inx h
ana a
jz retgetnumfromtoken
cpi 3ah
jnc chkatof
cpi 30h
jc errorinfile
sbi 30h
jmp hexnummult
chkatof:
cpi 67h
jnc errorinfile
cpi 61h
jc errorinfile
sbi 57h
hexnummult:
push h
mov h,b
mov l,c
dad h
dad h
dad h
dad h
add l
mov c,a
mov b,h
pop h
jnc hexnumloop
inr b
jmp hexnumloop
retgetnumfromtoken:
mov h,b
mov l,c
pop d
pop b
ret
labelfind:
lxi h,labelmap
labelsearch:
xra a
cmp m
rz
lxi d,curtoken
labelcmp:
ldax d
cmp m
jnz nextlabel
inx d
inx h
ana a
jnz labelcmp
stc
ret
nextlabel:
mov a,m
ana a
inx h
jnz nextlabel
inx h
inx h
jmp labelsearch
labellookup:
push d
lda inel
ana a
jz gofindlabel
lxi h,0h
jmp retlabellookup
gofindlabel:
call labelfind
jnc errorinfile
mov e,m
inx h
mov h,m
mov l,e
retlabellookup:
pop d
ret
labeldef:
push d
push h
call labelfind
jc errorinfile
push h
lxi h,curtoken
call strlen
lhld mapused
dad b
inx h
inx h
inx h
ana a
mov a,h
sbi 8h
jnc errorinfile
shld mapused
pop h
lxi d,curtoken
writelabelloop:
ldax d
mov m,a
inx h
inx d
ana a
jnz writelabelloop
pop d
mov m,e
inx h
mov m,d
inx h
xra a
mov m,a
pop d
ret
opctblstart:
db 61h,63h,69h,00h,0ceh,02h
db 61h,64h,63h,00h,088h,04h
db 61h,64h,64h,00h,080h,04h
db 61h,64h,69h,00h,0c6h,02h
db 61h,6eh,61h,00h,0a0h,04h
db 61h,6eh,69h,00h,0e6h,02h
db 63h,61h,6ch,6ch,0cdh,01h
db 63h,63h,00h,00h,0dch,01h
db 63h,6dh,00h,00h,0fch,01h
db 63h,6dh,61h,00h,02fh,00h
db 63h,6dh,63h,00h,03fh,00h
db 63h,6dh,70h,00h,0b8h,04h
db 63h,6eh,63h,00h,0d4h,01h
db 63h,6eh,7ah,00h,0c4h,01h
db 63h,70h,00h,00h,0f4h,01h
db 63h,70h,65h,00h,0ech,01h
db 63h,70h,69h,00h,0feh,02h
db 63h,70h,6fh,00h,0e4h,01h
db 63h,7ah,00h,00h,0cch,01h
db 64h,61h,61h,00h,027h,00h
db 64h,61h,64h,00h,009h,40h
db 0e4h,62h,0h,0h
dw dodb
db 64h,63h,72h,00h,005h,80h
db 64h,63h,78h,00h,00bh,40h
db 64h,69h,00h,00h,0f3h,00h
db 0e4h,73h,0h,0h
dw dods
db 0e4h,77h,0h,0h
dw dodw
db 65h,69h,00h,00h,0fbh,00h
db 0e5h,6eh,64h,0h
dw doend
db 68h,6ch,74h,00h,076h,00h
db 69h,6eh,00h,00h,0dbh,02h
db 69h,6eh,72h,00h,004h,80h
db 69h,6eh,78h,00h,003h,40h
db 6ah,63h,00h,00h,0dah,01h
db 6ah,6dh,00h,00h,0fah,01h
db 6ah,6dh,70h,00h,0c3h,01h
db 6ah,6eh,63h,00h,0d2h,01h
db 6ah,6eh,7ah,00h,0c2h,01h
db 6ah,70h,00h,00h,0f2h,01h
db 6ah,70h,65h,00h,0eah,01h
db 6ah,70h,6fh,00h,0e2h,01h
db 6ah,7ah,00h,00h,0cah,01h
db 6ch,64h,61h,00h,03ah,01h
db 6ch,64h,61h,78h,00ah,20h
db 6ch,68h,6ch,64h,02ah,01h
db 6ch,78h,69h,00h,001h,49h
db 6dh,6fh,76h,00h,040h,8ch
db 6dh,76h,69h,00h,006h,8ah
db 6eh,6fh,70h,00h,000h,00h
db 6fh,72h,61h,00h,0b0h,04h
db 0efh,72h,67h,0h
dw doorg
db 6fh,72h,69h,00h,0f6h,02h
db 6fh,75h,74h,00h,0d3h,02h
db 70h,63h,68h,6ch,0e9h,00h
db 70h,6fh,70h,00h,0c1h,10h
db 70h,75h,73h,68h,0c5h,10h
db 72h,61h,6ch,00h,017h,00h
db 72h,61h,72h,00h,01fh,00h
db 72h,63h,00h,00h,0d8h,00h
db 72h,65h,74h,00h,0c9h,00h
db 72h,69h,6dh,00h,020h,00h
db 72h,6ch,63h,00h,007h,00h
db 72h,6dh,00h,00h,0f8h,00h
db 72h,6eh,63h,00h,0d0h,00h
db 72h,6eh,7ah,00h,0c0h,00h
db 72h,70h,00h,00h,0f0h,00h
db 72h,70h,65h,00h,0e8h,00h
db 72h,70h,6fh,00h,0e0h,00h
db 72h,72h,63h,00h,00fh,00h
db 0f2h,73h,74h,0h
dw dorst
db 72h,7ah,00h,00h,0c8h,00h
db 73h,62h,62h,00h,098h,04h
db 73h,62h,69h,00h,0deh,02h
db 73h,68h,6ch,64h,022h,01h
db 73h,69h,6dh,00h,030h,00h
db 73h,70h,68h,6ch,0f9h,00h
db 73h,74h,61h,00h,032h,01h
db 73h,74h,61h,78h,002h,20h
db 73h,74h,63h,00h,037h,00h
db 73h,75h,62h,00h,090h,04h
db 73h,75h,69h,00h,0d6h,02h
db 78h,63h,68h,67h,0ebh,00h
db 78h,72h,61h,00h,0a8h,04h
db 78h,72h,69h,00h,0eeh,02h
db 78h,74h,68h,6ch,0e3h,00h
asmend:
endThe file "asmsml.do" above is a cleaned up and comment-free version of "asm.do". It has the advantage of fitting in the RAM of the emulator.
Instructions for Assembling the Assembly Assembler
asm.do and asmsml.do are both written to be assembled into memory addresses 58253 to 62959 which I am assuming can be CLEARed on your machine. If you need to change this, change the value given to the ORG directive, and adjust the value "58253" on line 25 of asmrun.ba to match.
Below, asm2.ba refers to the BASIC assembler from the previous Project Log titled "BASIC Assembler Fix".
Below, "<enter>" means press the enter key.
- start with no files in the emulator
- load asmsml.do and asm2.ba into emulator
- select the BASIC menu item, <enter>
- type: CLEAR0,58253 <enter>
- type: LOAD"ASM2.BA <enter>
- type: RUN <enter>
- when prompted for 'FILENAME?', type: ASMSML.DO
- wait for a really long time...
- if it works, you will see a success message; if not, an error message
- type: KILL"ASMSML.DO <enter>
- type: SAVEM"ASM",58253,62959 <enter>
- type: NEW <enter>
- type: KILL"ASM2.BA <enter>
- type: MENU <enter>
- you should probably back up "ASM.CO" somewhere
- load asmrun.ba into the emulator
You now have asmrun.ba and asm.co in the filesystem; these 2 files work together as an assembler that I will call "asmasm".
Using ASMASM
Now suppose you have an assembly file called "prog.do" that you want assembled into machine code in RAM, for example between addresses 58000 and 58252. To do this:
- make sure prog.do is written to assemble into those locations (use the ORG directive, keep the program short enough)
- load prog.do into emulator
- select the BASIC menu item, <enter>
- type: CLEAR0,58000 <enter>
- type: MENU <enter>
- select ASMRUN.BA in the menu, <enter>
- when prompted for 'input file?', type: PROG.DO <enter>
- wait for not very long
- success or error will be reported on the screen
I hope someone will find this useful or at least interesting. A few more features are hopefully coming later.
-
Can't Get Away From BASIC
09/23/2014 at 04:09 • 0 commentsMy 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'.
-
BASIC Assembler Fix
09/12/2014 at 03:42 • 0 commentsWell, it turns out that BASIC strings can be at most 255 bytes long. I was trying to keep all of the labels from an assembly file in one BASIC string, which meant I couldn't have very many labels. Below is an updated BASIC assembler which fixes this problem by using arrays (turns out BASIC has arrays...)
Here is the updated BASIC assembler:
100 GOTO1000
200 PRINT"err near line",CL!
210 CLOSE1
220 END
1000 CLEAR2048:PS%=1:LM!=65535:HM!=0
1005 GOSUB2000
1006 INPUT"FILENAME";FN$
1007 PRINT"pass 1"
1010 OPENFN$FORINPUTAS1
1020 GOSUB10000
1030 CLOSE1
1040 IFLM!>HM!THENGOTO1120
1050 IFHM!>=MAXRAMTHENGOTO1120
1060 IFLM!>=HIMEMTHENGOTO1140
1120 PRINT"bounds error":PRINT"LO =",LM!:PRINT"HI =",HM!
1130 END
1140 PS%=2:CL!=1:BF%=0
1145 PRINT"pass 2"
1150 OPENFN$FORINPUTAS1
1160 GOSUB10000
1170 CLOSE1
1180 PRINT"success":PRINT"LO =",LM!:PRINT"HI =",HM!
1190 END
2000 A1$=".cma.47.cmc.63.daa.39.di.243.ei.251.hlt.118.nop.0.pchl.233.ral.23.rar.31.ret.201.rim.32.rlc.7.rrc.15.sim.48.sphl.249.stc.55.xchg.235.xthl.227"
2010 A2$=".adc.136.add.128.ana.160.cmp.184.ora.176.sbb.152.sub.144.xra.168"
2020 A3$=".dcr.5.inr.4"
2030 A4$=".dad.9.dcx.11.inx.3.ldax.10.pop.193.push.197.stax.2"
2040 A5$=".aci.206.adi.198.ani.230.cpi.254.in.219.ori.246.out.211.sbi.222.sui.214.xri.238"
2050 A6$=".call.205.jmp.195.lda.58.lhld.42.shld.34.sta.50"
2060 B2$=".a.7.b.0.c.1.d.2.e.3.h.4.l.5.m.6"
2070 B3$=".a.56.b.0.c.8.d.16.e.24.h.32.l.40.m.48"
2080 B4$=".b.0.d.16.h.32.sp.48.psw.48"
2090 B5$=".nz.0.z.8.nc.16.c.24.po.32.pe.40.p.48.m.56"
2100 DIMLB$(160)
2110 DIMLB!(160)
2120 MI%=0
2199 RETURN
2300 GOSUB2700
2320 IFNM!>255THENGOTO200
2330 BB!=NM!:GOTO2800
2400 GOSUB9000
2406 IFNL%<>0THENGOTO200
2410 CC$=LEFT$(TK$,1):GOSUB8100
2420 IFSC%=0THENGOTO2470
2430 NB$=TK$:GOSUB5000
2440 IFSC%=0THENGOTO200
2450 RETURN
2470 SS$=TK$:GOTO12000
2500 GOSUB2700
2570 GOSUB2600
2580 BB!=MD!:GOSUB2800
2590 BB!=DV!:GOTO2800
2600 IFNM!<=32767THENGOTO2620
2610 N!=NM!-32768:DV!=128:GOTO2630
2620 DV!=0:N!=NM!
2630 DV!=DV!+(N!\256):MD!=N!MOD256
2640 RETURN
2700 GOSUB2400
2720 IFSC%<>0THENRETURN
2730 IFPS%=2THENGOTO200
2740 TK$="0":NM!=0:RETURN
2800 IFPS%=1THENGOTO2850
2810 POKEPC!,BB!
2830 PC!=PC!+1:RETURN
2850 IFPC!<=HM!THENGOTO2870
2860 HM!=PC!
2870 IFPC!>=LM!THENGOTO2890
2880 LM!=PC!
2890 PC!=PC!+1:RETURN
3000 P%=INSTR(MP$,"."+SS$+".")
3010 IFP%<>0GOTO3040
3020 SC%=0:RETURN
3040 T$=MID$(MP$,P%+2+LEN(SS$))
3041 P%=INSTR(T$,"."):IF P%=0 GOTO 3050
3048 T$=MID$(T$,1,P%-1)
3050 NM!=VAL(T$):SC%=1:RETURN
3200 GOSUB9000
3210 CC$=LEFT$(TK$,1):GOSUB8000
3220 IFSC%=0THENGOTO200
3230 SS$=TK$:GOSUB3000
3240 IFSC%=0THENGOTO200
3250 OC!=OC!+NM!:RETURN
5000 N$=NB$:SC%=0:NM!=0
5010 K%=LEN(N$)-1
5020 IFK%<1THENRETURN
5030 A$=RIGHT$(N$,1)
5040 IFA$<>"h"THENRETURN
5050 N$=LEFT$(N$,K%)
5060 FORI%=1TOK%
5080 A$=MID$(N$,I%,1)
5090 X%=ASC(A$)
5100 IFX%>=48ANDX%<=57GOTO5130
5110 IFX%>=97ANDX%<=102GOTO5140
5120 RETURN
5130 X%=X%-48:GOTO5150
5140 X%=X%-87
5150 NM!=NM!*16
5160 NM!=NM!+X%
5170 NEXT
5180 SC%=1:RETURN
8000 A%=ASC(CC$):SC%=1
8040 IFA%>=97ANDA%<=122THENRETURN
8050 SC%=0:RETURN
8100 A%=ASC(CC$):SC%=1
8120 IFA%>=48ANDA%<=57THENRETURN
8130 SC%=0:RETURN
8200 GOSUB8000
8210 IFSC%=1THENRETURN
8220 GOTO8100
8300 SC%=1
8305 IFBF%<>0THENGOTO8345
8306 NL%=0
8310 IFEOF(1)THENGOTO200
8320 CB$=INPUT$(1,1)
8322 C%=ASC(CB$)
8324 IFC%<>10THENRETURN
8326 NL%=1:CL!=CL!+1:RETURN
8345 BF%=0:RETURN
8400 SC%=1:A%=ASC(CC$)
8420 IFA%<=32THENRETURN
8430 SC%=0:RETURN
9000 TK$=""
9010 GOSUB8300
9015 IFNL%<>0THENGOTO9430
9030 CC$=CB$
9040 GOSUB8400
9060 IFSC%<>0THENGOTO9010
9070 IFCB$<>";"THENGOTO9200
9100 GOSUB8300
9105 IFNL%<>0THENGOTO9430
9150 GOTO9100
9200 IFCB$<>","THENGOTO9220
9210 TK$=CB$:RETURN
9220 CC$=CB$
9222 GOSUB8200
9230 IFSC%=0THENGOTO200
9231 BF%=1
9240 GOSUB 8300
9245 IFNL%<>0THENGOTO9440
9250 CC$=CB$
9260 GOSUB8200
9270 IFSC%=0THENGOTO9300
9280 TK$=TK$+CB$
9290 GOTO9240
9300 IFCB$<>":"THENGOTO9340
9320 TK$=TK$+CB$
9330 RETURN
9340 IFCB$<>","ANDCB$<>";"THENRETURN
9350 BF%=1
9360 RETURN
9380 GOSUB8400
9390 IFSC%=0THENGOTO200
9430 TK$=CB$:RETURN
9440 BF%=1:RETURN
9700 GOSUB9000
9710 IFNL%<>0ORTK$<>","THENGOTO200
9720 RETURN
10000 CL!=1:BF%=0
10010 GOTO10050
10030 GOSUB9000
10040 IFNL%=0THENGOTO200
10050 GOSUB9000
10055 IFNL%<>0THENGOTO10050
10060 TT$=TK$:SS$=TT$:MP$=A1$:GOSUB3000
10070 IFSC%=0THENGOTO10100
10080 BB!=NM!:GOSUB2800
10090 GOTO10030
10100 SS$=TT$:MP$=A2$:GOSUB3000
10110 IFSC%=0THENGOTO10150
10120 OC!=NM!:MP$=B2$:GOSUB3200
10130 BB!=OC!:GOSUB2800
10140 GOTO10030
10150 SS$=TT$:MP$=A3$:GOSUB3000
10160 IFSC%=0THENGOTO10200
10170 OC!=NM!:MP$=B3$:GOSUB3200
10180 GOTO10130
10200 SS$=TT$:MP$=A4$:GOSUB3000
10210 IFSC%=0THENGOTO10250
10220 OC!=NM!:MP$=B4$:GOSUB3200
10230 GOTO10130
10250 SS$=TT$:MP$=A5$:GOSUB3000
10260 IFSC%=0THENGOTO10300
10270 BB!=NM!:GOSUB2800
10280 GOSUB2300
10290 GOTO10030
10300 SS$=TT$:MP$=A6$:GOSUB3000
10310 IFSC%=0THENGOTO10350
10320 BB!=NM!:GOSUB2800
10330 GOSUB2500
10340 GOTO10030
10350 IFTT$<>"lxi"THENGOTO10400
10360 OC!=1:MP$=B4$:GOSUB3200
10370 BB!=OC!:GOSUB2800
10375 GOSUB9700
10380 GOSUB2500
10390 GOTO10030
10400 IFTT$<>"mov"THENGOTO10450
10410 OC!=64:MP$=B3$:GOSUB3200
10415 GOSUB9700
10420 MP$=B2$:GOSUB3200
10430 GOTO10130
10450 IFTT$<>"mvi"THENGOTO10500
10460 OC!=6:MP$=B3$:GOSUB3200
10470 BB!=OC!:GOSUB2800
10475 GOSUB9700
10480 GOSUB2300
10490 GOTO10030
10500 IFTT$<>"rst"THENGOTO10550
10510 OC!=199:GOSUB2700
10520 OC!=OC!+8*NM!
10530 GOTO10130
10550 IFTT$<>"org"THENGOTO10595
10560 GOSUB2700
10570 PC!=NM!
10590 GOTO10030
10595 A$=RIGHT$(TT$,1):IFA$=":"THENGOTO11110
10600 A$=LEFT$(TT$,1):SS$=MID$(TT$,2):MP$=B5$
10610 IFA$<>"r"THENGOTO10650
10620 OC!=192:TK$=SS$:GOSUB3230
10630 GOTO10130
10650 IFA$<>"c"THENGOTO10700
10660 OC!=196:TK$=SS$:GOSUB3230
10670 BB!=OC!:GOSUB2800
10680 GOSUB2500
10690 GOTO10030
10700 IFA$<>"j"THENGOTO10750
10710 OC!=194:TK$=SS$:GOSUB3230
10720 GOTO10670
10750 IFTT$="end"THENRETURN
10760 IFA$<>"d"THENGOTO200
10770 B$=MID$(TT$,2)
10800 IFB$<>"s"THENGOTO10890
10810 GOSUB9000
10820 IFNL%<>0THENGOTO200
10830 CC$=LEFT$(TK$,1):GOSUB8100
10840 IFSC%=0THENGOTO200
10850 NB$=TK$:GOSUB5000
10860 IFSC%=0THENGOTO200
10870 PC!=PC!+NM!
10880 GOTO10030
10890 IFB$<>"b"THENGOTO10950
10900 GOSUB2300
10910 GOSUB9000
10920 IFNL%<>0THENGOTO10050
10930 IFTK$<>","THENGOTO200
10940 GOTO10900
10950 IFB$<>"w"THENGOTO200
10960 GOSUB2500
10970 GOSUB9000
10980 IFNL%<>0THENGOTO10050
10990 IFTK$<>","THENGOTO200
11000 GOTO10960
11110 IFPS%=2THENGOTO10050
11120 B%=LEN(TT$)-1:A$=LEFT$(TT$,B%):GOSUB12500
11130 GOTO10050
12000 SC%=0
12010 IFMI%=0THENRETURN
12020 FORI%=1TOMI%
12030 IFLB$(I%)<>SS$THENGOTO12060
12040 NM!=LB!(I%):SC%=1
12050 RETURN
12060 NEXT
12070 RETURN
12500 IFMI%>=160THENGOTO200
12510 MI%=MI%+1
12520 LB$(MI%)=A$
12530 LB!(MI%)=PC!
12540 RETURN
Here is the documentation:
asm.ba contains BASIC code for an assembler for the model 100. This is a
very limited assembler which is meant only to be used to create a better
assembler written in assembly.
Assembly reference: 8080/8085 ASSEMBLY LANGUAGE PROGRAMMING MANUAL
1977,1978 Intel Corporation
This assembler only understands lowercase.
It understands only the following:
- usual 8085 opcodes
- numbers in hexadecimal only
- these must begin with a decimal digit and terminate with 'h', e.g.:
0ab43h
- the usual operands a b c d e h l m sp psw
- labels
- immediate operands may be labels or hex numbers only
- assembler directives:
- org
- end -- everything after "end" in the file is ignored
-- this is required
- db -- byte data as hex numbers only
- dw -- word data as hex numbers only
- ds -- note the # bytes may only be given as a number - not a label
- comments -- from ';' to the end of the line
The assembly code is converted to machine code directly in the model 100 RAM.
You could then use the BASIC keyword SAVEM to put the machine code in a file.
There are errors that this assembler won't catch; try not to make any.
*** Program Documentation ***
Before running the assembler:
- Your input file must start with an org directive.
- The assembler will only write to RAM from HIMEM to MAXRAM-1, so you need to
make sure your program will fit in that space. See documentation on the BASIC
CLEAR keyword for help with this.
** 1000 main
The assembler makes two passes of the file. The first pass makes sure that all
writes to RAM will be within the range from HIMEM to MAXRAM-1, and determines
the values of all the labels. The second pass writes the program to RAM.
The main program starts at line 1000, and calls the subroutine at 10000 once
for each pass.
** Variables:
FN$ input filename
PC! next location in RAM to write ('program counter')
PS% pass number
LM! lowest RAM address changed
HM! highest RAM address changed
CL! current line in input file
SC% used for boolean return values from subroutines
NL% boolean indicating whether a newline was read from input file
TK$, TT$ tokens read from input file
OC! instruction opcode
** 10000 single pass
The subroutine at line 10000 handles one pass. It is a big loop which reads
and assembles one instruction or directive at a time. The following cases
are treated within the loop:
line number case
10060 all instructions listed in A1$
10100 all instructions listed in A2$
10150 all instructions listed in A3$
10200 all instructions listed in A4$
10250 all instructions listed in A5$
10300 all instructions listed in A6$
10350 lxi instruction
10400 mov instruction
10450 mvi instruction
10500 rst instruction
10550 org assembler directive
10595 labels
10610 all conditional return instructions
10650 all conditional call instructions
10700 all conditional jump instructions
10750 end assembler directive
10800 ds assembler directive
10890 db assembler directive
10950 dw assembler directive
** 2000 init
The subroutine at line 2000 sets up some string variables before the first pass.
** 9000 get token
The subroutine at line 9000 gets the next token from the input file. It skips
whitespace and comments.
Recognized tokens are:
- a newline
- a comma
- a string of alphanumeric characters followed immediately by a colon
- a string of alphanumeric characters
If the end of the file is reached before any token, TK$="" upon return;
else if the token was a newline, NL%=1 upon return;
else TK$ contains the token found.
** 5000 hex string to number
The subroutine at line 5000 attempts to read a hexadecimal number in NB$. The
number must be in the range 0h to 0ffffh. If successful, upon return SC%=1
and NM! contains the number read. Otherwise, SC%=0.
** 8000 isalpha
The subroutine at line 8000 expects a single-character string in CC$. It checks
whether the character is a lowercase letter: if so it returns with SC%=1, else
it returns with SC%=0.
** 8100 isnum
The subroutine at line 8100 expects a single-character string in CC$. It checks
whether the character is a decimal digit: if so it returns with SC%=1, else
it returns with SC%=0.
** 8200 isalphanum
The subroutine at line 8200 expects a single-character string in CC$. It checks
whether the character is a lowercase letter or a decimal digit: if so it
returns with SC%=1, else it returns with SC%=0.
** 8300 get next char
BF%=0:
The subroutine at line 8300 reads the next character from the input file, which
must not yet be at eof. The character is put in CB$ and, if the character is
newline, NL% is set to 1, else NL% is set to 0. Also, whenever newline is
read, the variable CL! is incremented.
BF%=1:
The subroutine sets BF% back to 0, and returns whatever it returned the last
time it was called. Setting BF% to 1 before calling basically "unreads" the
last character read from the input file.
** 8400 iswhitespace
The subroutine at line 8400 expects a single-character string in CC$. It checks
whether the character is whitespace: if so it returns with SC%=1, if
not it returns with SC%=0. Here we consider any character with ASCII code
<= 20 to be whitespace.
** 2300 get imm1
The subroutine at line 2300 reads the next token from the input file, expecting
it to be a valid one-byte immediate operand (either a hex number or label, with
value in range 0h to 0ffh). The value of this byte is written to RAM at the
current PC! location, and the PC! is incremented.
** 2500 get imm2
The subroutine at line 2500 reads the next token from the input file, expecting
it to be a valid two-byte immediate operand (either a hex number or label, with
value in range 0h to 0ffffh). The value of this word is written to RAM at the
current PC! location, and the PC! is increased by 2.
** 2700 get number
The subroutine at line 2700 reads the next token from the input file, expecting
it to be a valid immediate operand (either a hex number or label, with value in
range 0h to 0ffffh). The token read is returned in TK$ and the value of the
operand is returned in NM! Note that if an undefined label is read, this is
allowed during the first pass but not the second pass.
** 2800 poke
The subroutine at line 2800 expects BB! to contain a number between 0h and 0ffh,
and PC! to contain the address in RAM where BB! should be written. During the
first pass, this subroutine just keeps track of the range of RAM to be written
(in LM! and HM!). During the second pass, this subroutine actually writes to
RAM.
I needed a way to associate string "keys" with values. I did this
with 'MAP' strings in the form ".key1.val1.key2.val2... .keyn.valn".
Every key must start with a lowercase letter, and the values must be
decimal numbers.
Originally I was using such a map to store labels and their values, but
this didn't work out so well because BASIC strings can be at most 255
characters. So I made another type of map just for labels, which uses
arrays for storage.
** 3000 string map lookup
The subroutine at line 3000 looks up the value associated with key SS$ in MAP
MP$. If the key is found, the value is put in NM! and the subroutine returns
with SC%=1. Otherwise returns with SC%=0.
** 3200 add field into opcode
The subroutine at line 3200 gets the next token from the input file and uses
it as a key to look up in string MAP MP$. The value found is added into OC!
** 12000 label lookup
The subroutine at line 12000 looks for the label is SS$ in the label map.
If found, returns the associated value in NM! and with SC%=1;
else, returns with SC%=0 and NM! should not be used.
** 12500 define label
The subroutine at line 12500 adds the label/value pair in A$/PC! to the
label map.
** MAPS
A1$ contains opcodes for instructions which take no operands
A2$ contains opcodes which take one operand which is the name of a register,
which is encoded into the opcode by adding the value in B2$ associated
with the register into the opcode
A3$ contains opcodes which take one operand which is the name of a register,
which is encoded into the opcode by adding the value in B3$ associated
with the register into the opcode
A4$ contains opcodes which take one operand which is the name of a register pair,
which is encoded into the opcode by adding the value in B4$ associated
with the register pair into the opcode
A5$ contains opcodes which take a one-byte immediate operand
A6$ contains opcodes which take a two-byte immediate operand
B2$ see A2$
B3$ see A3$
B4$ see A4$
B5$ for rXX, jXX, cXX instructions, XX being the condition code, the condition
code is encoded into the opcode as bits 5, 4, 3 of the value associated
with the condition code in B5$
-
BASIC Assembler
08/15/2014 at 03:28 • 0 commentsCan't find a way to add files to the project, so I'm just going to paste below - sorry. First is the BASIC program, then a text file I wrote to document the code. My apologies to anyone who tries to read this; I have no experience with BASIC.
Here is the BASIC program:
100 GOTO1000
200 PRINT"err near line",CL!
210 CLOSE1
220 END
1000 CLEAR2048:PS%=1:LM!=MAXRAM:HM!=HIMEM-1
1005 GOSUB2000
1006 INPUT"FILENAME";FN$
1007 PRINT"pass 1"
1010 OPENFN$FORINPUTAS1
1020 GOSUB10000
1030 CLOSE1
1040 IFLM!>HM!THENGOTO1120
1050 IFHM!>=MAXRAMTHENGOTO1120
1060 IFLM!>=HIMEMTHENGOTO1140
1120 PRINT"bounds error":PRINT"LO =",LM!:PRINT"HI =",HM!
1130 END
1140 PS%=2:CL!=1:BF%=0
1145 PRINT"pass 2"
1150 OPENFN$FORINPUTAS1
1160 GOSUB10000
1170 CLOSE1
1180 PRINT"success":PRINT"LO =",LM!:PRINT"HI =",HM!
1190 END
2000 A1$=".cma.47.cmc.63.daa.39.di.243.ei.251.hlt.118.nop.0.pchl.233.ral.23.rar.31.ret.201.rim.32.rlc.7.rrc.15.sim.48.sphl.249.stc.55.xchg.235.xthl.227"
2010 A2$=".adc.136.add.128.ana.160.cmp.184.ora.176.sbb.152.sub.144.xra.168"
2020 A3$=".dcr.5.inr.4"
2030 A4$=".dad.9.dcx.11.inx.3.ldax.10.pop.193.push.197.stax.2"
2040 A5$=".aci.206.adi.198.ani.230.cpi.254.in.219.ori.246.out.211.sbi.222.sui.214.xri.238"
2050 A6$=".call.205.jmp.195.lda.58.lhld.42.shld.34.sta.50"
2060 B2$=".a.7.b.0.c.1.d.2.e.3.h.4.l.5.m.6"
2070 B3$=".a.56.b.0.c.8.d.16.e.24.h.32.l.40.m.48"
2080 B4$=".b.0.d.16.h.32.sp.48.psw.48"
2090 B5$=".nz.0.z.8.nc.16.c.24.po.32.pe.40.p.48.m.56"
2100 LB$=""
2199 RETURN
2300 GOSUB2700
2320 IFNM!>255THENGOTO200
2330 BB!=NM!:GOTO2800
2400 GOSUB9000
2406 IFNL%<>0THENGOTO200
2410 CC$=LEFT$(TK$,1):GOSUB8100
2420 IFSC%=0THENGOTO2470
2430 NB$=TK$:GOSUB5000
2440 IFSC%=0THENGOTO200
2450 RETURN
2470 SS$=TK$:MP$=LB$:GOTO3000
2500 GOSUB2700
2570 GOSUB2600
2580 BB!=MD!:GOSUB2800
2590 BB!=DV!:GOTO2800
2600 IFNM!<=32767THENGOTO2620
2610 N!=NM!-32768:DV!=128:GOTO2630
2620 DV!=0:N!=NM!
2630 DV!=DV!+(N!\256):MD!=N!MOD256
2640 RETURN
2700 GOSUB2400
2720 IFSC%<>0THENRETURN
2730 IFPS%=2THENGOTO200
2740 TK$="0":NM!=0:RETURN
2800 IFPS%=1THENGOTO2850
2810 POKEPC!,BB!
2830 PC!=PC!+1:RETURN
2850 IFPC!<=HM!THENGOTO2870
2860 HM!=PC!
2870 IFPC!>=LM!THENGOTO2890
2880 LM!=PC!
2890 PC!=PC!+1:RETURN
3000 P%=INSTR(MP$,"."+SS$+".")
3010 IFP%<>0GOTO3040
3020 SC%=0:RETURN
3040 T$=MID$(MP$,P%+2+LEN(SS$))
3041 P%=INSTR(T$,"."):IF P%=0 GOTO 3050
3048 T$=MID$(T$,1,P%-1)
3050 NM!=VAL(T$):SC%=1:RETURN
3200 GOSUB9000
3210 CC$=LEFT$(TK$,1):GOSUB8000
3220 IFSC%=0THENGOTO200
3230 SS$=TK$:GOSUB3000
3240 IFSC%=0THENGOTO200
3250 OC!=OC!+NM!:RETURN
5000 N$=NB$:SC%=0:NM!=0
5010 K%=LEN(N$)-1
5020 IFK%<1THENRETURN
5030 A$=RIGHT$(N$,1)
5040 IFA$<>"h"THENRETURN
5050 N$=LEFT$(N$,K%)
5060 FORI%=1TOK%
5080 A$=MID$(N$,I%,1)
5090 X%=ASC(A$)
5100 IFX%>=48ANDX%<=57GOTO5130
5110 IFX%>=97ANDX%<=102GOTO5140
5120 RETURN
5130 X%=X%-48:GOTO5150
5140 X%=X%-87
5150 NM!=NM!*16
5160 NM!=NM!+X%
5170 NEXT
5180 SC%=1:RETURN
8000 A%=ASC(CC$):SC%=1
8040 IFA%>=97ANDA%<=122THENRETURN
8050 SC%=0:RETURN
8100 A%=ASC(CC$):SC%=1
8120 IFA%>=48ANDA%<=57THENRETURN
8130 SC%=0:RETURN
8200 GOSUB8000
8210 IFSC%=1THENRETURN
8220 GOTO8100
8300 SC%=1
8305 IFBF%<>0THENGOTO8345
8306 NL%=0
8310 IFEOF(1)THENGOTO200
8320 CB$=INPUT$(1,1)
8322 C%=ASC(CB$)
8324 IFC%<>10THENRETURN
8326 NL%=1:CL!=CL!+1:RETURN
8345 BF%=0:RETURN
8400 SC%=1:A%=ASC(CC$)
8420 IFA%<=32THENRETURN
8430 SC%=0:RETURN
9000 TK$=""
9010 GOSUB8300
9015 IFNL%<>0THENGOTO9430
9030 CC$=CB$
9040 GOSUB8400
9060 IFSC%<>0THENGOTO9010
9070 IFCB$<>";"THENGOTO9200
9100 GOSUB8300
9105 IFNL%<>0THENGOTO9430
9150 GOTO9100
9200 IFCB$<>","THENGOTO9220
9210 TK$=CB$:RETURN
9220 CC$=CB$
9222 GOSUB8200
9230 IFSC%=0THENGOTO200
9231 BF%=1
9240 GOSUB 8300
9245 IFNL%<>0THENGOTO9440
9250 CC$=CB$
9260 GOSUB8200
9270 IFSC%=0THENGOTO9300
9280 TK$=TK$+CB$
9290 GOTO9240
9300 IFCB$<>":"THENGOTO9340
9320 TK$=TK$+CB$
9330 RETURN
9340 IFCB$<>","ANDCB$<>";"THENRETURN
9350 BF%=1
9360 RETURN
9380 GOSUB8400
9390 IFSC%=0THENGOTO200
9430 TK$=CB$:RETURN
9440 BF%=1:RETURN
9700 GOSUB9000
9710 IFNL%<>0ORTK$<>","THENGOTO200
9720 RETURN
10000 CL!=1:BF%=0
10010 GOTO10050
10030 GOSUB9000
10040 IFNL%=0THENGOTO200
10050 GOSUB9000
10055 IFNL%<>0THENGOTO10050
10060 TT$=TK$:SS$=TT$:MP$=A1$:GOSUB3000
10070 IFSC%=0THENGOTO10100
10080 BB!=NM!:GOSUB2800
10090 GOTO10030
10100 SS$=TT$:MP$=A2$:GOSUB3000
10110 IFSC%=0THENGOTO10150
10120 OC!=NM!:MP$=B2$:GOSUB3200
10130 BB!=OC!:GOSUB2800
10140 GOTO10030
10150 SS$=TT$:MP$=A3$:GOSUB3000
10160 IFSC%=0THENGOTO10200
10170 OC!=NM!:MP$=B3$:GOSUB3200
10180 GOTO10130
10200 SS$=TT$:MP$=A4$:GOSUB3000
10210 IFSC%=0THENGOTO10250
10220 OC!=NM!:MP$=B4$:GOSUB3200
10230 GOTO10130
10250 SS$=TT$:MP$=A5$:GOSUB3000
10260 IFSC%=0THENGOTO10300
10270 BB!=NM!:GOSUB2800
10280 GOSUB2300
10290 GOTO10030
10300 SS$=TT$:MP$=A6$:GOSUB3000
10310 IFSC%=0THENGOTO10350
10320 BB!=NM!:GOSUB2800
10330 GOSUB2500
10340 GOTO10030
10350 IFTT$<>"lxi"THENGOTO10400
10360 OC!=1:MP$=B4$:GOSUB3200
10370 BB!=OC!:GOSUB2800
10375 GOSUB9700
10380 GOSUB2500
10390 GOTO10030
10400 IFTT$<>"mov"THENGOTO10450
10410 OC!=64:MP$=B3$:GOSUB3200
10415 GOSUB9700
10420 MP$=B2$:GOSUB3200
10430 GOTO10130
10450 IFTT$<>"mvi"THENGOTO10500
10460 OC!=6:MP$=B3$:GOSUB3200
10470 BB!=OC!:GOSUB2800
10475 GOSUB9700
10480 GOSUB2300
10490 GOTO10030
10500 IFTT$<>"rst"THENGOTO10550
10510 OC!=199:GOSUB2700
10520 OC!=OC!+8*NM!
10530 GOTO10130
10550 IFTT$<>"org"THENGOTO10595
10560 GOSUB2700
10570 PC!=NM!
10590 GOTO10030
10595 A$=RIGHT$(TT$,1):IFA$=":"THENGOTO11110
10600 A$=LEFT$(TT$,1):SS$=MID$(TT$,2):MP$=B5$
10610 IFA$<>"r"THENGOTO10650
10620 OC!=192:TK$=SS$:GOSUB3230
10630 GOTO10130
10650 IFA$<>"c"THENGOTO10700
10660 OC!=196:TK$=SS$:GOSUB3230
10670 BB!=OC!:GOSUB2800
10680 GOSUB2500
10690 GOTO10030
10700 IFA$<>"j"THENGOTO10750
10710 OC!=194:TK$=SS$:GOSUB3230
10720 GOTO10670
10750 IFTT$="end"THENRETURN
10760 IFA$<>"d"THENGOTO200
10770 B$=MID$(TT$,2)
10800 IFB$<>"s"THENGOTO10890
10810 GOSUB9000
10820 IFNL%<>0THENGOTO200
10830 CC$=LEFT$(TK$,1):GOSUB8100
10840 IFSC%=0THENGOTO200
10850 NB$=TK$:GOSUB5000
10860 IFSC%=0THENGOTO200
10870 PC!=PC!+NM!
10880 GOTO10030
10890 IFB$<>"b"THENGOTO10950
10900 GOSUB2300
10910 GOSUB9000
10920 IFNL%<>0THENGOTO10050
10930 IFTK$<>","THENGOTO200
10940 GOTO10900
10950 IFB$<>"w"THENGOTO200
10960 GOSUB2500
10970 GOSUB9000
10980 IFNL%<>0THENGOTO10050
10990 IFTK$<>","THENGOTO200
11000 GOTO10960
11110 IF PS%=2 THEN GOTO10050
11120 B%=LEN(TT$)-1:A$=LEFT$(TT$,B%):LB$="."+A$+"."+STR$(PC!)+LB$
11130 GOTO10050
Here is the documentation:
asm.ba contains BASIC code for an assembler for the model 100. This is a
very limited assembler which is meant only to be used to create a better
assembler written in assembly.
Assembly reference: 8080/8085 ASSEMBLY LANGUAGE PROGRAMMING MANUAL
1977,1978 Intel Corporation
This assembler only understands lowercase.
It understands only the following:
- usual 8085 opcodes
- numbers in hexadecimal only
- these must begin with a decimal digit and terminate with 'h', e.g.:
0ab43h
- the usual operands a b c d e h l m sp psw
- labels
- immediate operands may be labels or hex numbers only
- assembler directives:
- org
- end -- everything after "end" in the file is ignored
-- this is required
- db -- byte data as hex numbers only
- dw -- word data as hex numbers only
- ds -- note the # bytes may only be given as a number - not a label
- comments -- from ';' to the end of the line
The assembly code is converted to machine code directly in the model 100 RAM.
You could then use the BASIC keyword SAVEM to put the machine code in a file.
There are errors that this assembler won't catch; try not to make any.
*** Program Documentation ***
Before running the assembler:
- Your input file must start with an org directive.
- The assembler will only write to RAM from HIMEM to MAXRAM-1, so you need to
make sure your program will fit in that space. See documentation on the BASIC
CLEAR keyword for help with this.
** 1000 main
The assembler makes two passes of the file. The first pass makes sure that all
writes to RAM will be within the range from HIMEM to MAXRAM-1, and determines
the values of all the labels. The second pass writes the program to RAM.
The main program starts at line 1000, and calls the subroutine at 10000 once
for each pass.
** Variables:
FN$ input filename
PC! next location in RAM to write ('program counter')
PS% pass number
LM! lowest RAM address changed
HM! highest RAM address changed
CL! current line in input file
SC% used for boolean return values from subroutines
NL% boolean indicating whether a newline was read from input file
TK$, TT$ tokens read from input file
OC! instruction opcode
** 10000 single pass
The subroutine at line 10000 handles one pass. It is a big loop which reads
and assembles one instruction or directive at a time. The following cases
are treated within the loop:
line number case
10060 all instructions listed in A1$
10100 all instructions listed in A2$
10150 all instructions listed in A3$
10200 all instructions listed in A4$
10250 all instructions listed in A5$
10300 all instructions listed in A6$
10350 lxi instruction
10400 mov instruction
10450 mvi instruction
10500 rst instruction
10550 org assembler directive
10595 labels
10610 all conditional return instructions
10650 all conditional call instructions
10700 all conditional jump instructions
10750 end assembler directive
10800 ds assembler directive
10890 db assembler directive
10950 dw assembler directive
** 2000 init
The subroutine at line 2000 sets up some string variables before the first pass.
** 9000 get token
The subroutine at line 9000 gets the next token from the input file. It skips
whitespace and comments.
Recognized tokens are:
- a newline
- a comma
- a string of alphanumeric characters followed immediately by a colon
- a string of alphanumeric characters
If the end of the file is reached before any token, TK$="" upon return;
else if the token was a newline, NL%=1 upon return;
else TK$ contains the token found.
** 5000 hex string to number
The subroutine at line 5000 attempts to read a hexadecimal number in NB$. The
number must be in the range 0h to 0ffffh. If successful, upon return SC%=1
and NM! contains the number read. Otherwise, SC%=0.
** 8000 isalpha
The subroutine at line 8000 expects a single-character string in CC$. It checks
whether the character is a lowercase letter: if so it returns with SC%=1, else
it returns with SC%=0.
** 8100 isnum
The subroutine at line 8100 expects a single-character string in CC$. It checks
whether the character is a decimal digit: if so it returns with SC%=1, else
it returns with SC%=0.
** 8200 isalphanum
The subroutine at line 8200 expects a single-character string in CC$. It checks
whether the character is a lowercase letter or a decimal digit: if so it
returns with SC%=1, else it returns with SC%=0.
** 8300 get next char
BF%=0:
The subroutine at line 8300 reads the next character from the input file, which
must not yet be at eof. The character is put in CB$ and, if the character is
newline, NL% is set to 1, else NL% is set to 0. Also, whenever newline is
read, the variable CL! is incremented.
BF%=1:
The subroutine sets BF% back to 0, and returns whatever it returned the last
time it was called. Setting BF% to 1 before calling basically "unreads" the
last character read from the input file.
** 8400 iswhitespace
The subroutine at line 8400 expects a single-character string in CC$. It checks
whether the character is whitespace: if so it returns with SC%=1, if
not it returns with SC%=0. Here we consider any character with ASCII code
<= 20 to be whitespace.
** 2300 get imm1
The subroutine at line 2300 reads the next token from the input file, expecting
it to be a valid one-byte immediate operand (either a hex number or label, with
value in range 0h to 0ffh). The value of this byte is written to RAM at the
current PC! location, and the PC! is incremented.
** 2500 get imm2
The subroutine at line 2500 reads the next token from the input file, expecting
it to be a valid two-byte immediate operand (either a hex number or label, with
value in range 0h to 0ffffh). The value of this word is written to RAM at the
current PC! location, and the PC! is increased by 2.
** 2700 get number
The subroutine at line 2700 reads the next token from the input file, expecting
it to be a valid immediate operand (either a hex number or label, with value in
range 0h to 0ffffh). The token read is returned in TK$ and the value of the
operand is returned in NM! Note that if an undefined label is read, this is
allowed during the first pass but not the second pass.
** 2800 poke
The subroutine at line 2800 expects BB! to contain a number between 0h and 0ffh,
and PC! to contain the address in RAM where BB! should be written. During the
first pass, this subroutine just keeps track of the range of RAM to be written
(in LM! and HM!). During the second pass, this subroutine actually writes to
RAM.
I needed a way to associate string "keys" with values. I did this
with 'MAP' strings in the form ".key1.val1.key2.val2... .keyn.valn".
Every key must start with a lowercase letter, and the values must be
decimal numbers.
** 3000 map lookup
The subroutine at line 3000 looks up the value associated with key SS$ in MAP
MP$. If the key is found, the value is put in NM! and the subroutine returns
with SC%=1. Otherwise returns with SC%=0.
** 3200 add field into opcode
The subroutine at line 3200 gets the next token from the input file and uses
it as a key to look up in MAP MP$. The value found is added into OC!
** MAPS
A1$ contains opcodes for instructions which take no operands
A2$ contains opcodes which take one operand which is the name of a register,
which is encoded into the opcode by adding the value in B2$ associated
with the register into the opcode
A3$ contains opcodes which take one operand which is the name of a register,
which is encoded into the opcode by adding the value in B3$ associated
with the register into the opcode
A4$ contains opcodes which take one operand which is the name of a register pair,
which is encoded into the opcode by adding the value in B4$ associated
with the register pair into the opcode
A5$ contains opcodes which take a one-byte immediate operand
A6$ contains opcodes which take a two-byte immediate operand
B2$ see A2$
B3$ see A3$
B4$ see A4$
B5$ for rXX, jXX, cXX instructions, XX being the condition code, the condition
code is encoded into the opcode as bits 5, 4, 3 of the value associated
with the condition code in B5$
LB$ holds the values of the labels
-
First Update
08/15/2014 at 02:55 • 0 commentsTo the person who gave me my first skull: thanks!
I'll be adding weblinks to some sites with useful information for Model 100 programmers. Among other things you will find other, better assemblers; I'm reinventing the wheel just for fun here.
I have some files to share (the BASIC assembler is working!) and will be adding them as soon as I figure out how that is done...
I started out programming on my Model 100, but it has only 8K of RAM. 8K is not enough for my BASIC assembler, so I've switched to the Virtual T emulator, which is a really useful tool.