Skip to content

Commit

Permalink
Read hex numbers like '0xCAFE4241'. Fixes #16
Browse files Browse the repository at this point in the history
  • Loading branch information
aw committed Jan 23, 2023
1 parent 9c755ab commit 50da46c
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 33 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/).
Expand All @@ -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.

Expand All @@ -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
Expand Down
51 changes: 43 additions & 8 deletions docs/HOWTO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -93,38 +94,72 @@ 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@ ! ;
```

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`:
Expand Down
3 changes: 3 additions & 0 deletions docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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).

Expand Down
6 changes: 3 additions & 3 deletions docs/TUTORIALS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
55 changes: 37 additions & 18 deletions src/05-internal-functions.s
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/06-initialization.s
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 2 additions & 0 deletions src/08-forth-primitives.s
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/09-interpreter.s
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 50da46c

Please sign in to comment.