#lang racket (require racket/mpair) ;;;;;;;;;; ;; 3.28 ;; ;;;;;;;;;; (define (or-gate a1 a2 output) (define (or-action-procedure) (define new-value (logical-or (get-signal a1) (get-signal a2))) (after-delay or-gate-delay (lambda () (set-signal! output new-value)))) (add-action! a1 or-action-procedure) (add-action! a2 or-action-procedure) 'ok) ;;;;;;;;;; ;; 3.29 ;; ;;;;;;;;;; ;; Defining an or-gate this way, the or-gate-delay would equal one and-gate-delay ;; plus two inverter-delay's because the first two inverters can run ;; simultaneously, but then the other gates must run in sequence. (define (alternate-or-gate a1 a2 output) (define b1 (make-wire)) (define b2 (make-wire)) (define c (make-wire)) (inverter a1 b1) (inverter a2 b2) (and-gate b1 b2 c) (inverter c output) 'ok) ;;;;;;;;;; ;; 3.30 ;; ;;;;;;;;;; (define (half-adder a b s c) (define d (make-wire)) (define e (make-wire)) (or-gate a b d) (and-gate a b c) (inverter c e) (and-gate d e s) 'ok) (define (full-adder a b c-in sum c-out) (define s (make-wire)) (define c1 (make-wire)) (define c2 (make-wire)) (half-adder b c-in s c1) (half-adder a s sum c2) (or-gate c2 c2 c-out) 'ok) (define (ripple-carry-adder as bs ss c) ; The book goes from index n to index 1; we go from n - 1 to 0. (unless (= (length as) (length bs) (length ss)) (error "Lists must be same length -- RIPPLE-CARRY-ADDER" as bs ss)) (define zero-wire (make-wire)) (set-signal! zero-wire 0) (define (loop i carry) (cond [(< i 0) 'ok] [else ; use c for the last carry (define next-carry (if (zero? i) c (make-wire))) (full-adder (list-ref as i) (list-ref bs i) carry (list-ref ss i) next-carry) (loop (sub1 i) next-carry)])) (loop (sub1 (length as)) zero-wire)) ;; How much is one ripple-carry-delay in terms of inverter-delay, or-gate-delay, ;; and and-gate-delay? ;; Look at the sicp-3-3-4-test-cases file to see these test runs. ;; For the given delay times: ;; inverter-delay = 2 ;; and-gate-delay = 3 ;; or-gate-delay = 5 ;; we had: ;; 1 half-adder-delay = 8 = 1 and-gate-delay + 1 or-gate-delay ;; Surprisingly, one full-adder-delay was also 8, and the ripple-carry-adder-delay, ;; for n = 3, was 48 = 2 * n * (or-gate-delay + and-gate-delay). ;; I would not have gotten this right just by looking at the formulas. The ;; function boxes trigger whenever an input wire changes state. They do not ;; necessarily run in sequence. For me that makes it hard to see. ;; Also I think the answer depends on the exact delay times for the primitive ;; gates. If one primitive gate were a bottleneck, that would change the function ;; box delay times in terms of the primitive gate delay times. For example, if ;; the inverter delay were huge compared to the others, the half-adder-delay would ;; be one and-gate-delay plus one inverter-delay, instead of one and-gate-delay ;; plus one or-gate-delay. ;;;;;;;;;; ;; 3.31 ;; ;;;;;;;;;; ;; Here is some half-adder test code: ;; TRY A HALF-ADDER (displayln 'HALF-ADDER) (define in5 (make-wire)) (set-signal! in5 1) (define in6 (make-wire)) (define sum (make-wire)) (define carry (make-wire)) (displayln 'BEFORE) (probe 'in5 in5) (probe 'in6 in6) (probe 'sum sum) (probe 'carry carry) (displayln 'DURING) (half-adder in5 in6 sum carry) (propagate) (displayln 'AFTER) (probe 'in5 in5) (probe 'in6 in6) (probe 'sum sum) (probe 'carry carry) (printf "~n") ;; When you run this "with proc", you get: ;; HALF-ADDER ;; 'done ;; BEFORE ;; in5 0 New-value = 1 ;; in6 0 New-value = 0 ;; sum 0 New-value = 0 ;; carry 0 New-value = 0 ;; DURING ;; 'ok ;; sum 8 New-value = 1 ;; 'done ;; AFTER ;; in5 8 New-value = 1 ;; in6 8 New-value = 0 ;; sum 8 New-value = 1 ;; carry 8 New-value = 0 ;; When you run this "without proc", you get: ;; HALF-ADDER ;; 'done ;; BEFORE ;; DURING ;; 'ok ;; 'done ;; AFTER ;; So even though running add-action! without proc will add the proc to the wire's ;; action-procedures list, it is not getting put on the agenda. So when we later ;; call propagate, nothing happens. This is because after-delay is the function ;; that adds actions to the agenda, and after-delay is not called until we run the ;; proc. ;; For example, if you look at inverter, the proc that gets added to the ;; action-procedures list for the input wire is invert-input. But invert-input ;; calls after-delay, and after-delay calls add-to-agenda!. So you have to call ;; the proc to put it on the agenda. ;;;;;;;;;; ;; 3.32 ;; ;;;;;;;;;; ;; Even though all the actions in a given segment occur at the same time, their ;; order still matters, some come before others. If the segment used a LIFO ;; container like a stack, then the actions would be performed in the wrong order. ;; We need a FIFO container like a queue to preserve the correct ordering. ;; To get LIFO behavior, I used the deque construction from 3.3.2. Then you just ;; had to change insert-queue! to front-insert-deque! etc. This was easier than ;; trying to rewrite the book program to use a list. ;; I couldn't get the exact book example to work. But I did get this example: ;; TRY AN AND-GATE (define in1 (make-wire)) (set-signal! in1 1) (define in2 (make-wire)) (set-signal! in2 0) (define out (make-wire)) (and-gate in1 in2 out) (set-signal! in2 1) (probe 'in1 in1) (probe 'in2 in2) (probe 'out out) (propagate) ;; Here we have two wires, in1 = 1 and in2 = 0, then we apply an and, then we ;; change the 0 to a 1, and then we propagate. So the out wire should be 1, which ;; it is with the FIFO queue: ;; 'done ;; 'done ;; 'ok ;; 'done ;; in1 0 New-value = 1 ;; in2 0 New-value = 1 ;; out 0 New-value = 0 ;; out 3 New-value = 1 ;; 'done ;; However, with the LIFO deque, out first becomes 1 at time 3 but then reverts to ;; 0 as the original set-signal! of in2 to 0 is hit coming out of the segment: ;; 'done ;; 'done ;; 'ok ;; 'done ;; in1 0 New-value = 1 ;; in2 0 New-value = 1 ;; out 0 New-value = 0 ;; out 3 New-value = 1 ;; out 3 New-value = 0 ;; 'done ;; So the book is right. Order within segments matters. We need a FIFO ;; container.