FPGA Üzerinde Cooley-Tukey Algoritması ile FFT Hesabı

Daha önce burada Cooley-Tukey FFT algoritması ile ilgili bir yazı yazmıştım. Bu yazımda ise bu algoritmayı kullanarak FPGA üzerinde hızlı fourier dönüşümü işlemi yapacağım.

\(N=8\) için Cooley-Tukey FFT algoritmasının şematik gösterimim şu şekildedir:

Buradaki en küçük parça Butterfly olarak adlandırılır:

Burada \(W_N^k\) Twiddle Factor'dür ve şu şekilde tanımlanır:

\(\begin{align*}W_N^k=e^{\frac{-2\pi j}{N}k}=\cos\Big(\frac{-2\pi}{N}k\Big)+j\sin\Big(\frac{-2\pi}{N}k\Big)\end{align*}\)

Buradaki sinüs ve kosinüs değerleri FPGA üzerinde bir tabloda tutulabilir(look-up table) ya da CORDIC gibi bir algoritma ile hesaplanabilir. Bu uygulamada sinüs ve kosinüs değerleri bir tabloda tutulacaktır.

Butterfly modülünün VHDL kodu:

----------------------------------------------------------
--               CFFT_Radix2_Butterfly.vhd
--                     Bertan Taşkın
--                       1.6.2017  
--
-- 2 noktalı Complex Fast Fourier Dönüşümü gerçekleştiren modül.
-- FFT işlemindeki toplam nokta sayısı generic kısmındaki
-- FFT_Power ile belirlenir. Sinüs ve Kosinüs tabloları
-- bu değere göre hesaplanır. Giriş verisinin genişliği
-- generic kısmındaki Data_Width ile belirlenir. FFT işlemi 
-- fixed-point sayılar üzerinde gerçekleştiğinde dolayı
-- işlem hassasiyetini arttırmak için veri genişliğine
-- FFT_Power'da eklenir.
--
-- Veri giriş ve çıkışları real ve complex olmak üzere
-- toplamda 8 sinyal üzerinden gerçekleşir. Twiddle
-- factor kuvveti k sinyali üzerinden gönderilir. Modül
-- asenkron çalışarak çıkış verilerini üretir.
--
-- Modülün doğru çalışabilmesi için Data_Width değeri, integer
-- genişliği olan 32'den büyük olmamalıdır!
-- 
-- Sinüs ve Kosinüs değerleri önceden hesaplanmış tablolarda
-- saklandığından dolayı modülün FPGA üzerinde harcadığı
-- toplam LE sayısı yaklaşık olarak,  Data_Width değeri ile
-- doğru orantılı, FFT_Power ile karesel orantılıdır.
--
-- Modülün yaklaşık max. çalışmak frekansı gömülü çarpıcı
-- elemanların hızı kadardır.
--
----------------------------------------------------------


--Kütüphaneler
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
USE ieee.numeric_std.ALL; 
use ieee.math_real.all;

entity CFFT_Radix2_Butterfly is
	generic(Data_Width : integer := 6;                                                  --İşlenecek verinin boyutu
		FFT_Power : integer := 3);                                                  --Hesaplanacak FFT Kuvveti
		
	port(k : in std_logic_vector(FFT_Power - 2 downto 0);                               --Twiddle Factor kuvveti (Wk)                  	                                
		X0Real, X0Complex : in std_logic_vector(Data_Width + FFT_Power - 1 downto 0);	 --Complex ve Real Veri Girişi - 0
		X1Real, X1Complex : in std_logic_vector(Data_Width + FFT_Power - 1 downto 0);    --Complex ve Real Veri Girişi - 1
		Y0Real, Y0Complex : out std_logic_vector(Data_Width + FFT_Power - 1 downto 0);   --Complex ve Real Veri Cıkışı - 0
		Y1Real, Y1Complex : out std_logic_vector(Data_Width + FFT_Power - 1 downto 0));	--Complex ve Real Veri Cıkışı - 1
end CFFT_Radix2_Butterfly;

architecture Behavioral of CFFT_Radix2_Butterfly is
	
	--Table veri tipi oluşturuluyor
	type Table is array(2**(FFT_Power - 1) - 1 downto 0) of signed(Data_Width + FFT_Power - 1 downto 0);
	signal SinTable, CosTable : Table;
	
	signal X0RealS, X0ComplexS, X1RealS, X1ComplexS : signed(Data_Width + FFT_Power - 1 downto 0);
	signal Y0RealS, Y0ComplexS, Y1RealS, Y1ComplexS : signed(Data_Width + FFT_Power - 1 downto 0);
	signal X1RealW, X1ComplexW : signed(Data_Width + FFT_Power - 1 downto 0);
	
	signal kint : integer;
	
begin

	--Twiddle Factor kuvveti integer'a dönüştürülüyor
	kint <= to_integer(unsigned(k));

	--Giriş verileri işlem kolaylığı için signed tipine dönüştürülüyor
	X0RealS <= signed(X0Real);
	X0ComplexS <= signed(X0Complex);
	X1RealS <= signed(X1Real);
	X1ComplexS <= signed(X1Complex);
		
	--Sinüs Tablosu / sin(-2*pi*i/N)
	Sine : for i in 0 to 2**(FFT_Power - 1) - 1 generate
		constant SinReal : REAL := SIN(real(-2) * MATH_PI * real(i) / real(2**FFT_Power));
	begin
		SinTable(i) <= to_signed(integer(SinReal * real(2**(Data_Width - 1))), Data_Width + FFT_Power);
	end generate;
	
	--Kosinüs Tablosu / cos(-2*pi*i/N)
	Cosine : for i in 0 to 2**(FFT_Power - 1) - 1 generate
		constant CosReal : REAL := COS(real(-2) * MATH_PI * real(i) / real(2**FFT_Power));
	begin
		CosTable(i) <= to_signed(integer(CosReal * real(2**(Data_Width - 1))), Data_Width + FFT_Power);
	end generate;
	
	----------FFT işlemleri----------
	
	--Değişkenlerin açıklaması:
	--X0R : 0. giriş verisinin reel kısmı
	--X0C : 0. giriş verisinin complex kısmı
	--X1R : 1. giriş verisinin reel kısmı
	--X1C : 1. giriş verisinin complex kısmı
	--Y0R : 0. cıkış verisinin reel kısmı
	--Y0C : 0. cıkış verisinin complex kısmı
	--Y1R : 1. cıkış verisinin reel kısmı
	--Y1C : 1. cıkış verisinin complex kısmı
	--X1RW : 1. giriş verisinin Twiddle factor ile çarpılmış halinin reel kısmı
	--X1CW : 1. giriş verisinin Twiddle factor ile çarpılmış halinin complex
	
	--FFT işlemi:
	--Y0 = X0 + X1 * W_k
	--Y1 = X0 - X1 * W_k
	
	--Twiddle factor:
	--W_k = cos(k) + i*sin(k)
	
	--Complex çarpma işlemi:
	--(a+bi)*(c+di) = (ac - bd)+i(ad+bc)
	
	--X1RW = X1R * cos(k) - X1C * sin(k)
	--X1CW = X1C * cos(k) + X1R * sin(k)
	--Çarpma işlemi sonucunda çıkan sonuç Data_Width kadar sağa kaydırılır
	X1RealW <= shift_right(signed(X1RealS * CosTable(kint) - X1ComplexS * SinTable(kint)), Data_Width  - 1)(Data_Width + FFT_Power - 1 downto 0);
	X1ComplexW <= shift_right(signed(X1ComplexS * CosTable(kint) + X1RealS * SinTable(kint)), Data_Width  - 1)(Data_Width + FFT_Power - 1 downto 0);
	
	--Y0R = X0R + X1RW
	--Y0C = X0C + X1CW
	Y0RealS <= X0RealS + X1RealW;
	Y0ComplexS <= X0ComplexS + X1ComplexW;
	
	--Y1R = X0R - X1RW
	--Y1C = X0C - X1CW
	Y1RealS <= X0RealS - X1RealW;
	Y1ComplexS <= X0ComplexS - X1ComplexW;	
	
	---------------------------------
	
	--Çıkış değerleri tekrar eski haline(std_logic_vector) döndürülüyor
	Y0Real <= std_logic_vector(Y0RealS);
	Y0Complex <= std_logic_vector(Y0ComplexS);
	Y1Real <= std_logic_vector(Y1RealS);
	Y1Complex <= std_logic_vector(Y1ComplexS);

end Behavioral;

Quartus II ile oluşturulmuş RTL şeması:

FFT işlemi 2 adımda gerçekleşir; Yeni verilerin alınması, FFT hesabı.

1.Yeni verilerin alınması

Cooley-Tukey algoritmasında yukarıda da görüldüğü gibi giriş verilerinin işlem sırası \(N=8\) için sırasıyla \(0, 4, 2, 6, 1, 5, 3, 7\) şeklindedir. FFT hesabının daha kolay yapılabilmesi için giriş verileri bu sıra ile RAM'e yazılması gerekir. Bu sırayı elde etmek için !!Bit-Reversal Permutation algoritması kullanılır. Bu algoritmaya göre giriş verisinin sırasının bitlerini ters çevirirsek verinin yazılacağı RAM adresini buluruz. Örnek olarak \(3.\) giriş verisinin RAM'de yazılacağı adres \((011)_2\to(110)_2=6_{10}\)'dır. Bu işlem sonunda veriler RAM'de aşağıdaki sıra ile dizilmiş olacaktır.

2.FFT Hesabı

FFT hesabı 2 adımda gerçekleşir; RAM'den eski verilerin okunması, RAM'e yeni verilerin yazılması

2.1. RAM'den Eski Verilerin Okunması

FFT hesabının doğru gerçekleşmesi için RAM'den veriler doğru sıra ile okunmalıdır. \(N=8\) için Butterfly'a girecek verilerin RAM'deki sırası sırasıyla \((0,1),(2,3),(4,5),(6,7),(0,2),(1,3),(4,6),(5,7),(0,4),(1,5),(2,6),(3,7)\) şeklindedir. Bu sayı gruplarını Satır, Küme ve Eleman şeklinde düzenlersek RAM'den verilerin hangi sıra ile okunacağını daha kolay anlayabiliriz.

Bu şemaya göre RAM'den okunacak verilerin sırası hakkında şu kuralları söyleyebiliriz:

  1. \(FP\) FFT kuvveti(\(N=2^{FP}\)) olmak üzere toplam satır sayısı \(FP\)'dir.
  2. \(n.\) satırdaki toplam küme sayısı ise \(2^{FP-n-1}\)'dir.
  3. \(n.\) satırdaki bir kümenin içindeki toplam eleman sayısı ise \(2^{n}\)'dir.
  4. \(n.\) satırdaki her bir kümenin ilk elemanının \(x\) değeri, o kümenin index sayısının \(2^{n+1}\) katıdır.
  5. Herhangi bir kümedeki bir sonraki elemanın \(x\) ve \(y\) değerleri, bir önceki elemanın \(x\) ve \(y\) değerlerinden \(1\) fazladır.
  6. \(n.\) satırdaki bir elemanın \(x\) ve \(y\) değerleri arasındaki fark \(2^n\)'dir.
  7. \(n.\) satırdaki twidle factor kuvveti, elemanın index sayısının \(2^{FP-n-1}\) katıdır.
2.1. RAM'e yeni verlerin yazılması

RAM'e yazılan veriler, okunan veriler ile aynı adreste olacaktır. En son satırda hesaplanan sonuçların magnitude karesi alınıp aynı anda çıkış verilerinin tutulacağı RAM'e de yazılacaktır. Çıkış verilerinin farklı RAM'de tutulması ile yeni FFT işlemi yapılırken eski sonuçların da erişimi sağlanır.

Tasarımın VHDL kodu:

----------------------------------------------------------
--                   CFFT_Engine.vhd
--                    Bertan Taşkın
--                      1.6.2017
--
-- Complex Hızlı Fourier Dönüşümü Alan Modül.
-- FFT işlemindeki toplam nokta sayısı generic kısmındaki
-- FFT_Power ile belirlenir. Giriş verisinin genişliği Data_Width
-- ile belirlenir. FFT işlemi fixed-point sayılar üzerinden 
-- yapıldığından dolayı hassasiyeti arttırmak için veri genişliğine
-- FFT_Power'da eklenmiştir. Modül yüksek hızlı Clock sinyali ile
-- çalıştığından dolayı DataIn sinyalinden okunan verilerin hızını 
-- yavaşlatmak için ADC_Sampling_Cycle değişkeni eklenmiştir.
--
-- FFT_RAM, FFT işlemi alınırken kullanılan RAM'dir. Dual-Port
-- yapıdadır. RAM'in çıkış sinyalleri(q_a, q_b) asenkron yapıda olmak
-- zorundadır. Her bir port 2*(Data_Width+FFT_Power) bit genişliğinde
-- olmalıdır. Kapasitesi ise 2*2^FFT_Power*(Data_Width+FFT_Power)Bit
-- olmalıdır.
--
-- OUTPUT_RAM, FFT işlemi sonucunda çıkan sonuçların tutulduğu RAM'dir.
-- SemiDual-Port yada Dual-Port yapıda olabilir. Giriş portu
-- Data_Width+FFT_Power bit olmalıdır. Kapasitesi ise  
-- 2^(FFT_Power-1)(Data_Width+FFT_Power) olmalıdır.
--
-- ADC verilerinin alınma işlemi toplamda ADC_Sampling_Cycle*2^FFT_Power
-- cycle'da gerçekleşir.
--
-- FFT işlemi için gereken cycle 2*2^(FFT_Power-1)*FFT_Power şeklinde
-- hesaplanır.
--
-- FFT sonuçları magnitude değildir, magnitude karedir.
--
-- Data_Width'in alabileceği en büyük değer integer genişliği
-- olan 32'dir.
--
----------------------------------------------------------


--Kütüphaneler
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
USE ieee.numeric_std.ALL; 
use ieee.math_real.all;

entity CFFT_Engine is
	generic(FFT_Power : integer := 3;                                                                           --FFT Kuvveti
		Data_Width : integer := 8;                                                                               --İşlenecek Verinin Boyutu
		Sampling_Cycle : integer := 250);                                                                        --ADC İçin Sampling Cycle Süresi
		                                                                                                         --FFT İçin Örnekleme Frekansı = Clk / Sampling_Cycle
		
	port(Clk : in std_logic;                                                                                    --Clock Girşi
		DataIn : in std_logic_vector(Data_Width - 1 downto 0);                                                   --ADC Veri Giriş Sinyali
		Reading : out std_logic;                                                                                 --ADC Okuması Yapılıp Yapılmadığını Belirten Sinyal
		DataIndex : out std_logic_vector(FFT_Power - 1 downto 0);                                                --O Anki Okunan ADC Verisinin Sırası
		
		                                                                                                         --FFT Hesaplmalarında Kullanılacak RAM'in Sinyalleri
		FFT_RAM_PORT1_DIN, FFT_RAM_PORT2_DIN : out std_logic_vector((Data_Width + FFT_Power) * 2 - 1 downto 0);  --Veri Giriş Sinyalleri
		FFT_RAM_PORT1_DOUT, FFT_RAM_PORT2_DOUT : in std_logic_vector((Data_Width + FFT_Power) * 2 - 1 downto 0); --Veri Çıkış Sinyalleri
		FFT_RAM_PORT1_ADDRESS, FFT_RAM_PORT2_ADDRESS : out std_logic_vector(FFT_Power - 1 downto 0);             --Adres Sinyalleri
		FFT_RAM_PORT1_WRE, FFT_RAM_PORT2_WRE : out std_logic;                                                    --Yazma Aktif Sinyalleri
		                                                                             
	                                                                                                            --Çıkış Verilerinin Tutulacağı RAM'in Sinyalleri																										  
		OUTPUT_RAM_DIN : out std_logic_vector(Data_Width + FFT_Power - 1 downto 0);                              --Veri Giriş Sinyali
		OUTPUT_RAM_WR_ADDRESS : out std_logic_vector(FFT_Power - 2 downto 0);                                    --Adres Sinyali
		OUTPUT_RAM_WREN : out std_logic);                                                                        --Yazma Aktif Sinyali
end CFFT_Engine;

architecture Behavioral of CFFT_Engine is
	
	--Girilen verinin bitlerini ters çeviren fonksiyon
	--ADC den alınan verilerin sırası bu fonksiyon ile belirlenir
	function Reverse_Bit(Data : std_logic_vector) return std_logic_vector is
	variable X : std_logic_vector(Data'length - 1 downto 0) := (others => '0');
	begin
		a : for i in 0 to Data'length - 1 loop
			X(i) := Data(Data'length - 1 - i);
		end loop;
		
		return X;
	end Reverse_Bit;
	
	--Girilen veriyi işaret bitine dikkat ederek yeniden boyutlandıran fonksiyon
	function Resize_Bit(Data : std_logic_vector; NewSize : integer) return std_logic_vector is
	variable X : std_logic_vector(NewSize - 1 downto 0) := (others => '0');
	begin
		--Data verisinin işaret biti hariç diğer bitleri X değikenine aktarılır
		a : for i in 0 to Data'length - 2 loop
			X(i) := Data(i);
		end loop;
		
		--X değişkeninde kalan bitler Data verisinin işaret biti ile doldurulur
		b : for i in Data'length - 1 to NewSize - 1 loop
			X(i) := Data(Data'length - 1);
		end loop;
		
		return X;
	end Resize_Bit;
	
--FFT işlemini gerçekleştirecek Butterfly komponenti
component CFFT_Radix2_Butterfly
	generic(Data_Width : integer := 6;                                                  --İşlenecek verinin boyutu
		FFT_Power : integer := 3);                                                       --Hesaplanacak FFT Kuvveti
		
	port(k : in std_logic_vector(FFT_Power - 2 downto 0);                               --Twiddle Factor kuvveti (Wk)                  	                                
		X0Real, X0Complex : in std_logic_vector(Data_Width + FFT_Power - 1 downto 0);		--Complex ve Real Veri Girişi - 0
		X1Real, X1Complex : in std_logic_vector(Data_Width + FFT_Power - 1 downto 0);    --Complex ve Real Veri Girişi - 1
		Y0Real, Y0Complex : out std_logic_vector(Data_Width + FFT_Power - 1 downto 0);   --Complex ve Real Veri Cıkışı - 0
		Y1Real, Y1Complex : out std_logic_vector(Data_Width + FFT_Power - 1 downto 0));	--Complex ve Real Veri Cıkışı - 1
end component;	
	
	signal k : std_logic_vector(FFT_Power - 2 downto 0);
	signal Enable : std_logic;
	signal X0Real, X0Complex, X1Real, X1Complex : std_logic_vector(Data_Width + FFT_Power - 1 downto 0);
	signal Y0Real, Y0Complex, Y1Real, Y1Complex : std_logic_vector(Data_Width + FFT_Power - 1 downto 0);
	
	type PROCESS_STATE_TYPE is (SAMPLING_ADC_DATA, CALCULATE_FFT);
	type FFT_STATE_TYPE is (READ_RAM, WRITE_RAM);
	
	signal Re, Im : signed((Data_Width + FFT_Power) - 1 downto 0);
	signal MagnitudeSquare : unsigned((Data_Width + FFT_Power) * 2 - 1 downto 0);
	
begin
	
	--FFT işlemini gerçekleştirecek Butterfly komponentinin üretilmesi
	UButterfly : CFFT_Radix2_Butterfly
		generic map(Data_Width,  FFT_Power)
		port map(k,
			X0Real, X0Complex, X1Real, X1Complex,
			Y0Real, Y0Complex, Y1Real, Y1Complex);
			
	--FFT_RAM'in çıkış sinyalleri Butterfly'a bağlanır
	--Butterfly'ın çıkış sinyallerinden de FFT sonucu alınır
	X0Real <= FFT_RAM_PORT1_DOUT((Data_Width + FFT_Power) * 2 - 1 downto (Data_Width + FFT_Power));
	X0Complex <= FFT_RAM_PORT1_DOUT((Data_Width + FFT_Power) - 1 downto 0);
				
	X1Real <= FFT_RAM_PORT2_DOUT((Data_Width + FFT_Power) * 2 - 1 downto (Data_Width + FFT_Power));
	X1Complex <= FFT_RAM_PORT2_DOUT((Data_Width + FFT_Power) - 1 downto 0);
	
	--Butterfly'ın Y0 çıkışındaki verinin magnitude karesi hesaplanır
	--(Magnitude değil!)
	Re <= signed(Y0Real);
	Im <= signed(Y0Complex);

	MagnitudeSquare <= unsigned((Re*Re)+(Im*Im));
			
			
	--ADC'den veri alma ve FFT işleminin organizasyonunu sağlayan process
	process(Clk)
	variable State0 : integer range 0 to 2**FFT_Power - 1 := 0;
	variable FFT_S : integer range 0 to FFT_Power - 1 := 0;
	variable FFT_K, FFT_E : integer range 0 to 2**(FFT_Power - 1) - 1 := 0;
	variable PROCESS_STATE : PROCESS_STATE_TYPE := SAMPLING_ADC_DATA;
	variable FFT_STATE : FFT_STATE_TYPE := READ_RAM;
	variable ADC_Sampling_Cycle : integer range 0 to Sampling_Cycle - 1 := 0;
	begin
		
		--ADC'den verilerin alınması
		if PROCESS_STATE = SAMPLING_ADC_DATA then
		
			--Okuma yapıldığı için Reading sinyali setlenir
			Reading <= '1';
			
	      --O an okunan ADC verisinin sırası
			DataIndex <= std_logic_vector(to_unsigned(State0, FFT_Power));
		
			--Okunan veriler, veri sırasının bitlerinin yerlerinin değiştirilmesiyle oluşan adrese yazılır
			FFT_RAM_PORT1_ADDRESS <= Reverse_Bit(std_logic_vector(to_unsigned(State0, FFT_Power)));

			--ADC den alınan veriler FFT_RAM'in DIN sinyaline gönderilir
			--Reel kısım = ADC verisi, Complex kısım = 0
			FFT_RAM_PORT1_DIN((Data_Width + FFT_Power) * 2 - 1 downto (Data_Width + FFT_Power)) <= std_logic_vector(Resize_Bit(DataIn, Data_Width + FFT_Power));--std_logic_vector(resize(signed(DataIn), Data_Width + FFT_Power));
			FFT_RAM_PORT1_DIN((Data_Width + FFT_Power) - 1 downto 0) <= (others=>'0');
			
			--Sadece PORT1 den yazma işlemi yapılacağından PORT1_WRE setlenir
			FFT_RAM_PORT1_WRE <= '1';			
			FFT_RAM_PORT2_WRE <= '0';			
			OUTPUT_RAM_WREN <= '0';
		
	   --FFT işleminin yapılması
		elsif PROCESS_STATE = CALCULATE_FFT then
		
			--Okuma yapılmadığı için Reading resetlenir
			Reading <= '0';
	
			--Twiddle factor
			k <= std_logic_vector(to_unsigned(FFT_E * (2**(FFT_Power - FFT_S - 1)), FFT_Power - 1));
			
			--Kullanılacak Adres
			FFT_RAM_PORT1_ADDRESS <= std_logic_vector(to_unsigned(FFT_K * (2**(FFT_S + 1)) + FFT_E, FFT_Power));
			FFT_RAM_PORT2_ADDRESS <= std_logic_vector(to_unsigned(FFT_K * (2**(FFT_S + 1)) + (2**FFT_S) + FFT_E, FFT_Power));
			
			OUTPUT_RAM_WR_ADDRESS <= std_logic_vector(to_unsigned(FFT_E, FFT_Power - 1));
			
			--Eğer FFT işleminin son satırında ise çıkan sonuçlar OUTPUT_RAM'e de yazılır
			if FFT_S = (FFT_Power - 1) then			
				OUTPUT_RAM_WREN <= '1';					
			else		
				OUTPUT_RAM_WREN <= '0';				
			end if;
			
			--Butterfly'dan çıkan veriler tekrar FFT_RAM'e yazılır
			FFT_RAM_PORT1_DIN((Data_Width + FFT_Power) * 2 - 1 downto (Data_Width + FFT_Power)) <= Y0Real;
			FFT_RAM_PORT1_DIN((Data_Width + FFT_Power) - 1 downto 0) <= Y0Complex;
			FFT_RAM_PORT2_DIN((Data_Width + FFT_Power) * 2 - 1 downto (Data_Width + FFT_Power)) <= Y1Real;
			FFT_RAM_PORT2_DIN((Data_Width + FFT_Power) - 1 downto 0) <= Y1Complex;

			--Aynı anda Butterfly'dan çıkan verilerden magnitude square hesaplanır ve OUTPUT_RAM'in
		   --veri giriş sinyalne aktarılır.
			--OUTPUT_RAM'in WRE sinyali setlenmeden bu veri OUTPUT_RAM'e yazılmaz
			OUTPUT_RAM_DIN <= std_logic_vector(MagnitudeSquare((Data_Width + FFT_Power) * 2 - 1 downto (Data_Width + FFT_Power)));
	
			--FFT hesaplaması 2 cycle'da gerçekleşir
			
			--1.Cycle FFT_RAM'den eski veriler okunur
			--Bu cycle'da yazma yapılmayacağından dolayı WRE sinyali resetlenir
			if FFT_STATE = READ_RAM then
							
				FFT_RAM_PORT1_WRE <= '0';
				FFT_RAM_PORT2_WRE <= '0';
			
			--2.Cycle'da Butterfly'dan çıkan yeni veriler FFT_RAM'e yazılır
			--Yazma yapılacağından dolayı WRE sinyal setlenir
			elsif FFT_STATE = WRITE_RAM then
	
				FFT_RAM_PORT1_WRE <= '1';
				FFT_RAM_PORT2_WRE <= '1';
			
			end if;
	
		end if;
		
		
		--Yukarıdaki işlemlerde kullanılan değişkenler Clk sinyalinin
		--yükselen kenarı ile güncellenir
		if rising_edge(Clk) then
		
			--ADC verilerinin örneklenmesi
			if PROCESS_STATE = SAMPLING_ADC_DATA then
			
				--Sampling_Cycle kadar beklenir
				if ADC_Sampling_Cycle < Sampling_Cycle - 1 then
					ADC_Sampling_Cycle := ADC_Sampling_Cycle + 1;	
				else
					ADC_Sampling_Cycle := 0;
					
					--Sampling_Cycle'a ulaşıldığında State0 değişkeni 1 arttırılır
					--ve bir sonraki ADC verisi için Sampling_Cycle saymaya başlanır
					if State0 < 2**FFT_Power - 1 then
						State0 := State0 + 1;
					else
						--Bütün ADC verilerinin örneklemesi bitince FFT hesaplama adımına geçilir
						State0 := 0;
						PROCESS_STATE := CALCULATE_FFT;
					end if;
					
				end if;
			
         --FFT işlemlerinin hesaplanması			
			elsif PROCESS_STATE = CALCULATE_FFT then
				
				--FFT_RAM'den veriler okunduktan sonra yazma işlemine geçilir
				if FFT_STATE = READ_RAM then
					FFT_STATE := WRITE_RAM;
					
				--Yazma işleminden sonra tekrar okuma işlemine geçilir	
				elsif FFT_STATE = WRITE_RAM then
					FFT_STATE := READ_RAM;
					
					--FFT hesabında kullanılan değişkenlerin güncellenmesi
					--FFT_S: Satır indexi
					--FFT_K: Küme indexi
					--FFT_E: Eleman indexi
					
					--Eleman sonuna ulaşıldı ise
					if FFT_E = 2**FFT_S - 1 then
						--Küme sonuna da ulaşıldı ise
						if FFT_K = 2**(FFT_Power - FFT_S - 1) - 1 then
							--Satır sonuna da ulaşıldı ise
							if FFT_S = FFT_Power - 1 then
								--FFT işlemi bitmiştir
								--Başa dön ve ADC verilerini tekrar örnekle
								PROCESS_STATE := SAMPLING_ADC_DATA;
								FFT_S := 0;
								FFT_K := 0;
								FFT_E := 0;							
							else --Satır sonuna ulaşılmadı ise
								--Bir sonraki satıra geç
								--Küme ve dizi değişkenlerini sıfırla
								FFT_S := FFT_S + 1;
								FFT_K := 0;
								FFT_E := 0;
							end if;		
						else --Küme sonuna ulaşılmadı ise
							--Bir sonraki kümeye geç
							--Dizi değişkenini sıfırla
							FFT_K := FFT_K + 1;
							FFT_E := 0;					
						end if;
					else --Eleman sonuna ulaşılmadı ise
						--Bir sonraki elemana geç
						FFT_E := FFT_E + 1;
					end if;
					
				end if;
			
			end if;
			
		end if; --rising_edge

	end process;

end Behavioral;

Quartus II programının oluşturduğu RTL şeması:

ADC ve VGA ile gerçekleştirilmiş \(2048\) noktalı CFFT uygulaması:

----------------------------------------------------------
--                      CFFT_ADC.vhd
--                     Bertan Taşkın
--                        2.6.2017
--
-- CFFT_Engine modülünü kullanak ADC'den alınan verilerin
-- FFT'sini VGA ekranda görselleştiren uygulama
--
--
----------------------------------------------------------


--Kütüphaneler
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
USE ieee.numeric_std.ALL; 
use ieee.math_real.all;

entity CFFT_ADC is
	port(Clk : in std_logic;                        --Clock Girişi
		R, G, B : out std_logic_vector(0 downto 0);  --VGA Arayüzünün Renk Sinyalleri
		HSync, VSync : out std_logic;                --VGA Arayüzünün Senkronizasyon Sinyalleri
		CS, SCLK, DIN : out std_logic;               --ADC Entegresinin kontrol Snyalleri
		DOUT : in std_logic);
end CFFT_ADC;

architecture Behavioral of CFFT_ADC is
	
component CFFT_Engine
	generic(FFT_Power : integer := 3;                                                                           --FFT Kuvveti
		Data_Width : integer := 8;                                                                               --İşlenecek Verinin Boyutu
		Sampling_Cycle : integer := 250);                                                                        --ADC İçin Sampling Cycle Süresi
		                                                                                                         --FFT İçin Örnekleme Frekansı = Clk / Sampling_Cycle
		
	port(Clk : in std_logic;                                                                                    --Clock Girşi
		DataIn : in std_logic_vector(Data_Width - 1 downto 0);                                                   --ADC Veri Giriş Sinyali
		Reading : out std_logic;                                                                                 --ADC Okuması Yapılıp Yapılmadığını Belirten Sinyal
		DataIndex : out std_logic_vector(FFT_Power - 1 downto 0);                                                --O Anki Okunan ADC Verisinin Sırası
		
		                                                                                                         --FFT Hesaplmalarında Kullanılacak RAM'in Sinyalleri
		FFT_RAM_PORT1_DIN, FFT_RAM_PORT2_DIN : out std_logic_vector((Data_Width + FFT_Power) * 2 - 1 downto 0);  --Veri Giriş Sinyalleri
		FFT_RAM_PORT1_DOUT, FFT_RAM_PORT2_DOUT : in std_logic_vector((Data_Width + FFT_Power) * 2 - 1 downto 0); --Veri Çıkış Sinyalleri
		FFT_RAM_PORT1_ADDRESS, FFT_RAM_PORT2_ADDRESS : out std_logic_vector(FFT_Power - 1 downto 0);             --Adres Sinyalleri
		FFT_RAM_PORT1_WRE, FFT_RAM_PORT2_WRE : out std_logic;                                                    --Yazma Aktif Sinyalleri
		                                                                             
	                                                                                                            --Çıkış Verilerinin Tutulacağı RAM'in Sinyalleri																										  
		OUTPUT_RAM_DIN : out std_logic_vector(Data_Width + FFT_Power - 1 downto 0);                              --Veri Giriş Sinyali
		OUTPUT_RAM_WR_ADDRESS : out std_logic_vector(FFT_Power - 2 downto 0);                                    --Adres Sinyali
		OUTPUT_RAM_WREN : out std_logic);                                                                        --Yazma Aktif Sinyali
end component;

--2048x36bit Dual-Port RAM
--Single Clock, Çıkış asenkron
component FFT_RAM
	PORT
	(
		address_a		: IN STD_LOGIC_VECTOR (10 DOWNTO 0);
		address_b		: IN STD_LOGIC_VECTOR (10 DOWNTO 0);
		clock		: IN STD_LOGIC  := '1';
		data_a		: IN STD_LOGIC_VECTOR (35 DOWNTO 0);
		data_b		: IN STD_LOGIC_VECTOR (35 DOWNTO 0);
		wren_a		: IN STD_LOGIC  := '0';
		wren_b		: IN STD_LOGIC  := '0';
		q_a		: OUT STD_LOGIC_VECTOR (35 DOWNTO 0);
		q_b		: OUT STD_LOGIC_VECTOR (35 DOWNTO 0)
	);
end component;

--1024x18bit SemiDual-Port RAM
--Dual Clock(Read-write), Çıkış asenkron
component OUTPUT_RAM
	PORT
	(
		data		: IN STD_LOGIC_VECTOR (17 DOWNTO 0);
		rdaddress		: IN STD_LOGIC_VECTOR (9 DOWNTO 0);
		rdclock		: IN STD_LOGIC ;
		wraddress		: IN STD_LOGIC_VECTOR (9 DOWNTO 0);
		wrclock		: IN STD_LOGIC  := '1';
		wren		: IN STD_LOGIC  := '0';
		q		: OUT STD_LOGIC_VECTOR (17 DOWNTO 0)
	);
end component;

--ADC kontrolcüsü
component ADC
	port(Clk : in std_logic;                         --Clock Girişi
		CS, SCLK, DIN : out std_logic;                --ADC Entegresinin CS, SCLK ve DIN Girişleri
		DOUT : in std_logic;                          --ADC Entegresinin DOUT Çıkışı
		ADCOut : out std_logic_vector(11 downto 0));	 --ADC'den okunan 12 bitlik sinyal
end component;

--VGA kontrolcüsü
component VGA
	generic(RGBLength : integer := 1);                               --R, G, B sinyallerinin genişliği
	                                                                 --(Renk derinliği)
																						  
	port(Clk : in std_logic;                                         --Clk girişi
	
		X, Y : out std_logic_vector(15 downto 0);                     --X,Y koordinatları
		                                                              --Bu sinyaller o an hangi pixelin DAC'ye
																						  --gönderileceğini belirtir.
		Rin, Gin, Bin : in std_logic_vector(RGBLength - 1 downto 0);  --R, G, B bilgileri
		                                                              --Okunan X, Y sinyallerine göre istenen 
																						  --RGB verisi bu sinyaller tarafında gönderilir.
		PixelClkOut : out std_logic;                                  --Pixel Clock çıkışı
		                                                              --Monitöre giden sinyaller
		R, G, B : out std_logic_vector(RGBLength - 1 downto 0);       --DAC'ye gönderilercek RGB sinyalleri
		HSync, VSync : out std_logic);                                --Yatay ve Dikey senkronizasyon sinyalleri
end component;

	signal FFT_RAM_PORT1_DIN, FFT_RAM_PORT2_DIN : std_logic_vector(35 downto 0);
	signal FFT_RAM_PORT1_DOUT, FFT_RAM_PORT2_DOUT : std_logic_vector(35 downto 0);
	signal FFT_RAM_PORT1_ADDRESS, FFT_RAM_PORT2_ADDRESS : std_logic_vector(10 downto 0);
	signal FFT_RAM_PORT1_WRE, FFT_RAM_PORT2_WRE : std_logic;
	
	signal OUTPUT_RAM_DIN, OUTPUT_RAM_DOUT : std_logic_vector(17 downto 0);
	signal OUTPUT_RAM_WR_ADDRESS : std_logic_vector(9 downto 0);
	signal OUTPUT_RAM_RD_ADDRESS : std_logic_vector(9 downto 0);
	signal OUTPUT_RAM_WREN : std_logic;
		
	signal DataIn : std_logic_vector(6 downto 0);
	signal DataIndex : std_logic_vector(10 downto 0);
	signal Reading : std_logic;
	
	signal X, Y : std_logic_vector(15 downto 0);
	signal Rin, Gin, Bin : std_logic_vector(0 downto 0);
	signal Pixel_Clock : std_logic;
	
	signal ADC_Data : std_logic_vector(11 downto 0);
		
begin
	
	--ADC verisinden 63 çıkartılarak [64,-63] aralığında veri elde edilir.
	--Bu şekilde AC bir sinyal elde edilir
	DataIn <= std_logic_vector(signed(unsigned(ADC_Data(11 downto 5))) - 63);

	--CFFT motorunun üretilmesi
	--2^11 = 2048 nokta
	--7 bit veri boyutu
	--50Mhz clock frekansı
	--250 sampling cycle: 50Mhz/250 = 200Khz
	--200Khz/2 = 100Khz spektrum
	--100khz/1024 = 97.7hz çözünürlük 
	CFFT : CFFT_Engine
		generic map(11, 7, 250)
		port map(Clk,
			DataIn, Reading, DataIndex,
			FFT_RAM_PORT1_DIN, FFT_RAM_PORT2_DIN,
			FFT_RAM_PORT1_DOUT, FFT_RAM_PORT2_DOUT,
			FFT_RAM_PORT1_ADDRESS, FFT_RAM_PORT2_ADDRESS,
			FFT_RAM_PORT1_WRE, FFT_RAM_PORT2_WRE,
			OUTPUT_RAM_DIN,
			OUTPUT_RAM_WR_ADDRESS,
			OUTPUT_RAM_WREN);
	
	--FFT işleminde kullanılacak RAM'in üretilmesi
	RAM1 : FFT_RAM
		port map(FFT_RAM_PORT1_ADDRESS, FFT_RAM_PORT2_ADDRESS,
			Clk,
			FFT_RAM_PORT1_DIN, FFT_RAM_PORT2_DIN,
			FFT_RAM_PORT1_WRE, FFT_RAM_PORT2_WRE,
			FFT_RAM_PORT1_DOUT, FFT_RAM_PORT2_DOUT);
	
	--FFT işlemi sonucunda hesaplanan magnitude karelerin depolandığı RAM'in üretilmesi
	RAM2 : OUTPUT_RAM
		port map(OUTPUT_RAM_DIN,
			OUTPUT_RAM_RD_ADDRESS,
			Pixel_Clock,
			OUTPUT_RAM_WR_ADDRESS,
			Clk,
			OUTPUT_RAM_WREN,
			OUTPUT_RAM_DOUT);
	
	--VGA kontrolsünün üretilmesi
	VGA_CONTROLLER : VGA
		generic map(1)
		port map(Clk, X, Y,
			Rin, Gin, Bin, Pixel_Clock,
			R, G, B, HSync, VSync);
	
   --ADC kontrolcüsünün üretilmesi
	ADC_CONTROLLER : ADC
		port map(Clk,
			CS, SCLK, DIN,
			DOUT,
			ADC_Data);
			
	--VGA display'i kontrol eden process
	process(Pixel_Clock, X, Y, OUTPUT_RAM_DOUT)
	begin
	
		--Sağdan ve soldan 208 pixel boşluk bırakılır
		--(1440 - 1024) / 2 = 208
		if unsigned(X) > 207 and unsigned(X) < 1232 then
		
			--OUTPUT_RAM'den o noktadaki FFT sonucu alınır
			OUTPUT_RAM_RD_ADDRESS <= std_logic_vector(unsigned(X) - 208)(9 downto 0);
			
			--Eğer FFT sonucu Y değerinin üzerinde ise pixele mavi bilgisi gönderilir
			if unsigned(OUTPUT_RAM_DOUT(16 downto 2)) > 899 - unsigned(Y) then
				Rin(0) <= '0'; Gin(0) <= '0'; Bin(0) <= '1';
			else
			--Değil ise beyaz gönderilir
				Rin(0) <= '1'; Gin(0) <= '1'; Bin(0) <= '1';
			end if;
			
		else
		
			--Çerçevenin dışında ise siyah gönderilir
			Rin(0) <= '0'; Gin(0) <= '0'; Bin(0) <= '0';
		
		end if;
	end process;

	
end Behavioral;

Quartus II programının oluşturduğu RTL şeması:

Kaynak kullanımı:

Toplam FFT cycle'ı: \(2\times2^{10}\times\log_2(2^{11})=22528\) Cycle

Toplam FFT süresi: \(\frac{22525\,\text{Cycle}}{50Mhz}\approx450\mu sn\)

\(2Khz\) Kare dalga:

\(5Khz\) Kare Dalga:

\(10Khz\) Kare Dalga:

\(20Khz\) Sinüs Dalga:

\(50Khz\) Sinüs Dalga:

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir