VHDL ile “moving average” filtre

Analog bir işareti yumuşatmanız, küçük genlikli gürültülerden kurtarmanız gerektiğinde yapabileceğiniz en basit şeylerden biri “moving average” filtreden geçirmektir. Türkçe “yürüyen ortalama” diyebileceğimiz bu filtre basitçe şöyle çalışır. 4 seviyelik (tab) bir filtre düşünelim. Böyle bir filtrenin çıkışında birim anda en son 4 örneğin ortalaması görünür.

t=0 anı için filtre çıkışını şöyle yazabiliriz.

q(0) = (d(0)+d(-1)+d(-2)+d(-3))/4

Burada d(-1), bir önceki örnek, d(-2) ondan da önceki örnek demektir. Formülü ‘n’ anı için yazarsak;

q(n) = (d(n)+d(n-1)+d(n-2)+d(n-3))/4

olur.

Peki bunu VHDL ile FPGA üzerinde nasıl gerçekleştireceğiz. Başlamadan önce bitmiş kodu görmek isteyenler için movaverage.vhdl.

Formülden görebileceğiniz gibi 4 adet örneğin saklanması (‘register’larda veya RAM’de olabilir), toplanması ve bölünmesi gerekiyor. Saklama kısmı tamam. Örnekleri saklayacağız. Orayı kurcalamıyoruz. Fakat 4 girişli bir toplayıcı büyük olmasa da, mesela 128 seviyelik bir ortalama istersek 128 girişli bir toplayıcı gerekecek bize. Böyle bir toplayıcı arda arda bağlanmış çok fazla, onlaaaarca lojik birim gerektireceğinden, hem çok fazla alan kullanılmış olacak hem de böyle bir toplayıcının hızı çok düşük olacaktır. O zaman formüle bakıyoruz ve filtre çıkışının aslında arada saklanan örneklere bağlı olmadığını, sadece giren ve çıkan örnekler tarafından değiştirildiğini görüyoruz. Öylese formülümüzü şöyle yeniden yazabiliriz.

q(n) = q(n-1)+d(n)/4-d(n-4)/4

Açıklarsak, q(n-1) çıkışın bir önceki değeri, d(n) ortalamaya yeni eklenen örnek d(n-4) ise artık filtreden çıkan ve sonuca etkisi olmayan örnek. Formülü şekillendirip şöyle yazıyoruz;

q(n) = q(n-1)+(d(n)-d(n-4))/4

Şimdi gördüğünüz gibi, seviye sayısı ne kadar fazla olursa olsun bir adet çıkarıcıya (aslında bu da bir toplayıcı) ve bir adet toplayıcıya ihtiyacımız var.

Bölme işlemine gelirsek, yapı itibariyle bölücüler büyük bileşenler olduklarından bölücülerden olabildiğince kaçıyoruz. Yalnız filtrenin seviye sayısını 2’nin kuvveti(4,8,16..) olacak biçimde seçersek bölme işlemi oldukça kolaylaşıyor. Zira ikili sistemde bir sayıyı 2’ye bölmek için bir defa sağa kaydırmak, 4’e bölmek için 2 defa sağa kaydırmak yeterli. Filtre tasarlarken boyutları 2’nin kuvveti olacak şekilde seçiyoruz ve fazla düşünmüyoruz.

Şimdi VHDL kısmına başlayalım. Tasarım kısmına girmeden ‘entity decleration’ımızı yazalım ve modülümüzün dışarıdan neye benzeyeceği belli olsun.

library ieee;

use ieee.std_logic_1164.all;

use ieee.numeric_std.all;

entity movaverage is

Generic(n: natural :=8; ntaps: natural :=4);

Port(datain: in std_logic_vector(n-1 downto 0);

dataout: out std_logic_vector(n-1 downto 0);

average: out std_logic_vector(n-1 downto 0);

clk: in std_logic);

end entity;

‘Generic’ tanımlamalarındaki n filtremizin kullanacağı sayıların büyüklüğü, ntaps ise seviye sayısıdır.

Şimdi modülümüzün iç kısmını tanımlayalım. Hesaplama kısmına geçmeden önce depolama kısmını halletmeliyiz. Yapacağımız şey aslında bir “shift register” fakat bitler yerine her defasında bir bit vektörü (sayılar binary olduklarından, sayı değil de bit vektör diyorum) kaydıracağız. Dolayısıyla bizim vektörleri yan yana saklayabileceğimiz bir diziye (array) ihtiyacımız var. Buyrun;

architecture unsiginput of movaverage is

–kullanılacak sinyalleri tanımlıyoruz

–bu iki satırda vektörlerimizi saklayacağımız ve kaydıracağımız

–diziyi tanımlıyoruz

type array_taps is array(0 to ntaps-1) of unsigned(n-1 downto 0);

signal taps:array_taps;

–toplam sonucu burada tutulacak

–sayılar toplandıkça büyüyeceklerinden bu vektör

–giriş vektöründen daha büyük

signal sum:unsigned(n-1+ntaps downto 0);

–ortalama sonucu da burada

signal ave:std_logic_vector(n-1+ntaps downto 0);

begin

–tasarım buraya yazılıyor, önce biraz açıklama yapacağız

end unsiginput

Dizimizi tanımlarken std_logic_vector yerine unsigned veri türünün kullanılması kafanızı karıştırmasın. Unsigned türü yapı olarak std_logic_vectorle aynı olan bir bit vektördür. Tek bir komutla std_logic_vector türüne dönüştürülebilir. Yalnız numeric_std kütüphanesinin operatörleri, fonksiyonları (toplama vs) unsigned türü için tanımlandığından bunu kullanıyoruz. Yoksa her ikisi de bellekte aynı şekilde saklanacaktır.

Şimdi kaydırma işlemini yapacak ‘process’imizi yazalım.

architecture unsiginput of movaverage is

begin

process(clk)

begin

if rising_edge(clk) then

taps(0) <= unsigned(datain);

taps(1 to taps’right) <= taps(0 to taps’right-1);

dataout <= std_logic_vector(taps(taps’right));

end if;

end process;

end unsiginput

Bu kısmın nasıl çalıştığına fazla giremeyeceğim. VHDL kodunun nasıl çalıştırıldığını ben de yeni yeni anlamaya başlıyorum. Ve maalesef kolayca açılayabilecek kadar iyi bilmiyorum. Ama satırları şöyle bir açıklayayım.

taps(0) <= unsigned(datain);

Bu satırda yeni data dizinin ilk elemanına atanıyor.

taps(1 to taps’right) <= taps(0 to taps’right-1);

Burada kaydırma işlemi yapılıyor. 4 seviyeli bir sistem için sözel ifade şöyle olurdu. 0,1 ve 2. seviyeler sırasıyla 1,2 ve 3. seviyelere yerleştirilerek kaydırma yapılıyor.

dataout <= std_logic_vector(taps(taps’right));

Ve dizinin sonundaki örnek çıkışa veriliyor. Dikkat bu ortalama sonucu değil.

Şimdi toplam ve ortalamayı hesaplayan kısımları da ekleyelim;

architecture unsiginput of movaverage is

begin

process(clk)

begin

if rising_edge(clk) then

taps(0) <= unsigned(datain);

taps(1 to taps’right) <= taps(0 to taps’right-1);

dataout <= std_logic_vector(taps(taps’right));

–toplama ve çıkarma işlemlerinin

–yapıldığı satır, vektörlerin toplanmadan önce

–resize() fonksiyonu ile en büyük vektörün boyutuna

–getirilmesine dikkat

sum <= sum+resize(unsigned(datain),sum’length)-resize(taps(taps’right),sum’length);

end if;

–bölme işlemi yapılıyor ve sonuç

–std_logic_vector’e dönüştürülüyor

ave <= std_logic_vector(sum/ntaps);

–hesaplama için kullanılan vektörlerin boyutları daha

–büyük olduğu için bu ara işlem yapılıyor

average <= ave(n-1 downto 0);

end process;

end unsiginput

Burada resize(unsigned(datain),sum’length) filtreye yeni giren data, resize(taps(taps’right),sum’length) filtreden çıkmakta olan datadır. Bölme işleminin bölme operatörü ile yapılmasına dikkat edin. VHDL’de aslında bu şekilde bölme yapmak mümkün değildir (an itibariyle bölme operatörünü destekleyen sentezleyici yok). Yalnız ‘ntaps’ yani bizim seviye sayımız her zaman 2’nin kuvvetleri şeklinde olacağından sentezleyici burada aslında basitçe bir kaydırma işlemi yapacaktır. ‘ntaps’ değerini 2’nin kuvveti olmayan bir sayıya atarsanız sentezleyici hata verecektir. Yanii vermesi lazım.

Vee bu kadar. Umarım birşeyler anlamışsınızdır. Hatta umarım bu yazıyı sonuna kadar okumuşsunuzdur. Dosyayı indirirseniz içerisinde birden fazla tasarım göreceksiniz. siginput olan unsiginput ile aynı tasarıma sahip fakat negatif sayılarla da çalışabiliyor. sigabsinput olanın girişi signed olabiliyor fakat girişin mutlak değerini alarak çalışıyor. Farklı bir amaçla tasarlandı yani. signedinput_pl olan ise biraz daha farklı bir tasarım. Özelliği ardışık (pipelined) tasarım kullanması. Ardışık tasarımların faydası üzerine de bir yazı yazmayı planlıyorum.

4 thoughts on “VHDL ile “moving average” filtre

  1. Merhaba bu kodu yazmadan önce seçmemiz gereken family kısmında hangisini seçmem gerektiği konusunda yardımcı olur musunuz? Yeni başladığım için yapamadım spartan3e yapmıştım fakat desteklemediği hatasını veriyor sürekli.

    1. Rukiye, anladığım kadarıyla Xilinx ISE ile sorun yaşıyorsun. “Family” seçimini sorduğuna göre henüz yeni bir proje oluşturma aşamasında olduğunu zannediyorum. Sana tavsiyem yeni proje oluşturmaya çalışmak yerine, elindeki hazır projelerden birini modifiye ederek çalışman. Kullandığın kitle birlikte mutlaka örnek projeler gelmiştir. Onlardan en basit olanını seçip, onun üzerine kendi kodunu ekle. Yeni bir proje oluşturmak çok da zor değil ama yeni başlayan birisi olarak mutlaka bir şeyleri gözden kaçıracaksındır. Sana bunun dışında detaylı bir cevap veremeyeceğim, çünkü uzun bir süredir Xilinx ile çalışmıyorum.

      Bir diğer tavsiye; mümkün olduğunca simülatör’de çalış. VHDL kodunun sorunsuz çalıştığından emin olduktan sonra FPGA’e geç. FPGA donanımı üzerinde debug imkanları sınırlı ve kullanması zahmetli. ActiveHDL simülatörünü öneririm. En son baktığımda ücretsiz bir öğrenci sürümü vardı.

    1. 4’ün özel bir yanı yok. Ancak seçilen sayı filtrenin tepkisini (response) belirleyecek. Daha alçak geçiren (sinyali daha fazla yumuşatan) bir filtre isterseniz bu sayıyı büyütmeniz gerekir. Yalnız 2’nün katı olan bir sayı seçmek önemli. Çünkü 2li sistemde, 2’nin katı olan bir sayı ile bölmek basit bir kaydırma işleminden ibaret. FPGA’lerde bu şekilde bölme işleminin gerçekleştirilmesi için hiç bir lojik işlem gerekmiyor. O yüzden bedava bir bölme işlemi olarak görebilirsiniz. Aksine 2’nin katı olmayan bir sayıya bölme işlemi nispeten çok fazla lojik harcanmasına sebep olabilir.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.