Ich versuche, mit DSP in meinem Spartan-3-Board zu beginnen. Ich habe ein AC97-Board mit einem Chip von einem alten Motherboard gemacht, und bisher habe ich es geschafft, ADC zu machen, die Samples für eine Zahl <1 (Lautstärke verringern) und dann DAC zu multiplizieren.
Jetzt würde ich gerne ein paar grundlegende DSP-Sachen machen, wie einen Tiefpassfilter, einen Hochpass usw. Aber ich bin wirklich verwirrt über die numerische Darstellung (Ganzzahlen? Festkomma? Q0.15? Überlauf oder Sättigung?).
Ich möchte nur einen Beispielcode eines tatsächlichen einfachen Filters, um mir den Einstieg zu erleichtern. Keine hocheffiziente, schnelle oder ähnliches. Nur der in VHDL implementierte theoretische Filter.
Ich habe gesucht, aber ich finde nur theoretische Formeln - ich verstehe, was ich nicht verstehe, ist, wie die signierten 16-Bit-48-kHz-Audio-Samples verarbeitet werden, die ich vom ADC bekomme. Ich habe diese Bibliotheken verwendet: http://www.vhdl.org/fphdl/ . Wenn ich meine Samples mit 0,5, 0,25 usw. multipliziere, kann ich den Unterschied hören. Aber ein größerer Filter gibt mir nur Rauschen.
Vielen Dank.
Es hört sich so an, als müssten Sie zuerst die DSP-Aspekte herausfinden und dann eine Implementierung in FPGA vornehmen.
In Bezug auf Datentypen können Sie problemlos ganze Zahlen verwenden.
Hier ist ein Beispielcode, um Ihnen den Einstieg zu erleichtern. Beachten Sie, dass viele reale Probleme fehlen (z. B. Zurücksetzen, Überlaufverwaltung) - aber hoffentlich aufschlussreich sind:
library ieee;
use ieee.std_logic_1164.all;
entity simple_fir is
generic (taps : integer_vector);
port (
clk : in std_logic;
sample : in integer;
filtered : out integer := 0);
end entity simple_fir;
----------------------------------------------------------------------------------------------------------------------------------
architecture a1 of simple_fir is
begin -- architecture a1
process (clk) is
variable delay_line : integer_vector(0 to taps'length-1) := (others => 0);
variable sum : integer;
begin -- process
if rising_edge(clk) then -- rising clock edge
delay_line := sample & delay_line(0 to taps'length-2);
sum := 0;
for i in 0 to taps'length-1 loop
sum := sum + delay_line(i)*taps(taps'high-i);
end loop;
filtered <= sum;
end if;
end process;
end architecture a1;
----------------------------------------------------------------------------------------------------------------------------------
-- testbench
----------------------------------------------------------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
entity tb_simple_fir is
end entity tb_simple_fir;
architecture test of tb_simple_fir is
-- component generics
constant lp_taps : integer_vector := ( 1, 1, 1, 1, 1);
constant hp_taps : integer_vector := (-1, 0, 1);
constant samples : integer_vector := (0,0,0,0,1,1,1,1,1);
signal sample : integer;
signal filtered : integer;
signal Clk : std_logic := '1';
signal finished : std_logic;
begin -- architecture test
DUT: entity work.simple_fir
generic map (taps => lp_taps) -- try other taps in here
port map (
clk => clk,
sample => sample,
filtered => filtered);
-- waveform generation
WaveGen_Proc: process
begin
finished <= '0';
for i in samples'range loop
sample <= samples(i);
wait until rising_edge(clk);
end loop;
-- allow pipeline to empty - input will stay constant
for i in 0 to 5 loop
wait until rising_edge(clk);
end loop;
finished <= '1';
report (time'image(now) & " Finished");
wait;
end process WaveGen_Proc;
-- clock generation
Clk <= not Clk after 10 ns when finished /= '1' else '0';
end architecture test;
Ein weiteres einfaches Code-Snippet (nur der Mut). Hinweis: Ich habe die VHDL nicht direkt geschrieben, ich habe MyHDL verwendet, um die VHDL zu generieren.
-- VHDL code snip
architecture MyHDL of sflt is
type t_array_taps is array(0 to 6-1) of signed (15 downto 0);
signal taps: t_array_taps;
begin
SFLT_RTL_FILTER: process (clk) is
variable sum: integer;
begin
if rising_edge(clk) then
sum := to_integer(x * 5580);
sum := to_integer(sum + (taps(0) * 5750));
sum := to_integer(sum + (taps(1) * 6936));
sum := to_integer(sum + (taps(2) * 6936));
sum := to_integer(sum + (taps(3) * 5750));
sum := to_integer(sum + (taps(4) * 5580));
taps(0) <= x;
for ii in 1 to 5-1 loop
taps(ii) <= taps((ii - 1));
end loop;
y <= to_signed(sum, 16);
end if;
end process SFLT_RTL_FILTER;
end architecture MyHDL;
Dies ist eine direkte Implementierung. Es werden Multiplikatoren benötigt. Die Synthese dieser Schaltung, die für einen Altera Cyclone III gedacht war, verwendete keine expliziten Multiplikatoren, sondern erforderte 350 Logikelemente.
Dies ist ein kleiner FIR-Filter und hat die folgende Reaktion (nicht so großartig), sollte aber als Beispiel nützlich sein.
Außerdem habe ich hier und hier ein paar Beispiele, die nützlich sein könnten.
Außerdem scheint Ihre Frage zu fragen: "Was ist eine angemessene Festkommadarstellung?" Häufig wird bei der Implementierung von DSP-Funktionen die Festkommadarstellung verwendet, weil sie die Analyse der Filter vereinfacht. Wie bereits erwähnt, ist Festkomma nur eine ganzzahlige Arthimetik. Die eigentliche Implementierung arbeitet einfach mit ganzen Zahlen, aber unsere angenommene Darstellung ist gebrochen.
Probleme treten normalerweise beim Konvertieren von Implementierungs-Integer (Festkomma) zu/von Design-Gleitkomma auf.
Ich weiß nicht, wie gut die VHDL-Festkomma- und Gleitkommatypen unterstützt werden. Sie werden in der Simulation gut funktionieren, aber ich weiß nicht, ob sie mit den meisten Synthesewerkzeugen synthetisiert werden können. Dazu habe ich eine separate Frage erstellt .
Der einfachste Tiefpass-FIR-Filter, den Sie ausprobieren können, ist y(n) = x(n) + x(n-1). Sie können dies ganz einfach in VHDL implementieren. Unten sehen Sie ein sehr einfaches Blockdiagramm der Hardware, die Sie implementieren möchten.
Gemäß der Formel benötigen Sie die aktuellen und vorherigen ADC-Samples, um die entsprechende Ausgabe zu erhalten. Was Sie tun sollten, ist, die eingehenden ADC-Samples an der fallenden Flanke des Takts zu speichern und die entsprechenden Berechnungen an der steigenden Flanke durchzuführen, um die entsprechende Ausgabe zu erhalten. Da Sie zwei 16-Bit-Werte addieren, ist es möglich, dass Sie am Ende eine 17-Bit-Antwort erhalten. Sie sollten die Eingabe in 17-Bit-Registern speichern und einen 17-Bit-Addierer verwenden. Ihre Ausgabe sind jedoch die unteren 16 Bits der Antwort. Code könnte etwa so aussehen, aber ich kann nicht garantieren, dass er vollständig funktioniert, da ich ihn nicht getestet, geschweige denn synthetisiert habe.
IEEE.numeric_std.all;
...
signal x_prev, x_curr, y_n: signed(16 downto 0);
signal filter_out: std_logic_vector(15 downto 0);
...
process (clk) is
begin
if falling_edge(clk) then
--Latch Data
x_prev <= x_curr;
x_curr <= signed('0' & ADC_output); --since ADC is 16 bits
end if;
end process;
process (clk) is
begin
if rising_edge(clk) then
--Calculate y(n)
y_n <= x_curr + x_prev;
end if;
end process;
filter_out <= std_logic_vector(y_n(15 downto 0)); --only use the lower 16 bits of answer
Wie Sie sehen können, können Sie diese allgemeine Idee verwenden, um kompliziertere Formeln hinzuzufügen, z. B. solche mit Koeffizienten. Kompliziertere Formeln wie IIR-Filter erfordern möglicherweise die Verwendung von Variablen, um die Algorithmuslogik korrekt zu erhalten. Schließlich besteht eine einfache Möglichkeit, Filter mit reellen Zahlen als Koeffizienten zu umgehen, darin, einen Skalierungsfaktor zu finden, damit alle Zahlen so nah wie möglich an ganzen Zahlen liegen. Ihr Endergebnis muss um denselben Faktor herunterskaliert werden, um das richtige Ergebnis zu erhalten.
Ich hoffe, dies kann Ihnen von Nutzen sein und Ihnen helfen, den Ball ins Rollen zu bringen.
*Dies wurde so bearbeitet, dass die Datenspeicherung und die Ausgangsspeicherung in separaten Prozessen erfolgen. Verwenden Sie auch signierte Typen anstelle von std_logic_vector. Ich gehe davon aus, dass Ihr ADC-Eingang ein std_logic_vector-Signal sein wird.
OpenCores hat eine Reihe von DSP-Beispielen, IIR und FIR, einschließlich BiQuad. Sie müssen sich registrieren, um die Dateien herunterzuladen.
Bearbeiten
Ich verstehe Kortuks Kommentar zu toten Links, und in der Tat, wenn der Link zu OpenCores stirbt, wird die Antwort nutzlos. Ich bin ziemlich zuversichtlich, dass dies nicht passieren wird; mein Link ist ein generischer Link, und er wird nur sterben, wenn die komplette OpenCores-Domäne verschwinden würde.
Ich habe versucht, nach einigen Beispielen zu suchen, die ich für diese Antwort verwenden könnte, aber sie sind alle zu lang, um hier dargestellt zu werden. Also bleibe ich bei meinem Rat, mich selbst für die Seite zu registrieren (ich musste nach New York umziehen, weil mein Heimatort nicht akzeptiert wurde) und mir den dort präsentierten Code ansehen.
Ich habe versucht, Skripte für die automatische Implementierung von IIR-Filtern zu implementieren, in denen Sie definieren können, ob das Design so schnell wie möglich sein soll (damit jede Multiplikation mit einem eigenen Multiplikator durchgeführt wird) oder so klein wie möglich (damit jeder Multiplikator wiederverwendet wird).
Die Quellen wurden auf alt.sources als „Behavioral but synthesizable implementierung of IIRfilters in VHDL“ veröffentlicht (auch im Google-Archiv zu finden: https://groups.google.com/group/alt.sources/msg/c8cf038b9b8ceeec ?dmode=Quelle )
Posts an alt.sources sind im „shar“-Format, also müssen Sie die Nachricht als Text speichern und die Freigabe aufheben (mit dem „unshar“-Dienstprogramm), um Quellen zu erhalten.
Wie wäre es damit? https://github.com/MauererM/VIIRF
Es implementiert einen auf Biquad (SOS, Abschnitte zweiter Ordnung) basierenden IIR-Filter, der sich um die Festkommaimplementierung kümmert. Es enthält auch Python-Skripte für das Design und die Überprüfung des Filters. Es verwendet keine herstellerspezifischen FPGA-Konstrukte, und Sie können den Kompromiss zwischen hoher Geschwindigkeit und geringer Flächennutzung wählen.
Benutzer3624