CPUの創りかた(5): 4 bitレジスタ

今回は、前回作ったD Flip Flop(以下D-FF)を使ってレジスタを作ろう。対象のCPU(TD4)は4 bitのレジスタを2つ持っている。これを実装したいわけだ。

## 1 bitレジスタ

最初から複雑なものを作るのは大変なので、まず簡単なところから1 bitレジスタを考えることにする。今回作るレジスタの基本構造は次の通り。

![reg1bit.png](https://qiita-image-store.s3.amazonaws.com/0/69542/7277b98d-1ed1-eed9-cd6f-4fd489ca09fe.png)

レジスタは基本的に値を保持し続けるものだ。しかし前回作ったD-FFはクロックの立ち上がり時点の入力値がそのまま出てくるだけだ。だからD-FFの出力をぐるりと回してきて入力に戻してやる。そうすれば、クロックが入るたびに出てきたものをまた入力するので同じ値がぐるぐる回る、というわけだ(入力A)。

でもそれだけだと値が変わらず何の役にも立たないので、外からの入力値(入力B)も受け入れられるようにする。その値と保持している値のどちらを新しい値とするかをスイッチで切り替えるのだ。スイッチには、以前作ったMultiplexerが使える。

なお、前回のD-FFは出力値を強制的にHIにセットするPR入力と反転した出力$\overline{Q}$を持っているが、使わないので割愛する（PRには常にHI(無効)を入力、出力は$Q$だけ取り出す）。早速コードにしてみよう。

```haskell
-- c: !CLR
-- l: !LD (LO -> select a, HI -> select b)
-- a,b: input value (A and B)

lc_register1 :: LogicCircuit
lc_register1 (c:l:a:b:_) = take 1 $ lc_dff_cp ([c, sHI] ++ d)
  where
    d = lc_multiplexer2ch [l, a, b]
```

リセットしたい時はCLRをLOにする。LDは負論理なのでHIのときは値を保持(Aを採用)、LOで外からの入力値をセット(Bを採用)だ。前回書いたように、値を内部的に保持することはせず、回路の外で管理することにした。なので保持したい値(上記ではA)を毎回入力してやる必要があるのだ。

テストコードは以下。

```haskell
>>> lc_register1 [sLO, sLO, sHI, sHI] == [sLO]  -- clear
True
>>> lc_register1 [sHI, sHI, sHI, sLO] == [sHI]  -- select A
True
>>> lc_register1 [sHI, sHI, sLO, sHI] == [sLO]  -- select A
True
>>> lc_register1 [sHI, sLO, sHI, sLO] == [sLO]  -- select B
True
>>> lc_register1 [sHI, sLO, sLO, sHI] == [sHI]  -- select B
True
```

## 4 bitレジスタ

1 bitレジスタができれば4 bitも簡単だ。4つ並べればよい。ただし、CLRとLDは4つの1 bitレジスタで共通に使う。早速コードにしてみよう。

```haskell
lc_register4 :: LogicCircuit
lc_register4 (c:l:ds)
  where
    a = take 4 ds
    b = take 4 $ drop 4 ds
    procReg1 :: Bin -> Bin -> (Bin, Bin) -> [Bin]
    procReg1 c l (a, b) = lc_register1 [c, l, a, b]
```

入力データ部分(`ds`)から、4 bitずつ2つの値A,Bを取り出している。A,Bのリストから同じ桁の要素を組みにして先の1 bitレジスタに入れているだけだ。とてもシンプルだ。

下記のような簡単なテストもしてみた。大丈夫そう。

```haskell
>>> let d0 = toBits "0000"
>>> let d1 = toBits "1111"
>>> let d2 = toBits "0101"
>>> let d3 = toBits "0011"
>>> lc_register4 ([sLO, sLO] ++ d1 ++ d2) == d0  -- when CLR = ON
True
>>> lc_register4 ([sLO, sHI] ++ d1 ++ d2) == d0  -- when CLR = ON
True
>>> lc_register4 ([sHI, sHI] ++ d1 ++ d2) == d1  -- when LD = OFF
True
>>> lc_register4 ([sHI, sLO] ++ d1 ++ d2) == d2  -- when LD = ON
True
```

CLRをLOにすればレジスタはリセットされるので入力によらず出力は"0000"。HIにすればLDのOFF/ONによって、現在値Aか新規入力値Bが選択されている。D-FFさえ用意してあれば、なんともあっけないものだ :-)

## n bitレジスタ

さて、先の4 bitレジスタのコードをあらためて見てみると'4'が散見される。このようなマジックナンバーはいただけない。定数にしてまとめたらどうか?いや、このコードであれば別に4に限定する必要すらなさそうだ。ならばビット数を引数で与えてやれば汎用的になるのではないか?

```haskell
lc_register :: Int -> LogicCircuit
lc_register w (c:l:ds) = concat $ map (procReg1 c l) $ zip a b
  where
    a = take w ds
    b = take w $ drop w ds
    procReg1 :: Bin -> Bin -> (Bin, Bin) -> [Bin]
    procReg1 c l (a, b) = lc_register1 [c, l, a, b]
```

これなら4 bitと言わず、8 bitでも64 bitでも行けそうだ! 今回は使い道がないが…。これを使って先の`lc_register4`を置き換えよう(部分適用だな）。

```haskell
lc_register4 :: LogicCircuit
lc_register4 = lc_register 4
```

さっきと同じテストを実行し、エラーがないことを確認。

ところで引数の`w`はビット数を表すが、チェックしなくていいのだろうか? これまでは面倒くさいので目をつむっていたが、やはり入力値チェックはしておいたほうがいいのだろうなと思う。さて`w`の条件は何だろう? ビット数だから1以上? でも内部で1 bitレジスタを呼んでいるから2以上でないと意味がない?

1だと無駄はあるが、結果は正しいので許容できる。0だと結果は0個=空リストになってしまうが、これはこれでありかもしれないと思い始めた。さすがに負のビット数はないので、`w`は0以上にしよう。となると入力データのサイズも気になる。現状値と新規入力値が必要なのでビット数 × 2だ。これらの条件を満たさないならどうするか? Haskellなら`Maybe`型で返すのが真っ当な気がする。が、あとあと大変な予感がするので、へなちょこながらとりあえず今の所は`error`で逃げることにする。

```haskell
lc_register w (c:l:ds)
  | w < 0       = error ("bit width is invalid (" ++ show w ++ ")")
  | len < w * 2 = error ("no enough input (" ++ show len ++ ")")
  | otherwise   = concat $ map (procReg1 c l) $ zip a b
  where
    len = length ds
      :
```

これで、入力値がまともでなければ処理が「止まる」。まあシステムがPANICを起こしたと考えればいいか。

## まとめ

前回のD-FFを用いてレジスタが準備できた。「状態」は別途管理しないと行けないので完璧とは言えないが、まあよしとしよう。

次は何にするか? 加算器あたりかな?

(ここまでのソースは[こちら](https://github.com/eijian/mkcpu))
※　最新のソースは執筆時点からだいぶ変更している。