[SystemVerilog]合成可能なSystemVerilogコードを書いてみる。

こんにちは、Verilogではあまり設計しない@tethys_seesaaです。

#はじめに。
今年、Sutherland HDL社が、米国シリコンバレーでのSynopsys Users Group Conference以下のような発表を行ったようです。（PDF）。
http://www.sutherland-hdl.com/papers/2013-SNUG-SV_Synthesizable-SystemVerilog_paper.pdf

タイトルは、「Synthesizing SystemVerilog - **Busting the Myth that SystemVerilog is only for Verification** -」抄訳すると、SystemVerilogは検証専用言語じゃない！といったところでしょうか。Design CompilerとSymplifyでSystemVerilogで書かれた記述の論理合成に挑戦したようです。
結論から言うと、 **微妙な結果** という印象ですが、参考になる記述例もあり、「Wish List and Recommendations」辺りは、ツールベンダーがサポートしてくれるといいなあとか思いました。

ということで、ここに触発されて、SystemVerilogの機能を使って（たぶん）論理合成可能なコードを書いてみることにしました。
SystemVerilogのLRMには合成可能なサブセットは明示的に規定されておりません。なので、どこまでサポートするかはツールベンダーに任されており、 **ツールベンダーのお客さんが支払うゼニの大きさ** 次第ではサポートが広がるのかもしれませんね。

#トライアル
日本語のサイト等を見ると、taskってテストしか書かれていないような気がしました。先に書いた通り、自分はデザインするときはVHDLがメインなのでVerilogの方はあまり詳しくありません。デザインでもtaskをバリバリ使う人がいたら申し訳ありません。
ともあれ、taskは値渡しだし、複数入出力でdelayを考慮しなければ組み合わせ回路が作れるだろうと思って書いたのがこのコードです。

```verilog:r.sv
typedef logic [7:0] data_t;

module r(
  input  logic       rst_n, clk,
  input  logic       rw,
  input  logic [7:0] addr,
  input  data_t      wdata,
  output data_t      rdata
  );

  data_t [255:0] reg_array;

  task write_reg(logic[7:0] addr, wdata);
    reg_array[addr] <= wdata;
  endtask

  function logic [7:0] read_reg(logic [7:0] addr);
    return reg_array[addr];
  endfunction

  always_ff @(negedge rst_n, posedge clk)
    if(!rst_n) begin
      reg_array <= '0;
      rdata     <= '0;
    end
    else if(rw)
      write_reg(addr, wdata);
    else
      rdata <= read_reg(addr);

endmodule
```

シングルポートSRAMみたいな感じの8bit x 256wordのレジスタを持つコードですね。コレは合成可能でした。
ちなみに、冒頭で紹介したSutherland HDL社のホワイトペーパーにあるオヌヌメに、 **task使うよりも、function void** を使え！というのがありました。

#だったら、
interfaceにtaskやfunctionを入れてマスター/スレーブちっくなことをしてみようと思いました。本格的に書くとブリッジとかアービターとか必要だから、ここではマスター/スレーブは一個ずつ、マスターには信号がつながって、そのままスレーブへ突き抜けるようなコードを書いてみました。
仕様は頭の中で想像して下さい。 たぶん、 **劣化したAPBバス** と思われます。

まずは各モジュールで共通するコード。$clog2とか使っていいのだろうか…。

```verilog:common.sv
parameter DEPTH = 256;
parameter WITDH = $clog2(DEPTH);

typedef logic [WITDH-1:0] addr_t;
typedef logic [WITDH-1:0] data_t;

typedef bit   [$bits(addr_t)-1 : 0] addr_bits;
typedef bit   [$bits(data_t)-1 : 0] data_bits;
```

こちらはトップ階層です。後述するinterfaceとマスター、スレーブモジュールをインスタンスします。

```verilog:core.sv
module core #(
)(
  input  logic  rst_n, clk,
  input  logic  SEL, RW,
  input  addr_t ADDR,
  input  data_t WDAT,
  output logic  EN,
  output data_t RDAT
);

  bus_t mod_bus();

  master mst(.bus_mst(mod_bus), .*);
  slave  slv(.bus_slv(mod_bus), .*);

endmodule
```
次にinterfaceとメソッド、modport指定です。若干無理矢理感があるかも…。

```verilog:bus_t.sv
interface bus_t ();
  logic     sel;
  logic     en;
  logic     rw;
  addr_t    addr;
  data_t    rdat;
  data_t    wdat;

// Master methods
  task automatic mst_rst();
    sel   <= '0;
    rw    <= '0;
    addr  <= '0;
    wdat  <= '0;
  endtask

  task automatic mst_write(addr_t i_addr, data_t i_data);
    sel   <= '1;
    rw    <= '1;
    sel   <= '1;
    addr  <= i_addr;
    wdat  <= i_data;
  endtask

  task automatic mst_read(addr_t i_addr);
    sel   <= '1;
    rw    <= '0;
    addr  <= i_addr;
  endtask

  function logic get_en();
    return en;
  endfunction

  function data_t get_rdata();
    return rdat;
  endfunction

// Slave methods
  task automatic slv_rst();
    en    <= '0;
    rdat  <= '0;
  endtask

  task automatic put_rdata(data_t i_data);
    en    <= '1;
    rdat  <= i_data;
  endtask

  function logic get_sel();
    return sel;
  endfunction

  function logic get_rw();
    return rw;
  endfunction

// modport
  modport master (
    import mst_rst, mst_write, mst_read,
    import get_en, get_rdata,
    output sel, rw, addr, wdat,
    input  en, rdat
    );

  modport slave (
    import slv_rst, put_rdata,
    import get_sel, get_rw,
    input  sel, rw, addr, wdat,
    output en, rdat
    );

endinterface
```

マスターモジュールはトップ階層からの信号を受け取って、スレーブモジュールとinterfaceで通信します。

```verilog:master.sv
module master #(
  )(
  input  logic   rst_n, clk,
  input  logic   SEL, RW,
  input  addr_t  ADDR,
  input  data_t  WDAT,
  output logic   EN,
  output data_t  RDAT,
  bus_t.master   bus_mst
  );

  task out_rst();
    EN   <= '0;
    RDAT <= '0;
  endtask

  task push_rdata (data_t data);
    EN   <= bus_mst.get_en();
    RDAT <= data;
  endtask

  always_ff @(negedge rst_n, posedge clk)
    if(!rst_n) begin
      bus_mst.mst_rst();
      out_rst();
    end
    else begin
      if(SEL && RW)
        bus_mst.mst_write(ADDR, WDAT);
      else if(SEL && !RW) begin
        bus_mst.mst_read(ADDR);
        if(bus_mst.get_en())
          push_rdata(bus_mst.get_rdata());
      end
      else begin
        bus_mst.mst_rst();
        out_rst();
      end
    end

endmodule

```
スレーブモジュールは先に書いたようなレジスタをもち、マスターからのselを受け取って、読み書きします。

```verilog:slave.sv
module slave #(
  )(
  input logic  rst_n, clk,
  bus_t.slave  bus_slv
  );

  data_t [DEPTH-1:0] reg_array;

  task write_reg (addr_t addr, data_t data);
    reg_array[addr] <= data;
  endtask

  function data_t read_reg (addr_t addr);
    return reg_array[addr];
  endfunction

  always_ff @(negedge rst_n, posedge clk)
    if(!rst_n) begin
      bus_slv.slv_rst();
      reg_array   <= '0;
    end
    else begin
      if(bus_slv.get_sel() && bus_slv.get_rw())
        write_reg(bus_slv.addr, bus_slv.wdat);
      else if(bus_slv.get_sel() && !bus_slv.get_rw())
        bus_slv.put_rdata(read_reg(bus_slv.addr));
    end

endmodule
```

一応テストも書いておきます。Simを流すとエラーにはなりません。
これは **DUTに都合のいいテストコード** ですね。
もしかすると、「's」部分はツールやバージョンによって未サポートの可能性が高いです。

```verilog:tb.sv
`timescale 1ns/1ps

module tb();

  parameter CYC = 10000;

  logic rst_n, clk;
  logic  SEL, RW, EN;
  addr_t ADDR;
  data_t WDAT, RDAT;
  data_t dbuf [$];

  core dut(.*);

  task automatic rst_clk();
    rst_n = '0;
    clk   = '0;
    fork
      #15 rst_n = '1;
      forever  #5 clk = ~ clk;
    join
  endtask

  task automatic cyc_wait(int n);
    repeat(n) @(posedge clk);
  endtask

  task automatic silent(int n);
    RW   = '1;
    SEL  = '0;
    ADDR = '0;
    WDAT = '0;
    cyc_wait(n);
  endtask

  task automatic input_data(int i_data);
    RW   = '1;
    SEL  = '1;
    ADDR = addr_bits's(i_data);
    WDAT = data_bits's(~(i_data));
    dbuf.push_back(data_bits's(~(i_data)));
    cyc_wait(1);
  endtask

  task automatic output_data(int i_addr);
    RW   = '0;
    SEL  = '1;
    ADDR = addr_bits's(i_addr);
    cyc_wait(1);
  endtask

  task automatic in_out();
    int i=0;
    forever begin
      input_data(i);
      silent(2);
      output_data(i);
      silent(2);
      i++;
    end
  endtask

  task automatic data_compare();
    data_t tmp;
    forever begin
      @(posedge EN);
      tmp = dbuf.pop_front();
      if(RDAT!=tmp)
        $display("%t compare error RDAT: %4x WDAT: %4x", $stime, RDAT, tmp);
     end
  endtask

  task automatic test_ctrl();
    cyc_wait(CYC);
    $finish(2);
  endtask

  initial begin
    fork
      rst_clk();
    join_none
    silent(10);
    fork
      data_compare();
      in_out();
      test_ctrl();
    join
  end

endmodule
```

#で、論理合成可能か？
…わかりません。 **たぶん** これらのコードは論理合成可能でしょう。
$clog2辺りがマズイような気がしますが、2のべき乗を入れているから、ツール側で8bitでうまく展開してくれるんじゃないですかねー。
誰かISEやVivadoでやってみて下さい。

#おわりに。
今回interfaceを用いたのは、バスシステムならばエージェントが多いほど接続記述を書くのが楽になりますし、簡単なプロトコルならば、中にメソッドを書いて比較的容易に変更が可能と考えたからです。
とはいうものの、Simでロジック的にオッケーだとしても、論理合成できあがったネットリストのQoSが高いのか低いのか、P&R可能でタイミングをMETできるか、そういったところも配慮してコーディングをしなくてはいけません。
この辺り、ツールのデキが悪いのか自分のコードが悪いのかは常に頭を悩ませる問題です。

まあでもCフレーバーのコーディングもSystemVerilogならある程度なら可能ということで、論理合成ツールがサポートする論理合成が可能なSystemVerilogの範囲が広がれば、 **HLSなんぞ駆逐** うわなにするやめ（ｒｙ