Compare commits

13 Commits

8 changed files with 134 additions and 106 deletions

View File

@@ -29,7 +29,7 @@ and use the system from there.
However, since everything is bootstrapped on the client, basically any
HTTP server will do as long as it sets the appropriate response
headers for `SharedMemoryBuffer` use:
headers for `SharedArrayBuffer` use:
- `Cross-Origin-Opener-Policy: same-origin`
- `Cross-Origin-Embedder-Policy: require-corp`
@@ -74,20 +74,21 @@ guile tests.scm | xmllint --format -
| 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 |
| TXBUF | 000h | 32 | write |
| RXBUF | 080h | 32 | read |
| TXHEAD | 100h | 4 | atomic read |
| TXTAIL | 104h | 4 | atomic write |
| RXHEAD | 108h | 4 | atomic write |
| RXTAIL | 10Ch | 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
- `xBUF` registers are 128-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.
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*
@@ -97,7 +98,10 @@ unoccupied byte between the tail and the head.
| Name | Address | Size / B | Access |
|----------|---------|----------|--------------|
| SYSREADY | 50h | 1 | atomic write |
| SYSREADY | 110h | 4 | atomic write |
| SYSINTER | 114h | 4 | atomic read |
The `SYSREADY` register is used to indicate when the system has booted
up and is ready for user input.
up and is ready for user input. `SYSINTER` is set (and notified on)
once the emulator has enabled user input and the system is
interactive.

39
asm.js
View File

@@ -14,20 +14,29 @@ class Tokenizer {
this.buffer = [];
this.comment = false;
this.string = false;
this.cursor = 0;
}
find(pred) {
for (let i = this.cursor; i < this.buffer.length; ++i) {
if (pred(this.buffer[i]))
return i;
}
return null;
}
skip() {
const idx = this.buffer.findIndex((cp) => !this.skips.has(cp));
this.buffer = idx == -1 ? [] : this.buffer.slice(idx);
const idx = this.find((cp) => !this.skips.has(cp));
this.cursor = idx == null ? this.buffer.length : idx;
}
next_string() {
const idx = this.buffer.findIndex((cp) => cp == this.string_quote);
if (idx == -1) {
const idx = this.find((cp) => cp == this.string_quote);
if (idx == null) {
this.string = true;
} else {
const string = this.buffer.slice(0, idx).join("");
this.buffer = this.buffer.slice(idx + 1);
const string = this.buffer.slice(this.cursor, idx).join("");
this.cursor = idx + 1;
this.string = false;
return { string: string };
}
@@ -38,18 +47,20 @@ class Tokenizer {
return this.next_string();
this.skip();
if (this.buffer[0] == LINE_END)
return this.buffer.shift();
if (this.buffer[this.cursor] == LINE_END) {
++this.cursor;
return LINE_END;
}
if (this.buffer[0] == this.string_quote) {
this.buffer.shift();
if (this.buffer[this.cursor] == this.string_quote) {
++this.cursor;
return this.next_string();
}
const idx = this.buffer.findIndex((cp) => this.delims.has(cp));
if (idx != -1) {
const token = this.buffer.slice(0, idx).join("");
this.buffer = this.buffer.slice(idx);
const idx = this.find((cp) => this.delims.has(cp));
if (idx != null) {
const token = this.buffer.slice(this.cursor, idx).join("");
this.cursor = idx;
return token;
}
}

View File

@@ -1,5 +1,7 @@
asm.js
boot.js
emu.js
favicon.png
index.html
prelude.f
styles.css

72
emu.js
View File

@@ -1,17 +1,17 @@
const TXBUF = 0x00;
const RXBUF = 0x20;
const TXHEAD = 0x40;
const TXTAIL = 0x44;
const RXHEAD = 0x48;
const RXTAIL = 0x4c;
const SYSREADY = 0x50;
const TXBUF = 0x000;
const RXBUF = 0x080;
const TXHEAD = 0x100;
const TXTAIL = 0x104;
const RXHEAD = 0x108;
const RXTAIL = 0x10c;
const TXBUF_SIZE = 32;
const RXBUF_SIZE = 32;
const PERIPHS_SIZE = 81;
const SYSREADY = 0x110;
const SYSINTER = 0x114;
const POLL_INTERVAL_MS = 20;
const DOT_INTERVAL_MS = 120;
const PERIPHS_SIZE = 0x200;
const POLL_INTERVAL_MS = 5;
const DOT_INTERVAL_MS = 25;
const COLS = 80;
const TAB_WIDTH = 8;
@@ -51,14 +51,18 @@ class Emulator {
document.addEventListener('keydown', (e) => this.handle_keydown(e));
window.addEventListener('resize', () => this.handle_resize());
this.forth = new Worker('boot.js', { type: 'module' });
this.print("Assembling kernel ");
const dots = setInterval(() => this.print("."), DOT_INTERVAL_MS);
this.worker = new Worker('boot.js', { type: 'module' });
this.worker.postMessage({ type: "load", mem: this.mem });
this.worker.onmessage = (e) => {
clearInterval(dots);
this.dots = setInterval(() => this.print("."), DOT_INTERVAL_MS);
this.forth.postMessage({ type: "load", mem: this.mem });
this.forth.onmessage = (e) => {
clearInterval(this.dots);
this.print(" done\n");
this.worker.postMessage({ type: "boot" });
this.print("Loading prelude ");
this.forth.postMessage({ type: "boot" });
this.dots = setInterval(() => this.print("."), DOT_INTERVAL_MS);
};
fetch('prelude.f')
@@ -85,15 +89,21 @@ class Emulator {
if (!this.input_enable) {
const sysready = Atomics.load(this.mem_u8, SYSREADY);
if (sysready != 0) {
clearInterval(this.dots);
this.print(" done\n");
Atomics.store(this.mem_u8, SYSINTER, 1);
Atomics.notify(this.mem_i32, SYSINTER / 4);
this.input_enable = true;
this.blink = true;
this.flush_output();
document.getElementById('cursor').classList.add('blinking');
}
}
}
fifo_next(idx) {
return (idx + 1) & 0x1f;
return (idx + 1) & 0x7f;
}
handle_tx_data(head, tail) {
@@ -181,12 +191,14 @@ class Emulator {
}
e.preventDefault();
this.blink = false;
this.flush_output();
document.getElementById('cursor').classList.remove('blinking');
if (this.idle_timer)
clearTimeout(this.idle_timer);
this.idle_timer = setTimeout(() => {
this.blink = true;
document.getElementById('cursor').classList.add('blinking');
}, CURSOR_IDLE_TIME_MS);
}
@@ -273,10 +285,12 @@ class Emulator {
return row.map((c, x) => {
const ec = this.html_escape(c);
if (this.input_enable
&& x == this.cursor.x && y == this.cursor.y)
return '<span id="cursor">' + ec + '</span>';
else
&& x == this.cursor.x && y == this.cursor.y) {
const cl = this.blink ? 'class="blinking"' : '';
return `<span id="cursor" ${cl}>` + ec + '</span>';
} else {
return ec;
}
}).join('').trimEnd();
}).join('\n');
this.output.innerHTML = html;
@@ -333,6 +347,16 @@ class Emulator {
}
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('output').innerText = '';
const output = document.getElementById('output');
output.innerText = '';
if (!self.crossOriginIsolated) {
output.innerText = "Yeah so there's this thing where Chromium ends "
+ "up ignoring COOP/COEP\nheaders after a hard reload "
+ "sometimes, and I haven't been able to\nfigure out how to "
+ "work around it yet. If you just wait a little while\nand "
+ "then reload normally then hopefully it should work haha";
} else {
window.emu = new Emulator();
}
});

View File

@@ -7,8 +7,7 @@
</head>
<body>
<script type="text/javascript" src="emu.js"></script>
<div id="output">I'm afraid you need javascript enabled for this to work :(
<div id="output"><noscript>I'm afraid you need javascript enabled for this to work :(
cringe, I know... but I promise there are no frameworks&#8482;</div>
</body>
cringe, I know... but I promise there are no frameworks&#8482;</noscript></div></body>
</html>

View File

@@ -1,12 +1,5 @@
76 EMIT 111 EMIT 97 EMIT 100 EMIT 105 EMIT 110 EMIT 103 EMIT 32 EMIT
112 EMIT 114 EMIT 101 EMIT 108 EMIT 117 EMIT 100 EMIT 101 EMIT 32 EMIT
: \ KEY 10 = 0BRANCH [ -20 , ] ; IMMEDIATE \ Now we have line comments :)
\ We'll periodically sprinkle these in so that it's clear to the user
\ that things are happening.
46 EMIT
\ Conditionals
: IF
@@ -27,8 +20,6 @@
SWAP !
; IMMEDIATE
46 EMIT
\ Loops
: BEGIN HERE @ ; IMMEDIATE
@@ -43,8 +34,6 @@
HERE @ - ,
; IMMEDIATE
46 EMIT
\ Recursive calls
: RECURSE LATEST @ >CFA , ; IMMEDIATE
@@ -61,8 +50,6 @@
( ( Take that, C ) )
46 EMIT
\ Printing utilities
: CR 10 EMIT ;
@@ -80,8 +67,6 @@
+ EMIT
;
CHAR . EMIT
: .
\ Handle negatives
DUP 0< IF CHAR - EMIT NEGATE THEN
@@ -110,8 +95,6 @@ CHAR . EMIT
2DROP
;
CHAR . EMIT
: TYPE ( addr len -- )
BEGIN
DUP 0= IF 2DROP EXIT THEN
@@ -120,8 +103,6 @@ CHAR . EMIT
AGAIN
;
CHAR . EMIT
: C, HERE @ C! 1 HERE +! ;
: ."
@@ -151,8 +132,6 @@ CHAR . EMIT
THEN
; IMMEDIATE
CHAR . EMIT
\ Misc utilities
: NIP SWAP DROP ;
@@ -171,8 +150,6 @@ CHAR . EMIT
: [COMPILE] ' , ; IMMEDIATE
CHAR . EMIT
\ Constants, variables and values
: CONSTANT
@@ -204,24 +181,21 @@ CHAR . EMIT
THEN
; IMMEDIATE
CHAR . EMIT
\ Peripheral register addresses
HEX
00 CONSTANT TXBUF
20 CONSTANT RXBUF
40 CONSTANT TXHEAD
44 CONSTANT TXTAIL
48 CONSTANT RXHEAD
4C CONSTANT RXTAIL
50 CONSTANT SYSREADY
000 CONSTANT TXBUF
080 CONSTANT RXBUF
100 CONSTANT TXHEAD
104 CONSTANT TXTAIL
108 CONSTANT RXHEAD
10C CONSTANT RXTAIL
110 CONSTANT SYSREADY
114 CONSTANT SYSINTER
DECIMAL
46 EMIT
\ A better word-not-found handler
: ANY-RX? RXHEAD AC@ RXTAIL AC@ <> ;
@@ -244,13 +218,11 @@ DECIMAL
' WNF-HANDLER TO WNFHOOK
CHAR . EMIT
\ Version number
0 CONSTANT VERSION-MAJOR
2 CONSTANT VERSION-MINOR
1 CONSTANT VERSION-PATCH
2 CONSTANT VERSION-PATCH
: PRINT-VERSION
CHAR v EMIT VERSION-MAJOR .
@@ -258,8 +230,6 @@ CHAR . EMIT
CHAR . EMIT VERSION-PATCH .
;
CHAR . EMIT
\ Welcome banner
: BANNER
@@ -269,7 +239,7 @@ CHAR . EMIT
." |__,__/_/ .__(_)___/_//_/" CR
." /_/ " CR
CR
." Wipforth " PRINT-VERSION CR
." Welcome to Wipforth " PRINT-VERSION ." !" CR
." Copyright (c) Camden Dixie O'Brien" CR
CR
." Wipforth is freely available to use, modify and distribute for personal use" CR
@@ -277,8 +247,8 @@ CHAR . EMIT
CR
;
." done" CR
BANNER
\ Set SYSREADY high to enable user input
\ Set SYSREADY high and wait until interactive
1 SYSREADY AC!
SYSINTER WAIT DROP
BANNER

View File

@@ -19,7 +19,7 @@
(define client (client-setup))
(navigate client "http://localhost:8080")
(sleep 5)
(sleep 1)
(define-test kernel-assembles-successfully
(let* ((display (get-display client))

View File

@@ -2,12 +2,12 @@
.import main "emu" "mem"
;; Peripheral registers
.def TXBUF 00h
.def RXBUF 20h
.def TXHEAD 40h
.def TXTAIL 44h
.def RXHEAD 48h
.def RXTAIL 4Ch
.def TXBUF 000h
.def RXBUF 080h
.def TXHEAD 100h
.def TXTAIL 104h
.def RXHEAD 108h
.def RXTAIL 10Ch
.def DICT_START 0200h
@@ -635,6 +635,15 @@
end
call next
.func wait
.elem codewords wait WAIT_CODEWORD
call pop
i32.const 0
i64.const -1
memory.atomic.wait32 2 0
call push
call next
;; Core utility words
.func exit
@@ -752,7 +761,7 @@
local.get head
i32.const 1
i32.add
i32.const 1Fh
i32.const 7Fh
i32.and
i32.atomic.store8 0 0
@@ -768,7 +777,7 @@
local.tee tail
i32.const 1
i32.add
i32.const 1Fh
i32.const 7Fh
i32.and
local.tee n
i32.const TXHEAD
@@ -1226,6 +1235,15 @@ COPY:
.word COPY_CODEWORD
.def PREV _COPY
_WAIT:
.word PREV
.byte 4
.utf8 "WAIT"
.align
WAIT:
.word WAIT_CODEWORD
.def PREV _WAIT
_EXIT:
.word PREV
.byte 4