本HP(シーズン15)では、FPGAボード(Sipeed Lichee Tang Primer)を使って最終的にはマイコンを完成させることを目指しています。
時に、「マイコン」って、どんなものでしょうか?筆者の考えるマイコンとは、以下の様な物を指します。
具体例としては、本HPではもうご存知のATmega328(Arduino UNOに搭載)、SAM3X8E(Arduino DUEに搭載)等がマイコン(MCU)です。
色々なI/Oユニットを作り揃えないとマイコンとは呼べないので、先は長いのですが、開幕はいきなりCPUから始めます。周辺のI/Oユニットも重要ですが、まずはCPUが無いと何も始まりませんから。
購入したFPGAボードにはRISC-Vと言うソフトCPUコアが移植されているので、本格的にシステムを組んでみたい方は、RISC-Vを選択するのが良いでしょう。
※FPGAボードに移植されていたRISC-V(E203)コアについては、「#15-5 RISC-Vを試す」にてコンパイル(論理合成・配置配線)を実施したレポートがあります。
筆者にとっては、CPUそのものを設計する楽しさの方が優先するので、RISC-Vは選択せず、自前でCPUコアを起こしたいと思います。
設計方針を以下の様に決めておきます。
さて、具体的に進めるには、どこから決めて行ったら良いのかな?まずは、名前から(って、そこ?)。
名前(コード名なり、型名なり)が決まらないと、このCPUを呼称することもできないし、ファイル名もモジュール名も付けられないので、設計が進みません。これ、意外と重要(1^o^;)/。
コード名をMoscoviumとして、ファイル名やモジュール名としては長いため、「mcvm」等と短縮して使おうと思います。
続いて、プログラマモデルを決めておきます。
※後日拡張したレジスタやSrレジスタの制御ビットなどが更新された最新のプログラマモデルとなっています。
汎用レジスタ8本(r0〜r7)と、状態レジスタsr、プログラムカウンタpc、スタックポインタspの構成です。すべてのレジスタはリセット解除後、0x0000に初期化されています。汎用レジスタ8本には4組の汎用レジスタバンクが有って、Srレジスタの{BK1,BK0}フィールドにて、切り替えて使用することが可能です。
16ビットの汎用レジスタが8本で16バイト、更に4バンクで64バイトの記憶域が必要ですので、論理規模を増加させてしまうのですが、プログラムの組み易さの方を取りました。
方針でも述べた通り、Cコンパイラを作るまでの実力は無いので、アセンブラレベルでプログラムを組むことになります。この時、例えば、何らかのサブルーチンをコールしたら、その中で使用するレジスタはスタックなど、外部メモリに退避してから使わないと、戻った時にレジスタ値が破壊されてしまって処理を継続することができません。
とても普通の処理(↑)なのですが、コンパイラに任せるならともかく、アセンブラでプログラムを組もうとすると、サブルーチンから戻る時にはスタックのデータを回復してから戻らなくてはならないし、意外と面倒なのです。
バンクがあると、サブルーチンの先頭でSrレジスタを書き換えてどこかの空きバンクを選択すると、そのレジスタは自由に使用することが出来ます。戻る時も、元のバンクに書き戻したら終了です。
この様に、バンクの選択はコンパイラに任せるわけでは無く、人間がリソース管理することが前提なので、あまりバンク数を増やしても管理しきれないかと、4バンクに留めてあります。設計上、バンク数を増やすことは容易なので、実際に使ってみて調整します。逆に、論理規模を圧縮したい場合には、バンク数を最小1個まで減らすことが出来ます。
SrはCPUのステータス(状態)レジスタです。演算結果を示すフラグビット(N,C,V,Z)等、CPUの状態を示すビットを備えています。
状態ビット:
Pcはプログラムカウンタです。Pcの指し示すアドレスからプログラムコードをフェッチして実行します。フェッチすると、自動的にインクリメント(+2加算)されます。
Pcの指し示すアドレスは、フェッチすべき命令のバイトアドレスです。命令コードは16ビット固定長で、16ビットワード境界に乗っているので、Pcは常に偶数となります。Pcが16ビット長なので、プログラムメモリ空間は最大64Kバイトです。
Pcはリセット解除後、0x0000に初期化されているので、Moscoviumは0x0000番地から命令を実行開始します。
Spはスタックポインタです。Moscoviumのスタックは、「フル下降型」で固定です。スタックの構成方法については以下の通りとなります。
プログラマモデルが決まったら、次は命令セットを決めます。CPUを設計する時は、この辺りが一番楽しいですね〜😆。
「コンパクトに、必要最小限の命令セットにしよう」とか思って始めたのだけど、作って見たら「あんな命令も欲しい、こんな命令も必要」と、つい調子に乗ってしまいました。色々と命令を追加したい衝動を抑えて、何とか出来上がったのが以下表となります。
No. | Inst | Opr1 | Opr2 | Type | Memo |
O15 (1b) |
RiBr (4b) |
RR (5b) |
R (3b) |
NO (11b) |
0 | nop | − | − | NO | no operation | 0 | 0 | − | − | 0 |
1 | rtn | − | − | NO | return from jal | 0 | 0 | − | − | 2 |
2 | bra | dsp11 | (always) | BR | branch always | 0 | 1 | − | − | − |
3 | bne | dsp11 | (Z=0) | BR | branch if not equal | 0 | 2 | − | − | − |
4 | beq | dsp11 | (Z=1) | BR | branch if equal | 0 | 3 | − | − | − |
5 | bcc | dsp11 | (C=0) | BR | branch if carry clear | 0 | 4 | − | − | − |
6 | blo | dsp11 | (C=0) | BR | branch if lower (unsigned) | 0 | 4 | − | − | − |
7 | bcs | dsp11 | (C=1) | BR | branch if carry set | 0 | 5 | − | − | − |
8 | bhs | dsp11 | (C=1) | BR | branch if higher or same (unsigned) | 0 | 5 | − | − | − |
9 | bpl | dsp11 | (N=0) | BR | branch if plus or zero | 0 | 6 | − | − | − |
10 | bmi | dsp11 | (N=1) | BR | branch if minus | 0 | 7 | − | − | − |
11 | bvc | dsp11 | (V=0) | BR | branch if overflow clear | 0 | 8 | − | − | − |
12 | bvs | dsp11 | (V=1) | BR | branch if overflow set | 0 | 9 | − | − | − |
13 | bge | dsp11 | (N^V=0) | BR | branch if greater or equal (signed) | 0 | 10 | − | − | − |
14 | blt | dsp11 | (N^V=1) | BR | branch if less than (signed) | 0 | 11 | − | − | − |
15 | jmp | Rn | − | R | jump | 0 | 15 | 30 | 0 | − |
16 | jal | Rn | − | R | jump and link | 0 | 15 | 30 | 1 | − |
17 | neg | Rd | − | R | negate sign | 0 | 15 | 31 | 0 | − |
18 | not | Rd | − | R | bitwise not | 0 | 15 | 31 | 1 | − |
19 | cendw | Rd | − | R | convert endian (swap byte order) | 0 | 15 | 31 | 2 | − |
20 | exsgw | Rd | − | R | extend sign bit | 0 | 15 | 31 | 3 | − |
21 | pushw | Rs | − | R | push register on stack | 0 | 15 | 31 | 4 | − |
22 | popw | Rd | − | R | pop register from stack | 0 | 15 | 31 | 5 | − |
23 | pushc | Rc | − | R | push control register on stack | 0 | 15 | 31 | 6 | − |
24 | popc | Rc | − | R | pop control register from stack | 0 | 15 | 31 | 7 | − |
25 | or | Rd | Rs | RR | bitwise or | 0 | 15 | 0 | − | − |
26 | and | Rd | Rs | RR | bitwise and | 0 | 15 | 1 | − | − |
27 | eor | Rd | Rs | RR | bitwise exclusive or | 0 | 15 | 2 | − | − |
28 | mov | Rd | Rs | RR | move | 0 | 15 | 3 | − | − |
29 | movtc | Rc | Rs | RR | move to control register | 0 | 15 | 4 | − | − |
30 | movfc | Rd | Rc | RR | move from control register | 0 | 15 | 5 | − | − |
31 | add | Rd | Rs | RR | add | 0 | 15 | 6 | − | − |
32 | adc | Rd | Rs | RR | add with carry | 0 | 15 | 7 | − | − |
33 | sub | Rd | Rs | RR | subtract | 0 | 15 | 8 | − | − |
34 | sbb | Rd | Rs | RR | subtract with borrow | 0 | 15 | 9 | − | − |
35 | cmp | Rd | Rs | RR | compare | 0 | 15 | 10 | − | − |
36 | ldb | Rd | Rs | RR | load data byte | 0 | 15 | 12 | − | − |
37 | stb | Rd | Rs | RR | store data byte | 0 | 15 | 13 | − | − |
38 | ldw | Rd | Rs | RR | load data word | 0 | 15 | 14 | − | − |
39 | stw | Rd | Rs | RR | store data word | 0 | 15 | 15 | − | − |
40 | lsft | Rd | Rn | RR | logical shift | 0 | 15 | 16 | − | − |
41 | asft | Rd | Rn | RR | arithmetical shift | 0 | 15 | 17 | − | − |
42 | ori | Rd | imm8 | RI | bitwise or immediate | 1 | 0 | − | − | − |
43 | andi | Rd | imm8 | RI | bitwise and immediate | 1 | 1 | − | − | − |
44 | eori | Rd | imm8 | RI | bitwise exclusive or immediate | 1 | 2 | − | − | − |
45 | addi | Rd | imm8 | RI | add immediate | 1 | 3 | − | − | − |
46 | subi | Rd | imm8 | RI | subtract immediate | 1 | 4 | − | − | − |
47 | cmpi | Rd | imm8 | RI | compare immediate | 1 | 5 | − | − | − |
48 | ldiu | Rd | imm8 | RI | load data unsigned immediate | 1 | 6 | − | − | − |
49 | ldbil | Rd | imm8 | RI | load data low byte immediate | 1 | 7 | − | − | − |
50 | ldbih | Rd | imm8 | RI | load data high byte immediate | 1 | 8 | − | − | − |
51 | ldcl | Rc | imm8 | RI | load data low byte immediate control register | 1 | 9 | − | − | − |
52 | ldch | Rc | imm8 | RI | load data high byte immediate control register | 1 | 10 | − | − | − |
53 | lsfti | Rd | imm8 | RI | logical shift immediate | 1 | 11 | − | − | − |
54 | asfti | Rd | imm8 | RI | arithmetical shift immediate | 1 | 12 | − | − | − |
※最新の命令セットについてはこちらのマニュアルでご確認ください。
「Memo」列より右の列については、今は読み飛ばしてください。次節の解説を読んでから戻ってくると意味が通じます。
本節ではまず、「Type」列に注目してください。命令のオペランド(関数への引数みたいなもの)の種類に従ってカテゴリ分けしてあります。「Type」列の記号の意味について以下の通り説明しておきます。
この表だけでも結構なボリューム(情報量)なのですが、実際にCPUを作ろうとすると、こんなものでは全く不足です。最終的にはもっと詳細な命令セットを定義する必要があります。それが以下リンクです。
Moscovium Instruction Set Manual
かなり絞った命令数でも、この(↑)分量となりました。まあ、一度ご覧くださいませ。
前節の命令セット一覧表を作成して見て判ったことは、各命令群に割り当てる必要のある命令数です(下表)。
命令群 | 命令数 | 表現に必要なビット数 |
NO命令群 | 2個 | 最低1ビット |
BR命令群 | 13個 | 最低4ビット |
R命令群 | 10個 | 最低4ビット |
RR命令群 | 16個 | 最低5ビット |
RI命令群 | 13個 | 最低4ビット |
全命令にユニークな命令コードを割り当てる必要があるので、上表の命令数を表現可能な様に考える必要があります。
今回のMoscoviumでは、命令数をなるべく抑えてあるので、何とか16ビット固定長の命令コードに押し込みたいと考えました。また、16ビット固定長であれば、「命令コードを常に16ビットワード境界に置く」という制限を設けて問題ありません。
検討の結果、16ビットの命令コードを以下の様なフィールドに割り当てることで、16ビット固定長で表現することが出来ました。
ビット | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
RI命令群 | 1 | OPC_RI | Rd | imm8 | ||||||||||||
BR命令群 | 0 | OPC_BR | dsp11 | |||||||||||||
NO命令群 | 0 | 0 | 0 | 0 | 0 | OPC_NO | ||||||||||
RR命令群 | 0 | 1 | 1 | 1 | 1 | OPC_RR | Rd | Rs | ||||||||
R命令群 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0/1 | OPC_R | Rn |
汎用レジスタを指定するRd,Rs,Rn等のフィールドには、3ビット(レジスタ番号0〜7を表現するため)のフィールドを割り当てる必要があります。即値imm8とディスプレースメント値dsp11も、命令コード内に含む必要があるので、これが結構な分量になります。
OPC_BRに割り当てた4ビット(0x0〜0xfまで)の中で、0x0と0xfの2コードはBR命令群以外の命令に割り当てているので、BR命令群に使える命令コード数は最大14個です。
同様に、OPC_RRに割り当てた5ビット(0x00〜0x1fまで)の中で、0x1eと0x1fの2コードはR命令群に割り当てているので、RR命令群に使えるコード数は最大30個となります。
命令を特定するための命令コード第15ビット、OPC_RI,OPC_BR,OPC_NO,OPC_RR,OPC_R各フィールドの割り当て数値について、前節の表に記載してあります。
上の方針に基づいて作ったCPUのブロック図を以下に示します。
論理設計らしい設計に到達できませんでしたが、今回はここまでです。次回は、最後に示したブロック図を使って、命令実行の様子をシミュレーションしてみます。