SystemVerilog 使い始め演習：同じ回路のカスケード接続に implicit port connection (.*) または struct を使ってみた

###（はじめに）
カスケード的な反復計算（同じ計算の多段化）を表現するために、Verilog HDL では generate 文中で for 文のループを回し、2次元配列かループ中のローカルな wire 変数で１つの計算 module を順に接続していく記述になると思います。(下図)
module が大規模化すると少々書くのが面倒になってきます。(赤枠の部分)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2270053/0ad9581e-40a4-b4c8-070d-08006f5548bd.png)

そこで SystemVerilog の implicit port connection (.*) や struct を使って書くとどう書けば良いか試してみました。
（タイトルからお分かりかと思いますが、今まで Verilog HDL しかほとんど使ったことが無い筆者が SystemVerilog のちょっと便利なところを試してみたというだけの初歩的な内容で、高度な検証や抽象的な記述スタイルなどの情報はありません）

####（テンプレート化）
先に結論めいてますがうまくいった結果(書き方)を汎用的に書いておきます。

(Verilog HDL 記述）
書き直す前の Verilog は上の図にも書いてますがこんな形です。

```Verilog:
module top
...
genvar i;
generate
	for(i = 0; i < N ; i = i + 1) begin: LOOP
		<wire 宣言>

		<wire assign>
	
		<module A> インスタンス名 ( 接続記述 );
	end
endgenerate
...
endmodule

module A (
	<port 宣言>...

```

これを、呼び出される下位モジュールと上位の呼び出し側で名前が同じであれば、接続記述は ".*" (implicit port connection) で繋げることができますので、「接続記述」の部分がサボれます。


```Verilog:
module top
...
genvar i;
generate
	for(i = 0; i < N ; i = i + 1) begin: LOOP
		<wire 宣言: 下位モジュールと統一>

		<wire assign>
	
		<module A> インスタンス名 ( .* );
	end
endgenerate
...
endmodule

module A (
	<port 宣言: 上位モジュールと統一>...

```

実際に、当方は Quartus Prime (Standard v18.1) と対応の ModelSim Intel FPGA Edition で後半の例を試してみて、問題なく処理できました。（結果は struct を使用した場合と同じなので詳細は省略します）
generate 文中のローカルな wire 宣言は、展開されると LOOP[i].wirename の様な名前になるのでちゃんと処理してくれるのか不安がありましたが対応できている様です。

しかし、参照させていただいたこちらの[Qiita記事「デザイン向け（論理合成可能）SystemVerilog記述」](https://qiita.com/vega77/items/398a343ae616bd618f22)では .* は "STARCの設計スタイルガイド" で非推奨となるのでよろしくないということになっています。理由の一つはやはりなにかエラーがあった際になにが良くないか特定に時間がかかる要因になることかと思います。この方法でサボるにしても明示的に .portname(netname) ⇒ .portname くらいにしておくべきというところでしょうか。

すると、やはり信号の数が多い場合はそれなりに量が増えてきます。まあ既存の Verilog の仕組みでも記述をサボる方法はありますが、それはおいておき、SystemVerilog の仕組みとして interface があるので試そうとしました。しかし interface はあくまで Port 接続の置き換えという扱いのためか、wire/reg/logic 代わりという書き方でうまくいかない面があり、とりあえず断念しました。

先ほどの参照記事でも typedef で struct を定義して package 化すれば、interface 風に使えるので便利と仰っています。こちらを先ほどのテンプレートに当てはめますと...

```Verilog:my_pkg.sv
package my_pkg;
	typedef struct packed {
		<logic 宣言>
 	} my_struct_t;
endpackage
```

配線が増えても定義はここを増やすだけで参照するところは当然連動します。(検証上の理由でもない限り）古い wire での宣言にこだわる意味はないと思いますので、logic で宣言しておくと、文脈で reg 扱いして配線に合わせた register (D-FF) を作ることもできます。

```Verilog:top.v,A.v
module top
...
	import my_pkg::*;
	my_struct_t [N:0] connect;
...
	genvar i;
	generate
		for(i = 0; i < N ; i = i + 1) begin: LOOP
			...
			<module A> インスタンス名 ( .sink(connect[i], .source(connect[i+1] );
	end
	endgenerate
...
endmodule

module A (
	input  my_pkg::my_struct_t sink ,
	output my_pkg::my_struct_t source ...
```

これでこんな構成が表現できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2270053/43eb59b9-1213-1773-e6b9-1f11cd848e76.png)

信号の構成を変更する場合、宣言やカスケード接続については my_pkg.sv を編集すれば連動しますのでそこは省力化や記述の簡単化・抽象化が図れていることになります。
信号線が増えても my_struct_t に収めている限り generate 文中の記述は増えず　my_pkg.sv の個所だけボリュームを増すことになります。
もちろんモジュール A の中身は連動して変える、増える場合も多いかと思いますが。

制限としては module や interface の様な parameter override は無い様なので、局所的にビット幅を変える様な使い方では指定方法を考える必要があるかもしれません。

####（例題：FIR フィルタ回路を Super Sample Rate で動かすため多段化）
今回こちらに投稿した動機の１つが、先に投稿しました[自分の記事](https://qiita.com/rtl_modeler/items/05d020b6ad6e9c0c006c)はどちらかというとこちらのグループの方が内容的に合っている面があるかも、という点です。
下記の図の様に、回路を Mealy Machine の如く組み合わせ回路と D-FF にまで単純化し、組み合わせ回路を多段化(カスケード)すると "Super Sample Rate" 動作する、という発想です。（回路によってはこの話は破綻、詳しくは記事をご覧ください。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2270053/d10e8a76-31ec-6509-3983-b4671cc206f4.png)

元の記事のコピー/リンクだけでアドベントカレンダーを埋めるのはちょっと申し訳ないと考え、Intel FPGA ひいきでも、そうで無い方でも、まあ役に立つ内容（初歩的ですが）にしようと思って、この様な内容にしました。

後半は、元の記事と同じ仕様の super sample rate の FIR フィルタを Verilog/SystemVerilog 混じりで記述してみて Quartus Prime /ModelSim Intel Edition でのコンパイル/simulatoion 確認はできましたのでそちらをざっと紹介します。

```Verilog:ssmfir_state_struct_pkg.sv
package ssmfir_state_struct_pkg;
	parameter n_tap = 9;
	typedef struct packed {
		logic [((n_tap-1)*32)-1:0] state;
 	} ssmfir_state_t;
endpackage
```

上の図で遷移関数～状態レジスタ D のループは当然ながら同じ概念のタイミング(サイクル)違いの信号でカスケード出来なければならないので、共通の１つの struct で扱うことができます。それを定義しています。

カスケードされる組み合わせ回路相当部分は下記になります。カスケード接続部分に上記 package で定義された struct を使用しています。実際には個別の計算部分がパイプライン化されているので clock, reset を入れています。

```Verilog:FIRCOMB.sv
module FIRCOMB #(parameter n_tap = 9 )
	(
		input clock,
		input areset,
		input  [31:0]	sin,
		output [31:0]	sout,
		input  ssmfir_state_struct_pkg::ssmfir_state_t prd ,
		output ssmfir_state_struct_pkg::ssmfir_state_t nxd );
	
	logic [32*(n_tap-1) - 1: 0] nst;
	logic [32*n_tap - 1: 0]	va;
	logic [32*n_tap - 1: 0]	vb;
	logic [31:0] q;

	assign va = { prd.state, sin };	// shift in vector data
	// coefficient constants (9 taps)
	assign vb = { 	32'hbb85e6f5, 32'h3bfbf042, 32'h3db6ce30, 32'h3e792bd3, 32'h3ea7bccb,
						32'h3e792bd3, 32'h3db6ce30, 32'h3bfbf042, 32'hbb85e6f5 };

	// shift out vector data
	assign nst = va; // truncate upper 1 fp data
	assign nxd.state = nst;
	assign sout = q;

	DOTPROD #(n_tap) DP ( .clock, .areset, .va, .vb, .q ) ;

endmodule
```

パラメータ n_tap を入れていますが、後半部分のタップ係数定義(assign vb)と積和演算(内積、dot product)を行うモジュール DOTPROD は実際には n_tap=9 限定になっています。DOTPROD は Intel FPGA 向けの浮動小数点演算用 FP_FUNCTIONS IP で "Scalar Product" 演算を選択してそちらを下位モジュールとして使用しています。

上記モジュールを各ステージとしてカスケードし、さらに１段の D-FF を定義してループバックする構成は下記の記述になります。Super Sample Rate で出し入れされるデータ信号 ssrin, ssrout も、より複雑化すればやはり Struct を定義して扱うことも考えられますが、今回は一個の単精度浮動小数点数が並列化されただけですので単にそれを並べた信号として扱っています。

```Verilog:ssr_fir.sv
module ssr_fir #(parameter super_ratio = 4, n_tap = 9)
	(
	input clk,
	input resetn,
	input [(super_ratio*32)-1:0] ssrin,
	output [(super_ratio*32)-1:0] ssrout
	);

	logic clock, areset;
	
	import ssmfir_state_struct_pkg::*;
	ssmfir_state_t [super_ratio:0] stin;
	ssmfir_state_t                 dst;

	assign clock = clk;
	assign areset = ~resetn;
	
	genvar i;
	generate
	for(i = 0; i < super_ratio; i = i + 1) begin: GLOOP
		logic [31:0]	sin, sout;
		assign sin = ssrin[ i*32+31 : i*32 ];
		assign ssrout[ i*32+31 : i*32 ] = sout;

		FIRCOMB #(n_tap) STAGE (.clock, .areset, .sin, .prd(stin[i]), .nxd(stin[i+1]), .sout );
	end
	endgenerate
	
	always @(posedge clock or posedge areset)
		if( areset ) dst.state <= {(n_tap-1){32'd0}};
		else dst <= stin[super_ratio];

	assign stin[0] = dst; // LOOP-BACK State data
	
endmodule
```

定義した struct で宣言した dst は always 文での扱いで register になります。
このモジュールをコンパイルした結果の "RTL View" (Quartus Prime での HDL 等の認識結果)は下記の図の様になり、意図した回路が構成できていることが分かります。

![v1_RTLView_ssrfir_implicit_connected.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2270053/646da012-e405-676f-5081-4da7ec5938ec.png)

先に投稿した記事で動作確認用に MAX10 ターゲットで作成したデザインを流用し、下記の構成でコンパイルと Simulation 動作を確認しています。ただし図では省略していますが、SSR(Super Sample Rate) FIR フィルタは単精度浮動小数点数で構成してますが NCO IP の出力や計算結果は 16 bit 固定小数にしていますので変換ユニットも入っています。

![A10SimDesign.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2270053/dc4140b7-48f7-3c75-0f90-e06f3f458325.png)

下記の図がコンパイル結果のスクリーンショットで赤枠内が今回 SystemVerilog で記述した部分です。リソース消費は 170.5 ALM, 36 DSP ブロックで数は合っています。（9 tap x 4 並列）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2270053/0cb4a135-4213-7e2f-e40f-597c91b6b89a.png)

シミュレーション波形は下記の通りで、 NCO IP で生成した Sweep 波形の通過帯域のみ振幅が出ていることが確認できます。SystemVerilog の記述部分の機能が目的とおりで、ModelSim でも正しく処理されていると判断できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2270053/70137621-8015-8515-fada-7dd62004f49d.png)

####（まとめ）
多段のカスケード接続回路を Verilog HDL の generate 文のループで表現する際に、SystemVerilog の ".*" や struct 定義を利用することで、繰り返し登場する信号の記述をまとめて簡略化し、信号の数が増えても変更の手間を節約して表現できることを確認できました。
例題の Super Sample Rate の FIR フィルタ回路をこの方法で表現し、Intel FPGA 開発環境の Quartus Prime および ModelSim で compile と Siumlation の処理が出来る事を確認できました。

####（終わりに）
予定当日の朝に完成せずその日一杯ぎりぎりでようやく掲載できました。
今回の回路に関連して Intel FPGA 固有ですがまだ少々投稿ネタがあるのでいずれ(元気があれば)投稿する予定です。ご閲覧ありがとうございました。

（文中に記載されている会社名、商品名は各社の商標または、登録商標です。）