Verilog-HDL 文法(7):シミュレーション記述(3)
2015/09/27
[CategoryTop] [Prev] [Next]
[目次]・シミュレーション記述例(3)
     + taskとは何か:引数についての注意
     + すでに使っています:システムタスク
     + taskの記述例


●シミュレーション記述例(3)

 ◆taskとは何か:引数についての注意

  ・前回はevent文を扱いましたが、今回はtask文を説明しようと思います。task文
   の特徴は「引数」を使用できる点にあります。

  ・この「引数」を使えることから、taskは「プログラムのサブルーチンに相当する
   機能」と説明されることが多いです。この場合しっくりこないのが
     あれ? moduleでも引数使うけど....?
   という点ではないでしょうか。

  ・これは、引数の扱い方に起因しています。通常のプログラミング言語で、関数や
   サブルーチンの引数と言えば
     値渡し(call-by-value)
   であるのが普通です。値渡しでは、渡されたサブルーチン側で値がコピーされ、
   渡し元の値とは独立になります。

  ・しかしVerilog HDLは回路を記述する言語です。wireやregといった物理的要素が
   勝手にコピーされるというのはありえません。つまりmodule等で使用している引
   数は、プログラミング言語で言うところの
     参照渡し(call-by-reference)
   なのです。

  ・これより引数に対する常識
    プログラミング言語(C等)では  : 引数と言えば : 値渡し
    回路記述言語(Verilog HDL)では : 引数と言えば : 参照(実体)渡し
   になります。

  ・これを頭に置いてもらった状態でtaskを説明すると、改めて
    プログラミング言語で言うところのサブルーチンである
   と言うことができます。そうです、taskの引数は「値渡し」になります。

  ・逆に言うと「値渡し」という現実世界を無視した動作を行うため、回路記述その
   ものに用いることは無く、あくまで検証対象回路の入出力データの処理や、外部
   回路のモデル記述に使用します。


 ◆すでに使っています:システムタスク

  ・実を言うと、taskはすでに使っています。今まで観測に使っていた
     $display, $monitor
   等がそうです。これはシステムタスクと呼ばれ、基本的にはVerilogコンパイラ
   に依存したセットになっています。

  ・ただし、ファイル入出力のシステムタスク等は大体共通化されています。
     $readmemh/$writemeh : 16進データread/write
     $readmemb/$readmemh : 2進データread/write

    (例)$readmemh("File名", レジスタ配列名, 開始アドレス, 終了アドレス);
       // 開始アドレス, 終了アドレスは省略可能

  ・その他のシステムタスクについては必要に応じてマニュアルやwebを探してみて
   下さい。


 ◆taskの記述例

  ・taskを使うのは、主に検証の場におけるモデル記述であることを先ほど説明しま
   した。ここでは例としてメモリコントローラからデータを与える場面を想定して
   みましょう。

   [List.1 taskの記述例]
  ┌───────────────────────────────────┐
   `timescale 1ns/1ns
   
   module top_verify; // テストベンチ
     parameter CYC = 100;
     
     reg     CLK; // クロック
     always #(CYC/2) CLK = ~CLK;

     reg [15:0] DI; // Input Data
     reg  [2:0] WA; // Write Address
     reg     WE; // Write Enable
     wire [15:0] DO; // Output Data
     reg  [2:0] RA; // Read Address
     reg     RE; // Read Enable

     mod_checked mod_checked ( // 検証回路のインスタンス
       .CLK(CLK), .DI(DI), .WA(WA), .WE(WE), .DO(DO), .RA(RA), .RE(RE)
     );

     // -----------------------------------------------------------------
     task write_task; // writeタスク (addr,data)
       input [2:0] addr_task;
       input [15:0] data_task;
       begin
         #(CYC) WE = 1'b0;
         #(CYC) WA = addr_task;
             DI = data_task;
         #(CYC) WE = 1'b1;
         #(CYC/2) $display("WData[%3b]=%8b",WA,DI);
         #(CYC/2) WE = 1'b0;
         #(CYC);
       end
     endtask

     task read_task; // readタスク (addr)
       input [2:0] addr_task;
       begin
         #(CYC) RE = 1'b0;
         #(CYC) RA = addr_task;
         #(CYC) RE = 1'b1;
         #(CYC/2) $display("Data[%3b]=%16b,RE=%b", RA,DO,RE);
         #(CYC/2) RE = 1'b0;
         #(CYC);
       end
     endtask
     // -----------------------------------------------------------------

     initial
       begin
         #(0) CLK = 1'b0; // クロック初期値決定
         #(CYC);
         #(CYC)
           write_task(3'b000, 16'b0000_0001_0000_0001); // write
           write_task(3'b010, 16'b0101_0101_0101_0101); // write
           write_task(3'b100, 16'b1111_1111_1111_1111); // write
           read_task(3'b000); // read
           read_task(3'b010); // read
           read_task(3'b100); // read
         #(CYC) $finish;
       end

   endmodule

   // ---------------------------------------------------------------------
   module mod_checked ( // チェック対象回路 インチキメモリ
     CLK,
     DI,WA,WE,
     DO,RA,RE
   );
     input     CLK;
     input [15:0] DI; // Write data
     input [2:0] WA; // Write address
     input     WE; // Write enable
     
     output [15:0] DO; // Read data
     input [2:0] RA; // Read address
     input     RE; // Read enable

     reg  [15:0] mem [7:0]; // Memory Cell

     always @(posedge CLK) begin
       if (WE)
         mem[WA] <= #(1) DI;
     end

     assign DO = (RE)? mem[RA] : 16'hx;
   endmodule
  └───────────────────────────────────┘
  ┌───────────────────────────────────┐
   [実行結果]
   WData[000]=0000000100000001
   WData[010]=0101010101010101
   WData[100]=1111111111111111
   Data[000]=0000000100000001,RE=1
   Data[010]=0101010101010101,RE=1
   Data[100]=1111111111111111,RE=1
  └───────────────────────────────────┘

  ・taskはfunctionと同じようにmodule中で記述します。今回の例では入力のみ引数
   を与えていますが、出力も与えることができます。この例だけ見ると、taskはテ
   ストベンチに記述しなければならないように見えますが、そうではありません。

  ・その意味ですが、taskを包んだmoduleを構成することで
     複数サイクルに渡る一連の動作モデル記述
   が可能になることを意味しています。これがポイントです。

  ┌───────────────────────────────────┐
   module blockA(信号リスト);
     信号定義;
   
     task task_A
       タスク定義
     endtask

     always @(入力信号リスト) begin
       task呼び出し(引数);
     end
   endmodule
  └───────────────────────────────────┘

  ・またtaskから他のtaskを呼ぶこともできるので、構造化記述も可能です。


●最後に

  ・さてVerilog HDLの基本文法としては今回最後になりましたが、いかがでしょ
   うか。RTL記述の場合、ついその後のプロセスを忘れて技巧に走りがちですが、
   最も重要なのは
     平易で、他の人間が(検証エンジニアや、他の協力者)見てわかりやすい
     バックエンドによるチップ化が容易
     テストパターン作成時に、特記/特例が無い
   等、むしろその他の面で考えることの方が多くなる傾向にあります。

  ・正直言って、RTLに自信があり、凝った記述が多いものほど、検証/量産から見る
   とひどいRTLになっている傾向があります。(私自身何度もひどい目にあいました。
   おかげ様ですっかりIP不信です。)

  ・RTLは抽象度が高いので、何となくソフトウェアを書いているような気になるこ
   とも多いですが、我々の作ったものはハードウェアになるということ...物理的
   に実現可能なことが絶対条件
です。

  ・例えばモデルの動作記述で、レジスタの初期値をinitial文で与えたとします。
   この初期値が与えられることを、どうやって保証しますか? 回路は常に規定シー
   ケンスで動作開始するわけではありません。テストモード時は? 評価/量産時は?
   周辺回路の誤動作時は?

  ・デジタル回路設計の記述そのものは、アナログに比べれば難しくありません。し
   かし、扱う機能範囲が広い分、システムとして考慮することが莫大になる。これ
   がデジタル回路設計の難しさ & 面白さ
なのです。

  ・ここで書いたことは、あくまでデジタル回路設計の入口です。デジタル回路設計
   の本当に面白いところはもっと奥にあります。これは企業Know-Howの色が濃く、
   一般の書籍ではまず見ることのできない内容がほとんどです。このコンテンツが
   その領域へ行くまでの導入路となれば幸いです。


[Revision Table]
 |Revision |Date    |Comments
 |----------|-----------|-----------------------------------------------------
 |1.00   |2008-05-01 |初版
 |1.01   |2008-05-02 |まとめ追加
[end]

Copyright(C) 2015 Altmo