Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
A tiny "Forth-like" interpreted language for MSP430 - in under 800 bytes of codespace
; SIMPL - a very small Forth Inspired Extensible Language
; Implementing the Initialisation, TextTead, TextEval and UART routines in MSP430 assembly language
; A Forth-Like Language in under 1024 bytes
; Ken Boak May June 2017
; Loops, I/O, Strings and Delays added
; Jump table reduced by 36 entries (72 bytes)
; times_32 subroutine further reduces codesize
; This version 782 bytes
; SIMPL_430ASM_13
.cdecls C,LIST,"msp430.h" ; Include device header file
.def RESET ; Export program entry-point to
; make it known to linker.
; Variables
.sect "vars"
.bss parray, 256
.bss x, 2
.bss name, 2
; Using the register model of CH Ting's Direct Thread Model of MSP430 eForth
; CPU registers
; Register Usage
; R0 MSP430 PC Program Counter
; R1 MSP430 SP Stack Pointer
; R2 MSP430 SR Status Register
tos .equ R4
stack .equ R5
ip .equ R6 ; instruction pointer
temp0 .equ R7 ; loop start
temp1 .equ R8 ; loop counter k
temp2 .equ R9 ; millisecond delay
temp3 .equ R10 ; microsecond delay
temp4 .equ R11 ; scratch
instr .equ R12 ; instruction fetched into R12 for decoding
temp5 .equ R13 ; arithmetic / scratch
temp6 .equ R14 ; used for buffer pointer
temp7 .equ R15 ; Return from alpha next IP
; Macros
pops .macro ;DROP
mov.w @stack +, tos
pushs .macro ;DUP
decd.w stack
mov.w tos, 0(stack)
; Constants
$NEXT .macro
jmp next
; mov @ip+, pc ; fetch code address into PC
; $NEST .macro
; .align 2
; call #DOLST ; fetch code address into PC, W = PFA
; .endm
;$CONST .macro
; .align 2
; call #DOCON ; fetch code address into PC, W = PFA
; .endm
;; Assembler constants - not used in this versionb
COMPO .equ 040H ;lexicon compile only bit
IMEDD .equ 080H ;lexicon immediate bit
MASKK .equ 07F1FH ;lexicon bit mask
CELLL .equ 2 ;size of a cell
BASEE .equ 10 ;default radix
VOCSS .equ 8 ;depth of vocabulary stack
BKSPP .equ 8 ;backspace
LF .equ 10 ;line feed
CRR .equ 13 ;carriage return
ERR .equ 27 ;error escape
TIC .equ 39 ;tick
CALLL .equ 012B0H ;NOP CALL opcodes
UPP .equ 200H
DPP .equ 220H
SPP .equ 378H ;data stack
TIBB .equ 380H ;terminal input buffer
RPP .equ 3F8H ;return stacl
CODEE .equ 0C000H ;code dictionary
COLDD .equ 0FFFEH ;cold start vector
EM .equ 0FFFFH ;top of memory
.text ; Assemble into program memory.
.retain ; Override ELF conditional linking
; and retain current section.
.retainrefs ; And retain any sections that have
; references to current section.
; We now come to decoding the characters - where based on the value of the character we perform some action routine
; But first we need to determine whether the characers form part of a number
; these must be decoded separately and put on the stack - using the "number" routine
; Register Usage
; ip - instruction pointer to the current character in the input buffer
; R12 is the accumulator for the number - then stored in location #0x380
; R13 Temporary - use in x10 multipication
; R14
; 17 Instructions
CMP.B #0x19,R13 ; Is it greater than '9'
JHS mod_pc
SUB.W #0x0030,R12 ; subtract 0x30 to get a decimal number
number1: CMP.B #0x0030,0x0000(ip) ; >= '0' Is the next digit a number
JLO endNumber ; break
CMP.B #0x003a,0x0000(ip) ; <= '9'
JHS endNumber ; break
times_10: ; This multipies R12 by 10
ADDC.W R12,R12 ; R12 = 2 * R12
MOV.W R12,R13 ; R13 = 2 * R12
ADDC.W R12,R12 ; R12 = 4 * R12
ADDC.W R12,R12 ; R12 = 8 x R12
ADDC.W R13,R12 ; R12 = 10 x R12
MOV.B @ip+,R14 ; Increment the instruction pointer fetching the next digit
SUB.W #0x0030,R14 ; Subtract 0x30 to make it a decimal between 0 and 9
ADD.W R14, R12 ; Add in the next digit
JMP number1 ; process the next digit
endNumber: MOV.W R12, tos ; Put the number in tos - the top of stack
; JMP next ; process the next character
; -------------------------------------------------------------------------------------------------------------------
; Character is either a primitive or an alpha - so form CALL address
; Restore R14 to start of RAM buffer
; Get the current character location
; If it's a primitive between 0x20 and 0x3F - point to a look-up table and fetch it's code segment address
; If its an Alpha, or character >0x40 calculate it's code address from (char - 65)x32
; Character is in R13 so calculate the destination address
; next fetches the next ascii character instruction from memory, decodes it into a jump address and executes the code
; found at that code address
; Each executed word jumps back to next
; Numbers are treated differenty - they are enummerated and put onto the stack by the number routine
; Now we need to decode the instructions using a jump table
; Jump table uses 2 bytes per instruction - 120 bytes
; Upper case letters and numbers have their own separate decode routines "alpha" and "number"
next: MOV.B @ip+,R12 ; Get the next character from the instruction memory
MOV.W R12,R13 ; Copy into R13 - as needed to decode Jump Address
SUB.W #0x0020,R13 ; subtract 32 to remove offset of space
CMP.B #0x0020,R13 ; Is it greater than ascii 64? Therefor Upper Case alpha
JHS alpha
CMP.B #0x0010,R13 ; Is it greater than 0x10 therefore a number
JHS number
mod_pc: ADD.W R13,R13 ; double it for word address
ADD.W R13,pc ; jump to table entry
jump_table: jmp space ; SP
jmp store ; !
jmp dup ; "
jmp lit ; #
jmp swap ; $
jmp over ; %
jmp and ; &
jmp drop ; '
jmp left_par ; (
jmp right_par ; )
jmp mult ; *
jmp add ; +
jmp push ; ,
jmp sub ; -
jmp pop ; .
jmp div ; /
; -------------------------------------------------------------------------------------------
; This section must be 20 bytes long in order to maintain the integrity of the jump table
OK: MOV.W #0x4F, R12
CALL #uart_putc ; output "O"
MOV.W #0x4B, R12
CALL #uart_putc ; output "K" ; Print OK
; ------------------------------------------------------------------------------------------
jmp colon ; :
jmp semi ; ;
jmp less ; <
jmp equal ; =
jmp greater ; >
jmp query ; ?
jmp fetch ; @
; -----------------------------------------------------------------------------------------------------------------------------
; This section must be 52 bytes long in order to maintain the integrity of the jump table
; UART Routines
uart_getc: BIT.B #1,&IFG2 ; while (!(IFG2&UCA0RXIFG)) // USCI_A0 RX buffer ready?
JEQ (uart_getc)
uart_putc: BIT.B #2,&IFG2 ; while (!(IFG2&UCA0TXIFG)) // USCI_A0 TX buffer ready?
JEQ (uart_putc)
prompt: MOV.W #0x3E, R12
CALL #uart_putc ; output ">"
crlf: MOV.W #0x0A, R12
CALL #uart_putc ; output CR
MOV.W #0x0D, R12
CALL #uart_putc ; output LF
; -----------------------------------------------------------------------------------------------------------------------------
jmp square_left ; [
jmp f_slash ; \ ;
jmp square_right ; ]
jmp xor ; ^
jmp underscore ; _
jmp tick ; `
jmp lower_a ; a
jmp lower_b ; b
jmp lower_c ; c
jmp lower_d ; d
jmp lower_e ; e
jmp lower_f ; f
jmp lower_g ; g
jmp lower_h ; h
jmp lower_i ; i
jmp lower_j ; j
jmp lower_k ; k
jmp lower_l ; l
jmp lower_m ; m
jmp lower_n ; n
jmp lower_o ; o
jmp lower_p ; p
jmp lower_q ; q
jmp lower_r ; r
jmp lower_s ; s
jmp lower_t ; t
jmp lower_u ; u
jmp lower_v ; v
jmp lower_w ; w
jmp lower_x ; x
jmp lower_y ; y
jmp lower_z ; z
jmp curly_left ; {
jmp or ; |
jmp curly_right ; }
jmp inv ; ~
jmp delete ; del
jmp textRead ; Loop back to Interpreter 0x80 is used as null terminator
; Handle the alpha and lower case chars
alpha: CMP.B #0x003A,R13 ; Is it greater than Z ascii 90?
JHS mod_pc ; Then it should be handled by jump table mechanism
call #times_32 ; subtract ascii "A" from R13 and multiply by 32 then add 0x220 offset
MOV.W ip,R15 ; Save the current ip on the return stack R15
; R13 now contains the jump address for the alpha code
MOV.W R13,ip ; instruction pointer
JMP next ; process the next character
; Handle the primitive instructions
space: pushs ; Move a 2nd number onto the stack
store: mov.b @stack +, 0(tos)
swap: mov.w tos, temp0
mov.w @stack, tos
mov.w temp0,0( stack)
over: mov.w @stack, temp0
mov.w temp0, tos
and: and @stack +, tos
drop: pops
left_par: ; code enters here on getting a left parenthesis
MOV.W tos,R8 ; save tos to R8 (R8 is the loop counter k)
MOV.W ip,R7 ; loop-start = ip the current instruction pointer at start of loop
JMP next ; get the next character and execute it
right_par: ; code enters here if instruction it's a right parenthesis
; TST.W R8 ; is loop counter zero
; JEQ next ; terminate loop
DEC.W R8 ; decrement loop counter R8
JEQ next ; terminate loop
MOV.W R7,ip ; set instruction pointer to the start of the loop
JMP next ; go around loop again until loop counter = 0
add: add @stack +, tos
sub: sub @stack +, tos
NEGAT: inv tos
inc tos
semi: ; On encountering a semicolon return program control to the next character in the input buffer
MOV.W R15,ip ; restore the ip
query: $NEXT
fetch: mov.b @tos, tos
underscore: ; Print the enclosed text
MOV.B @ip+,R12 ; Get the next character from the instruction memory
CMP.B #0x005f,R12 ; is it an underscore
jeq print_end
CALL #uart_putc ; send it to uart
jmp print_start
print_end call #crlf ; line feed at end of text string
tick: ; tick allows access to the loop counter
; MOV.W R8,tos
delete: $NEXT
or: bis @stack +, tos
xor: xor @stack +, tos
inv: inv tos
less: cmp @stack +, tos
jge TRUE
equal: xor @stack +, tos
jmp TRUE
greater: cmp @stack +, tos
jmp TRUE
FALSE: clr tos
TRUE: mov #0x01, tos
;lower case routines
MOV.B #0x0001,&P1OUT ; P1OUT = BIT0 LED1 on
; k allows access to the loop counter variable stored in R8
MOV.W R8,tos
MOV.B #0x0000,&P1OUT ; P1OUT = BIT0 LED1 off
lower_m: ; millisecond delay
MOV.W tos,R10
mov.w #5232,R9 ; 5232 gives 1mS at 16MHz
uS3_loop: DEC.W R9
JNE uS3_loop
JNE mS_loop
; Take the 16 bit value in top of stack register and print to terminal as an integer
; do by repeated subtraction of powers of 10
; Uses R10,11,12,13
printNum: MOV.W #10000,R10 ; R10 used as the decimation register
CLR.W R11 ; Use R11 as scratch
MOV.W tos,R12 ; copy the top of stack into R12
CLRC ; clear the carry
sub10K: SUB.W R10,R12
JLO end10K
add10K: ADD.B #1,R11 ; increments the digit count
add_zero: ADD.W R10,R13 ; R13 increases by the decimal value each time
JMP sub10K
end10K: ;TST.W R11 ; If R11 is zero - don't print leading zeros
;JEQ skip_print
do_print: ADD.B #0x30,R11 ; make it an ascii number character
MOV.W R11,R12
CALL #uart_putc ; output character
;JMP dec_stack
skip_print: ;TST.W tos
;JGE do_print
dec_stack: SUB.W R13,tos ; Decrement the stack count by n x 10
CLR.W R11 ; Use R11 as scratch
MOV.W tos,R12
decimate: CMP.W #10000,R10
JEQ use1K
CMP.W #1000,R10
JEQ use100
CMP.W #100,R10
JEQ use10
CMP.W #10,R10
JEQ use1
call #crlf
JMP next
use1K: MOV.W #1000,R10
JMP sub10K
use100: MOV.W #100,R10
JMP sub10K
use10: MOV.W #10,R10
JMP sub10K
use1: MOV.W #1,R10
JMP sub10K
lower_q: mov.b @tos, R12
; MOV.B tos,R12
CALL #uart_putc
lower_u: ; 3 microsecond deelay
MOV.W tos,R10
JNE uS_loop
; User Routines
; Main loop here
RESET: ; mov.w #03E0h,SP ; Initialize stackpointer
mov #RPP, SP ; set up stack
mov #SPP, stack
clr tos
StopWDT: mov.w #WDTPW|WDTHOLD,&WDTCTL ; Stop watchdog timer WDTCTL = WDTPW + WDTHOLD; Stop WDT
OSC_GPIO_init: ; Run the CPU at full 16MHz with 921600 baud UART
; MOV.B #0x0000,&P1OUT ;P1OUT = BIT0 + BIT6; // All LEDs off
SetupP1: bis.b #041h,&P1DIR ;P1.0 P1.6 output as P1.0 and P1.6 are the red+green LEDs
MOV.B #0x0006,&P1SEL ;Initialise the UART for 921600 baud
MOV.B #0x0006,&P1SEL2 ;P1SEL2 = RXD + TXD;
MOV.B #0x0011,&UCA0BR0 ;UCA0BR0 = 138 // 16MHz 921600
CLR.B &UCA0BR1 ;UCA0BR1 = 0 // 16MHz 115200
MOV.B #2,&UCA0MCTL ;UCA0MCTL = UCBRS0 // Modulation UCBRSx = 1
BIC.B #1,&UCA0CTL1 ;UCA0CTL1 &= ~UCSWRST Initialize USCI state machine
; This implements the SIMPL interpreter is MSP430 assembly Language
; textRead
; ------------------------------------------------------------------------------
; Get a character from the UART and store it in the input buffer starting at 0x0200
; Supports immediate mode - where characters are stored at 0x200 and executed directly
; Supports compiled mode - where string starts with a colon and the next character "NAME" is an upper case alpha
; String is stored at 0x200 plus a multiple of 32 byte offset according to the value of "NAME"
; Register Usage
; The input buffer - start is at 0x0200, which is pointed to by R14
; R11 is a counter to ensure that we don't exceed 64 characters in input buffer
; R12 receives the character from the uart_get_c routine and puts in the buffer, pointed to by R14
; R14 is the current character position in the input buffer
; 33 instructions
textRead: MOV.W #0x0200,R14 ; R14 = start of input buffer in RAM
; CLR.B R11 ; i = 0
getChar: CALL #uart_getc ; char ch = uart_getc()
CMP.B #0x000d,R12 ; is it carriage return? 0d
JEQ textEnd
CMP.B #0x000a,R12 ; Is it newline? 0a
JEQ textEnd
CMP.B #0x0020,R12 ; if (ch >= ' ' && ch <= '~')
JLO nonValid
CMP.B #0x007f,R12 ; this looks for 0x80 as null terminator
JHS nonValid
CMP.B #0x003A,R12 ; is it colon? 0x3A
JNE notColon
colon: ; If the input character is a colon
CALL #uart_getc ; get the next character - which is the NAME
call #times_32 ; subtract ascii "A" from R13 and multiply by 32 then add 0x220
; ADD.W R13,R14 ; Add (32*R13) to the index pointer R14
; ADD.W #0x020,R14 ; Add to array pointer 0x0220
MOV.W R13,R14
MOV.B R12,0x0000(R14) ; Store character at RAM buffer indexed by R14
; R14 now contains the destination address
JMP incPointer
notColon: INC.W R14 ; Increment buffer pointer
MOV.B R12,0xffff(R14) ; Store character at RAM buffer indexed by R14
incPointer: ; INC.B R11 ; Increment the input buffer counter ;
nonValid: ;CMP.B #0x003f,R11 ; If input pointer <64 loop back to start
; JLO getChar ; loop back and get next character
jmp getChar
textEnd: mov.b #0x80,0x0000(R14) ; Put a null terminating (0x80) zero on the end of the buffer
MOV.W #0x0200,ip ; set ip (instruction pointer) - to start of input buffer in RAM at address 0x0200
jmp next ; get the next instruction
times_32: MOV.B R12,R13 ; move the 1st character after the colon to "name" variable in R13
SUB.B #0x0041,R13 ; Calculate the destination address - subtract 65 to remove offset of letter A
ADD.W R13,R13 ; Double R13 ; multiply by 2
ADD.W R13,R13 ; Double R13 ; multiply by 4
ADD.W R13,R13 ; Double R13 ; multiply by 8
times_4: ADD.W R13,R13 ; Double R13 ; multiply by 16
ADD.W R13,R13 ; Double R13 ; multiply by 32
ADD.W #0x220,R13 ; Add start address of command RAM (0x0220) to (32*R13)
; Stack Pointer definition
.global __STACK_END
.sect .stack
; Interrupt Vectors
.sect ".reset" ; MSP430 RESET Vector
.short RESET
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment