aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--atk16_fpga/isa_idea.md193
-rw-r--r--atk16_schasm/lib.scm233
-rw-r--r--atk16_schasm/main.scm27
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))