数字电路模拟
基于 SICP 练习 3.28 - 3.32
引言
数字逻辑模拟系统是一个事件驱动的模拟 程序,一些事件引发另一些在随后时间发生的事件,它们又会引发随后的事件,并如此继续下去。
本文用 scheme 实现一个数字电路模拟器,可以估计数字系统的时延。
队列
队列标记一个队首、队尾指针的序对。
(define (front-ptr queue) (car queue))
(define (rear-ptr queue) (cdr queue))
(define (set-front-ptr! queue item) (set-car! queue item))
(define (set-rear-ptr! queue item) (set-cdr! queue item))
(define (empty-queue? queue) (null? (front-ptr queue)))
(define (make-queue) (cons '() '()))
(define (front-queue queue)
(if (empty-queue? queue)
(error "FRONT called with an empty queue" queue)
(car (front-ptr queue))))
(define (insert-queue! queue item)
(let ((new-pair (cons item '())))
(cond ((empty-queue? queue)
(set-front-ptr! queue new-pair)
(set-rear-ptr! queue new-pair))
(else
(begin
(set-cdr! (rear-ptr queue) new-pair)
(set-rear-ptr! queue new-pair))))))
(define (delete-queue! queue)
(if (empty-queue? queue)
(error "DELETE! called with an empty queue" queue)
(set-front-ptr! queue (cdr (front-ptr queue)))))
需要注意的是,scheme 解释器往往没有“正确地”显示这个队列。如果有调试需要,可以考虑实现一个合适的打印过程。
导线
当导线上的信号改变时,这种改变应当能引起其他导线的变化。
(define (make-wire)
(define (call-each procedures)
(if (not (null? procedures))
(begin
((car procedures))
(call-each (cdr procedures)))))
(let ((signal-value 0) (action-procedures '()))
(define (set-my-signal! new-value)
(if (not (= signal-value new-value))
(begin (set! signal-value new-value)
(call-each action-procedures))))
(define (accept-action-procedure! proc)
(set! action-procedures (cons proc action-procedures))
(proc))
(define (dispatch m)
(cond ((eq? m 'get-signal) signal-value)
((eq? m 'set-signal!) set-my-signal!)
((eq? m 'add-action!) accept-action-procedure!)
(else (error "Unknow operation -- WIRE" m))))
dispatch))
函数式风格包装:
(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))
逻辑门
所谓逻辑门,就是规定一根导线如何影响另一根导线。
(define (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)))
(define (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)))
(define (inverter input output)
(define (logical-not s)
(cond ((= s 0) 1)
((= s 1) 0)
(else (error "Invalid signal" s))))
(define (invert-input)
(let ((new-value (logical-not (get-signal input))))
(after-delay inverter-delay
(lambda ()
(set-signal! output new-value)))))
(add-action! input invert-input))
(define (and-gate a1 a2 output)
(define (logical-and s1 s2)
(cond ((and (= s1 1) (= s2 1)) 1)
((and (= s1 0) (= s2 1)) 0)
((and (= s1 1) (= s2 0)) 0)
((and (= s1 0) (= s2 0)) 0)
(else (error "Invalid signal" s1 s2))))
(define (and-action-procedure)
(let ((new-value
(logical-and (get-signal a1) (get-signal a2))))
(after-delay and-gate-delay
(lambda ()
(set-signal! output new-value)))))
(add-action! a1 and-action-procedure)
(add-action! a2 and-action-procedure))
(define (or-gate a1 a2 output)
(define (logical-or s1 s2)
(cond ((and (= s1 1) (= s2 1)) 1)
((and (= s1 0) (= s2 1)) 1)
((and (= s1 1) (= s2 0)) 1)
((and (= s1 0) (= s2 0)) 0)
(else (error "Invalid signal" s1 s2))))
(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))
事件的待处理表
时间片
时间是一种设施,发明它就是为了不让所有的事情都立刻发生。
;; time segment
(define (make-time-segment time queue)
(cons time queue))
(define (segment-time s) (car s))
(define (segment-queue s) (cdr s))
时间片标记了一个时刻,并赋予该时刻的事件发生顺序。
待处理表
表头存放当前时间,表项存放时间片。
;; agenda
(define (make-agenda) (list 0))
(define (current-time agenda) (car agenda))
(define (set-current-time! agenda time)
(set-car! agenda time))
(define (segments agenda) (cdr agenda))
(define (set-segments! agenda segments)
(set-cdr! agenda segments))
(define (first-segment agenda) (car (segments agenda)))
(define (rest-segments agenda) (cdr (segments agenda)))
(define (empty-agenda? agenda)
(null? (segments agenda)))
(define (add-to-agenda! time action agenda)
(define (belongs-before? segments)
(or (null? segments)
(< time (segment-time (car segments)))))
(define (make-new-time-segment time action)
(let ((q (make-queue)))
(insert-queue! q action)
(make-time-segment time q)))
(define (add-to-segments! segments)
(if (= (segment-time (car segments)) time)
(insert-queue! (segment-queue (car segments))
action)
(let ((rest (cdr segments)))
(if (belongs-before? rest)
(set-cdr!
segments
(cons (make-new-time-segment time action)
(cdr segments)))
(add-to-segments! rest)))))
(let ((segments (segments agenda)))
(if (belongs-before? segments)
(set-segments!
agenda
(cons (make-new-time-segment time action)
segments))
(add-to-segments! segments))))
(define (remove-first-agenda-item! agenda)
(let ((q (segment-queue (first-segment agenda))))
(begin
(delete-queue! q)
(if (empty-queue? q)
(set-segments! agenda (rest-segments agenda))))))
(define (first-agenda-item agenda)
(if (empty-agenda? agenda)
(error "Agenda is empty -- FIRST_AGENDA_ITEM")
(let ((first-seg (first-segment agenda)))
(begin
(set-current-time! agenda (segment-time first-seg))
(front-queue (segment-queue first-seg))))))
按这种方式,当前时间将总是最近处理的动作的时间。
信号传播
;; main loop
(define (after-delay delay action)
(add-to-agenda! (+ delay (current-time the-agenda))
action
the-agenda))
(define (propagate)
(if (not (empty-agenda? the-agenda))
(let ((first-item (first-agenda-item the-agenda)))
(begin
(first-item)
(remove-first-agenda-item! the-agenda)
(propagate)))))
测试
监视器
安装监视器后,导线信号的改变不仅会引起未来的对其他导线信号的操作,还会触发监视器打印当前时刻和所监视信号。
(define (probe name wire)
(add-action! wire
(lambda ()
(begin
(newline)
(display (current-time the-agenda))
(display " ")
(display name)
(display " New-value = ")
(display (get-signal wire))))))
组合逻辑
;; test
(define the-agenda (make-agenda))
(define inverter-delay 10)
(define and-gate-delay 2)
(define or-gate-delay 2)
(define a (make-wire))
(define b (make-wire))
(define c (make-wire))
(define o (make-wire))
(and-gate a c o)
(inverter b c)
(probe 'a a)
(probe 'b b)
(probe 'c c)
(probe 'o o)
;; init event
(set-signal! a 1)
(set-signal! b 0)
(propagate)
0 a New-value = 0
0 b New-value = 0
0 c New-value = 0
0 o New-value = 0
0 a New-value = 1
10 c New-value = 1
12 o New-value = 1
值得注意的是,set-signal!
添加了初始事件。整个模拟过程结束在 12 时间片处,是电路中关键路径的耗时。
时序逻辑
;; test
(define max-iteration 100)
(define the-agenda (make-agenda))
(define inverter-delay 1)
(define and-gate-delay 2)
(define or-gate-delay 2)
(define and-not-gate-delay 3)
(define S (make-wire))
(define R (make-wire))
(define Q (make-wire))
(define Q- (make-wire))
(and-not-gate S Q- Q)
(and-not-gate Q R Q-)
(probe 'Q Q)
(probe 'Q- Q-)
(probe 'S S)
(probe 'R R)
;; init event
(set-signal! S 1)
(set-signal! R 1)
(propagate)
0 Q New-value = 0
0 Q- New-value = 0
0 S New-value = 0
0 R New-value = 0
0 S New-value = 1
0 R New-value = 1
3 Q New-value = 1
3 Q- New-value = 1
6 Q- New-value = 0
6 Q New-value = 0
9 Q New-value = 1
9 Q- New-value = 1
12 Q- New-value = 0
12 Q New-value = 0
15 Q New-value = 1
15 Q- New-value = 1
18 Q- New-value = 0
18 Q New-value = 0
21 Q New-value = 1
21 Q- New-value = 1
24 Q- New-value = 0
24 Q New-value = 0
27 Q New-value = 1
...
震荡电路的行为也可以模拟,真是十分神奇。
关于同时发生的事件
时间片队列中动作的执行顺序必须和动作的添加顺序一致,这样才能让同一导线上信号的最新的计算结果体现出来(如果存在对同一导线的信号赋值的话)。这里和网络数据包乱序有相似性。