SystemVerilog‎ > ‎

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