mikecat が 2022年12月02日19時24分22秒 に編集
初版
タイトルの変更
ロジックICによる自作CPU「CalicoCPU」
タグの変更
自作CPU
ロジックIC
74シリーズ
CalicoCPU
メイン画像の変更
記事種類の変更
製作品
Lチカの変更
ライセンスの変更
(CC BY 4+) Creative Commons Attribution CC BY version 4.0 or later
本文の変更
++[自作CPU Advent Calendar 2022](https://qiita.com/advent-calendar/2022/diycpu) 2日目++ ロジックIC (74シリーズ) を用いてCPU「CalicoCPU」を製作したので、紹介する。 # 開発の目標 * 机上やFPGA上で動くだけでなく、実際のロジックIC (74シリーズ) を組み合わせたハードウェアのCPUを製作する * フィボナッチ数列や素数判定など、ある程度実用的なプログラムを動かせるようにする * コストが高くなりすぎないようにする # 名前の決定 「みけ」→「calico」をベースに、意味をこじつけた。 「Calculator IC organized CPU」(計算用ICを組み合わせたCPU) を略してCalicoCPU、ということにした。 # 扱うデータのサイズの決定 4ビットでは小さすぎる気がするが、ビット数を増やしすぎても製作が大変になる…ということで、 計算に用いるデータのサイズは8ビットとすることにした。 プログラムカウンタのサイズも、これに合わせて8ビットとした。 # 命令セットの決定 ロジックICで組むことを考慮してコストが高くなりすぎないようにしつつ、命令セットを決めた。 例えば、 * レジスタの指定に用いるビットの場所を揃える * 似た機能はビット列の違いを少なくする * あるビットが0なら代入、1なら加算とすることで、加算先にこのビットをANDしたものを加算することでどちらも実行できるようにする * 比較命令が欲しい気がしたが、比較に用いるIC (7485) の入手が難しそうなので諦める などの工夫をした。 さらに、設定できる命令の種類が限られる中、 * ビット演算は、NANDのみを採用する (他はNANDの組み合わせでできるはずなので) * シフト命令(特に右シフト)は、無くて困ることがあったので入れる * 無条件分岐 (jmp的な) は省略する (0番地以外に飛ぶなら条件分岐でできる) * 即値代入は飛び先の設定に使いやすいようゼロ拡張とする一方、即値加算は「少し減らす」がしやすいよう符号拡張とする * 柔軟な応用ができるよう、ポートは市販のマイコンのように1ビットずつ入力か出力かを設定できるようにする といった工夫をした。 最終的に、命令セットは以下に決定した。 4個の汎用レジスタを持っており、命令中ではrrおよびssの2ビットでそれぞれ指定する。 データ用のRAMにアクセスする組み込みの命令は無い。(ポートにRAMを接続し、アクセスすることはできると予想している) ポートの出力モードは、各ビットごとに0なら入力、1なら出力となる。 実行開始(リセット)時には、全データレジスタとプログラムカウンタ、およびポートの出力値レジスタと出力モードレジスタを0に初期化する。 ``` rr00ssff : 演算 r ← f(r, s) f = 00 : r ← s f = 01 : r ← r + s f = 10 : r ← r NAND s f = 11 : rについてのシフト演算 s = 00 : 2ビット左シフト s = 01 : 4ビット左シフト s = 10 : 1ビット論理右シフト s = 11 : 2ビット論理右シフト rr01ssff : その他処理 f = 00 : ポート入力 s = 00 : r ← ポート0の入力値 s = 01 : r ← ポート0の出力値 s = 10 : r ← ポート1の入力値 s = 11 : r ← ポート1の出力値 f = 01 : サブルーチン呼び出し (r ← PC+1、PC ← s) f = 10 : ポート出力 s = 00 : ポート0の出力値 ← r s = 01 : ポート0の出力モード ← r s = 10 : ポート1の出力値 ← r s = 11 : ポート1の出力モード ← r f = 11 : 条件分岐 if (r != 0) PC ← s rr10vvvv : 即値代入 r ← v (ゼロ拡張) rr11vvvv : 即値加算 r ← r + v (符号拡張) ``` # 命令セットから回路へ ## レジスタに書き込む値の決定木 今回の設計では、全ての命令が何らかの値をrrで指定するレジスタに書き込むようになっている。 (rrの値を変更しない命令は、「rrの値をrrに書き込む」とする) そこで、書き込む値のもととなるデータを左に用意し、 選択や演算を通して右に向かってrrに書き込む形の木で書き込む値の決め方を整理した。 作成した図が以下である。  なお、この図は作成時の仕様で作っているため、決定した仕様とは異なり即値代入も符号拡張となっている。 ## 部品の基板上の配置の検討 作成した決定木をもとに、次の部品が近くになるようになど考え、 それぞれの計算を行う部品を基板上のどこに配置するかを検討し、以下の配置図を作成した。  なお、この配置図はこの時点での案であり、最終的な配置とは異なる。 # 回路図 KiCadで回路図を作成した。 作成した回路図を以下に示す。  CalicoCPUは、大きくわけて以下の部分からなる。 * レジスタ (regs) * レジスタ選択 (regs_mux) * 計算ロジック (rin_logic) * 入出力ポート (ports) * 命令フェッチ (inst_fetch) * 制御 (ctl_logic) * LED (led) ## レジスタ  ロード機能付きカウンタの74HC161のカウント機能を停止に固定し、ロード機能をレジスタとして使用する。 さらに、74HC138を用い、命令の上位2ビットに基づいて計算結果をどのレジスタに書き込むかを決定する。 ## レジスタ選択  4個のレジスタの値から、命令の対応するビットに基づいて計算に使用する2個の値を選択し、それぞれ出力する。 ## 計算ロジック  * レジスタ加算・代入 (logic_rr00ss0x) * レジスタ論理演算 (logic_rr00ss1x) * 入出力・分岐 (logic_rr01ss0x) * 定数加算・代入 (logic_rr1xvvvv) の計算をそれぞれ行い、その結果を命令の対応するビットに基づいて選択 (logic_mux) する。 ### レジスタ加算・代入  加算器を用いて2個のレジスタの値の足し算を行う。 また、足される値と命令の対応するビットのAND演算を行い、足される値を0にすることで、レジスタの代入を行う。 ### レジスタ論理演算  2個のレジスタの値のNAND演算、およびレジスタの値の論理シフト演算を行い、命令の対応するビットに基づいて結果を選択する。 論理シフト演算も、命令の対応するビットに基づいて結果を選択することで行う。 ### 入出力・分岐  加算器を用い、サブルーチン呼び出し時にレジスタに格納する次の命令のアドレスを生成する。 さらに、命令の対応するビットに基づき、ポートからの入力情報と次の命令のアドレスのうちいずれかを選択する。 ### 定数加算・代入  加算器を用い、命令の定数を表すビット群をレジスタの値に加算する。 さらに、命令の対応するビットと以下のようなAND演算を行うことで、定数の加算時は符号拡張、代入時はゼロ拡張という仕様を実現する。 * 下位4ビットは加算の前にAND演算を行い、レジスタの値の影響をなくす。 * 上位4ビットは加算の後にAND演算を行い、レジスタの値に加えて符号拡張される部分の影響もなくす。 ### 選択  命令の対応するビットに基づき、これまで紹介した回路からの出力の中から最終的にレジスタに格納する値を選択する。 また、レジスタの値を変更しない命令 (ポート出力・条件分岐) の場合は、現在のレジスタの値を選択する。 ## 入出力ポート  * 出力値と方向(入力/出力)を管理する部分 (port_out_regs) * 設定に基づいて出力を行い、入力を受け取る部分 (port_interface) * 命令の対応するビットに基づいて取得する状態を選択する部分 (port_mux) からなる。 ### 出力値・方向  74HC161を用い、ポートに出力する値と出力を行うかの情報をそれぞれ記憶する。 さらに、74HC138を用い、命令の対応するビットに基づいて情報を更新するか、および更新する場合はどの情報かを決定する。 ### 入出力  74HC126 (3ステートバッファ) をポートの端子に接続し、記憶した情報に基づいて出力を行う。 さらに、ポートの入力値をレジスタに記憶する。 ここでレジスタを挟むことで、ポートの入力値が切り替わるタイミングが悪く、値の安定が遅れる場合でも、次のクロックまでには安定して影響がここのレジスタのみに抑えられることを期待している。 ポートは1MΩの抵抗でプルアップしている。 ### 選択  命令の対応するビットに基づき、ポートからどの状態を読み取るかを選択する。 ## 命令フェッチ  プログラムカウンタと、プログラムROMおよびクロック・リセット信号と電源を接続する端子がある。 プログラムカウンタは、ジャンプを行うかの情報と、ジャンプ先のアドレスを受け取る。 ## 制御  命令の各ビット、およびレジスタの値に基づき、 * ジャンプを行うか * 「その他処理」の命令か (ポートの状態を更新する条件の一部) の判定を行う。 ## LED  LEDにより、プログラムカウンタ、命令、各レジスタの値を出力する。 # 基板の設計・発注・実装 回路図に基づいて基板を設計した。 KiCadの、接続するべき場所をハイライト表示してくれる機能が大いに役立った。 最終的に、横275mm、縦168mmとなり、A4の大きさ以内に収まった。 入出力ポートの仕様は、[Digilent Pmod™ Interface Specification](https://digilent.com/reference/pmod/start) を参考にした。 レジスタの下の部分のスペースが余ったため、三毛猫の絵を配置した。 [設計データ](https://github.com/mikecat/hardware_cpu/releases/tag/20220404-boards-v1.0) 設計した基板を [Elecrow](https://www.elecrow.com/) に発注した。 三毛猫の見た目をよくするため、Surface Finish は Immersion gold とした。 部品の実装を行い、以下が完成品である。   # 使用したIC CalicoCPU では、以下のICが使用されている。 |種類|機能|個数| |---|---|--:| |74HC00|2入力NAND|3| |74HC02|2入力NOR|1| |74HC08|2入力AND|4| |74HC21|4入力AND|1| |74HC126|3ステートバッファ|4| |74HC138|3ビットデコーダ|2| |74HC153|4入力セレクタ|20| |74HC157|2入力セレクタ|6| |74HC161|4ビットカウンタ|18| |74HC273|8ビットD-FF|2| |74HC283|4ビット加算器|6| 合計で67個のICを使用している。 これは本体だけの数であり、クロックやROMに使用するICは含んでいない。 # 開発ツール CalicoCPU の命令セットは、自作のアセンブラ [MikeAssembler](https://mikecat.github.io/mike_assembler/) によるアセンブルに対応している。 また、HTML・JavaScript による[シミュレータ](https://github.com/mikecat/hardware_cpu/tree/main/cpus/calico_cpu/simulator)を用意した。 # プログラム例 これは、PORT0のうち1ビットをHIGH、ほかをLOWにし、HIGHにする位置を往復させる、 いわゆる「ナイトライダー」のプログラムである。 ``` target calico ADDI A, -1 DRIVE 0, A MOVI D, main_loop main_loop: MOVI A, 2 loop1: OUT 0, A SHL A, 1 MOVI C, loop1 JNZ A, C MOVI A, 0x40 loop2: OUT 0, A SHR A, 1 MOVI C, loop2 JNZ A, C JNZ D, D ``` 機械語のビット列を加えると、以下のようになる。 ``` 00111111 ADDI A, -1 00010110 DRIVE 0, A 11100011 MOVI D, main_loop main_loop: 00100010 MOVI A, 2 loop1: 00010010 OUT 0, A 00000001 SHL A, 1 10100100 MOVI C, loop1 00011011 JNZ A, C 00100100 MOVI A, 0x40 00000111 loop2: 00010010 OUT 0, A 00001011 SHR A, 1 10101010 MOVI C, loop2 00011011 JNZ A, C 11011111 JNZ D, D ``` 以下が、出力ポートに[Pmod 8LD](https://digilent.com/shop/pmod-8ld-eight-high-brightness-leds/)を接続し、このプログラムを実際に動作させた様子である。 @[youtube](https://www.youtube.com/watch?v=22QHPZAtwjw) # 反省点 * 比較命令、もしくは加算時のキャリーにアクセスする手段が欲しかった。 * RAMにアクセスする命令が欲しかった。レジスタ4個だけではすぐにいっぱいになってしまう。 * 直接ジャンプ命令が欲しかった。分岐のたびにレジスタが1個潰れるのは厳しい。