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

# 事始め
去年のアドベントカレンダーで[TD4を実装した記事](https://qiita.com/Prokuma/items/ceb9e0487a992bb020b6)を書きましたが、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 &#124; 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 &#124; 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487309/2c17bfae-2ea0-1aec-9e33-6f5309ef9d44.jpeg)
点灯しているのが確認できます。一定時刻が経つと点灯するLEDが変化します。数は二進数で表現されます。

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

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