Skip to content

Commit 50da46c

Browse files
committed
Read hex numbers like '0xCAFE4241'. Fixes #16
1 parent 9c755ab commit 50da46c

8 files changed

Lines changed: 100 additions & 33 deletions

File tree

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
[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.
66

7+
![fiveforths-terminal](https://user-images.githubusercontent.com/153401/213904509-089dd490-62e2-4c9d-9f9c-a6e5fab34e17.png)
8+
79
_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).
810

911
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
2123

2224
The quickest way to get started is to download and flash one of the firmware binaries listed below:.
2325

24-
* [fiveforths-longan-nano-lite.bin](https://github.com/aw/fiveforths/releases/download/v0.3/fiveforths-longan-nano-lite.bin) (64K Flash, 20K RAM)
25-
* [fiveforths-longan-nano.bin](https://github.com/aw/fiveforths/releases/download/v0.3/fiveforths-longan-nano.bin) (128K Flash, 32K RAM)
26+
* [fiveforths-longan-nano-lite.bin](https://github.com/aw/fiveforths/releases/download/v0.4/fiveforths-longan-nano-lite.bin) (64K Flash, 20K RAM)
27+
* [fiveforths-longan-nano.bin](https://github.com/aw/fiveforths/releases/download/v0.4/fiveforths-longan-nano.bin) (128K Flash, 32K RAM)
2628

2729
See the [TUTORIALS](docs/TUTORIALS.md) for detailed download and flashing information.
2830

@@ -43,6 +45,12 @@ Please create a pull-request or [open an issue](https://github.com/aw/picolisp-k
4345

4446
# Changelog
4547

48+
## 0.4 (2023-01-23)
49+
50+
* Fix issue #16 - Add the ability to read hex numbers
51+
* Update documentation
52+
* Add example for toggling an LED
53+
4654
## 0.3 (2023-01-19)
4755

4856
* Fix issue #7 - Implement bounds checks for stacks

docs/HOWTO.md

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ This document provides more detailed information on build, use, and write code f
1212
2. [Rebuilding the firmware](#rebuilding-the-firmware)
1313
3. [Debug with JTAG](#debug-with-jtag)
1414
4. [Defining words (Forth)](#defining-words)
15-
5. [Adding primitives (Assembly)](#adding-primitives)
15+
5. [Toggle an LED](#toggle-an-led)
16+
6. [Adding primitives (Assembly)](#adding-primitives)
1617

1718
### Building for other boards
1819

@@ -93,38 +94,72 @@ info all-registers
9394
Accessing _FiveForths_ through the terminal should look similar to this:
9495

9596
```
97+
$ pyserial-miniterm --eol LF /dev/ttyUSB0 115200
9698
--- Miniterm on /dev/ttyUSB0 115200,8,N,1 ---
9799
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
98-
FiveForths v0.3, Copyright (c) 2021~ Alexander Williams, https://a1w.ca
100+
FiveForths v0.4, Copyright (c) 2021~ Alexander Williams, https://a1w.ca
99101
100102
```
101103

102-
Some basic words can then be defined (borrowed from [derzforth prelude.forth](https://github.com/theandrew168/derzforth/blob/main/lexicons/prelude.forth)):
104+
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)):
103105

104106
```
105107
: dup sp@ @ ;
106-
: invert dup nand ;
108+
: invert -1 nand ;
107109
: negate invert 1 + ;
108110
: - negate + ;
109111
: drop dup - + ;
110-
: over sp@ 4 - @ ;
111-
: swap over over sp@ 12 - ! sp@ 4 - ! ;
112+
: over sp@ 4 + @ ;
113+
: swap over over sp@ 12 + ! sp@ 4 + ! ;
112114
: nip swap drop ;
113115
: 2dup over over ;
114116
: 2drop drop drop ;
115117
: and nand invert ;
116-
: or invert swap invert and invert ;
118+
: or invert swap invert nand ;
117119
: = - 0= ;
118120
: <> = invert ;
119121
: , here @ ! here @ 4 + here ! ;
120-
: immediate latest @ 4 + dup @ 2147483648 or swap ! ;
122+
: immediate latest @ 4 + dup @ 0x80000000 or swap ! ;
121123
: [ 0 state ! ; immediate
122124
: ] 1 state ! ;
123125
: branch rp@ @ dup @ + rp@ ! ;
124126
```
125127

126128
Of course, it is possible to define many other words to suit your needs.
127129

130+
### Toggle an LED
131+
132+
The following code can be used to turn on the green and blue LEDs on GPIOA pins 1 and 2:
133+
134+
```
135+
: green_led_on 0x40010800 @ 0xFFFFFF0F and 0x00000030 or 0x40010800 ! ;
136+
: blue_led_on 0x40010800 @ 0xFFFFF0FF and 0x00000300 or 0x40010800 ! ;
137+
green_led_on
138+
blue_led_on
139+
```
140+
141+
And to turn off the same LEDs:
142+
143+
```
144+
: green_led_off 0x40010800 @ 0xFFFFFF0F and 0x00000040 or 0x40010800 ! ;
145+
: blue_led_off 0x40010800 @ 0xFFFFF0FF and 0x00000400 or 0x40010800 ! ;
146+
green_led_off
147+
blue_led_off
148+
```
149+
150+
This requires the above defined words: `invert, over, swap, and, or`.
151+
152+
To explain the values:
153+
154+
* `0x40010800`: GPIOA base address with offset `0x00` for `CTL0` pins 0-7 (would be `CTL1` with offset `0x04` for pins 8-15).
155+
* `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`).
156+
* `0x00000030`: GPIO pin 1 setting `0b0011` which is `push-pull output, max speed 50MHz`.
157+
* `0x00000040`: GPIO pin 1 setting `0b0100` which is `floating input`.
158+
* `0x00000300`: GPIO pin 2 setting `0b0011` which is `push-pull output, max speed 50MHz`.
159+
* `0x00000400`: GPIO pin 2 setting `0b0100` which is `floating input`.
160+
161+
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).
162+
128163
### Adding primitives
129164

130165
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`:

docs/REFERENCE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ Below is a list of specifications for _FiveForths_, most can be changed in the s
3636
* Stack effects comments support `( x -- x )`: **yes**
3737
* Stack and memory overflow/underflow protection: **yes**
3838
* Backslash comments support `\ comment`: **yes**
39+
* Decimal string number input support `12345` and `-202`: **yes**
40+
* Hex string number input support `0xCAFE4241` and `-0xCA`: **yes**
3941
* Multiline code definitions support: **no**
4042
* OK message: `" ok\n"`
4143
* ERROR message: `" ?\n"`
@@ -182,6 +184,7 @@ This document would be incomplete without listing other Forths which inspired me
182184
* [nasmjf, the devlog idea and well documented](http://ratfactor.com/nasmjf/)
183185
* [CamelForth, by Brad Rodriguez (Moving Forth)](http://www.camelforth.com)
184186
* [muforth, the sum of all Forth knowledge](https://muforth.nimblemachines.com/)
187+
* [planckforth, how to bootstrap a Forth](https://github.com/nineties/planckforth/)
185188

186189
Additional information can be found in the [devlogs](https://aw.github.io/fiveforths).
187190

docs/TUTORIALS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ It is possible to download a pre-built firmware binary, or build the firmware ma
5454

5555
Download one of the firmware binaries from the [releases page](https://github.com/aw/fiveforths/releases).
5656

57-
* [fiveforths-longan-nano-lite.bin](https://github.com/aw/fiveforths/releases/download/v0.3/fiveforths-longan-nano-lite.bin) (64K Flash, 20K RAM)
58-
* [fiveforths-longan-nano.bin](https://github.com/aw/fiveforths/releases/download/v0.3/fiveforths-longan-nano.bin) (128K Flash, 32K RAM)
57+
* [fiveforths-longan-nano-lite.bin](https://github.com/aw/fiveforths/releases/download/v0.4/fiveforths-longan-nano-lite.bin) (64K Flash, 20K RAM)
58+
* [fiveforths-longan-nano.bin](https://github.com/aw/fiveforths/releases/download/v0.4/fiveforths-longan-nano.bin) (128K Flash, 32K RAM)
5959

6060
### Build it
6161

@@ -87,7 +87,7 @@ $ make
8787

8888
Additional build options are explained in the [HOWTO](HOWTO.md) section.
8989

90-
The firmware file is called `fiveforths.bin` and is **nearly 2.5 KBytes** as of _release v0.3_ since _January 19, 2023_.
90+
The firmware file is called `fiveforths.bin` and is **approximately 2.5 KBytes** as of _release v0.4_ since _January 23, 2023_.
9191

9292
### Flash it
9393

src/05-internal-functions.s

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,41 +50,60 @@ token_done:
5050

5151
# convert a string token to a 32-bit integer
5252
# arguments: a0 = token buffer start address, a1 = token size (length in bytes)
53-
# returns: a0 = integer value, a1 = 1=OK, 0=ERROR
53+
# returns: a0 = integer value, a1 = 0 = OK, 1 or greater = ERROR
5454
number:
55-
li t1, 10 # initialize temporary to 10: multiplier
55+
li t1, 10 # initialize temporary to 10: number base (decimal)
5656
mv t0, zero # initialize temporary to 0: holds the final integer
5757
li t3, CHAR_MINUS # initialize temporary to minus character '-'
5858
mv t4, zero # initialize temporary to 0: sign flag of integer
5959
lbu t2, 0(a0) # load first character from W working register
60-
bne t2, t3, number_digit # jump to number digit loop if the first character is not a minus sign
60+
bne t2, t3, number_check # jump to number check if the first character is not a minus sign
61+
6162
# first character is a minus sign, so the number will be negative
6263
li t4, 1 # number is negative, store a 1 flag in temporary
6364
addi a0, a0, 1 # increment buffer address by 1 character
64-
addi a1, a1, -1 # decrease buffer size by 1
65+
addi a1, a1, -1 # decrement the string length by 1
66+
beqz a1, number_error # jump to error if the number is only a minus '-'
67+
number_check:
68+
li t3, 0x00007830 # load the '0x' string into temporary
69+
lhu t2, 0(a0) # load the first 2 characters into temporary
70+
bne t2, t3, number_digit # jump to number digit loop if the first 2 characters are not '0x'
71+
72+
# first 2 characters are '0x', so let's assume the rest are hex digits
73+
li t1, 16 # initialize temporary to 16: number base (hex)
74+
addi a0, a0, 2 # increment buffer address by 2 characters
75+
addi a1, a1, -2 # decrement the string length by 2
76+
beqz a1, number_error # jump to error if the number is only '0x'
6577
number_digit:
66-
# check if the character in the token is a digit between "0" (0x30) and "9" (0x39)
67-
# if we take the digit and subtract 0x30 and the result is < 0, then it's not a digit (error)
68-
# if we take the digit and subtract 0x30 and the result is > 9, then it's not a digit (error)
69-
# otherwise it's a digit (loop)
7078
beqz a1, number_done # if the size of the buffer is 0 then we're done
79+
mul t0, t0, t1 # multiply the number by the number base (10 or 16)
7180
lbu t2, 0(a0) # load next character into temporary
72-
addi t2, t2, -0x30 # subtract 0x30 from the character
73-
bgeu t2, t1, number_error # check if character is < 0 or >= 10
74-
mul t0, t0, t1 # multiply previous number by 10 (base 10)
75-
add t0, t0, t2 # add previous number to current digit
7681
addi a0, a0, 1 # increment buffer address by 1 character
77-
addi a1, a1, -1 # decrease buffer size by 1
78-
j number_digit # loop to check the next character
79-
number_error:
80-
li a1, 0 # number is too large or not an integer, return 0
81-
ret
82+
83+
# convert the character to a number
84+
sltiu t3, t2, 0x30 # set the result in t3 if the character is lower than '0'
85+
bnez t3, number_done # we're done if it's not a digit!
86+
addi t2, t2, -0x30 # subtract '0' from the character
87+
sltiu t3, t2, 10 # set the result in t3 if the character is lower than 10
88+
bnez t3, number_number # the character is a number (0-9)
89+
sltiu t3, t2, 0x41-0x30 # set the result in t3 if the character is lower than 17
90+
bnez t3, number_done # we're done if it's not a letter!
91+
addi t2, t2, -7 # subtract 7 from the character to convert ascii to hex
92+
number_number:
93+
slt t3, t2, t1 # set the result in t3 if it's lower than the base (10 or 16)
94+
beqz t3, number_done # we're done if it's not a number (0-9) or (0-F)
95+
add t0, t0, t2 # add previous number to current digit
96+
addi a1, a1, -1 # decrement the string length by 1
97+
bnez a1, number_digit # loop to check the next character if the length is > 0
8298
number_done:
8399
beqz t4, number_store # don't negate the number if it's positive
84100
neg t0, t0 # negate the number using two's complement
85101
number_store:
102+
# the value in a1 will be greater than 0 if it wasn't a valid number
86103
mv a0, t0 # copy final number to W working register
87-
li a1, 1 # number is an integer, return 1
104+
ret
105+
number_error:
106+
li a1, 1 # number is too large or not an integer, return 1
88107
ret
89108

90109
# search for a hash in the dictionary

src/06-initialization.s

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,4 @@ tib_zerofill:
7373
tib_done:
7474
j interpreter_start # jump to the main interpreter REPL
7575

76-
msg_boot: .ascii "FiveForths v0.3, Copyright (c) 2021~ Alexander Williams, https://a1w.ca \n\n"
76+
msg_boot: .ascii "FiveForths v0.4, Copyright (c) 2021~ Alexander Williams, https://a1w.ca \n\n"

src/08-forth-primitives.s

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ defcode "key", 0x0388878e, KEY, EXIT
8888
# emit ( x -- ) Write 8-bit character to uart output
8989
defcode "emit", 0x04964f74, EMIT, KEY
9090
checkunderflow 0 # check for stack underflow of data stack (1 CELL)
91+
li a0, CHAR_SPACE # load space character into W
92+
call uart_put # send character from W to uart
9193
POP a0 # copy top of data stack into W
9294
call uart_put # send character from W to uart
9395
NEXT

src/09-interpreter.s

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ process_token:
9494
mv t5, a0 # save a0 temporarily
9595
mv t6, a1 # save a1 temporarily
9696
call number # try to convert the token to an integer
97-
bnez a1, push_number # push the token to the stack or memory if it's a number
97+
beqz a1, push_number # push the token to the stack or memory if it's a number
9898
mv a0, t5 # restore a0
9999
mv a1, t6 # restore a1
100100

0 commit comments

Comments
 (0)