公開日:2021/02/20
title.png

15th Season


#15-3 マイコンを作るよ

 本HP(シーズン15)では、FPGAボード(Sipeed Lichee Tang Primer)を使って最終的にはマイコンを完成させることを目指しています。

 時に、「マイコン」って、どんなものでしょうか?筆者の考えるマイコンとは、以下の様な物を指します。

 具体例としては、本HPではもうご存知のATmega328(Arduino UNOに搭載)、SAM3X8E(Arduino DUEに搭載)等がマイコン(MCU)です。

開幕はいきなりCPU

 色々な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」等と短縮して使おうと思います。

プログラマモデル

 続いて、プログラマモデルを決めておきます。

a153_prgmdl.png

※後日拡張したレジスタや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のブロック図を以下に示します。

a153_cpublk.png


 論理設計らしい設計に到達できませんでしたが、今回はここまでです。次回は、最後に示したブロック図を使って、命令実行の様子をシミュレーションしてみます。


btn_arduino.png

btn_prev.png btn_next.png

home