modportで定義した信号にclockingを適用する

interfaceではmodportを使うことで信号の向きを定義できるが、このmodportにclockingを適用したい場合は、下記のように書けばよい。

interface bus_if(input clk);
  〜
    // clockingブロックで信号の向きと遅延を設定
    default clocking cb @(posedge clk);
        default input #1ns output #1ns;
        
        output   cs, rw, addr, wdata;
        input    rdata;
    endclocking

    modport master(clocking cb); // ここではclockingだけ定義
endinterface

ここまではよいのだけど、このmodportで定義した信号を下記のような別モジュール内(test)でドライブしようとしたら、またはまってしまった。

bus_if bus(clk); // interfaceのインスタンス
test test(.bus_m(bus.master), .clk(clk));

testモジュール内ではどのように信号に値を入力すればよいか?下記のように書けば行けそうなんだけど、これだと信号が見つからないと怒られた。

module test(bus_if bus_m, input clk);
    task write(input [31:0] ad, dt);
        @(posedge clk);
        bus_m.cs    <= 1'b1;
        bus_m.rw    <= 1'b1;
  〜

正解はというと、"bus_m.cb.○○"のようにclockingブロックのブロック名を付ければよいだけ。こんなんで結構悩んだような気が・・・

module test(bus_if bus_m, input clk);
    task write(input [31:0] ad, dt);
        @(posedge clk);
        bus_m.cb.cs    <= 1'b1;
        bus_m.cb.rw    <= 1'b1;
  〜

以下、参考までに。

interface bus_if(input clk);
    logic        cs;
    logic        rw;
    logic [31:0] addr;
    logic [31:0] wdata;
    logic [31:0] rdata;

    // clockingブロックで信号の向きと遅延を設定
    default clocking cb @(posedge clk);
        default input #1ns output #1ns;
        
        output   cs, rw, addr, wdata;
        input    rdata;
    endclocking

    modport master(clocking cb);
endinterface
`timescale 1ns/1ns
// テストベンチトップ
module tb_top;
    logic clk;

    bus_if bus(clk); // interfaceのインスタンス
    test test(.bus_l(bus.master), .clk(clk)); // ここではclockingだけ定義
  
    initial begin // クロック
        clk <= 1'b0;
        forever #5 clk = ~clk;
    end

    initial begin
        bus.cs    <= 1'b0;
        bus.rw    <= 1'b0;
        bus.addr  <= 32'h0;
        bus.wdata <= 32'h0;
        bus.rdata <= 32'h0;
        
        #100;
        test.write(32'h1234, 32'h5678);
        #100;
        $finish;
    end
endmodule
module test(bus_if bus_l, input clk);
    // "cb."付けないとエラー
    task write(input [31:0] ad, dt);
        @(posedge clk);
        bus_l.cb.cs    <= 1'b1;
        bus_l.cb.rw    <= 1'b1;
        bus_l.cb.addr  <= ad;
        bus_l.cb.wdata <= dt;
        @(posedge clk);        
        bus_l.cb.cs    <= 1'b0;
        bus_l.cb.rw    <= 1'b0;
        bus_l.cb.addr  <= 32'h0;
        bus_l.cb.wdata <= 32'h0;
    endtask
endmodule

interface内のtaskでclockingを使う

interface内ではtaskを定義できる。

interface bus_if(input clk);
    logic        cs;
    logic [31:0] addr;
    logic [31:0] data;
    
    task write(input [31:0] ad, dt);
        @(posedge clk);
        #1;
        cs    = 1'b1;
        addr  = ad;
        data  = dt;

        @(posedge clk);
        #1;
        cs      = 1'b0;
        addr    = 32'h0;
        data    = 32'h0;
    endtask
endinterface

上記のtaskでは、遅延をシミュレートするために「@(posede clk);」の後に「#1;」を書いている。
SystemVerilogで追加されたclockingという機能を使うと、この「#1;」を省略できる。

`timescale 1ns/1ns
interface bus_if(input clk);
    logic        cs;
    logic [31:0] addr;
    logic [31:0] data;

    clocking cb @(posedge clk);
        default output #1ns;
        output    cs, addr, data;
    endclocking
    
    task write(input [31:0] ad, dt);
        @(posedge clk);
        cb.cs    <= 1'b1;
        cb.addr  <= ad;
        cb.data  <= dt;

        @(posedge clk);
        cb.cs     <= 1'b0;
        cb.addr   <= 32'h0;
        cb.data   <= 32'h0;
    endtask
endinterface

clockingを使う場合、ブロッキング代入を使うとエラーになってしまう。はじめそのことに気付かなかったせいで、
コンパイルが通らず散々悩んでしまったorz

タスクを実行した波形は下図のようになり、ちゃんと遅延が付いているのが分る。

・テストベンチトップ

`timescale 1ns/1ns
module tb_top;
    logic clk;   
    bus_if bus(clk);

    initial
      forever #5 clk = ~clk;

    initial begin
        clk       <= 0;
        bus.cs    <= 1'b0;
        bus.addr  <= 32'h0;
        bus.data  <= 32'h0;

        #100;
        bus.write(32'h1234, 32'h5678);

        #100;
        $finish;
    end
endmodule

Interfaceを使ってVerilog記述のRTLと接続

Verilogで書いたRTLとSystemVerilogで書いたテスト用モデルをInterfaceを使って繋ぎたい場合は、RTLのインスタンス呼び出し時のポートリストに『インターフェイス名.信号名』と書くことで上手くいった。記述量はあんまり減っていないような気がするけど・・・

Verilogで書いたRTL

module master_rtl(cs, rw, ad, wdt, rdt);
  output cs;
  output rw;
  output [31:0] ad;
  output [31:0] wdt;
  input  [31:0] rdt;endmodule

・SystemVerilogで書いたシミュレーションモデル

module slave_model(interface slave_if);endmodule

・Interfaceの定義

interface bus_if;
  logic cs;
  logic rw;
  logic [31:0] ad;
  logic [31:0] wdt;
  logic [31:0] rdt;

  // slave_modelに接続するポートの向きを宣言
  modport slave(input cs, rw, ad, wdt,
                output rdt);
  /*
  modport master(output cs, rw, ad, wdt,
                 input rdt);
  */
endinterface

・テストベンチのトップ

module bench_top;
  bus_if bus;
  master_rtl master( .cs(bus.cs),
                     .rw(bus.rw),
                     .ad(bus.ad),
                     .wdt(bus.wdt),
                     .rdt(bus.rdt));
  slave_model slave( .slave_if(bus.slave) );
endmodule

ちなみに、Interfaceの定義内でRTL用のmodportを宣言し、RTLのインスタンス呼び出しで『.cs(bus.master.cs)』のように記述するとエラーになった。RTL内とInterface内の両方でポートの向きを宣言しているとダメみたい。

コマンドラインでModelsimを実行する方法

コマンドラインでModelsimを実行する方法をよく忘れるのでメモ。

作業ディレクトリを作成

$ vlib dir

プロジェクト名と作業ディレクトリを関連付ける

$ vmap prj dir_path

コンパイル

vlog test1.v test2.v \
     -f file_list \
     -work prj \              ←プロジェクト名
     -incr \                  ←変更のあったファイルのみコンパイル
     +incdir+include_dir \    ←`includeで読み込むファイルがあるディレクトリを指定
     -v lib_file \            ←ライブラリファイル
     -libext+.v \             ←ライブラリディレクトリ内のコンパイルするファイルを拡張子で指定
     -y lib_dir               ←ライブラリディレクト

-fで読み込むファイルには、コンパイルしたいファイルを相対パスで書けばおk

../file3.v
../../file4.v

最後にシミュレーションを実行

vsim test_top \               ←テストベンチトップ
     -lib prj \               ←プロジェクト名
     -do "run -all;quit"

Verilogでセマフォ

『SystemVerilogによるLSI設計』に組み込みのセマフォを使わずに、中身を自分で定義している箇所があった(294ページ)。これはちょっと書き換えるとVerilogで使えそう。

module Semaphore;
  parameter initial_keys  = 1;
  integer keys            = initial_keys;

  task get;
    input n;
    begin
      wait(keys >= n);
      keys  = keys - n;
    end
  endtask

  task put;
    input n;
      begin
      keys  = keys + n;
    end
  endtask

endmodule

テストベンチ

module test;
  Semaphore sem();

  initial begin

    $display("%t, keys = %d", $time, sem.keys);

    fork      
      begin
        #100;
        sem.get(1);
        $display("%t, keys = %d", $time, sem.keys);
        #100;
        sem.put(1);
      end

      begin
        #100;
        sem.get(1);
        $display("%t, keys = %d", $time, sem.keys);
        #100;
        sem.put(1);
      end
    join

    $finish;
  end
endmodule

modelsimでの実行結果

#                    0, keys =           1
#                  100, keys =           0
#                  200, keys =           0

VerilogオンリーでSystemVerilogが使えないようなプロジェクトだと役に立つ?