Guowei Lv

5 minute read

Do you think Computer Science equals building websites and mobile apps?

Are you feeling that you are doing repetitive and not so intelligent work?

Are you feeling a bit sick about reading manuals and copy-pasting code and keep poking around until it works all day long?

Do you want to understand the soul of Computer Science?

If yes, read SICP!!!

In part 3 we can finally start talking about the digital circuits.


The wire is an object. It has two states: signal-value and action-procedures.

signal-value represents the current signal on the wire (0 or 1), initialized to 0.

action-proceduresis a collection of procedures to be run when signal changes on the wire.


The constructor of wire is implemented using the *message-passing*style. If you don’t know what is message-passing, take a look at my previous post.

(define (make-wire)
  (let ((signal-value 0)
        (action-procedures '()))
    (define (set-my-signal! new-value)
      (if (not (= signal-value new-value))
          (begin (set! signal-value new-value)
    (define (accept-action-procedure! proc)
      (set! action-procedures
            (cons proc action-procedures))
    (define (dispatch m)
      (cond ((eq? m 'get-signal)
            ((eq? m 'set-signal!)
            ((eq? m 'add-action!)
            (else (error "Unknown operation:
                          WIRE" m))))
(define (call-each procedures)
  (if (null? procedures)
      (begin ((car procedures))
             (call-each (cdr procedures)))))


With the constructor at hand, we can now define the following operations on wires.

(define (get-signal wire)
  (wire 'get-signal))

(define (set-signal! wire new-value)
  ((wire 'set-signal!) new-value))

(define (add-action! wire action-procedure)
  ((wire 'add-action!) action-procedure))

You can see that these procedures are more of a syntatic sugar. I think they are very self explanatory and need no more words.

Logical Gates

Let’s make gates out of wires.


The inverter consisted of two wires, inputand output.

The functionality is to invert signal. But it happens after a delay.

(define (inverter input output)
  (define (invert-input)
    (let ((new-value
           (logical-not (get-signal input))))
       (lambda ()
         (set-signal! output new-value)))))
  (add-action! input invert-input)

(define (logical-not s)
  (cond ((= s 0) 1)
        ((= s 1) 0)
        (else (error "Invalid signal" s))))

;; Adds to the global the-agenda an action with a delay
(define (after-delay delay action)
   (+ delay (current-time the-agenda))
(define inverter-delay 2)

AND Gate

The AND gate is similar.

(define (and-gate a1 a2 output)
  (define (and-action-procedure)
    (let ((new-value
           (logical-and (get-signal a1)
                        (get-signal a2))))
       (lambda ()
         (set-signal! output new-value)))))
  (add-action! a1 and-action-procedure)
  (add-action! a2 and-action-procedure)
(define (logical-and a b)
  (if (and (= a 1) (= b 1))
(define and-gate-delay 3)

OR Gate

Try to implement the OR gate by yourself.

(define (or-gate a1 a2 output)
  (define (or-action-procedure)
    (let ((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)

(define (logical-or a b)
  (if (and (= a 0) (= b 0))

(define or-gate-delay 5)

OK, we have implemented everything now. Let’s try to make a real circuit!

Make a simple circuit

Let’s make a circuit as in the following picture:

First let’s make a global agenda called the-agenda:

(define the-agenda (make-agenda))

Now let’s make the wires and connect them:

; make wires
(define a1 (make-wire))
(define a2 (make-wire))
(define a3 (make-wire))
(define output (make-wire))

;connect the circuit
(and-gate a1 a2 a3)
(inverter a3 output)

Let’s pause now, and take a look at the-agenda, what do you think it looks like now? Empty, right? Wrong!

Let’s print the-agenda:

;(0 (2 (#[compound-procedure 14]) #[compound-procedure 14]) (3 (#[compound-procedure 15] #[compound-procedure 16]) #[compound-procedure 16]))

It may be a bit hard to make sense of it, but it means:

  • The current simulation time is 0.

  • There are 1 procedure scheduled to run at time 2(#[compound-procedure 14]), and 2 procedures schedulted to run at time 3(#[compound-procedure 15] and #[compound-procedure 16]).

You may wonder, well, we just simply connected the wires and haven’t done anything yet, why there are procedures already scheduled to run in the agenda?

The key is the accept-action-procedure! inside the make-wire procedure:

(define (accept-action-procedure! proc)
      (set! action-procedures
            (cons proc action-procedures))

Notice that it execute the new proc right after it put it in the action-procedureslist.

In order to understand why it does it, let’s compare what we get if it does not do it.

Let’s run everything again with the (proc) removed.

Then if we check the-agenda again, it shows:


Which one do you think is correct?

Let’s judge using common sense.

One thing to note is that all wires have signal 0 by default. Imagine in real life you assembled a circuit just like this, what will happen right after everything is connected?

a1 and a2 both are 0 now, that means and-gate-delay time later, a3 will become 0. Notice also that a3 is already 0 now, that means inverter-delay time later, output will be 1.

Now you should see that even without changing any signal in the circuit, after construction, the circuit will start running automatically, because all the wires have initial signal 0.

OK, now we set the a1’s signal to 1 and call propagate.

(set-signal! a1 1)
; (3)

Next we set the a2 also to 1 and propagate.

(set-signal! a2 1)
; (8)

Try to go through what really happened and see if you really understand what is going on.

comments powered by Disqus