Code Monkey home page Code Monkey logo

derzforth's Introduction

DerzForth

Bare-metal Forth implementation for RISC-V RV32I core.

About

Forth was initially designed and created by Charles Moore. Many folks have adapted its ideas and principles to solve their own problems. Moving Forth by Brad Rodriguez is an amazing source of Forth implementation details and tradeoffs. If you are looking for some introductory content surrounding the Forth language in general, I recommend the book Starting Forth by Leo Brodie.

This implementation's general structure is based on Sectorforth by Cesar Blum. He took inspiration from a 1996 Usenet thread wherein folks discussed requirements for a minimal yet fully functional Forth implementation.

Requirements

The hardware requirements for running DerzForth are minimal and straightforward:

  • At least 16KB of RAM (define RAM_BASE_ADDR and RAM_SIZE)
  • At least 16KB of ROM (define ROM_BASE_ADDR and ROM_SIZE)
  • Serial UART (implement serial_init, serial_getc, and serial_putc)

DerzForth has been tested on the following RISC-V development boards:

Setup

If you are unfamiliar with virtual environments, I suggest taking a brief moment to learn about them and set one up. The Python docs provide a great tutorial for getting started with virtual environments and packages.

DerzForth is an assembly program based on the Bronzebeard project. Consult Bronzebeard's project page for how to get it all setup (it's pretty easy and works on all major platforms).

Bronzebeard (and a few other tools) can be installed via pip:

pip install -r requirements.txt

Boards

Some boards require a USB to UART cable in order to program and/or interact. I recommend the CP2012.

Longan Nano [Lite]

For this board, the only setup necessary is a USB to UART cable.

  • Attach TX to pin R0 (PA10)
  • Attach RX to pin T0 (PA9)
  • Attach GND to pin GND
  • Attach 3.3V to pin 3V3 (be sure not to supply 5V to 3.3V or vice versa)

Wio Lite

For this board, the only setup necessary is a USB to UART cable.

  • Attach TX to pin PA10
  • Attach RX to pin PA9
  • Attach GND to GND
  • Attach 3.3V to 3V3 (be sure not to supply 5V to 3.3V or vice versa)

GD32 Dev Board

For this board, the only setup necessary is a USB to UART cable.

  • Attach TX to pin RXD (PA10)
  • Attach RX to pin TXD (PA9)
  • Attach GND to GND
  • Attach 3.3V to 3V3 (be sure not to supply 5V to 3.3V or vice versa)

HiFive1 Rev B

Programming this board requires Segger's J-Link software. These tools work on all major platforms but depend on Java.

As far as cables go, just a single USB to Micro-USB cable is necessary.

  • Plug the Micro-USB cable into the Micro-USB port

Build

With Bronzebeard installed:

bronzebeard -c -i boards/<target_board>/ --include-definitions derzforth.asm

Program

Some boards share a common method of programming and interacting.

GD32VF103 Boards

Enable boot mode on your given device:

  • Longan Nano - press BOOT, press RESET, release RESET, release BOOT
  • Wio Lite - flip BOOT switch to 1, press and release RESET
  • GD32 Dev Board - swap BOOT0 jumper to 3V3, press and release RESET, swap BOOT0 jumper to GND

To get a list of available serial ports, run the following command:

python3 -m serial.tools.list_ports

Then, program the device over serial UART:

stm32loader -p <device_port> -ewv bb.out

Here are some examples:

# Windows
stm32loader -p COM3 -ewv bb.out
# macOS
stm32loader -p /dev/cu.usbserial-0001 -ewv bb.out
# Linux
stm32loader -p /dev/ttyUSB0 -ewv bb.out

FE310-G002 Boards

After converting the output binary to Intel HEX format, Segger J-Link handles the rest:

bin2hex.py --offset 0x20010000 bb.out bb.hex
JLinkExe -device FE310 -if JTAG -speed 4000 -jtagconf -1,-1 -autoconnect 1 scripts/hifive1_rev_b.jlink

Execute

GD32VF103 Boards

After programming, put the device back into normal mode:

  • Longan Nano - press and release RESET
  • Wio Lite - flip BOOT switch to 0, press and release RESET
  • GD32 Dev Board - TODO how does this board work?

FE310-G002 Boards

The J-Link command from the previous step will automatically reset the chip after programming!

Interact

To interact with the device, the same port as above can used with pySerial's builtin terminal:

python3 -m serial.tools.miniterm <device_port> 115200

Here are some examples:

# Windows
python3 -m serial.tools.miniterm COM3 115200
# macOS
python3 -m serial.tools.miniterm /dev/cu.usbserial-0001 115200
# macOS (J-Link Serial over USB)
python3 -m serial.tools.miniterm /dev/cu.usbmodem0009790147671 115200
# Linux
python3 -m serial.tools.miniterm /dev/ttyUSB0 115200

Primitive Words

This minimal selection of primitive words is used to bootstrap the Forth system.

Word Stack Effects Description
: ( -- ) Start the definition of a new secondary word
; ( -- ) Finish the definition of a new secondary word
@ ( addr -- x ) Fetch memory contents at addr
! ( x addr -- ) Store x at addr
sp@ ( -- sp ) Get pointer to top of data stack
rp@ ( -- rp ) Get pointer to top of return stack
0= ( x -- flag ) -1 if top of stack is 0, 0 otherwise
+ ( x y -- z ) Sum the two numbers at the top of the stack
nand ( x y -- z ) NAND the two numbers at the top of the stack
key ( -- x ) Read ASCII character from serial input
emit ( x -- ) Write ASCII character to serial output

derzforth's People

Contributors

aw avatar mlg556 avatar theandrew168 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

derzforth's Issues

Split out docs by board

Group the necessary setup, build, program, and interact steps out by board. Some sections will be duped but that's fine. Still cleaner than the weird flow currently in the README.

Consider shrinking the word size for dict entries

Currently, dictionary entries have their entire name spelled out and aligned to a 4-byte boundary. While this is no big deal for words with small names, it starts to really consume Flash / RAM as words get larger. For example, the word name alone for GPIO_CTL_OUT_ALT_OPEN_DRAIN occupies 28 bytes!

There are already strategies in the Forth world to mitigate this issue. Some use a standard 4-byte header to detail the word's length (1 byte) and then the first 3 characters of the name (3 bytes). This method is detailed in the book Threaded Interpretive Languages. This would lead to a high number of collisions for similarly-prefixed words, however.

I wonder if a simple 32-bit string hash would be better fit here? Maybe something like the basic hash function described in TPOP? It'd be fairly easy to implement in assembly, too. I'll do some basic testing to see what sort of collision issues I'd have for the words I currently define to utilize the RCU and GPIO.

Forgot a _BIT ?

Hi,

Thanks for that Forth, is a reference for riscv boards.

There is no file file GD32VF103.asm in sources, then I get one from github, but usart control bits have a sufix _BIT, as

USART_STAT_RBNE_BIT = 5
USART_STAT_TBE_BIT = 7

in file https://github.com/theandrew168/derzforth/blob/main/boards/gd32_dev_board/board.asm, there is no suffix, then maybe could changed for

line 98, andi t1, t1, USART_STAT_RBNE # isolate read buffer not empty (RBNE) bit
to
line 98, andi t1, t1, USART_STAT_RBNE_BIT # isolate read buffer not empty (RBNE) bit

line 112, andi t1, t1, USART_STAT_TBE # isolate transmit buffer empty (TBE) bit
to
line 112, andi t1, t1, USART_STAT_TBE_BIT # isolate transmit buffer empty (TBE) bit

Thanks.

Alvaro

Please explain dictionary entry header

I'm trying to understand derzforth's word header but I can't seem to grok how you're defining it.

The sectorforth's diagram of a word's dictionary entry looks like this:

; Each dictionary entry is laid out in memory as such:
;
; *--------------*--------------*--------------*--------------*
; | Link pointer | Flags+Length |    Name...   |    Code...   |
; *--------------*--------------*--------------*--------------*
;     2 bytes         1 byte      Length bytes     Variable
;

I made a diagram for derzforth:

+--------------+-------+-----------+------------+
| Link pointer | Flags | Word Name | Code Field |
---------------+-------+-----------+------------+
  32-bits        2 bits  30 bits     32-bits

My questions to better understand derzforth:

  • Can you confirm if this is accurate?
  • Why is the length not saved in the header as in other Forths?
  • Why are the flags part of the Name instead of a separate byte/word?
  • Why is the Code field always enter?

I think with this knowledge I'll be better positioned to contribute here.

Thanks!

Ideas for fixing variables

As discussed previously, variables STATE, HERE, LATEST, TIB should be stored at memory addresses, not in registers.

Below are a few ideas which could help you:

  1. Store each variable at a memory location right below TIB_BASE_ADDR = 0x3000:
TIB = TIB_BASE_ADDR - 4
LATEST = TIB - 4
HERE = LATEST - 4
STATE = HERE - 4
  1. Since the values are in memory instead of registers, the body_fun needs a small change, example:
body_latest:
    li t0, LATEST # load memory address into temporary
    lw t0, 0(t0) # load address stored at LATEST into temporary
    sw t0, 0(DSP) # store address to top of data stack
    addi DSP, DSP, 4 # increment data stack by 32-bits
    j next
  1. Repeat for body_state, body_tib, etc... Well you know, there's quite a few other places this is needed, but if you had macros you could write one which performs those first two instructions (li and lw), so anywhere derzforth.asm makes use of LATEST, STATE, HERE, TIB could be replaced with t0 or something like that.. just a thought (I realize it's not going to be that simple).

I don't know if the above code is correct since I haven't tested it, but I think that's the idea.

Can beqz simply jump to 0(ra) ?

Hi,

In looking at the functions such as memclr and memcpy, I noticed there's a line like this:

memclr:
    beqz a1, memclr_done  # loop til size == 0
    ...
memclr_done:
    ret

I wonder if it's necessary to jump to a label which only performs a ret .. jalr zero, 0(ra).

Could that label simply be omitted and the code changed to this?:

memclr:
    beqz a1, 0(ra)  # loop til size == 0 then return

I haven't made a PR because I thought maybe there was a reason you did it that way.

Single-line comment support

Currently, the Forth interpreter doesn't properly ignore comments (both backslash line comments and paren-range comments). Ignoring them shouldn't be too hard to do directly in assembly. In the future, comments could possibly be implemented in Forth itself (if that'd even be necessary).

General refactor

Once Bronzebeard supports compressed instruction optimizations, make a pass through the DerzForth assembly code to better prepare it for compression. Optimize the register selection as well based on the C extension's "popular" registers. Consider flipping the stack direction but only slightly. Also simplify how procedures are handled: no need to hardcode each arg / ret register if they never nest (or only nest a couple times).

Verify that the existing examples still work.

How to effectively test the project

I've been thinking about how I could verify that DerzForth "works" and that new changes didn't break anything. I think there are a couple of routes:

  1. Find a simulator that works and abstract out the IO-specific portions
  2. Test on a real Longan Nano / Wio Lite and use something like pexpect to test commands / responses

Either way, having something in place to verify behavior will definitely pay itself off as new features / optimizations are introduced.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.