If you’re here, it’s because you want to try using muforth to explore the world of RISC-V.
Currently this means that you have a HiFive1 board; it is, at the moment, the only RISC-V hardware that muforth supports.
After getting a HiFive1, the next hurdle you have to jump is getting a RISC-V-aware copy of
openocd. (Read that page and then come back here.)
You do not need a GCC toolchain! Yay! Jump up and down and shout with glee!
openocd is the only external tool that muforth needs in order to run code on a HiFive1.
Once it is built, cd to
muforth/mu/. You should always be in this directory when running muforth code.
Edit the file
target/RISC-V/start-openocd.sh and change the
openocd variable so it references your local copy of the RISC-V
If you are on a Mac and running Mavericks or later, unload the Apple FTDI driver; otherwise it will interfere with JTAG:
sudo kextunload /System/Library/Extensions/AppleUSBFTDI.kext/
Plug your HiFive1 into a USB port. Open a second terminal window, leaving the first one sitting in
muforth/mu. cd to
muforth/mu in this window as well. Then type the following command to start up
Back in the first
muforth/mu window type this:
./muforth -f target/RISC-V/build.mu4
This will load the RISC-V meta-compiler and the RISC-V Forth kernel (which currently loads into RAM). To connect to the board via JTAG, type
You should see a dump of four registers, all zero except for SP. You are now connected to the target board!
To see some of the kernel code that was loaded into RAM, try this:
This will start disassembling the beginning of the Forth kernel. All memory dumping and disassembly in muforth is interactive; pressing <RET> or n will advance; pressing <BS> or p will back up; q will quit, leaving the last-viewed address on the stack.
@ram is a constant; executing it pushes the address of the start of RAM (“at-ram”).
You can also look at built-in features. The Debug ROM is interesting. To see it type
To try actually executing some code, try this:
10305 2040 +
This pushes two hex values onto the stack, copies this stack over to the target, executes the word
+ on the target, which adds them and pushes their sum, and then copies that back to the target. You should see the result in the stack dump.
To list the words in the target kernel, try this:
Something more interesting: Let’s write a word to read the value of one of the chip’s CSRs. This one –
misa – tells us which base ISA – RV32 or RV64 – and which extensions the chip supports.
code read-misa misa w csrr wpush j ;c read-misa
The first line defines
read-misa as a “code” word. This means that its definition is entirely in RISC-V assembler. It consists of just two instructions: one to read the CSR into the w register (an alias for ABI register t0), and one to jump to a routine that pushes w onto the stack and then executes NEXT. (On an ITC Forth all code words have to end by executing NEXT, directly or indirectly.)
The second lines executes our new word, and the result should show up on the stack. By reading the privileged ISA spec (see RISC-V resources) we can peel this apart. But first, let’s print its value in binary to make it easier to see:
binary u. hex
This switches the input and output radix to binary, prints as an unsigned number the top of the stack (the contents of
misa), then switches back to hex.
u. doesn’t print leading zeros, so it’s hard to tell that the top two bits – which define the base ISA – are
01 – meaning RV32. The low order 26 bits each correspond to an ISA extension; the bit is set when that extension is present. I read the result as showing that IMAC are all present – exactly what we would expect from the FE310.
Next, let’s try a simple “colon” word. A colon word is any word defined by
: whose body consists of other Forth words. Most Forth code is colon words rather than code words.
: bic ( value mask - result) invert and ;
bic is the logical “bit clear” operation. It takes two parameters – shown in the “stack comment” (in parentheses) on the left of the dash – and produces one result – shown to the right of the dash. The first word,
invert, does a ones complement of the mask; this is then
anded with the value. Any one bits in the mask clear the corresponding bit in the value. As usual, we see the result on the stack dump (which is always in hex, regardless of the setting of the input/output radix – an odd quirk of muforth).
Colon words always end with a
;. When execution reaches this point, the word returns to its caller.
Let’s try it.
c0defeed ffff0000 bic
Hopefully the resulting value makes sense.
A couple of notes about numbers in muforth. Any number can be prefixed by a radix operator, which changes the radix for that number only. The operators, and their resulting radices, are:
% binary ' octal " hexadecimal # decimal
These can be handy if you are writing an assembler where most values are in octal but need to switch to another radix to enter a single value.
Following the (optional) radix operator can be an optional sign operator: the usual “-” character.
Another thing: numbers are generally printed with separators. This makes them much easier to parse. They can also be input with separators. Any of the following characters is a valid separator:
. , : / _ -
You should also realize that numbers are 64-bit on the host stack (in muforth) but truncated to 32-bits when copied over to the target.
Our stack has now accumulated some junk. You can get rid of it by typing
over and over. Each execution of
. prints as a signed value the top of the stack, thus removing one value. If there is a ton of junk on the stack, the word
sp-reset clears the stack in one go.
Forth has both definite and indefinite loops. Definite loops are based on a count; indefinite loops loop until some condition becomes true. Let’s look each in turn.
There are two kinds of definite loops: for/next and do/loop. We use for/next when we want to iterate a known number of times and don’t care about the value of the loop index. (It’s hidden from our code, but the loop index counts down to zero from the given starting value.) Let’s try two versions of for/next: one that simply does a calculation, and one that has a “bug” in it, so it stops every time through to let us look around.
: lshift ( n #shifts) for 2* next ; 0abcd 4 lshift
We’ve included a stack comment to remind us what’s going on here. (We are basically re-implementing
<< in Forth.) Since
2* shifts left by one bit, the loop will shift left by the number of bits specified.
Now the bugged version:
: lshift-bug ( n #shifts) for 2* bug next ; 0abcd 4 lshift-bug
When you first execute
lshift-bug you’ll see that the IP register has a
* next to it. This means that execution of your word did not complete. We are somewhere in the body of word (in this case,
lshift-bug) and we can look around. There is not much to see; however, the value on the stack has been shifted left by one bit. Now type
to continue execution. The same thing will happen. IP will be starred, and the value on the stack will have been shifted left again. Keep typing
cont until IP is unstarred. Execution has completed and we should see the same result on the stack that we got with
One note here: muforth has primitive but very useful command history. You can use the up and down arrow keys to navigate the command history, and the left and right arrow keys to move around and change a previously-entered line of text. Pressing enter will execute the edited version. If you use any command-line shell, the behavior should feel familiar. (And for the inveterately curious, this is implemented entirely in Forth! See the file lib/editline.mu4 for the gory details. It’s a whopping 175 lines of code!)
The second kind of definite loop is the
do loop. It consumes two values from the stack – a limit and an index – and loops until the index reaches or crosses the limit. In the body of the loop the word
i pushes the current loop index onto the stack.
A do loop always starts with
do but can end with either
loop increments the index by 1;
+loop consumes a stack value and increments the index by that value. Since it will be hard to see what’s going on if we let these run free, let’s only try the bugged versions.
: doit ( limit index) do i bug drop loop ; 5 0 doit
This should count up – starting at index, continuing until index reaches limit – from 0 to 4.
i will push the current index,
bug will stop so we can see it on the stack, and then
drop loop will throw away the current index value, and loop again, or exit.
As before, keep typing
cont until IP is unstarred. Try some other limits and indices and see what happens.
: doit+ ( incr limit index) do i bug drop dup +loop drop ; -3 -20 -10 doit+
Note that we are passing an increment to
doit+ (the -3 value) and using
dup to make a copy of it before calling
+loop so we can use it the next time around as well. Since at the end of the loop the increment will still be sitting on the stack, we
drop when we are done.
As before, keep typing
cont until IP is unstarred. Try some other increments, limits, and indices and see what happens. Since the stack dump shows unsigned hex values it can be hard to see what’s going on here. The first time you stop at the bug, try doing
to see the increment value (-3). And every time you stop at the bug, try
to see the current loop index.
. will sign-extend the 32-bit target value to 64-bits and then print it as a signed number. This should help. Remember too: all those loop values are in hexadecimal! So, -10 is really -16 (decimal), and -20 is really -32 (decimal).
Does the behavior of
doit+ make sense? Try different values of the increment, limit, and index.
Using negative increments with do loops seems to have odd behavior in that the idea of “reaching or crossing” the limit seems different from the behavior with positive increments.
The behavior that you see here conforms to “standard” Forth practice, and is essentially a result of the implementation: the index value is translated (shifted along the integer “number line”) so that the loop terminates when the translated index crosses the threshold from
ffff_ffff (when counting down) or from
0000_0000 (when counting up). You can see the translated index in the IX register during loop execution.
So what about indefinite loops? There are two varieties of these as well: one that always executes the loop body at least once (like C’s do...while) and one that tests the termination condition at or near the beginning (like C’s while). Here they are:
begin <loop body> <condition> until begin <condition> while <loop body> repeat
There is also an unending version that we won’t be trying out here:
begin <loop body> again
which is useful for the main loop of an embedded application.
I’m going to write “bugged” versions of these. Feel free to try them with or without the bug. Try these definitions:
: overflow ( n - n') begin 2* bug dup 0< until ; 800_0000 overflow
0< consumes the value on the stack (hence we
dup it first) and pushes -1 (true) if the value is less than 0, and 0 (false) otherwise.
Don’t pass 0 to
: till-even ( n - n') begin dup 1 and bug 0= while u2/ repeat ; 47 till-even
A bit of explanation.
0= is like
0<: it consumes a value and pushes -1 if it is equal to zero and 0 otherwise.
dup 1 and makes a copy of a value and then tests the low-order bit (which is 1 if the value is odd).
u2/ does an unsigned right shift by one bit. (
2/ is the signed version.) I’ve placed the bug to show both the value being tested and its low-order bit.
Remember: the stack is showing hex values. If you really want to see what’s going on, set the radix to
binary, and use
over u. to copy and print the value on the stack each time through the loop. (
over makes a copy of the second value on the stack, where
dup copies the top value.)
I hope this whets your appetite for more! There is a lot more coming. See RISC-V support to see the current state of things.
I’ve compiled a page with lots of RISC-V resources.