Verilogでの配列

Verilogでの配列

 ソフトウェアの経験がある人にとって、配列を使うのは当たり前の事と思われますが、ハードウェアではあまり配列を使う機会がありません。配列で思い浮かぶ用途のRAMは多くの場合、メーカーが用意しているIPを使うからです。

 RAMを構築する際に、これから述べる配列を使うのも良いのですが、メーカ性IPを使う方が良い場合がほとんどです。それは、FPGAには予めメモリブロックという領域があり、そこはRAMやFIFO等を構築する専用のハードウェアとして利用できるからです。メーカー製のRAMやFIFOのIPはその領域に最適化されたコードを生成してくれます。

 この利点は、FPGAのリソースを有効に使うことができる。メモリ専用ブロックは高速・大型である。などがあげられます。特に、FIFOを構築する場合には、高速なメリットを生かして異なるクロックでリードライトができるなど利用範囲が広がっています。

 逆に、本ブログのように独自にRAMを構築したとしても、FPGAのメモリ領域に最適化されるかも知れません。そこは、コンパイルレポートで確認してください。

 配列という言葉はソフトウェアの中で良く使われます。ソフトウェアの場合、メモリの中の構造として扱われます。ハードウェアの場合には、そもそもレジスタやワイヤーを宣言するときにビット数やバス幅といった塊で宣言をするので、それが一次元配列を意味しています。それゆえ、積極的に配列という言葉を使う事はありません。

 しかしながら、上の図のように8bitのデータを数個用意したい。というような場面も出てくるわけで、その場合、ソフトウェアで言う所の2次元配列のような構造が必要になります。

 じつは、Verilogの場合、処理系によっては配列を許さない物もあります。特に3次元以上の配列の場合には、SystemVerilogという処理系はサポートしていますが、それ以外はサポートしていないと考えた方が良いでしょう。

 また、regやwireには2次元配列が扱えますが、ポートには2次元配列が扱えません。こういったこともあり、積極的に配列を使う人は少ないと思います。

//  TITLE:					"array.v"
//  MODULE NAME:			
//  PROJECT CODE:			
//  AUTHOR:					 (****@nakaharagiken.com)
//  CREATION DATE:			2020.8.28
//  SOURCE:            		
//  LICENSE:           		Copyright (c) 2020 nakaharagiken All rights reserved. 
//  DESCRIPTION:            配列学習
//  NOTES TO USER:			
//  SOURCE CONTROL:			
//  REVISION HISTORY:  	    v0.1	2020.8.28	First edition
//
//							rMemory[15] <= indata;					// 8bitをrMemoryの16番目に記憶
//							rMemory[15][2] <= indata[2];			// 2bit目だけをrMemoryの16番目に記憶
//							rMemory[15][3:2] <= indata[3:2];		// 2,3bit目をrMemoryの16番目に記憶
//							rMemory[15][5:2] <= indata[5:2];		// 2~5bit目をrMemoryの16番目に記憶
//
`timescale 1ns/1ps


module array(
	clock,							//	system clock
	nreset,							//	asynchronous reset 				____reset___|~~~normal~~~
	address,						//	アドレス
	indata,							//	入力データ
	write,							//	データライト					~~~write~~~|__read_____
	outdata							//	出力
);

input			clock;				//	system clock
input			nreset;				//	asynchronous reset 				____reset___|~~~normal~~~
input	[3:0]	address;			//	アドレス
input	[7:0]	indata;				//	入力データ
input			write;				//	データライト					~~~write~~~|__read_____
output	[7:0]	outdata;			//	出力

///////////////////////////////////////////////////////////////
// SIGNALS
///////////////////////////////////////////////////////////////
reg		[7:0]	rMemory[0:15];		// 8bit幅 16個のDFF

integer i;							// forループ用カウンタ

//////////////////////////////////////////
// memory ライト処理
//////////////////////////////////////////
always @ (posedge  clock, negedge nreset) begin
	if (!nreset) begin
		for (i = 0; i <= 15; i = i + 1) begin		// 16回ループ
			rMemory[i] <= 8'h00;
		end
	end else begin
		if (write == 1'b1) begin
			case (address)
				4'b0000	:	rMemory[0] <= indata;	// 8bitを記憶
				4'b0001	:	rMemory[1] <= indata;
				4'b0010	:	rMemory[2] <= indata;
				4'b0011	:	rMemory[3] <= indata;
				4'b0100	:	rMemory[4] <= indata;
				4'b0101	:	rMemory[5] <= indata;
				4'b0110	:	rMemory[6] <= indata;
				4'b0111	:	rMemory[7] <= indata;
				4'b1000	:	rMemory[8] <= indata;
				4'b1001	:	rMemory[9] <= indata;
				4'b1010	:	rMemory[10] <= indata;
				4'b1011	:	rMemory[11] <= indata;
				4'b1100	:	rMemory[12] <= indata;
				4'b1101	:	rMemory[13] <= indata;
				4'b1110	:	rMemory[14] <= indata;
				4'b1111	:	rMemory[15] <= indata;
				default	:	rMemory[0] <= indata;
			endcase
		end
	end
end

//////////////////////////////////////////
// memory リード側
//////////////////////////////////////////
assign	outdata = 	(address == 4'b0000)? rMemory[0] : 
					(address == 4'b0001)? rMemory[1] : 
					(address == 4'b0010)? rMemory[2] : 
					(address == 4'b0011)? rMemory[3] : 
					(address == 4'b0100)? rMemory[4] : 
					(address == 4'b0101)? rMemory[5] : 
					(address == 4'b0110)? rMemory[6] : 
					(address == 4'b0111)? rMemory[7] : 
					(address == 4'b1000)? rMemory[8] : 
					(address == 4'b1001)? rMemory[9] : 
					(address == 4'b1010)? rMemory[10] : 
					(address == 4'b1011)? rMemory[11] : 
					(address == 4'b1100)? rMemory[12] : 
					(address == 4'b1101)? rMemory[13] : 
					(address == 4'b1110)? rMemory[14] : 
											rMemory[15] ;

endmodule

  配列を扱ったソースコードの例を示しました。40行目が2次元配列の宣言部です。reg [7:0] rMemory[0:15]; は8bit幅で16個の要素でDFFを宣言しています。つまり16byteのメモリになります。これぐらい小さいメモリであれば、このように自前で作る意味があるかも知れません。ビット幅を宣言するにも、要素数を宣言するにも[]を使うので混乱しがちです。特に要素数の記述方法に注意してください。

 せっかく配列を扱うので、forループで記載を簡素化する方法も49行目に示しました。42行目でiを宣言しています。これがforループカウンタです。このiはFPGAの中では展開されません。Verilogへの命令だと思ってください。forでカウントされた(この場合0~15の16回)数だけ、Verilogの記述を簡素化しているだけです。50行目のrMemory[i] <= 8’h00;をiをカウントアップしながら16回繰り返す。という事なので、書き換えると次のようになります。

        rMemory[0] <= 8'h00;
        rMemory[1] <= 8'h00;
        rMemory[2] <= 8'h00;
        rMemory[3] <= 8'h00;
        rMemory[4] <= 8'h00;
        rMemory[5] <= 8'h00;
        rMemory[6] <= 8'h00;
        rMemory[7] <= 8'h00;
        rMemory[8] <= 8'h00;
        rMemory[9] <= 8'h00;
        rMemory[10] <= 8'h00;
        rMemory[11] <= 8'h00;
        rMemory[12] <= 8'h00;
        rMemory[13] <= 8'h00;
        rMemory[14] <= 8'h00;
        rMemory[15] <= 8'h00;

 これをエディタで書くのが面倒なので、for (i = 0; i <= 15; i = i + 1) beginでVerilogコンパイラに指示を出している。と読み取ってください。FPGAの中にforループができる訳ではありませんので、注意が必要です。

 配列への入力は55行目のrMemory[0] <= indata;のようにします。要素0番目に8ビットを入力しています。

 各ビットへのアクセス方法ですが、以下のように行います。
rMemory[15] <= indata; 8bitをrMemoryの16番目に記憶
rMemory[15][2] <= indata[2]; 2bit目だけをrMemoryの16番目に記憶
rMemory[15][3:2] <= indata[3:2]; 2,3bit目をrMemoryの16番目に記憶
rMemory[15][5:2] <= indata[5:2]; 2~5bit目をrMemoryの16番目に記憶
 先に要素番号を記せば、あとは普通にビットアクセスする方法と同じです。

 80行目のassignの使い方も良く使う方法なので覚えましょう。切り替えスイッチの記載など多く使う場面があります。

 最後にテストベンチも載せておきます。forループはテストベンチの方が使う頻度が高いと思います。

//  TITLE:					"TB_array.v""
//  MODULE NAME:			TB_array.v"
//  PROJECT CODE:			
//  AUTHOR:					 (****@nakaharagiken.com)
//  CREATION DATE:			2020.8.23
//  SOURCE:            		
//  LICENSE:           		Copyright (c) 2020 nakaharagiken All rights reserved. 
//  DESCRIPTION:            array.vのテストベンチ
//  NOTES TO USER:			
//  SOURCE CONTROL:			
//  REVISION HISTORY:  	    v0.1	2020.8.23	First edition
//
//
//
//
//
//
//
//
`timescale 1ns/1ps

module TB_array;

parameter SYSCLK_PERIOD = 100;		// 10MHz

reg				i_clock;					// system clock
reg				i_nreset;					// asynchronous reset 				____reset___|~~~normal~~~
reg		[3:0]	i_address;					//	アドレス
reg		[7:0]	i_indata;					//	入力データ
reg				i_write;					//	データライト					~~~write~~~|__read_____
wire	[7:0]	o_outdata;					//	出力




// for文内で使用する変数の宣言
integer i;

//////////////////////////////////////////////////////////////////////
// Clock Driver
//////////////////////////////////////////////////////////////////////
always @(i_clock)
    #(SYSCLK_PERIOD / 2.0) i_clock <= !i_clock;


//////////////////////////////////////////////////////////////////////
// Main
//////////////////////////////////////////////////////////////////////
initial
begin

	i_clock <= 0;
	i_nreset <= 0;						//	初期値
	i_address <=0;						//	アドレス
	i_indata <= 0;						//	入力データ
	i_write <= 0;						//	データライト					~~~write~~~|__read_____

	repeat(50) @(posedge i_clock);
	i_nreset <= 1;
	repeat(100) @(posedge i_clock);


	for (i = 0; i <= 15; i = i+1) begin
		i_address <= i;
		i_indata <= {4'b0000,i};
		i_write <= 1'b1;
		repeat(1) @(posedge i_clock);
		i_write <= 1'b0;
		repeat(1) @(posedge i_clock);
	end

	for (i = 0; i <= 15; i = i+1) begin
		i_address <= i;
		i_indata <= 8'b00000000;
		i_write <= 1'b1;
		repeat(1) @(posedge i_clock);
		i_write <= 1'b0;
		repeat(1) @(posedge i_clock);
	end

	for (i = 0; i <= 15; i = i+1) begin
		i_address <= i;
		i_indata <= 8'b11111111;
		i_write <= 1'b1;
		repeat(1) @(posedge i_clock);
		i_write <= 1'b0;
		repeat(1) @(posedge i_clock);
	end

	i_write <= 1'b0;

	for (i = 0; i <= 15; i = i+1) begin
		i_address <= i;
		repeat(1) @(posedge i_clock);
	end

end


array inst_array(
	.clock		(i_clock),							//	system clock
	.nreset		(i_nreset),							//	asynchronous reset 				____reset___|~~~normal~~~
	.address	(i_address),						//	アドレス
	.indata		(i_indata),							//	入力データ
	.write		(i_write),							//	データライト					~~~write~~~|__read_____
	.outdata	(o_outdata)							//	出力
);


endmodule