SSブログ

ふにゃふにゃHDL [HDL]

最近verilogが多いです。好みはVHDLですが。
検証は(system)verilogが使えると便利だと思います。しかしModelSimの混在シミュレーションライセンスなんて持ってるわけもないので(というかAltera Starter Edition、無料、ありがたや)、やるときはどちらかですが。ホントはCが使いたいですけどね~

そんなverilogの妙ながら便利な記述について。
非同期な動作と同期な動作を記述するとき、例えばリセットが2系統あるとき、
always @(posedge a_reset or posedge c_clock) begin
	if (a_reset) begin
		/*非同期リセット処理*/
	end
	else begin
		if (c_reset) begin
			/*同期リセット処理*/
		end
		else begin
			/*同期処理*/
		end
	end
end	
って書いたりします。これは割と普通。VHDLではこんな感じ。
process(a_reset, c_clock)
begin
	if (a_reset = '1') then
		--非同期リセット処理
	elsif (c_clock'event and c_clock = '1') then
		if (c_reset = '1') then
			--同期リセット処理
		else
			--同期処理
		end if;
	end if;
end process;
似てますが、クロックエッジの判定をverilogではalways文で行って、VHDLではif文で行うところが異なります。実はこの書き方、VHDL風に寄せてverilogを書いていて、「非同期処理と同期処理は別ですよ~」って明示するために「a_reset 」のブロックとそれ以外のブロックに大きく分けて書いてます。

けど、verilogはそんな書き方をする必要なかったりします。
always @(posedge a_reset or posedge c_clock) begin
	if (a_reset) begin
		/*非同期リセット処理*/
	end
	else if (c_reset) begin
		/*同期リセット処理*/
	end
	else begin
		/*同期処理*/
	end
end	
非同期処理と同期処理が同じレベルな事に注意。この書き方はVHDLは不許可です。
さらにもし非同期と同期のリセット処理が同じだったら、
always @(posedge a_reset or posedge c_clock) begin
	if (a_reset || c_reset) begin
		/*非同期/同期リセット処理*/
	end
	else begin
		/*同期処理*/
	end
end	
という書き方もできます。条件が増えてもOK。
always @(posedge a_reset or posedge c_clock) begin
	if (a_reset || c_reset) begin
		/*非同期/同期リセット処理*/
	end
	else if (ready) begin
		/*同期処理*/
	end
	else begin
		/*同期処理*/
	end
end	
シミュレーションはもちろん、合成してもちゃんとした回路ができてます(全ての処理系で試した訳では無いですが)。c_resetやready周りに変な回路ができるような様子も無さそうです。

…うわ~きもちわるい!!けど便利w
下の書き方を見ても「?なにがおかしいの?」と思う人がいるかも知れません。一方「ありえねぇ~!」って人もいるかも知れません。両極端になりそう。私は後者に近いです。けど便利なのでこの記述もしちゃいます。
verilogはVHDLに比べ記述が少ないのですが、この辺の緩さが逆に気持ち悪かったりします。

ところで、alwaysの記述で最初納得のいかなかった所は、非同期のa_resetの扱い。
例えばリセットの極性を切り替えるときはどうすれば良いでしょう?
正論理
always @(posedge a_reset or posedge c_clock) begin
	if (a_reset) begin
負論理
always @(negedge a_reset or posedge c_clock) begin
	if (~a_reset) begin
こう書くとシミュレーションできても合成はできない。
always @(a_reset or posedge c_clock) begin
	if (a_reset) begin
反転してから使う。
wire	reset = a_reset ^ pol;
always @(posedge reset or posedge c_clock) begin
	if (reset) begin
レベルセンスの信号なのにエッジを指定する必要がある、というのが腑に落ちません。また、posedge→if (areset)、negedge→if (~areset)、の関係は崩れないので冗長です。これまた気持ち悪い。そういうモノだ、と思うしか無いです。
VHDLでは素直な話です。
generic (
	pol	: std_logic	:= '0'	極性
…
process(a_reset, c_clock)
begin
	if (a_reset = pol) then

Vivado発表 [HDL]

Xilinxの次世代ツール、Vivadoが発表されました。
C/C++/SystemC辺りは「ハイレベル合成」のライセンスが必要らしい。これってAutoESLかな?お値段ちょっとお高い?といっても50万ぐらい。ちょっと前からしたら格安の値段だと思います。
WebPackでもSystemVerilog使えるなら、QuartusIIはだいぶ前からsv使えるので、svメインにしても良いかな~

Vivadoって名前聞いたとき、もちろん「ビビデ・バビデ・ブー」からもじったんだと思いましたよ(笑)。こっちはBでVじゃないですが。

好き嫌いと効率 [HDL]

いくつかHDLのサンプルを上げてますが、verilog HDLばかりです。

でも実は言語としてはVHDLの方が好みです。記述量は増えますが、型管理が厳密で妙な値の変換による齟齬に悩まされないところとか、同期非同期リセットの記述がはっきり分かれてるとか、自分の型定義が便利とか、が良いです。あとVHDLは文法が通れば曖昧な解釈は無いだろうという点も好きです。
ついでにツールもずっとAlteraのQuartus II、というよりSOPC Builderべったりです。Nios IIを使わない事は良くありますが、SOPC Builderを使わない事はほとんどありませんでした。

けど、最近はXilinx ISE+verilogばかりだったりします。verilogの妙な癖のカンがついてきたのと、たまたま最近使ったボードがXilinxだったぐらいが理由でしょうか。また、AXIで作るようになって、SOPC Builderじゃ無くてもつなぐのが楽になったというのもある感じです。QsysがAXIをサポートしたら…やっぱり楽な方に転ぶと思いますが(笑)

結局何でも良かったりします。VHDLとverilog、どっちが優れた言語か、なんて話は興味ありません。言語の細かい文法をろくに覚えてなかったりして、毎回書籍や前のソースを参照してます。その代わり、全く知らない言語でも取っつくようにしてます。「道具」ですから、目的に合っていれば使うし、合わなければ別の道具を使います。FPGAだからHDLで書かなければならないワケでもありませんし、ロジックを使わなければならない理由もありません。CPU(DSP)+C/C++の方が良いならそうします。PC+GPGPUの方が適したアプリケーションもあります。

別に言語マスターになりたいわけでは無いので、これで良いとも思ってます。
一番良いのは、何も書かずに物ができあがることです。

「手を抜くための努力は惜しまない」

部品(クロック載せ替え) [HDL]

信号のクロック載せ替えを行うモジュールです。
こういうモジュールでは非同期リセットを使ってます。

i_clkに同期したi_sigを、o_clkに同期したo_sigにつなぎます。sigwパラメーターは信号の本数です。i→oへ載せ替えた信号(fwd)を、o→iへ載せ替え返して戻します(ret)。相手が信号を受け取ったことを確認するためです。ハンドシェイクを行っているので、双方のクロックの周波数がどんな関係でも使えます。
fwd、retの[1]は必ずメタステーブル状態になるので使えません。[2]以降も可能性はありますが確率は下がるのでこれを使います。できれば[1]~[2]以降の距離(遅延時間)に制約をかけます。
modeでそのまま、立ち上がり、立ち下がりを切り替えてます。

`timescale 1ns / 1ps
`default_nettype none

module  signal_transit
#(
  parameter  sigw  = 1,  // signal width
  parameter  mode  = ""  // mode, "","UP","DOWN"
)
(
  // common
  input   wire             a_rst,  // Async Reset
  // input
  input   wire             i_clk,  // clock
  input   wire [sigw-1:0]  i_sig,  // signal
  // output
  input   wire             o_clk,  // clock
  output  reg  [sigw-1:0]  o_sig   // signal
);
  generate
    genvar i;
    for (i = 0; i < sigw; i = i+1) begin  : sigwL
      reg    [3:1]  fwd;    // [1] may have meta-state
      reg    [2:1]  ret;    // [1] may have meta-state
      reg           i_trg;  

      always @(posedge a_rst or posedge i_clk) begin
        if (a_rst) begin
          i_trg  <= 'b0;
          ret    <= 'b0;
        end
        else begin
          if      (i_sig[i] )  i_trg  <= 1'b1;
          else if (ret[2])     i_trg  <= 1'b0;

          ret    <= {ret[  1], fwd[2]};  // return
        end
      end

      always @(posedge a_rst or posedge o_clk) begin
        if (a_rst) begin
          fwd       <= 'b0;
          o_sig[i]  <= 'b0;
        end
        else begin
          fwd    <= {fwd[2:1], i_trg };  // forward

          if      (mode == "UP")    o_sig[i]  <= (fwd[3:2] == 2'b01);  // Up-Edge
          else if (mode == "DOWN")  o_sig[i]  <= (fwd[3:2] == 2'b10);  // Down-Edge
          else                      o_sig[i]  <= fwd[2];               // Normal
        end
      end
    end
  endgenerate

endmodule

`default_nettype wire

複数本数の信号が載せ替えられますが、当然データのやりとりには使えません。それぞれ独立した信号として扱います。

回路図追加。リセットとかエッジの記述は省いてます。仕組みはわかりやすいですが、AND-ORの部分はHDLの方がわかりやすそうです。
signal_transit.png

部品(リセットの同期化) [HDL]

共通で使ってる部品も残しておきます。
これは非同期リセットを同期化するモジュール。クロック1個に対して最低1つ実装します。クロックが止まっていてもリセットが入るように、リセットするときは非同期。リセット解除はクロック同期で、クロックとリセットがレーシングしないようにしてます。
パラメーターsrwはリセット解除後、何クロックリセットを維持するか、になります。2以上を設定します。

`timescale 1ns / 1ps
`default_nettype none

module  sync_reset
#(
  parameter  srw  = 2  // shift register width
)
(
  input   wire  a_rst,
  input   wire  clk,
  output  wire  rst
);
  reg   [srw:1]  rsr;

  always @(posedge a_rst or posedge clk) begin
    if (a_rst)  rsr  <= {srw{1'b1}};          // Async Reset
    else        rsr  <= {rsr[srw-1:1],1'b0};  // Sync Reset Release
  end

  assign rst  = rsr[srw];

endmodule

`default_nettype wire

library ieee;
use ieee.std_logic_1164.all;

entity sync_reset is
  generic  (
    srw  : integer  := 2
  );
  port (
    a_reset : in   std_logic;
    clock   : in   std_logic;
    reset   : out  std_logic
  );
end sync_reset;


architecture RTL of sync_reset is
  signal  rsr  : std_logic_vector(1 to srw);
begin
  process (clock, a_reset)
  begin
    if (a_reset = '1') then
      rsr  <= (others=>'1');                   -- Async Reset
    elsif (clock'event and clock = '1') then
      rsr  <= '0' & rsr(1 to srw-1);           -- Sync Reset Release
    end if;
  end process;

  reset  <= rsr(srw);
end  RTL;  -- of sync_reset


回路図追加。こういうのは図の方がわかりやすいですね。
sync_reset.pngリセット同期化
ちなみに回路図入力にはQuartusII使ってます。もちろん実際に使うことは無いのですが、回路図の清書には便利です。

最初に(その2) [HDL]

記述上の癖です。

バス幅とかはだいたいパラメータ化してます。
例えばAXIのアドレスは32bitと決まってるんですが、[31:0]とは記述してません。以下のように書いてます。
parameter axaw = 32;
wire [axaw-1:0] address;

なぜ?まどろっこしい。
例えば「wire [31:0] signal;」と書かれたこの信号、32bitというのはデータの幅でしょうか?アドレスの幅でしょうか?
というのがわからないのが気持ち悪いので…元々ソース中にマジックナンバー的な数字を入れることに抵抗があり、こういったマクロやパラメータをよく使います。

一見、パラメタライズされたモジュールができあがるように見えてしまうのですが、数値が変動することを想定しているとは限らないので、「パラメータで与えてるけど数値は変えちゃダメ」という妙なモジュールになってしまうのが問題です。

最初に [HDL]

最近AXIとか勉強中です。
趣味とは言え、一応決まりとかポリシーみたいのを並べてみます。自分にとっても再確認。この決まりは増えたり減ったり都合で書き換わったり例外措置を平気で認めたりします(笑)

(1)ターゲットはシミュレーションとFPGA。趣味だし。
結果、以下が決まります。
(1.1)同期回路が基本。
(1.2)同期リセットがデフォルト。非同期リセットは注意して使う。
この方がFPGAのツール側の自由度が上がります。FPGAのファミリによっては非同期リセットが使えないブロックを持つ物もありますし(XilinxのDSPブロックとか)。
非同期リセットを特別扱いにします。基本、クロックが無いところとか、クロックをまたぐところだけで使うようにします。

あと、
(1.3)リセットやクロックイネーブルは正論理で。
TTL全盛時代からロジックやってる人は頭の中が負論理な人もいます(自分もそう)が、FPGAによってはこれら制御信号が正論理のみというのもあります。負論理をサポートしたFPGAもありますが、サポートされてない方に合わせてます。

で、これからの流行りはたぶんこっち方向。
(2)合成は階層維持で。それで性能出すように心がける。
前のリセットの論理を気にするのも、次の出力のレジスタ化も、これが引っかかってたりします。

そして、性能を出しやすくするための決まり。
(3)モジュールの出力は必ずレジスタ(FF)化する。
(4)入力は可能ならレジスタ化する、のは実際難しいのでほどほどであきらめる。

モジュールの出力はレジスタ化する、これは極力守る。
ただし実機で使わないシミュレーションモデルは特にこの辺関知しないです。
(↑早速の例外事項)

もちろんこれらに縛られて物が作れないのでは本末転倒なので、必要があれば景気よく無視します。
(↑早速の例外事項その2)

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。