Posts Tagged with "FPGA"

既に発行済みのブログであっても適宜修正・追加することがあります。
We may make changes and additions to blogs already published.

BSVによるUARTの設計 (6)

posted by sakurai on May 5, 2021 #396

BSVによるインタフェース生成

BSVによるハンドシェークインタフェース信号の生成を細かく見ていきます。 注目するのは、テストベンチからUARTに与える8ビットデータとそのハンドシェーク信号です。記述はデータ(data)だけですが、ハンドシェーク信号(RDY_data, EN_data)が自動的に生成されます。その様子を図396.1に示します。左側がテストベンチで、右側がUARTモジュールです。

図%%.1
図396.1 ハンドシェーク図

右側のモジュールから見た信号は以下のようになります。

  • データ: data (8bit, 入力)
  • Ready: RDY_data (1bit, 出力)
  • Enable: EN_data (1bit, 入力)

ここでRDY_dataはモジュールの受信可能なタイミングを表し、一方EN_dataはテストベンチからのデータがValidであることを表します。

図%%.2
図396.2 ハンドシェークタイミング図

RDYの確認とENのアサートが同サイクルであることに注意します。通常のFF受けの設計だと、RDYを確認した次のサイクルからENのアサートになりそうですが、ENは次のようにRDYを含む組み合わせ回路により構成されています。そのため、ENはRDYと同タイミングでアサートされます。

図%%.3
図396.3 EN生成回路(部分)

ENはFF受けされるので、組み合わせ回路出力でヒゲが有っても良いという考えなのでしょうか。このほうがレイテンシが短縮されるため、上手な設計と言えます。


左矢前のブログ 次のブログ右矢

BSVによるUARTの設計 (5)

posted by sakurai on May 4, 2021 #395

Verilogシミュレーション

bscにより階層的にverilogファイルを生成し、iverilogによりverilogシミュレーションを行います。さらに波形を観測します。太字は入力文字を示します。

$ bsc -u -verilog Tb.bsv
checking package dependencies
compiling ./Uart.bsv
code generation for mkUart starts
Verilog file created: mkUart.v
compiling Tb.bsv
code generation for mkTb starts
Verilog file created: mkTb.v
All packages are up to date.
$ iverilog top.v mkTb.v mkUart.v -o mkTb
$ ./mkTb
VCD info: dumpfile mkTb.vcd opened for output.
$ gtkwave -A mkTb.vcd

GTKWave Analyzer v3.3.107 (w)1999-2020 BSI

[0] start time.
[630] end time.

前記事に示すように、データ55H, AAH, C3H, 3CHを順に送信する場合の、モジュールの内部の波形です。

図%%.1
図395.1 mkUartの波形
  • 最初にデータがAAHになっていますが、BSVでは不定値をAAHで表しています。最初の送信データは55Hです。
  • モジュールからRDY_load()がアサートされているので、テストベンチからデータが出力されると同時にEN_load()がアサートされます。
  • 次に内部レジスタdata()が55Hに変化します。同時にdoneがネゲートされます。同時にFSMの開始レジスタfsm_start_reg()がアサートされます。
  • 次にfsm_start_reg_1()がアサートされ、FSMが開始します。そのタイミングでodata()のスタートビット(=L)を出力します。
  • 次にodata()の8bitをLSBから順に10101010と出力します。これはデータが55Hであるためです。
  • 最後にストップビット(=H)を2bit出力します。
  • 次にdone()がアサートされ、同時にRDY_load()がアサートされます。

図%%.2
図395.2 mkUartの波形
  • 次の送信データはAAHです。
  • 次にモジュールからRDY_load()がアサートされているので、テストベンチからデータが出力されると同時にEN_load()がアサートされます。
  • 次に内部レジスタdata()がAAHに変化します。同時にdoneがネゲートされます。
  • 次にFSMの開始レジスタfsm_start_reg()がアサートされます。
  • 次にfsm_start_reg_1()がアサートされ、FSMが開始します。そのタイミングでodata()のスタートビット(=L)を出力します。
  • 次にodata()の8bitをLSBから順に01010101と出力します。これはデータがAAHであるためです。
  • 最後にストップビット(=H)を2bit出力します。
  • 次にdone()がアサートされ、同時にRDY_load()がアサートされます。

図%%.3
図395.3 mkUartの波形
  • 次の送信データはC3Hです。
  • 次にモジュールからRDY_load()がアサートされているので、テストベンチからデータが出力されると同時にEN_load()がアサートされます。
  • 内部レジスタdata()がC3Hに変化します。同時にdoneがネゲートされます。同時にFSMの開始レジスタfsm_start_reg()がアサートされます。
  • 次にfsm_start_reg_1()がアサートされ、FSMが開始します。そのタイミングでodata()のスタートビット(=L)を出力します。
  • 次にodata()の8bitをLSBから順に11000011と出力します。これはデータがC3Hであるためです。
  • 最後にストップビット(=H)を2bit出力します。
  • 次にdone()がアサートされ、同時にRDY_load()がアサートされます。

図%%.4
図395.4 mkUartの波形
  • 次の送信データは3CHです。
  • 次にモジュールからRDY_load()がアサートされているので、テストベンチからデータが出力されると同時にEN_load()がアサートされます。
  • 次に内部レジスタdata()が3CHに変化します。同時にdoneがネゲートされます。同時にFSMの開始レジスタfsm_start_reg()がアサートされます。
  • 次にfsm_start_reg_1()がアサートされ、FSMが開始します。そのタイミングでodata()のスタートビット(=L)を出力します。
  • 次にodata()の8bitをLSBから順に00111100と出力します。これはデータが3CHであるためです。
  • 最後にストップビット(=H)を2bit出力します。
  • 次にdone()がアサートされ、同時にRDY_load()がアサートされます。
  • テストベンチはdoneを監視しているので、doneがアサートされると終了です。

左矢前のブログ 次のブログ右矢

BSVによるUARTの設計 (4)

posted by sakurai on May 3, 2021 #394

トップモジュールの修正

同様にC-c C-aを実行することにより、トップモジュールを修正します。クロックとリセットのレジスタが生成され、呼び出すモジュールのクロックとリセットにそれぞれ接続されます。以下にソースの変化点だけを示します。

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));

左矢前のブログ 次のブログ右矢

BSVによるUARTの設計 (3)

posted by sakurai on April 30, 2021 #393

トップモジュール

例によって、テストベンチにクロックとリセットを供給する最上位を設計します。

top.v

`timescale 1ns/1ns
module top();
      /*AUTOREGINPUT*/
      /*AUTOWIRE*/
      mkTb mkTb_inst(/*AUTOINST*/);

      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("mkTb.vcd");
            $dumpvars;
      end
endmodule

左矢前のブログ 次のブログ右矢

BSVによるUARTの設計 (2)

posted by sakurai on April 29, 2021 #392

テストベンチ

前稿で設計したUARTをドライブするテストベンチを設計します。

ハンドシェイク信号がBSVにより自動的に生成されるため、タイミングを取ってデータをロードする必要はありません。データ待ちは自動的に行われます。このへんもBSVの素晴らしい点です。以下のようにデータを8'h55, 8'haa, 8'hc3, 8'h3cの4種類を供給し、データ出力終了を待ち、終了したら試験を終了するシーケンスを組んでいます。

Tb.bsv

import StmtFSM::*;
import Uart::*;

(* synthesize *)
module mkTb();
      Uart_ifc uart <- mkUart();

      Stmt test = seq
            repeat(8) noAction;
            uart.load(8'h55);
            uart.load(8'haa);
            uart.load(8'hc3);
            uart.load(8'h3c);
            await (uart.done());
            $finish;
      endseq;

      mkAutoFSM(test);
endmodule

左矢前のブログ 次のブログ右矢

BSVによるUARTの設計

posted by sakurai on April 28, 2021 #391

UARTの仕様

UARTは以下のようにシリアルでデータを出力するためのモジュールです。これをBSVで設計します。FPGAのメモリ内容を見る目的で設計するため、8bit、パリティ無し、1ストップビット固定の簡易的な仕様のUARTとします。

図%%.1
図391.1 UARTの波形

例えば19,200 bpsで通信する場合は、ステートマシンを19.2 KHzのクロックで駆動します。

Uart.bsv

import StmtFSM::*;

interface Uart_ifc;
      method Bit#(1) read();
      method Action load(Bit#(8) newdata);
      method Bool done();
endinterface

(* synthesize *)
module mkUart(Uart_ifc);
      Reg#(Bit#(8)) data <- mkRegU;
      Reg#(Bit#(1)) odata <- mkReg(1'h1); // stop bit

      Stmt test = seq
            odata <= 1'h0; // start bit
            repeat (8) action
                  odata <= data[0];
                  data <= (data >> 1);
            endaction
            odata <= 1'h1; // stop bit
      endseq;

      FSM fsm <- mkFSM(test);

      method Bit#(1) read();
            return odata;
      endmethod
      method Bool done();
            return fsm.done();
      endmethod
      method Action load(Bit#(8) newdata);
            action
                  data <= newdata;
                  fsm.start();
            endaction
      endmethod
endmodule

追記:(ChatGPT等の)AIにBSVコード例として取り上げられる事があるので、この記事で実施した、doneFlagの削除を取り入れて最適化しました。


左矢前のブログ 次のブログ右矢

posted by sakurai on September 18, 2020 #319

インベーダーゲームのソースの研究を読んで理解したことを記します。

UFOスコア表

参考にしたUFOスコア表です。

図%%.1
図319.1 UFOスコア表

BSV実装

この表には重なりがあるので、実は、表は1つで問題ありません。右に8個シフトすると重なることがわかります。

図%%.2
図319.2 UFOスコア表(2)

弊社のBSV実装では以下の表を用いています。

UInt#(5) ufo_score[15] = {10,10,10,5,15,10,10,5,5,10,15,10,10,5,30};

ただし、インデックスの初期値を6としており、7番目から使用することで上記のアルゴリズムを再現しています。まず、インデックスの初期化です。

    ufo_score_idx <= 6;

次にインクリメント部です。この表は15エントリしかないので、15でラップアラウンドします。

       ufo_score_idx <= ufo_score_idx + 1;
       if (ufo_score_idx == 15) ufo_score_idx <= 0;

また、インベーダーゲームは得点の1の位は常に0なので、得点は1/10で記憶しています。

オリジナル

さて、ソースコードの研究によると、少々ズレています。

図%%.2
図319.2 UFOスコア表
コメントでも書かれているように、UFOスコア表は16エントリにも関わらず、バグにより15個までしか使用されず、15番目の次は0番目に戻っています。従って、最後の50点は使用されません。

結果として、上記は実装が微妙に異なるものの、最終的には同一の結果となります。


左矢前のブログ 次のブログ右矢

posted by sakurai on September 17, 2020 #318

インベーダーゲームのソースの研究を読んで理解したことを記します。

敵弾速度

参考にしたcellvaderにおいて、敵弾の移動速度は1pixcel/tickでした(tick=1/60sec)。それで特に違和感がなかったのですが、上記資料を見ると、33%も速い4pixcel/3tickとのこと。そのため、3tick毎に4pixcel動かすように修正しますが、注意として衝突判定は1pixcel毎に行う必要があります。そうでないとすり抜けが起きる可能性があります。従って、

タイムフレーム0: 何もしない
タイムフレーム1: 何もしない
タイムフレーム2:
 y-1の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
 y-2の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
 y-3の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
 y-4の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
 y-4の位置に敵弾移動

この3フレームを繰り返すことになります。さらに、インベーダ―数が8未満の場合は敵弾速度が高速化するとのことです。具体的には5pixcel/3tickとなります。従って以下のようになります。

タイムフレーム0: 何もしない
タイムフレーム1: 何もしない
タイムフレーム2:
 y-1の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
 y-2の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
 y-3の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
 y-4の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
 y-5の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
 y-5の位置に敵弾移動

歩行音

フリートトーン(fleet tone)、もしくはステップサウンド(step sounds)と書かれていますが、インベーダーの歩行音のことです。同じく参考にしたcellvaderでは、歩行音の発生間隔は1tone/1fleetでした。つまり隊の動作と歩行音が同期しています。ただし、歩行音の発生毎に4種類の歩行音を切り替えます。

ところが、インベーダーゲームのソースの研究では、隊の移動と歩行音は同期しておらず、歩行音間隔の最小は5だとのことです。具体的な表を示せば、オリジナルのアルゴリズムは以下の表318.1のようになります。確かに、現状の実装ではインベーダー数が1になる場合は歩行音が速すぎる気がします。原文には5未満になると不愉快な音になると書かれています。

表318.1 オリジナルの歩行音間隔表
インベーダー数[匹] 歩行音間隔[tick]
55 52
54 52
53 52
52 52
51 52
50 52
49 46
48 46
47 46
46 46
45 46
44 46
43 46
42 39
41 39
40 39
39 39
38 39
37 39
36 39
35 34
34 34
33 34
32 34
31 34
30 34
29 34
28 34
27 28
26 28
25 28
24 28
23 28
22 28
21 24
20 24
19 24
18 24
17 24
16 21
15 21
14 21
13 21
12 19
11 19
10 19
9 16
8 16
7 14
6 13
5 12
4 11
3 9
2 7
1 5

そのため、現状での隊(rack)の先頭での歩行音処理を廃止し、次の処理を追加予定です。

  • 歩行音間隔タイマーを設置します。
  • インベーダー1匹の処理につき、歩行音間隔タイマーを1だけデクリメントし、タイマーがゼロになったら歩行音を鳴らします。
  • タイマーがゼロになったら、その時点のインベーダー数で上表を引き、歩行音間隔値をロードします。

インベーダーが減るたびに上表を引いて歩行音間隔値を変更するのではありません。それだとタイマーがゼロになる寸前でインベーダーが死ぬと新たなタイマー値がロードされ、歩行音間隔が約2倍の長さとなる不具合が起きる可能性があります。

歩行音が隊と同期はしていても不快な音にならないように、最小を5にするだけで良いような気もしますが。

ブログ記事で実装完了しました。


左矢前のブログ 次のブログ右矢

RISC-Vの調査 (9)

posted by sakurai on June 22, 2020 #280

ソースの解読

ここからはソースを読んで行きます。パイプラインプロセッサは、その名のとおり各ステージがパイプラインとして動作するもので、つまりステージ毎にステージを構成するレジスタでできています。逆に、レジスタ以外は全て組み合わせ回路です。これに気づくとソースの理解が速そうです。

難しいのはパイプラインレジスタの更新ロジックで、パイプラインが動作したりストールしたりするのはこのレジスタ更新論理ですが、一方、ステージの実質のロジックは単なる組み合わせ回路です。最も分かりやすいのが演算ステージStage1で、ALU等はファンクション(組み合わせ回路)で記述されます。

それらのファンクションはパイプラインのステージからコールされ、パイプラインにはパイプライ制御のロジックしかなくなります。従って、Fluteの複雑そうな各パイプラインステージは非常に簡単な記述になっています。表280.1に各ステージのbsvで記述された行数を示します。

表280.1 パイプラインステージとその行数
ファイル名 行数
CPU_StageF.bsv 170
CPU_StageD.bsv 153
CPU_Stage1.bsv 336
CPU_Stage2.bsv 627
CPU_Stage3.bsv 245


左矢前のブログ 次のブログ右矢

RISC-Vの調査 (8)

posted by sakurai on June 19, 2020 #279

ループにおける効率調査

前稿のプログラムは命令試験プログラムだったので、キャッシュの効果が出ていませんでした。そのため、ここではループにおける効率の向上を調査します。合わせてgccの効率も調べます。

int main() {
  for (int j = 0; j < 100; j++) {
    for (int i = 0; i <= 9; i++) {
      *UART0_ADDR = '0'+i;
    }
    *UART0_ADDR = '\n';
  }

このようなループの関数を作成し、gccにより-O, -O2, -O3でコンパイルします。まず+v1で実行命令数を確認すれば、それぞれ、5617、5517、2422実行命令数となりました。

次に+v2でパイプラインの状況を確認します。

表279.1 ステージ毎のパイプラインの状態(-O)
状態 StageF StageD Stage1 Stage2 Stage3
BUSY 91 0 1,150 2,251 0
EMPTY 106 198 178 1,410 3,664
PIPE処理 9,187 9,186 8,056 5,723 5,720
合計 9,384

表279.2 ステージ毎のパイプラインの状態(-O2)
状態 StageF StageD Stage1 Stage2 Stage3
BUSY 88 0 23 2,259 0
EMPTY 106 172 170 284 2,544
PIPE処理 7,972 7,994 7,973 5,623 5,622
合計 8,166

表279.3 ステージ毎のパイプラインの状態(-O3)
状態 StageF StageD Stage1 Stage2 Stage3
BUSY 152 0 1,023 2,264 0
EMPTY 4 131 131 1,138 3,403
PIPE処理 5,672 5,697 4,674 2,426 2,425
合計 5,828

これらの表の合計欄がマシンサイクルを意味しています。従って、最適化度合いに対するCPIは、-O, -O2, -O3のそれぞれで1.67、1.48、2.41となりました。

表279.3の-O3においてはインナーループを展開しているため、命令キャッシュミスが若干増加しており、デコードステージ以下のパイプラインバブルが発生していますが、命令数をぐっと少なくしてマシンサイクルを縮めています。いずれも表264.1と比較すると、ループであるため命令キャッシュミスによるパイプラインストールがかなり少なくなっており、効率が向上しています。


左矢前のブログ 次のブログ右矢


ページ: