ComProcWorkshop/第3章:加減算ができるコンパイラを作る
コンパイラを「整数+整数」や「整数−整数」を認識するように改造する
CPUが加減算を実行できるように拡張する
動作の分割
「1バイトの数値を受け取ったら即座に出力」は柔軟性がなさ過ぎる
動作を分割する
レジスタに値を格納する
レジスタの値を出力する
中間にレジスタの値を変化させる命令を挟めば動作を変えられるようになる
値を記憶しておく電子部品
FPGAでは1ビットのレジスタを組み合わせ、任意幅のレジスタを作れる
CPUに8ビットのレジスタreg0を追加
レジスタに値を格納する命令
01xx(2バイト)
reg0に数値xxを書き込む
数値は0~255(00~ff)の整数
レジスタの値を出力する命令
0200(2バイト)
reg0にそのとき格納されている値を出力する
命令長
CPUが実行する命令のビット数
このCPUは固定長命令で、1命令=16ビット
Intel x86は可変長命令
UARTでは8ビットずつ受信なので、受信2回=1命令
UART受信の改造
ffffを受け取ったら受信動作を止める
CPUの作成
受け取った2バイトの上位1バイトで動作を選択
01なら下位1バイトをreg0に書き込む
02ならreg0の値を出力する
CPUとメイン基板のつなぎ込み
pc: プログラムカウンタ
insn: 命令
pcが指すメモリに存在する命令
プログラムを格納するメモリ
ada/dinは書き込み側
UARTからBRAMへの書き込み
adb/doutは読み出し側
BRAMからCPUへの読み出し
命令の読み書き経路
https://gyazo.com/634505cafb41fbfdc601d8da7b2ff981
BRAM(ブロックRAM)
FPGAに内蔵されているメモリ領域
BRAMはLUTを消費しない
GowinのFPGAが持つBRAMは1個18Kbits
読み書きデータ幅を柔軟に設定できる
16Kbitsモード:1,2,4,8,16,32ビット幅
18Kbitsモード:9,8,36ビット幅
プログラムの受信と実行
全部受信してから一気に実行するか
1命令受信したら実行、を繰り返すか
後々のことを考えると前者が良い
同じ命令の繰り返し実行のためには、受信したプログラムを記憶しておく必要がある
コンパイラの改造
加減算に対応する前に、ひとまず分割命令に対応させる
例:ソースコード「42」
code: コンパイラ出力
012a
0200
機械語
上記でいう012aや0200
CPUが直接実行するプログラミング言語のこと
もし、世の中にC言語プログラムをそのまま実行するCPUがあれば、C言語はそのCPUにとっては機械語である
多くのCPUにとってはC言語は機械語ではない
FPGAへの転送
機械語命令は2バイト単位
上位バイト、下位バイトどちらを先に送信するかを、送受信双方で決めておく
やってみよう:FPGAへの転送(WSL with usbipd)
code:Shell
$ cd workshop/chap3/compiler
$ gcc main.c
$ ../../../tool/attach_vcom.sh xxx
$ sudo ../../../tool/uart.py --unit 2 $(echo 42 | ./a.out) ffff
2a
attach_vcom.shの実行はUSBシリアル変換を接続した直後1回だけでOK
Achnowrichは、16進数を大文字で入力する必要がある
trコマンドを用いて、コピペに適した文字列へ変換する
code:sh
012A0200
AcknowrichをBINモードに設定し、出力された 012A0200 コピペする
最後にFFFFを加えて送信すると、2Aが返ってくる
https://gyazo.com/23f88a4ce0fb06f7eecf10865cbe5099
機械語に分かりやすい名前を付けたもの
例えば、「レジスタに値を格納する命令」をMOVと命名する
01xxはMOV xxとなる
アセンブル=アセンブリ言語を機械語に変換すること
table: ハンドアセンブル表
アセンブリ言語 機械語
MOV xx 01xx
OUT 0200
ADD xx 03xx
加算命令ADD
ADD xx(03xx)
reg0に数値xxを足す
reg0 += xx
数値は0~255(00~ff)の整数
加算式のコンパイル
加算式とは整数 + 整数の形の式
整数を1つ読んだらMOV 整数
次に+を読んだらADD 整数
最後にOUT
加算式のコンパイル例
例:ソースコード「4 + 5」
code: コンパイラ出力
0104
0305
0200
やってみよう:加算式の実行
コラム:加算器
Verilogでは+と書くだけで数値の加算が行える
加算器は論理回路で、2つの入力を加算した結果を出力する回路となっている
+を使わず、&、|、~を組み合わせて加算器を自作することも可能
基本式と加算式
基本式:整数1つからなる式
例:42
加算式:基本式 + 基本式
例:1 + 2
基本式と加算式の両対応
まず基本式を読み込み、MOV xxを出力
次の文字によって処理を変える
「+」なら、さらに基本式を読み込み、ADD xxを出力
それ以上文字がなければ何もしない
最後にOUTを出力
2個以上の加算への対応
「1 + 2 + 3」のような式
「+」と基本式を読み込んでADD xxを出力する処理を「+」が無くなるまで繰り返せば良い
code:処理の例(擬似コード)
printf("01%02x\n", scan_int()); // MOV xx
while (scan_char() == '+') {
printf("03%02x\n", scan_int()); // ADD xx
}
printf("0200\n"); // OUT
減算
ADDと同じようにSUBを増やす
……と思いきや、CPU側に命令を増やさなくても減算に対応できる
ADDで減算
「1 - 2」を「1 + (-2)」だと考える
コンパイラは次のプログラムを出力すればよい
code:コンパイラ出力
MOV 1
ADD -2
OUT
負値の表現
「-2」を03xxのxxに収めなくてはいけない
8ビットの16進数で表すということ
ADD -2 → 03fe
多くのCコンパイラでは整数を2の補数で扱っている
表示したい負数を無符号整数にキャストすればOK
code:負数xを8ビットの16進数で表示する
printf("%02x", (uint8_t)x);
加減算に対応したコンパイラの実装例
1+2や1 + 2など、空白があっても読み飛ばす工夫
1+2-3のような複数個の加減算にも対応