commit d3e2407f3b6144e659d3e03ccf5cb3e715113c0a Author: Camden Dixie O'Brien Date: Mon Feb 23 17:37:30 2026 +0000 Create initial emulator and hello world demo 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))))