diff options
| -rw-r--r-- | atk16_fpga/isa_idea.md | 193 | ||||
| -rw-r--r-- | atk16_schasm/lib.scm | 233 | ||||
| -rw-r--r-- | atk16_schasm/main.scm | 27 |
3 files changed, 453 insertions, 0 deletions
diff --git a/atk16_fpga/isa_idea.md b/atk16_fpga/isa_idea.md new file mode 100644 index 0000000..ef87189 --- /dev/null +++ b/atk16_fpga/isa_idea.md @@ -0,0 +1,193 @@ +## Registers + +There are 16 general-purpose registers, `R0` to `R15`. + +`R13` is the flag/condition register. +`R14` is the program counter. +`R15` is the stack pointer. + +Additionally, `R12` mey be clobbered by the assembler when expanding macros. + +## Instructions + +XXXX denotes ignored bits. + +**HLT** is a halt operation. It stops the processor. + +```python +HLT +0000 XXXX XXXX XXXX +``` + +**ALU** is a 16-bit ALU operation. + +- SSS selects the operation to perform +- LLLL is the left operand and destination register. +- In register mode (M=0), RRRR is the right operand register. +- In immediate mode (M=1), the next word is interpreted as a 16-bit immediate value. + +```python +ALU +# register +0001 SSSM LLLL RRRR +# immediate +0001 SSSM LLLL XXXX +IIII IIII IIII IIII +``` + +**LD** is a load operation. + +- LLLL is the destination register. +- In register mode (M=0), RRRR is the register holding the address to load from. +- In immediate mode (M=1), the next word is interpreted as a 16-bit address to load from. +- In direct mode (D=0), the address is used as-is. +- In indirect mode (D=1), the address is used as a pointer to another address. An indirect load can be thought of as a pointer dereference. +- In pop mode (P = 1), the RRRR register is incremented before loading. This in conjunction with an indirect load can be used as a stack pop operation. POP is only valid in register mode. + +```python +LD +# register +0010 DPXM LLLL RRRR +# immediate +0010 DPXM LLLL XXXX +IIII IIII IIII IIII +``` + +**MOV** is a move operation. It copies the value from one register to another. + +- LLLL is the destination register. +- RRRR is the source register. + +```python +MOV +0011 XXXX LLLL RRRR +``` + +**ST** is a store operation. + +- LLLL is the source register. +- In register mode (M=0), RRRR is the register holding the address to store to. +- In immediate mode (M=1), the next word is interpreted as a 16-bit address to store to. +- In direct mode (D=0), the address is used as-is. +- In indirect mode (D=1), the address is used as a pointer to another address. An indirect store can be thought of as a pointer assignment. +- In push mode (P = 1), the RRRR register is decremented after storing. This in conjunction with an indirect store can be used as a stack push operation. PUSH is only valid in register mode. + +```python +ST +# register +0100 DPXM LLLL RRRR +# immediate +0100 DPXM LLLL XXXX +IIII IIII IIII IIII +``` + +**BR** is a branch operation. + +- FF selects the condition to branch on (carry, overflow, zero, sign). +- S determines if the selected flag should be set (1) or not set (0). +- The I octet is an 8-bit signed offset. + +```python +BR +0101 FFXS IIII IIII +``` + +## Calling convention + +Arguments are passed in registers R0..R10. The return value is stored in R0. +The return address is stored on the stack to support nested calls. + +Registers `R0..R3` are caller-saved (called function can clobber these registers, calling code must save them on the stack or higher registers if needed). +Registers `R4..R10` are callee-saved (called function must save these on the stack or lower registers). + +## Example assembly + +```java +@at 0x0 + LD R0 0x1 + LD R1 0x2 + ADD R0 R1 ; ADD = macro that expands to ALU 000 + + BR SIGN UNSET $br_true +br_false: + LD R0 0xEE + HLT +br_true: + LD R0 0xFF + HLT +``` + +## Example macro assembly + +```java +@macro ADD lhs rhs + ALU 0 $lhs $rhs +@endmacro + +@let threshold 0x80 +@if R1 < $threshold ; expands into a SUB and a BR + LD R0 0x1 + LD R1 0x2 + ADD R0 R1 + HLT +@else + ; something +@endif + +@let PC R14 +@let SP R15 + +@macro SPUSH reg + ST INDIRECT PUSH $reg $SP +@endmacro + +@macro SPOP reg + LD INDIRECT POP $reg $SP +@endmacro + +@macro CALL1 fn_lbl arg +@let ret_addr $gen_uniq ; generate a unique label + LD R0 $arg ; load argument to R0 + LD R1 $ret_addr ; load return address to R1 + SPUSH R1 ; push return address to stack + LD $PC $fn_lbl ; jump to function address +$ret_addr: + HLT +@endmacro + +@at 0x0 +main: + CALL1 :fn 0x1 + HLT + +; example of an absolute jump + LD $SP + $abs_jump_addr + +; example of a relative jump + ADD $PC + $rel_jump_offset + +; idea: syntax for passing the immediate on the same line + ADD $PC % $rel_jump_offset + +; probably should just require the core instructions to be defined "correctly" and have convenience macros for the rest, such as + +@macro LOAD reg from +@c_if immediate $from + LD $reg + $from +$c_else + LD $reg $from +$c_endif +@endmacro +``` + +### Assembler concepts + +- `R0` to `R15` are register literals. +- Rows with no indentation are either directives (`@` prefix) or labels (`:` suffix). +- Rows with indentation are instructions. +- Comments are prefixed with `;`. +- Labels can be literals (`my_label:`) or variables (`$my_label_var:`). +- Compile time variables are defined with `@let` and used with `$`. diff --git a/atk16_schasm/lib.scm b/atk16_schasm/lib.scm new file mode 100644 index 0000000..adf681b --- /dev/null +++ b/atk16_schasm/lib.scm @@ -0,0 +1,233 @@ +(import (chicken base) + (chicken bitwise) + (chicken format)) + +(define *buffer* (make-vector (expt 2 16) #f)) +(define *cursor* 0) +(define *labels* '()) + +;; Directives + +(define (def-label sym) + (set! *labels* (cons (cons sym *cursor*) *labels*))) + +(define (at-addr addr) + (unless (and (number? addr) + (>= addr 0) + (< addr (expt 2 16))) + (error "invalid addr" addr)) + + (set! *cursor* addr)) + +;; Value constructors and references + +(define (label sym) + (assocdr sym *labels*)) + +(define (reg n) + (cond + ((or (< n 0) (>= n 16)) (error "invalid arg to reg" n)) + (else `(reg ,n)))) + +(define (imm n) + (let* ((n16 (modulo n (expt 2 16)))) + `(imm ,n16))) + +(define (alu-op n) + (cond + ((or (< n 0) (>= n 8)) (error "invalid arg to alu-op" n)) + (else `(alu-op ,n)))) + +(define alu-plus (alu-op 0)) +(define alu-minus (alu-op 1)) +(define alu-and (alu-op 2)) +(define alu-or (alu-op 3)) +(define alu-xor (alu-op 4)) +(define alu-shl (alu-op 5)) +(define alu-shr (alu-op 6)) +(define alu-sar (alu-op 7)) + +;; Encode and emit + +(define (emit . chunks) + (unless (eq? (vector-ref *buffer* *cursor*) #f) + (error "overwriting already written memory at" *cursor*)) + (define word + (if (and (= (length chunks) 1) + (number? (car chunks))) + ;; single argument + (car chunks) + ;; multiple (bits . value) pairs + (apply encode chunks))) + + (vector-set! *buffer* *cursor* word) + (set! *cursor* (+ *cursor* 1))) + +(define (encode . chunks) + (let ((total-bits (apply + (map car chunks)))) + (unless (= total-bits 16) + (error "emit: total bits in chunks must equal 16, got" total-bits))) + + (let loop ((chs chunks) (acc 0)) + (if (null? chs) + acc + (let* ((chunk (car chs)) + (n (car chunk)) + (val (cdr chunk))) + + (when (>= val (expt 2 n)) + (error "emit: value" val "does not fit in" n "bits")) + + ;; shift acc left by n bits and combine with val + (loop (cdr chs) + (bitwise-ior (arithmetic-shift acc n) val)))))) + +;; Instructions + +(define (hlt) + ;; just full zeros + (emit 0)) + +(define (alu op lhs rhs) + (unless (eq? 'alu-op (type-of op)) (error "invalid op" op)) + (unless (eq? 'reg (type-of lhs)) (error "invalid lhs" lhs)) + + (define mode + (cond ((eq? 'reg (type-of rhs)) 0) + ((eq? 'imm (type-of rhs)) 1) + (else (error "invalid rhs" rhs)))) + + (if (= mode 0) + ;; reg mode + (emit `(4 . 1) ; opcode = 1 + `(3 . ,(cadr op)) ; alu op + `(1 . ,mode) ; reg/imm mode + `(4 . ,(cadr lhs)) ; lhs + `(4 . ,(cadr rhs))) ; rhs + + ;; immediate mode + (begin + (emit `(4 . 1) ; opcode = 1 + `(3 . ,(cadr op)) ; alu op + `(1 . ,mode) ; reg/imm mode + `(4 . ,(cadr lhs)) ; lhs + `(4 . 0)) ; unused + (emit `(16 . ,(cadr rhs)))) ; rhs + )) + +(define (add lhs rhs) (alu (alu-op 0) lhs rhs)) +(define (sub lhs rhs) (alu (alu-op 1) lhs rhs)) +(define (and lhs rhs) (alu (alu-op 2) lhs rhs)) +(define (or lhs rhs) (alu (alu-op 3) lhs rhs)) +(define (xor lhs rhs) (alu (alu-op 4) lhs rhs)) +(define (shl lhs rhs) (alu (alu-op 5) lhs rhs)) +(define (shr lhs rhs) (alu (alu-op 6) lhs rhs)) +(define (sar lhs rhs) (alu (alu-op 7) lhs rhs)) + +(define (ld lhs rhs #!key (indirect #f) (pop #f)) + (unless (eq? 'reg (type-of lhs)) (error "invalid lhs" lhs)) + + (define d-mode (if indirect 1 0)) + (define p-mode (if pop 1 0)) + + (define m-mode + (cond ((eq? 'reg (type-of rhs)) 0) + ((eq? 'imm (type-of rhs)) 1) + (else (error "invalid rhs" rhs)))) + + (if (= m-mode 0) + ;; reg mode + (emit `(4 . 2) ; opcode = 2 + `(1 . ,d-mode) ; indirect mode + `(1 . ,p-mode) ; pop mode + `(1 . 0) ; unused + `(1 . ,m-mode) ; reg/imm mode + `(4 . ,(cadr lhs)) ; lhs + `(4 . ,(cadr rhs))) ; rhs + + ;; immediate mode + (begin + (emit `(4 . 2) ; opcode = 2 + `(1 . ,d-mode) ; indirect mode + `(1 . ,p-mode) ; pop mode + `(1 . 0) ; unused + `(1 . ,m-mode) ; reg/imm mode + `(4 . ,(cadr lhs)) ; lhs + `(4 . 0)) ; unused + (emit `(16 . ,(cadr rhs)))) ; rhs + )) + +(define (mov lhs rhs) + (unless (eq? 'reg (type-of lhs)) (error "invalid lhs" lhs)) + (unless (eq? 'reg (type-of rhs)) (error "invalid rhs" rhs)) + + (emit `(4 . 3) ; opcode = 3 + `(4 . 0) ; unused + `(4 . ,(cadr lhs)) ; lhs + `(4 . ,(cadr rhs)))) ; rhs + +(define (st lhs rhs #!key (indirect #f) (push #f)) + (unless (eq? 'reg (type-of lhs)) (error "invalid lhs" lhs)) + + (define d-mode (if indirect 1 0)) + (define p-mode (if push 1 0)) + + (define m-mode + (cond ((eq? 'reg (type-of rhs)) 0) + ((eq? 'imm (type-of rhs)) 1) + (else (error "invalid rhs" rhs)))) + + (if (= m-mode 0) + ;; reg mode + (emit `(4 . 4) ; opcode = 4 + `(1 . ,d-mode) ; indirect mode + `(1 . ,p-mode) ; pop mode + `(1 . 0) ; unused + `(1 . ,m-mode) ; reg/imm mode + `(4 . ,(cadr lhs)) ; lhs + `(4 . ,(cadr rhs))) ; rhs + + ;; immediate mode + (begin + (emit `(4 . 4) ; opcode = 4 + `(1 . ,d-mode) ; indirect mode + `(1 . ,p-mode) ; pop mode + `(1 . 0) ; unused + `(1 . ,m-mode) ; reg/imm mode + `(4 . ,(cadr lhs)) ; lhs + `(4 . 0)) ; unused + (emit `(16 . ,(cadr rhs)))) ; rhs + )) + +(define (br flag offset #!key (asserted #t)) + (unless (eq? 'flag (type-of flag)) (error "invalid flag selector" flag)) + (define asserted + (cond (#t 1) + (#f 0) + (else (error "invalid asserted bool" asserted)))) + + (unless (eq? 'imm (type-of offset)) (error "invalid offset" offset)) + (define offset (cadr offset)) + (unless (< offset (expt 2 8)) (error "offset too large" offset)) + + (emit `(4 . 5) ; opcode = 5 + `(2 . ,(cadr flag)) ; flag selector + `(1 . 0) ; unused + `(1 . ,asserted) ; asserted (set / not set) + `(8 . ,offset)) ; offset + ) + +;; Utils + +(define (type-of v) + (if (and (list? v) + (> (length v) 0)) + (car v) + #f)) + +(define (assocar k alist) + (let ((v (assoc k alist))) + (and v (car v)))) +(define (assocdr k alist) + (let ((v (assoc k alist))) + (and v (cdr v)))) diff --git a/atk16_schasm/main.scm b/atk16_schasm/main.scm new file mode 100644 index 0000000..79f7686 --- /dev/null +++ b/atk16_schasm/main.scm @@ -0,0 +1,27 @@ +(import (chicken base)) + +(load "lib.scm") + +(define FL (reg 13)) +(define PC (reg 14)) +(define SP (reg 15)) + +(ld SP (imm 0)) + +(at-addr #x10) +(def-label 'reset-vector) +(emit #xF800) +(emit #xF801) + +(at-addr #x20) +(def-label 'main) +(add (reg 1) (reg 2)) +(add (reg 3) (imm #xFF)) + +;; abs jump +(ld PC (imm (label 'main))) + +;; rel jump +(add PC (imm 10)) + +(mov (reg 0) (reg 1)) |
