; ----------------------------------------------------------------------
; CamelForth for the Texas Instruments MSP430
; (c) 2009 Bradford J. Rodriguez.
;
; This program is free software; you can redistribute it and/or modify
; it 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 for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see .
;
; Commercial inquiries should be directed to the author at
; 115 First St., #105, Collingwood, Ontario L9Y 4W3 Canada
; or via email to bj@camelforth.com
; ----------------------------------------------------------------------
; core430.s43 - Machine Language Primitives - MSP430G2553
; mk version
; ----------------------------------------------------------------------
; Revision History
; 1 mar 09 bjr - changed Flash write and erase primitives to correctly
; write RAM outside Info Flash and Main Flash address limits.
#include "msp430.h" ; #define controlled include file
#include "se-CF430G2553forth.h" ; header macros and register defs
EXTERN UP,UAREA,PADAREA,LSTACK,PSTACK,RSTACK
EXTERN TIBAREA,RAMDICT,ROMDICT
EXTERN TIB_SIZE,UAREA_SIZE,nullirq
RSEG CODE ; place program in 'CODE' segment
link SET 0 ; initial dictionary link
version:
DB (verend-ver0)
ver0: DB '4E4th-se v0.34 ',__date__,'|'
EVEN
verend:
; ----------------------------------------------------------------------
; INTERPRETER LOGIC
; ITC NEXT is defined as
; MOV @IP+,W ; 2 fetch word address into W
; MOV @W+,PC ; 2 fetch code address into PC, W=PFA
;C EXECUTE i*x xt -- j*x execute Forth word
;C at 'xt'
HEADER EXECUTE,7,'execute',DOCODE
MOV TOS,W ; 1 put word address into W
MOV @PSP+,TOS ; 2 fetch new TOS
MOV @W+,PC ; 2 fetch code address into PC, W=PFA
;Z lit -- x fetch inline literal to stack
; This is the primtive compiled by LITERAL.
HEADER lit,3,'lit',DOCODE
SUB #2,PSP ; 1 push old TOS..
MOV TOS,0(PSP) ; 4 ..onto stack
MOV @IP+,TOS ; 2 fetch new TOS value
NEXT ; 4
;C EXIT -- exit a colon definition
HEADER EXIT,4,'exit',DOCODE
MOV @RSP+,IP ; 2 pop old IP from return stack
NEXT ; 4
; ----------------------------------------------------------------------
; DEFINING WORDS - ROMable ITC model
; DOCOLON enters a new high-level thread (colon definition.)
; (internal code fragment, not a Forth word)
PUBLIC DOCOLON
DOCOLON:
PUSH IP ; 3 save old IP on return stack
MOV W,IP ; 1 set new IP to PFA
NEXT ; 4
;C VARIABLE -- define a Forth VARIABLE
; CREATE CELL ALLOT ;
; Action of ROMable variable is the same as CREATE; it builds a
; constant holding the RAM address. See CREATE in hilvl430.s43.
HEADER VARIABLE,8,'variable',DOCOLON
DW CREATE,CELL,ALLOT,EXIT
;C CONSTANT -- define a Forth constant
; (machine code fragment)
; Note that the constant is stored in Code space.
HEADER CONSTANT,8,'constant',DOCOLON
DW BUILDS,ICOMMA,XDOES
; DOCON, code action of CONSTANT,
; entered with W=Parameter Field Adrs
; This is also the action of VARIABLE (Harvard model)
; This is also the action of CREATE (Harvard model)
PUBLIC DOCON
PUBLIC docreate
PUBLIC DOVAR
docreate: ; -- a-addr ; ROMable CREATE fetches address from PFA
DOVAR: ; -- a-addr ; ROMable VARIABLE fetches address from PFA
DOCON: ; -- x ; CONSTANT fetches cell from PFA to TOS
SUB #2,PSP ; make room on stack
MOV TOS,0(PSP)
MOV @W,TOS ; fetch from parameter field to TOS
NEXT
; DOCREATE's action is for a table in RAM.
; DOROM is the code action for a table in ROM;
; it returns the address of the parameter field.
PUBLIC DOROM
DOROM: ; -- a-addr ; Table in ROM: get PFA into TOS
SUB #2,PSP
MOV TOS,0(PSP)
MOV W,TOS
NEXT
;Z USER n -- define user variable 'n'
; (machine code fragment) Flashable model
HEADER USER,4,'user',DOCOLON
DW BUILDS,ICOMMA,XDOES
PUBLIC DOUSER
DOUSER: ; -- a-addr ; add constant to User Pointer, result in TOS
SUB #2,PSP
MOV TOS,0(PSP)
MOV @W,TOS
ADD &UP,TOS
NEXT
; DOALIAS used to build a word which performs the action of
; another word. Its action is to fetch the "alias" CFA from
; the parameter field, and execute that, e.g. DOES> I@ EXECUTE ;
; This is currently used only within the Forth kernel.
PUBLIC DOALIAS
DOALIAS: ; -- ; fetch CFA of word to execute
MOV @W,W ; 2 fetch from parameter field to W
MOV @W+,PC ; 2 fetch code address into PC, W=PFA
; DODOES is the code action of a DOES> clause. For ITC Forth:
; defined word: CFA: doescode
; PFA: parameter field
;
; doescode: MOV #DODOES,PC ; 16-bit direct jump, in two cells
; high-level thread
;
; Note that we use JMP DODOES instead of CALL #DODOES because we can
; efficiently obtain the thread address. DODOES is entered with W=PFA.
; It enters the high-level thread with the address of the parameter
; field on top of stack.
PUBLIC dodoes
dodoes: ; -- a-addr ; 3 for MOV #DODOES,PC
SUB #2,PSP ; 1 make room on stack
MOV TOS,0(PSP) ; 4
MOV W,TOS ; 1 put defined word's PFA in TOS
PUSH IP ; 3 save old IP on return stack
MOV -2(W),IP ; 3 fetch adrs of doescode from defined word
ADD #4,IP ; 1 skip MOV instruction to get thread adrs
NEXT ; 4
; OPTION 1 ; OPTION 2
; MOV #DODOES,PC 3 ; CALL #DODOES 5
; ... ; ...
; PUSH IP 3 ; POP W 2
; MOVE -2(W),IP 3 ; PUSH IP 3
; ADD #4,IP 1 ; MOV W,IP 1
; ----------------------------------------------------------------------
; STACK OPERATIONS
;C DUP x -- x x duplicate top of stack
HEADER DUP,3,'dup',DOCODE
PUSHTOS: SUB #2,PSP ; 1 push old TOS..
MOV TOS,0(PSP) ; 4 ..onto stack
NEXT ; 4
;C ?DUP x -- 0 | x x DUP if nonzero
HEADER QDUP,4,'?dup',DOCODE
CMP #0,TOS ; 1 test for TOS nonzero
JNZ PUSHTOS ; 2
NODUP: NEXT ; 4
;C DROP x -- drop top of stack
HEADER DROP,4,'drop',DOCODE
MOV @PSP+,TOS ; 2
NEXT ; 4
;C SWAP x1 x2 -- x2 x1 swap top two items
HEADER SWAP,4,'swap',DOCODE
MOV @PSP,W ; 2
MOV TOS,0(PSP) ; 4
MOV W,TOS ; 1
NEXT ; 4
;C OVER x1 x2 -- x1 x2 x1 per stack diagram
HEADER OVER,4,'over',DOCODE
MOV @PSP,W ; 2
SUB #2,PSP ; 2
MOV TOS,0(PSP) ; 4
MOV W,TOS ; 1
NEXT ; 4
;C ROT x1 x2 x3 -- x2 x3 x1 per stack diagram
HEADER ROT,3,'rot',DOCODE
MOV @PSP,W ; 2 fetch x2
MOV TOS,0(PSP) ; 4 store x3
MOV 2(PSP),TOS ; 3 fetch x1
MOV W,2(PSP) ; 4 store x2
NEXT ; 4
;X NIP x1 x2 -- x2 per stack diagram
HEADER NIP,3,'nip',DOCODE
ADD #2,PSP ; 1
NEXT ; 4
;C >R x -- R: -- x push to return stack
HEADER TOR,2,'>r',DOCODE
PUSH TOS
MOV @PSP+,TOS
NEXT
;C R> -- x R: x -- pop from return stack
HEADER RFROM,2,'r>',DOCODE
SUB #2,PSP ; 2
MOV TOS,0(PSP) ; 4
MOV @RSP+,TOS
NEXT
;C R@ -- x R: x -- x fetch from rtn stk
HEADER RFETCH,2,'r@',DOCODE
SUB #2,PSP
MOV TOS,0(PSP)
MOV @RSP,TOS
NEXT
;Z SP@ -- a-addr get data stack pointer
HEADER SPFETCH,3,'sp@',DOCODE
SUB #2,PSP
MOV TOS,0(PSP)
MOV PSP,TOS
NEXT
;Z SP! a-addr -- set data stack pointer
HEADER SPSTORE,3,'sp!',DOCODE
MOV TOS,PSP
MOV @PSP+,TOS ; 2
NEXT
;Z RP@ -- a-addr get return stack pointer
HEADER RPFETCH,3,'rp@',DOCODE
SUB #2,PSP
MOV TOS,0(PSP)
MOV RSP,TOS
NEXT
;Z RP! a-addr -- set return stack pointer
HEADER RPSTORE,3,'rp!',DOCODE
MOV TOS,RSP
MOV @PSP+,TOS ; 2
NEXT
;X TUCK x1 x2 -- x2 x1 x2 per stack diagram
HEADER TUCK,4,'tuck',DOCOLON
DC16 SWAP,OVER,EXIT
; ----------------------------------------------------------------------
; MEMORY OPERATIONS
;C @ a-addr -- x fetch cell from memory
HEADER FETCH,1,'@',DOCODE
MOV @TOS,TOS
NEXT
;C ! x a-addr -- store cell in memory
HEADER STORE,1,'!',DOCODE
MOV @PSP+,0(TOS)
MOV @PSP+,TOS
NEXT
;C C@ c-addr -- char fetch char from memory
HEADER CFETCH,2,'c@',DOCODE
MOV.B @TOS,TOS
NEXT
;C C! char c-addr -- store char in memory
HEADER CSTORE,2,'c!',DOCODE
MOV @PSP+,W
MOV.B W,0(TOS)
MOV @PSP+,TOS
NEXT
; FLASH MEMORY OPERATIONS
; Note that an I! or IC! to a RAM address >FLASHSTART will work -- it
; will enable the flash, write the RAM, and then disable the flash.
; An FLERASE to a RAM address will merely clear that one RAM cell.
;Z FLERASE a-addr n --
HEADER FLERASE,7,'flerase',DOCODE
MOV @PSP+,W ; get address in W
ADD W,TOS ; TOS=end adrs (first unerased adrs)
FLE_1:
CMP TOS,W ; adr-end
JC FLE_X ; if no borrow, adr>=end, do not erase
; is it within Main flash?
CMP #FLASHSTART,W ; flash start
JNC FLE_INFO ; if borrow, adrend, check if Info
FLE_INFO: ; is it within Info flash?
CMP #INFOSTART,W
JNC FLE_X ; if borrow, adrend, do not erase
FLE_OK: ; Address is either in Main flash, or in Info flash.
; Segment Erase from flash.
; Assumes ACCVIE = NMIIE = OFIE = 0, watchdog disabled.
; Per section 5.3.2 of MSP430 Family User's Guide
DINT ; Disable interrupts
MOV #FWKEY,&FCTL3 ; Clear LOCK
MOV #FWKEY+ERASE,&FCTL1 ; Enable segment erase
MOV #-1,0(W) ; Dummy write in segment to erase
MOV #FWKEY,&FCTL1 ; Done. Clear erase command.
MOV #FWKEY+LOCK,&FCTL3 ; Done, set LOCK
EINT ; Enable interrupts
; Advance flash pointer by 512 bytes or 128 bytes
; is it within Main flash?
CMP #FLASHSTART,W
JNC FL_INFO ; if borrow, adrend, must be Info
ADD #(MAINSEG-INFOSEG),W
FL_INFO: ADD #INFOSEG,W
JMP FLE_1 ; continue till past end or outside limits
FLE_X: MOV @PSP+,TOS
NEXT
; Program Space (Flash) operators
;Z I! x a-addr -- store cell in Instruction memory
HEADER ISTORE,2,'i!',DOCODE
MOV @PSP+,W ; get data to write
BIT #1,TOS
JNZ IST_X ; if not even address, do not write
CMP @TOS,W
JZ IST_X ; if memory is desired value, do not write
; is it within Main flash?
CMP #FLASHSTART,TOS
JNC IST_INFO ; if borrow, adrend, check if Info
IST_INFO: ; is it within Info flash?
CMP #INFOSTART,TOS
JNC IST_RAM ; if borrow, adrend, assume it's RAM
IST_OK: ; Address is either in Main flash, or in Info flash.
; Byte/word write from flash.
; Assumes location to write is already erased
; Assumes ACCVIE = NMIIE = OFIE = 0, watchdog disabled.
; Per section 5.3.3 of MSP430 Family User's Guide
DINT ; Disable interrupts
MOV #FWKEY,&FCTL3 ; Clear LOCK
MOV #FWKEY+WRT,&FCTL1 ; Enable write
IST_RAM: ; If RAM, jump here to write. FCTL1,FCTL3,EINT are superfluous
MOV W,0(TOS) ; Write word to flash location
MOV #FWKEY,&FCTL1 ; Done. Clear WRT.
MOV #FWKEY+LOCK,&FCTL3 ; Set LOCK
EINT ; Enable interrupts
IST_X: MOV @PSP+,TOS ; pop new TOS
NEXT
;Z IC! x a-addr -- store char in Instruction memory
HEADER ICSTORE,3,'ic!',DOCODE
MOV @PSP+,W ; get data to write
CMP.B @TOS,W
JZ IST_X ; if memory is desired value, do not write
; is it within Main flash?
CMP #FLASHSTART,TOS
JNC ICST_INFO ; if borrow, adrend, check if Info
ICST_INFO: ; is it within Info flash?
CMP #INFOSTART,TOS
JNC ICST_RAM ; if borrow, adrend, assume it's RAM
ICST_OK: ; Address is either in Main flash, or in Info flash.
; Byte/word write from flash.
; Assumes location to write is already erased
; Assumes ACCVIE = NMIIE = OFIE = 0, watchdog disabled.
; Per section 5.3.3 of MSP430 Family User's Guide
DINT ; Disable interrupts
MOV #FWKEY,&FCTL3 ; Clear LOCK
MOV #FWKEY+WRT,&FCTL1 ; Enable write
ICST_RAM: ; If RAM, jump here to write. FCTL1,FCTL3,EINT are superfluous
MOV.B W,0(TOS) ; Write byte to flash location
MOV #FWKEY,&FCTL1 ; Done. Clear WRT.
MOV #FWKEY+LOCK,&FCTL3 ; Set LOCK
EINT ; Enable interrupts
JMP IST_X
;Z I@ a-addr -- x fetch cell from Instruction memory
HEADER IFETCH,2,'i@',FETCH+2
;Z IC@ a-addr -- x fetch char from Instruction memory
HEADER ICFETCH,3,'ic@',CFETCH+2
;Z D->I c-addr1 c-addr2 u -- move Data->Code
; Block move from Data space to Code space. Flashable.
; For the MSP430, this uses a "smart" algorithm that uses word writes,
; rather than byte writes, whenever possible. Note that byte reads
; are used for the source, so it need not be aligned.
HEADER DTOI,4,'d->i',DOCODE
MOV @PSP+,W ; dest adrs
MOV @PSP+,X ; src adrs
CMP #0,TOS
JZ DTOI_X
DTOI_LOOP: ; Begin flash write sequence
DINT ; Disable interrupts
MOV #FWKEY,&FCTL3 ; Clear LOCK
MOV #FWKEY+WRT,&FCTL1 ; Enable write
; If length is 1, or dest. address is odd, do a byte write.
; Else, do a word write.
CMP #1,TOS
JZ DTOI_BYTE
BIT #1,W
JNZ DTOI_BYTE
DTOI_WORD: MOV.B @X+,Y ; get low byte of word
MOV.B @X+,Q ; get high byte of word
SWPB Q
BIS Q,Y ; merge bytes
MOV.W Y,0(W) ; write byte to dest
ADD #2,W
SUB #1,TOS ; another 1 will be subtracted below
JMP DTOI_END
DTOI_BYTE: MOV.B @X+,0(W) ; copy byte from src to dest
ADD #1,W
DTOI_END: ; End flash write sequence
MOV #FWKEY,&FCTL1 ; Done. Clear WRT.
MOV #FWKEY+LOCK,&FCTL3 ; Set LOCK
EINT ; Enable interrupts
SUB #1,TOS
JNZ DTOI_LOOP
DTOI_X: MOV @PSP+,TOS ; pop new TOS
NEXT
; ----------------------------------------------------------------------
; ARITHMETIC OPERATIONS
;C + n1/u1 n2/u2 -- n3/u3 add n1+n2
HEADER PLUS,1,'+',DOCODE
ADD @PSP+,TOS
NEXT
;C +! n/u a-addr -- add cell to memory
HEADER PLUSSTORE,2,'+!',DOCODE
ADD @PSP+,0(TOS)
MOV @PSP+,TOS
NEXT
;X M+ d n -- d add single to double
HEADER MPLUS,2,'m+',DOCODE
ADD TOS,2(PSP)
ADDC #0,0(PSP)
MOV @PSP+,TOS
NEXT
;C - n1/u1 n2/u2 -- n3/u3 subtract n1-n2
HEADER MINUS,1,'-',DOCODE
MOV @PSP+,W
SUB TOS,W
MOV W,TOS
NEXT
;C AND x1 x2 -- x3 logical AND
HEADER ANDD,3,'and',DOCODE
AND @PSP+,TOS
NEXT
;C OR x1 x2 -- x3 logical OR
HEADER ORR,2,'or',DOCODE
BIS @PSP+,TOS
NEXT
;C XOR x1 x2 -- x3 logical XOR
HEADER XORR,3,'xor',DOCODE
XOR @PSP+,TOS
NEXT
;C INVERT x1 -- x2 bitwise inversion
HEADER INVERT,6,'invert',DOCODE
XOR #-1,TOS
NEXT
;C NEGATE x1 -- x2 two's complement
HEADER NEGATE,6,'negate',DOCODE
XOR #-1,TOS
ADD #1,TOS
NEXT
;C 1+ n1/u1 -- n2/u2 add 1 to TOS
HEADER ONEPLUS,2,'1+',DOCODE
ADD #1,TOS
NEXT
;C 1- n1/u1 -- n2/u2 subtract 1 from TOS
HEADER ONEMINUS,2,'1-',DOCODE
SUB #1,TOS
NEXT
;Z >< x1 -- x2 swap bytes (not ANSI)
HEADER SWAPBYTES,2,'><',DOCODE
SWPB TOS
NEXT
;C 2* x1 -- x2 arithmetic left shift
HEADER TWOSTAR,2,'2*',DOCODE
ADD TOS,TOS
NEXT
;C 2/ x1 -- x2 arithmetic right shift
HEADER TWOSLASH,2,'2/',DOCODE
RRA TOS
NEXT
;C LSHIFT x1 u -- x2 logical L shift u places
HEADER LSHIFT,6,'lshift',DOCODE
MOV @PSP+,W
AND #1Fh,TOS ; no need to shift more than 16
JZ LSH_X
LSH_1: ADD W,W
SUB #1,TOS
JNZ LSH_1
LSH_X: MOV W,TOS
NEXT
;C RSHIFT x1 u -- x2 logical R shift u places
HEADER RSHIFT,6,'rshift',DOCODE
MOV @PSP+,W
AND #1Fh,TOS ; no need to shift more than 16
JZ RSH_X
RSH_1: CLRC
RRC W
SUB #1,TOS
JNZ RSH_1
RSH_X: MOV W,TOS
NEXT
; ----------------------------------------------------------------------
; COMPARISON OPERATIONS
;C 0= n/u -- flag return true if TOS=0
HEADER ZEROEQUAL,2,'0=',DOCODE
SUB #1,TOS ; borrow (clear cy) if TOS was 0
SUBC TOS,TOS ; TOS=-1 if borrow was set
NEXT
;C 0< n -- flag true if TOS negative
HEADER ZEROLESS,2,'0<',DOCODE
ADD TOS,TOS ; set cy if TOS negative
SUBC TOS,TOS ; TOS=-1 if carry was clear
XOR #-1,TOS ; TOS=-1 if carry was set
NEXT
;C = x1 x2 -- flag test x1=x2
HEADER EQUAL,1,'=',DOCODE
MOV @PSP+,W
SUB TOS,W ; x1-x2 in W, flags set
JZ TOSTRUE
TOSFALSE: MOV #0,TOS
NEXT
;X <> x1 x2 -- flag test not eq (not ANSI)
HEADER NOTEQUAL,2,'<>',DOCOLON
DW EQUAL,ZEROEQUAL,EXIT
;C < n1 n2 -- flag test n1 n1 n2 -- flag test n1>n2, signed
HEADER GREATER,1,'>',DOCOLON
DW SWAP,LESS,EXIT
;C U< u1 u2 -- flag test u1 u1 u2 -- flag u1>u2 unsgd (not ANSI)
HEADER UGREATER,2,'u>',DOCOLON
DW SWAP,ULESS,EXIT
; ----------------------------------------------------------------------
; LOOP AND BRANCH OPERATIONS
; These use relative branch addresses: a branch is ADD @IP,IP
;Z branch -- branch always
HEADER bran,6,'branch',DOCODE
dobran: ADD @IP,IP ; 2
NEXT ; 4
;Z ?branch x -- branch if TOS zero
HEADER qbran,7,'?branch',DOCODE
ADD #0,TOS ; 1 test TOS value
MOV @PSP+,TOS ; 2 pop new TOS value (doesn't change flags)
JZ dobran ; 2 if TOS was zero, take the branch
ADD #2,IP ; 1 else skip the branch destination
NEXT ; 4
;Z (do) n1|u1 n2|u2 -- R: -- sys1 sys2
;Z run-time code for DO
; '83 and ANSI standard loops terminate when the boundary of
; limit-1 and limit is crossed, in either direction. This can
; be conveniently implemented by making the limit 8000h, so that
; arithmetic overflow logic can detect crossing. I learned this
; trick from Laxen & Perry F83.
; fudge factor = 8000h-limit, to be added to the start value.
HEADER xdo,4,'(do)',DOCODE
SUB #4,RSP ; push old loop values on return stack
MOV LIMIT,2(RSP)
MOV INDEX,0(RSP)
MOV #8000h,LIMIT ; compute 8000h-limit "fudge factor"
SUB @PSP+,LIMIT
MOV TOS,INDEX ; loop ctr = index+fudge
ADD LIMIT,INDEX
MOV @PSP+,TOS ; pop new TOS
NEXT
;Z (loop) R: sys1 sys2 -- | sys1 sys2
;Z run-time code for LOOP
; Add 1 to the loop index. If loop terminates, clean up the
; return stack and skip the branch. Else take the inline branch.
; Note that LOOP terminates when index=8000h.
HEADER xloop,6,'(loop)',DOCODE
ADD #1,INDEX
BIT #100h,SR ; is overflow bit set?
JZ dobran ; no overflow = loop
ADD #2,IP ; overflow = loop done, skip branch ofs
MOV @RSP+,INDEX ; restore old loop values
MOV @RSP+,LIMIT
NEXT
;Z (+loop) n -- R: sys1 sys2 -- | sys1 sys2
;Z run-time code for +LOOP
; Add n to the loop index. If loop terminates, clean up the
; return stack and skip the branch. Else take the inline branch.
HEADER xplusloop,7,'(+loop)',DOCODE
ADD TOS,INDEX
MOV @PSP+,TOS ; get new TOS, doesn't change flags
BIT #100h,SR ; is overflow bit set?
JZ dobran ; no overflow = loop
ADD #2,IP ; overflow = loop done, skip branch ofs
MOV @RSP+,INDEX ; restore old loop values
MOV @RSP+,LIMIT
NEXT
;C I -- n R: sys1 sys2 -- sys1 sys2
;C get the innermost loop index
HEADER II,1,'i',DOCODE
SUB #2,PSP ; make room in TOS
MOV TOS,0(PSP)
MOV INDEX,TOS ; index = loopctr - fudge
SUB LIMIT,TOS
NEXT
;C J -- n R: 4*sys -- 4*sys
;C get the second loop index
HEADER JJ,1,'j',DOCODE
SUB #2,PSP ; make room in TOS
MOV TOS,0(PSP)
MOV @RSP,TOS ; index = loopctr - fudge
SUB 2(RSP),TOS
NEXT
;C UNLOOP -- R: sys1 sys2 -- drop loop parms
HEADER UNLOOP,6,'unloop',DOCODE
MOV @RSP+,INDEX ; restore old loop values
MOV @RSP+,LIMIT
NEXT
; ----------------------------------------------------------------------
; MULTIPLY AND DIVIDE
;C UM* u1 u2 -- ud unsigned 16x16->32 mult.
HEADER UMSTAR,3,'um*',DOCODE
; IROP1 = TOS register
MOV @PSP,IROP2L ; get u1, leave room on stack
;
; T.I. SIGNED MULTIPLY SUBROUTINE: IROP1 x IROP2L -> IRACM|IRACL
MPYU: CLR IRACL ; 0 -> LSBs RESULT
CLR IRACM ; 0 -> MSBs RESULT
; UNSIGNED MULTIPLY AND ACCUMULATE SUBROUTINE:
; (IROP1 x IROP2L) + IRACM|IRACL -> IRACM|IRACL
MACU: CLR IROP2M ; MSBs MULTIPLIER
MOV #1,IRBT ; BIT TEST REGISTER
L$002: BIT IRBT,IROP1 ; TEST ACTUAL BIT
JZ L$01 ; IF 0: DO NOTHING
ADD IROP2L,IRACL ; IF 1: ADD MULTIPLIER TO RESULT
ADDC IROP2M,IRACM
L$01: RLA IROP2L ; MULTIPLIER x 2
RLC IROP2M
;
RLA IRBT ; NEXT BIT TO TEST
JNC L$002 ; IF BIT IN CARRY: FINISHED
; END T.I. ROUTINE section 5.1.1 of MSP430 Family Application Reports
MOV IRACL,0(PSP) ; low result on stack
MOV IRACM,TOS ; high result in TOS
NEXT
;C UM/MOD ud u1 -- u2 u3 unsigned 32/16->16
HEADER UMSLASHMOD,6,'um/mod',DOCODE
; IROP1 = TOS register
MOV @PSP+,IROP2M ; get ud hi
MOV @PSP,IROP2L ; get ud lo, leave room on stack
;
; T.I. UNSIGNED DIVISION SUBROUTINE 32-BIT BY 16-BIT
; IROP2M|IROP2L : IROP1 -> IRACL REMAINDER IN IROP2M
; RETURN: CARRY = 0: OK CARRY = 1: QUOTIENT > 16 BITS
DIVIDE: CLR IRACL ; CLEAR RESULT
MOV #17,IRBT ; INITIALIZE LOOP COUNTER
DIV1: CMP IROP1,IROP2M ;
JLO DIV2
SUB IROP1,IROP2M
DIV2: RLC IRACL
JC DIV4 ; Error: result > 16 bits
DEC IRBT ; Decrement loop counter
JZ DIV3 ; Is 0: terminate w/o error
RLA IROP2L
RLC IROP2M
JNC DIV1
SUB IROP1,IROP2M
SETC
JMP DIV2
DIV3: CLRC ; No error, C = 0
DIV4: ; Error indication in C
; END T.I. ROUTINE Section 5.1.5 of MSP430 Family Application Reports
MOV IROP2M,0(PSP) ; remainder on stack
MOV IRACL,TOS ; quotient in TOS
NEXT
; ----------------------------------------------------------------------
; BLOCK AND STRING OPERATIONS
;C FILL c-addr u char -- fill memory with char
HEADER FILL,4,'fill',DOCODE
MOV @PSP+,X ; count
MOV @PSP+,W ; address
CMP #0,X
JZ FILL_X
FILL_1: MOV.B TOS,0(W) ; store char in memory
ADD #1,W
SUB #1,X
JNZ FILL_1
FILL_X: MOV @PSP+,TOS ; pop new TOS
NEXT
;X CMOVE c-addr1 c-addr2 u -- move from bottom
; as defined in the ANSI optional String word set
; On byte machines, CMOVE and CMOVE> are logical
; factors of MOVE. They are easy to implement on
; CPUs which have a block-move instruction.
HEADER CMOVE,5,'cmove',DOCODE
MOV @PSP+,W ; dest adrs
MOV @PSP+,X ; src adrs
CMP #0,TOS
JZ CMOVE_X
CMOVE_1: MOV.B @X+,0(W) ; copy byte
ADD #1,W
SUB #1,TOS
JNZ CMOVE_1
CMOVE_X: MOV @PSP+,TOS ; pop new TOS
NEXT
;X CMOVE> c-addr1 c-addr2 u -- move from top
; as defined in the ANSI optional String word set
HEADER CMOVEUP,6,'cmove>',DOCODE
MOV @PSP+,W ; dest adrs
MOV @PSP+,X ; src adrs
CMP #0,TOS
JZ CMOVU_X
ADD TOS,W ; start at end
ADD TOS,X
CMOVU_1: SUB #1,X
SUB #1,W
MOV.B @X,0(W) ; copy byte
SUB #1,TOS
JNZ CMOVU_1
CMOVU_X: MOV @PSP+,TOS ; pop new TOS
NEXT
;Z I->D c-addr1 c-addr2 u -- move Code->Data
; Block move from Code space to Data space.
; On the MSP430, this is the same as CMOVE.
HEADER ITOD,4,'i->d',CMOVE+2
;Z SKIP c-addr u c -- c-addr' u'
;Z skip matching chars
; Although SKIP, SCAN, and S= are perhaps not the ideal factors
; of WORD and FIND, they closely follow the string operations
; available on many CPUs, and so are easy to implement and fast.
HEADER SKIP,4,'skip',DOCODE
MOV @PSP+,X ; get count
MOV @PSP,W ; get address, leave space on stack
CMP #0,X
JZ SKIP_X
SKIP_1: CMP.B @W,TOS ; does character match?
JNZ SKIP_X ; no, we are done
ADD #1,W
SUB #1,X
JNZ SKIP_1
SKIP_X: MOV W,0(PSP) ; store updated address on stack
MOV X,TOS ; updated count to TOS
NEXT
;Z SCAN c-addr u c -- c-addr' u'
;Z find matching char
HEADER SCAN,4,'scan',DOCODE
MOV @PSP+,X ; get count
MOV @PSP,W ; get address, leave space on stack
CMP #0,X
JZ SCAN_X
SCAN_1: CMP.B @W,TOS ; does character match?
JZ SCAN_X ; yes, we are done
ADD #1,W
SUB #1,X
JNZ SCAN_1
SCAN_X: MOV W,0(PSP) ; store updated address on stack
MOV X,TOS ; updated count to TOS
NEXT
;Z S= c-addr1 c-addr2 u -- n string compare
;Z n<0: s10: s1>s2
HEADER SEQUAL,2,'s=',DOCODE
MOV @PSP+,W ; adrs2
MOV @PSP+,X ; adrs1
CMP #0,TOS
JZ SEQU_X
SEQU_1: CMP.B @W+,0(X) ; compare char1-char2
JNZ SMISMATCH
ADD #1,X
SUB #1,TOS
JNZ SEQU_1
; no mismatch found, strings are equal, TOS=0
JMP SEQU_X
; mismatch found, CY clear if borrow set (s10: s1>s2
; For Harvard model, c-addr1 is Data, c-addr2 is Header.
; On MSP430, both use the same fetch instruction, so N= is the same as S=.
HEADER NEQUAL,2,'n=',SEQUAL+2
; ----------------------------------------------------------------------
; TERMINAL I/O
;C EMIT c -- output character to console
HEADER EMIT,4,'emit',DOCODE
EMITLOOP:
BIT.B #UCA0TXIFG,&IFG2
JZ EMITLOOP
MOV.B TOS,&UCA0TXBUF
MOV @PSP+,TOS
NEXT
/*
;C EMIT c -- output character to console
HEADER EMIT,4,'emit',DOCOLON
DW EMITVEC,FETCH,EXECUTE,EXIT
PUBLIC OEMIT
;C OEMIT c -- output character to console
HEADER OEMIT,5,'oemit',DOCODE
EMITLOOP:
BIT.B #UCA0TXIFG,&IFG2
JZ EMITLOOP
MOV.B TOS,&UCA0TXBUF
MOV @PSP+,TOS
NEXT
*/
;C KEY -- c get character from keyboard
HEADER KEY,3,'key',DOCODE
KEYLOOP:
BIT.B #UCA0RXIFG,&IFG2
JZ KEYLOOP
SUB #2,PSP ; 1 push old TOS..
MOV TOS,0(PSP) ; 4 ..onto stack
MOV.B &UCA0RXBUF,TOS ; read character into TOS
donoop:
donext: NEXT
;X KEY? -- f return true if char waiting
HEADER KEYQ,4,'key?',DOCODE
SUB #2,PSP ; 1 push old TOS..
MOV TOS,0(PSP) ; 4 ..onto stack
BIT.B #UCA0RXIFG,&IFG2
JNZ TOSTRUE
JMP TOSFALSE
; ----------------------------------------------------------------------
; We #include the following source files, rather than compiling them
; separately, so that they can inherit the value of 'link'.
#include "se-deps430G2553.s43"
#include "se-hilvl430G2553.s43"
#include "se-LaunchPad.s43"
/*
; DEBUG FORTH EXECUTION
; debug serieal
PUBLIC DEBUGIP
DEBUGIP:
; DW DOTID
DEBUG1:
; DW TASK
DW KEY ; 1@A0 test 1=rot,@=grün,A=beide,0=aus
DW DUP ;,DOTS,CR
DW STORELEDS
; DW COLD
DW EMIT
DW lit,0,qbran
DW DEBUG1-$
DW bran,-2
/*
; debugging only
HEADLESS CREATE,DOCOLON
HEADLESS ALLOT,DOCOLON
HEADLESS BUILDS,DOCOLON
HEADLESS ICOMMA,DOCOLON
HEADLESS XDOES,DOCOLON
HEADLESS IHERE,DOCOLON
HEADLESS IALLOT,DOCOLON
HEADLESS CELL,DOCOLON
HEADLESS PJOUT,DOCOLON
*/
PUBLIC lastword
lastword equ link
; for debug map only:
CF430FRend:
#define CFlength = CF430FRend-CF430FRstart
END