Kanade Electronicskanaelecom
🏗

マイコン回路

標準ロジック IC で組む自作マイコン RK16 の回路実装

RK16 は 16bit データ幅/16bit アドレス幅、命令長 32bit のハーバードアーキテクチャ CPU です。命令メモリ (imem) と データメモリ (dmem) は完全に分離していて、レジスタファイルも dmem の先頭にメモリマップしてあります。

 この記事では、機械語仕様 (命令セット) ・呼出規約 (ABI) ・割り込み仕様 (割り込み処理) ・メモリ空間 (メモリマップ) を満たす回路実装に集中します。各仕様の意味論はそれぞれのリンク先を参照してください。

全体構成

Debugger
Debugger
CLK
CLK
PC
PC
ROM
ROM
RAM
RAM
ID
ID
ALU
ALU
IMM
IMM
S2_SEL
S2_SEL
SR2
SR2
SR1
SR1
IO
IO
SR2
SR2
IMM
IMM
SR2

_CLK
SR2...
SR1

_CLK
SR1...
ALU_

CTRL[4]
ALU_...
PFC_CTRL
PFC_CTRL
PC_CLK
PC_CLK
ALU
ALU
SA1
SA1
SA1
SA1
ADR_SEL[6]
ADR_SEL[6]
MEM_

CTRL
MEM_...
DIN_SEL[3]
DIN_SEL[3]
SR2
SR2
ALU
ALU
RA
RA
SA2
SA2
SA2
SA2
DA
DA
DA
DA
S1
S1
S2
S2
IRA
IRA
VRAM
VRAM
STG_0
STG_0
STG_1
STG_1
STG_2
STG_2
=0?
=0?
+1
+1
INTR_PC
INTR_PC
PC+1
PC+1
ALU
ALU
DUMP

_ADDR
DUMP...
DUMP

_DATA
DUMP...
ROM_

DATA
ROM_...
ROM_

CTRL
ROM_...
Arduino
Arduino
PC
PC
USB
USB
DBG
DBG
ZERO
ZERO
PC
PC
IRA
IRA
SP
SP
SP_CTRL
SP_CTRL
INSTR
INSTR
REG
REG
INTR_IN
INTR_IN
ROM
ROM
0x0000
0x0000
0x0001
0x0001
0x0002
0x0002
0x0003
0x0003
0x0004
0x0004
0x000F
0x000F
0x0010
0x0010
0x0FFF
0x0FFF
0x1000
0x1000
0x2FFF
0x2FFF
0x3000
0x3000
0x4FFF
0x4FFF
0x5000
0x5000
0xFFFF
0xFFFF
|
|
|
|
|
|
|
|
|
|
Text is not SVG - cannot display

 メインの回路ブロックは次の通りです。

  • 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
calc
REG
REG
func
func
calci
calci
REG
REG
load
load
calif
calif
PC
PC
SR1
SR1
SR2
SR2
func
func
SR1
SR1
IMM
IMM
add
add
SR2
SR2
IMM
IMM
SR1
SR1
=0?
=0?
REG
REG
store
store
REG
REG
RAM
RAM
add
add
IMM
IMM
SR1
SR1
SR2
SR2
REG
REG
RAM
RAM
IMM
IMM
SR1
SR1
SR2
SR2
add
add
+1
+1
Text is not SVG - cannot display

ステージCALCCALCILOADSTORECTRL
1.Load[rs1] => SR1[rs1] => SR1
ALU(SR1,imm,func)
[rs1] => SR1
ALU(SR1,imm,ADD)
[rs1] => SR1
ALU(SR1,imm,ADD)
[rs1] => SR1
ALU(SR1,imm,ADD)
2.Load[rs2] => SR2
ALU(SR1,SR2,func)
[ALU] => SR2[rs2] => SR2[rs2] => SR2
IS_ZERO(SR2)
3.StoreALU => [rd]ALU => [rd]SR2 => [rd]SR2 => mem[ALU]PC+1 => [rd]
4.DumpPC+1 => PCPC+1 => PCPC+1 => PCPC+1 => PCIS_ZERO ? ALU : PC+1 => PC

 各ステージは 1 クロックずつで、1 命令あたり 4 クロックかかります。パイプラインは組まずに、命令ごとに完全に逐次実行する設計です。理由は単純で、ロジック IC のチップ数を抑えるためにフォワーディング回路やハザード検出を入れないと決めたからです。

Load
Load
calc
calc
Load
Load
Load
Load
Load
Load
Load
Load
ALU
ALU
calci
calci
load
load
store
store
calif
calif
Load
Load
Load
Load
Load
Load
Load
Load
ALU
ALU
ALU
ALU
Store
Store
Store
Store
Store
Store
Store
Store
IS_ZERO
IS_ZERO
PC++
PC++
PC=ALU
PC=ALU
Dump
Dump
Store
Store
PC++
PC++
PC++
PC++
PC++
PC++
Dump
Dump
Dump
Dump
Dump
Dump
Dump
Dump
ALU
ALU
ALU
ALU
Text is not SVG - cannot display

 ステージカウンタはモジュロ 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 が始まる前) でしか割り込みを受理しません。命令の途中でラッチを書き換えると、その命令が壊れた状態で復帰することになるためです。

 受理時の制御は次のステージで完結します。

  1. ステージカウンタを 0 に強制
  2. PC を ira レジスタに転送
  3. csr.bit0 (ENABLE) を 0 に
  4. csr.bit (8+i) (FIREi) を 1 に
  5. PC を 0x0004 に強制

 これらは全て次のステージ 1 で行われる動作で、ソフトウェアからは「次に実行されるのはベクタ命令」というふうに見えます。タイミング図と詳しい仕様は 割り込み処理 を参照。

デコーダの設計

 opcode が「下位ビットから 1 が連続する」パターンになっているのは、デコーダを軽くするためです。opc[3:0] から各制御信号は次のように直接生成できます。

信号論理立つ opcode
use_immopc[0]CALCI, LOAD, STORE, CTRL
mem_readopc[1] & ~opc[2]LOAD
mem_writeopc[2] & ~opc[3]STORE
branchopc[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)

関連項目