diff --git a/README.md b/README.md index fbd7927..fc831c6 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ [FiveForths](https://github.com/aw/fiveforths) is a tiny [Forth](https://www.forth.com/starting-forth/) written in hand-coded RISC-V assembly, initially designed to run on the 32-bit [Longan Nano](https://longan.sipeed.com/en/) (GD32VF103) microcontroller. +![fiveforths-terminal](https://user-images.githubusercontent.com/153401/213904509-089dd490-62e2-4c9d-9f9c-a6e5fab34e17.png) + _FiveForths_ currently uses the _indirect threading_ model and only has 19 built-in primitive words. It is 100% fully functional and can be extended by adding new primitives (in Assembly) or by defining new words (in Forth). This implementation is loosely inspired by [sectorforth](https://github.com/cesarblum/sectorforth), [jonesforth](https://github.com/nornagon/jonesforth), and [derzforth](https://github.com/theandrew168/derzforth). Development progress has been logged regularly in the [devlogs](https://aw.github.io/fiveforths/). @@ -21,8 +23,8 @@ Development progress has been logged regularly in the [devlogs](https://aw.githu The quickest way to get started is to download and flash one of the firmware binaries listed below:. -* [fiveforths-longan-nano-lite.bin](https://github.com/aw/fiveforths/releases/download/v0.3/fiveforths-longan-nano-lite.bin) (64K Flash, 20K RAM) -* [fiveforths-longan-nano.bin](https://github.com/aw/fiveforths/releases/download/v0.3/fiveforths-longan-nano.bin) (128K Flash, 32K RAM) +* [fiveforths-longan-nano-lite.bin](https://github.com/aw/fiveforths/releases/download/v0.4/fiveforths-longan-nano-lite.bin) (64K Flash, 20K RAM) +* [fiveforths-longan-nano.bin](https://github.com/aw/fiveforths/releases/download/v0.4/fiveforths-longan-nano.bin) (128K Flash, 32K RAM) See the [TUTORIALS](docs/TUTORIALS.md) for detailed download and flashing information. @@ -43,6 +45,12 @@ Please create a pull-request or [open an issue](https://github.com/aw/picolisp-k # Changelog +## 0.4 (2023-01-23) + + * Fix issue #16 - Add the ability to read hex numbers + * Update documentation + * Add example for toggling an LED + ## 0.3 (2023-01-19) * Fix issue #7 - Implement bounds checks for stacks diff --git a/docs/HOWTO.md b/docs/HOWTO.md index 059453b..bf204ec 100644 --- a/docs/HOWTO.md +++ b/docs/HOWTO.md @@ -12,7 +12,8 @@ This document provides more detailed information on build, use, and write code f 2. [Rebuilding the firmware](#rebuilding-the-firmware) 3. [Debug with JTAG](#debug-with-jtag) 4. [Defining words (Forth)](#defining-words) -5. [Adding primitives (Assembly)](#adding-primitives) +5. [Toggle an LED](#toggle-an-led) +6. [Adding primitives (Assembly)](#adding-primitives) ### Building for other boards @@ -93,31 +94,32 @@ info all-registers Accessing _FiveForths_ through the terminal should look similar to this: ``` +$ pyserial-miniterm --eol LF /dev/ttyUSB0 115200 --- Miniterm on /dev/ttyUSB0 115200,8,N,1 --- --- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- -FiveForths v0.3, Copyright (c) 2021~ Alexander Williams, https://a1w.ca +FiveForths v0.4, Copyright (c) 2021~ Alexander Williams, https://a1w.ca ``` -Some basic words can then be defined (borrowed from [derzforth prelude.forth](https://github.com/theandrew168/derzforth/blob/main/lexicons/prelude.forth)): +Some basic words can then be defined (borrowed from [sectorforth hello-world](https://github.com/cesarblum/sectorforth/blob/master/examples/01-helloworld.f) and [planckforth bootstrap](https://github.com/nineties/planckforth/blob/main/bootstrap.fs)): ``` : dup sp@ @ ; -: invert dup nand ; +: invert -1 nand ; : negate invert 1 + ; : - negate + ; : drop dup - + ; -: over sp@ 4 - @ ; -: swap over over sp@ 12 - ! sp@ 4 - ! ; +: over sp@ 4 + @ ; +: swap over over sp@ 12 + ! sp@ 4 + ! ; : nip swap drop ; : 2dup over over ; : 2drop drop drop ; : and nand invert ; -: or invert swap invert and invert ; +: or invert swap invert nand ; : = - 0= ; : <> = invert ; : , here @ ! here @ 4 + here ! ; -: immediate latest @ 4 + dup @ 2147483648 or swap ! ; +: immediate latest @ 4 + dup @ 0x80000000 or swap ! ; : [ 0 state ! ; immediate : ] 1 state ! ; : branch rp@ @ dup @ + rp@ ! ; @@ -125,6 +127,39 @@ Some basic words can then be defined (borrowed from [derzforth prelude.forth](ht Of course, it is possible to define many other words to suit your needs. +### Toggle an LED + +The following code can be used to turn on the green and blue LEDs on GPIOA pins 1 and 2: + +``` +: green_led_on 0x40010800 @ 0xFFFFFF0F and 0x00000030 or 0x40010800 ! ; +: blue_led_on 0x40010800 @ 0xFFFFF0FF and 0x00000300 or 0x40010800 ! ; +green_led_on +blue_led_on +``` + +And to turn off the same LEDs: + +``` +: green_led_off 0x40010800 @ 0xFFFFFF0F and 0x00000040 or 0x40010800 ! ; +: blue_led_off 0x40010800 @ 0xFFFFF0FF and 0x00000400 or 0x40010800 ! ; +green_led_off +blue_led_off +``` + +This requires the above defined words: `invert, over, swap, and, or`. + +To explain the values: + +* `0x40010800`: GPIOA base address with offset `0x00` for `CTL0` pins 0-7 (would be `CTL1` with offset `0x04` for pins 8-15). +* `0xFFFFF0FF`: mask to clear GPIO pin 2 (would be the same for GPIO pin 10, while GPIO pin 1 would be `0xFFFFFF0F` and GPIO pin 8 would be `0xFFFFFFF0`). +* `0x00000030`: GPIO pin 1 setting `0b0011` which is `push-pull output, max speed 50MHz`. +* `0x00000040`: GPIO pin 1 setting `0b0100` which is `floating input`. +* `0x00000300`: GPIO pin 2 setting `0b0011` which is `push-pull output, max speed 50MHz`. +* `0x00000400`: GPIO pin 2 setting `0b0100` which is `floating input`. + +The code above uses those pre-calculated values to read the existing GPIOA config from a memory address (with `@`), apply a mask (with `and`), apply the new config (with `or`), then store it back to the memory address (with `!`), thus writing the new GPIOA which toggles the pins low/high (active-low, therefore low turns on the LED, high turns it off). + ### Adding primitives New primitives can be written in RISC-V Assembly. It is recommended to add them to a **new file** and then include the file at _the end_ of `fiveforths.s`: diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 451e43e..f232a25 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -36,6 +36,8 @@ Below is a list of specifications for _FiveForths_, most can be changed in the s * Stack effects comments support `( x -- x )`: **yes** * Stack and memory overflow/underflow protection: **yes** * Backslash comments support `\ comment`: **yes** +* Decimal string number input support `12345` and `-202`: **yes** +* Hex string number input support `0xCAFE4241` and `-0xCA`: **yes** * Multiline code definitions support: **no** * OK message: `" ok\n"` * ERROR message: `" ?\n"` @@ -182,6 +184,7 @@ This document would be incomplete without listing other Forths which inspired me * [nasmjf, the devlog idea and well documented](http://ratfactor.com/nasmjf/) * [CamelForth, by Brad Rodriguez (Moving Forth)](http://www.camelforth.com) * [muforth, the sum of all Forth knowledge](https://muforth.nimblemachines.com/) +* [planckforth, how to bootstrap a Forth](https://github.com/nineties/planckforth/) Additional information can be found in the [devlogs](https://aw.github.io/fiveforths). diff --git a/docs/TUTORIALS.md b/docs/TUTORIALS.md index d42b9d8..778da7f 100644 --- a/docs/TUTORIALS.md +++ b/docs/TUTORIALS.md @@ -54,8 +54,8 @@ It is possible to download a pre-built firmware binary, or build the firmware ma Download one of the firmware binaries from the [releases page](https://github.com/aw/fiveforths/releases). -* [fiveforths-longan-nano-lite.bin](https://github.com/aw/fiveforths/releases/download/v0.3/fiveforths-longan-nano-lite.bin) (64K Flash, 20K RAM) -* [fiveforths-longan-nano.bin](https://github.com/aw/fiveforths/releases/download/v0.3/fiveforths-longan-nano.bin) (128K Flash, 32K RAM) +* [fiveforths-longan-nano-lite.bin](https://github.com/aw/fiveforths/releases/download/v0.4/fiveforths-longan-nano-lite.bin) (64K Flash, 20K RAM) +* [fiveforths-longan-nano.bin](https://github.com/aw/fiveforths/releases/download/v0.4/fiveforths-longan-nano.bin) (128K Flash, 32K RAM) ### Build it @@ -87,7 +87,7 @@ $ make Additional build options are explained in the [HOWTO](HOWTO.md) section. -The firmware file is called `fiveforths.bin` and is **nearly 2.5 KBytes** as of _release v0.3_ since _January 19, 2023_. +The firmware file is called `fiveforths.bin` and is **approximately 2.5 KBytes** as of _release v0.4_ since _January 23, 2023_. ### Flash it diff --git a/src/05-internal-functions.s b/src/05-internal-functions.s index eb4cd48..ee1e97b 100644 --- a/src/05-internal-functions.s +++ b/src/05-internal-functions.s @@ -50,41 +50,60 @@ token_done: # convert a string token to a 32-bit integer # arguments: a0 = token buffer start address, a1 = token size (length in bytes) -# returns: a0 = integer value, a1 = 1=OK, 0=ERROR +# returns: a0 = integer value, a1 = 0 = OK, 1 or greater = ERROR number: - li t1, 10 # initialize temporary to 10: multiplier + li t1, 10 # initialize temporary to 10: number base (decimal) mv t0, zero # initialize temporary to 0: holds the final integer li t3, CHAR_MINUS # initialize temporary to minus character '-' mv t4, zero # initialize temporary to 0: sign flag of integer lbu t2, 0(a0) # load first character from W working register - bne t2, t3, number_digit # jump to number digit loop if the first character is not a minus sign + bne t2, t3, number_check # jump to number check if the first character is not a minus sign + # first character is a minus sign, so the number will be negative li t4, 1 # number is negative, store a 1 flag in temporary addi a0, a0, 1 # increment buffer address by 1 character - addi a1, a1, -1 # decrease buffer size by 1 + addi a1, a1, -1 # decrement the string length by 1 + beqz a1, number_error # jump to error if the number is only a minus '-' +number_check: + li t3, 0x00007830 # load the '0x' string into temporary + lhu t2, 0(a0) # load the first 2 characters into temporary + bne t2, t3, number_digit # jump to number digit loop if the first 2 characters are not '0x' + + # first 2 characters are '0x', so let's assume the rest are hex digits + li t1, 16 # initialize temporary to 16: number base (hex) + addi a0, a0, 2 # increment buffer address by 2 characters + addi a1, a1, -2 # decrement the string length by 2 + beqz a1, number_error # jump to error if the number is only '0x' number_digit: - # check if the character in the token is a digit between "0" (0x30) and "9" (0x39) - # if we take the digit and subtract 0x30 and the result is < 0, then it's not a digit (error) - # if we take the digit and subtract 0x30 and the result is > 9, then it's not a digit (error) - # otherwise it's a digit (loop) beqz a1, number_done # if the size of the buffer is 0 then we're done + mul t0, t0, t1 # multiply the number by the number base (10 or 16) lbu t2, 0(a0) # load next character into temporary - addi t2, t2, -0x30 # subtract 0x30 from the character - bgeu t2, t1, number_error # check if character is < 0 or >= 10 - mul t0, t0, t1 # multiply previous number by 10 (base 10) - add t0, t0, t2 # add previous number to current digit addi a0, a0, 1 # increment buffer address by 1 character - addi a1, a1, -1 # decrease buffer size by 1 - j number_digit # loop to check the next character -number_error: - li a1, 0 # number is too large or not an integer, return 0 - ret + + # convert the character to a number + sltiu t3, t2, 0x30 # set the result in t3 if the character is lower than '0' + bnez t3, number_done # we're done if it's not a digit! + addi t2, t2, -0x30 # subtract '0' from the character + sltiu t3, t2, 10 # set the result in t3 if the character is lower than 10 + bnez t3, number_number # the character is a number (0-9) + sltiu t3, t2, 0x41-0x30 # set the result in t3 if the character is lower than 17 + bnez t3, number_done # we're done if it's not a letter! + addi t2, t2, -7 # subtract 7 from the character to convert ascii to hex +number_number: + slt t3, t2, t1 # set the result in t3 if it's lower than the base (10 or 16) + beqz t3, number_done # we're done if it's not a number (0-9) or (0-F) + add t0, t0, t2 # add previous number to current digit + addi a1, a1, -1 # decrement the string length by 1 + bnez a1, number_digit # loop to check the next character if the length is > 0 number_done: beqz t4, number_store # don't negate the number if it's positive neg t0, t0 # negate the number using two's complement number_store: + # the value in a1 will be greater than 0 if it wasn't a valid number mv a0, t0 # copy final number to W working register - li a1, 1 # number is an integer, return 1 + ret +number_error: + li a1, 1 # number is too large or not an integer, return 1 ret # search for a hash in the dictionary diff --git a/src/06-initialization.s b/src/06-initialization.s index 7b1245f..f24c134 100644 --- a/src/06-initialization.s +++ b/src/06-initialization.s @@ -73,4 +73,4 @@ tib_zerofill: tib_done: j interpreter_start # jump to the main interpreter REPL -msg_boot: .ascii "FiveForths v0.3, Copyright (c) 2021~ Alexander Williams, https://a1w.ca \n\n" +msg_boot: .ascii "FiveForths v0.4, Copyright (c) 2021~ Alexander Williams, https://a1w.ca \n\n" diff --git a/src/08-forth-primitives.s b/src/08-forth-primitives.s index 17437b2..031272a 100644 --- a/src/08-forth-primitives.s +++ b/src/08-forth-primitives.s @@ -88,6 +88,8 @@ defcode "key", 0x0388878e, KEY, EXIT # emit ( x -- ) Write 8-bit character to uart output defcode "emit", 0x04964f74, EMIT, KEY checkunderflow 0 # check for stack underflow of data stack (1 CELL) + li a0, CHAR_SPACE # load space character into W + call uart_put # send character from W to uart POP a0 # copy top of data stack into W call uart_put # send character from W to uart NEXT diff --git a/src/09-interpreter.s b/src/09-interpreter.s index bf511a9..517af1a 100644 --- a/src/09-interpreter.s +++ b/src/09-interpreter.s @@ -94,7 +94,7 @@ process_token: mv t5, a0 # save a0 temporarily mv t6, a1 # save a1 temporarily call number # try to convert the token to an integer - bnez a1, push_number # push the token to the stack or memory if it's a number + beqz a1, push_number # push the token to the stack or memory if it's a number mv a0, t5 # restore a0 mv a1, t6 # restore a1