目次
SAP32アーキテクチャ
Simple Architecture Processor for educational
CPUの実装がまだよくわからない人向けの、シンプルな32bitプロセッサアーキテクチャです。とあるセミナーで使った実装を元に、某研究室のプロセッサ実装入門のために作りました。
コンパイラやアセンブラはありません。
SAP32基本仕様
オペランド | 3オペランド |
エンディアン | リトルエンディアン |
レジスタ数 | 16(汎用レジスタ*13, スタックポインタ*1, プログラムカウンタ*1, flagレジスタ), (IOレジスタ) |
メモリアクセス | Only Load/Store, Byte, Half Word, Word |
分岐 | Flag & Condition Code方式 |
IO | MMIO, IOレジスタ(1ワードのIOアドレス空間と考える/これはデバッグ用) |
レジスタ
レジスタマップ
addr | register name | Description |
0 | r0 | 汎用レジスタ0 |
1 | r1 | 汎用レジスタ1 |
2 | r2 | 汎用レジスタ2 |
3 | r3 | 汎用レジスタ3 |
4 | r4 | 汎用レジスタ4 |
5 | r5 | 汎用レジスタ5 |
6 | r6 | 汎用レジスタ6 |
7 | r7 | 汎用レジスタ7 |
8 | r8 | 汎用レジスタ8 |
9 | r9 | 汎用レジスタ9 |
10 | r10 | 汎用レジスタ10 |
11 | r11 | 汎用レジスタ11 |
12 | r12 | 汎用レジスタ12 |
13 | flagr | フラグレジスタ |
14 | spr | スタックレジスタ |
15 | pcr | プログラムカウンタ |
汎用レジスタ
31:0 |
---|
r? |
FLAGR
ビットフィールド | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
名称 | SP | OF | CF | — | ZF |
機能 | 演算結果が負数の場合1 | 演算結果がオーバーフローした際1 | 演算結果に桁上げが生じた場合1 | — | 演算結果が0ならば1 |
分岐
無条件分岐
b <label>, #al
条件分岐
以下のコンディションコードが適用される。
b <label>, #eq
CMP命令を使用した際のフラグは、r0-r1のフラグがCCと比較されます。
// R0 >= 1 cmp r0, 1 b <hoge>, #ge
Level | Code | Operate | 条件 | CC1 | CC2 | CC3(GCC) | フラグ条件 |
---|---|---|---|---|---|---|---|
◎ | 0x0 | Always | Always | AL | Always | ||
◎ | 0x1 | Equal | == | EQ | Z | EQ | ZF |
◎ | 0x2 | Not Equal | != | NEQ | NZ | NE | !ZF |
◎ | 0x3 | Minus | - | MI | SF | ||
◎ | 0x4 | Plus | + | PL | !SF | ||
— | 0x5 | — | — | — | — | — | — |
— | 0x6 | — | — | — | — | — | — |
△ | 0x7 | Overflow | OVF | OF | |||
○ | 0x8 | Unsigned >=(Cary Set) | >= | UEO | C | GEU | CF |
○ | 0x9 | Unsigned <(Not Cary Set) | < | UU | NC | LTU | !CF |
○ | 0xA | Unsigned > | > | UO | GTU | CF and !ZF | |
○ | 0xB | Unsigned ⇐ | ⇐ | UEU | LEU | !CF or ZF | |
△ | 0xC | Signed >= | >= | SEO | GE | (SF and OF) or (!SF and !OF) | |
△ | 0xD | Signed < | < | SU | LT | (SF and !OF) or (!SF and OF) | |
△ | 0xE | Signed > | > | SO | GT | !((SF xor OF) or ZF) | |
△ | 0xF | Signed ⇐ | ⇐ | SEU | LE | (SF xor OF) or ZF |
ISA
インストラクションフォーマット
C Format
31:24 | 23:0 |
OpCode | Reserve |
O3 Format
3 Operand Format
31:24 | 23:12 | 11:8 | 7:4 | 3:0 |
OpCode | Reserve | Rs1 | Rs0 | Rd |
O2 Format
2 Operand Format
31:24 | 23:12 | 11:8 | 7:4 | 3:0 |
OpCode | Reserve | Reserved | Rs0 | Rd |
O1 Format
1 Operand Format
31:24 | 23:12 | 11:8 | 7:4 | 3:0 |
OpCode | Reserve | Reserved | Reserved | Rd |
I16 Format
Immediate 16 Format
31:24 | 23:8 | 7:4 | 3:0 |
OpCode | Imm16 | Rs0 | Rd |
JO2 Format
2 Operand for Jump Format
31:24 | 23:20 | 19:8 | 7:4 | 3:0 |
OpCode | CC | Reserved | Rs0 | Rd |
JI16 Format
Immediate 16 for Jump for Jump
31:24 | 23:20 | 19:16 | 15:0 |
OpCode | CC | Reserved | Imm16 |
ニーモニック
Level | number | opcode | format | operation | immediate |
◎ | 0 | nop | c | no operation | |
◎ | 1 | wl16 | i16 | rd = {rd[31:16], imm16} | unsigned |
◎ | 2 | wh16 | i16 | rd = {imm16, rd[15:0]} | unsigned |
○ | 3 | lil | i16 | rd = SignExtend(imm16) | signed |
○ | 4 | lih | i16 | rd = imm16 « 16 | signed |
○ | 5 | ulil | i16 | rd = imm16 | unsigned |
◎ | 6 | clr | o1 | rd = 0 | |
—- | 7 | —- | —- | —- | —- |
◎ | 8 | move | o2 | rd = rs0 | |
◎ | 9 | not | o2 | rd = not(rs0) | |
—- | 10 | —- | —- | —- | —- |
—- | 11 | —- | —- | —- | —- |
◎ | 12 | and | o3 | rd = rs0 and rs1 | |
◎ | 13 | or | o3 | rd = rs0 or rs1 | |
◎ | 14 | xor | o3 | rd = rs0 xor rs1 | |
—- | 15 | —- | —- | —- | |
○ | 16 | test | o3 | rd = flag(rs0 & rs1) | |
—- | 17 | —- | —- | —- | —- |
—- | 18 | —- | —- | —- | —- |
—- | 19 | —- | —- | —- | —- |
◎ | 20 | add | o3 | rd = rs0 + rs1 | |
○ | 21 | addi | i16 | rd = rs0 + imm16 | signed |
○ | 22 | uaddi | i16 | rd = rs0 + imm16 | unsigned |
◎ | 23 | sub | o3 | rd = rs0 - rs1 | |
○ | 24 | subi | i16 | rd = rs0 - imm16 | signed |
○ | 25 | usubi | i16 | rd = rs0 - imm16 | unsigned |
○ | 26 | mull | o3 | rd = 0xffffffff & (rs0 * rs1) | |
○ | 27 | mulh | o3 | rd = (rs0 * rs1) » 32 | |
—- | 28 | —- | —- | —- | —- |
—- | 29 | —- | —- | —- | —- |
—- | 30 | —- | —- | —- | —- |
◎ | 31 | shl | i16 | rd = rs0 « (imm16 & 0x1f) | —- |
◎ | 32 | shr | i16 | rd = rs0 » (imm16 & 0x1f) | —- |
◎ | 33 | sar | i16 | rd = rs0 »> (imm16 & 0x1f) | —- |
—- | 34 | —- | —- | —- | —- |
◎ | 35 | cmp | o3 | flag = only_flag(rs0 - rs1) | |
—- | 36 | —- | —- | —- | —- |
—- | 37 | —- | —- | —- | —- |
—- | 38 | —- | —- | —- | —- |
—- | 39 | —- | —- | —- | —- |
◎ | 40 | b | jo2 | pcr = rs0 | |
○ | 41 | bi | ji16 | pcr = imm16 | signed |
○ | 42 | br | jo2 | pcr = pcr + rs0 | |
◎ | 43 | bri | ji16 | pcr = pcr + imm16 | signed |
—- | 44 | —- | —- | —- | —- |
—- | 45 | —- | —- | —- | —- |
—- | 46 | —- | —- | —- | —- |
—- | 47 | —- | —- | —- | —- |
◎ | 48 | din | o2 | rd = ior | |
◎ | 49 | dout | o2 | ior = rs0 | |
—- | 50 | —- | —- | —- | —- |
—- | 51 | —- | —- | —- | —- |
—- | 52 | —- | —- | —- | —- |
—- | 53 | —- | —- | —- | —- |
—- | 54 | —- | —- | —- | —- |
—- | 55 | —- | —- | —- | —- |
○ | 56 | ld8 | o2 | rd = 0xff & mem[rs0] | |
○ | 57 | ld16 | o2 | rd = 0xffff & mem[rs0] | |
◎ | 58 | ld32 | o2 | rd = 0xffffffff & mem[rs0] | |
○ | 59 | st8 | o2 | mem[rd] = {mem[rd], mem[rs0]} | |
○ | 60 | st16 | o2 | mem[rd] = {mem[rd], mem[rs0]} | |
◎ | 61 | st32 | o2 | mem[rd] = mem[rs0] | |
○ | 62 | pop | o1 | rd = mem[spr] / spr = spr + 4 | |
○ | 63 | push | o1 | mem[spr] = rs0 / spr = spr - 4 | |
— | 64~127 | — | — | — | — |
アセンブリフォーマット
分岐
opcode label, cc
b <label>, #al b 1024, #al
1-Operand
opcode r?
push r0
2-Operand
opcode r?, r?
//r0 <= r1 move r0, r1
3-Operand
opcode r?, r?, r?
//r0 <= r1 + r2 add r0, r1, r2
I16
opcode r?, imm16
or
opcode r?, r?, imm16
//r0 <= {r0[31:16], 1024} wl16 r0, 1024 //r0 <= r1 << 2 shr r0, r1, 2
実装
実装例のブロック図
演習手順
まずはパイプライン化なしで◎の命令及びコンディションコードを実装します。その後演習2で○、演習3で△を実装します。最終演習でパイプライン化をします。
各章ごとに正しく動作することを確認する為に、シミュレーションを行う必要があります。
演習1
最低限の実装です。まずはこれをパイプライン無しで実装してください。今後の演習で命令も追加されます。そして、パイプライン化も行います。その見通しを持ってHDLのモジュール分を行って実装してください。
実装したハードウェアが正しく実行できるかどうかをシミュレータで検証してください。
次に、実際に論理合成してFPGAで動作確認をします。検証方法としては、プロセッサのIO(IOレジスタ)にLEDやスイッチを接続して、プログラムでそれを用いて簡単なアプリケーションを作ってみてください。
実装のアドバイス
大まかなモジュール分割としては以下のようになります。
- Fetch
- 命令の読み出し。命令メモリから命令を読み出します。
- Decode
- 命令をから、内部で使用する信号に変換します。
- Allocate
- 使用するレジスタを割り当てます。
- Execute
- 命令を実行します(Load/Storeの場合はアドレス計算)。
- Memory Access
- Load/Storeの実行をします。
- Writeback
- レジスタに値を書き戻します。
このモジュール分割は、パイプライン化を行うときにも意味を持つので重要です。
今はパイプライン化を行わないので、これらを1サイクルで実行するように構成してください。
検証のアドバイス(シミュレーション)
一般的に、プロセッサの検証は、命令レベル・ファンクションレベル・システムレベル検証の3つのレベルで行います。
- 命令レベル検証
- 各命令が個別に正しく実行されることを検証します。これは命令ごとにテストベクタ(検証のためのプログラム)を作り、期待値と実際にRTLで演算された値を比較します(通常Assertionも用いて組み合わせて検証を行います)。
- 検証するテストプログラムが最低限動く必要があります。よって一般的にload, store, cmp, add, branch, imm_set命令が正しく動かない限りこの懸賞は成り立ちません。
- これらの命令は何かしらの方法で、予め正しく動くことを保証しておく必要がある。
- 通常この検証は自動化します。
- より深く検証するには、全命令でカバレッジを取り、最終的にマージして、カバレッジ値が100%に近づくように努力します。
- 不要なハードウェアを見つけることができる。不要なハードウェアはバグの元
- 但しISIMとかVIVADO Simulatorとかではこれはできません。
- ファンクションレベル検証
- 命令の組み合わせや、割り込み、命令の実行権限などが正しいかどうか検証します。これも期待値と実際の値とで比較することで実現します。
- SAP32では割り込み・実行権限は未定義なのでテスト不要
- 命令の組み合わせはforwardingなどを実装した時に検証が必要。
- システムレベル検証
- 具体的なワークロードやOSが動作するかどうか検証。
- SAP32では、何かしらの意味のある簡単なプログラムを書いて検証してみるべき。
これらの検証に関してはMIST32アーキテクチャ向けの検証ツール(テストベクタ生成)及び、検証環境を参考にしてください。
検証のアドバイス(実機)
命令メモリはとりあえずHDL内でROM記述で書きます。そうすることで簡単なアセンブラもどきもできます。
データメモリはFPGAのRAMを用います。この時、まだパイプライン化及びインターロックを実現していないので、レイテンシ0でデータが読めるようにします。
演習2
○の命令を実装して、演習1と同様の検証を行ってください。
演習3
△の命令を実装して、演習1と同様の検証を行ってください。
演習4
パイプライン化をします。演習1の実装のアドバイスで述べたモジュールごとにパイプラインレジスタの追加を行います。
パイプライン化するにあたり、インターロックを追加する必要があります。例えば、メモリアクセス等、通常の演算より実行に時間がかかったりする命令があった場合、それに続く命令が追い越して実行されたりしないようにするためです。
これらを実装し、命令が正しく実行されることを検証してください。
実装のアドバイス
インターロックの実装としては、ある待ちが発生したステージより下のステージをロックします。もしインターロックの要因が解除されたら下のモジュールのロックも解除します。
検証のアドバイス
命令レベル及びファンクションレベルの検証が重要です。特に、ファンクションレベル検証でLoad/Storeに関する検証を重点的に行なってください。
発展課題(演習5)
演習4までの実装では多くのストールが発生してまともに使えません。最も、このプロセッサアーキテクチャはあくまで教育用なわけですが。最近の比較的高性能なプロセッサは以下の仕組みを実装しているので、勉強のために実現してみるのも良いと思います。
5-1フォワーディングを実現してください。
5-2キャッシュを追加してください。
5-3アウトオブオーダ実行(load/store以外)を実現してください。
5-4アウトオブオーダ実行(load/storeを含む)を実現してください。