LoginSignup
3
2

More than 1 year has passed since last update.

8ビット独自命令セット自作CPUを作ってみた

Last updated at Posted at 2021-12-04

事始め

去年のアドベントカレンダーでTD4を実装した記事を書きましたが、TD4は実用性がなく、もっと実際に使えるようなCPUを作りたくなりました。
ということで始めた二番目のCPU自作です。今回のVerilogで書くことにしました。
8ビットのマイコンとして使えるCPUを目指しました。

命名

初めての独自命令セットのCPUということで「歩む」、また虹ヶ咲スクールアイドル同好会のアニメのキャラクターの登場順序によって一番最初の「上原歩夢」から「歩夢」。
名前は「AYUMU」になりました。

スペック

16ビットの固定命令長の命令セットです。プログラムのメモリアドレスは16ビット(2バイト)を単位としてつけられます。メモリアドレスは最大16ビットで、最大65536個の命令を持つプログラムを作成することができます。

8ビットの加減乗算及び論理演算(AND, OR, XOR, NOT)をサポートし、条件分岐もサポートしています。また、割り込み処理のための専用命令も用意しました。

プログラムはROMに直接書き込みます。今回はVerilogで直接機械語の命令を書き込みました。
データメモリは8ビット単位で8ビットアドレスであり、合計256バイトのデータメモリを持っています。

レジスタ

AYUMUには汎用レジスタ、入出力レジスタ、割込レジスタが存在します。
R0~R15の合計16本の汎用レジスタ、Out0~Out3・In0~In3のそれぞれ4本ずつの入力及び出力レジスタ、T0~T3のタイマー割込レジスタ、INT0~INT3の入力割込レジスタが存在します。

汎用レジスタ及び入出力レジスタは8ビットのレジスタです。8ビットのマイコンのように扱えばいいと思います。

T0~T3及びINT0~INT3の割込レジスタはポインタとフラグで分かれています。
ポインタレジスタには16ビットの割込発生時に飛ぶメモリアドレスを格納します。
フラグレジスタには8ビットのフラグを格納します。フラグの機能は下に示しています。

ビット 機能
0 割込発生フラグ(0: X, 1: O)
1 入力割込発生条件(0: 0→1, 1: 1→0)
2~7 タイマー倍率

命令セット

レジスタ+即値タイプ

OPCODE(4bit) + REG(4bit) + CONSTANT(8bit)
名前 ビット列 演算/機能
addi 1000 reg = reg + constant
subi 1001 reg = reg - constant
muli 1010 reg = reg * constant
shli 1011 reg = reg << constant
shri 1100 reg = reg >> constant
andi 1101 reg = reg & constant
ori 1110 reg = reg | constant
xori 1111 reg = reg ^ constant

レジスタ+レジスタタイプ

OPCODE(8bit) + REG1(4bit) + REG2(4bit)

汎用レジスタ= 汎用レジスタ+汎用レジスタ

名前 ビット列 演算/機能 備考
add 01000000 reg1 = reg1 + reg2
sub 01000100 reg1= reg1 - reg2
mul 01001000 reg1 = reg1 * reg2 符号付き整数
shl 01001100 reg1 = reg1 << reg2
shr 01010000 reg1 = reg1 >> reg2
and 01010100 reg1 = reg1 & reg2
or 01011000 reg1 = reg1 | reg2
xor 01011000 reg1 = reg1 ^ reg2
mulu 01011100 reg1 = reg1 * reg2 符号なし整数
addc 01100000 reg1 = reg1 + reg2 + C
subc 01101000 reg1 = reg1 - reg2 + C

分岐フラグ = 汎用レジスタ+汎用レジスタ

名前 ビット列 演算/機能 備考
beg 01000001 F = reg1 == reg2
bne 01000101 F = reg1 != reg2
blt 01001001 F = reg1 < reg2 符号なし整数
bgt 01001101 F = reg1 > reg2 同上
ble 01010001 F = reg1 <= reg2 同上
bge 01010101 F = reg1 >= reg2 同上

入出力/割込レジスタの値の設定(割込レジスタ+汎用レジスタ)

intputreg, outputreg, interruptregはそれぞれ入力レジスタ、出力レジスタ、割込レジスタを意味します。
[pointer]は割込発生時のジャンプ先、[flag]は割込発生フラグを意味します。
inputreg, outpureg, interruptregをreg1、regをreg2と考えてください。

名前 ビット列 演算/機能
rd 01000010 reg <- inputreg
wt 01000110 outputreg <- reg
intp 01010010 interruptreg[pointer] <- reg
intf 01100010 interruptreg[flag] <- reg

レジスタ組み合わせ番号タイプ

OPCODE(12bit) + NO(4bit)

NOは0001(R12+R13)と0010(R14+R15)が存在します。このNOは16ビットのメモリアドレスを8ビットのレジスタで表すための手段です。

名前 ビット列 演算/機能
jmp 000100000000 NOへジャンプ
jmpf 000100010000 分岐フラグが1の場合NOへジャンプ

ロード・ストア命令

データメモリへのロード・ストアのための命令です。

OPCODE(8bit) + REG1(4bit) + REG2(4bit)

REG1はロード先(ロード時)またはストア元(ストア時)でREG2はメモリのアドレスを示します。

名前 ビット列 演算/機能
st 01000011 mem[reg2] <- reg1
ld 01000111 reg1 <- mem[reg2]

レジスタ一個タイプ

一つしかありません。

名前 ビット列 演算/機能
not 000100000001 reg = ~reg

その他

名前 ビット列 演算/機能
jmpp 0001000000100000 割込ハンドラから抜ける

テストプログラム

タイマー割込を用いて一定時刻ごとにカウントアップし、出力するプログラムです。

xor r0, r0
addi r0, 201
intf T0, r0
xor r8, r8
xor r12, r12
xor r13, r13
addi r12, 0
addi r13, 15
intp T0, 0 #NOの0だからr12,r13の値(15)
wt Out0, r1
xor r12, r12
xor r12, r13
addi r12, 0
addi r13, 9
jmp 0 #NOの0だからr12,r13の値(9)
st r8, r8
addi r8, 1
st r12, r8
addi r8, 1
st r13, r8
addi r1, 1
wt Out0, r1
ld r13, r8
subi r8, 1
ld r12, r8
subi r8, 1
ld r8, r8
jmpp

動作確認

IMG_6422.jpeg
点灯しているのが確認できます。一定時刻が経つと点灯するLEDが変化します。数は二進数で表現されます。

コード

Githubのレポジトリに公開しています。今でもいくつかのミスは見つけましたが、保守のための余裕がないためコードの修正とかはできていません…プルリクエスト等の受理できないので予めご了承ください。

最後に

初めて自分で命令セットを設計し、それを実装したことに意義があると思っています。実装が汚い、無駄な実装がある、命令セットの設計が良くないなど、いろんな問題点のある作品ですが、どうか大目に見てください。
次はRISC-V命令セットの32ビットCPUを設計しようと思っています。上手くいくかどうかわかりませんが…

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2