Interface(1)

Interfaceはverilogからsystemverilogへの拡張の中で、設計者にとってもっとも重要なものの一つだ。この機能により、モジュール間の接続が大幅に簡潔に行え、しかもミスの発生を抑えることができる。

本稿でのサンプルプログラムは、Quartus IIで合成できることを確認しているが、論理合成での対応状況はツールによって異なるようだ。interfaceは、複数のファイルやモジュールで共用されることが多く、ツールが非対応で修正が入った場合に影響が大きい。使う機能に関して、サンプルコードが合成できることを事前に確認しておくことをお勧めする。

基本的な使い方

モジュールのポートにinterfaceを使用することで、モジュール間の接続の記述量を大幅に減らすことができる。

interfaceは、複数のregやwire宣言を一つにまとめて接続する、構造体のようなものだ。

interfaceはインスタンス化してモジュールのポートに接続する。下の例では、interface myIfをmifというインスタンス名でインスタンス化し、各モジュールに接続している。これにより、個々のwireやlogic等を一つ一つ記述する必要がなくなる。

module TopModule(input wire ck);

myIf mif;

module ModuleA(.mif(mif), .ck(ck));

module ModuleB(.mif(mif), .ck(ck));

endmodule

interfaceの定義は以下のように行う。以下ではporta, portb, potcというレジスタ、ロジック、ワイヤを含めている。

reg/wireの区別は、接続先のモジュールでの値の代入方法で選択する。alwaysによって値が代入されるならreg、assignによって継続代入されるならwireを選択する。logicならどちらの状況でも使用できる。

interface myIf;

reg porta;

logic portb;

wire portc;

endinterface

接続先のモジュールでは、ポートの定義は以下のように行う。portの記述においては、interface自体にはinput/outputの属性はないので、指定は不要だ(後述するが、interfaceのメンバにはmodportによりinput/outputを指定することもできる)。また、接続先のモジュールでインタフェース内の各信号にアクセスするには。「ポート名.信号名」といった形で行う。

module ModuleA(myIf mif, input wire ck);

...

assign mif.portb = mif.porta;

...

endmodule

ポートには、使用するinterfaceの種類を指定しないで、interfaceのハンドルだけを宣言することもできる。これをgeneric interfaceという。どのinterfaceが接続されるかは、親モジュール委譲するということだ。ただ、どちらにせよinterface内の信号にアクセスするためには、使用するinterfaceを特定しなければならないため、あまり利点が無いような気がするのだが。。。一応、他人のコードを読んだ時にあわてないよう、知識としては覚えておいたほうがいいだろう。

module ModuleA(interface mif, input wire ck);

...

assign mif.portb = mif.porta;

...

endmodule

それでは、実際に動くコードを見てみよう。

以下の例は、SenderとReceiverというモジュール間をSrIfというinterfaceで接続している。

module test1();

reg clk, rst;

wire [7:0] finalData;

top dut(.finalData(finalData), .clk(clk), .rst(rst));

//Clock and Reset Generator

initial begin

clk = '0; rst = '1;

#10 rst = '0;

forever begin

#10 clk = ~clk;

end

end

//Monitor signal

always @(finalData) begin

$display("finalData = %x", finalData);

end

initial begin

#300 $finish();

end

endmodule

interface SrIf;

reg [7:0] rawData;

reg rawDataEnable;

reg [7:0] processedData;

reg processedDataEnable;

endinterface

module top(

output wire [7:0] finalData,

input wire clk, rst);

//Instantiate Interface

SrIf srif();

//Instantiate modules

Sender senderIns(.finalData(finalData), .srif(srif),

.clk(clk), .rst(rst));

Receiver receiverIns(.srif(srif), .clk(clk), .rst(rst));

endmodule

module Sender(output reg [7:0] finalData, SrIf srif,

input wire clk, rst);

always @(posedge clk or posedge rst) begin

if (rst) begin

srif.rawData <= '0;

srif.rawDataEnable <= '0;

end else begin

srif.rawData <= srif.rawData + 1'b1;

srif.rawDataEnable <= srif.rawData[0];

end

end

always @(posedge clk or posedge rst) begin

if (rst) begin

finalData <= '0;

end else if (srif.processedDataEnable) begin

finalData <= srif.processedData;

end

end

endmodule

module Receiver(SrIf srif, input clk, rst);

always @(posedge clk or posedge rst) begin

if (rst) begin

srif.processedData <= '0;

srif.processedDataEnable <= '0;

end else begin

if (srif.rawDataEnable) begin

srif.processedData <= srif.rawData + 8'h10;

srif.processedDataEnable <= 1'b1;

end else begin

srif.processedDataEnable <= '0;

end

end

end

endmodule

実行結果は以下の通り。

# 6.5e

# vsim -do {run -all; quit} -c test1

# Loading sv_std.std

# Loading work.test1

# Loading work.top

# Loading work.SrIf

# Loading work.Sender

# Loading work.Receiver

# run -all

# finalData = 00

# finalData = 12

# finalData = 14

# finalData = 16

# finalData = 18

# finalData = 1a

# finalData = 1c

ところで、interfaceの中にreg宣言があり、interface自体はtopの中で宣言されているが、これらinterfaceの中のregが、どの階層の中でFFとして合成されるかを確認しておく。特にフロアプランの階層の区切りでは、意図した部分でFFで切れていないとレイアウトに大きな問題を引き起こしてしまう。

LRMは文法と動作の規定のみで、合成結果については言及していない。論理合成ツールとしては、どのように解釈してもいいわけだ。

Qualtus IIでのtopの合成結果は以下の通り。SenderとReceiverのほかinterfaceの実体であるSrIfもインスタンス化されている(中身は空になっている)。

Sender側の合成結果は以下の通り。interfaceの中のregのうち、データを代入しているrowDataとrawDataEnableの2つがFFとして合成されていることがわかる。

Receiver側も同様に、代入を行っているprocessedDataとprocessedDataEnableがFFとして合成される。

以上のように、interfaceの中のregは、代入を行っている側のモジュールに組み入れられる。他のツールでも、私が確認した限りでは同様の結果となっている。

Port

上記の例では、clkとrstは別ポートとして宣言し、interfaceの中には含めなかった。これらの信号は、上位モジュールからくる信号が接続されるため、SenderとReceiverの親モジュールであるtopで接続する必要があったためだ。

このような信号は、intefaceの入力ポートとして定義することが可能だ。

interfaceの定義は以下のように変更になる。

interface SrIf(input wire clk, rst);

reg [7:0] rawData;

reg rawDataEnable;

reg [7:0] processedData;

reg processedDataEnable;

endinterface

topでは、interfaceの入力ポートにclkとrstを接続する

module top(

output wire [7:0] finalData,

input wire clk, rst);

//Instantiate Interface

SrIf srif(.clk(clk), .rst(rst));

//Instantiate modules

Sender senderIns(.finalData(finalData), .srif(srif));

Receiver receiverIns(.srif(srif));

endmodule

各モジュールでは、他の信号と同じようにsrif.clkでアクセスできる。

module Sender(output reg [7:0] finalData, SrIf srif);

always @(posedge srif.clk or posedge srif.rst) begin

if (srif.rst) begin

srif.rawData <= '0;

srif.rawDataEnable <= '0;

end else begin

srif.rawData <= srif.rawData + 1'b1;

srif.rawDataEnable <= srif.rawData[0];

end

end

...

Modport

上記の例では、interfaceの各要素には入出力の方向を定義していなかったが、実際にはモジュール毎に入出力の方向が存在する。上記例では、senderではrowDataとrowDataEnableが出力でprocessedDataとprocessedDataEnableが入力、receiverではその逆になる。

このようなinterfaceに対する、module毎の事情の違いに対応するのがmodportだ。interfaceの定義内で、modportによって使用する要素と入出力の方向を定義する。

これにより、誤接続のない、より安全なモジュール間の信号接続が可能になる。

早速上記例にmodportを追加したものを見てみよう。interfaceの定義は以下のようになる。例では、全てのregとwireをmodportに含めているが、使用しるものだけ記述すればいい。また、modport名はregやwire等の名前と同じにしてはならない。例だと、modportにrawData等の名前をつけることはできないということだ。

interface SrIf(input wire clk, rst);

reg [7:0] rawData;

reg rawDataEnable;

reg [7:0] processedData;

reg processedDataEnable;

modport sender(

output rawData, rawDataEnable,

input processedData, processedDataEnable,

input clk, rst);

modport receiver(

output processedData, processedDataEnable,

input rawData, rawDataEnable,

input clk, rst);

endinterface

接続先のモジュール側では、「インターフェース名.modport名」がポートの宣言になる。例を以下に示す。

module Sender(output reg [7:0] finalData, SrIf.sender srif);

これで、上記の入出力の定義に違反した操作はエラーとなる。

たとえば、Sender内で、入力であるprocessedDataに対して代入を行おうとするとエラーが表示される。

モジュール間の接続に関しては、特に変更点は無い。

module top(

output wire [7:0] finalData,

input wire clk, rst);

//Instantiate Interface

SrIf srif(.clk(clk), .rst(rst));

//Instantiate modules

Sender senderIns(.finalData(finalData), .srif(srif));

Receiver receiverIns(.srif(srif));

endmodule

ただし、interfaceのmodportを明示的に接続することも可能だ。

module top(

output wire [7:0] finalData,

input wire clk, rst);

//Instantiate Interface

SrIf srif(.clk(clk), .rst(rst));

//Instantiate modules

Sender senderIns(.finalData(finalData), .srif(srif.sender));

Receiver receiverIns(.srif(srif.receiver));

endmodule

Modport expressions

実際にinterfaceの使用場面では、接続する信号のビット幅が異なることが考えられる。たとえば、アドレス空間が32Bitのバスでも、そのバスに接続するコンポーネントは20Bitのアドレスしか入力されず、上位ビットのデコード結果が、セレクト信号として入力されるといった具合だ。

このような場合、modport expressionを使う。modportの信号に別名をつける機能だ。上記の例だと、20Bitのアドレスに対して別の名前をつければ解決する。

上記のサンプルプログラムのデータ幅をmodportで4ビットに変更してみよう。

interface SrIf(input wire clk, rst);

reg [7:0] rawData;

reg rawDataEnable;

reg [7:0] processedData;

reg processedDataEnable;

modport sender(

output .rawData(rawData[3:0]), rawDataEnable,

input .processedData(processedData[3:0]), processedDataEnable,

input clk, rst);

modport receiver(

output .processedData(processedData[3:0]), processedDataEnable,

input .rawData(rawData[3:0]), rawDataEnable,

input clk, rst);

endinterface

上記例ではビットの選択を行ったが、ビットの連結や別名への変更も可能だ。

output .newsig(oldsig[3:0]),

input .newsig2({oldsig[3], oldsig[0]}),

といった具合になる。

parameter

interfaceはパラメータをとることも可能だ。書き方は、moduleの時のパラメータと同様だ。上記例をパラメータを使うように変更してみよう。

(注)LRMの例ではparameterというキーワードが使われておらず、#(DWIDTH = 4)のような記述になっている。BNFから判断するとこれはミスだと思われる。ModelSimはこのような記述も許すが、Quartusはエラーとして処理している。

interface SrIf #(parameter DWIDTH = 4) (input wire clk, rst);

reg [DWIDTH - 1:0] rawData;

reg rawDataEnable;

reg [DWIDTH - 1:0] processedData;

reg processedDataEnable;

modport sender(

output rawData, rawDataEnable,

input processedData, processedDataEnable,

input clk, rst);

modport receiver(

output processedData, processedDataEnable,

input rawData, rawDataEnable,

input clk, rst);

endinterface

インスタンス化する時の記法もmoduleと変わらない。

//Instantiate Interface

SrIf #(.DWIDTH(8)) srif(.clk(clk), .rst(rst));