Skip to content

Instantly share code, notes, and snippets.

@dfischer
Forked from monsonite/SIMPL_MSP430ASM_6
Created February 6, 2021 09:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfischer/fd8dc25c6847a2153c2d1effb192b6e8 to your computer and use it in GitHub Desktop.
Save dfischer/fd8dc25c6847a2153c2d1effb192b6e8 to your computer and use it in GitHub Desktop.
SIMPL - a tiny forth like language implemented in under 900 bytes of MSP430 Assembly Language (For MSP430G2553 Launchpad)
;-------------------------------------------------------------------------------
; 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 22nd/23rd 2017
; Loops, I/O, Strings and Delays added
; This version 888 bytes
; SIMPL_430ASM_6
;-------------------------------------------------------------------------------
.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
.endm
pushs .macro ;DUP
decd.w stack
mov.w tos, 0(stack)
.endm;
; Constants
$NEXT .macro
jmp next
; mov @ip+, pc ; fetch code address into PC
.endm
$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.
;-------------------------------------------------------------------------------
; 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
MOV.B R12,R13 ; move the 1st character after the colon to "name" variable in R13
times_32: 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
ADD.W R13,R13 ; Double R13 ; multiply by 16
ADD.W R13,R13 ; Double R13 ; multiply by 32
ADD.W R13,R14 ; Add (32*R13) to the index pointer R14
ADD.W #0x020,R14 ; Add to array pointer 0x0220
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
textEnd: mov.b #0x80,0x0000(R14) ; Put a null terminating (0x80) zero on the end of the buffer
RET
;-------------------------------------------------------------------------------------------------------------------
; 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
; 16 Instructions
number: 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 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 - so 2 x 96 = 192 bytes
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
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 ; /
jmp number ; 0
jmp number ; 1
jmp number ; 2
jmp number ; 3
jmp number ; 4
jmp number ; 5
jmp number ; 6
jmp number ; 7
jmp number ; 8
jmp number ; 9
jmp colon ; :
jmp semi ; ;
jmp less ; <
jmp equal ; =
jmp greater ; >
jmp query ; ?
jmp fetch ; @
jmp alpha ; A
jmp alpha ; B
jmp alpha ; C
jmp alpha ; D
jmp alpha ; E
jmp alpha ; F
jmp alpha ; G
jmp alpha ; H
jmp alpha ; I
jmp alpha ; J
jmp alpha ; K
jmp alpha ; L
jmp alpha ; M
jmp alpha ; N
jmp alpha ; O
jmp alpha ; P
jmp alpha ; Q
jmp alpha ; R
jmp alpha ; S
jmp alpha ; T
jmp alpha ; U
jmp alpha ; V
jmp alpha ; W
jmp alpha ; X
jmp alpha ; Y
jmp alpha ; Z
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 textEval_end ; 0x80 is used as null terminator
;-----------------------------------------------------------------------------------------
; Handle the alpha and lower case chars
alpha: SUB.B #0x0041,R12 ; subtract 65 to remove offset of letter A from original character
MOV.W R12,R13 ; get it into R13 for multiplying
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
ADD.W R13,R13 ; Double R13 ; multiply by 16
ADD.W R13,R13 ; Double R13 ; multiply by 32
ADD.W #0x220,R13 ; Add (32*R13) to the index pointer R14
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
$NEXT
store: mov.b @stack +, 0(tos)
pops
$NEXT
dup: pushs
$NEXT
lit:
$NEXT
swap: mov.w tos, temp0
mov.w @stack, tos
mov.w temp0,0( stack)
$NEXT
over: mov.w tos, temp0
mov.w @stack, tos
mov.w temp0,0( stack)
$NEXT
and: and @stack +, tos
$NEXT
drop: pops
$NEXT
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
mult:
$NEXT
add: add @stack +, tos
$NEXT
push:
$NEXT
sub: sub @stack +, tos
; jmp NEGAT
NEGAT: inv tos
inc tos
$NEXT
pop:
jmp printNum ; go to decimal number print
$NEXT
div:
$NEXT
semi: ; On encountering a semicolon return program control to the next character in the input buffer
MOV.W R15,ip ; restore the ip
$NEXT
query: $NEXT
fetch: mov.b @tos, tos
$NEXT
square_right:
f_slash:
square_left:
curly_right:
curly_left:
underscore: ; Print the enclosed text
print_start:
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
$NEXT
tick: ; tick allows access to the loop counter
MOV.W R8,tos
$NEXT
delete: $NEXT
or: bis @stack +, tos
$NEXT
xor: xor @stack +, tos
$NEXT
inv: inv tos
$NEXT
less: cmp @stack +, tos
jz FALSE
jge TRUE
jmp FALSE
equal: xor @stack +, tos
jnz FALSE
jmp TRUE
greater: cmp @stack +, tos
jge FALSE
jmp TRUE
FALSE: clr tos
$NEXT
TRUE: mov #0x01, tos
$NEXT
;------------------------------------------------------------------------------------------------
;lower case routines
lower_a:
$NEXT
lower_b:
$NEXT
lower_c:
$NEXT
lower_d:
$NEXT
lower_e:
$NEXT
lower_f:
$NEXT
lower_g:
$NEXT
lower_h:
MOV.B #0x0001,&P1OUT ; P1OUT = BIT0 LED1 on
$NEXT
lower_i:
$NEXT
lower_j:
$NEXT
lower_k:
; k allows access to the loop counter variable stored in R8
MOV.W R8,tos
$NEXT
lower_l:
MOV.B #0x0000,&P1OUT ; P1OUT = BIT0 LED1 off
$NEXT
lower_m: ; millisecond delay
MOV.W tos,R10
mS_loop:
mov.w #5232,R9 ; 5232 gives 1mS at 16MHz
uS3_loop: DEC.W R9
JNE uS3_loop
DEC.W R10
JNE mS_loop
$NEXT
lower_n:
$NEXT
lower_o:
$NEXT
lower_p:
JMP printNum
$NEXT
lower_q: mov.b @tos, tos
MOV.B tos,R12
; CALL #uart_putc
BIT.B #2,&IFG2 ; while (!(IFG2&UCA0TXIFG)) // USCI_A0 TX buffer ready?
JEQ (uart_putc)
MOV.B R12,&UCA0TXBUF ; UCA0TXBUF = c; // TX
$NEXT
lower_r:
$NEXT
lower_s:
$NEXT
lower_t:
$NEXT
lower_u: ; 3 microsecond deelay
MOV.W tos,R10
uS_loop:
DEC.W R10
JNE uS_loop
$NEXT
lower_v:
$NEXT
lower_w:
$NEXT
lower_x:
$NEXT
lower_y:
$NEXT
lower_z:
$NEXT
;------------------------------------------------------------------------------------------------
; User Routines
;-------------------------------------------------------------------------------------------------
printNum: ; 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
;-------------------------------------------------------------------------------------------------
MOV.W #10000,R10 ; R10 used as the decimation register
CLR.W R11 ; Use R11 as scratch
CLR.W R13
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
CLR.W R13
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
newline:
MOV.W #0x000A, R12
CALL #uart_putc ; output CR
MOV.W #0x000D, R12
CALL #uart_putc ; output LF
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
;-------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------------------
; Uses R12 to send receive chars via the UART at 115200 baud.
uart_getc: BIT.B #1,&IFG2 ; while (!(IFG2&UCA0RXIFG)) // USCI_A0 RX buffer ready?
JEQ (uart_getc)
MOV.B &UCA0RXBUF,R12 ; return UCA0RXBUF;
RET
uart_putc: BIT.B #2,&IFG2 ; while (!(IFG2&UCA0TXIFG)) // USCI_A0 TX buffer ready?
JEQ (uart_putc)
MOV.B R12,&UCA0TXBUF ; UCA0TXBUF = c; // TX
RET
crlf: MOV.W #0x0A, R12
CALL #uart_putc ; output CR
MOV.W #0x0D, R12
CALL #uart_putc ; output LF
RET
;-------------------------------------------------------------------------------
; Main loop here
;-------------------------------------------------------------------------------
main:
;-------------------------------------------------------------------------------
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 11500baud UART
MOV.B &CALBC1_16MHZ,&BCSCTL1 ;BCSCTL1 = CALBC1_16MHZ; Set DCO
MOV.B &CALDCO_16MHZ,&DCOCTL ;DCOCTL = CALDCO_16MHZ;
SetupP1: bis.b #041h,&P1DIR ;P1.0 P1.6 output as P1.0 and P1.6 are the red+green LEDs
MOV.B #0x0000,&P1OUT ;P1OUT = BIT0 + BIT6; // All LEDs off
uart_init: MOV.B #0x0006,&P1SEL ;Initialise the UART for 115200 baud
MOV.B #0x0006,&P1SEL2 ;P1SEL2 = RXD + TXD;
BIS.B #0x0080,&UCA0CTL1 ;UCA0CTL1 |= UCSSEL_2// SMCLK
MOV.B #0x0011,&UCA0BR0 ;UCA0BR0 = 138 // 16MHz 115200
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
MOV.W #0x4F, R12
CALL #uart_putc ; output "O"
MOV.W #0x4B, R12
CALL #uart_putc ; output "K" ; Print OK
;-------------------------------------------------------------------------------
interpreter:
call #textRead
MOV.W #0x0200,ip ; set ip (instruction pointer) - to start of input buffer in RAM at address 0x0200
jmp next ; get the next instruction
textEval_end: jmp interpreter ; loop around
; Stack Pointer definition
;-------------------------------------------------------------------------------
.global __STACK_END
.sect .stack
;-------------------------------------------------------------------------------
; Interrupt Vectors
;-------------------------------------------------------------------------------
.sect ".reset" ; MSP430 RESET Vector
.short RESET
.end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment