「diceをverilogに書き換えてみました」の続きになります。FPGAの部屋のmarseeさんが書かれたコードを別の言語に移植する第2弾です。元のコードはこちら(ISE11.1iのチュートリアル1(導入編))です。marseeさんのverilog 2001版はこちら( diceのVerilog版)になります。
LatticeのispLEVERではSystem Verilogが使えます。正確に言うとispLEVERは論理合成を外部ツールであるSynplifyに丸投げしていて、SynplifyではVerilog 2001やSystem Verilogを使えるということになります。ispLEVER自体はVerilog 1995しか分からないのですが、論理合成済みのEDIFファイルを入力としてやることでispLEVERがSystem Verilogを理解できなくても、論理合成の後の配置配線等を行えることになります。
ispLEVERでプロジェクトを作るときにEDIFファイルを入力するようにします。その後Synplifyを起動してEDIFファイルの名前を指定してやります。注意点としてはEDIFファイルのSynplifyのデフォルトの拡張子が.ediでispLEVERの方は.edfになっていることです。
System Verilogにしてみたdiceです。(<は全角です)。
意味もなくSystem Verilog固有の機能を使いまくっています。かなり無理矢理みたいなところもあります。
使ったSystem Verilogの機能です。
- logic。regとかwireは全部logicと書けます。VHDLのsignalみたいです。
- enum。Cのenumみたいなことが書けるようになりました。VHDLのtypeみたいなものです。
- typedef。やはりCの型の再定義であるtypedefが書けます。
- struct。構造体が使えます。構造体にはpackedとunpackの2種類があります。packedにすると、Cのフィールドみたいな使い方ができるようになります。
struct packed { logic x, y; } s;だと、s.x、s.yを個別にアクセスすることもsとして2ビットの変数にアクセスすることもできます。
構造体があることの利点は、詳細を隠すことができる点です。今回で言うと出力を7セグメントのLEDにしてますが、別の出力装置に変更したときに、構造体の中身と7セグメントデコーダの中身だけ書き換えて信号の増減を見かけ上隠すといったことができます。この手の隠蔽の仕組みはソフトではよく使われていて、詳細を隠すことで、後で書き換えやすくしたり、理解しやすくしたりします。
System Verilogにある別の隠蔽の仕組みはinterfaceです。これはCPUのバスみたいなものをパッケージにしたものです。
- インスタンスのパラメータの省略。.a(a)みたく信号名と渡す変数名が一致するときは.*として一気に省略できます。名前が違うものだけ記述することもできます。.b(c)だけ書きます。
- always_ff。フリップフロップにすることを指定したalwaysです。
- always_comb。組み合わせ回路にすることを指定したalwaysです。
- unique case。並列処理できることを指定したcaseです。他にpriority caseがあります。同じくunique ifとかpriority ifなんかもあります。
今回使ったのは、どちらかというと、本質的ではなく見かけの違いだけです。しかし言語というのは実は見かけが一番重要です。要は、言語の良さって、かっこよく書けるかどうかだけです。よく言われる生産性みたいな話でいうと、同じことを記述するのに何行で書けるかが一番重要ということになります。センシティビティリストにちゃんと全ての変数が書かれているかをプログラマがチェックするなんて、はっきり言ってありえません。機械の方が得意なことは機械にやらせた方がいいんです。その意味ではVerilog 1995よりはVerilog 2001の方が好きですし、さらにはSystem Verilogの方が好きです。
ところで、Verilog 2001で、`default_nettype noneを指定したとき、regでないinputとかoutputにinput wireとかoutput wireって書いた方がいいんでしょうか?xst、Veritak、Symplifyなんかはinput wireじゃなく、inputだけでも怒られないんですが、某論理合成ツールではinput wireと書かないと怒られるんだそうです。
論理合成結果は28sliceでした。
ちなみに、ステートマシンをワンホットにするには、以下のようにtypedefを変更するだけでいいです。
論理合成結果は30sliceになりました。今回は元のverilogと違ってstateを直接デコードするようにしたので6入力セレクタができたせいだと思います。LUTが4入力1出力であることを考えると、3ビットの中間コードに落としているmarseeさんはFPGAのことを知り尽くしていると思いました。
非同期リセットを同期リセットに変えると27sliceに減ります。Synplifyは内部的にstateをワンホットに書き換えているので、stateをワンホットにしても同じです。非同期リセットのとき書き換えないのは謎です。
どちらかと言うと、せっかくSystem Verilogを使うんだったらassertionを使わないと意味ないですね。
LatticeのispLEVERではSystem Verilogが使えます。正確に言うとispLEVERは論理合成を外部ツールであるSynplifyに丸投げしていて、SynplifyではVerilog 2001やSystem Verilogを使えるということになります。ispLEVER自体はVerilog 1995しか分からないのですが、論理合成済みのEDIFファイルを入力としてやることでispLEVERがSystem Verilogを理解できなくても、論理合成の後の配置配線等を行えることになります。
ispLEVERでプロジェクトを作るときにEDIFファイルを入力するようにします。その後Synplifyを起動してEDIFファイルの名前を指定してやります。注意点としてはEDIFファイルのSynplifyのデフォルトの拡張子が.ediでispLEVERの方は.edfになっていることです。
System Verilogにしてみたdiceです。(<は全角です)。
// dice_top.vhd `default_nettype none typedef enum logic [2:0] { ST1, ST2, ST3, ST4, ST5, ST6 } STATE; typedef struct packed { logic a_n, b_n, c_n, d_n, e_n, f_n, g_n; } SEVENSEG; module dice_top ( input reset_sw, input clk, input roll, output [3:0] an_n, output a_n, b_n, c_n, d_n, e_n, f_n, g_n, output dp_n); logic roll_sig; // とりあえずなんでもlogic logic roll_ena; STATE state; assign an_n = 4'b1110; assign dp_n = 1'b1; reject_chatter inst_reject_chatter ( .* // パラメータ完全省略 ); dice_state_machine inst_dice_sm ( .*, .roll(roll_sig) // 違う所だけ書くのもあり ); seven_seg_dec inst_seven_seg_dec ( .state(state), // 全パラメータ記述 .ss({a_n, b_n, c_n, d_n, e_n, f_n, g_n}) // packed構造体渡し ); endmodule // reject_chatter.vhd module reject_chatter ( input reset_sw, input clk, input roll, output roll_sig, output logic roll_ena); // parameter frequency_KHz = 1; parameter frequency_KHz = 50000; parameter divided_200Hz = frequency_KHz * 5; logic [17:0] sw_cnt; logic [1:0] roll_cnt; logic roll_node; // 200Hz, 5ms always_ff @(posedge clk, negedge reset_sw) // FF指定 コンマ区切り if(~reset_sw) sw_cnt <= 18'h0; else if(sw_cnt == (divided_200Hz - 1)) sw_cnt <= 18'h0; else sw_cnt <= sw_cnt + 1; always_ff @(posedge clk, negedge reset_sw) if(~reset_sw) roll_node <= 1'b1; else if(sw_cnt == (divided_200Hz - 1)) roll_node <= roll; assign roll_sig = roll_node; // 50Hz, 20ms always_ff @(posedge clk, negedge reset_sw) if(~reset_sw) begin roll_cnt <= 2'b00; roll_ena <= 1'b0; end else if(sw_cnt == (divided_200Hz - 1)) if(roll_cnt == 2'b11) begin roll_cnt <= 2'b00; roll_ena <= 1'b1; end else begin roll_cnt <= roll_cnt + 1; roll_ena <= 1'b0; end else roll_ena <= 1'b0; endmodule // dice_state_machine.vhd module dice_state_machine ( input reset_sw, input clk, input roll, roll_ena, output STATE state); // enum出力 always_ff @(posedge clk, negedge reset_sw) if(~reset_sw) state <= ST1; else if(roll & roll_ena) unique case(state) ST1 : state <= ST2; ST2 : state <= ST3; ST3 : state <= ST4; ST4 : state <= ST5; ST5 : state <= ST6; ST6 : state <= ST1; default : state <= ST1; endcase endmodule // seven_seg_dec.vhd module seven_seg_dec ( input STATE state, output SEVENSEG ss); always_comb // 組み合わせ回路指定 unique case(state) // uniqueなcase ST1: begin ss.a_n = 1'b1; ss.b_n = 1'b0; ss.c_n = 1'b0; ss.d_n = 1'b1; ss.e_n = 1'b1; ss.f_n = 1'b1; ss.g_n = 1'b1; end ST2: ss = 7'b0010010; // こんな書き方もあり ST3: ss = 7'b0_0_0_0_1_1_0; ST4: ss = {1'b1, 1'b0, 1'b0, 1'b1, 1'b1, 1'b0, 1'b0}; ST5: ss = 7'b0100100; ST6: ss = 7'b0100000; default : ss = 7'b1001111; endcase endmodule
意味もなくSystem Verilog固有の機能を使いまくっています。かなり無理矢理みたいなところもあります。
使ったSystem Verilogの機能です。
- logic。regとかwireは全部logicと書けます。VHDLのsignalみたいです。
- enum。Cのenumみたいなことが書けるようになりました。VHDLのtypeみたいなものです。
- typedef。やはりCの型の再定義であるtypedefが書けます。
- struct。構造体が使えます。構造体にはpackedとunpackの2種類があります。packedにすると、Cのフィールドみたいな使い方ができるようになります。
struct packed { logic x, y; } s;だと、s.x、s.yを個別にアクセスすることもsとして2ビットの変数にアクセスすることもできます。
構造体があることの利点は、詳細を隠すことができる点です。今回で言うと出力を7セグメントのLEDにしてますが、別の出力装置に変更したときに、構造体の中身と7セグメントデコーダの中身だけ書き換えて信号の増減を見かけ上隠すといったことができます。この手の隠蔽の仕組みはソフトではよく使われていて、詳細を隠すことで、後で書き換えやすくしたり、理解しやすくしたりします。
System Verilogにある別の隠蔽の仕組みはinterfaceです。これはCPUのバスみたいなものをパッケージにしたものです。
- インスタンスのパラメータの省略。.a(a)みたく信号名と渡す変数名が一致するときは.*として一気に省略できます。名前が違うものだけ記述することもできます。.b(c)だけ書きます。
- always_ff。フリップフロップにすることを指定したalwaysです。
- always_comb。組み合わせ回路にすることを指定したalwaysです。
- unique case。並列処理できることを指定したcaseです。他にpriority caseがあります。同じくunique ifとかpriority ifなんかもあります。
今回使ったのは、どちらかというと、本質的ではなく見かけの違いだけです。しかし言語というのは実は見かけが一番重要です。要は、言語の良さって、かっこよく書けるかどうかだけです。よく言われる生産性みたいな話でいうと、同じことを記述するのに何行で書けるかが一番重要ということになります。センシティビティリストにちゃんと全ての変数が書かれているかをプログラマがチェックするなんて、はっきり言ってありえません。機械の方が得意なことは機械にやらせた方がいいんです。その意味ではVerilog 1995よりはVerilog 2001の方が好きですし、さらにはSystem Verilogの方が好きです。
ところで、Verilog 2001で、`default_nettype noneを指定したとき、regでないinputとかoutputにinput wireとかoutput wireって書いた方がいいんでしょうか?xst、Veritak、Symplifyなんかはinput wireじゃなく、inputだけでも怒られないんですが、某論理合成ツールではinput wireと書かないと怒られるんだそうです。
論理合成結果は28sliceでした。
ちなみに、ステートマシンをワンホットにするには、以下のようにtypedefを変更するだけでいいです。
typedef enum logic [5:0] { ST1 = 6'b100000, ST2 = 6'b010000, ST3 = 6'b001000, ST4 = 6'b000100, ST5 = 6'b000010, ST6 = 6'b000001 } STATE;
論理合成結果は30sliceになりました。今回は元のverilogと違ってstateを直接デコードするようにしたので6入力セレクタができたせいだと思います。LUTが4入力1出力であることを考えると、3ビットの中間コードに落としているmarseeさんはFPGAのことを知り尽くしていると思いました。
非同期リセットを同期リセットに変えると27sliceに減ります。Synplifyは内部的にstateをワンホットに書き換えているので、stateをワンホットにしても同じです。非同期リセットのとき書き換えないのは謎です。
どちらかと言うと、せっかくSystem Verilogを使うんだったらassertionを使わないと意味ないですね。