래치와 플립플롭 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

# 09.래치와 플립플롭: NOR 두 개를 맞물린 SR 래치, D 래치, 래치 두 개로 만든 D 플립플롭을 흉내 내어
# 문서의 추적 표와 성질을 확인한다.
# 실행: python3 09_latch-flip-flop_verify.py  (표준 라이브러리만 쓴다)
# 실제 하드웨어가 아니라 파이썬 흉내다. 게이트 지연, 준비 시간, 유지 시간은 다루지 않고
# "입력이 바뀐 뒤 값이 가라앉은 상태"만 계산한다.
import random


def NOR(a, b): return 1 - (a | b)


class SRLatch:
    """NOR 두 개를 서로의 입력에 맞물린 회로. Q = NOR(R, Qbar), Qbar = NOR(S, Q)"""

    def __init__(self, q=0):
        self.q, self.qb = q, 1 - q

    def step(self, s, r):
        for _ in range(4):                     # 값이 더 안 바뀔 때까지 되풀이
            q = NOR(r, self.qb)
            qb = NOR(s, q)
            if (q, qb) == (self.q, self.qb):
                break
            self.q, self.qb = q, qb
        return self.q


class DLatch:
    """C=1이면 열려서 Q가 D를 따라가고, C=0이면 닫혀서 마지막 값을 붙든다."""

    def __init__(self, q=0):
        self.sr = SRLatch(q)

    def step(self, d, c):
        return self.sr.step(s=d & c, r=(1 - d) & c)

    @property
    def q(self):
        return self.sr.q


class DFlipFlop:
    """상승 에지에서 D를 잡는다. 앞 래치는 CLK=0일 때, 뒤 래치는 CLK=1일 때 열린다."""

    def __init__(self, q=0):
        self.master, self.slave = DLatch(q), DLatch(q)

    def step(self, d, clk):
        m = self.master.step(d, 1 - clk)
        return self.slave.step(m, clk)

    @property
    def q(self):
        return self.slave.q


# 문서의 추적 표: 시각마다 (CLK, D)
WAVE = [(0, 0), (0, 1), (1, 1), (1, 0), (1, 1), (0, 1), (0, 0), (1, 0), (1, 1), (0, 1), (1, 1), (0, 0)]


def run_trace(wave):
    latch, ff = DLatch(0), DFlipFlop(0)
    rows = []
    for clk, d in wave:
        rows.append((clk, d, latch.step(d, clk), ff.step(d, clk)))
    return rows


if __name__ == "__main__":
    # SR 래치: S=1이면 1, R=1이면 0, 둘 다 0이면 붙든다
    sr = SRLatch(0)
    assert sr.step(1, 0) == 1 and sr.step(0, 0) == 1
    assert sr.step(0, 1) == 0 and sr.step(0, 0) == 0
    assert (sr.q, sr.qb) == (0, 1)
    # S=R=1이면 Q와 Qbar가 둘 다 0이 되어 "서로 반대"가 깨진다
    sr.step(1, 1)
    assert (sr.q, sr.qb) == (0, 0)
    # D 래치: 열려 있으면 따라가고 닫혀 있으면 붙든다
    rnd = random.Random(3)
    lt = DLatch(0)
    held = 0
    for _ in range(5000):
        d, c = rnd.randint(0, 1), rnd.randint(0, 1)
        q = lt.step(d, c)
        if c:
            held = d
        assert q == held
    # D 플립플롭: 출력은 상승 에지에서만 바뀌고, 그때의 D가 된다
    ff = DFlipFlop(0)
    prev_clk, q_model, prev_d = 0, 0, 0
    for _ in range(5000):
        # 한 번에 CLK와 D 가운데 하나만 바꾼다(에지와 D가 동시에 바뀌는 것은 준비·유지 시간 위반이라 뺀다)
        if rnd.random() < 0.5:
            clk, d = 1 - prev_clk, prev_d
        else:
            clk, d = prev_clk, rnd.randint(0, 1)
        q = ff.step(d, clk)
        if prev_clk == 0 and clk == 1:
            q_model = d
        assert q == q_model
        prev_clk, prev_d = clk, d
    # 문서의 추적 표
    rows = run_trace(WAVE)
    assert [r[2] for r in rows] == [0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], [r[2] for r in rows]
    assert [r[3] for r in rows] == [0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1], [r[3] for r in rows]
    for i, r in enumerate(rows, 1):
        print(i, *r)
    # 카드 C2: CLK,D = (0,1),(1,1),(1,0),(0,0),(0,1),(1,1) 처음 Q=0
    rows = run_trace([(0, 1), (1, 1), (1, 0), (0, 0), (0, 1), (1, 1)])
    assert [r[2] for r in rows] == [0, 1, 0, 0, 0, 1]
    assert [r[3] for r in rows] == [0, 1, 1, 1, 1, 1]
    print("ALL CHECKS PASSED")