RK16 は 16bit データ幅/16bit アドレス幅、命令長 32bit のハーバードアーキテクチャ CPU です。命令メモリ (imem) と データメモリ (dmem) は完全に分離していて、レジスタファイルも dmem の先頭にメモリマップしてあります。
この記事では、機械語仕様 (命令セット) ・呼出規約 (ABI) ・割り込み仕様 (割り込み処理) ・メモリ空間 (メモリマップ) を満たす回路実装に集中します。各仕様の意味論はそれぞれのリンク先を参照してください。
全体構成
メインの回路ブロックは次の通りです。
- imem — 命令メモリ。32bit 幅、PC でアドレッシング
- dmem — データメモリ。16bit 幅、64K word
- レジスタファイル — dmem 先頭 16 word に重ねるように配置 (アドレス 0x0000-0x000F は D-FF を選択)
- ALU — 16bit 演算器、14 種の演算
- PC / IRA / SP / RA / CSR / A0-A1 / T0-T3 / S0-S3 — 個別の D-FF (本数を抑えるため一部はレジスタファイルに含む)
- デコーダ — opcode を MUX 制御信号に変換
- 割り込み入力 —
intr0-intr3の 4 本、CSR に発火フラグを立てる
命令の実行 (4 ステージ)
RK16 の命令は「データメモリから 2 回読み出して 1 回書き込む」という形式です。これを 4 ステージで実行します。imem は別バスなので、命令フェッチは dmem アクセスと干渉しません。
| ステージ | CALC | CALCI | LOAD | STORE | CTRL |
|---|---|---|---|---|---|
| 1.Load | [rs1] => SR1 | [rs1] => SR1ALU(SR1,imm,func) | [rs1] => SR1ALU(SR1,imm,ADD) | [rs1] => SR1ALU(SR1,imm,ADD) | [rs1] => SR1ALU(SR1,imm,ADD) |
| 2.Load | [rs2] => SR2ALU(SR1,SR2,func) | [ALU] => SR2 | [rs2] => SR2 | [rs2] => SR2IS_ZERO(SR2) | |
| 3.Store | ALU => [rd] | ALU => [rd] | SR2 => [rd] | SR2 => mem[ALU] | PC+1 => [rd] |
| 4.Dump | PC+1 => PC | PC+1 => PC | PC+1 => PC | PC+1 => PC | IS_ZERO ? ALU : PC+1 => PC |
各ステージは 1 クロックずつで、1 命令あたり 4 クロックかかります。パイプラインは組まずに、命令ごとに完全に逐次実行する設計です。理由は単純で、ロジック IC のチップ数を抑えるためにフォワーディング回路やハザード検出を入れないと決めたからです。
ステージカウンタはモジュロ 4 のカウンタで、リセット時に 0、各クロックで +1、4 のとき 0 に戻ります。これがそのまま Load1 / Load2 / Store / Dump のセレクタになります。
各レジスタへのアクセスタイミング
- PC は Stage 4 でのみ書き換わる。Stage 1 の命令フェッチは「現在の PC が指す imem 番地」を読む
- SR1 / SR2 は内部の作業用 D-FF。命令の途中でしか使われない
- レジスタファイル / dmem は Stage 1, 2 で読み出し、Stage 3 で書き込み。同じステージで read/write が衝突しないように MUX を配線
- CSR は普通のレジスタなので
andi(csr, csr, ...)/ori(csr, csr, ...)の 1 命令で書ける
割り込み受理のタイミング
ハードウェアは命令の境界 (Stage 4 の Dump 直後、Stage 1 が始まる前) でしか割り込みを受理しません。命令の途中でラッチを書き換えると、その命令が壊れた状態で復帰することになるためです。
受理時の制御は次のステージで完結します。
- ステージカウンタを 0 に強制
- PC を
iraレジスタに転送 csr.bit0(ENABLE) を 0 にcsr.bit (8+i)(FIREi) を 1 に- PC を
0x0004に強制
これらは全て次のステージ 1 で行われる動作で、ソフトウェアからは「次に実行されるのはベクタ命令」というふうに見えます。タイミング図と詳しい仕様は 割り込み処理 を参照。
デコーダの設計
opcode が「下位ビットから 1 が連続する」パターンになっているのは、デコーダを軽くするためです。opc[3:0] から各制御信号は次のように直接生成できます。
| 信号 | 論理 | 立つ opcode |
|---|---|---|
use_imm | opc[0] | CALCI, LOAD, STORE, CTRL |
mem_read | opc[1] & ~opc[2] | LOAD |
mem_write | opc[2] & ~opc[3] | STORE |
branch | opc[3] | CTRL |
alu_func_from_rs2 | ~opc[3] & opc[0] & ~opc[1] | CALCI のみ rs2 を ALU op に |
実際の回路ではこれらをさらに最適化して、74 シリーズの 1 〜 2 チップで全制御を作っています。
レジスタファイルと dmem の統合
レジスタファイルを dmem の先頭 16 word に重ねる設計は、機械語フォーマット側からも回路側からも嬉しい性質があります。
- 機械語:
load(rd, rs, imm)でレジスタ → レジスタ転送も書ける (load(a0, z, 0x0006)でa0 = a0のような) - 回路: アドレス比較
addr[15:4] == 0の信号一本で「レジスタ選択 vs dmem 選択」が決まる
逆に dmem は SRAM、レジスタは D-FF と素子が違うので、書き込み信号は次のように分岐します。
addr [15:0] ─┬→ [15:4] == 0 ─→ reg_select
└→ [3:0] ─→ reg_decoder ─→ reg_we[0..15]
(reg_select で gating)
関連項目
- 機械語仕様 (opcode / ALU op / 疑似命令): 命令セットアーキテクチャ
- 呼出規約とスタックレイアウト: 関数呼び出し規約
- 割り込み・例外・CSR の詳細: 割り込み処理
- メモリ空間全体: メモリマップ
- 物理基板の話: ハードウェア