今回は、前回作ったD Flip Flop(以下D-FF)を使ってレジスタを作ろう。対象のCPU(TD4)は4 bitのレジスタを2つ持っている。これを実装したいわけだ。
1 bitレジスタ
最初から複雑なものを作るのは大変なので、まず簡単なところから1 bitレジスタを考えることにする。今回作るレジスタの基本構造は次の通り。
レジスタは基本的に値を保持し続けるものだ。しかし前回作ったD-FFはクロックの立ち上がり時点の入力値がそのまま出てくるだけだ。だからD-FFの出力をぐるりと回してきて入力に戻してやる。そうすれば、クロックが入るたびに出てきたものをまた入力するので同じ値がぐるぐる回る、というわけだ(入力A)。
でもそれだけだと値が変わらず何の役にも立たないので、外からの入力値(入力B)も受け入れられるようにする。その値と保持している値のどちらを新しい値とするかをスイッチで切り替えるのだ。スイッチには、以前作ったMultiplexerが使える。
なお、前回のD-FFは出力値を強制的にHIにセットするPR入力と反転した出力$\overline{Q}$を持っているが、使わないので割愛する(PRには常にHI(無効)を入力、出力は$Q$だけ取り出す)。早速コードにしてみよう。
-- 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)を毎回入力してやる必要があるのだ。
テストコードは以下。
>>> 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レジスタで共通に使う。早速コードにしてみよう。
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レジスタに入れているだけだ。とてもシンプルだ。
下記のような簡単なテストもしてみた。大丈夫そう。
>>> 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に限定する必要すらなさそうだ。ならばビット数を引数で与えてやれば汎用的になるのではないか?
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
を置き換えよう(部分適用だな)。
lc_register4 :: LogicCircuit
lc_register4 = lc_register 4
さっきと同じテストを実行し、エラーがないことを確認。
ところで引数のw
はビット数を表すが、チェックしなくていいのだろうか? これまでは面倒くさいので目をつむっていたが、やはり入力値チェックはしておいたほうがいいのだろうなと思う。さてw
の条件は何だろう? ビット数だから1以上? でも内部で1 bitレジスタを呼んでいるから2以上でないと意味がない?
1だと無駄はあるが、結果は正しいので許容できる。0だと結果は0個=空リストになってしまうが、これはこれでありかもしれないと思い始めた。さすがに負のビット数はないので、w
は0以上にしよう。となると入力データのサイズも気になる。現状値と新規入力値が必要なのでビット数 × 2だ。これらの条件を満たさないならどうするか? HaskellならMaybe
型で返すのが真っ当な気がする。が、あとあと大変な予感がするので、へなちょこながらとりあえず今の所はerror
で逃げることにする。
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を用いてレジスタが準備できた。「状態」は別途管理しないと行けないので完璧とは言えないが、まあよしとしよう。
次は何にするか? 加算器あたりかな?
(ここまでのソースはこちら)
※ 最新のソースは執筆時点からだいぶ変更している。