Skip to content

Instantly share code, notes, and snippets.

@ks888
Created February 4, 2017 15:07
Show Gist options
  • Save ks888/57db41c117ff171b29156c8c906b3814 to your computer and use it in GitHub Desktop.
Save ks888/57db41c117ff171b29156c8c906b3814 to your computer and use it in GitHub Desktop.
[Clojure] SICP 3.3.4: A Simulator for Digital Circuits
(ns sicp.ch3-3-4)
;;; queue
(defn make-queue []
(let [q (atom [])]
(letfn [(empty-queue? []
(empty? @q))
(front-queue []
(first @q))
(insert-queue! [item]
(reset! q (conj @q item)))
(delete-queue! []
(reset! q (rest @q)))
(queue-items [] @q)]
(letfn [(dispatch [op]
(cond
(= op 'empty-queue?) empty-queue?
(= op 'front-queue) front-queue
(= op 'insert-queue!) insert-queue!
(= op 'delete-queue!) delete-queue!
(= op 'queue-items) queue-items
:else (throw (RuntimeException. (str "NoOp")))))]
dispatch))))
(defn empty-queue? [q] ((q 'empty-queue?)))
(defn insert-queue! [q item] ((q 'insert-queue!) item))
(defn front-queue [q] ((q 'front-queue)))
(defn delete-queue! [q] ((q 'delete-queue!)))
(defn queue-items [q] ((q 'queue-items)))
;;; wire
(defn call-each [procedures]
(if (empty? procedures)
'done
(do
((first procedures))
(call-each (rest procedures)))))
(defn make-wire []
(let [signal-value (atom 0)
action-procedures (atom [])]
(letfn [(set-my-signal! [new-value]
(if-not (= @signal-value new-value)
(do (reset! signal-value new-value)
(call-each @action-procedures))
'done))
(accept-action-procedure! [proc]
(reset! action-procedures (cons proc @action-procedures))
(proc))
(action-items []
(println @action-procedures))]
(letfn [(dispatch [m]
(cond
(= m 'get-signal) @signal-value
(= m 'set-signal!) set-my-signal!
(= m 'add-action!) accept-action-procedure!
(= m 'action-items) action-items
:else (throw (RuntimeException. (str "NoOps")))))]
dispatch))))
(defn get-signal [wire] (wire 'get-signal))
(defn set-signal! [wire new-value] ((wire 'set-signal!) new-value))
(defn add-action! [wire action-procedure] ((wire 'add-action!) action-procedure))
(defn action-items [wire] ((wire 'action-items)))
;;; agenda
(defn make-agenda [] (atom [0]))
(defn current-time [agenda] (first @agenda))
(defn segments [agenda] (second @agenda))
(defn set-current-time! [agenda time]
(reset! agenda [time (segments agenda)]))
(defn set-segments! [agenda segments]
(reset! agenda [(current-time agenda) segments]))
(defn first-segment [agenda] (first (segments agenda)))
(defn rest-segments [agenda] (rest (segments agenda)))
(defn make-time-segment [time queue] [time queue])
(defn segment-time [s] (first s))
(defn segment-queue [s] (second s))
(defn empty-agenda? [agenda]
(empty? (segments agenda)))
(defn add-to-agenda! [time action agenda]
(letfn [(belongs-before? [segments]
(or (empty? segments)
(< time (segment-time (first segments)))))
(make-new-time-segment [time action]
(let [q (make-queue)]
(insert-queue! q action)
(make-time-segment time q)))
(add-to-segments! [segments]
(if (= (segment-time (first segments)) time)
(do
(insert-queue! (segment-queue (first segments))
action)
segments)
(if (belongs-before? (rest segments))
(cons (first segments) (cons (make-new-time-segment time action) (rest segments)))
(cons (first segments) (add-to-segments! (rest segments))))))]
(let [segs (segments agenda)]
(if (belongs-before? segs)
(set-segments! agenda (cons (make-new-time-segment time action) segs))
(set-segments! agenda (add-to-segments! segs))))))
(defn remove-first-agenda-item! [agenda]
(let [q (segment-queue (first-segment agenda))]
(delete-queue! q)
(if (empty-queue? q)
(set-segments! agenda (rest-segments agenda)))))
(defn first-agenda-item [agenda]
(if (empty-agenda? agenda)
(throw (RuntimeException. (str "Agenda is empty")))
(let [first-seg (first-segment agenda)]
(set-current-time! agenda (segment-time first-seg))
(front-queue (segment-queue first-seg)))))
;;; the-agenda
(def the-agenda (make-agenda))
(def inverter-delay 2)
(def and-gate-delay 3)
(def or-gate-delay 5)
(defn after-delay [delay action]
(add-to-agenda! (+ delay (current-time the-agenda))
action
the-agenda))
(defn propagate []
(if (empty-agenda? the-agenda)
'done
(let [first-item (first-agenda-item the-agenda)]
(first-item)
(remove-first-agenda-item! the-agenda)
(propagate))))
(defn probe [name wire]
(add-action! wire
(fn []
(print name)
(print " ")
(print (current-time the-agenda))
(print " New-value = ")
(println (get-signal wire)))))
;;; function boxes
(defn logical-not [s]
(cond
(zero? s) 1
(= s 1) 0
:else (throw (RuntimeException. (str "Invalid Signal")))))
(defn inverter [input output]
(letfn [(invert-input []
(let [new-value (logical-not (get-signal input))]
(after-delay inverter-delay
(fn [] (set-signal! output new-value)))))]
(add-action! input invert-input)
'ok))
(defn logical-and [a b]
(cond
(and (= a 1) (= b 1)) 1
:else 0))
(defn and-gate [a1 a2 output]
(letfn [(and-action-procedure []
(let [new-value (logical-and (get-signal a1) (get-signal a2))]
(after-delay and-gate-delay (fn [] (set-signal! output new-value)))))]
(add-action! a1 and-action-procedure)
(add-action! a2 and-action-procedure)))
(defn logical-or [a b]
(cond
(or (= a 1) (= b 1)) 1
:else 0))
(defn or-gate [a1 a2 output]
(letfn [(or-action-procedure []
(let [new-value (logical-or (get-signal a1) (get-signal a2))]
(after-delay or-gate-delay (fn [] (set-signal! output new-value)))))]
(add-action! a1 or-action-procedure)
(add-action! a2 or-action-procedure)))
(defn half-adder [a b s c]
(let [d (make-wire) e (make-wire)]
(or-gate a b d)
(and-gate a b c)
(inverter c e)
(and-gate d e s)
'ok))
(defn full-adder [a b c-in sum c-out]
(let [s (make-wire) c1 (make-wire) c2 (make-wire)]
(half-adder b c-in s c1)
(half-adder a s sum c2)
(or-gate c1 c2 c-out)
'ok))
(defn ripple-carry-adder [an bn sn c]
(letfn [(block [an bn sn c-in c-out]
(let [a (first an) b (first bn) s (first sn)]
(if (= 1 (count an))
(full-adder a b c-in s c)
(do
(full-adder a b c-in s c-out)
(block (rest an) (rest bn) (rest sn) c-out (make-wire))))))]
(block an bn sn (make-wire) (make-wire))))
(ns sicp.ch3-3-4-test
(:use [clojure.test :refer :all]
[sicp.ch3-3-4 :refer :all]))
(deftest queue
(testing "empty-queue?"
(let [q (make-queue)]
(is (empty-queue? q))
(insert-queue! q 1)
(is (not (empty-queue? q)))))
(testing "insert-queue!"
(let [q (make-queue)]
(insert-queue! q 1)
(insert-queue! q 2)
(insert-queue! q 3)
(is (= [1 2 3] (queue-items q)))))
(testing "front-queue"
(let [q (make-queue)]
(insert-queue! q 1)
(insert-queue! q 2)
(is (= 1 (front-queue q)))))
(testing "delete-queue!"
(let [q (make-queue)]
(insert-queue! q 1)
(delete-queue! q)
(is (empty-queue? q)))))
(deftest wire
(testing "call-each"
(let [count (atom 0)]
(letfn [(inc! [] (reset! count (inc @count)))]
(call-each [inc! inc! inc!])
(is (= 3 @count)))))
(testing "get-signal"
(let [w (make-wire)]
(is (zero? (get-signal w)))
(set-signal! w 1)
(is (= 1 (get-signal w)))))
(testing "set-signal!"
(let [w (make-wire)]
(set-signal! w 1)
(is (= 1 (get-signal w)))))
(testing "add-action!"
(let [w (make-wire) count (atom 0)]
(add-action! w (fn [] (reset! count (inc @count))))
(is (= 1 @count))
(set-signal! w 1)
(is (= 2 @count))
(set-signal! w 1)
(is (= 2 @count)))))
(deftest agenda
(testing "make-agenda"
(is (= [0] @(make-agenda))))
(testing "current-time, set-current-time!"
(let [a (make-agenda)]
(set-current-time! a 1)
(is (= 1 (current-time a)))))
(testing "segments, set-segments!"
(let [a (make-agenda)]
(set-segments! a [1 2])
(= [1 2] (segments a))))
(testing "add-to-agenda!"
(testing "add 1 action"
(let [a (make-agenda)]
(add-to-agenda! 1 '1 a)
(is (= 1 (segment-time (first-segment a))))
(is (= '1 (front-queue (segment-queue (first-segment a)))))))
(testing "add 2 same-time action"
(let [a (make-agenda)]
(add-to-agenda! 1 '1 a)
(add-to-agenda! 1 '2 a)
(is (= 1 (segment-time (first-segment a))))
(is (= ['1 '2] (queue-items (segment-queue (first-segment a)))))))
(testing "add 2 different-time action"
(let [a (make-agenda)]
(add-to-agenda! 1 '1 a)
(add-to-agenda! 2 '2 a)
(is (= 2 (segment-time (second (segments a)))))
(is (= ['2] (queue-items (segment-queue (second (segments a))))))))
(testing "add 2 different-time action (reverse-order)"
(let [a (make-agenda)]
(add-to-agenda! 2 '2 a)
(add-to-agenda! 1 '1 a)
(is (= 2 (segment-time (second (segments a)))))
(is (= ['2] (queue-items (segment-queue (second (segments a))))))))
(testing "add 3 different-time action"
(let [a (make-agenda)]
(add-to-agenda! 1 '1 a)
(add-to-agenda! 3 '3 a)
(add-to-agenda! 2 '2 a)
(remove-first-agenda-item! a)
(is (= 2 (segment-time (first (segments a)))))
(is (= ['2] (queue-items (segment-queue (first (segments a))))))
(is (= 3 (segment-time (second (segments a)))))
(is (= ['3] (queue-items (segment-queue (second (segments a)))))))))
(testing "remove-first-agenda-item!"
(let [a (make-agenda)]
(add-to-agenda! 1 '1 a)
(add-to-agenda! 2 '2 a)
(remove-first-agenda-item! a)
(is (= 2 (segment-time (first-segment a))))
(is (= '2 (front-queue (segment-queue (first-segment a)))))))
(testing "front-agenda-item"
(let [a (make-agenda)]
(add-to-agenda! 1 '1 a)
(is (= '1 (first-agenda-item a))))))
(deftest function-boxes
(testing "inverter"
(let [in (make-wire) out (make-wire)]
(inverter in out)
(propagate)
(is (= 1 (get-signal out)))
(set-signal! in 1)
(propagate)
(is (zero? (get-signal out)))))
(testing "and-gate"
(let [a1 (make-wire) a2 (make-wire) out (make-wire)]
(and-gate a1 a2 out)
(propagate)
(is (zero? (get-signal out)))
(set-signal! a1 1)
(propagate)
(is (zero? (get-signal out)))
(set-signal! a2 1)
(propagate)
(is (= 1 (get-signal out)))))
(testing "or-gate"
(let [a1 (make-wire) a2 (make-wire) out (make-wire)]
(or-gate a1 a2 out)
(propagate)
(is (zero? (get-signal out)))
(set-signal! a1 1)
(propagate)
(is (= 1 (get-signal out)))
(set-signal! a2 1)
(propagate)
(is (= 1 (get-signal out)))))
(testing "half-adder"
(let [a (make-wire) b (make-wire) s (make-wire) c (make-wire)]
(half-adder a b s c)
(propagate)
(is (zero? (get-signal s)))
(is (zero? (get-signal c)))
(set-signal! a 1)
(propagate)
(is (= 1 (get-signal s)))
(is (zero? (get-signal c)))
(set-signal! b 1)
(propagate)
(is (zero? (get-signal s)))
(is (= 1 (get-signal c)))))
(testing "full-adder"
(let [a (make-wire) b (make-wire) c-in (make-wire) s (make-wire) c-out (make-wire)]
(full-adder a b c-in s c-out)
(propagate)
(is (zero? (get-signal s)))
(is (zero? (get-signal c-out)))
(set-signal! a 1)
(propagate)
(is (= 1 (get-signal s)))
(is (zero? (get-signal c-out)))
(set-signal! b 1)
(propagate)
(is (zero? (get-signal s)))
(is (= 1 (get-signal c-out)))
(set-signal! c-in 1)
(propagate)
(is (= 1 (get-signal s)))
(is (= 1 (get-signal c-out)))))
(testing "ripple-carry-adder"
(testing "1-bit"
(let [a (make-wire) b (make-wire) s (make-wire) c-out (make-wire)]
(ripple-carry-adder [a] [b] [s] c-out)
(propagate)
(is (zero? (get-signal s)))
(is (zero? (get-signal c-out)))
(set-signal! a 1)
(set-signal! b 1)
(propagate)
(is (zero? (get-signal s)))
(is (= 1 (get-signal c-out)))))
(testing "3-bits"
(letfn [(wire-gen [num]
(if (= num 0)
[]
(cons (make-wire) (wire-gen (dec num)))))]
(let [an (wire-gen 3) bn (wire-gen 3) sn (wire-gen 3) c-out (make-wire)]
(ripple-carry-adder an bn sn c-out)
(propagate)
(is (zero? (get-signal (first sn))))
(is (zero? (get-signal c-out)))
(set-signal! (first an) 1)
(set-signal! (second an) 1)
(set-signal! (first bn) 1)
(set-signal! (second bn) 1)
(propagate)
(is (zero? (get-signal (first sn))))
(is (= 1 (get-signal (second sn))))
(is (= 1 (get-signal (second (rest sn)))))
(is (zero? (get-signal c-out)))
(set-signal! (second (rest an)) 1)
(set-signal! (second (rest bn)) 1)
(propagate)
(is (zero? (get-signal (first sn))))
(is (= 1 (get-signal (second sn))))
(is (= 1 (get-signal (second (rest sn)))))
(is (= 1 (get-signal c-out))))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment