From d3e2407f3b6144e659d3e03ccf5cb3e715113c0a Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Mon, 23 Feb 2026 17:37:30 +0000 Subject: [PATCH] Create initial emulator and hello world demo --- .gitignore | 1 + LICENSE.md | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 49 +++++++++++++++++++++++ boot.js | 6 +++ emu.js | 69 ++++++++++++++++++++++++++++++++ index.html | 12 ++++++ server.py | 16 ++++++++ styles.css | 17 ++++++++ wipforth.wat | 22 +++++++++++ 9 files changed, 300 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 boot.js create mode 100644 emu.js create mode 100644 index.html create mode 100644 server.py create mode 100644 styles.css create mode 100644 wipforth.wat diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19e1bce --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.wasm diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..59e5aad --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,108 @@ +# Komorebi License + +Version 2.0.0 + +## Acceptance + +In order to get any license under these terms, you must agree +to them as both strict obligations and conditions to all +your licenses. + +## Copyright License + +The licensor grants you a copyright license for the software +to do everything you might do with the software that would +otherwise infringe the licensor's copyright in it for any +permitted purpose. However, you may only distribute the source +code of the software according to the [Distribution License]( +#distribution-license), you may only make changes according +to the [Changes License](#changes-license), and you may not +otherwise distribute the software or new works based on the +software. + +## Distribution License + +The licensor grants you an additional copyright license to +distribute copies of the source code of the software. Your +license to distribute covers distributing the source code of +the software with changes permitted by the [Changes License]( +#changes-license). + +## Changes License + +The licensor grants you an additional copyright license to +make changes for any permitted purpose. + +## Patent License + +The licensor grants you a patent license for the software that +covers patent claims the licensor can license, or becomes able +to license, that you would infringe by using the software. + +## Personal Uses + +Personal use for research, experiment, and testing for +the benefit of public knowledge, personal study, private +entertainment, hobby projects, amateur pursuits, or religious +observance, without any anticipated commercial application, +is use for a permitted purpose. + +## Fair Use + +You may have "fair use" rights for the software under the +law. These terms do not limit them. + +## No Other Rights + +These terms do not allow you to sublicense or transfer any of +your licenses to anyone else, or prevent the licensor from +granting licenses to anyone else. These terms do not imply +any other licenses. + +## Patent Defense + +If you make any written claim that the software infringes or +contributes to infringement of any patent, your patent license +for the software granted under these terms ends immediately. If +your company makes such a claim, your patent license ends +immediately for work on behalf of your company. + +## Violations + +The first time you are notified in writing that you have +violated any of these terms, or done anything with the software +not covered by your licenses, your licenses can nonetheless +continue if you come into full compliance with these terms, +and take practical steps to correct past violations, within +32 days of receiving notice. Otherwise, all your licenses +end immediately. + +## No Liability + +***As far as the law allows, the software comes as is, without +any warranty or condition, and the licensor will not be liable +to you for any damages arising out of these terms or the use +or nature of the software, under any kind of legal claim.*** + +## Definitions + +The **licensor** is the individual or entity offering these +terms, and the **software** is the software the licensor makes +available under these terms. + +**You** refers to the individual or entity agreeing to these +terms. + +**Your company** is any legal entity, sole proprietorship, +or other kind of organization that you work for, plus all +organizations that have control over, are under the control of, +or are under common control with that organization. **Control** +means ownership of substantially all the assets of an entity, +or the power to direct its management and policies by vote, +contract, or otherwise. Control can be direct or indirect. + +**Your licenses** are all the licenses granted to you for the +software under these terms. + +**Use** means anything you do with the software requiring one +of your licenses. diff --git a/README.md b/README.md new file mode 100644 index 0000000..31e030e --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# Wipforth + +## Building and Running + +To run, first compile the WebAssembly module: + +``` +wat2wasm --enable-threads wipforth.wat +``` + +Then run the server: + +``` +python3 server.py +``` + +You should then be able to open in a browser +and use the system from there. + +**NOTE**: The server is just a very simple instantiation of Python's +built-in `http.server` that sets the cross-origin headers required for +`SharedMemoryBuffer` use. You could use any HTTP server that sets +these headers. + +## Peripherals + +# Serial + +| Name | Offset | Size / B | Access | +|--------|--------|----------|--------------| +| TXBUF | 0 | 32 | write | +| RXBUF | 32 | 32 | read | +| TXDATA | 64 | 1 | atomic write | +| TXHEAD | 65 | 1 | atomic read | +| TXTAIL | 66 | 1 | atomic write | +| RXDATA | 67 | 1 | atomic read | +| RXHEAD | 68 | 1 | atomic write | +| RXTAIL | 69 | 1 | atomic read | + +For both sending (`TX`) and receiving (`RX`), there are four +registers: `xBUF`, `xDATA`, `xHEAD` and `xTAIL`: + +- `xBUF` registers are 32-byte FIFO ring buffers used for data +- The `xDATA` registers indicate whether data is available (0 for + data, FFh for no data) +- The `xHEAD` and `xTAIL` registers specify the start and end of + data in the FIFO, `xHEAD` being the offset of the first byte of + data, and `xTAIL` being the offset of the first byte after the + data. diff --git a/boot.js b/boot.js new file mode 100644 index 0000000..d757741 --- /dev/null +++ b/boot.js @@ -0,0 +1,6 @@ +self.onmessage = async (e) => { + const exports = { emu: { mem: e.data } }; + const mod = await WebAssembly.instantiateStreaming( + fetch('wipforth.wasm'), exports) + mod.instance.exports.reset(); +}; diff --git a/emu.js b/emu.js new file mode 100644 index 0000000..79a652a --- /dev/null +++ b/emu.js @@ -0,0 +1,69 @@ +const TXBUF = 0; +const RXBUF = 32; +const TXDATA = 64; +const TXHEAD = 65; +const TXTAIL = 66; +const RXDATA = 67; +const RXHEAD = 68; +const RXTAIL = 69; + +const TXBUF_SIZE = 32; +const RXBUF_SIZE = 32; +const PERIPHS_SIZE = 70; + +const POLL_INTERVAL_MS = 20; + +class Emulator { + constructor() { + this.mem = new WebAssembly.Memory({ + initial: 1, + maximum: 1, + shared: true + }); + this.mem_u8 = new Uint8Array(this.mem.buffer); + for (let i = 0; i < PERIPHS_SIZE; ++i) + this.mem_u8[i] = 0; + + this.decoder = new TextDecoder('utf-8'); + + this.output = document.getElementById('output'); + this.input = document.getElementById('input'); + + this.timer = setInterval(() => this.poll(), POLL_INTERVAL_MS); + + this.worker = new Worker('boot.js'); + this.worker.postMessage(this.mem); + } + + poll() { + const txdata = Atomics.load(this.mem_u8, TXDATA); + if (txdata !== 0) + this.handle_txdata(); + } + + handle_txdata() { + const head = Atomics.load(this.mem_u8, TXHEAD); + const tail = Atomics.load(this.mem_u8, TXTAIL); + + const data = []; + let i = head; + do { + data.push(this.mem_u8[TXBUF + i]); + i = (i + 1) % TXBUF_SIZE; + } while (i !== tail); + + Atomics.store(this.mem_u8, TXHEAD, tail); + + // More data could have been added -- only clear TXDATA if + // tail is unchanged. + if (tail === Atomics.load(this.mem_u8, TXTAIL)) + Atomics.store(this.mem_u8, TXDATA, 0); + + const str = this.decoder.decode(new Uint8Array(data)); + this.output.innerText += str; + } +} + +window.addEventListener('DOMContentLoaded', () => { + window.emu = new Emulator(); +}); diff --git a/index.html b/index.html new file mode 100644 index 0000000..4f52248 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + Wipforth + + + + +
+ + + diff --git a/server.py b/server.py new file mode 100644 index 0000000..001b78f --- /dev/null +++ b/server.py @@ -0,0 +1,16 @@ +from http.server import HTTPServer, SimpleHTTPRequestHandler +import sys + +class CORSRequestHandler(SimpleHTTPRequestHandler): + def end_headers(self): + self.send_header( + 'Cross-Origin-Opener-Policy', 'same-origin') + self.send_header( + 'Cross-Origin-Embedder-Policy', 'require-corp') + self.send_header( + 'Cache-Control', 'no-store, no-cache, must-revalidate') + super().end_headers() + +if __name__ == '__main__': + server = HTTPServer(('localhost', 8080), CORSRequestHandler) + server.serve_forever() diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..6c637c9 --- /dev/null +++ b/styles.css @@ -0,0 +1,17 @@ +body { + line-height: 1.4em; + font-family: 'Courier 10 Pitch', monospace; + font-size: 12pt; + background-color: black; + color: white; +} + +#output { + margin: 1.4em auto; + width: 80em; + height: 48em; +} + +#input { + display: none; +} diff --git a/wipforth.wat b/wipforth.wat new file mode 100644 index 0000000..ffc4b10 --- /dev/null +++ b/wipforth.wat @@ -0,0 +1,22 @@ +(module + (import "emu" "mem" (memory 1 1 shared)) + + (func (export "reset") + ;; Write message into TXBUF + (i32.store8 (i32.const 0x0) (i32.const 0x48)) ;; H + (i32.store8 (i32.const 0x1) (i32.const 0x65)) ;; e + (i32.store8 (i32.const 0x2) (i32.const 0x6c)) ;; l + (i32.store8 (i32.const 0x3) (i32.const 0x6c)) ;; l + (i32.store8 (i32.const 0x4) (i32.const 0x6f)) ;; o + (i32.store8 (i32.const 0x5) (i32.const 0x2c)) ;; , + (i32.store8 (i32.const 0x6) (i32.const 0x20)) ;; + (i32.store8 (i32.const 0x7) (i32.const 0x77)) ;; w + (i32.store8 (i32.const 0x8) (i32.const 0x6f)) ;; o + (i32.store8 (i32.const 0x9) (i32.const 0x72)) ;; r + (i32.store8 (i32.const 0xa) (i32.const 0x6c)) ;; l + (i32.store8 (i32.const 0xb) (i32.const 0x64)) ;; d + (i32.store8 (i32.const 0xc) (i32.const 0x21)) ;; ! + + ;; Update TXTAIL and set TXDATA + (i32.atomic.store8 (i32.const 0x42) (i32.const 0xd)) + (i32.atomic.store8 (i32.const 0x40) (i32.const 0xff))))