DIVE

Part 1: 物理・電子・論理回路

第 2 章

時を刻む機械 — クロックとレジスタが生む「時間」と「記憶」

前章で、トランジスタと論理ゲートが「計算」を実現することを見た。だが、あの回路には決定的な弱点がある。何も覚えられないのだ。

AND ゲートに 1 と 1 を入れれば 1 が出る。しかし入力を切った瞬間、出力は消える。ゲートは「今この瞬間の計算」しかできない。過去を保持できない回路に、コンピュータは作れない。

この章では、回路が「記憶」と「時間」を獲得する物語を追う。


記憶の正体 — 出力を入力に戻す

論理ゲートに記憶をさせる方法は、意外なほど単純だ。出力を自分の入力に戻す(フィードバック)。

2つのNORゲートの出力を、互いの入力に交差させて接続する。これがSRラッチ(Set-Reset Latch)、最も原始的な記憶素子だ。

      ┌──────────────┐
  S ──┤NOR├──┬─────── Q
      └──────┘  │
             ╲ ╱
              ╳     (出力を互いに交差フィードバック)
             ╱ ╲
      ┌──────┘  │
  R ──┤NOR├──┴─────── Q̄
      └──────────────┘

動作はこうだ:

入力を切っても、フィードバックループが状態を「循環」させ続ける。電気が回路をぐるぐる回ることで、1ビットの情報が保たれる。記憶とは、情報が回路の中を循環し続けている状態なのだ。

SRQ(次の状態)意味
00Q(保持)記憶を維持
101セット
010リセット
11禁止未定義(使ってはいけない)

クロック — 全員で歩調を合わせる号令

SRラッチは記憶できるが、問題がある。入力が変われば即座に出力が変わってしまう。何億個もの回路がバラバラのタイミングで反応したら、計算はカオスになる。

そこで登場するのがクロック信号(Clock)だ。一定周期で 0 と 1 を繰り返す、回路全体の「メトロノーム」である。

クロック周期 T=1f(f:クロック周波数)\text{クロック周期}\ T = \frac{1}{f} \quad (f: \text{クロック周波数})

例えば 3.0 GHz のCPUなら:

T=13.0×109 Hz≈0.33 ナノ秒T = \frac{1}{3.0 \times 10^9\,\text{Hz}} \approx 0.33\,\text{ナノ秒}

1秒間に30億回、全回路が歩調を合わせて動く。厨房で例えるなら、クロックは「料理長の号令」だ。「せーの!」の合図でだけ全員が一斉に次の作業に移る。各自が勝手に動けば厨房は混乱するが、号令に合わせれば統率された動きになる。

クロックの「立ち上がり(0→1の瞬間)」でのみ状態を更新する仕組みをエッジトリガと呼び、これを実装したのがDフリップフロップだ。

        ┌─────────────┐
  D ────┤D           Q├──── 出力
        │             │
CLK ──▷ │ (立ち上がり  │
        │  でのみ取込) │
        └─────────────┘

「号令がかかった瞬間だけ、目の前の値を記憶し、次の号令まで守り続ける」。これがフリップフロップの本質だ。


レジスタ — フリップフロップを束ねる

1個のフリップフロップは1ビットしか覚えられない。8個並べれば8ビット、64個並べれば64ビットを同時に記憶できる。この束がレジスタ(Register)だ。

レジスタは、CPUが計算のために使う最速の記憶領域である。メインメモリ(RAM)より桁違いに速い。なぜなら、CPUコアの真横(同じシリコン上)にあり、クロック1周期でアクセスできるからだ。

記憶階層の速度差を体感してみよう:

記憶場所アクセス時間の目安厨房の比喩
レジスタ~0.3 ns(1クロック)利き手に持った包丁
L1キャッシュ~1 nsまな板の上の食材
メインメモリ~100 ns冷蔵庫の中
SSD~100,000 ns倉庫まで取りに行く

レジスタとメモリでは300倍以上の速度差がある。だから、CPUは「今まさに使うデータ」をレジスタに載せて計算する。

Pythonでレジスタの動きを模す

Dフリップフロップの「クロックごとに値を取り込む」挙動を、シンプルにシミュレートしてみる。

class Register:
    """クロックの立ち上がりで入力を取り込む8ビットレジスタ"""
    def __init__(self):
        self.value = 0          # 保持している状態
        self._next = 0          # 次に取り込む値

    def set_input(self, d: int):
        self._next = d & 0xFF   # 8ビットにマスク

    def clock_edge(self):
        """クロックの立ち上がり: このときだけ状態が更新される"""
        self.value = self._next

reg = Register()
reg.set_input(42)
print(reg.value)     # 0  ← まだ号令(クロック)が来ていないので変わらない
reg.clock_edge()     # 号令!
print(reg.value)     # 42 ← ここで初めて取り込まれる

入力を与えただけでは変わらず、clock_edge()(号令)が来て初めて値が確定する。この「タイミングの規律」こそ、順序回路の心臓だ。


組み合わせ回路 vs 順序回路

ここまでで、回路には2種類あることが分かった。

組み合わせ回路:出力=f(入力)\text{組み合わせ回路:}\quad \text{出力} = f(\text{入力}) 順序回路:出力=f(入力, 現在の状態)\text{順序回路:}\quad \text{出力} = f(\text{入力},\ \text{現在の状態})

コンピュータが「プログラムを順に実行する」「変数に値を覚える」といった動作ができるのは、順序回路が状態を持つからだ。前章の計算能力に、この章の記憶能力が加わって、ようやく「機械」が動き出す。


まとめ — 「おまじない」の消し方

  1. 記憶=フィードバック:出力を入力に戻すことで、情報が回路内を循環し続ける。これがビットを保持する仕組み
  2. クロック=全体の号令:一定周期の信号で全回路の歩調を合わせ、カオスを防ぐ
  3. フリップフロップ=規律ある記憶:クロックの立ち上がりの瞬間だけ値を取り込み、次まで守る
  4. レジスタ=最速の記憶領域:フリップフロップの束で、CPUの計算の足場になる

「なぜCPUに”GHz”という単位があるのか」「なぜレジスタはメモリより速いのか」——その答えの下地が整った。

次章では、この計算(ALU)と記憶(レジスタ)を組み合わせ、命令を読んで実行する機械=CPUを、自分の手で組み立てていく。


「時間とは、すべてが一度に起こらないようにするための自然の仕組みである。コンピュータはこの原理を、一本のクロック信号として飼い慣らした。」