레지스터 파일과 메모리 소자 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

# 10.레지스터 파일과 메모리 소자: 읽기 포트 2개(멀티플렉서), 쓰기 포트 1개(디코더)인 레지스터 파일을
# 흉내 내어 문서의 추적 표와 성질을 확인한다.
# 실행: python3 10_register-file_verify.py  (표준 라이브러리만 쓴다)
# 실제 하드웨어가 아니라 파이썬 흉내다. 클록의 상승 에지 하나를 clock_edge() 호출 하나로 본다.
import random


def decoder(k, n):
    """번호 k -> 2^n개 가운데 k번만 1"""
    return [int(i == k) for i in range(2 ** n)]


def mux(data, sel):
    """디코더 + AND + OR로 고르는 멀티플렉서(값은 정수로 다룬다)"""
    out = 0
    for hot, d in zip(decoder(sel, (len(data) - 1).bit_length()), data):
        out |= d if hot else 0
    return out


class RegisterFile:
    def __init__(self, nreg=32, width=32, zero_reg=True, init=None):
        self.nbits = (nreg - 1).bit_length()
        self.mask = (1 << width) - 1
        self.reg = list(init) if init else [0] * nreg
        self.zero_reg = zero_reg

    def read(self, r1, r2):
        """읽기는 조합 회로다. 번호만 주면 클록과 상관없이 지금 들어 있는 값이 나온다."""
        return mux(self.reg, r1), mux(self.reg, r2)

    def clock_edge(self, write_reg, write_data, reg_write):
        """상승 에지: 디코더 출력 AND RegWrite가 1인 레지스터 하나만 새 값을 잡는다."""
        enable = [hot & reg_write for hot in decoder(write_reg, self.nbits)]
        if self.zero_reg:
            enable[0] = 0                      # MIPS: 0번 레지스터는 늘 0
        assert sum(enable) <= 1
        self.reg = [write_data & self.mask if en else old for en, old in zip(enable, self.reg)]


if __name__ == "__main__":
    # 문서의 추적 표: 레지스터 4개 x 8비트, 처음 값 R0..R3 = 0, 7, 0, 0
    rf = RegisterFile(nreg=4, width=8, zero_reg=False, init=[0, 7, 0, 0])
    steps = [  # (읽기1, 읽기2, 쓰기 번호, 쓰기 값, RegWrite)
        (1, 2, 2, 25, 1),
        (2, 1, 3, 32, 1),
        (3, 2, 1, 99, 0),
        (1, 3, 1, 57, 1),
    ]
    seen, after = [], []
    for r1, r2, wr, wd, we in steps:
        seen.append(rf.read(r1, r2))           # 사이클 동안 보이는 값(에지 전)
        rf.clock_edge(wr, wd, we)
        after.append(list(rf.reg))
    assert seen == [(7, 0), (25, 7), (32, 25), (7, 32)], seen
    assert after == [[0, 7, 25, 0], [0, 7, 25, 32], [0, 7, 25, 32], [0, 57, 25, 32]], after
    assert 25 + 32 == 57
    # 같은 사이클에 같은 레지스터를 읽고 쓰면 읽히는 것은 옛값이다(첫 줄: R2를 읽으며 R2에 25를 쓴다)
    assert seen[0][1] == 0 and after[0][2] == 25
    # 디코더: 32개 가운데 하나만 켜진다. 쓰기 번호 5비트, 읽기 멀티플렉서는 32입력
    for k in range(32):
        d = decoder(k, 5)
        assert len(d) == 32 and sum(d) == 1 and d[k] == 1
    # 32 x 32 레지스터 파일을 사전 하나로 만든 본보기와 무작위 20,000사이클 비교
    rnd = random.Random(10)
    rf = RegisterFile()
    model = [0] * 32
    for _ in range(20000):
        r1, r2, wr = rnd.randrange(32), rnd.randrange(32), rnd.randrange(32)
        wd, we = rnd.getrandbits(32), rnd.randint(0, 1)
        assert rf.read(r1, r2) == (model[r1], model[r2])
        rf.clock_edge(wr, wd, we)
        if we and wr != 0:
            model[wr] = wd
        assert rf.reg == model and rf.reg[0] == 0
    # 플립플롭 수: 32 x 32 = 1,024개. 문서의 작은 예는 4 x 8 = 32개
    assert 32 * 32 == 1024 and 4 * 8 == 32
    # 카드 C2: R1 = 7, R2 = 25에서 한 사이클에 (읽기 R2, R2 / 쓰기 R2 <- 50, RegWrite 1)
    rf = RegisterFile(nreg=4, width=8, zero_reg=False, init=[0, 7, 25, 0])
    assert rf.read(2, 2) == (25, 25)
    rf.clock_edge(2, 50, 1)
    assert rf.read(2, 2) == (50, 50)
    # 카드 C3: RegWrite = 0이면 쓰기 번호와 값이 무엇이든 그대로다
    before = list(rf.reg)
    rf.clock_edge(1, 123, 0)
    assert rf.reg == before
    print("ALL CHECKS PASSED")