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が使えないようなプロジェクトだと役に立つ?