スピーカアレイ駆動システム VHDLコード (2) (FIFO内蔵版)

FPGA

システムの構成

VHDLコード (1) (FIFO無し版)で合成したハードウェアでは,出力データにジッタが見られます。このジッタの原因の一つは,USBによるデータ送信ではデータを受信するタイミングがばらばらで,それをFT232H内蔵のFIFOでは吸収しきれないためと考えました。

そこで,FPGA内部にFIFOを構築したハードウェアを作り,PCからデータを送信する動作確認をしました。

<注意を読む>

注意:

この投稿は,未完成のシステムの開発過程の備忘録として書かれたものです。紹介されているコードにはバグや冗長な部分が含まれています。このコードを参考にする場合は注意をお願いします。また,解説の文章などに誤りや意味不明の箇所が含まれていることにも配慮してください。

<閉じる>

信号発生部分の構成を示します。

アレイスピーカ駆動システム(FIFO内蔵FPGA版)
  • PC
    音声データを2次元ΔΣ変調により1ビット信号群に変換する。このデータをUSBインタフェースを介して送信する。現在,1ビット駆動信号群の生成はMATLABコードにより,データ送信はC++コードで作成したソフトウェアで実行している。
  • USBインタフェース
    データは,FTDI社のUSBインタフェースIC,FT232Hにより受信される。FT232HはAdafruit社の FT232H Breakout基板に搭載されている。
  • FPGA
    FT232Hとのインタフェース,駆動信号のタイミングの調整,データのスピーカへの割り当てなどを実行するハードウェアをFPGA上に構成する。FPGA内部のRAMモジュールを利用してFIFO(First In First Out)バッファを構成している。

動作の概要

試作中のシステムは64個のスピーカをサンプリング周波数3MHzで駆動する。このシステムの動作は以下のようになっている。

  • PCからのデータ送信
    サンプリング周波数3MHz,1回のサンプル当たり64ビットの1ビット信号群を,8ビット単位でUSBインタフェースを介して送信する。
  • FT232Hによる受信
    データはFT232Hで受信され,容量1kByteの内蔵FIFOバッファに入力される。
  • FT232HとFPGAによる受信と出力
    FPGAはFT232HのFIFOバッファから60MHzのクロック信号に同期してデータを読み出す(Synchronous FIFOモード)。読みだされたデータはFPGA内部の容量16kByteのFPGAに書き込まれる。このFIFOは入力幅8bit・出力幅64bitで構成されている。この64bitの出力データは,60MHzのクロック信号を分周して作った3MHzのオーディオ用サンプリングクロックに同期して出力される。

FIFOを内蔵することのメリットは3つあります。

  • 通信のばらつきを吸収することで出力のタイミングのゆらぎ(ジッタ)を無くすことができる
  • タイミングの調整用回路を簡略化できる
    FIFOを使うと,入力と出力のタイミングは同期がとれていなくてもよいので,読み出しタイミングの制御回路が楽になります。
    また,今回採用したFIFOは入力幅と出力幅を変更できるので,それも回路の簡略化につながります。
    FT232H内蔵のFIFOは,FIFOが空でないことを示すRXFフラグしか,FIFOの内部状態を知る手段がありませんでした。これに対し,Lattice DiamondのIPExpressで合成されるFIFOでは,FIFOバッファに設定したデータ数が設定値に達したことを示すAlMostFull信号などを使うことができます。
  • 1回書き込んだデータの再利用もできる
    FPGA内部のFIFOに書き込んだデータは,何度も読み出すことができます。これは,テスト信号を書き込んでおき,何度も送信を繰り返すような用途で便利です。読み出しクロックは100MHz程度まで高くできるようなので,医用超音波帯域の信号の発生にも使えます。

FPGAの構成

 FPGAについて解説します。

  • 使用するFPGA基板
    Lattice社MachXO2 Breakout Board
  • 開発ソフトウェア
    Lattice Diamond
  • ハードウェア記述言語
    VHDL

Lattice Diamondのプロジェクト

プロジェクトのアーカイブ:ダウンロード
解凍せず,そのままLattice Diamondから開くようにしてください。

では,FPGAのハードウェアを記述するVHDLコードについて解説していきます。システムを構築するためには生成したハードウェアの入出力ピンをFPGAのIOピンに割り当てるための作業も必要ですが,それについては別稿で解説する予定です。

VHDLコード

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;
USE ieee.std_logic_unsigned.all;
USE work.ARRAY_PKG.all;	-- 自作のパッケージは“work.”で指定

-- TOP Level Hardware
ENTITY array_speaker_driver IS
	PORT(
			clk_60MHz	: in 		STD_LOGIC;	-- Clock signal from FT232H
			data_in		: in 		STD_LOGIC_VECTOR(N_BYTE*8-1 downto 0);
												--Input from FT232H
			WR			: buffer	STD_LOGIC; 	-- ホストにデータ転送のとき1に
			TXE			: in		STD_LOGIC; 	-- Transmit Bufffer Enough 
												-- 0を確認してバッファに書込み
			RD			: buffer	STD_LOGIC;	-- ホストからデータ転送のとき1に
			RXF			: in		STD_LOGIC;	-- Receiver Buffer Full
												-- 0を確認してバッファから読出し
			OE			: buffer	STD_LOGIC;	-- Output Enable
												-- FIFOからのデータ出力する際1に
			RESET_SW  	: in  		STD_LOGIC; 	-- Connect to tact switch1
			START_SW	: in  		STD_LOGIC; 	-- Connect to tact switch2
			drive		: buffer 	SIG_ARRAY;	-- Output to Loudspeaker Array
			drive_clk	: buffer 	STD_LOGIC; 	-- Clock for loudspeaker array
			LED			: out 		STD_LOGIC_VECTOR(7 downto 0)-- LED Indicator 
	     );
END array_speaker_driver;

ARCHITECTURE behavior OF array_speaker_driver IS
	signal out_data	: SIG_ARRAY;	-- アレイスピーカへの出力
	signal ch_data 	: SIG_ARRAY; 	-- チャンネルセレクタの出力	
	signal FIFO_out	: STD_LOGIC_VECTOR(N_CH*N_BYTE*8-1 downto 0);
	-- タイミング用信号:
	signal d_clk 	: STD_LOGIC; 	-- driver clock 駆動出力の外部同期用
	signal cnt_d	: STD_LOGIC_VECTOR(7 downto 0); -- d_clk用カウンタ
	signal d_clk_D 	: STD_LOGIC;	-- d_clkを60MHzクロックの1周期だけ遅らせたもの
	signal reset 	: STD_LOGIC;	-- システム全体のリセット信号
    signal Ch_cnt	: STD_LOGIC_VECTOR(4 downto 0); --チャンネルの番号を指定
	signal Ch_cnt_D	: STD_LOGIC_VECTOR(4 downto 0); --チャンネルの番号を指定
	signal ch_n		: STD_LOGIC_VECTOR(4 downto 0); --チャンネル数を指定
	signal RD_busy	: STD_LOGIC;	-- Read Busy
	signal RD_busy_D: STD_LOGIC;
	signal OE_busy	: STD_LOGIC; 	-- Output Enable Busy
	signal RXF_in 	: STD_LOGIC;
	-- 動作のスタートなどに関連する信号:
	signal START_D 	: STD_LOGIC := '1';		-- START_SWを1クロック遅延させた信号
	signal START 	: STD_LOGIC := '1'; 	-- スタート状態を表す
	signal RUN 		: STD_LOGIC := '0'; 	-- 実行状態(RUN)を示す
	-- 動作確認用の信号
	signal STATUS	: STD_LOGIC_VECTOR(7 downto 0);
	--CONSTANT ND 	: integer := 19;
	-- FIFO用信号
	signal WrEn 	: STD_LOGIC;
	signal RdEn 	: STD_LOGIC;
	signal RdEn_D 	: STD_LOGIC;
	signal Empty 	: STD_LOGIC;
	signal Full 	: STD_LOGIC;
	signal AmEmpty 	: STD_LOGIC;
	signal AmFull  	: STD_LOGIC;
	signal FIFO_reset  : STD_LOGIC;
	signal RPReset 	   : STD_LOGIC;
	signal AmEmptyThresh : STD_LOGIC_VECTOR(10 downto 0); 
	signal AmFullThresh  : STD_LOGIC_VECTOR(13 downto 0); 
COMPONENT FIFO8to64
    port (
        Data: in  std_logic_vector(7 downto 0); 
        WrClock: in  std_logic; 
        RdClock: in  std_logic; 
        WrEn: in  std_logic; 
        RdEn: in  std_logic; 
        Reset: in  std_logic; 
        RPReset: in  std_logic; 
        AmEmptyThresh: in  std_logic_vector(10 downto 0); 
        AmFullThresh: in  std_logic_vector(13 downto 0); 
        Q: out  std_logic_vector(63 downto 0); 
        Empty: out  std_logic; 
        Full: out  std_logic; 
        AlmostEmpty: out  std_logic; 
        AlmostFull: out  std_logic);
end COMPONENT;
	
BEGIN
drive_clk	<= d_clk;	-- 60MHzを分周して作ったd_clkを外部同期用に出力
reset 	<= RESET_SW; 	-- システムリセット信号をRESET用スイッチにより生成
Ch_n	<= CONV_std_logic_vector(N_CH,5); 	-- N_CHの値をCh_nに設定
AmEmptyThresh <= CONV_std_logic_vector(2048,11); 
AmFullThresh <= CONV_std_logic_vector(2048,14); 

FIFO:FIFO8to64
	PORT MAP(
			Data 		=> data_in,
			WrClock 	=> clk_60MHz,
			RdClock 	=> d_clk,
			WrEn 		=> WrEn,
			RdEn 		=> RdEn,
			Reset 		=> FIFO_reset,
			RPReset 	=> RPReset,
			AmEmptyThresh => AmEmptyThresh,
			AmFullThresh => AmFullThresh,
			Q 			=> FIFO_out,
			Empty 		=> Empty,
			Full 		=> Full,
			AlmostEmpty	=> AmEmpty,
			AlmostFull	=> AmFull
			);
			
WrEn <= RUN AND (NOT RXF) AND NOT(Full) ;	-- RUN=1かつRXF=0のとき1
	-- RXF=0 のときFIFOバッファに読むデータが存在する
RD <= NOT WrEn;
--RdEn <= RUN AND AmEmpty;
RdEn <= RUN AND AmFull;
--RPReset <= '0'; 		-- FIFO_DCのRead Pointerはリセットしない
RPReset <= NOT RESET_SW; 
FIFO_reset <= NOT RESET_SW;-- FIFO_DCのリセット信号
			
-- FT232からの60MHz信号を分周してd_clkを生成
PROCESS(clk_60MHz,reset) -- 60MHzからdrive_clkを作る
CONSTANT WIDTH : integer := 8;	-- '1'の幅
CONSTANT ND : integer := 19;	-- 分周比 
BEGIN
	IF(reset ='0')THEN 
		cnt_d  <= (others => '0'); 
		d_clk <='0';
	ELSIF(clk_60MHz'EVENT AND clk_60MHz='1')THEN
		IF(cnt_d < ND)THEN
			cnt_d <= cnt_d + 1;
			IF(cnt_d < WIDTH)THEN
				d_clk <= '1';
			ELSE
				d_clk <= '0';
			END IF;
		ELSE
			cnt_d  <= (others => '0');
		END IF;
	END IF;
END PROCESS;

PROCESS(d_clk,reset) 
BEGIN
IF(reset = '0')THEN
	out_data <= (others =>(others => '0')); 
	ELSIF(d_clk'EVENT AND d_clk='1')THEN
    for I in 0 to N_CH-1 loop
		out_data(I) <= FIFO_out((I+1)*N_BYTE*8-1 downto I*N_BYTE*8);
	end loop;
	END IF;
END PROCESS;

drive <= out_data;
			
-- クロックに同期したSTART信号を生成

START <= '0' WHEN cnt_d = 0 AND RUN = '1' ELSE
         '1';

-- 外部スイッチで動作信号RUNを生成
PROCESS(clk_60MHz,reset)
BEGIN
	IF(reset='0')THEN
		RUN <= '0';
	ELSIF(clk_60MHz'EVENT AND clk_60MHz = '1')THEN
		IF(START_SW = '0')THEN
			RUN <= '1';
		END IF;
	END IF;
END PROCESS;

-- RD control: FT232HのFIFO出力への制御信号RDを生成
--RD <= NOT (RUN AND (NOT RXF));
	
OE <=  	NOT RUN; 	-- FT232Hの出力許可信号
WR <= '1';			-- FT232HへのWR,とりあえず常に1に
LED <= NOT STATUS;	-- LEDはLowで点灯するので反転

-- STATUS LED表示用の信号
PROCESS(clk_60MHz,reset)
CONSTANT K : integer := 0;
BEGIN
	IF(reset ='0')THEN
		STATUS <= (others =>'0');
		ELSIF(clk_60MHz'EVENT AND clk_60MHz = '1')THEN
			-- 表示が不要な信号はコメントアウト
			STATUS(7) <= drive(K)(7); STATUS(6) <= drive(K)(6);
			STATUS(5) <= drive(K)(5); STATUS(4) <= drive(K)(4);
			STATUS(3) <= drive(K)(3); STATUS(2) <= drive(K)(2);
			STATUS(1) <= drive(K)(1); STATUS(0) <= drive(K)(0);	
			--STATUS <= data_in;
	END IF;  
END PROCESS;

END behavior;

トップレベル回路のEntity宣言

ENTITY array_speaker_driver IS
	PORT(
			clk_60MHz	: in 		STD_LOGIC;	-- Clock signal from FT232H
			data_in		: in 		STD_LOGIC_VECTOR(N_BYTE*8-1 downto 0);
												--Input from FT232H
			WR			: buffer	STD_LOGIC; 	-- ホストにデータ転送のとき1に
			TXE			: in		STD_LOGIC; 	-- Transmit Bufffer Enough 
												-- 0を確認してバッファに書込み
			RD			: buffer	STD_LOGIC;	-- ホストからデータ転送のとき1に
			RXF			: in		STD_LOGIC;	-- Receiver Buffer Full
												-- 0を確認してバッファから読出し
			OE			: buffer	STD_LOGIC;	-- Output Enable
												-- FIFOからのデータ出力する際1に
			RESET_SW  	: in  		STD_LOGIC; 	-- Connect to tact switch1
			START_SW	: in  		STD_LOGIC; 	-- Connect to tact switch2
			drive		: buffer 	SIG_ARRAY;	-- Output to Loudspeaker Array
			drive_clk	: buffer 	STD_LOGIC; 	-- Clock for loudspeaker array
			LED			: out 		STD_LOGIC_VECTOR(7 downto 0)-- LED Indicator 
	     );
END array_speaker_driver;

トップレベル回路の入出力はFIFO無し版と同じです。

Architecture宣言(信号の宣言)

ARCHITECTURE behavior OF array_speaker_driver IS
	signal out_data	: SIG_ARRAY;	-- アレイスピーカへの出力
	signal ch_data 	: SIG_ARRAY; 	-- チャンネルセレクタの出力	
	signal FIFO_out	: STD_LOGIC_VECTOR(N_CH*N_BYTE*8-1 downto 0);
	-- タイミング用信号:
	signal d_clk 	: STD_LOGIC; 	-- driver clock 駆動出力の外部同期用
	signal cnt_d	: STD_LOGIC_VECTOR(7 downto 0); -- d_clk用カウンタ
	signal d_clk_D 	: STD_LOGIC;	-- d_clkを60MHzクロックの1周期だけ遅らせたもの
	signal reset 	: STD_LOGIC;	-- システム全体のリセット信号
    signal Ch_cnt	: STD_LOGIC_VECTOR(4 downto 0); --チャンネルの番号を指定
	signal Ch_cnt_D	: STD_LOGIC_VECTOR(4 downto 0); --チャンネルの番号を指定
	signal ch_n		: STD_LOGIC_VECTOR(4 downto 0); --チャンネル数を指定
	signal RD_busy	: STD_LOGIC;	-- Read Busy
	signal RD_busy_D: STD_LOGIC;
	signal OE_busy	: STD_LOGIC; 	-- Output Enable Busy
	signal RXF_in 	: STD_LOGIC;
	-- 動作のスタートなどに関連する信号:
	signal START_D 	: STD_LOGIC := '1';		-- START_SWを1クロック遅延させた信号
	signal START 	: STD_LOGIC := '1'; 	-- スタート状態を表す
	signal RUN 		: STD_LOGIC := '0'; 	-- 実行状態(RUN)を示す
	-- 動作確認用の信号
	signal STATUS	: STD_LOGIC_VECTOR(7 downto 0);
	--CONSTANT ND 	: integer := 19;
	-- FIFO用信号
	signal WrEn 	: STD_LOGIC;
	signal RdEn 	: STD_LOGIC;
	signal RdEn_D 	: STD_LOGIC;
	signal Empty 	: STD_LOGIC;
	signal Full 	: STD_LOGIC;
	signal AmEmpty 	: STD_LOGIC;
	signal AmFull  	: STD_LOGIC;
	signal FIFO_reset  : STD_LOGIC;
	signal RPReset 	   : STD_LOGIC;
	signal AmEmptyThresh : STD_LOGIC_VECTOR(10 downto 0); 
	signal AmFullThresh  : STD_LOGIC_VECTOR(13 downto 0); 

signal宣言で定義される内部信号には,FIFO関連の信号が追加されています。主要なものだけ解説します。

  • signal FIFO_out : STD_LOGIC_VECTOR(N_CH*N_BYTE*8-1 downto 0);
    内蔵FIFOの出力。
  • signal WrEn : STD_LOGIC;
    内蔵FIFOに入力する書き込み許可信号。1のとき,書き込みクロックの立上りに同期してデータが書き込まれる。
  • signal RdEn : STD_LOGIC;
    内蔵FIFOに入力する読み出し許可信号。1のとき読み出しクロックの立上りに同期してFIFOの内容が出力される。
  • signal Empty : STD_LOGIC;
    内蔵FIFOのEmpty端子に接続されている。FIFOが空になっているとき1となる。
  • signal Full : STD_LOGIC;
    内蔵FIFOのFull端子に接続されている。FIFOが一杯になっているとき1となる。
  • signal AmEmpty : STD_LOGIC;
    内蔵FIFOのAlmostEmpty(“ほとんど空”)信号に接続されている。FIFOに蓄積されているデータの個数がAmEmptyThreshで設定した値以下のとき1になる。
  • signal AmFull : STD_LOGIC;
    内蔵FIFOのAlmostFull(“ほとんど一杯”)信号に接続されている。FIFOに蓄積されているデータの個数がAmFullThreshで設定した値より大きのとき1になる。

Architecture宣言(Component宣言)

COMPONENT FIFO8to64
    port (
        Data: in  std_logic_vector(7 downto 0); 
        WrClock: in  std_logic; 
        RdClock: in  std_logic; 
        WrEn: in  std_logic; 
        RdEn: in  std_logic; 
        Reset: in  std_logic; 
        RPReset: in  std_logic; 
        AmEmptyThresh: in  std_logic_vector(10 downto 0); 
        AmFullThresh: in  std_logic_vector(13 downto 0); 
        Q: out  std_logic_vector(63 downto 0); 
        Empty: out  std_logic; 
        Full: out  std_logic; 
        AlmostEmpty: out  std_logic; 
        AlmostFull: out  std_logic);
end COMPONENT;

Signal宣言の次に,トップレベル回路の中で使用する部品(component)を宣言する。Entity宣言と似ている。

  • COMPONENT FIFO8to64
    部品の名前を定義する。end COMPONENT;までの間にport文を配置する。
  • port ( ・・・
    port宣言。入出力信号の属性を定義する。

この部品,FIFO8to64は,LatticeDiamondのIPExpressで作製された。内部構成はVHDLで記述されているので,ある程度のカスタマイズが可能と考えられる。しかし,コードは2785行にわたり非常に長いので紹介はしない。

Architecture宣言(回路記述の冒頭部分)

BEGIN
drive_clk	<= d_clk;	-- 60MHzを分周して作ったd_clkを外部同期用に出力
reset 	<= RESET_SW; 	-- システムリセット信号をRESET用スイッチにより生成
Ch_n	<= CONV_std_logic_vector(N_CH,5); 	-- N_CHの値をCh_nに設定
AmEmptyThresh <= CONV_std_logic_vector(2048,11); 
AmFullThresh <= CONV_std_logic_vector(2048,14); 
  • AmEmptyThresh <= CONV_std_logic_vector(2048,11);
    11ビット幅の信号AmEmptyThreshを2048という値に設定している。FIFOに蓄積されたデータの個数(8バイトで1つとする)が,この値以下になると,信号AmEmptyは1になる。
  • AmFullThresh <= CONV_std_logic_vector(2048,14);
    14ビット幅の信号AmFullThreshを2048という値に設定している。FIFOに蓄積されたデータの個数(1バイトで1つとする)が,この値以上になると,信号AmFullは1になる。

FIFO8To64のポートマップ

FIFO:FIFO8to64
	PORT MAP(
			Data 		=> data_in,
			WrClock 	=> clk_60MHz,
			RdClock 	=> d_clk,
			WrEn 		=> WrEn,
			RdEn 		=> RdEn,
			Reset 		=> FIFO_reset,
			RPReset 	=> RPReset,
			AmEmptyThresh => AmEmptyThresh,
			AmFullThresh => AmFullThresh,
			Q 			=> FIFO_out,
			Empty 		=> Empty,
			Full 		=> Full,
			AlmostEmpty	=> AmEmpty,
			AlmostFull	=> AmFull
			);

FIFO8To64という部品に内部信号を接続します。これはPortmap文で記述します。

  • FIFO:FIFO8to64
    Portmap文の初めに使う部品とその名前を指示する。FIFO8To64という部品にFIFOという名前を付けて使うことを示す。部品はあらかじめComponent宣言で定義しておく。C言語のプリミティブ宣言に当たる。
  • PORT MAP( ・・・
    Portmap文は,信号を部品の各端子に接続するための記述。記述には2通りの方法がある。一つは,C言語の関数呼び出しのときの実引数のように,PORT MAPの次の括弧の中に,Component宣言で定義した順に信号を配置する方法である。もう1つは,“=>”を使って,端子名と信号名を結合する方法である。後者の方法は,記述が長くなるが,結合リストの順番を変えても良いので信号の数が多いときは楽になる。

制御信号の発生

WrEn &lt;= RUN AND (NOT RXF) AND NOT(Full) ;	-- RUN=1かつRXF=0のとき1
	-- RXF=0 のときFIFOバッファに読むデータが存在する
RD &lt;= NOT WrEn;
--RdEn &lt;= RUN AND AmEmpty;
RdEn &lt;= RUN AND AmFull;
--RPReset &lt;= '0'; 		-- FIFO_DCのRead Pointerはリセットしない
RPReset &lt;= NOT RESET_SW; 
FIFO_reset &lt;= NOT RESET_SW;-- FIFO_DCのリセット信号

制御信号を発生する部分です。FT232Hとのインタフェース用の制御信号も混ざっているので,見づらいコードになっています。

  • WrEn <= RUN AND (NOT RXF) AND NOT(Full) ;
    内蔵FIFOへの書き込み許可信号。RUN状態のときにFT232HのフラグRXFが0,かつ,内蔵FIFOが一杯でなければ1にする。
  • RD <= NOT WrEn;
    FT232Hへの読み出し制御信号RDは,WrEnの否定とする。RD=0のときFT232Hから読み出しをするからである。
  • RdEn <= RUN AND AmFull;
    内蔵FIFOへの読み出し許可信号。RUN状態のときに内蔵FIFOのAmFull信号が1に達したら読み出す。
  • RPReset <= NOT RESET_SW;
    RPResetが1のとき,FIFOの読み出しポインタをリセットする。ブレッドボード上のリセット用タクトスイッチを押したときのみRPResetが1になるようにしている。
  • FIFO_reset <= NOT RESET_SW;
    FIFO全体のリセットもRPResetと同じ信号を入力している。

AmFullは,FIFOに蓄積されたデータ数がAmFullThreshで設定した値以上になると1になる信号です。ですから,内蔵FIFOが空の状態からスタートすると,設定数だけのデータが書き込まれるまでFIFOからの出力は止められることになります。

しかし,読み出し速度が書き込み速度を超えて,FIFOの蓄積データ数が設定数を切ると,出力が途切れることになってしまいます。

以上のように,この制御信号の生成部は未完成で,システムを動かして調整していく必要があります。

分周によりサンプリングクロックを生成

-- FT232からの60MHz信号を分周してd_clkを生成
PROCESS(clk_60MHz,reset) -- 60MHzからdrive_clkを作る
CONSTANT WIDTH : integer := 8;	-- '1'の幅
--CONSTANT ND : integer := 19;	-- 分周比 
BEGIN
	IF(reset ='0')THEN
		cnt_d  &lt;= (others => '0');
		d_clk &lt;='0';
	ELSIF(clk_60MHz'EVENT AND clk_60MHz='1')THEN
		IF(cnt_d &lt; ND)THEN
			cnt_d &lt;= cnt_d + 1;
			IF(cnt_d &lt; WIDTH)THEN
				d_clk &lt;= '1';
			ELSE
				d_clk &lt;= '0';
			END IF;
		ELSE
			cnt_d  &lt;= (others => '0');
		END IF;
	END IF;
END PROCESS;

コメントにあるように,SyncFIFOモードのFT232Hが出力する60MHzのクロック信号を分周して3MHzのクロック信号を生成する部分です。このクロック信号に同期してデータを読み出すことで,スピーカを駆動する信号を発生します

このシステムでは,FPGAに接続したスイッチにより動作の開始やリセットを制御しています。またFT232Hとデータをやり取りするための制御信号を生成します。これらの制御信号の発生回路について説明します。

出力レジスタの記述

PROCESS(d_clk,reset) 
BEGIN
IF(reset = '0')THEN
	out_data &lt;= (others =>(others => '0')); 
	ELSIF(d_clk'EVENT AND d_clk='1')THEN
    for I in 0 to N_CH-1 loop
		out_data(I) &lt;= FIFO_out((I+1)*N_BYTE*8-1 downto I*N_BYTE*8);
	end loop;
	END IF;
END PROCESS;

drive &lt;= out_data;

FIFOの64bit出力FIFO_outを8個の出力レジスタout_data(0)~out_data(7)に接続する回路の記述です。データは

  • ELSIF(d_clk’EVENT AND d_clk=’1′)THEN
    3MHzのクロックd_clkの立上りでデータをレジスタに読み込む。
  • for I in 0 to N_CH-1 loop
    out_data(I) <= FIFO_out((I+1)*N_BYTE*8-1 downto I*N_BYTE*8);
    end loop;
    for loop文となっている。C言語のfor loopとは異なり,「時間的な繰り返しループ」ではなく,「空間的な繰り返し」を記述している。
  • drive <= out_data;
    out_dataはFPGAの出力driveに接続される。

スイッチによる制御信号の発生

-- クロックに同期したSTART信号を生成

START &lt;= '0' WHEN cnt_d = 0 AND RUN = '1' ELSE
         '1';

-- 外部スイッチで動作信号RUNを生成
PROCESS(clk_60MHz,reset)
BEGIN
	IF(reset='0')THEN
		RUN &lt;= '0';
	ELSIF(clk_60MHz'EVENT AND clk_60MHz = '1')THEN
		IF(START_SW = '0')THEN
			RUN &lt;= '1';
		END IF;
	END IF;
END PROCESS;
  • START <= ‘0’ WHEN cnt_d = 0 AND RUN = ‘1’ ELSE ‘1’;
    この信号STARTは,現在,使われていない。動作中であることを示すRUNが1のときに,3MHzの駆動クロックに同期して周期的に0となる信号である。
  • このProcess文は,ブレッドボード上のスタート用タクトスイッチを押すと,RUN=1を生成する。

その他の信号発生回路

OE &lt;=  	NOT RUN; 	-- FT232Hの出力許可信号
WR &lt;= '1';			-- FT232HへのWR,とりあえず常に1に
LED &lt;= NOT STATUS;	-- LEDはLowで点灯するので反転
  • OE <= NOT RUN;
    Output Eneble。FT232Hの出力を許可するアクティブローの制御信号。RUN=1つまり動作状態のとき0にしてFT232Hの出力を許可する。
  • WR <= ‘1’;
    WR(Write)はFPGAがFT232HのFIFOバッファにデータを書き込むときに使うアクティブローの制御信号。今回設計しているシステムではデータはFT232Hから出力されるだけなので,WRは常に1になるようにしている。
  • LED <= NOT STATUS;
    STATUS信号をFPGA基板上の8個のLEDで表示するための記述。MachXO2BB基板上のLEDは接続された信号が‘0’のときに発光する。モニタしたい信号の値が’1’のときにLEDが点灯する方が直感的には望ましいと考えたので,反転して信号LEDに代入している。

STATUSへの内部信号の割り当て

-- STATUS LED表示用の信号
PROCESS(clk_60MHz,reset)
CONSTANT K : integer := 0;
BEGIN
	IF(reset ='0')THEN
		STATUS &lt;= (others =>'0');
		ELSIF(clk_60MHz'EVENT AND clk_60MHz = '1')THEN
			-- 表示が不要な信号はコメントアウト
			--STATUS(7)	&lt;= TXE; 	STATUS(6) &lt;= NOT (WR);
			--STATUS(5)	&lt;= RXF; 	STATUS(4) &lt;= NOT (RD);
			--STATUS(3)	&lt;= OE; 		  
			--STATUS(0)	&lt;= RUN; 
			STATUS(7) &lt;= drive(K)(7); STATUS(6) &lt;= drive(K)(6);
			STATUS(5) &lt;= drive(K)(5); STATUS(4) &lt;= drive(K)(4);
			STATUS(3) &lt;= drive(K)(3); STATUS(2) &lt;= drive(K)(2);
			STATUS(1) &lt;= drive(K)(1); STATUS(0) &lt;= drive(K)(0);	
			--STATUS &lt;= data_in;
	END IF;  

信号STATUSにLEDで表示したい内部信号を割り当てる回路です。ここでは,整数型変数kを指定すると,出力のkチャンネル目のバイトがSTATUSに代入されます。clk_60MHzの立上りに同期して変化するようにしています。単なる代入文だけでも目的は達成するのですが,開発の初期にクロック信号が生成することを確認したかったため,このようにしています。

例えば入力信号の1つをSTATUSの1つのビットに割り当てておきます。その入力端子に取り付けたリード線のもう一方の端子を電源に接触させると対応するLEDが点灯,GNDに接触させたときはLEDは消灯します。リード線の電圧を切り替えてもLEDの点灯状態が変化しない場合はクロックが入力されていないことがわかります。

コメント