diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d5c26b --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# use glob syntax. +syntax: glob +*.pyc +*.pyo +*~ +TAGS +*.rom +img +etc +test-roms +junk +img +.theme +.snarf +*.sym +wave +*.mp3 +*.wav +*.mp4 +scratch diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8026e02 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +vm: vm.scm + csc -o vm vm.scm + +hello.rom: hello.tal + uxnasm hello.tal hello.rom + +run: hello.rom vm + ./vm hello.rom aa bb cc + +clean: + rm -f hello.rom + rm -f vm + +.DEFAULT: run +.PHONY: clean run diff --git a/README.md b/README.md index e69de29..6646c66 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,29 @@ +# vm.scm + +Scheme implementation of Varvara. + +It is closely based on [uxnmin.c](https://wiki.xxiivv.com/etc/uxnmin.c.txt). + +Currently it supports: + + * console stdin, stdout, and stderr + * command-line arguments + +# goals + +The main goals of this project are: + + * portability across Scheme implementations + * implementations simplicity and correctness + * support bootstrapping uxn + +Secondary goals are: + + * supporting more devices + * emulator performance + +# scheme dialects + +The default dialect used is [Chicken Scheme](http://www.call-cc.org/). + +The source code contains shims for [Guile](https://www.gnu.org/software/guile/), [Chibi-scheme](http://synthcode.com/scheme/chibi), and [Chez Scheme](https://cisco.github.io/ChezScheme/). By commenting out the Chicken stanza and uncommenting a different one other Scheme dialects may be supported. diff --git a/hello.tal b/hello.tal new file mode 100644 index 0000000..149c00e --- /dev/null +++ b/hello.tal @@ -0,0 +1,17 @@ +|100 + ;on-input #10 DEO2 + #0123 INC2 #45 INC POP2 POP + LIT "h #18 DEO + LIT "e #18 DEO + LIT "l #18 DEO + LIT "l #18 DEO + LIT "o #18 DEO + #0a18 DEO + ( #830f DEO ) + BRK + +@on-input + #010e DEO + #17 DEI LIT "0 ADD #18 DEO + #12 DEI #18 DEO + BRK diff --git a/vm.scm b/vm.scm index 844e3db..5f8f068 100644 --- a/vm.scm +++ b/vm.scm @@ -1,33 +1,43 @@ ; basic varvara emulator implementation in scheme ; -; does not currently use any macros or advanced features. -; first stanza defines non-portable bit-related functions. +; does not currently use any macros or advanced features. the first +; stanza defines non-portable functions across a variety of scheme +; implementations. -; implementation-dependent features which differ between scheme impls +; === implementation-dependent functions; uncomment one stanza ; chicken scheme -(import chicken.bitwise) -(define (b-and x y) (bitwise-and x y)) -(define (b-or x y) (bitwise-ior x y)) -(define (b-xor x y) (bitwise-xor x y)) +(import (chicken bitwise) (chicken process-context)) +(define (get-u8 port) + (let ((c (read-char port))) (if (eof-object? c) c (char->integer c)))) +(define (open-rom path) (open-input-file path #:binary)) + +;; ; guile, invoke with guile vm.scm ROM ... +;; (use-modules (srfi srfi-60) (ice-9 binary-ports)) +;; (define (open-rom path) (open-input-file path)) +;; (define (command-line-arguments) (cdr (command-line))) + +;; ; chibi scheme, invoke with ??? +;; (import (scheme base) (scheme bitwise) (scheme file)) +;; (import (chibi io) (chibi process)) +;; (define (get-u8 port) (read-u8 port)) +;; (define (open-rom path) (open-input-file path)) +;; (define (display s) (write-string s)) + +;; ; chez scheme, invoke with chezscheme --script vm.scm ROM ... +;; (define (open-rom path) (open-file-input-port path)) +;; (define (command-line-arguments) (cdr (command-line))) + +; === portable scheme code follows + (define (b<< n s) (arithmetic-shift n s)) (define (b>> n s) (arithmetic-shift n (- s))) -(define (b-read-u8 port) (u8 (char->integer (read-char port)))) - -;; ; guile -;; (use-modules (srfi srfi-60)) -;; (use-modules (ice-9 binary-ports)) -;; (define (b-and x y) (bitwise-and x y)) -;; (define (b-or x y) (bitwise-ior x y)) -;; (define (b-xor x y) (bitwise-xor x y)) -;; (define (b<< n s) (arithmetic-shift n s)) -;; (define (b>> n s) (arithmetic-shift n (- s))) -;; (define (b-read-u8 port) (u8 (get-u8 port))) - -; portable scheme code follows ; global state for the virtual machine -(define instructions (make-vector 256 (lambda () (display "!!! no instruction\n")))) +; +; we use vectors to simulate memory addresses +; we use mutable cons cells for pointer+stack pairs +(define instructions (make-vector 256)) (define mem (make-vector 65536 0)) (define dev (make-vector 256 0)) (define wst (cons 0 (make-vector 256 0))) @@ -46,22 +56,9 @@ (define (u16 n) (modulo n 65536)) (define (s16 n) (- (u16 (+ n 32768)) 32768)) -; convert a cons cell of two bytes to one short -; (0 . 0) -> 0 -; (1 . 1) -> 257 -; (2 . 2) -> 514 -(define (join-u16 parts) - (let ((hi (car parts)) - (lo (cdr parts))) - (+ (* 256 hi) lo))) - -; convert a short to a cons cell of two bytes -; 12 -> (0 . 12) -; 257 -> (1 . 1) -(define (tear-u16 n) - (let ((n2 (modulo n 256)) - (n1 (floor (/ n 256)))) - (cons n1 n2))) +(define (u16->lo n) (modulo n 256)) +(define (u16->hi n) (quotient n 256)) +(define (hilo->u16 hi lo) (+ (* 256 hi) lo)) ; pop from active stack, returning value ; uses _2, _wst, and _rst to respect instruction modes @@ -72,9 +69,7 @@ (set-car! _wst i) n)) (define (_pop2) - (let* ((hi (_pop1)) - (lo (_pop1))) - (join-u16 (cons hi lo)))) + (let* ((lo (_pop1)) (hi (_pop1))) (hilo->u16 hi lo))) ; push value to active stack ; uses _2, _wst, and _rst to respect instruction modes @@ -84,8 +79,7 @@ ; push value to passive stack ; uses _2, _wst, and _rst to respect instruction modes -(define (_rpush n) (if _2 (_rpush2 n) (_rpush1 n))) -(define (_rpush1 n) (_xpush1 _rst n)) +(define (_rpush n) (if _2 (_rpush2 n) (_xpush1 _rst n))) (define (_rpush2 n) (_xpush2 _rst n)) ; push value to the given stack @@ -94,9 +88,8 @@ (vector-set! (cdr st) i (u8 n)) (set-car! st (u8 (+ i 1))))) (define (_xpush2 st n) - (let ((parts (tear-u16 n))) - (_xpush1 st (car parts)) - (_xpush1 st (cdr parts)))) + (_xpush1 st (u16->hi n)) + (_xpush1 st (u16->lo n))) ; read value from memory onto active stack ; uses _2, _wst, and _rst to respect instruction modes @@ -107,19 +100,16 @@ ; write value to memory ; uses _2 to respect instruction modes (define (_write addr n) - (if _2 (_write2 addr n) (_write1 addr n))) -(define (_write1 addr n) - (vector-set! mem addr n)) -(define (_write2 addr n) - (let ((parts (tear-u16 n))) - (vector-set! mem addr (car parts)) - (vector-set! mem (+ addr 1) (cdr parts)))) + (if _2 (begin + (vector-set! mem addr (u16->hi n)) + (vector-set! mem (+ addr 1) (u16->lo n))) + (vector-set! mem addr n))) ; load signed 16-bit value from memory, returning value (define (_load-s16) (let ((hi (vector-ref mem (+ pc 1))) (lo (vector-ref mem (+ pc 2)))) - (s16 (join-u16 (cons hi lo))))) + (s16 (hilo->u16 hi lo)))) ; load 8 variants of an instruction (define (op impl s k r) @@ -142,32 +132,32 @@ (define (lit-impl) (_read (+ pc 1)) (set! pc (+ pc (if _2 2 1)))) (define (inc-impl) (_push (+ (_pop) 1))) (define (pop-impl) (_pop)) -(define (nip-impl) (let ((a (_pop))) (_pop) (_push a))) -(define (swp-impl) (let ((b (_pop)) (a (_pop))) (_push b) (_push a))) -(define (rot-impl) (let ((c (_pop)) (b (_pop)) (a (_pop))) (_push b) (_push c) (_push a))) -(define (dup-impl) (let ((a (_pop))) (_push a) (_push a))) -(define (ovr-impl) (let ((b (_pop)) (a (_pop))) (_push a) (_push b) (_push a))) -(define (equ-impl) (let ((b (_pop)) (a (_pop))) (_push1 (if (= a b) 1 0)))) -(define (neq-impl) (let ((b (_pop)) (a (_pop))) (_push1 (if (not (= a b)) 0 1)))) -(define (gth-impl) (let ((b (_pop)) (a (_pop))) (_push1 (if (> a b) 0 1)))) -(define (lth-impl) (let ((b (_pop)) (a (_pop))) (_push1 (if (< a b) 0 1)))) +(define (nip-impl) (let* ((a (_pop))) (_pop) (_push a))) +(define (swp-impl) (let* ((b (_pop)) (a (_pop))) (_push b) (_push a))) +(define (rot-impl) (let* ((c (_pop)) (b (_pop)) (a (_pop))) (_push b) (_push c) (_push a))) +(define (dup-impl) (let* ((a (_pop))) (_push a) (_push a))) +(define (ovr-impl) (let* ((b (_pop)) (a (_pop))) (_push a) (_push b) (_push a))) +(define (equ-impl) (let* ((b (_pop)) (a (_pop))) (_push1 (if (= a b) 1 0)))) +(define (neq-impl) (let* ((b (_pop)) (a (_pop))) (_push1 (if (not (= a b)) 0 1)))) +(define (gth-impl) (let* ((b (_pop)) (a (_pop))) (_push1 (if (> a b) 0 1)))) +(define (lth-impl) (let* ((b (_pop)) (a (_pop))) (_push1 (if (< a b) 0 1)))) (define (jmp-impl) (let ((n (_pop))) (set! pc (if _2 n (+ pc (s8 n)))))) (define (jcn-impl) (if (= 0 (_pop1)) (_pop) (jmp-impl))) (define (jsr-impl) (_rpush2 pc) (jmp-impl)) (define (sth-impl) (_rpush (_pop))) (define (ldz-impl) (_read (_pop1))) -(define (stz-impl) (let ((zp (_pop1)) (n (_pop))) (_write zp n))) +(define (stz-impl) (let* ((zp (_pop1)) (n (_pop))) (_write zp n))) (define (ldr-impl) (_read (+ pc (s8 (_pop1))))) -(define (str-impl) (let ((off (_pop1)) (n (_pop))) (_write (+ pc (s8 off)) n))) +(define (str-impl) (let* ((off (_pop1)) (n (_pop))) (_write (+ pc (s8 off)) n))) (define (lda-impl) (_read (_pop2))) -(define (sta-impl) (let ((addr (_pop2)) (n (_pop))) (_write addr n))) -(define (add-impl) (let ((b (_pop)) (a (_pop))) (_push (+ a b)))) -(define (sub-impl) (let ((b (_pop)) (a (_pop))) (_push (- a b)))) -(define (mul-impl) (let ((b (_pop)) (a (_pop))) (_push (* a b)))) -(define (div-impl) (let ((b (_pop)) (a (_pop))) (_push (quotient a b)))) -(define (and-impl) (let ((b (_pop)) (a (_pop))) (_push (b-and a b)))) -(define (ora-impl) (let ((b (_pop)) (a (_pop))) (_push (b-or a b)))) -(define (eor-impl) (let ((b (_pop)) (a (_pop))) (_push (b-xor a b)))) +(define (sta-impl) (let* ((addr (_pop2)) (n (_pop))) (_write addr n))) +(define (add-impl) (let* ((b (_pop)) (a (_pop))) (_push (+ a b)))) +(define (sub-impl) (let* ((b (_pop)) (a (_pop))) (_push (- a b)))) +(define (mul-impl) (let* ((b (_pop)) (a (_pop))) (_push (* a b)))) +(define (div-impl) (let* ((b (_pop)) (a (_pop))) (_push (quotient a b)))) +(define (and-impl) (let* ((b (_pop)) (a (_pop))) (_push (bitwise-and a b)))) +(define (ora-impl) (let* ((b (_pop)) (a (_pop))) (_push (bitwise-ior a b)))) +(define (eor-impl) (let* ((b (_pop)) (a (_pop))) (_push (bitwise-xor a b)))) ; DEI base instruction implementation (define (dei-impl) @@ -175,31 +165,32 @@ (if _2 (let ((hi (_dei port)) (lo (_dei (+ port 1)))) - (_push2 (join-u16 (cons hi lo)))) + (_push2 (hilo->u16 hi lo))) (_push1 (_dei port))))) (define (_dei port) - (_push1 (vector-ref dev port))) + (u8 (vector-ref dev port))) ; DEO base instruction implementation (define (deo-impl) - (let ((dev (_pop1)) (n (_pop))) + (let* ((dev (_pop1)) (n (_pop))) (if _2 - (let ((parts (tear-u16 n))) - (_deo (car parts) dev) - (_deo (cdr parts) (+ dev 1))) + (begin + (_deo (u16->hi n) dev) + (_deo (u16->lo n) (+ dev 1))) (_deo n dev)))) (define (_deo byte port) (vector-set! dev port (u8 byte)) (cond ((= 24 port) (write-char (integer->char byte) (current-output-port))) - ((= 25 port) (write-char (integer->char byte) (current-error-port))))) + ((= 25 port) (write-char (integer->char byte) (current-error-port))) + (* '()))) ; SFT base instruction implementation (define (sft-impl) - (let ((n (_pop1)) - (a (_pop)) - (left (b-and (b>> n 4) 15)) - (right (b-and n 15))) + (let* ((n (_pop1)) + (a (_pop)) + (left (bitwise-and (b>> n 4) 15)) + (right (bitwise-and n 15))) (_push (b<< (b>> a right) left)))) ; add 8 variants for a given instruction base @@ -267,14 +258,69 @@ (set! pc (+ pc 1)) (u:eval)))) +(define (u:run addr) + (set! done #f) + (set! pc addr) + (u:eval) + (let ((n (vector-ref dev 15))) + (if (= n 0) '() + (exit (bitwise-and n 127))))) + +(define (u:read-args args) + (if (null? args) + (u:send-input #\newline 4) + (begin + (u:read-arg (string->list (car args))) + (if (null? (cdr args)) '() (u:send-input #\newline 3)) + (u:read-args (cdr args))))) + +(define (u:read-arg chars) + (if (null? chars) '() + (begin + (u:send-input (car chars) 2) + (u:read-arg (cdr chars))))) + +; send a character of console input with type +; types are: +; - 1 stdin +; - 2 argument data +; - 3 spacer between arguments +; - 4 spacer after arguments +(define (u:send-input c type) + (let* ((hi (vector-ref dev 16)) + (lo (vector-ref dev 17)) + (addr (hilo->u16 hi lo))) + (if (= 0 addr) '() + (begin + (vector-set! dev 18 (u8 (char->integer c))) + (vector-set! dev 23 (u8 type)) + (u:run addr))))) + +(define (u:read-stdin port) + (let ((c (read-char port))) + (if (eof-object? c) '() + (begin + (u:send-input c 1) + (u:read-stdin port))))) + ; load a ROM from a file, byte-by-byte (define (u:load-rom rom addr) - (if (eof-object? (peek-char rom)) - '() - (begin (vector-set! mem addr (b-read-u8 rom)) - (u:load-rom rom (+ addr 1))))) + (let ((byte (get-u8 rom))) + (if (eof-object? byte) + '() + (begin (vector-set! mem addr (u8 byte)) + (u:load-rom rom (+ addr 1)))))) -; load the rom and begin evaluation -(let ((rom (open-input-file "hello.rom"))) - (u:load-rom rom 256) - (u:eval)) +(let ((prog-args (command-line-arguments))) + (if (null? prog-args) + (begin (display "usage: ./vm ROM [ARGS...]\n") (exit)) + (let* ((rom-path (car prog-args)) + (rom-args (cdr prog-args)) + (rom (open-rom rom-path)) + (start 256)) + (vector-set! dev 23 (if (null? rom-args) 0 1)) + (u:load-rom rom start) + (u:run start) + (u:read-args rom-args) + (u:read-stdin (current-input-port)) + (exit 0))))