【Verilog HDL】同期回路は徹底的に同期せよ

>この記事はkstm Advent Calendar 18日目です。
>http://qiita.com/advent-calendar/2016/kstm

#Verilog HDL概要
　Verilog HDLとは、巷で話題なFPGA(Field-Programmable Gate Array)の構成を定義できるHDLの一種です。HDLの中でも、C言語ライクな文法、ソフトウェアシミュレーション環境サポート、等々が売りなようです【*要出典*】。某教員曰く、「ソフトウェア畑のプログラマでもハードウェアを扱えるようになる夢の環境」とのお言葉を頂きましたが、個人的な所感としては、ハードが絡んだ時点で闇です。
#今回の課題
　2ch入力アナログパルス信号に任意のトリガをかけ、同時に立ち上がった時のみにカウントする
#環境
- ハードウェア
    - FPGA開発ボード（The Cyclone V GX Starter Board）
    - DAC/ADC搭載ドーターカード (AD/DA Data Conversion Card)
- 開発環境
    - Quartus Prime Design Suite version 16.0 (Lite edition)

- 開発下地
    - C5G_DCC.qpf　(上記のハードのデモンストレーションプロジェクト)

#偽実装
　パルスの立ち上がり->立ち下がりを検知するサブモジュールと、クロック単位時間でマージンを取って同時入力判定が出来るように、下記のように半ば脳死実装しました。かなり長いですが、要点は、サブモジュールは元モジュールの同期クロックで同期されていることと、マージンはサブモジュールの1bit出力をきっかけとしてカウントしていく方針で実現していることくらいでしょうか。

```verilog:sub_module.v

module is_pulse (
	in_sig,      //入力信号
	clk,         //同期クロック
	threshold,   //立ち上がり検出の閾値
	reset_n,     //非同期リセット用入力
	is_pulse,    //パルスを検出したとき、1同期クロックだけ立ち上がる
	);

input	[13:0]	in_sig;
input			clk;
input	[13:0]	threshold;
input			reset_n;
output			is_pulse;

wire	[13:0]	in_sig;
wire	[13:0]	threshold;
wire			clk;
wire			reset_n;

reg				is_pulse;

reg		[31:0]	width_cnt;
reg				flag;


always @(negedge reset_n or posedge clk)
begin
	if (!reset_n) begin
		is_pulse 	<= 0;
		pulse_width	<= 0;
		width_cnt	<= 0;
		flag		<= 0;
	end
	else if (!flag) begin
		if (in_sig >= threshold && in_sig < 14'd8192) begin
			width_cnt	<= 1'b1;
			flag		<= 1'b1;
		end
		else begin
			is_pulse	<= 0;
		end
	end
	else begin
		if (in_sig < threshold || in_sig >= 14'd8192) begin
			is_pulse 	<= 1'b1;
			width_cnt	<= 0;
			flag		<= 0;
		end
		else begin
			width_cnt	<= width_cnt + 1'b1;
		end
	end
end

endmodule
```

```verilog:top_module.v
/*かなり長いので重要な部分のみ抜粋*/
//--- analog to digital converter capture and sync
	//--- Channel A
always @(negedge reset_n or posedge ADA_DCO)
begin
	if (!reset_n) begin
		per_a2da_d	<= 14'd0;
	end
	else begin
		per_a2da_d	<= ADA_D;
	end
end

always @(negedge reset_n or posedge sys_clk)
begin
	if (!reset_n) begin
		a2da_data	<= 14'd0;
	end
	else begin
		a2da_data	<= per_a2da_d;
	end
end

	//--- Channel B
always @(negedge reset_n or posedge ADB_DCO)
begin
	if (!reset_n) begin
		per_a2db_d	<= 14'd0;
	end
	else begin
		per_a2db_d	<= ADB_D;
	end
end

always @(negedge reset_n or posedge sys_clk)
begin
	if (!reset_n) begin
		a2db_data	<= 14'd0;
	end
	else begin
		a2db_data	<= per_a2db_d;
	end
end


wire			trigA;
reg		[15:0]	cntA;

wire			trigB;
reg		[15:0]	cntB;

reg		[15:0]	marginA;
reg		[15:0]	marginB;
reg		[15:0]	mixcnt;
//サブモジュールを2ch分呼び出し
is_pulse ipA(
	.in_sig(a2da_data),
	.clk(CLK),
	.threshold(14'd12288),
	.reset_n(reset_n),
	.is_pulse(trigA),
	);

is_pulse ipB(
	.in_sig(a2db_data),
	.clk(CLK),
	.threshold(14'd12288),
	.reset_n(reset_n),
	.is_pulse(trigB),
	);

//パルス検出の回数モニター用カウンタ
always @(negedge reset_n or posedge trigA)
begin
	if (!reset_n) begin
		cntA			<= 0;
	end
	else begin
		cntA			<= cntA + 1'b1;
	end
end

always @(negedge reset_n or posedge trigB)
begin
	if (!reset_n) begin
		cntB			<= 0;
	end
	else begin
		cntB			<= cntB + 1'b1;
	end
end
//マージンタイム設定
parameter threthold = 16'd32;
//マージンタイム内で、両信号が立ち上がったらmixcntをカウントアップ
always @(negedge reset_n or posedge CLK)
begin
	if (!reset_n) begin
		mixcnt 			<= 0;
		marginA			<= 0;
		marginB			<= 0;
	end
	else begin
		if (marginA > 16'd0 && marginA < threthold && marginB > 16'd0 && marginB < threthold) begin
			mixcnt			<= mixcnt + 1'b1;
			marginA			<= 16'd0;
			marginB			<= 16'd0;
		end
		else begin
			if (marginA >= threthold) begin
				marginA			<= 16'd0;
			end
			else if (marginA > 16'd0) begin
				marginA			<= marginA + 16'd1;
			end
			else begin
				marginA[0]		<= trigA;
			end
			
			if (marginB >= threthold) begin
				marginB			<= 16'd0;
			end
			else if (marginB > 16'd0) begin
				marginB			<= marginB + 16'd1;
			end
			else begin
				marginB[0]		<= trigB;
			end
		end
	end
end


//7セグLEDでモニター
SEG7_LUT U1(.oSEG(SEG_OUT1),.iDIG(cntA[3:0]));
SEG7_LUT U2(.oSEG(SEG_OUT2),.iDIG(cntB[3:0]));
SEG7_LUT U3(.oSEG(SEG_OUT3),.iDIG(4'h0));
SEG7_LUT U4(.oSEG(SEG_OUT4),.iDIG(mixcnt[3:0]));
```

#7秒間オンリー問題
　しかし、このコードをコンパイルして実機に流し込むと問題が生じます。Achに周期1000msで40nsの幅を持つパルスを、Bchに周期500msで40nsの幅を持つパルスをそれぞれ入力したところ、きっかり7秒間だけ同時入力カウントアップが動作し、その後は全く動作しなくなりました。
　非同期リセットをかけると、また7秒だけ動作する、7とは2^3-1であるなど、コード記述者側に問題がありそうな振る舞いがありましたが、この問題の不可解極まる現象として、if文の中でしか用いていない`parameter threthold = 16'd32;`の値を、例えば128にしたときは31秒だけ動作するという、謎の線形性を持つ再現性が認められたことを挙げます。
　
#解決編
##原因（？）
　怪しいところを洗っても、ネットの海を漁っても、なかなか解決に至らず、当初要素を追加するたびに整数でバージョンを上げて管理していたものが、少数第２位まで到達したときはどうしてくれようかと考えていました。
　いよいよ、手詰まりかと思いきや、[公式のサポート](https://www.altera.co.jp/support/support-resources/knowledge-base/solutions/rd10022013_898.html)に行き着きました。拙い英語力を駆使して訳すと、
>レジスタの変更するタイミングに用いる信号がおかしそう。
ロジックを変更するか、信号を適切に制御してください。

とのことでした。正しいか検証できないので、話半分に聞いて欲しいのですが、ファンアウト数とかに影響されるらしいです。すごい掻い摘んで言うとハード故の問題でした。

##真実装前準備
　今更ではありますが、Verilog HDLにおける変数のような何か、wireとregに言及します。wireは分かりやすく配線のことで、これにassignされているものは変更が即時に反映されます。。regはレジスタのことで、値の保持ができることから、こちらの方が変数っぽいです。が、値の変更タイミングはalways文＋@(イベント)で制御するところはハードらしいところで、プログラマに求められるシリアルな考え方をぶち壊してくれます。回路って、基本的にパラレルで永続ですからね。

##真実装
　問題は、"レジスタの変更するタイミング"、つまり、"always文＋@(イベント)"のイベントに難ありということです。ここで、上記のコードを見返すと、サブモジュールからの出力をwireで受け取り、これをalwaysのイベントに使ったり、マージンタイムのきっかけなどに多く使われています。サブモジュールをクロックで同期させていたことから完全にノーマークでしたが、即時変更のあり得るwireです。怖いです。例え1bitでも、何か遅延があったら、何かチャタリングのようなものを起こしていたら、エトセトラ、とても怖いです。そこで、サブモジュールの出力をクロックで更新されるレジスタで同期を取り、これで各種イベントなどを駆動させて事なきを得ました。

```verilog:neo_top_module.v
//以上上記と同文
wire			w_trigA;
reg				trigA;
reg		[15:0]	cntA;

wire			w_trigB;
reg				trigB;
reg		[15:0]	cntB;

reg		[15:0]	marginA;
reg		[15:0]	marginB;
reg		[15:0]	mixcnt;

is_pulse ipA(
	.in_sig(a2da_data),
	.clk(CLK),
	.threshold(14'd12288),
	.reset_n(reset_n),
	.is_pulse(w_trigA),
	.pulse_width(w_pulwidA)
	);

is_pulse ipB(
	.in_sig(a2db_data),
	.clk(CLK),
	.threshold(14'd12288),
	.reset_n(reset_n),
	.is_pulse(w_trigB),
	.pulse_width(w_pulwidB)
	);


//唯一にして最大の変更点
always @(negedge reset_n or posedge CLK)
begin
	if (!reset_n) begin
		trigA				<= 0;
		trigB				<= 0;
	end
	else begin
		trigA				<= w_trigA;
		trigB				<= w_trigB;
	end
end
	
//以下上記と同文
```

#まとめ
結局パラメータをいじって動作時間が決まってしまう直接の原因は分からず終いでしたが、何物にも代え難き教訓を得ました。
もうタイトルにありますが、これです。
**同期回路は徹底的に同期せよ**