Splunkで自作命令CPUエミュを作る

皆さんメリークリスマス。
ということで[前回](https://qiita.com/7GHz/items/abac19ef5d22bc391691)の宿題の値を保存したり読み込んだりするファイル保存をするところから再開です。
#ファイルに保存
ファイルに保存といってもSplunkでファイルに保存できる方法はそれほど多くありません。以下の2つのコマンドで実現可能です。
| outputlookup output_test.csv
| outputcsv output_test.csv
これでファイルの保存が可能ですが、ちゃんと記録されたか調べるため読み込みのコマンドも一緒に紹介します。
| inputlookup output_test.csv
| inputcsv output_test.csv

この2つの命令により、outputの前の時点のテーブルの内容をファイルに保存しinputでそのままのテーブルを読み込むことができます。

####実際に記録してみる。
前回の記事のHello Worldのサンプルを使い、そのままファイルに保存してみます。

```
| stats count 
| eval output="Hello world!!" 
| table output
| outputlookup output_test.csv
```
このサーチ文を実行するとoutput_test.csvに”Hello world!!”という文字列が記録されます。
ちゃんと記録されたかどうか見るにはinputlookupを使います。

```
| inputlookup output_test.csv
```

![2019-12-25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535472/e530e1a0-0966-eed5-2e8d-4c28fb459439.png)

無事読み書きができました。

####追記書き込み
基本的にoutputlookupなどは上書き保存なのだが追記保存も工夫すれば可能です。

```
| stats count 
| eval output="Hello SPL!!" 
| table output
| inputlookup append=true output_test.csv
| outputlookup output_test.csv
```

![2019-12-25 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535472/2db11921-0507-f3a0-661a-cfa6145d3957.png)

#Splunkで自作CPUエミュを作る
すみません、かなり難産で25日中にSPLを作るのが精いっぱいでした。
が、ちゃんと自作命令のCPUエミュで5!(5の階乗)を計算できました！！
使い方としては下記の***サーチ文を検索窓に入れてサーチボタンを連打***すると最終的に結果が表示されます。
***サーチボタン＝クロック入力***です。

```
| stats count 

| eval bin="1105,1201,1301,4331,3112,3A12,8A08,E003,FFFF"

| eval pcn="PC"
| lookup Reg.csv name as pcn outputnew val as pc
| eval mv_bin=split(bin,",")
| eval op_bin=mvindex(mv_bin,pc)
| table op_bin pc
| rex field=op_bin "(?<op>\S{1})(?<opr0>\S{1})(?<opr1>\S{1})(?<opr2>\S{1})"
| eval opr0r="R"+opr0,opr1r="R"+opr1,opr2r="R"+opr2
| lookup Reg.csv name as opr0r outputnew val as opr0v
| lookup Reg.csv name as opr1r outputnew val as opr1v
| lookup Reg.csv name as opr2r outputnew val as opr2v
| eval opr0v = if(opr0r=="R0",0,opr0v)

| lookup Hex.csv HEX as opr1 outputnew val as vhi
| lookup Hex.csv HEX as opr2 outputnew val as vlo 
| eval setval = vhi*16+vlo

| eval addval = opr1v + opr2v
| eval subval = opr1v - opr2v
| eval mulval = opr1v * opr2v
| eval divval = opr1v / opr2v
| eval divval = mvindex( split(divval,"."),0)

| eval res = if(op==1,setval,res)
| eval res = if(op==2,addval,res)
| eval res = if(op==3,subval,res)
| eval res = if(op==4,mulval,res)
| eval res = if(op==5,divval,res)
| eval pc = if(op==8 AND opr0v==0,setval - 1,pc)
| eval pc = if(op==9 AND opr0v>=0,setval - 1,pc)
| eval pc = if(op=="A" AND opr0v>0,setval - 1,pc)
| eval pc = if(op=="E",setval - 1,pc)
| eval pc = if(op=="F",pc - 1,pc)

| eval res = opr0r +","+ res
| eval pct = (pc + 1)
| eval pct = "PC,"+pct
| table res pct
| untable _time FieldName FieldValue
| eval mv_csv=split(FieldValue,",")
| eval name=mvindex(mv_csv,0)
| eval val=mvindex(mv_csv,1)
| table name val
| inputlookup append=true Reg.csv
| dedup name

| outputlookup Reg.csv
```
以下が実行結果になります。

![2019-12-25 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535472/986b5b9e-4406-fb03-600b-cb7696269086.png)

#今回作成したCPU
今回設計したCPUの基本設計は以下の通りです。
とりあえず必要最低限の機能を搭載しました。

ビット数 8bit
命令長 16bit固定
汎用レジスタ数 15個 (R1～RF)
ゼロセジスタ搭載　(R0)
四則演算機能
条件ジャンプ機能（x==0,x>=0,x>0）
無条件ジャンプ機能

多分これで大抵のことはできるようになるはずです。

####汎用レジスタ
汎用レジスタはR1〜RFまでとしました。そのほうがコード量が抑えられるためです。
#####R0レジスタについて
R0レジスタはゼロレジスタでリードオンリーです。計算で使用すると0が読み込まれます。
####その他レジスタ
CPUには他にもレジスタが必要です。今回設計したCPUでは汎用レジスタの他にはプログラムカウンタしか搭載していません。オーバーフローフラグとかでジャンプするのも良いのですがコード量が増えるのでやっていません。
####命令一覧
命令一覧は以下のようになります。
![2019-12-26 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535472/be6231f9-1417-4f68-b913-05961b73cb36.png)

####レジスタ長とオーバーフロー
レジスタ長は16bitとしておりますが、オーバーフローの処理は省略してます。
####サンプルプログラム
サンプルプログラムは以下のようにしました。

```
SET	R1,	0x05
SET	R2,	0x01
SET	R3,	0x01
LOOP:
MAL	R3,	[R3,R1]
SUB	R1,	[R1,R2]
SUB	RA,	[R1,R2]
JZ	RA,	#ENDB
JMP	  ,	#LOOP
ENDB:
END
```
これをコンマ区切りで機械語にすると
1105,1201,1301,4331,3112,3A12,8A08,E003,FFFF
になります。

#SPL説明
ここからSPLを説明しますが基本的にやっていることを説明する感じになります。
####プログラムメモリ
プログラムメモリはbinフィールドの中に格納されています。コンマごとに命令を入れています。

```
| eval bin="1105,1201,1301,4331,3112,3A12,8A08,E003,FFFF"
```
####プログラムメモリから命令読み込み
プログラムカウンタに基づき、プログラムメモリから実行する命令を読み出します。また今後不要なフィールドを削除します。

```
| eval pcn="PC"
| lookup Reg.csv name as pcn outputnew val as pc
| eval mv_bin=split(bin,",")
| eval op_bin=mvindex(mv_bin,pc)
| table op_bin pc
```

####命令の分割
計算に入る前に命令を分解します。

```
| rex field=op_bin "(?<op>\S{1})(?<opr0>\S{1})(?<opr1>\S{1})(?<opr2>\S{1})"
```

####各種レジスタの読み込みと直値の計算
命令の種類に関係なく、レジスタを読み込み直値の計算を行います。

```
| rex field=op_bin "(?<op>\S{1})(?<opr0>\S{1})(?<opr1>\S{1})(?<opr2>\S{1})"
| eval opr0r="R"+opr0,opr1r="R"+opr1,opr2r="R"+opr2
| lookup Reg.csv name as opr0r outputnew val as opr0v
| lookup Reg.csv name as opr1r outputnew val as opr1v
| lookup Reg.csv name as opr2r outputnew val as opr2v
| eval opr0v = if(opr0r=="R0",0,opr0v)

| lookup Hex.csv HEX as opr1 outputnew val as vhi
| lookup Hex.csv HEX as opr2 outputnew val as vlo 
| eval setval = vhi*16+vlo
```

####四則演算の計算
これも命令に関係なくすべての四則演算を実行します。

```
| eval addval = opr1v + opr2v
| eval subval = opr1v - opr2v
| eval mulval = opr1v * opr2v
| eval divval = opr1v / opr2v
| eval divval = mvindex( split(divval,"."),0)
```

####命令の種類に基づき結果を出す
ここで初めて命令の種類に基づき、結果や動作を分けます。

```
| eval res = if(op==1,setval,res)
| eval res = if(op==2,addval,res)
| eval res = if(op==3,subval,res)
| eval res = if(op==4,mulval,res)
| eval res = if(op==5,divval,res)
| eval pc = if(op==8 AND opr0v==0,setval - 1,pc)
| eval pc = if(op==9 AND opr0v>=0,setval - 1,pc)
| eval pc = if(op=="A" AND opr0v>0,setval - 1,pc)
| eval pc = if(op=="E",setval - 1,pc)
| eval pc = if(op=="F",pc - 1,pc)
```

####レジスタを更新
プログラムカウンタ等を更新したあとレジスタCSV用に整形しレジスタを更新します。

```
| eval res = opr0r +","+ res
| eval pct = (pc + 1)
| eval pct = "PC,"+pct
| table res pct
| untable _time FieldName FieldValue
| eval mv_csv=split(FieldValue,",")
| eval name=mvindex(mv_csv,0)
| eval val=mvindex(mv_csv,1)
| table name val
| inputlookup append=true Reg.csv
| dedup name

| outputlookup Reg.csv
```

####必要なルックアップ
このサーチ文を実行するためには以下二つのCSVをルックアップとして登録する必要があります。

```Reg.csv
name,val
PC,0
R1,0
R2,0
R3,0
R4,0
R5,0
R6,0
R7,0
R8,0
R9,0
RA,0
RB,0
RC,0
RD,0
RE,0
RF,0
```

```Hex.csv
HEX,dec
0,0
1,1
2,2
3,3
4,4
5,5
6,6
7,7
8,8
9,9
A,10
B,11
C,12
D,13
E,14
F,15
```

またCPUリセットのサーチ文が必要なので用意しましたが、PCのリセットしか行いません。

```
| stats count 
| eval pct = 0
| eval pct = "PC,"+pct
| table res pct
| untable _time FieldName FieldValue
| eval mv_csv=split(FieldValue,",")
| eval name=mvindex(mv_csv,0)
| eval val=mvindex(mv_csv,1)
| table name val
| inputlookup append=true Reg.csv
| dedup name
```

#サンプルプログラムいろいろ
#####116+89
結果出力 R1
| eval bin="1A59,1B74,21BA,FFFF"
#####5!
結果出力 R3
| eval bin="1105,1201,1301,4331,3112,3A12,8A08,E003,FFFF"
#####2^10
結果出力 R1
| eval bin="1A01,1B00,1C02,1D09,1102,411C,2BBA,3EBD,8E0A,E005,FFFF"

#やり残したこと
クロックの自動生成（止まるまでループする処理）
外部メモリの実装とアクセス
オーバーフロー処理