VHDLで配列

VHDLで配列

 Verilogで配列を扱うのはVHDLに比べると簡単に思えます。こちらに記載しましたので参考にしてください。配列を扱いたいと考える場面やFPGAでの配列の扱い等も記載していますので、本ブログと並行して読むと理解が深まると思います。

 VHDLの配列は頑張れば多次元配列が可能ですし、ポートに配列を使う事も可能です。VHDLしか使わない。という人は3次元配列やポートで配列を渡す方法も研究してみてください。

 配列は下図のようにDFFを束にして扱う方法ですが、データ型(幅)と要素数に定義することができます。この図は2次元配列を示しています。

 VHDLでバスを定義する場合のstd_logic_vector(7 downto 0)はすでに1次元配列になっています。なので、1次元配列をVHDLでわざわざ配列と呼ぶ事はありません。

 VHDLでは配列を宣言する際に、型を作って宣言をします。Verilogとの大きな違いです。逆に言えば、型を作るので何次元でも作ろうと思えば作れる。という事になります。

--  TITLE:					"Memory.vhd"
--  MODULE NAME:			
--  PROJECT CODE:			
--  AUTHOR:					 (****@nakaharagiken.com)
--  CREATION DATE:			2020.8.30
--  SOURCE:            		
--  LICENSE:           		Copyright (c) 2020 nakaharagiken All rights reserved. 
--  DESCRIPTION:            配列学習
--  NOTES TO USER:			
--  SOURCE CONTROL:			
--  REVISION HISTORY:  	    v0.1	2020.8.30	First edition
--
--							rMemory(15) <= indata;							8bitをrMemoryの16番目に記憶
--							rMemory(15)(2) <= indata(2);					2bit目だけをrMemoryの16番目に記憶
--							rMemory(15)(3 downto 2) <= indata(3 downto 2);	2,3bit目をrMemoryの16番目に記憶
--							rMemory(15)(5 downto 2) <= indata(5 downto 2);	2~5bit目をrMemoryの16番目に記憶
--
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
use IEEE.std_logic_arith.all;

entity Memory is
port (
	clock:				in	std_logic;						-- system clock
	nreset:				in	std_logic;						-- asynchronous reset 			____reset___|~~~normal~~~
	address:			in	std_logic_vector(3 downto 0);	-- アドレス
	indata:				in	std_logic_vector(7 downto 0);	-- 入力データ
	writeen:			in	std_logic;						-- データライト					~~~write~~~|__read_____
	outdata:			out	std_logic_vector(7 downto 0)	-- 出力
);
end entity Memory;

architecture rtl of Memory is

---------------------------------------------------------------
-- SIGNALS
---------------------------------------------------------------
subtype	BYTE_TYPE is std_logic_vector(7 downto 0);					-- 配列の幅
type	BYTE_ELEM is array(0 to 15) of BYTE_TYPE;					-- 配列の要素数
signal	rMemory:	BYTE_ELEM;										-- 配列レジスタの宣言

begin

------------------------------------------
-- memory ライト処理
------------------------------------------
process (clock,nreset) begin
	if nreset = '0' then									-- リセット初期化
		for i in 0 to 15 loop
			rMemory(i) <= (others => '0');
		end loop;
	elsif clock' event and clock = '1' then					-- clock 立ち上がり指示
		if (writeen = '1') then
			case address is
				when "0000"	=>	rMemory(0) <= indata;		-- 8bitを記憶
				when "0001"	=>	rMemory(1) <= indata;
				when "0010"	=>	rMemory(2) <= indata;
				when "0011"	=>	rMemory(3) <= indata;
				when "0100"	=>	rMemory(4) <= indata;
				when "0101"	=>	rMemory(5) <= indata;
				when "0110"	=>	rMemory(6) <= indata;
				when "0111"	=>	rMemory(7) <= indata;
				when "1000"	=>	rMemory(8) <= indata;
				when "1001"	=>	rMemory(9) <= indata;
				when "1010"	=>	rMemory(10) <= indata;
				when "1011"	=>	rMemory(11) <= indata;
				when "1100"	=>	rMemory(12) <= indata;
				when "1101"	=>	rMemory(13) <= indata;
				when "1110"	=>	rMemory(14) <= indata;
				when "1111"	=>	rMemory(15) <= indata;
				when others =>	rMemory(0) <= indata;
			end case;
		end if;
	end if;
end process;

outdata <= rMemory(0) when (address = "0000") else
			rMemory(1) when (address = "0001") else
			rMemory(2) when (address = "0010") else
			rMemory(3) when (address = "0011") else
			rMemory(4) when (address = "0100") else
			rMemory(5) when (address = "0101") else
			rMemory(6) when (address = "0110") else
			rMemory(7) when (address = "0111") else
			rMemory(8) when (address = "1000") else
			rMemory(9) when (address = "1001") else
			rMemory(10) when (address = "1010") else
			rMemory(11) when (address = "1011") else
			rMemory(12) when (address = "1100") else
			rMemory(13) when (address = "1101") else
			rMemory(14) when (address = "1110") else
			rMemory(15) when (address = "1111") else
			(others => 'Z');

end architecture rtl;

 配列の型宣言は39~41行目です。subtypeで配列の幅を宣言し、typeでそれを何個まとめるのか。を定義した型を作ります。最後にその型でsignalを作ってやると配列になります。

 一旦配列の型を作ってしまえば、扱いはVerilogと同程度に扱う事ができます。

 ついでにforループを使った記述方法もソースに組み込みました。VHDLでもVerilogでもfor ループを使うことができますが、プログラミングでのforループと考え方が違うので注意が必要です。プログラムのforループは数を数えて判断するのが一般的な使い方ですが、VHDLやVerilogの場合には何かを繰り返すコンパイラ命令です。ループそのものをFPGAに構築するのではなく、ループした結果をFPGAに構築します。したがって、ループする回数はコンパイル前に決定している必要があります。

 50行目がforループですが、rMemory(i) <= (others => ‘0’);を0~15まで繰り返す。という指示になります。つまり、コンパイラによって以下のように書き換えられたのと同じになります。

rMemory(0) <= (others => '0');
rMemory(1) <= (others => '0');
rMemory(2) <= (others => '0');
rMemory(3) <= (others => '0');
rMemory(4) <= (others => '0');
rMemory(5) <= (others => '0');
rMemory(6) <= (others => '0');
rMemory(7) <= (others => '0');
rMemory(8) <= (others => '0');
rMemory(9) <= (others => '0');
rMemory(10) <= (others => '0');
rMemory(11) <= (others => '0');
rMemory(12) <= (others => '0');
rMemory(13) <= (others => '0');
rMemory(14) <= (others => '0');
rMemory(15) <= (others => '0');

78行目からは出力の選択です。when~elseの使い方としてこのようなセレクタ的な使い方があります。94行目はあまり意味がありませんが、(others => ‘Z’)で全部のビットをハイインピーダンスにします。昔はFPGA内部でハイインピーダンスにすることができませんでしたが、最近のFPGAはこのような書き方ができます。実際にハイインピーダンスにできるデバイスも増えてきています。

 配列における各ビットへのアクセスですが、次のように行います。

rMemory(15) <= indata;    8bitをrMemoryの16番目に記憶
rMemory(15)(2) <= indata(2); 2bit目だけをrMemoryの16番目に記憶
rMemory(15)(3 downto 2) <= indata(3 downto 2);
2,3bit目をrMemoryの16番目に記憶
rMemory(15)(5 downto 2) <= indata(5 downto 2);
2~5bit目をrMemoryの16番目に記憶

 ここまでで、配列のソースコードは終わりですが、テストベンチで少しVHDL的な書き方をしているので解説します。

--  TITLE:					"TB_Memory.vhd.vhd"
--  MODULE NAME:			TB_Memory.vhd
--  PROJECT CODE:			
--  AUTHOR:					 (****@nakaharagiken.com)
--  CREATION DATE:			2020.8.30
--  SOURCE:            		
--  LICENSE:           		Copyright (c) 2020 nakaharagiken All rights reserved. 
--  DESCRIPTION:            配列学習
--  NOTES TO USER:			
--  SOURCE CONTROL:			
--  REVISION HISTORY:  	    v0.1	2020.8.30	First edition
--
--
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
use IEEE.std_logic_arith.all;
use IEEE.numeric_std.all;					-- コレ必要 for loop変換部

entity TB_Memory is
end TB_Memory;

architecture sim of TB_Memory is

component Memory is
port (
	clock:				in	std_logic;						-- system clock
	nreset:				in	std_logic;						-- asynchronous reset 			____reset___|~~~normal~~~
	address:			in	std_logic_vector(3 downto 0);	-- アドレス
	indata:				in	std_logic_vector(7 downto 0);	-- 入力データ
	writeen:			in	std_logic;						-- データライト					~~~write~~~|__read_____
	outdata:			out	std_logic_vector(7 downto 0)	-- 出力
);
end component;

constant SYSCLK_PERIOD : time := 100 ns; 				-- 10MHZ

signal	i_clock:		std_logic;						-- system clock
signal	i_nreset:		std_logic;						-- asynchronous reset 			____reset___|~~~normal~~~
signal	i_address:		std_logic_vector(3 downto 0);	-- アドレス
signal	i_indata:		std_logic_vector(7 downto 0);	-- 入力データ
signal	i_writeen:		std_logic;						-- データライト					~~~write~~~|__read_____
signal	o_outdata:		std_logic_vector(7 downto 0);	-- 出力

begin

inst_Memory :Memory
port map(
	clock			=>	i_clock,					-- in	std_logic;						-- system clock
	nreset			=>	i_nreset,					-- in	std_logic;						-- asynchronous reset 			____reset___|~~~normal~~~
	address			=>	i_address,					-- in	std_logic_vector(3 downto 0);	-- アドレス
	indata			=>	i_indata,					-- in	std_logic_vector(7 downto 0);	-- 入力データ
	writeen			=>	i_writeen,					-- in	std_logic;						-- データライト					~~~write~~~|__read_____
	outdata			=>	o_outdata					-- out	std_logic_vector(7 downto 0)	-- 出力
);


------------------------------------
-- Clock Driver
------------------------------------
process begin
	i_clock <= '1';
	wait for SYSCLK_PERIOD / 2;
	i_clock <= '0';
	wait for SYSCLK_PERIOD / 2;
end process;


------------------------------------
-- 信号作成
------------------------------------
process begin

	i_nreset <= '0';				-- std_logic;						-- asynchronous reset 			____reset___|~~~normal~~~
	i_address <= (others => '0');	-- std_logic_vector(3 downto 0);	-- アドレス
	i_indata <= (others => '0');	-- std_logic_vector(7 downto 0);	-- 入力データ
	i_writeen <= '0';				-- std_logic;						-- データライト					~~~write~~~|__read_____

	wait for (SYSCLK_PERIOD / 10);
	wait for (SYSCLK_PERIOD * 50);

	i_nreset <= '1';									--	初期値
	wait for (SYSCLK_PERIOD * 100);

	for i in 0 to 15 loop
		i_address <= std_logic_vector(to_unsigned(i,4));
		i_indata <= "0000" &amp; std_logic_vector(to_unsigned(i,4));
		i_writeen <= '1';
		wait for (SYSCLK_PERIOD);
		i_writeen <= '0';
		wait for (SYSCLK_PERIOD);
	end loop;
	wait for (SYSCLK_PERIOD * 100);

	for i in 0 to 15 loop
		i_address <= std_logic_vector(to_unsigned(i,4));
		i_indata <= (others => '0');
		i_writeen <= '1';
		wait for (SYSCLK_PERIOD);
		i_writeen <= '0';
		wait for (SYSCLK_PERIOD);
	end loop;
	wait for (SYSCLK_PERIOD * 100);

	for i in 0 to 15 loop
		i_address <= std_logic_vector(to_unsigned(i,4));
		i_indata <= (others => '1');
		i_writeen <= '1';
		wait for (SYSCLK_PERIOD);
		i_writeen <= '0';
		wait for (SYSCLK_PERIOD);
	end loop;
	wait for (SYSCLK_PERIOD * 100);

	wait;
end process;

end sim;

 まずは18行目です。ライブラリにuse IEEE.numeric_std.allを追加しています。これは、forループの変数をstd_logic_vectorに変換するために使用しています。VHDLは型が厳密だと聞いたことが無いでしょうか? まさにこの部分がその型の厳密さを示していて、integerをstd_logic_vectorに型変換するためのライブラリを宣言しておきます。

 85行目からがforループです。VHDLは変数iを型宣言しなくて良いです。型が厳密な割には不思議な仕様です。

 86行目のi_address <= std_logic_vector(to_unsigned(i,4));でiをstd_logic_vectorに変換しています。to_unsigned(i,4)で4ビットを示しています。iの4ビット分をstd_logic_vectorに変換。という意味です。

 87行目は i_indata <= “0000” & std_logic_vector(to_unsigned(i,4)); で下位4ビットだけiを変換しています。

 このように、VHDLでも配列を扱う事ができます。若干Verilogより使いにくい面もありますが、同じ事が出来ます。