diff --git a/core/dev/Makefile b/core/dev/Makefile index 7a8d69e..9d6c516 100644 --- a/core/dev/Makefile +++ b/core/dev/Makefile @@ -261,6 +261,13 @@ tests-mcu: --tee ./build/tests-mcu.log ./build/tests-mcu.frt awk -f $(AMFORTH)/tests/results-mcu.awk ./build/tests-mcu.log +## Run tests-mcu.frt through amforth-shell on hardware +tests-sock: + # export EDITOR in your .env file to make the #edit shell directive work. + $(SKIP) -x assembler-with-cpp -I $(AMFORTH) $(AMFORTH)/tests/tests-mcu.frt > ./build/tests-mcu.frt + $(AMFORTH)/tools/socket-shell.py -I $(AMFORTH) --no-error-on-output ./build/tests-mcu.frt + #awk -f $(AMFORTH)/tests/results-mcu.awk ./build/tests-mcu.log + ## Detailed dump of ELF sections for debugging linker issues sections: sec-type sec-addr sec-sum diff --git a/core/words/see.s b/core/words/see.s index c4a07fc..b0f3049 100644 --- a/core/words/see.s +++ b/core/words/see.s @@ -361,7 +361,7 @@ SEEDOTDODOTXT_0001: /* then */ .word XT_DP_RAM .word XT_DP .word XT_MAX - .word XT_LESS + .word XT_ULESS .word XT_DOCONDBRANCH,SEEDOTDODOTXT_0002 /* if */ .word XT_SEEDOTIP .word XT_FETCH diff --git a/rv/mcu/fe310/Makefile b/rv/mcu/fe310/Makefile new file mode 100644 index 0000000..c73da81 --- /dev/null +++ b/rv/mcu/fe310/Makefile @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: GPL-3.0-only + +-include .env + +AMFORTH ?= $(shell git rev-parse --show-toplevel) + +QEMU_RV32 ?= qemu-system-riscv32 + +# Default target to build +TARGET ?= REDFIVE +TARGETS = REDFIVE HIFIVE1 + +CHIPSET=$(TARGET) +MCU_LD := ./$(TARGET).ld +LIB_WCH := ./lib_$(TARGET) +LIB_USER := ./lib_$(TARGET)_user + +ASFLAGS += -march=rv32imac_zicsr_zifencei -mabi=ilp32 + +# Included after various configuration bits are defined (importantly MCU_LD and TARGET) +include $(AMFORTH)/rv/dev/Makefile + +.PRECIOUS: amforth.s + +#? --- QEM --- + +# This is quite different from the -kernel option, which will for the RV 'virt' jump to +# 0x8000_0000 no matter what ENTRY() has in the linker file. The cpu-num=0 sets the PC +# to the value indicated by ENTRY(), which is much more suitable our needs. The commented +# out line gives a hint as to how to set the PC to an absolute address at boot time. + +QEMU := $(QEMU_RV32) -M virt -bios none -device loader,file=build/amforth.elf,cpu-num=0 +# -device loader,addr=0x20010000,cpu-num=0 + +## Start AmForth under QEMU with operator uart connected to stdio +stdio: + $(QEMU) -display none -serial stdio + +## Start AmForth under QEMU with operator uart connected to a pty +pty: + $(QEMU) -nographic -serial pty + +# Add -S if you want QEMU to not start the quest. +## Like pty but allows GDB to attach to it (-s). +debug: + $(QEMU) -nographic -serial pty -s + +## Connect GDB to amforth running on QEMU (must be running with -s, see debug) +gdb: + $(GDBPY) build/amforth.elf --quiet \ + -ex 'target remote :1234' \ + -ex "source tui-full.gdb" + +## Serve AmForth on :4444 (make nc|socshell for client) - logs to /tmp/serial.log +socket: + $(QEMU) -display none \ + -chardev socket,id=sock0,host=127.0.0.1,port=4444,server=on,wait=on,logfile=/tmp/serial.log \ + -serial chardev:sock0 + +## Serve AmForth on :4444 with debug on :1234 - logs to /tmp/serial.log +socbug: + $(QEMU) -display none \ + -chardev socket,id=sock0,host=127.0.0.1,port=4444,server=on,wait=on,logfile=/tmp/serial.log \ + -serial chardev:sock0 -s + +## rlwrap nc client connecting to :4444 +nc: + rlwrap nc localhost 4444 + +## socket-shell as client connecting to :4444 +socshell: + $(AMFORTH)/tools/socket-shell.py --no-error-on-output -i --ignore-error + +read-elf: + $(TC_DIR)/bin/$(CROSS)readelf -h build/amforth.elf + +ifeq ($(strip $(TARGET)),REDFIVE) +QEMU:=$(QEMU_RV32) -M sifive_e,revb=true -bios none -kernel build/amforth.elf +## Upload hex file to the device +upload: + cp build/amforth.hex /Volumes/HiFive +else +QEMU:=$(QEMU_RV32) -M sifive_e -bios none -kernel build/amforth.elf +upload: + echo "no upload for $(TARGET) available" +endif diff --git a/rv/mcu/fe310/amforth.s b/rv/mcu/fe310/amforth.s new file mode 100644 index 0000000..1fc7ade --- /dev/null +++ b/rv/mcu/fe310/amforth.s @@ -0,0 +1,32 @@ +# This is a template to start from. +# Copy it into your mcu/ directory and modify as needed. + +.text + +.include "mcu/qemu/config.inc" # most configuration options are in this file +.include "build-config.inc" + +.print "INFO: using FE310 startup" +.include "fe310.startup" # startup and master_int handler for both C and Forth + +.section amforth, "ax" + +.option push # save option set +.option norvc # don't use compressed +.option norelax # don't relax + +.include "config.inc" +.include "build-config.inc" +.include "rv/macros.inc" +.include "user.inc" + +STARTDICT + +.include "dict_prims.inc" +.include "dict_secs.inc" +.include "dict_env.inc" +.include "dict_mcu.inc" + +ENDDICT + +.option pop # restore option set diff --git a/rv/mcu/fe310/config.inc b/rv/mcu/fe310/config.inc new file mode 100644 index 0000000..87bb975 --- /dev/null +++ b/rv/mcu/fe310/config.inc @@ -0,0 +1,18 @@ + +.include "core/config.inc" + +.equ datastack_size, 32 * cellsize +.equ returnstack_size, 32 * cellsize + +/* TODO: this is made up, needs correcting */ +.equ flash_page, 1*1024 +.equ flash_cell, 1 +.equ flash_erased, 0xFFFFFFFF /* a word in erased flash */ + +.equ rampool_size, 1024*1 + +.equ WANT_DEBUGGER , YES +.equ WANT_IGNORECASE , YES +.equ WANT_SEE , YES +.equ WANT_TRANSPILER , NO + diff --git a/rv/mcu/fe310/dict_mcu.inc b/rv/mcu/fe310/dict_mcu.inc new file mode 100644 index 0000000..8e3f590 --- /dev/null +++ b/rv/mcu/fe310/dict_mcu.inc @@ -0,0 +1,21 @@ + +.include "words/gpio.s" +.include "words/usart.s" + +.if TARGET==HIFIVE1 + .include "words/leds.s" +.else + .include "words/redvled.s" +.endif + +.include "words/mtime.s" + +.include "words/applturnkey.s" + +.include "words/type0.s" + +.include "words/pll.s" +.include "words/qspidiv.s" +.include "words/csr.s" + + diff --git a/rv/mcu/fe310/eval.f b/rv/mcu/fe310/eval.f new file mode 100644 index 0000000..4f46ac9 --- /dev/null +++ b/rv/mcu/fe310/eval.f @@ -0,0 +1,64 @@ +variable strlen +variable str +variable str-buf $20 allot + +: source-string str @ strlen @ dup . ; +: refill-false ( -- false ) false ; + +: (evaluate) + break + ['] source defer@ >r + ['] refill defer@ >r + >in @ >r + 0 >in ! + strlen ! + str ! + ['] source-string is source + ['] interpret catch + r> >in ! + r> is refill + r> is source + throw +; + +\ : (evaluate) \ i*x addr len -- j*y +\ break +\ ['] source defer@ >r \ 1. Save current source implementation +\ >in @ >r \ 2. Save current input offset +\ 0 >in ! \ 3. Reset offset to start of buffer +\ strlen ! \ 4. Store string length +\ str ! \ 5. Store string address +\ ['] source-string is source \ 6. Redirect source to our buffer +\ ['] interpret catch \ 7. Execute interpreter (with error trapping) +\ r> >in ! \ 8. Restore original input offset +\ r> is source \ 9. Restore original source +\ throw \ 10. Re-throw any caught errors +\ ; + +: evaluate + state @ if + postpone (evaluate) + else + (evaluate) + then +; immediate + + +: ww ( addr len -- ) + dup strlen ! \ a n + str-buf swap move \ + str-buf str ! + \ (evaluate) +; + +: (evaluate-test) + strlen ! + str ! + ['] source-string is source + source + ." len=" . + ." addr=" hex. + ['] source is source +; + +\ s" 1 1 +" (evaluate-test) diff --git a/rv/mcu/fe310/fe310.startup b/rv/mcu/fe310/fe310.startup new file mode 100755 index 0000000..436c099 --- /dev/null +++ b/rv/mcu/fe310/fe310.startup @@ -0,0 +1,16 @@ + +.option norvc # no opportunistic use of compressed ... +.option norelax # instructions or other such optimisations + +#la gp, __global_pointer$ + +.global PFA_COLD +.global _start + +.section .init +_start: + la gp, __global_pointer$ + la sp, _eusrstack + + j PFA_COLD + diff --git a/rv/mcu/fe310/hifive1.ld b/rv/mcu/fe310/hifive1.ld new file mode 100644 index 0000000..7296e7e --- /dev/null +++ b/rv/mcu/fe310/hifive1.ld @@ -0,0 +1,20 @@ +OUTPUT_ARCH( "riscv" ) + +ENTRY( _start ) + +/* Not enough memory to support pvalues practically */ +PVFLASH_PAGE = 0; +PVFLASH_CELL = 0; +PVFLASH_ERASED = flash_erased; + +/* !! Memory regions are defined early and their parameters MUST NOT be computed from object file symbols !! */ +MEMORY +{ + /* FLASH(RX) : ORIGIN = 0x20400000, LENGTH = 32K */ + FLASH(RX) : ORIGIN = 0x20400000, LENGTH = 64K + RAM(WAIXL) : ORIGIN = 0x80000000, LENGTH = 16K + /* MUST define the PVFLASH region otherwise it will default to occupying the entire memory space */ + PVFLASH(RW) : ORIGIN = 0 , LENGTH = 0 +} + +INCLUDE amforth32.ld diff --git a/rv/mcu/fe310/readme.md b/rv/mcu/fe310/readme.md new file mode 100644 index 0000000..981992a --- /dev/null +++ b/rv/mcu/fe310/readme.md @@ -0,0 +1,9 @@ +# AmForth for SiFive Hifive1 + +AmForth is published under the GNU Public License v3 (GPL). + +Arduino-compatible development board featuring the SiFive Freedom E310 (FE310) SoC [32-bit RV32IMAC core]. + +# References + +* https://www.sifive.com/boards/hifive1 \ No newline at end of file diff --git a/rv/mcu/fe310/redfive.ld b/rv/mcu/fe310/redfive.ld new file mode 100644 index 0000000..061b060 --- /dev/null +++ b/rv/mcu/fe310/redfive.ld @@ -0,0 +1,20 @@ +OUTPUT_ARCH( "riscv" ) + +ENTRY( _start ) + +/* Not enough memory to support pvalues practically */ +PVFLASH_PAGE = 0; +PVFLASH_CELL = 0; +PVFLASH_ERASED = flash_erased; + +/* !! Memory regions are defined early and their parameters MUST NOT be computed from object file symbols !! */ +MEMORY +{ + /* FLASH(RX) : ORIGIN = 0x20400000, LENGTH = 32K */ + FLASH(RX) : ORIGIN = 0x20010000, LENGTH = 64K + RAM(WAIXL) : ORIGIN = 0x80000000, LENGTH = 16K + /* MUST define the PVFLASH region otherwise it will default to occupying the entire memory space */ + PVFLASH(RW) : ORIGIN = 0 , LENGTH = 0 +} + +INCLUDE amforth32.ld diff --git a/rv/mcu/fe310/words/applturnkey.s b/rv/mcu/fe310/words/applturnkey.s new file mode 100755 index 0000000..f5d145c --- /dev/null +++ b/rv/mcu/fe310/words/applturnkey.s @@ -0,0 +1,19 @@ +COLON "hifive-turnkey", APPLTURNKEY + + .word XT_LED_INIT + .word XT_DECIMAL + +/* setup for 256MHz via PLL with 115200 */ + + .word XT_PLL + .word XT_QSPIDIV + .word XT_INIT_USART + + .word XT_DOT_VER, XT_SPACE + .word XT_ENV_BOARD,XT_TYPE, XT_CR + .word XT_ENV_DOT_BUILD + + .word XT_EXIT +END APPLTURNKEY + + diff --git a/rv/mcu/fe310/words/build-info.tmpl b/rv/mcu/fe310/words/build-info.tmpl new file mode 100644 index 0000000..31e9fd8 --- /dev/null +++ b/rv/mcu/fe310/words/build-info.tmpl @@ -0,0 +1,8 @@ + +COLON "build-info", BUILD_INFO + STRING "%d" +.word XT_EXIT + +COLON "rev-info", REV_INFO + STRING "%r" +.word XT_EXIT diff --git a/rv/mcu/fe310/words/clock.s b/rv/mcu/fe310/words/clock.s new file mode 100644 index 0000000..4472951 --- /dev/null +++ b/rv/mcu/fe310/words/clock.s @@ -0,0 +1,26 @@ + +.equ PRCI_BASE, 0x10008000 + +.equ PRCI_HFROSCCFG , PRCI_BASE + 0x00 +.equ PRCI_HFXOSCCFG , PRCI_BASE + 0x04 +.equ PRCI_PLLCFG , PRCI_BASE + 0x08 +.equ PRCI_PLLDIV , PRCI_BASE + 0x0C +.equ PRCI_PROCMONCFG , PRCI_BASE + 0xF0 + +CODEWORD "clock", CLOCK + + # set up the clock system and make it run + +1:li t0, PRCI_HFXOSCCFG # 0x10008004 + lw t1, 0(t0) + li t2, 0xC0000000 + bne t1, t2, 1b + + # Select crystal as main clock source + + li t0, PRCI_PLLCFG + li t1, 0x00070df1 # 0x00060df1 | (1<<16) | (1<<17) | (1<<18) # Reset value | PLLSEL | PLLREFSEL | PLLBYPASS + sw t1, 0(t0) + + NEXT +END CLOCK diff --git a/rv/mcu/fe310/words/cold.s b/rv/mcu/fe310/words/cold.s new file mode 100644 index 0000000..495c860 --- /dev/null +++ b/rv/mcu/fe310/words/cold.s @@ -0,0 +1,51 @@ + +.equ PRCI_BASE, 0x10008000 + +.equ PRCI_HFROSCCFG , PRCI_BASE + 0x00 +.equ PRCI_HFXOSCCFG , PRCI_BASE + 0x04 +.equ PRCI_PLLCFG , PRCI_BASE + 0x08 +.equ PRCI_PLLDIV , PRCI_BASE + 0x0C +.equ PRCI_PROCMONCFG , PRCI_BASE + 0xF0 + + +CODEWORD "cold", COLD + + # set up the clock system and make it run + +# 1:li x10, PRCI_HFXOSCCFG # 0x10008004 +# lw x11, 0(x10) +# li x12, 0xC0000000 +# bne x11, x12, 1b + +# # Select crystal as main clock source + +# li x10, PRCI_PLLCFG +# li x11, 0x00070df1 # 0x00060df1 | (1<<16) | (1<<17) | (1<<18) # Reset value | PLLSEL | PLLREFSEL | PLLBYPASS +# sw x11, 0(x10) + + # set up the clock system and make it run + +1:li t0, PRCI_HFXOSCCFG # 0x10008004 + lw t1, 0(t0) + li t2, 0xC0000000 + bne t1, t2, 1b + + # Select crystal as main clock source + + li t0, PRCI_PLLCFG + li t1, 0x00070df1 # 0x00060df1 | (1<<16) | (1<<17) | (1<<18) # Reset value | PLLSEL | PLLREFSEL | PLLBYPASS + sw t1, 0(t0) + + # This is the same as in quit, in order to prepare for whatever the user might want to do within "init". + + la s5, RAM_upper_returnstack + la s4, RAM_upper_datastack + +# .ifdef initflash +# call initflash +# .endif + + la s1, XT_WARM + j DO_EXECUTE + +END COLD diff --git a/rv/mcu/fe310/words/constants.s b/rv/mcu/fe310/words/constants.s new file mode 100755 index 0000000..632e743 --- /dev/null +++ b/rv/mcu/fe310/words/constants.s @@ -0,0 +1,44 @@ +# +# platform_regs.inc: FE310-G002 registers as ASM constants +# +# 2021 flabbergast@drak.xyz +# Unlicense: https://unlicense.org/ +# +.equ GPIO_BASE, 0x10012000 +.equ INPUT_VAL, 0x00 +.equ INPUT_EN, 0x04 +.equ OUTPUT_EN, 0x08 +.equ OUTPUT_VAL, 0x0C +.equ PUE, 0x10 +.equ DS, 0x14 +.equ RISE_IE, 0x18 +.equ RISE_IP, 0x1C +.equ FALL_IE, 0x20 +.equ FALL_IP, 0x24 +.equ HIGH_IE, 0x28 +.equ HIGH_IP, 0x2C +.equ LOW_IE, 0x30 +.equ LOW_IP, 0x34 +.equ IOF_EN, 0x38 +.equ IOF_SEL, 0x3C +.equ OUTPUT_XOR, 0x40 + +CON GPIO , GPIO_BASE +CON GPIO.INPUT_VAL , GPIO_BASE + INPUT_VAL +CON GPIO.INPUT_EN , GPIO_BASE + INPUT_EN +CON GPIO.OUTPUT_EN , GPIO_BASE + OUTPUT_EN +CON GPIO.OUTPUT_VAL , GPIO_BASE + OUTPUT_VAL +CON GPIO.PUE , GPIO_BASE + PUE +CON GPIO.DS , GPIO_BASE + DS +CON GPIO.RISE_IE , GPIO_BASE + RISE_IE +CON GPIO.RISE_IP , GPIO_BASE + RISE_IP +CON GPIO.FALL_IE , GPIO_BASE + FALL_IE +CON GPIO.FALL_IP , GPIO_BASE + FALL_IP +CON GPIO.HIGH_IE , GPIO_BASE + HIGH_IE +CON GPIO.HIGH_IP , GPIO_BASE + HIGH_IP +CON GPIO.LOW_IE , GPIO_BASE + LOW_IE +CON GPIO.LOW_IP , GPIO_BASE + LOW_IP +CON GPIO.IOF_EN , GPIO_BASE + IOF_EN +CON GPIO.IOF_SEL , GPIO_BASE + IOF_SEL +CON GPIO.OUTPUT_XOR , GPIO_BASE + OUTPUT_XOR + diff --git a/rv/mcu/fe310/words/csr.s b/rv/mcu/fe310/words/csr.s new file mode 100755 index 0000000..b0dd195 --- /dev/null +++ b/rv/mcu/fe310/words/csr.s @@ -0,0 +1,22 @@ +#.option arch, +zicsr + +CSR 0x000, ustatus +CSR 0x004, uie +CSR 0x005, utvec +CSR 0x040, uscratch +CSR 0x041, uepc +CSR 0x042, ucause +CSR 0x042, ubadaddress + +CSR 0x300, mstatus +CSR 0x301, misa +CSR 0x342, mcause + +CSR 0xb00, mcycle +CSR 0xb80, mcycleh + +COLON "@cycle", FETCH_CYCLE + .word XT_CSR_mcycle + .word XT_CSR_mcycleh + .word XT_EXIT +END FETCH_CYCLE diff --git a/rv/mcu/fe310/words/env-board.s b/rv/mcu/fe310/words/env-board.s new file mode 100755 index 0000000..8401b5a --- /dev/null +++ b/rv/mcu/fe310/words/env-board.s @@ -0,0 +1,9 @@ +ENVIRONMENT "board", BOARD +.if TARGET==REDFIVE + STRING "sifive_e, Rev B" +.else + STRING "sifive_e" +.endif +.word XT_EXIT +END BOARD + diff --git a/rv/mcu/fe310/words/env-cpu.s b/rv/mcu/fe310/words/env-cpu.s new file mode 100755 index 0000000..79041af --- /dev/null +++ b/rv/mcu/fe310/words/env-cpu.s @@ -0,0 +1,5 @@ + +ENVIRONMENT "cpu", CPU + STRING "RV32IM" +.word XT_EXIT +END CPU diff --git a/rv/mcu/fe310/words/gpio.s b/rv/mcu/fe310/words/gpio.s new file mode 100755 index 0000000..a44378e --- /dev/null +++ b/rv/mcu/fe310/words/gpio.s @@ -0,0 +1,14 @@ +# ----------------------------------------------------------------------------- +# Labels for a few hardware ports +# ----------------------------------------------------------------------------- + +.equ GPIOBASE, 0x10012000 + +.equ GPIO_VALUE , GPIOBASE + 0x00 +.equ GPIO_INPUT_EN , GPIOBASE + 0x04 +.equ GPIO_OUTPUT_EN , GPIOBASE + 0x08 +.equ GPIO_PORT , GPIOBASE + 0x0C +.equ GPIO_PUE , GPIOBASE + 0x10 +.equ GPIO_IOF_EN , GPIOBASE + 0x38 +.equ GPIO_IOF_SEL , GPIOBASE + 0x3C +.equ GPIO_OUT_XOR , GPIOBASE + 0x40 diff --git a/rv/mcu/fe310/words/leds.s b/rv/mcu/fe310/words/leds.s new file mode 100755 index 0000000..6d0dd87 --- /dev/null +++ b/rv/mcu/fe310/words/leds.s @@ -0,0 +1,73 @@ + + +# Common Anode tied to Vcc. LEDs shine on low. +.equ green, 1<<19 # GPIO 19: Green LED +.equ blue, 1<<21 # GPIO 21: Blue LED +.equ red, 1<<22 # GPIO 22: Red LED + +CODEWORD "led-init", LED_INIT + li t3, red|green|blue + li t4, GPIO_OUTPUT_EN + sw t3, 0(t4) + li t3, red|green|blue + li t4, GPIO_PORT + sw t3, 0(t4) +NEXT +END LED_INIT + +CODEWORD "red", RED + li t3, blue|green + li t4, GPIO_PORT + sw t3, 0(t4) +NEXT +END RED + +CODEWORD "green", GREEN + li t3, blue|red + li t4, GPIO_PORT + sw t3, 0(t4) +NEXT +END GREEN + +CODEWORD "blue", BLUE + li t3, red|green + li t4, GPIO_PORT + sw t3, 0(t4) +NEXT +END BLUE + +CODEWORD "white", WHITE + li t3, 0 + li t4, GPIO_PORT + sw t3, 0(t4) +NEXT +END WHITE + +CODEWORD "yellow", YELLOW + li t3, blue + li t4, GPIO_PORT + sw t3, 0(t4) +NEXT +END YELLOW + +CODEWORD "cyan", CYAN + li t3, red + li t4, GPIO_PORT + sw t3, 0(t4) +NEXT +END CYAN + +CODEWORD "magenta", MAGENTA + li t3, green + li t4, GPIO_PORT + sw t3, 0(t4) +NEXT +END MAGENTA + +CODEWORD "black", BLACK + li t3, red|green|blue + li t4, GPIO_PORT + sw t3, 0(t4) +NEXT +END BLACK + diff --git a/rv/mcu/fe310/words/mtime.s b/rv/mcu/fe310/words/mtime.s new file mode 100755 index 0000000..aa30632 --- /dev/null +++ b/rv/mcu/fe310/words/mtime.s @@ -0,0 +1,16 @@ + +.equ CLINT_CTRL_ADDR, 0x02000000 +.equ CLINT_MTIME, 0xBFF8 +.equ CLINT_MTIMECMP, 0x4000 + +COLON "mtime", MTIME + .word XT_DOLITERAL, CLINT_MTIME+CLINT_CTRL_ADDR + .word XT_FETCH +.word XT_EXIT +END MTIME + +COLON "mtimecmp", MTIMECMP + .word XT_DOLITERAL, CLINT_MTIMECMP+CLINT_CTRL_ADDR +.word XT_EXIT +END MTIMECMP + diff --git a/rv/mcu/fe310/words/pll.s b/rv/mcu/fe310/words/pll.s new file mode 100755 index 0000000..32bfefb --- /dev/null +++ b/rv/mcu/fe310/words/pll.s @@ -0,0 +1,63 @@ +# "PRCI_BASE" : "$10008000", +# "PRCI_HFROSCCFG":"$1000800", +# "PRCI_HFXOSCCFG":"$1000804", +# "PRCI_PLLCFG" :"$1000808", +# "PRCI_PLLDIV" :"$100080C", +# "PRCI_PROCMONCFG":"$10008F0" + +CONSTANT "sysclock" SYSCLOCK 256 +END SYSCLOCK + +CODEWORD "pll", PLL + +# PRCI_PLLCFG +# [02:00] PLL R input clock divider %01 is /2 +# [03:03] RESERVED +# [09:04] PLL F multiply ratio %11111 +# [11:10] PLL Q divider %11 is /8 +# [15:12] RESERVED +# [16:16] PLL select +# [17:17] PLL Reference %1 +# [18:18] PLL bypass + +# (16/2)*64*(1/4)=64 +# 9876 5432 1098 7654 3210 +# 000000000000 0111 0000 1101 1111 0001 +# R F Q XTAL +#.equ TMPPLL, (1) | (31 << 4) | (3 << 10) | (1 << 17) # 64MHz +.equ TMPPLL, (1) | (31 << 4) | (1 << 10) | (1 << 17) # 256MHz +#.equ TMPPLL, (1) | (31 << 4) | (2 << 10) | (1 << 17) # 128MHz + + li t3, TMPPLL + la t4, PRCI_PLLCFG + sw t3, 0(t4) + + li t0, 10000000 # approx 1s, for the hifive1-board + li t0, 10000 # approx 0.001s, for the hifive1-board +1: + addi t0,t0,-1 + bne t0,zero,1b + +nope: +# lw t5, 0(t4) +# li t6, 1<<31 +# and t3,t5,t6 +# beq t3,zero,nope + + lw t5, 0(t4) + bgez t5,nope + + + lw t5, 0(t4) + +# li t6, 1 << 18 +# not a6,t6 +# and t3,t3,a6 + + li t6, 1 << 16 + or t3, t5, t6 + + sw t3, 0(t4) +NEXT +END PLL + diff --git a/rv/mcu/fe310/words/qspidiv.s b/rv/mcu/fe310/words/qspidiv.s new file mode 100755 index 0000000..c934fed --- /dev/null +++ b/rv/mcu/fe310/words/qspidiv.s @@ -0,0 +1,10 @@ +.equ QSPIBASE , 0x10014000 +.equ QSPIDIV , QSPIBASE + 0 + +CODEWORD "qspidiv", QSPIDIV + li t3, 0x02 + li t4, QSPIDIV + sw t3, 0(t4) +NEXT +END QSPIDIV + diff --git a/rv/mcu/fe310/words/redvled.s b/rv/mcu/fe310/words/redvled.s new file mode 100755 index 0000000..1ff91d9 --- /dev/null +++ b/rv/mcu/fe310/words/redvled.s @@ -0,0 +1,29 @@ + +.equ LED , 1 << 5 + +CODEWORD "+led" , PLUSLED + li a1 , GPIO_PORT + li a0 , LED + amoor.w a2, a0 , 0(a1) # (a1)<-{ {a2<-(a1)} or a0} +NEXT +END PLUSLED + +CODEWORD "-led" , MINUSLED + li a1 , GPIO_PORT + li a0 , LED + li a3 , -1 + xor a0 , a0, a3 + amoand.w a2, a0, 0(a1) # (a1)<-{ {a2<-(a1)} and a0} +NEXT +END MINUSLED + +CODEWORD "led.init" , LED_INIT + li a1 , GPIO_OUTPUT_EN + li a0 , LED + amoor.w a2, a0 , 0(a1) # (a1)<-{ {a2<-(a1)} or a0} + + li a1 , GPIO_PORT + li a0 , LED + amoor.w a2, a0 , 0(a1) # (a1)<-{ {a2<-(a1)} or a0} +NEXT +END LED_INIT diff --git a/rv/mcu/fe310/words/serial-emit-pause.s b/rv/mcu/fe310/words/serial-emit-pause.s new file mode 100644 index 0000000..8f16a67 --- /dev/null +++ b/rv/mcu/fe310/words/serial-emit-pause.s @@ -0,0 +1,25 @@ +COLON "serial-key-pause" , SERIAL_KEY_PAUSE + .word XT_PAUSE,XT_SERIAL_KEYQ, XT_DOCONDBRANCH, PFA_SERIAL_KEY_PAUSE + .word XT_SERIAL_KEY + .word XT_EXIT +END SERIAL_KEY_PAUSE + +COLON "serial-emit-pause" , SERIAL_EMIT_PAUSE # ( c -- ) SERIAL: emit c on serial connection or pause if unable + .word XT_PAUSE,XT_SERIAL_EMITQ, XT_DOCONDBRANCH, PFA_SERIAL_EMIT_PAUSE + .word XT_SERIAL_EMIT + .word XT_EXIT +END SERIAL_EMIT_PAUSE + +# # ----------------------------------------------------------------------------- +# CODEWORD "serial-emit?", SERIAL_EMITQ +# # ----------------------------------------------------------------------------- +# savetos +# # li t0, UART0_TXDATA +# # lw t0, 0(t0) +# # srai t0, t0, 31 # Sign extend the "transmit FIFO full" bit +# # xori s3, t0, -1 # Invert it + +# # a fudge for the moment FIXME +# li s3, -1 + +# NEXT diff --git a/rv/mcu/fe310/words/type0.s b/rv/mcu/fe310/words/type0.s new file mode 100755 index 0000000..d079fe5 --- /dev/null +++ b/rv/mcu/fe310/words/type0.s @@ -0,0 +1,11 @@ + +COLON "type0", TYPE0 + +PFA_TYPE0_LOOP: + .word XT_DUP,XT_CFETCH,XT_DUP, XT_NOTZEROEQUAL,XT_DOCONDBRANCH,PFA_TYPE0_LEAVE + .word XT_DUP,XT_DOLITERAL,10,XT_EQUAL,XT_DOCONDBRANCH,PFA_TYPE0_1,XT_DOLITERAL,13,XT_EMIT +PFA_TYPE0_1: + .word XT_EMIT, XT_1PLUS, XT_DOBRANCH,PFA_TYPE0_LOOP +PFA_TYPE0_LEAVE: + .word XT_2DROP,XT_EXIT +END TYPE0 diff --git a/rv/mcu/fe310/words/usart.s b/rv/mcu/fe310/words/usart.s new file mode 100755 index 0000000..b316e60 --- /dev/null +++ b/rv/mcu/fe310/words/usart.s @@ -0,0 +1,140 @@ +.equ UART0BASE, 0x10013000 + +.equ UART0_TXDATA , UART0BASE + 0x00 +.equ UART0_RXDATA , UART0BASE + 0x04 +.equ UART0_TXCTRL , UART0BASE + 0x08 +.equ UART0_RXCTRL , UART0BASE + 0x0C +.equ UART0_IE , UART0BASE + 0x10 +.equ UART0_IP , UART0BASE + 0x14 +.equ UART0_DIV , UART0BASE + 0x18 + +# ====================================================================== + +CODEWORD "+usart", INIT_USART + + # UART RX/TX are selected IOF_SEL on Reset. Set IOF_EN bits. + + # Clear IOF_SEL bits 16,17 first → selects IOF0 (UART0 TX/RX) + li t0, GPIO_IOF_SEL + lw t2, 0(t0) + li t1, ~((1<<17)|(1<<16)) # mask with bits 16,17 clear + and t2, t2, t1 + sw t2, 0(t0) + + # Set IOF_EN bits 16,17 — read/modify/write to preserve other peripherals + li t0, GPIO_IOF_EN + lw t2, 0(t0) + li t1, (1<<17)|(1<<16) + or t2, t2, t1 + sw t2, 0(t0) + + # Set baud rate + + li t0, UART0_DIV + + li t1, 2222-1 # 256 MHz / 115200 Baud = 2222.2 + +# li a1, 6667-1 # 256 MHz / 38400 Baud = 6666.67 +# li t1, 1111-1 # 128 MHz / 115200 Baud = 1111.1 +# li t1, 555-1 # 64 MHz / 115200 Baud = 555.55 +# li t1, 139-1 # 16 MHz / 115200 Baud = 138.89 +# li t1, 417-1 # 16 MHz / 38400 Baud = 416,67 +# li t1, 3300-1 # 128MHz 38400 Baud + + sw t1, 0(t0) + + # Enable transmit + + li t0, UART0_TXCTRL + li t1, 1 + sw t1, 0(t0) + + # Enable receive + + li t0, UART0_RXCTRL + li t1, 1 + sw t1, 0(t0) + + # initialise the one cell buffer with -1 + + la t0, PFA_SERIAL_LASTCHAR + lw t0, 0(t0) + li t1, -1 + sw t1, 0(t0) + + NEXT +END INIT_USART + + VARIABLE "serial-lastchar", SERIAL_LASTCHAR # ( -- addr ) + +# ----------------------------------------------------------------------------- + CODEWORD "serial-key", SERIAL_KEY +# ----------------------------------------------------------------------------- + savetos + la t1, PFA_SERIAL_LASTCHAR + lw t1, 0(t1) + lw s3, 0(t1) + + li t0, -1 + sw t0, 0(t1) + + NEXT +END SERIAL_KEY + +# ----------------------------------------------------------------------------- + CODEWORD "serial-key?", SERIAL_KEYQ +# ----------------------------------------------------------------------------- + + savetos + + # Check buffer for waiting character + + la t1, PFA_SERIAL_LASTCHAR +# lw t0, 0(t1) + lw t1 , 0(t1) + lw t0 , 0(t1) + srai s3, t0, 31 # Sign extend the "receive FIFO empty" bit + beq s3, zero, 1f + + # No character waiting in the buffer variable. Check UART for new character: + + li t1, UART0_RXDATA + lw t0, 0(t1) + la t1, PFA_SERIAL_LASTCHAR + lw t1, 0(t1) + sw t0, 0(t1) + + srai s3, t0, 31 # Sign extend the "receive FIFO empty" bit + +1: + xori s3, s3, -1 + NEXT +END SERIAL_KEYQ + +# ----------------------------------------------------------------------------- + CODEWORD "serial-emit", SERIAL_EMIT +# ----------------------------------------------------------------------------- + +SERIAL_EMIT_WAIT: + li t0, UART0_TXDATA + lw t0, 0(t0) + blt t0,zero, SERIAL_EMIT_WAIT + + li t1, UART0_TXDATA + sw s3, 0(t1) + loadtos + + NEXT +END SERIAL_EMIT + +# ----------------------------------------------------------------------------- + CODEWORD "serial-emit?", SERIAL_EMITQ +# ----------------------------------------------------------------------------- + savetos + li t0, UART0_TXDATA + lw t0, 0(t0) + srai t0, t0, 31 # Sign extend the "transmit FIFO full" bit + xori s3, t0, -1 # Invert it + + NEXT +END SERIAL_EMITQ diff --git a/rv/mcu/fe310/words/warm.s b/rv/mcu/fe310/words/warm.s new file mode 100644 index 0000000..ddda20d --- /dev/null +++ b/rv/mcu/fe310/words/warm.s @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-3.0-only +COLON "warm", WARM /* ( -- ) high level part of the boot sequence, VM is running */ +/* This is the high level part of the boot sequence with the VM already running. + After initializations calls TURNKEY and if TURNKEY returns calls QUIT + to enter the endless outer interpreter loop. + When called from within forth it is the equivalent of a soft RESET. + + MCUs may override this to inject additional initialization at specific points +*/ + + /* initialize values and defers to their defaults */ + .word XT_INIT_RAM + +.if WANT_DEBUGGER == YES + /* initialize debugger to disabled state, + needs UP initialized so after INIT_RAM! */ + .word XT_DEBUGMINUS +.endif + +# /* initialize pvalue system */ +# .word XT_QFIRST_BOOT, XT_DOCONDBRANCH, 1f +# .word XT_PVARENA1, XT_DOTO, XT_PVARENA, XT_PV_RESET_HARD +# .word XT_DOBRANCH, 2f +# 1: /* else */ +# .word XT_PV_INIT +# 2: +# /* check and mark first-boot done */ +# .word XT_QFIRST_BOOT, XT_DOCONDBRANCH, 1f +# .word XT_FIRST_BOOT_DONE +# 1: +# /* find the end of the used flash and set DP; +# do it after first-boot-done so that the first-boot page is erased already */ +# .word XT_INIT_DP_FLASH + + .word XT_LBRACKET + .word XT_TURNKEY + .word XT_QUIT + .word XT_EXIT +END WARM diff --git a/tools/socket-shell.py b/tools/socket-shell.py new file mode 100755 index 0000000..8a66600 --- /dev/null +++ b/tools/socket-shell.py @@ -0,0 +1,1386 @@ +#! /usr/local/bin/python3 +# SPDX-License-Identifier: GPL-3.0-only +#! /Library/Frameworks/Python.framework/Versions/3.9/bin/python3 +# +# pySerial based upload & interpreter interaction module for amforth. +# +# Copyright 2011 Keith Amidon (camalot@picncipark.org) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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 . +# +# Patcher remarks: +# ================ +# +# This uploader saves dictionary space and words clutter by substituting +# uC register names and application constants with numbers. The +# appl_defs.frt (Forth) file in the application's local directory +# provides the constant definitions. In appl_defs.frt put each constant +# on a line of its own. The first line, if it begins with a backslash +# comment, would be echoed to the screen when uploading the Forth +# code. It is recommended to place in appl_defs.frt global constant +# definitions which would affect compilation of the library and the +# project code. For example: +# +# \ Project Name +# $d5 constant CRC8MSB +# 10 constant max_number_of_users +# +# Invoke the shell with the argument --log log.frt to collect the lines +# which were uploaded to the AmForth system that received an " ok" +# response (log.frt is just a file-name example). This file can later be +# uploaded to another system using a tool simpler than the shell. Leave +# the shell by #exit to close log.frt properly. +# +# Invoke the shell with the argument --rtscts to enable serial port +# RTS/CTS hardware handshake connection. +# +# ===================================================================== +# DOCUMENTATION +# ===================================================================== +# This module module may be used as a script or imported for use as a +# part of a larger python program. +# +# Script Usage +# ------------ +# When used as a script this module provides two main functions, the +# ability to reliably upload files to the amforth interpreter and an +# interpreter interaction mode with line editing, word completion and +# previous input history. For information on how to access these +# features when invoking the module as a script, execute it with the +# --help option and read the following sections on the interaction +# protocol and local directives. +# +# +# Interaction Protocol +# -------------------- +# The amforth interaction protocol used by this module is to send a +# line to amforth character by character, reading the echos as quickly +# as possible. Once the entire line has been sent it then reads the +# response up until the " ok" prompt or a prompt that looks like an +# error response. The character by character handling of echos +# appears to eliminate unexpected device resets when compared to the +# line by line method used by previous tools, possibly by eliminating +# the possibility of serial tx overrun on the device. +# +# To further optimize interaction with the device lines are evaluated +# before sending and redundant whitespace is compressed out. Lines +# which are all whitespace or whitespace and comments are not sent and +# the next line is handled. +# +# +# Local Directives +# ---------------- +# A number of special directives are supported which instruct the +# script to do something and are handled locally without being sent to +# amforth. Directives may be specified within comments or outside +# comments as controlled by the "#directive" directive. They must be +# the only contents of a line or they will be ignored. The directives +# include: +# +# #include +# Upload the named before proceeding further. +# +# #require +# Like #include but would skip if was already uploaded +# during the shell session. +# +# #cd +# Change the current local directory to the location specified. +# During uploads, this directive affects the current file and +# files it includes. Once the current file is complete the old +# value will be restored. +# +# #directive +# Change how directives are discovered. The valid values for +# are: +# none : Stop looking for any directives +# commented : Only look for directives within comments +# Commented directives must be the first word of the +# comment. The remaining text in the comment is the +# argument provided to the directive. There must +# not be any other non-whitespace text other than +# the comment start and (if required) end characters +# and the directive and any directive argument on a +# commented directive line. If any other text is +# present on the line an error will be generated. +# uncommented : Only look for directives outside comments. +# Uncommented directives must be the first word of a +# line and extend to the end of the line. If a +# directive name exists in a subsequent word of a +# line it will be sent to the interpreter as a word +# like any other. +# all : Allow both commented and uncommented directives. +# This is the default. +# During uploads, this directive affects the current file and +# files it includes. Once the current file is complete the old +# value will be restored. +# +# #timeout +# Change the timeout value to seconds. Fractional +# values are supported. During uploads, this directive affects +# the current file and files it includes. Once the current file +# is complete the old value will be restored. +# +# #timeout-next +# Change the timeout value for the next line sent to the +# interpreter to seconds. Fractional values are +# supported. The timeout returns to its previous value after +# the next line is sent to the interpreter. If this directive +# is encountered as the very last line of an upload file it will +# have no effect. +# +# #error-on-output [] +# Controls whether an error is generated if unexpected output +# occurs during an upload. The default is yes. This directive +# can not be used in interactive mode as it would not have any +# effect. During uploads it affects the rest of the current file +# and any files it includes. The argument is optional. If not +# given it is assumed to be "yes". +# +# #ignore-error [] +# Ignore any error that occurs later in the current upload file +# or a file it includes. The argument is optional. If given +# the behavior is set as specified. If not given it is assumed +# to be "yes". +# +# #ignore-error-next [] +# Ignore any error that occurs on the next line. The argument +# is optional. If given the behavior is set as specified. If +# not given it is assumed to be "yes". +# +# #expect-output-next [] +# Expect specific output on the next line. The argument is +# optional. If it is not specified a default regular expression +# of ".*" (match everything) is assumed. This overrides the +# #error-on-output directive. An error is raised if the output +# doesn't match the regular expression. It will be ignored if +# #ignore-error is yes. Use of this directive without an +# argument is the way to prevent an error on output when +# #error-on-output is yes +# +# #start-string-word +# Add a word that starts a string. The string will end when a +# double quote character is read. +# +# #quote-char-word +# Add a word that quotes the immediately next word +# +# #interact +# Start an interactive session before proceeding with file upload. +# This only makes sense during a file upload. +# +# #edit [] +# Edit a file. The filename is optional. If it is provided the +# named file will be edited. If it is not provided and the last +# upload ended in an error the file that had the error will be +# edited at the location of the error. If there was no previous +# upload or the last upload completed successfully but an #edit +# directive was previously issued with a filename, edit the file +# previously named. Finally, if none of these apply an error is +# printed. The editor used can be specified with the --editor +# option when starting the program or through the EDITOR +# environment variable. +# +# #update-words +# This directive is only available in an interactive session. +# It cause the interaction code to reload the list of words used +# for completion from the amforth interpreter. Typically it is +# not required as words are updated automatically when the +# session starts and any time a file is uploaded as a results of +# a #include directive. The case where it is required is when +# completion is needed for words defined interactively during +# the session. +# +# #update-cpu +# This directive is only available in an interactive session. +# It causes the interaction code to read the controller name +# from the device and tries to load a specific python module +# which contains names for registers and addresses. These names +# can be used in forth code and get replace with the corresponding +# numbers. +# +# #exit +# Exit an interactive session or the current upload immediately. +# If encountered during an upload, no further lines from the +# file will be processed. +# +# +# Programmatic Usage +# ------------------ +# For programmatic usage, a single class named AMForth is provided. +# It can be instantiated with no arguments but typically a serial port +# device and port speed will be provided as the defaults are unlikely +# to be correct. +# +# Once an instance is obtained, and connected the high-level entry +# points are the "upload_file" and "interact" methods, the former +# uploading a file to the AMForth interperter and the latter providing +# an interative interpreter shell with command history and word +# completion. These methods provide progress information in various +# cases by calling the function stored in the "progress_callback" +# property with three arguments, the type of progress being reported, +# a line number if available (otherwise it is None) and a message with +# further information. The default progress callback prints this +# information to the screen in a terse format. Other programs may +# wish to replace this with their own progress presentation function. +# +# Low-level interaction with the AMForth interpreter would typically +# use the "send_line" and "read_response" methods. Before these can +# be used the serial connection must be established. The +# serial_connected property indicates whether a connection currently +# exists. A good way to obtain a connection and rule out errors in +# serial communication is to call "find_prompt" which ensures the +# existence of a serial connection and sends a newline to the AMForth +# interperter and watches for the echo. This is usually the best way +# of establishing a connection but the "serial_connect" method will +# open a connection without sending anything if that is required. +# +# Elimination of whitespace and discovery of directives (see below) is +# provided through the "preprocess_line" method and directives that +# have common implementations can be handled with the +# "handle_common_directives" method. + +# TODO: - Update comments on most functions explaining what they do. + +import argparse +import atexit +import copy +import glob +import os +import re +import readline +import serial +import serial.rfc2217 + +from io import StringIO +import subprocess +import sys +import traceback + +class AMForthException(Exception): + pass + +class Behaviors(object): + """Simple class for storing configurable processing behaviors""" + def __init__(self): + self.working_directory = os.getcwd() + self.filename = None + self.timeout = 15.0 + self.quote_char_words = ["[char]", "char"] + self.start_string_words = ['s"', '."', 'abort"'] + self.error_on_output = True + self.ignore_errors = False + self.directive_uncommented = True + self.directive_commented = True + self.expected_output_regexp = None + + @property + def directive_config(self): + "Get the current directive configuration" + if self.directive_uncommented: + if self.directive_commented: + return "all" + else: + return "uncommented" + else: + if self.directive_commented: + return "commented" + else: + return "none" + + @directive_config.setter + def directive_config(self, value): + "Set the directive configuration" + if value == "none": + self.directive_uncommented = False + self.directive_commented = False + elif value == "all": + self.directive_uncommented = True + self.directive_commented = True + elif value == "uncommented": + self.directive_uncommented = True + self.directive_commented = False + elif value == "commented": + self.directive_uncommented = False + self.directive_commented = True + else: + raise AMForthException("Unknown directive config: %s" % value) + + +class BehaviorManager(object): + """Class for determining currently configured behavior + + This class manages the lifetime of behaviors established through + configuration options and directives to minimize the impact of + that support on the AMForth class. """ + def __init__(self): + self.default_behavior = Behaviors() + self.clear() + + def clear(self): + "Clear out accumulated behavior" + self._next_line_behavior = None + self._current_line_behavior = None + self._file_behaviors = [] + + @property + def current_behavior(self): + """The behavior currently in effect""" + if self._current_line_behavior: + return self._current_line_behavior + elif self._file_behaviors: + return self._file_behaviors[0] + else: + return self.default_behavior + + def advance_line(self): + """Call when changing to the next line""" + self._current_line_behavior = self._next_line_behavior + self._next_line_behavior = None + + def push_file(self, filename): + """Call when starting processing a new nested file""" + behavior = copy.deepcopy(self.current_behavior) + behavior.filename = filename + self._file_behaviors.insert(0, behavior) + + def pop_file(self): + """Call when returning from a nested file""" + del(self._file_behaviors[0]) + + @property + def next_line_behavior(self): + """The behavior to use for the next line""" + return self._next_line_behavior + + @next_line_behavior.setter + def next_line_behavior(self, behavior): + self._next_line_behavior = behavior + + @property + def current_file_behavior(self): + """The behavior for the current file. + + Will raise an exception if there is no file currently.""" + return self._file_behaviors[0] + + @current_file_behavior.setter + def current_file_behavior(self, behavior): + self._file_behaviors[0] = behavior + + +class AMForth(object): + "Class for interacting with the AMForth interpreter" + + amforth_error_cre = re.compile(r" \?\? -\d+ \d+ \r\n> $") + upload_directives = [ + "#cd", "#require", "#include", "#directive", "#ignore-error", + "#ignore-error-next", "#error-on-output", "#expect-output-next", + "#string-start-word", "#quote-char-word", + "#timeout", "#timeout-next", "#interact", "#exit" + ] + interact_directives = [ + "#cd", "#edit", "#require", "#include", "#directive", "#ignore-error", + "#error-on-output", "#string-start-word", "#quote-char-word", + "#timeout", "#timeout-next", "#update-words", "#exit", + "#update-cpu", "#update-files" + ] + # standard words are usually uppercase, but amforth needs + # them in lowercase. + stdwords = [ + # *** Wordset BLOCK + "BLK","BLOCK","BUFFER","EVALUATE","FLUSH","LOAD","SAVE-BUFFERS", + "UPDATE", + # *** Wordset BLOCK-EXT + "EMPTY-BUFFERS","LIST","REFILL","SCR","THRU", + # *** Wordset CORE + "#S","*/MOD","+LOOP","/MOD","0<","0=","1+","1-","2!", + "2*","2/","2@","2DROP","2DUP","2OVER","2SWAP",">BODY", + ">IN",">NUMBER",">R","?DUP","ABORT","ABORT\"","ABS", + "ACCEPT","ALIGN","ALIGNED","ALLOT","AND","BASE","BEGIN", + "BL","C!","C,","C@","CELL+","CELLS","CHAR","CHAR+", + "CHARS","CONSTANT","COUNT","CR","CREATE","DECIMAL", + "DEPTH","DO","DOES>","DROP","DUP","ELSE","EMIT","ENVIRONMENT?", + "EVALUATE","EXECUTE","EXIT","FILL","FIND","FM/MOD", + "HERE","HOLD","I","IF","IMMEDIATE","INVERT","J","KEY", + "LEAVE","LITERAL","LOOP","LSHIFT","M*","MAX","MIN", + "MOD","MOVE","NEGATE","OR","OVER","POSTPONE","QUIT", + "R>","R@","RECURSE","REPEAT","ROT","RSHIFT","S\"","S>D", + "SIGN","SM/REM","SOURCE","SPACE","SPACES","STATE","SWAP", + "THEN","TYPE","U.","U<","UM*","UM/MOD","UNLOOP","UNTIL", + "VARIABLE","WHILE","WORD","XOR","[CHAR]", + # *** Wordset CORE-EXT + ".R","0<>", + "0>","2>R","2R>","2R@",":NONAME","?DO","AGAIN","C\"", + "CASE","COMPILE,","ENDCASE","ENDOF","ERASE","FALSE", + "HEX","MARKER","NIP","OF","PAD","PARSE","PICK","REFILL", + "RESTORE-INPUT","ROLL","SAVE-INPUT","SOURCE-ID","TO", + "TRUE","TUCK","U.R","U>","UNUSED","VALUE","WITHIN", + "[COMPILE]", + # *** Wordset CORE-EXT-obsolescent + "#TIB","CONVERT","EXPECT","QUERY","SPAN", + "TIB", + # *** Wordset DOUBLE + "2CONSTANT","2LITERAL","2VARIABLE","D+","D-", + "D.","D.R","D0<","D0=","D2*","D2/","D<","D=","D>S", + "DABS","DMAX","DMIN","DNEGATE","M*/","M+", + # *** Wordset DOUBLE-EXT + "2ROT","DU<", + # *** Wordset EXCEPTION + "CATCH","THROW", + # *** Wordset EXCEPTION-EXT + "ABORT","ABORT\"", + # *** Wordset FACILITY + "AT-XY","KEY?","PAGE", + # *** Wordset FACILITY-EXT + "EKEY","EKEY>CHAR","EKEY?","EMIT?","MS","TIME&DATE", + # *** Wordset FILE + "BIN","CLOSE-FILE","CREATE-FILE","DELETE-FILE","FILE-POSITION", + "FILE-SIZE","INCLUDE-FILE","INCLUDED","OPEN-FILE","R/O", + "R/W","READ-FILE","READ-LINE","REPOSITION-FILE","RESIZE-FILE", + "S\"","SOURCE-ID","W/O","WRITE-FILE","WRITE-LINE", + # *** Wordset FILE-EXT + "FILE-STATUS", + "FLUSH-FILE","REFILL","RENAME-FILE", + # *** Wordset FLOAT + ">FLOAT","D>F", + "F!","F*","F+","F-","F/","F0<","F0=","F<","F>D","F@", + "FALIGN","FALIGNED","FCONSTANT","FDEPTH","FDROP","FDUP", + "FLITERAL","FLOAT+","FLOATS","FLOOR","FMAX","FMIN", + "FNEGATE","FOVER","FROT","FROUND","FSWAP","FVARIABLE", + "REPRESENT", + # *** Wordset FLOAT-EXT + "DF!","DF@","DFALIGN","DFALIGNED","DFLOAT+", + "DFLOATS","F**","F.","FABS","FACOS","FACOSH","FALOG", + "FASIN","FASINH","FATAN","FATAN2","FATANH","FCOS","FCOSH", + "FE.","FEXP","FEXPM1","FLN","FLNP1","FLOG","FS.","FSIN", + "FSINCOS","FSINH","FSQRT","FTAN","FTANH","F~","PRECISION", + "SET-PRECISION","SF!","SF@","SFALIGN","SFALIGNED","SFLOAT+", + "SFLOATS", + # *** Wordset LOCAL + "(LOCAL)","TO", + # *** Wordset LOCAL-EXT + "LOCALS|", + # *** Wordset MEMORY + "ALLOCATE","FREE", + "RESIZE", + # *** Wordset SEARCH + "DEFINITIONS","FIND","FORTH-WORDLIST","GET-CURRENT", + "GET-ORDER","SEARCH-WORDLIST","SET-CURRENT","SET-ORDER", + "WORDLIST", + # *** Wordset SEARCH-EXT + "ALSO","FORTH","ONLY","ORDER","PREVIOUS", + # *** Wordset STRING + "-TRAILING","/STRING","BLANK","CMOVE","CMOVE>","COMPARE", + "SEARCH","SLITERAL", + # *** Wordset TOOLS + ".S","DUMP","SEE","WORDS", + # *** Wordset TOOLS-EXT + ";CODE", + "AHEAD","ASSEMBLER","BYE","CODE","CS-PICK","CS-ROLL", + "EDITOR","STATE","[ELSE]","[IF]","[THEN]", + # *** Wordset TOOLS-EXT-obsolescent + "FORGET", + # *** Tester wordset + "T{", "}T", + ] + def __init__(self, serial_port="/dev/amforth", rtscts=False, speed=38400): + self.debug = False + self.max_line_length = 100 + self.progress_callback = self.print_progress + self.editor = None + self._serial_port = serial_port + self._serial_rtscts = rtscts + self._serial_speed = speed + self._serialconn = None + self._readline_initialized = False + self._amforth_dp = None + self._filedirs = {} + self._search_path = [] + self._uploaded = set() + self._update_uploaded = False + self._amforth_words = [] + self._amforth_regs = {} + self._amforth_cpu = "" + self._last_error = () + self._last_edited_file = None + self._config = BehaviorManager() + if "AMFORTH_LIB" in os.environ: + self._search_list = os.environ["AMFORTH_LIB"].split(":") + else: + self._search_list=["."] + + # define application constants to substitute + try: + ad_file = open("appl_defs.frt") + ad_line = ad_file.readline() + ad_mat = re.match(r"^\\\\\s+(\S.*)\n", ad_line) + if ad_mat: + self.progress_callback("Information", None, "appl_defs: " + ad_mat.group(1)) + ad_pat=re.compile(r"^\s*(\S+)\s+constant\s+(\S+)\s") + ad_def = {} + while ad_line: + ad_mat = ad_pat.match(ad_line) + if ad_mat: + ad_def[ad_mat.group(2)] = ad_mat.group(1) + ad_line = ad_file.readline() + except: + ad_def = {} + self.progress_callback("Information", None, "appl_defs: %d loaded" % len(ad_def)) + self._appl_defs = ad_def + + @property + def serial_port(self): + "Serial port device attached to AMForth" + return self._serial_port + + @serial_port.setter + def serial_port(self, value): + """Set the serial port device attached to AMForth + + If the value provided is different than the current value any + existing serial connection will be closed and a new connection + opened.""" + if self._serial_port != value: + self._serial_port = value + self.serial_reconnect() + + @property + def serial_rtscts(self): + "RTS/CTS enable of serial connection to AMForth" + return self._serial_rtscts + + @serial_rtscts.setter + def serial_rtscts(self, value): + if self._serial_rtscts != value: + self._serial_rtscts = value + self.serial_reconnect() + + @property + def serial_speed(self): + "Speed of the serial connection to AMForth" + return self._serial_speed + + @serial_speed.setter + def serial_speed(self, value): + if self._serial_speed != value: + self._serial_speed = value + self.serial_reconnect() + + @property + def serial_connected(self): + "Boolean status for whether currently connected to AMForth" + return self._serialconn is not None + + def main(self): + "Main function called when module is used as a script" + upload_files, interact = self.parse_arg() + try: + for fn in upload_files: + if fn == "-": + self.interact() + else: + self.upload_file(fn, install=True) + if interact: + self.interact() + except AMForthException: + return 1 + except KeyboardInterrupt: + print( "\nBye bye") + except Exception as e: + print( "\n---- Unexpected exception ----") + traceback.print_exc() + return 1 + finally: + self.serial_disconnect() + if self._log: + self._log.close() + return 0 + + def parse_arg(self): + "Argument parsing used when module is used as a script" + parser = argparse.ArgumentParser(description="Interact with AMForth", + epilog=""" +The environment variable AMFORTH_LIB can be set with to a colon (:) separated +list of directories that are recursivly searched for file names. If not set, +the current work directory is used instead. + +The script assumes to be located in the standard amforth installation under +the tools/ directory. It uses files from the core/devices directories for +additional definitions (e.g. register names) +""" + ) + parser.add_argument("--timeout", "-t", action="store", + type=float, default=15.0, + help="Response timeout (seconds, float value)") + parser.add_argument("--port", "-p", action="store", + type=str, default=self.serial_port, help="Serial port name") + parser.add_argument("--rtscts", action="store_true", + default=self.serial_rtscts, help="Serial port RTS/CTS enable") + parser.add_argument("--speed", "-s", action="store", + type=int, default=self.serial_speed, help="Serial port speed") + parser.add_argument("--log", type=argparse.FileType('w'), + help="Uploaded Forth log-file") + parser.add_argument("--line-length", "-l", action="store", + type=int, default=self.max_line_length, + help="Maximum length of amforth input line") + parser.add_argument("--interact", "-i", action="store_true", + help="Enter interactive prompt after upload") + parser.add_argument("--directive", "-d", action="store", + default="all", + help="Local directive configuration (where found)") + parser.add_argument("--editor", action="store", + default = os.environ.get("EDITOR", None), + help="Editor to use for #edit directive") + parser.add_argument("--no-error-on-output", action="store_true", + help="Indicate an error if upload causes output") + parser.add_argument("--ignore-error", action="store_true", + help="Ignore errors during upload (not recommended)") + parser.add_argument("--debug-serial", action="store_true", + help="Output extra info about serial transfers in stderr") + parser.add_argument("--Include", "-I", action="append", + help="Add Include directory") + parser.add_argument("--uploaded-wl", "-U", action="store_true", default=False, + help="Keep the list of uploaded filenames in the dictionary.") + parser.add_argument("--no-regsub", "-X", action="store_true", default=False, + help="Do NOT replace mcu registers with their values.") + + parser.add_argument("files", nargs="*", help="may be found via the environment variable AMFORTH_LIB") + arg = parser.parse_args() + self.noregsub = arg.no_regsub + self.debug = arg.debug_serial + self.max_line_length = arg.line_length + self._serial_port = arg.port + self._serial_rtscts = arg.rtscts + self._serial_speed = arg.speed + self._log = arg.log + self._update_uploaded = arg.uploaded_wl + self.editor = arg.editor + if arg.Include: + self._search_list.extend(arg.Include) + behavior = self._config.current_behavior + behavior.error_on_output = not arg.no_error_on_output + behavior.directive_config = arg.directive + behavior.timeout = arg.timeout + behavior.ignore_errors = arg.ignore_error + return arg.files, (arg.interact or len(arg.files) == 0) + + def serial_connect(self, port=None, rtscts=None, speed=None): + """Connect to AMForth on a serial port + + The port and speed argument are optional. If not specified + the current values set in the object are used. These will be + the defaults if the have not been changed. If either is + specified corresponding property of the instance will be + updated to the new value. + + This is safe to call even if a connection already exists as + existing an existing connection will be closed before the new + connection is made.""" + if port != None: + self.serial_port = port + if rtscts != None: + self.serial_rtscts = rtscts + if speed != None: + self.serial_speed = speed + if self._serialconn: + self.serial_disconnect() + try: + timeout = self._config.current_behavior.timeout + self._serialconn = serial.serial_for_url("socket://localhost:4444") +# self._serialconn = serial.serial_for_url("socket://localhost:4321?logging=debug") + +# self._serialconn = serial.Serial(None, #self.serial_port, +# self.serial_speed, +# serial.EIGHTBITS, +# serial.PARITY_NONE, +# serial.STOPBITS_ONE, +# timeout, False, +# self.serial_rtscts, +# None, False) +# self._serialconn.port=self.serial_port +# self._serialconn.rts=True +# self._serialconn.dtr=True +# self._serialconn.open() + except serial.SerialException as e: + raise AMForthException("Serial port connect failure: %s" % str(e)) + + def serial_disconnect(self): + """Disconnect the serial connection to AMForth + + This is safe to call even if there is currently no connection.""" + if self._serialconn: + self._serialconn.close() + self._serialconn = None + + def serial_reconnect(self): + """Reconnect the serial connection to AMForth + + This is the same as calling serial_connect while there is an + existing connection. It is provided to make the clear when + the intent is to re-establish an existing connection (usually + to apply new settings) versus creating a new connectoion.""" + self.serial_connect() + + def find_prompt(self): + "Attempt to find a prompt by sending a newline and verifying echo" + if not self.serial_connected: + self.serial_connect() + # Use a short timeout to quickly detect if can't communicate + self._serialconn.timeout = 2.0 + try: + try: + self.send_line("\n") # Get empty line echo to make sure ready + self.read_response() # Throw away the response. + except serial.SerialException as e: + self.progress_callback("Error", None, str(e)) + raise AMForthException("Failed to get prompt: %s" % str(e)) + finally: + # Restore the current timeout + self._serialconn.timeout = self._config.current_behavior.timeout + + def upload_file(self, filename, install=False): + if not install and filename in self._uploaded: + return False + else: + self._uploaded.add(filename) + self._update_files() + if os.path.dirname(filename): + fpath=filename + self.progress_callback("Information", None, "using "+ filename+" verbatim") + else: + if not filename in self._filedirs: + self.progress_callback("Error", None, "file "+ filename+" not found in search path") + raise AMForthException("file " + filename + " not found in search path") + if len(self._filedirs[filename])!=1: + # oops, too many files or no one at all found? + self.progress_callback("Error", None, "Wrong # of file " + filename + " found in search path") # add this line above the one below + raise AMForthException("Wrong # of file occurances: " + filename + " ("+str(len(self._filedirs[filename]))+")\n\t"+"\n\t".join(self._filedirs[filename])) + self.progress_callback("Information", None, "using "+ filename+" from"+ self._filedirs[filename][0]) + fpath = os.path.join(self._filedirs[filename][0], filename) + self._config.push_file(fpath) + fdir=os.path.dirname(fpath) + print ("**** " + self._config.current_behavior.working_directory) + if os.path.isabs(fdir): + dirpath = os.path.normpath(fdir) + else: + oldpath = self._config.current_behavior.working_directory + dirpath = os.path.normpath(os.path.join(oldpath, fdir)) + self._config.current_behavior.working_directory = dirpath + + try: + try: + self.find_prompt() + except AMForthException as e: + self.progress_callback("Error", None, str(e)) + raise + if self._amforth_cpu=="": + self._update_cpu() + self._update_files() + self.progress_callback("File", None, fpath) + try: + with open(fpath, "r") as f: + self._send_file_contents(f) + except (OSError, IOError) as e: + self.progress_callback("Error", None, str(e)) + raise AMForthException("Unknown file: " + fpath) + self._last_error = () + finally: + print ("**** " + self._config.current_behavior.working_directory) + self._config.pop_file() + self._serialconn.timeout = self._config.current_behavior.timeout + try: + os.chdir(self._config.current_behavior.working_directory) + except OSError as e: + errmsg = ("Failed to change to directory '%s': %s" + % (self._config.current_behavior.working_directory, + str(e))) + self.progress_callback("Error", None, errmsg) + raise AMForthException(errmsg) + # update the included-wl on the controller + if self._update_uploaded: + self.send_line("get-current uploaded-wl set-current create " + filename + " set-current") + return True + + def _send_file_contents(self, f): + in_comment = False + lineno = 0 + for full_line in f: + self._config.advance_line() + self._serialconn.timeout = self._config.current_behavior.timeout + try: + os.chdir(self._config.current_behavior.working_directory) + except OSError as e: + errmsg = ("Failed to change to directory '%s': %s" + % (self._config.current_behavior.working_directory, + str(e))) + self.progress_callback("Error", None, errmsg) + raise AMForthException(errmsg) + lineno += 1 + if full_line and full_line[-1] == "\n": + full_line = full_line[:-1] + if full_line and full_line[-1] == "\r": + full_line = full_line[:-1] + line = full_line.strip() + if len(line) == 0: + if in_comment: + self.progress_callback("Comment", lineno, full_line) + else: + self.progress_callback("Whitespace", lineno, full_line) + continue + try: + (line, in_comment, + directive, + directive_arg) = self.preprocess_line(full_line, in_comment, + self.upload_directives) + except AMForthException as e: + self._record_error(lineno) + self.progress_callback("Error", lineno, full_line) + self.progress_callback("Error", None, str(e)) + raise + if directive: + self.progress_callback("Directive", lineno, full_line) + if directive == "#exit": + break + elif directive == "#interact": + self.interact() + continue + self.handle_common_directives(directive, directive_arg) + continue + if len(line) == 0: + self.progress_callback("Comment", lineno, full_line) + continue + try: + self.send_line(line) + except AMForthException as e: + self._record_error(lineno) + self.progress_callback("Error", lineno, full_line) + self.progress_callback("Error", None, str(e)) + raise + response = self.read_response() + self.progress_callback("Sent", lineno, full_line) + if response[-3:] == " ok": + if len(response) > 3: + for l in StringIO(response[:-3]): + self.progress_callback("Output", lineno, l.rstrip()) + r = self._config.current_behavior.expected_output_regexp + if r: + m = re.match(r, response[:-3], re.MULTILINE) + response_ok = m is not None + else: + response_ok = False + if not response_ok: + if self._config.current_behavior.error_on_output: + errmsg = "Unexpected output after line." + errmsg += " To allow, specify --no-error-on-output." + self.progress_callback("Error", lineno, errmsg) + if not self._config.current_behavior.ignore_errors: + self._record_error(lineno) + raise AMForthException(errmsg) + elif self._log: + self._log.write(line + "\n") + else: + self.progress_callback("Error", None, response) + if not self._config.current_behavior.ignore_errors: + self._record_error(lineno) + raise AMForthException("Error in line sent") + + def preprocess_line(self, line, in_delim_comment=False, directives=[]): + # Compresses whitespace, including comments so send minimum + # data to atmega + result = [] + comment_words = [] + char_quote = False + in_string = False + in_line_comment = False + directive = None + directive_arg = [] + words = self._split_space_or_tab(line) + for iw,w in enumerate(words): + if in_string: + try: + i = w.index('"') + except ValueError: + result[-1] += " " + w + continue + in_string = False + result[-1] += " " + w[:i+1] + result[-1] = result[-1][1:] # remove extra initial space + w = w[i+1:] + + if in_delim_comment: + try: + i = w.index(")") + except ValueError: + pass + else: + in_delim_comment = False + w = w[i+1:] + + if not w: + continue + if w in self._appl_defs: + w = self._appl_defs[w] + elif w in self._amforth_regs and not self.noregsub : + w = self._amforth_regs[w] + elif w.upper() in self.stdwords: + w = w.lower() + if char_quote: + result.append(w) + char_quote = False + continue + + if w == "(": + if not in_delim_comment: + in_delim_comment = True + else: + raise AMForthException("Illegal nested comment") + continue + + if not in_delim_comment and not in_line_comment: + if w == "\\" and (iw == 0 or words[iw-1].lower() != "postpone"): + in_line_comment = True + continue + + elif w in self._config.current_behavior.start_string_words: + in_string = True + result.append(w) + result.append('') + continue + + if w in self._config.current_behavior.quote_char_words: + char_quote = True # no continue deliberately + + if directive: + directive_arg.append(w) + else: + if (self._config.current_behavior.directive_uncommented + and not result + and w in directives): + directive = w + else: + result.append(w) + else: + if directive: + directive_arg.append(w) + else: + if (self._config.current_behavior.directive_commented + and not result + and not comment_words + and w in directives): + directive = w + else: + comment_words.append(w) + + if directive and len(result): + raise AMForthError("Directive must not have other content: %s", + " ".join(result)) + + return (" ".join(result), in_delim_comment, + directive, " ".join(directive_arg)) + + def _record_error(self, lineno): + fn = self._config.current_behavior.filename + if fn: + self._last_error = (fn, lineno) + + def _split_space_or_tab(self, line): + result = [""] + for c in line: + if c == " " or c == "\t": + result.append("") + else: + result[-1] += c + return result + + def handle_common_directives(self, directive, directive_arg): + if directive == "#include" or directive == "#require": + fn = directive_arg.strip() + if self.upload_file(fn, directive == "#include"): + resume_fn = self._config.current_behavior.filename + if resume_fn: + self.progress_callback("File", None, resume_fn + " (resumed)") + else: + self.progress_callback("Information", None, "already uploaded") + elif directive == "#cd": + dirname = directive_arg.strip() + if os.path.isabs(dirname): + dirpath = os.path.normpath(dirname) + else: + oldpath = self._config.current_behavior.working_directory + dirpath = os.path.normpath(os.path.join(oldpath, dirname)) + self._config.current_behavior.working_directory = dirpath + elif directive == "#timeout": + try: + timeout = float(directive_arg) + except ValueError as e: + self.progress_callback("Error", None, "Invalid timeout") + return + self._config.current_file_behavior.timeout = timeout + elif directive == "#timeout-next": + try: + timeout = float(directive_arg) + except ValueError as e: + self.progress_callback("Error", None, "Invalid timeout") + return + behavior = copy.deepcopy(self._config.current_behavior) + behavior.timeout = timeout + self._config.next_line_behavior = behavior + elif directive == "#ignore-error": + v = self._yes_or_no_arg(directive_arg) + self._config.current_file_behavior.ignore_errors = v + elif directive == "#ignore-error-next": + v = self._yes_or_no_arg(directive_arg) + behavior = copy.deepcopy(self._config.current_behavior) + behavior.ignore_errors = v + self._config.next_line_behavior = behavior + elif directive == "#error-on-output": + v = self._yes_or_no_arg(directive_arg) + behavior = self._config.current_file_behavior + behavior.error_on_output = v + elif directive == "#expect-output-next": + regexp = directive_arg.strip() + if not regexp: + regexp = ".*" + behavior = copy.deepcopy(self._config.current_behavior) + behavior.expected_output_regexp = regexp + self._config.next_line_behavior = behavior + elif directive == "#start-string-word": + behavior = self._config.current_file_behavior + behavior.start_string_words.append(directive_arg.strip().split(" ")) + elif directive == "#quote-char-word": + behavior = self._config.current_file_behavior + behavior.quote_char_words.append(directive_arg.strip().split(" ")) + elif directive == "#directive": + behavior = self._config.current_file_behavior + behavior.directive_config = directive_arg.strip() + else: + errmsg = "Unknown directive: %s %s" % (directive, directive_arg) + raise AMForthException(errmsg) + + def _yes_or_no_arg(self, directive_arg): + if not directive_arg: + return True + else: + if directive_arg.lower() == "yes": + return True + elif directive_arg.lower() == "no": + return False + else: + errmsg = "Invalid directive argument. Must be yes or no." + raise AMForthExcetion(errmsg) + + def send_line(self, line): + if len(line) > self.max_line_length - 1: # For newline + raise AMForthException("Input line > %d char" + % self.max_line_length) + if self.debug: + sys.stderr.write("|a( )" + repr(line)[1:-1] + "\n") + sys.stderr.write("|s( )") + for c in line + "\n": + if self.debug: + sys.stderr.write(repr(c)[1:-1]+"->") + sys.stderr.flush() + self._serialconn.write(c.encode()) + self._serialconn.flush() + r = self._serialconn.read(1) # Read echo of character we just sent + r=r.decode() + while r and (r != c or (c == '\t' and r != ' ')): + if self.debug: + sys.stderr.write(repr(r)[1:-1]) + sys.stderr.flush() + r = self._serialconn.read(1).decode() + if not r: + raise AMForthException("Input character not echoed.") + if self.debug: + sys.stderr.write(repr(r)[1:-1] + "|") + sys.stderr.flush() + if self.debug: + sys.stderr.write("\n") + + def read_response(self): + if self.debug: + sys.stderr.write("|r( )") + response = "" + r = self._serialconn.read(1).decode() + while r != "": + if self.debug: + sys.stderr.write(repr(r)[1:-1]) + sys.stderr.flush() + response = response + r + if response[-3:] == " ok": + # Interactive prompt read and discarded while handling + # echo of next line sent. + break + elif self.amforth_error_cre.search(response) is not None: + response = response[:-3] # Don't return prompt in response + break + r = self._serialconn.read(1).decode() + if not response: + response = "Timed out waiting for ok response" + if self.debug: + sys.stderr.write("\n") + return response + + def print_progress(self, type, lineno, info): + if not lineno: + print("|%s=%s" % (type[:1], info)) + else: + print ("|%s|%5d|%s" % (type[:1], lineno, info)) + + def interact(self): + self.progress_callback("Interact", None, + "Entering amforth interactive interpreter") + # Use null filename "file" to capture interactive config + self._config.push_file(None) + try: + self.find_prompt() + except AMForthException as e: + self.progress_callback("Error", None, str(e)) + self._config.pop_file() + raise + self._init_readline() + in_comment = False + while True: + try: + if self._amforth_cpu: + prompt="("+self._amforth_cpu+")> " + else: + prompt="> " + full_line = input(prompt) + except EOFError as e: + print("") + break + self._config.advance_line() + self._serialconn.timeout = self._config.current_behavior.timeout + try: + os.chdir(self._config.current_behavior.working_directory) + except OSError as e: + errmsg = ("Failed to change to directory '%s': %s" + % (self._config.current_behavior.working_directory, + str(e))) + self.progress_callback("Error", None, errmsg) + raise AMForthException(errmsg) + (line, in_comment, + directive, + directive_arg) = self.preprocess_line(full_line, in_comment, + self.interact_directives) + try: + if directive: + self.progress_callback("Directive", None, full_line) + if directive == "#exit": + break + elif directive == "#update-words": + self._update_words() + continue + elif directive == "#update-cpu": + self._update_cpu() + continue + elif directive == "#update-files": + self._update_files() + continue + elif directive == "#edit": + if directive_arg: + self.edit_file(directive_arg.strip()) + elif self._last_error: + self.edit_file(*self._last_error) + elif self._last_edited_file: + self.edit_file(self._last_edited_file) + else: + print("No file to edit") + continue + self.handle_common_directives(directive, directive_arg) + if directive == "#include" or directive == "#require": + self._update_words() + continue + if in_comment or not line: + continue + else: + self.send_line(line) + print (self.read_response()) + except AMForthException as e: + print ("Error: " + str(e)) + self._config.pop_file() + self._serialconn.timeout = self._config.current_behavior.timeout + try: + os.chdir(self._config.current_behavior.working_directory) + except OSError as e: + errmsg = ("Failed to change to directory '%s': %s" + % (self._config.current_behavior.working_directory, + str(e))) + self.progress_callback("Error", None, errmsg) + raise AMForthException(errmsg) + self.progress_callback("Interact", None, + "Leaving interactive interpreter") + + def _init_readline(self): + if not self._readline_initialized: + readline.set_completer_delims(" ") + readline.set_completer(self._rlcompleter) + readline.parse_and_bind("tab: complete") + histfn = os.path.join(os.path.expanduser("~"), + ".frt-interact.history") + try: + readline.read_history_file(histfn) + except IOError as e: + pass + self._update_words() + self._update_cpu() + self._update_files() + self._update_uploaded_files() + atexit.register(readline.write_history_file, histfn) + + def _update_words(self): + # get all words that are available in the search order + self.send_line("base @ decimal dp u. base !") + dp = self.read_response() + if dp[-3:] != " ok": + return # Something went wrong, just silently ignore + dp = int(dp[:-3]) + if self._amforth_dp != dp: + self._amforth_dp = dp + self.send_line("words") + words = self.read_response() + if words[-3:] != " ok": + return # Something went wrong, just silently ignore + self._amforth_words = words.split(" ") + self.interact_directives + + def _update_cpu(self): + return + self.progress_callback("Information", None, "getting MCU name..") + self.send_line("s\" cpu\" environment search-wordlist drop execute itype") + words = self.read_response() + if words[-3:] != " ok": + return # Something went wrong, just silently ignore + mcudef = words[:-3].lower() + self._amforth_regs = {} + if mcudef.startswith("msp"): + flavor="msp430" + else: + if mcudef.startswith("rv"): + flavor="risc-v" + else: + flavor="avr8" + self._search_list.insert(0,os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),"..", flavor, "lib"))) + self._search_list.insert(0,os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),"..", flavor, "devices",mcudef))) + sys.path.insert(1,os.path.join(os.path.dirname(sys.argv[0]),"..", flavor, "devices",mcudef)) + try: + from device import MCUREGS + self._amforth_regs=MCUREGS + self._amforth_cpu = words[:-3] + self.progress_callback("Information", None, "successfully loaded register definitions for " + mcudef) + except: + self.progress_callback("Warning", None, "failed loading register definitions for " + mcudef + " (" + flavor + ") .. continuing") + + def _update_files(self): + self.progress_callback("Information", None, "getting filenames on the host") + return + self._filedirs = {} + for p in self._search_list: + self.progress_callback("Information", None, " Reading "+p) + for root, dirs, files in os.walk(p): + for f in files: + print(f) # TWTW + fpath=os.path.realpath(os.path.join(root, f)) + fpathdir=os.path.dirname(fpath) + if f in self._filedirs: + # check for duplicates + for d in self._filedirs[f]: + if d==fpathdir: + fpath=None + if fpath: self._filedirs[f].append(fpathdir) + else: + self._filedirs[f]=[fpathdir] + + def _update_uploaded_files(self): + self.progress_callback("Information", None, "getting filenames from the controller") + return + self.send_line("uploaded-wl show-wordlist") + files = self.read_response() + if files[-3:] != " ok": + return # Something went wrong, just silently ignore + for f in files.split(" "): + self._uploaded.add(f) + self.progress_callback("Information", None, "already uploaded "+ ", ".join(self._uploaded)+" ") + self._update_uploaded = True + + def _rlcompleter(self, text, state): + if state == 0: + line_words = readline.get_line_buffer().split(" ") + if line_words and line_words[-1] == text: + line_words = line_words[:-1] + while line_words and line_words[-1] == "": + line_words = line_words[:-1] + if line_words: + if line_words[-1] in ["#require", "#include", "#edit"]: + self._rl_matches = [f for f in self._filedirs.keys() + if f.startswith(text)] + elif line_words[-1] == "#cd": + fnames = glob.glob(text + '*') + self._rl_matches = [f + "/" for f in fnames + if os.path.isdir(f)] + elif line_words[-1] == "#directive": + self._rl_matches = [w for w in ("all ", "uncommented ", + "commented ", "none ") + if w.startswith(text)] + elif line_words[-1] in ["#error-on-output", + "#ignore-error", "#ignore-error-next"]: + self._rl_matches = [w for w in ["yes", "no"] + if w.startswith(text)] + elif line_words[-1] in ["#exit", "#update-words", + "#timeout", "#timeout-next"]: + self._rl_matches = [] + else: + self._rl_matches = [w + " " for w in self._amforth_words+self._amforth_regs.keys() + if not text or w.startswith(text)] + else: + self._rl_matches = [w + " " for w in self._amforth_words+self._amforth_regs.keys() + if not text or w.startswith(text)] + if self._rl_matches: + return self._rl_matches[0] + else: + return None + else: + if state < len(self._rl_matches): + return self._rl_matches[state] + else: + return None + + def edit_file(self, filename, lineno=0): + if self.editor: + # Have to construct command line differently for different + # editors to be able to move to specific line... + exename = os.path.basename(self.editor) + if exename in ["emacs", "emacsclient", "nano"]: + cmd = [self.editor, "+" + str(lineno), filename] + elif exename in ["vi", "vim"]: + cmd = [self.editor, filename, "+" + str(lineno)] + elif exename == "mcedit": + cmd = [self.editor, " " + filename, ":" + str(lineno)] + elif exename == "gedit": + cmd = [self.editor, "-b", filename, "+" + str(lineno)] + elif exename == "pn.exe": + cmd = [self.editor, " --line", " "+str(lineno)+" ", filename] + else: + cmd = [self.editor, filename] + try: + subprocess.call(cmd) + self._last_edited_file = filename + except OSError as e: + raise AMForthException("Could not start editor: "+self.editor) + else: + raise AMForthException("No editor specified. Use --editor or EDITOR environment variable") + +if __name__ == "__main__": + sys.exit(AMForth().main()) +