![]() |
21 |
RISC-Vプロセッサの設計 (4) |
![]() |
Posts Tagged with "Design"
既に発行済みのブログであっても適宜修正・追加することがあります。We may make changes and additions to blogs already published.
![]() |
20 |
RISC-Vプロセッサの設計 (3) |
![]() |
「ハードウェアインタプリター」に食わせる機械語列が必要です。そこで、この記事を参考に、Fibonacciプログラムをコンパイルし機械語化しました。試みにFibonacciが通るための「インタプリター」を書いていきます。前述のとおりこの「インタプリター」は実行ステージが逆アセンブル相当の表示をするだけのものです。
入力するFibonacciのソースは以下のように短いプログラムです。
fibo.c:
int fib(int n) {
if(n <= 1) return 1;
return fib(n-1) + fib(n-2);
}
int main() {
fib(10);
for(;;) {}
return 0;
}
これをクロスコンパイルしてRISC-Vの命令を作成し、BSVの入力とします。シーケンサの自動生成を利用して1サイクル毎に、命令デコーダに命令を供給します。
Stmt main = seq
instr <= 32'h074000ef;
instr <= 32'hfe010113;
instr <= 32'h00112e23;
instr <= 32'h00812c23;
instr <= 32'h00912a23;
instr <= 32'h02010413;
instr <= 32'hfea42623;
instr <= 32'hfec42703;
instr <= 32'h00100793;
instr <= 32'h00e7c663;
instr <= 32'h00100793;
instr <= 32'h0300006f;
instr <= 32'hfec42783;
instr <= 32'hfff78793;
instr <= 32'h00078513;
instr <= 32'hfc9ff0ef;
instr <= 32'h00050493;
instr <= 32'hfec42783;
instr <= 32'hffe78793;
instr <= 32'h00078513;
instr <= 32'hfb5ff0ef;
instr <= 32'h00050793;
instr <= 32'h00f487b3;
instr <= 32'h00078513;
instr <= 32'h01c12083;
instr <= 32'h01812403;
instr <= 32'h01412483;
instr <= 32'h02010113;
instr <= 32'h00008067;
instr <= 32'hff010113;
instr <= 32'h00112623;
instr <= 32'h00812423;
instr <= 32'h01010413;
instr <= 32'h00a00513;
instr <= 32'hf7dff0ef;
instr <= 32'h0000006f;
endseq;
今回はまだデコーダのテストだけなので、PCは実装していません。
![]() |
18 |
RISC-Vプロセッサの設計 (2) |
![]() |
図532.1にBSV版のBitpatのREADMEに掲載されていた使用例を示します。

挙げられた例はちょうどRISC-Vの命令パターンに一致しており、add命令とaddi命令のデコード部分を示したものです。この要領で、次々に他の命令を実装していくことができます。
本ライブラリの動作は以下の2ステップとなっています。
- whenの中のパターンマッチでは可変部をvで表し、固定部をnとビットパターンで記述します。vの幅を指定しないで良いのは使いやすそうです。whenの最後に識別した機能を解釈するための関数名を記述します。
- マッチした後に呼ばれる関数では、可変部のみを変数で受け(固定部は捨てる)、処理を実行します。結局vの幅は意識しなければなりません。
このように最初に固定部、次に可変部という考え方に慣れる必要があります。最初は使いにくいと感じましたが、慣れれば気にならないのかもしれません。
プロセッサ設計と言ってもパイプラインでなければ、見方を変えれば、RISC-V機械語のインタプリターをHDLで作成するだけなので、それほど難しいことではありません。ひとつずつデコードし、対応する処理を実装していくだけです。この「ハードウェアインタプリター」の1段目はデコードステップで、2段目は実行ステップになります。
![]() |
17 |
RISC-Vプロセッサの設計 |
![]() |
「はじめてのCPU自作」という本を購入したので、これを参考にRISC-Vプロセッサを設計します。ただし、この本ではChiselベースとなっていますが、本稿ではBSVベースとします。またパイプラインプロセッサの経験があるため、最初からパイプラインプロセッサを設計します。
さて、この本を読んでいたらChisel(だかScalaだか)にはBitpatという便利な機能があるようです。

命令デコーダを書くのに便利そうなので、BSVにもないのか調べたら、GithubにBitpatという似たようなものがありました。これはAlexandre Joannouさんが作成されたものであり、Readmeには以下のように書いています。
BitPat BitPat is a bit-string pattern matching library for Bluespec, inspired by Morten Rhiger's "Type-Safe Pattern Combinators".
BSVにおいてのパターンマッチライブラリとのことです。便利そうなのでRISC-Vの命令デコーダに採用することにします。
![]() |
27 |
BSVによるSpace Invadersの変更 (9) |
![]() |
![]() |
26 |
BSVによるSpace Invadersの変更 (8) |
![]() |
Y字リプレースアニメーションのソース
Y字リプレースアニメーションのソースを示します。Y字リプレースアニメーションもFボタンにより中断するため、各所でFボタンを見ています。
function Stmt replaceY;
return (seq
// from right to left
for (i <= 228; i >= 142; i <= i - 2) seq
copyArea((pack(i)[1] == 1'b1) ? 68 : 84 , 32, i, 67, 10, 8);
wait_timer(`TICK_WAIT3);
if (fbutton) break;
endseq // for
if (fbutton) break;
// from left to right
for (i <= 136; i <= 226; i <= i + 2) seq
copyArea((pack(i)[1] == 1'b1) ? 75 : 91 , 107, i, 67, 16, 8);
wait_timer(`TICK_WAIT3);
if (fbutton) break;
endseq // for
eraseArea(226, 67, 16, 8);
wait_timer(`TICK_WAIT32);
if (fbutton) break;
// from right to left
for (i <= 226; i >= 136; i <= i - 2) seq
copyArea((pack(i)[1] == 1'b1) ? 77 : 93 , 117, i, 67, 16, 8);
wait_timer(`TICK_WAIT3);
if (fbutton) break;
endseq // for
wait_timer(`TICK_WAIT32);
if (fbutton) break;
eraseArea(141, 67, 9, 8);
wait_timer(`TICK_WAIT32);
if (fbutton) break;
endseq);
endfunction
これだけでなく、タイマールーチンの中でもFボタンによる中断を見ていますが、ちょっとやり過ぎのようです。実際には多少間引いても体感に影響しないと思います。
![]() |
23 |
BSVによるSpace Invadersの変更 (7) |
![]() |
オープニングアニメーションのソース
オープニングアニメーションのソースを示します。オープニングアニメーションはFボタン(コイン投入の模擬)により中断するため、各所でFボタンを見ています。
function Stmt openingAnimation;
return (seq
// Opening Animation
foa <= True;
eraseArea( 0, 41, 255, 199); // erase screen
eraseArea(25,242, 5, 7); // erase zanki
stringS1; // PLAY ...
if (fbutton) break;
wait_timer(`TICK_WAIT64);
if (fbutton) break;
stringS2; // *SCORE ...
if (fbutton) break;
wait_timer(`TICK_WAIT32);
if (fbutton) break;
stringS3; // =? MYSTERY ...
if (fbutton) break;
wait_timer(`TICK_WAIT64);
if (fbutton) break;
replaceY; // ^ -> Y
if (fbutton) break;
wait_timer(`TICK_WAIT64);
if (fbutton) break;
foa <= False;
endseq);
endfunction
![]() |
22 |
BSVによるSpace Invadersの変更 (6) |
![]() |
オープニングアニメーションの追加
テスト用のソースを示します。コンパイル時間短縮のため、ゲーム部分をカットしています。
// メインフロー
Stmt main = seq
while (True) seq
while (!fbutton) seq
openingAnimation; // Fボタンによりブレーク
endseq // while
openingDisplay; // 表示のみ、ボタンを待たない
await(sbutton); // Sボタンによりブレーク
openingDisplay2; // タイマーによりブレーク
// game start
endseq // while
endseq;
// CREDIT 01, "PUSH ONLY 1PLAYER BUTTON"
function Stmt openingDisplay;
return (seq
eraseArea( 0, 41, 255, 199); // erase screen
stringS5; // PUSH ONLY ...
copyArea(10, 162, 217, 241, 5, 7); // CREDIT 00->01
endseq);
endfunction
// CREDIT 00, "PLAY PLAYER<1>, 00000"
function Stmt openingDisplay2;
return (seq
eraseArea( 0, 41, 255, 199); // erase screen
stringS6; // PLAY PLAYER<1>
copyArea(2, 162, 217, 241, 5, 7); // CREDIT 01->00
for (i <= 1; i < 15; i <= i + 1) seq
// erase zero
eraseArea(40, 25, 37, 7);
wait_timer(`TICK_WAIT4); // wait 66.66msec
stringS7; // 00000
wait_timer(`TICK_WAIT4); // wait 66.66msec
endseq // for
endseq);
endfunction
![]() |
21 |
BSVによるSpace Invadersの変更 (5) |
![]() |
オープニングアニメーションの追加
この動画の最初の部分を参考にして、オープニングアニメーションを作成します。
- インベーダの種類や点数の紹介、と同時に逆さYを引っ張って行き正立Yに入れ替えます。アニメーションがメインなので、これをopeningAnimationシーケンスと呼び、同名の関数により実行します。Fボタンによりコイン投入を模擬します。

図513.1 openingAnimation画面 - コインを投入すると、"PUSH ONLY 1PLAYER BUTTON"と表示され、CREDITが+1されます。アニメーションは無いため、これをopeningDisplayシーケンスと呼び、同名の関数により実行します。Sボタンを待ちます。

図513.2 openingDisplay画面 - Sボタンを押すと、"PLAY PLAYER<1>"と表示され、CREDITが-1されます。同時に得点が"00000"となり、点滅します。これをopeningDisplay2シーケンスと呼び、同名の関数により実行します。

図513.3 openingDisplay画面1 
図513.4 openingDisplay画面2 - ゼロ点滅を規定回数実行すると自動的にゲームを開始します。
図513.5にSボタンとFボタンの配置を示します。

![]() |
20 |
BSVによるUARTの再設計 (2) |
![]() |
テストベンチ
テストベンチは変わりません。
Tb.bsv
import StmtFSM::*;
import Uart::*;
(* synthesize *)
module mkTb();
Uart_ifc uart <- mkUart();
Stmt s = seq
delay(8);
uart.load(8'h55);
uart.load(8'haa);
uart.load(8'hc3);
uart.load(8'h3c);
await(uart.done());
$finish;
endseq;
mkAutoFSM(s);
endmodule
Bsimシミュレーション
Bsimシミュレーションのコマンドは次のとおりです。
$ bsc -u -sim Tb.bsv; bsc -sim -e mkTb -o Tb.exec; ./Tb.exec -V bsim.vcd; gtkwave -A bsim.vcd
以下にBsimシミュレーション結果を示します。意外なことに明示的にdone信号を書いたにも関わらず、シミュレーションのダンプの中にdone信号がありませんでした。

テストベンチ内でレジスタにuart.doneを格納するようにしたら、インタフェースにuart.doneが現れました。awaitで使用するくらいでは削除され、レジスタに取って初めて残すようです。

Verilogシミュレーション
Verilogシミュレーションにおいては、モジュール(mkUart.v)、それをドライブするテストベンチ(mkTb.v)の上位に最上位(top.v)を配備します。これはクロックやリセットを供給するモジュールですが、Bsimの場合はシステムから暗黙にクロックやリセットが供給される一方、Verilogでは供給されないためです。
top.v
`timescale 1ns/1ns
module top();
/*AUTOREGINPUT*/
// Beginning of automatic reg inputs (for undeclared instantiated-module inputs)
reg CLK; // To mkTb_inst of mkTb.v
reg RST_N; // To mkTb_inst of mkTb.v
// End of automatics
/*AUTOWIRE*/
mkTb mkTb_inst(/*AUTOINST*/
// Inputs
.CLK (CLK),
.RST_N (RST_N));
initial begin
RST_N = 1'b0;
#10;
RST_N = 1'b1;
end
initial begin
CLK = 1'b0;
forever begin
#5 CLK = ~CLK;
end
end
initial begin
$dumpfile("verilog.vcd");
$dumpvars;
end
endmodule // top
Verilogシミュレーションのコマンドは次のとおりです。
$ bsc -u -verilog Tb.bsv; iverilog top.v mkTb.v mkUart.v -o ./mkTb.exev; ./mkTb.exev; gtkwave -A verilog.gtkw
Verilogシミュレーションのほうには当然ですが、uart.done信号が存在します。

ページ:




