102 lines
3.2 KiB
Markdown
102 lines
3.2 KiB
Markdown
# Wipforth
|
|
|
|
Wipforth is a Forth implementation that runs in the WebAssembly
|
|
virtual machine. The system is bootstrapped from source on page load:
|
|
the only binary file is the favicon :)
|
|
|
|
I/O is done via memory-mapped peripherals, which are emulated in
|
|
JavaScript.
|
|
|
|
- For the Forth kernel, see [wipforth.ws](./wipforth.ws)
|
|
- For the emulator, see [emu.js](./emu.js)
|
|
- For the assembler, see [asm.js](./asm.js)
|
|
- For the prelude (Forth code loaded right after the kernel boots),
|
|
see [prelude.f](./prelude.f)
|
|
- For a description of the peripherals, see the
|
|
[Peripherals](#peripherals) section below.
|
|
|
|
## Building and Running Locally
|
|
|
|
There is simple [Guile](https://www.gnu.org/software/guile/) script
|
|
you can use for this:
|
|
|
|
```
|
|
guile server.scm
|
|
```
|
|
|
|
You should then be able to open <http://localhost:8080> in a browser
|
|
and use the system from there.
|
|
|
|
Since everything is bootstrapped on the client, you only need an HTTP
|
|
server, so if you don't have Guile on your system you can bring your
|
|
own. The only requirement is that it sets the cross-origin isolation
|
|
headers required for `SharedMemoryBuffer` use:
|
|
|
|
- `Cross-Origin-Opener-Policy: same-origin`
|
|
- `Cross-Origin-Embedder-Policy: require-corp`
|
|
|
|
You should **definitely not** use `server.scm` to serve the
|
|
application on the open internet or anything like that; I just hacked
|
|
it together for testing on localhost during development and it's
|
|
probably hilariously insecure.
|
|
|
|
## End-to-End Tests
|
|
|
|
There's a (fairly minimal at the moment) end-to-end test suite defined
|
|
in [tests.scm](./tests.scm). To run it you'll need:
|
|
|
|
- [Guile](https://www.gnu.org/software/guile/) again (no substitute
|
|
this time, sorry)
|
|
- [guile-json](https://github.com/aconchillo/guile-json)
|
|
- Firefox
|
|
|
|
I'm also pretty sure it won't work on a non-POSIX system, though I
|
|
haven't tried it.
|
|
|
|
Given that's all sorted, you should be able to run:
|
|
|
|
```
|
|
guile tests.scm
|
|
```
|
|
|
|
It will print a JUnit XML report to standard out. You can
|
|
pretty-print it with `xmllint` if you have it installed:
|
|
|
|
```
|
|
guile tests.scm | xmllint --format -
|
|
```
|
|
|
|
## Peripherals
|
|
|
|
### Terminal
|
|
|
|
| Name | Address | Size / B | Access |
|
|
|--------|---------|----------|--------------|
|
|
| TXBUF | 00h | 32 | write |
|
|
| RXBUF | 20h | 32 | read |
|
|
| TXHEAD | 40h | 4 | atomic read |
|
|
| TXTAIL | 44h | 4 | atomic write |
|
|
| RXHEAD | 48h | 4 | atomic write |
|
|
| RXTAIL | 4Ch | 4 | atomic read |
|
|
|
|
For both sending (`TX`) and receiving (`RX`), there are three
|
|
registers: `xBUF`, `xHEAD` and `xTAIL`:
|
|
|
|
- `xBUF` registers are 32-byte FIFO ring buffers used for data
|
|
- The `xHEAD` and `xTAIL` registers specify the start and end of data
|
|
in the ring buffer, `xHEAD` being the offset of the first byte of
|
|
data, and `xTAIL` being the offset of the first byte *after* the data.
|
|
|
|
In order to be distinguishable from the empty state, the ring buffers
|
|
must never be completely full -- there must always be *at least one*
|
|
unoccupied byte between the tail and the head.
|
|
|
|
### System status
|
|
|
|
| Name | Address | Size / B | Access |
|
|
|----------|---------|----------|--------------|
|
|
| SYSREADY | 50h | 1 | atomic write |
|
|
|
|
The `SYSREADY` register is used to indicate when the system has booted
|
|
up and is ready for user input.
|