こんにちは、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を考慮しなければ組み合わせ回路が作れるだろうと思って書いたのがこのコードです。
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とか使っていいのだろうか…。
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とマスター、スレーブモジュールをインスタンスします。
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指定です。若干無理矢理感があるかも…。
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で通信します。
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を受け取って、読み書きします。
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」部分はツールやバージョンによって未サポートの可能性が高いです。
`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なんぞ駆逐 うわなにするやめ(ry