Assembler, Part 2
April 18, 2014
Here’s our very simple simulator:
(define (sim mem)
(let loop ((pc 0) (acc 0))
(let ((addr (modulo (vector-ref mem pc) 1000))
(code (quotient (vector-ref mem pc) 1000)))
(case code
((1) (loop (+ pc 1) (read))) ; get
((2) (display acc) (newline) (loop (+ pc 1) acc)) ; put
((3) (loop (+ pc 1) (vector-ref mem addr))) ; ld
((4) (vector-set! mem addr acc) (loop (+ pc 1) acc)) ; st
((5) (loop (+ pc 1) (+ acc (vector-ref mem addr)))) ; add
((6) (loop (+ pc 1) (- acc (vector-ref mem addr)))) ; sub
((7) (loop (if (positive? acc) addr (+ pc 1)) acc)) ; jpos
((8) (loop (if (zero? acc) addr (+ pc 1)) acc)) ; jz
((9) (loop addr acc)) ; j
((10) (if #f #f)) ; halt
(else (error 'sim "unrecognized command"))))))
The two loop control variables are the program counter and accumulator. At each step, the current memory location is split into address and opcode, then the program dispatches on opcode, performs the indicated action, then either advances the program counter or resets it for a jump. The (if #f #f) is the standard Scheme idiom for a no-op that does nothing and returns nothing; output is created by the put opcode, not by returning a value from the function. Here’s what a run of the sample program looks like:
> (sim (asm2 (asm1 "program.asm"))) 17 35 102 0 154
There is no prompt in the program, so the program pauses, waiting for input. At the first pause the user enters 17, at the second pause the user enters 35, at the third pause the user enters 102, and at the fourth pause the user enters 0 to stop the program. Then the program reports the result, 154, and exits.
You can run the program at http://programmingpraxis.codepad.org/gDjSzX9P.
Simulator in python
def run(memory): accumulator = 0 pc = 0 while True: op, arg = divmod(memory[pc], 1000) if 1 <= op <= 6: if op == 1: accumulator = int(input('? ')) elif op == 2: print(accumulator) elif op == 3: accumulator = memory[arg] elif op == 4: memory[arg] = accumulator elif op == 5: accumulator = (accumulator + memory[arg]) % 100000 elif op == 6: accumulator = (accumulator - memory[arg]) % 100000 pc = (pc + 1) % 1000 elif 7 <= op <= 9: if op == 7: test = 0 < accumulator < 50000 # 10's complement ? elif op == 8: test = accumulator == 0 elif op == 9: test = True pc = arg if test else (pc + 1) elif op == 10: # halt break else: fmt = "Trap: invalid instruction '{:05}' at location '{:03}'." print(fmt.format(memory[pc], pc)) break(defn delta [C] (let [pc (:pc C) instr (aget (:M C) pc) opc ((clojure.set/map-invert (:opc C)) (apply str (take 2 instr))) arg (Integer/parseInt (apply str (nthrest instr 2))) C (assoc C :pc (inc pc))] (condp = opc "get" (assoc C :acc (Integer/parseInt (read-line))) "put" (println (:acc C)) "ld" (assoc C :acc (aget (:M C) arg)) "st" (do (aset (:M C) arg (str (:acc C))) C) "add" (assoc C :acc (+ (:acc C) (Integer/parseInt (aget (:M C) arg)))) "sub" (assoc C :acc (- (:acc C) (Integer/parseInt (aget (:M C) arg)))) "jpos" (if (< 0 (:acc C)) (assoc C :pc arg) C) "jz" (if (= 0 (:acc C)) (assoc C :pc arg) C) "j" (assoc C :pc arg) "halt" (dissoc C :pc) :default C))) (defn run [C code] (loop [C (load-asm C code)] (if (:pc C) (recur (delta C)) C)))