Hallo zusammen,
ich versuche aktuell, einen "einfachen" Tiefpass in VHDL als FIR Filter
zum Laufen zu bekommen. Ich habe aus den gewünschten Parametern mir per
Matlab die Filterkoeffizienten ausspucken lassen. Insgesamt sind es 27
Koeffizienten. Als Zahlendarstellung habe ich mich auf fixed Point 1.11
festgelegt, da der AD Wandler, der das analoge Signal einließt, ein
digitales 12 Bit Wort als Ergebnis der Wandlung ausgibt. Ich habe mir
mit Matlab jetzt mal einen Sinus mit 200Hz Grundfrequenz, überlagert mit
einem weiteren Sinus mit 10kHz Frequenz erstellt. Das wird dann über die
Soundkarte des PC's auf den AD Wandler gegeben. Das Eingangssignal habe
ich angehängt. Das Offset habe ich absichtlich auf das Eingangssignal
gegeben, weil der AD Wandler nur im positiven Spannungsbereich arbeitet.
Ich füge hier mal den VHDL Code des FIR Filters ein. Ich habe den
weitestgehend von diesem Link übernommen:
http://public.beuth-hochschule.de/~marganit/Implementierung%20digitaler%20Filter%20auf%20FPGA.pdf
Die Rundung nach der Multiplikation erfolgt per Truncate.
1
--
2
--Implementierung der Direktform eines FIR - Tiefpasses
3
--Kennwerte: Abtastrate: 50 kHz
4
--f_Durchlass = 0,8kHz
5
--f_stopp: 5000kHz bei delta_s = 40dB Absenkung
6
--passband Ripple = 0,1db
7
8
library IEEE;
9
use IEEE.Std_logic_1164.all;
10
use IEEE.Numeric_Std.all;
11
12
13
entity FIR_Test is
14
generic(N : integer := 26); --Anzahl der Koeffizienten = Ordnung des FIR Filters
15
port
16
(
17
x_in : in std_logic_vector(11 downto 0); --Input 1.11
18
clk : in std_logic;
19
y : out std_logic_vector(11 downto 0) --Output 1.11
20
);
21
end FIR_Test;
22
23
architecture FIR_Test_arch of FIR_Test is
24
25
type tap_line is array(0 to N) of std_logic_vector(11 downto 0); --Typenerklärung: Array zum Verschieben
26
-- =(Zeitverzögern) des Inputs
27
type table is array(0 to N) of signed(11 downto 0); --Typenerklärung: Array aus Filterkoeffizienten
28
29
signal x : tap_line;
30
constant coeff : table:= (
31
X"FF0",
32
X"FEF",
33
X"FEC",
34
X"FEC",
35
X"FF3",
36
X"002",
37
X"01B",
38
X"03D",
39
X"065",
40
X"091",
41
X"0BA",
42
X"0DB",
43
X"0F2",
44
X"0FA",
45
X"0F2",
46
X"0DB",
47
X"0BA",
48
X"091",
49
X"065",
50
X"03D",
51
X"01B",
52
X"002",
53
X"FF3",
54
X"FEC",
55
X"FEC",
56
X"FEF",
57
X"FF0"
58
); --Koeffiziententabelle, von a_26 bis a_0
59
--Darstellung: signed 1.11
60
begin
61
62
63
process(clk)
64
variable sop : signed(23 downto 0); --Variable für Zwischenergebnis der Multiplikation, Darstellung 2.22
65
begin
66
if rising_edge(clk) then
67
for i in N downto 1 loop
68
x(i) <= x(i - 1); --(Zeit-)Verschieben des Inputs
69
end loop;
70
x(0) <= x_in; --Neues Datum einlesen
71
y <= std_logic_vector(sop(22 downto 11)); --Truncate: Erstes Vorzeichenbit und die folgenden 11
72
--Nachkommastellen übernehmen, um wieder 1.11 Format zu haben
73
end if;
74
sop := X"000000"; --Zurücksetzen des Zwischenergebnisses
75
for i in 0 to N loop
76
sop := sop + coeff(i) * signed(x(i));
77
end loop;
78
end process;
79
80
end FIR_Test_arch;
Das Clock Signal für den Filter kommt vom AD - Wandler. Wenn dieser neue
Daten hat, sendet dieser einen kurzen Impuls. Der Filter reagiert auf
die steigende Flanke dieses Impulses.
So, jetzt zu meiner Frage: Ich habe leider so gut wie keine Erfahrung in
der Signalverarbeitung. Mein Ausgangssignal sieht jetzt aus, wie im 2.
Bild. Ich erkenne zwar die 200Hz Frequenz wieder, aber das
Eingangssignal ist ja nicht mehr wiederzuerkennen. Woran liegt das? Ist
meine Signalverarbeitung stark fehlerhaft? Ist die Filterordnung zu
klein?
Liegt die Abtastrate zu hoch? So wie ich das verstanden habe, ist eine
viel zu hohe Abtastrate problematisch, weil sich in die Koeffizienten
dann in meiner Zahlendarstellung sehr nahe 1 bewegen, das ist bei mir
jedoch nicht der Fall. Bezüglich des Vorzeichenfehlers kann ich Dir
leider nicht folgen.
sieht für mich so aus, als wären in deiner Signalkette die
Vorzeichendarstellung in Zweierkomplement und in binary offset gemischt.
Schau mal im Datenblatt von ADC (und ggf. DAC) nach, welches
Zahlenformat sie verwenden.
Oha, ich habe mir jetzt erst deinen Code im Detail angeschaut. Du hast
einen schrecklichen Mix aus synchronem und asynchronem Design. Mach das
bitte richtig (d.h. die Berechnung innnerhalb des Prozesses in den "if
risign_edge(clk)" mit hinein nehmen).
Und du versuchst, alle Multiplikationen und Additionen des Filters
innerhalb eines CLK-Zyklus zu berechnen. Das wird nur bei sehr
langsamem Takt funktionieren, ansonsten "verrechnet" sich dein FPGA. Du
solltest zumindest ein timing-Constraints auf den Taktzyklus setzen.
Achim S. schrieb:> Schau mal im Datenblatt von ADC (und ggf. DAC) nach, welches> Zahlenformat sie verwenden.
Der AD - Wandler sendet mir die 12 Bit nach jeder Wandlung per I2C Bus.
Ich erhalte also Werte zwischen 0 und 4095. Die einzelnen Bits dazu
speichere ich in einem 12 Bit std logic Vector und stelle diesen Vektor
dem Filter als Input zur Verfügung. So gesehen benutzt der AD Wandler
doch gar kein Zahlenformat, sondern die Interpretation in ein
Zahlenformat nehme ich doch selbst vor? Der DA Wandler erhält ebenfalls
sequentiell 12 Bit. Wenn ich in einer Darstellungsform bleibe, sollte
der DA Wandler doch etwas sinnvolles herausgeben, oder?
Achim S. schrieb:> sieht für mich so aus, als wären in deiner Signalkette die> Vorzeichendarstellung in Zweierkomplement und in binary offset gemischt.
Das kann ich irgendwie nicht nachvollziehen. Mein Plan war, durchgängig
eine 1.11 Fixed Point Darstellung zu erreichen. Durch die Multiplikation
zweier 1.11 Zahlen entsteht ja eine 2.22 Zahl, davon nehme ich dann das
niedrigste Vorzeichenbit und die folgenden 11 Nachkommastellen und
erhalte so wieder meine 1.11 Darstellung, indem ich den Rest einfach
verwerfe.
Könntest Du mir zeigen, wo ich Zweierkomplement und binary offset
vermische?
Achim S. schrieb:> Und du versuchst, alle Multiplikationen und Additionen des Filters> innerhalb eines CLK-Zyklus zu berechnen. Das wird nur bei sehr> langsamem Takt funktionieren, ansonsten "verrechnet" sich dein FPGA. Du> solltest zumindest ein timing-Constraints auf den Taktzyklus setzen.
Das war auch eine Frage, die ich mir noch nicht beantworten konnte,
nämlich wie schnell eigentlich die for - Schleifen innerhalb eines
Prozesses ablaufen. Wie kann man sich das vorstellen? Ich habs ja
offenbar schlecht gemacht. In dem Zusammenhang habe ich leider nicht
verstanden, was Du damit meinst, einen "timing constraints auf den
Taktzyklus zu setzen". Meinst Du damit, dass ich in dem constraints file
noch einen zusätzlichen Takt erstellen soll, mit dem ich den Filter
versorge? Allerdings wäre die steigende Flanke dann ja dieselbe, die ich
auch vom ADC - Startsignal erhalte. --Könnte man alternativ die for
Schleife mit der Multiplikation außerhalb des Prozesses setzen, sodass
diese quasi durchgängig durchgeführt wird?
Achim S. schrieb:> Oha, ich habe mir jetzt erst deinen Code im Detail angeschaut. Du hast> einen schrecklichen Mix aus synchronem und asynchronem Design. Mach das> bitte richtig (d.h. die Berechnung innnerhalb des Prozesses in den "if> risign_edge(clk)" mit hinein nehmen).
>Das war auch eine Frage, die ich mir noch nicht beantworten konnte,>nämlich wie schnell eigentlich die for - Schleifen innerhalb eines>Prozesses ablaufen.
Es gibt keine "for" Schleifen in VHDL. Es sieht aus wie eine Schleife,
ist aber keine. Stell diese Frage im FPGA-Forum hier.
Carl schrieb:>>Das war auch eine Frage, die ich mir noch nicht beantworten konnte,>>nämlich wie schnell eigentlich die for - Schleifen innerhalb eines>>Prozesses ablaufen.>> Es gibt keine "for" Schleifen in VHDL. Es sieht aus wie eine Schleife,> ist aber keine. Stell diese Frage im FPGA-Forum hier
Habs mir eben auch durchgelesen. Die for Schleife bedeutet in dem
Zusammenhang, dass der Block innerhalb der for - Schleife dupliziert
wird. Mit meinen anderen Problemen bin ich bisher leider nicht
weitergekommen.
Carl schrieb:>>Woran liegt das?>> Am Vorzeichenfehler und der Abtastrate.
Könntest Du deinen vorigen Post vielleicht weiter erläutern?
Alex K. schrieb:> So gesehen benutzt der AD Wandler> doch gar kein Zahlenformat, sondern die Interpretation in ein> Zahlenformat nehme ich doch selbst vor?
Der ADC liefert dir die Daten natürlich auch in einem definierten
Zahlenformat. Nach deiner Beschreibung tippe ich auf einfache Dualzahl
(nur positive Werte darstellbar). Wenn bei diesem Signal das
höchstwertige Bit gesetzt ist (d.h. der Momentanwert größer als Vref/2
ist), dann wird deine Berechnung mit "signed(x(i))" das
fälschlicherweise als negative Zahl interpretieren.
Alex K. schrieb:> Könntest Du mir zeigen, wo ich Zweierkomplement und binary offset> vermische?
Nein, kann ich leider nicht. Weil ich nicht weiß, welchen ADC und
welchen DAC du verwendest. Deswegen wäre es nett, wenn du sowas gleich
zu Beginn des Threads verrätst, dann kann man zielgenauer antworten. Die
"senkrechten Sprünge" in deinem Ausgangssignal erinnern mich aber stark
an die Situation, dass Zweierkomplement mit binary offset vermischt
wird. Daher meine Vermutung, dass das hier auftreten könnte. Und wenn du
eine unipolare Dualzahl einfach mit "signed" typecastest, dann kommt in
der Hälfte der Fälle das falsche Ergebnis raus.
Alex K. schrieb:> for i in 0 to N loop> sop := sop + coeff(i) * signed(x(i));> end loop;> Damit meinst du den Teil?
Ja, den Teil meine ich.
Alex K. schrieb:> Das war auch eine Frage, die ich mir noch nicht beantworten konnte,> nämlich wie schnell eigentlich die for - Schleifen innerhalb eines> Prozesses ablaufen.
Die For-schleife im Prozess wird bei der Synthese in eine parallele
Logik umgewandelt, die alle Multiplikationen und Additionen gleichzeitig
zu berechnen versucht. Wie schnell diese Logik auf Änderungen am Eingang
reagiert, hängt von der Durchlaufzeit des Signals durch diesen recht
großen Logikblock ab. Diese Geschwindigkeit gibt dir dann die maximal
erlaubte Taktfrequenz vor.
Um einen Eindruck zu bekommen, was du damit zusammengebastelt hast: lass
dir vom Synthesetool mal das RTL-Schematic der erzeugten Schaltung
anzeigen.
Um sicher zu sein, dass das FPGA "richtig rechnet": erzeuge ein
Constraint für dein Taktssignal (das dem Synthesetool mitteilt, wie viel
Zeit für die Berechnung zwischen zwei Taktflanken zur Verfügung steht).
Dann kann dir das Synthesetool mitteilen, ob deine Implementierung
schnell genug ist und funktionieren wird oder nicht.
Wenn du noch verrätst, welches Synthesetool du verwendest, kann man ggf.
auch einen Tipp geben, wie das constraint am besten erzeugt wird. In der
Xilinx-ISE gibt es dafür z.B. den Befehl "Create Timing Constraints",
mit dessen Hilfe du ggf. ein CLK Period Constraint für das Signal CLK
erzeugen kannst.
Achim S. schrieb:> Der ADC liefert dir die Daten natürlich auch in einem definierten> Zahlenformat. Nach deiner Beschreibung tippe ich auf einfache Dualzahl> (nur positive Werte darstellbar). Wenn bei diesem Signal das> höchstwertige Bit gesetzt ist (d.h. der Momentanwert größer als Vref/2> ist), dann wird deine Berechnung mit "signed(x(i))" das> fälschlicherweise als negative Zahl interpretieren.
Stimmt, das habe ich komplett außer Acht gelassen... Mir fällt es im
Augenblick schwer, mir dafür eine Lösung vorzustellen. Die negativen
Werte brauche ich ja eigentlich, weil die Filterkoeffizienten ja zum
Teil negativ sind.
Achim S. schrieb:> Nein, kann ich leider nicht. Weil ich nicht weiß, welchen ADC und> welchen DAC du verwendest. Deswegen wäre es nett, wenn du sowas gleich> zu Beginn des Threads verrätst, dann kann man zielgenauer antworten. Die> "senkrechten Sprünge" in deinem Ausgangssignal erinnern mich aber stark> an die Situation, dass Zweierkomplement mit binary offset vermischt> wird. Daher meine Vermutung, dass das hier auftreten könnte. Und wenn du> eine unipolare Dualzahl einfach mit "signed" typecastest, dann kommt in> der Hälfte der Fälle das falsche Ergebnis raus.
Entschuldige bitte. Ich bin davon ausgegangen, dass du dich hiermit auf
meinen geposteten Code beziehst. Das gesamte AD - Interface ist das Pmod
AD2 von Digilent, der hier verwendete AD - Wandler (ich benutze nur
einen Kanal) ist der Analog Devices AD7991. Das DA Wandler Interface ist
das Pmod DA2 von Digilent, darauf kommt der Texas Instruments DAC121S101
zum Einsatz.
Achim S. schrieb:> Wenn du noch verrätst, welches Synthesetool du verwendest, kann man ggf.> auch einen Tipp geben, wie das constraint am besten erzeugt wird. In der> Xilinx-ISE gibt es dafür z.B. den Befehl "Create Timing Constraints",> mit dessen Hilfe du ggf. ein CLK Period Constraint für das Signal CLK> erzeugen kannst.
Ich benutze Xilinx Vivado. Ich ergoogle mir mal den Befehl.
Ich hänge auch mal mein constraints file an.
Alex K. schrieb:> Mir fällt es im> Augenblick schwer, mir dafür eine Lösung vorzustellen.
Dabei ist es in dem Fall einfach. Wenn du eine 12 Bit breite positive
Dualzahl hast (das liefert der AD7991 tatsächlich, siehe Fig. 20 im
Datenblatt), dann kannst du korrekt eine 13 Bit breite signed-Zahl
daraus machen, indem du links eine Null anhängst.
Alex K. schrieb:> darauf kommt der Texas Instruments DAC121S101> zum Einsatz
ok, auch der ist unipolar. Wenn aus deiner Berechnung mal zufällig eine
negative Zahl rauskommen sollte, dann wird er das Ergebnis falsch
umsetzen. Solange nur positive Werte aus deiner Berechnung rauskommen,
kannst du das höchstwertige Bit (das dann immer Null ist) weglassen und
die folgenden Bits in den DAC füttern.
Alex K. schrieb:> Ich benutze Xilinx Vivado.
Dann mache mal wie oben vorgeschlagen: schau dir in
RTL-Analysis->Schematic das Ergebnis deines Codes an. Und dann erzeuge
zum Vergleich mal einen FIR-Filter mit 26 Taps über den IP-Generator und
schau, wie er das löst. Man kann natürlich im Prinzip alle
Multiplikationen und Additionen von einer Riesenlogik durchführen lassen
und das dann mit 50kHz takten (und hoffen, dass es aufgeht). Aber die
normale Rangehensweise sieht deutlich anders aus.
Ein typischer Ansatz wäre:
- du taktest den Filter mit einer "schnellen" Clock (z.B. 125MHz deines
Main_CLK).
- Sobald ein neues Sample am Eingang liegt (alle 20µs ein mal) nutzt du
per einfacher Statemachine die nächsten 26 Zyklen des 125MHz-Taktes, um
pro Takt jeweils eine Multiplikation und Addition durchzuführen.
- Nach ca. 26 Zyklen hast du dann eine neues Sample am Filterausgang
vorliegen und signalisierst das per Valid-Flag am Filterausgang.
Wenn du den IP-Generator deines Synthesetools benutzt, packt er dir das
gleich alles in einer handlichen Komponente zusammen.
Alex K. schrieb:> also für das 50kHz> Signal dann mit
Ist das 50kHz-Signal aus Sicht des FPGAs denn ein Logiksignal oder ein
Taktsignal? Geht er auf einen Clock-Eingang deines FPGAs und läuft er
innerhalb des FPGAs über den Clock-Tree? Andernfalls wäre es gefährlich,
das als Taktsignal zu verwenden. Und du müsstest dir in jedem Fall
Gedanken machen, wie du mit korrekten Signalen aus der 50kHz-Takt-Domäne
wieder in die Main-Clock-Domäne zurückkommst.
Es klingt jetzt vielleicht ein wenig demotivierend, aber: um einen
Einstieg in VHDL zu bekommen hast du dir ein Beispielprojekt ausgesucht,
bei dem du viele Baustellen gleichzeitig beackern musst. Ohne ein Stück
weit das Grundkonzept der Hardwarebeschreibung per VHDL zu kennen ist
das nicht einfach. Und das Dokument der Beuth-Hochschule, aus dem du
deinen Code abgeleitet hast, finde ich für einen Einsteiger nicht
besonders gut geeignet.
Wenn es dir um die schnelle Implenetierung eines FIR geht würde ich
stattdessen empfehlen, mit dem IP-Generator von Vivado zu arbeiten und
dir dessen Dokumentation durchzulesen.
(Project Manager -> IP Catalog -> FIR Compiler und
https://www.xilinx.com/support/documentation/ip_documentation/fir_compiler/v7_1/pg149-fir-compiler.pdf)
Und dabei wirst du vielleicht zur Erkenntnis kommen, dass du zunächst
besser mit einem einfacheren Projekt einsteigst.
Achim S. schrieb:> Dabei ist es in dem Fall einfach. Wenn du eine 12 Bit breite positive> Dualzahl hast (das liefert der AD7991 tatsächlich, siehe Fig. 20 im> Datenblatt), dann kannst du korrekt eine 13 Bit breite signed-Zahl> daraus machen, indem du links eine Null anhängst.
Das werde ich dann zunächst mal versuchen.
Achim S. schrieb:> Ein typischer Ansatz wäre:> - du taktest den Filter mit einer "schnellen" Clock (z.B. 125MHz deines> Main_CLK).> - Sobald ein neues Sample am Eingang liegt (alle 20µs ein mal) nutzt du> per einfacher Statemachine die nächsten 26 Zyklen des 125MHz-Taktes, um> pro Takt jeweils eine Multiplikation und Addition durchzuführen.> - Nach ca. 26 Zyklen hast du dann eine neues Sample am Filterausgang> vorliegen und signalisierst das per Valid-Flag am Filterausgang.
Das leuchtet ein, auch das werde ich versuchen, umzusetzen.
Achim S. schrieb:> Wenn du den IP-Generator deines Synthesetools benutzt, packt er dir das> gleich alles in einer handlichen Komponente zusammen.
Werde ich mir auch ansehen. Wobei ich die Dokus leider nicht sehr gut
verständlich finde. Muss ich dann wohl durch.
Achim S. schrieb:> Und dabei wirst du vielleicht zur Erkenntnis kommen, dass du zunächst> besser mit einem einfacheren Projekt einsteigst.
Die Möglichkeit habe ich leider nicht, da das Ganze Teil einer
Bachelorarbeit ist. Mal sehen, wie weit ich damit komme :) Vielen Dank
auf jeden Fall für die Hilfestellung!
Ich habe nochmal eine Nachfrage zu der Zahlendarstellung. Also: Ich
erhalte vom A/D Wandler eine 12 Bit Dualzahl. Dort hänge ich jetzt vorn
eine 0 drann, das bedeutet ich könnte als Fixed Point Darstellung mit
1.12 Aufteilung Zahlen von -1 bis (1-2^-12) darstellen. Da ich aber eine
0 vorne dranhänge, sind alle Werte, die vom A/D Wandler kommen,
automatisch immer positiv. Also habe ich den Darstellungsbereich
verkleinert auf 0 - (1-2^-12). Für die Filterkoeffizienten möchte ich ja
jetzt dieselbe Darstellung wählen. Sagen wir, ich berechne die
Filterkoeffizienten mit Matlab. Matlab gibt diese Filterkoeffizienten
als Dezimalzahl aus. Muss ich diese Filterkoeffizienten dann direkt als
1.12 Darstellung umsetzen, oder zunächst ebenfalls als 0.12 und dann
eine 0 vor dranhängen, um auf 1.12 zu gelangen? Das leuchtet mir gerade
nicht wirklich ein.
Ob du signed oder unsigned betrachtest ist für die Rechnung wichtig:
wenn du hier etwas falsch machst, liefert dir dein FPGA falsche
Ergebnisse. Wenn dein ADC den Bitstring
hex BB8
bin 1011 1011 1000
dec 3000
liefert, dann steht er für die positive Spannung 3000*(Vref/4096).
Wenn du diesen Bitstring in der Rechnung auf signed castest, dann wird
der Bitstring falsch als negativer Wert -1096 interpretiert und dein
Rechenergebnis ist falsch. Du kannst den Bitstring aber zuvor um eine
Null ganz links ergänzen. Dann ist er 13 Bit breit, er wird auch mit dem
typcast auf signed korrekt als positiver Wert interpretiert.
Die Hälfte des Zahlenbereichs nutzt du dann natürlich tatsächlich nicht
(du hast nie Werte in der negativen Hälfte, aber dein ADC liefert ja
auch nur positive Werte).
Ob du die Zahlenwerte als Ganzzahlen oder als Festkommazahlen
betrachtest, ist der Rechnung dann aber weitgehend egal. Das ist nur
eine Interpretation von dir, ob der Wert ein Vielfaches von 1 angibt
(Ganzzahl) oder ein Vielfaches von 1/4096 (1.12 Festkommazahl). Binär
sieht der Wert beide male gleich aus, und bei den FIR-Rechnungen wird
ihn dein FPGA gleichermaßen behandeln. Du musst deine Interpration nur
konsistent anwenden (d.h. den Bits im Ergebnis der Rechnung die richtige
Wertigkeit zuordnen).
Ich persönlich würde eine solche FIR-Berechnung immer als
Ganzzahlenrechnung betrachten. Dann stellst sich eine solche Frage
praktisch nicht:
Alex K. schrieb:> Muss ich diese Filterkoeffizienten dann direkt als> 1.12 Darstellung umsetzen, oder zunächst ebenfalls als 0.12 und dann> eine 0 vor dranhängen, um auf 1.12 zu gelangen?
Wenn du bei der Festkommabetrachtung bleiben willst, dann lies ggf. bei
Wikipedia nach:
https://de.wikipedia.org/wiki/Zweierkomplement#Zweierkomplementdarstellung_bei_Festkommazahlen
Dann siehst du, dass du natürlich nicht einfach eine Null ans linke Ende
stellen darfst (sonst könntest du keine negativen Koeffizienten
darstellen).
Achim S. schrieb:> Ob du die Zahlenwerte als Ganzzahlen oder als Festkommazahlen> betrachtest, ist der Rechnung dann aber weitgehend egal. Das ist nur> eine Interpretation von dir, ob der Wert ein Vielfaches von 1 angibt> (Ganzzahl) oder ein Vielfaches von 1/4096 (1.12 Festkommazahl). Binär> sieht der Wert beide male gleich aus, und bei den FIR-Rechnungen wird> ihn dein FPGA gleichermaßen behandeln. Du musst deine Interpration nur> konsistent anwenden (d.h. den Bits im Ergebnis der Rechnung die richtige> Wertigkeit zuordnen).
Nur geht das dann doch nicht, wenn ich Koeffizienten habe, die z.b. den
Wert
0.082763671875 oder so haben, oder? Somit bleibt mir nach meinem
Verständnis doch gar nicht die Wahl zwischen Ganzzahl und Festkommazahl?
ich brauche ja bei den Koeffizienten definitiv eine Vorkommazahl für das
Vorzeichen, der Rest findet dann doch aber hinter dem Komma statt. Mit
12 Bit habe ich da also definitiv eine 1.11 Darstellung. Da ich Ganzzahl
und Festkommazahl nicht mischen kann, bin ich doch praktisch zur
Festkommazahl gezwungen, oder?
Tut mir Leid, dass ich hier so nachhake, aber das ist gerade mein
zentrales Problem und ich bekomme es irgendwie nicht gelöst.
Du hast den Koeffizienten 0,082763671875, weil du festgelegt hast, dass
deine Koeffizienten zwischen -1 und 1 liegen. In der Darstellung 1.11 im
Zweierkomplement bekommst du dafür den Binärstring 0000 1010 1001 (das
entspricht genau genommen 0,08251953125. In 1.11 Darstellung kommst du
nicht näher an deinen Koeffizienten 0,082763671875 heran.)
Das FPGA sieht nur diesen Binärstring und multipliziert mit ihm. Es
interessiert sich nicht dafür, ob damit 0,08251953125 oder die Ganzzahl
169 gemeint ist.
Du hättest ebensogut angeben können, dass deine Koeffizienten um den
Faktor 2^11 größer sein sollen - also nicht zwischen -1 und +1 sondern
zwischen -2048 und 2047 liegen. In dem Fall hättest du als Wert für den
Koeffizienten 169 erhalten. Die binäre Darstellung wäre ebenfalls wieder
0000 1010 1001. Dein Endergebnis wäre in dieser Interpreation um den
Faktor 2^11 größer. Aber der Binärstring ist derselbe.
Das FPGA multipliziert also in beiden Fällen mit dem identischen
Binärwert und liefert das identische Binärergebnis, nur deine
Interpretation der Zahl ist eine andere. Solange du die Faktoren und
Endergebnis konsistent interpretierst, bleibt alles richtig.
Machen wir ein Rechenbeispiel. Binär:
0000 1010 1001 * 0001 0101 0110 = 0000 0000 1110 0001 1100 0110
Als Ganzzahl interpretiert ergibt das dezimal:
169*342=57798
Interpretierst du die beiden Faktoren als 1.11 und das Ergebnis als 2.22
lautet das Ergebnis dezimal:
(169/2^11)/(342/2^11) = 0,082519*0,16699 = 0,01378 = 57798/2^22
Für die Berechnungen deines FIR interessiert sich das FPGA nicht dafür,
ob du 1.11 meinst oder Ganzzahlen mit einem 2^11 größeren Bereich. Die
Rechnung ist für das FPGA identisch, und solange du deine Interpretation
konsistent machst (bei Eingabewerten und Endergebnis) ist auch das
Ergebnis richtig.
Okay, ich fange nochmal bei den Koeffizienten an:
Ich hole mir jetzt einen Satz Koeffizienten aus Matlab. Diese werden mir
immer im Zahlenbereich von -1 bis (1 - Auflösung) geliefert. Jetzt
entscheide ich quasi für mich entweder
1) Ah, der AD Wandler liefert mir 12 Bit und der DA Wandler benötigt
ebenfalls 12 Bit. Diese 12 Bit möchte ich als Fixed Point darstellen,
und zwar praktischerweise als 1.12 Format, weil ich dann vor die AD
Werte einfach nur eine 0 anhängen muss, da ich nur positive Werte vom AD
Wandler erhalte. Ab hier müsste die Berechnung ja dann klappen, wenn ich
mir die Koeffizienten auch als 1.12 Darstellung darstelle.
2) Die Ganzzahlrechnung verstehe ich in dem Zusammenhang leider
immernoch nicht ;( Möchte ich hier jetzt auf signed verzichten, oder
muss ich trotzdem zum signed erweitern? Der AD Wandler liefert mir einen
12 Bit Wert, dieser kann dann einfach stehen bleiben, oder muss
ebenfalls mit einer vorangestellten 0 ins signed Format gebracht werden?
Dann wären wir ja im Darstellungsbereich von -4096 bis 4095 (13.0),
wegen der vorangestellten 0 allerdings nur im Bereich von 0 bis 4095.
Jetzt denke ich mir meinen Koeffizienten binär nicht im Bereich von -1
bis (1- Auflösung), sondern shifte die binäre Darstellung (im Kopf) um
2^12 nach links und gelange so ebenfalls in den Darstellungsbereich von
-4096 bis 4095, die Werte können aber diesmal auch negativ werden.
Am Ende muss ich mir dann aus der Multiplikation (am einfachsten wohl
per Truncate) meinen Darstellungsbereich heraussuchen, da ich ein 2.24
Ergebnis erhalte. Wie erhalte ich jetzt aus dem Ergebnis meine 12 Bit
für den DA - Wandler? Aus dem von mir eingangs angehängtem Dokument habe
ich das denke ich falsch verstanden. Muss ich das Ergebnis wieder nach
links shiften, um an meinen Wert zu gelangen?
Alex K. schrieb:> Möchte ich hier jetzt auf signed verzichten, oder> muss ich trotzdem zum signed erweitern?
Ich hatte versucht, das oben zu beschreiben: trenne gedanklich
vollständig zwischen den Aspekten signed-unsigned und
Ganzzahlen-Festkommazahlen. Beides hat nichts miteinander zu tun.
Die erste Frage (signed-unsigned) ist wichtig: wenn du hier einen Fehler
machst, rechnest du falsch.
Die zweite Frage (Ganzzahlen-Festkomma) ist nur eine
Interpretationsfrage, von der das FPGA nichts mitkriegt.
Zur ersten Frage (signed-unsigned): wenn du aussschließlich mit
positiven Werten arbeiten würdest, könntest du alles mit unsigned
rechnen. Aber wenn einer deiner Koeffizienten negativ ist, oder wenn du
z.B. einen IP-Core zur Ansteuerung eines dsp-Kerns einsetzt, der von
signed ausgeht, musst du signed rechnen.
Zur zweiten Frage (Ganzzahl-Festkommazahl): du darfst gerne deine deine
Zahlen in 1.11 und 1.12 eingeben und das Ergebnis in 2.24 erhalten. Den
Wert für den DAC wirst du dann im 2.24 Ergebnis ablesen, indem du links
Bits ohne Informationsgehalt weglässt (die in deinem Fall immer Null
sein werden) und rechts die Bits weglässt, die die Auflösung deines DACs
überschreiten. Von den 26 Bits deines Ergebnisses werden das z.B. die
Bits 23..12 sein. (Ob es genau diese Bits sein werden oder nicht hängt
aber von deinem Filter ab, das musst du selbst herausfinden! Denn der
FIR kann ja insgesamt auch das Signal verstärken oder abschwächen.)
Wenn du das richtig gemacht hast, kannst du gerne die Rechnung nochmal
nachvollziehen und dabei die Zahlen anders interpretieren. Die 1.11 Zahl
als eine 2^11 größere Ganzzahl. Die 1.12 Zahl als eine 2^12 größere
Ganzzahl. .....
Dabei wirst du sehen, dass die Rechnung genau gleich aufgeht. Und dass
du genau die selben Bitpositionen aus dem 26-Bittigen Ergebnis für
deinen DAC auslesen musst wie zuvor bei der Interpretation mit
Festkommazahlen.
Alex K. schrieb:> Jetzt denke ich mir meinen Koeffizienten binär nicht im Bereich von -1> bis (1- Auflösung), sondern shifte die binäre Darstellung (im Kopf) um> 2^12 nach links und gelange so ebenfalls in den Darstellungsbereich von> -4096 bis 4095, die Werte können aber diesmal auch negativ werden.
Nein: die binäre Darstellung bleibt exakt identisch. Und das Vorzeichen
ändert sich natürlich auch nicht.
Nur deine Interpretation des identischen Binärwerts ändert sich: mal
interpretierst du den Binärwert als Vielfache von 2^-12, mal als
Vielfache von 1. Dadurch arbeitest du in deiner Interpretation mal mit
einem Zahlenraum der Messwerte von -1 bis knapp unter +1, mal mit einem
Zahlenraum von -4096 bis 4095.
Okay, ich bekomme am Ausgang jetzt tatsächlich endlich ein ähnliches
Signal, wie ich es hereingegeben habe. Das bedeutet schonmal Fortschritt
für mich!
Vielen Dank für deine ausführliche Hilfestellung bis hierhin. Es fehlt
mir jetzt gerade nur das Verständnis dafür, wieso ich die Bits 23 bis 12
an den DA Wandler geben muss. Du hast das ja bereits erklärt, allerdings
konnte ich das noch nicht ganz nachvollziehen. Da werde ich mich nochmal
drannsetzen.
Der hochfrequente Anteil ist allerdings so gut wie gar nicht
unterdrückt. Ich werde mal versuchen, meinen VHDL Code vielleicht mit
mehr Filterkoeffizienten auszustatten, vielleicht habe ich ja auch noch
Logikfehler im Code und ich checke nochmal das Zusammenspiel aller
Komponenten. Den VHDL Code für den Filter hänge ich hier mal an. Die
Filterkoeffizienten hat mir der Filter Designer von Matlab ausgespuckt.
1
--
2
--Implementierung der Direktform eines FIR - Tiefpasses
3
--Kennwerte: Abtastrate: 50 kHz
4
--f_Durchlass = 0,8kHz
5
--f_stopp: 5kHz bei delta_s = 20dB Absenkung
6
--passband Ripple = 0,1db
7
8
library IEEE;
9
use IEEE.Std_logic_1164.all;
10
use IEEE.Numeric_Std.all;
11
use IEEE.math_real.all;
12
13
14
entity FIR_Test is
15
generic(N : integer := 10); --(Anzahl der Koeffizienten - 1)
16
port
17
(
18
x_in : in std_logic_vector(11 downto 0); --Input 12 Bit vom AD Wandler
19
20
clk : in std_logic; --Input Clk mit hoher Frequenz
21
22
rst : in std_logic; --Reset Active Low
23
enable_data : in std_logic; --next Sample von ADC
24
25
data_acknowledged : in std_logic; --DA hat Daten erhalten
26
27
filter_rdy : out std_logic; --Signalisiere dem DA ready.
28
29
y : out std_logic_vector(11 downto 0) --Output 12 Bit an den DA - Wandler
30
);
31
end FIR_Test;
32
33
architecture FIR_Test_arch of FIR_Test is
34
35
type tap_line is array(0 to (N - 1)) of std_logic_vector(12 downto 0); --Typenerklärung: Array zum Verschieben
36
-- =(Zeitverzögern) des Inputs
37
38
type table is array(0 to N) of signed(12 downto 0); --Typenerklärung: Array aus Filterkoeffizienten,
39
40
41
--States
42
type states is (
43
waitForADC,
44
readData,
45
filter,
46
shiftToDA
47
);
48
49
50
constant coeff : table:= (
51
"0000100001010", --0.06494140625
52
"0000011111110", --0.06201171875
53
"0000101010011", --0.082763671875
54
"0000110011011", --0.100341796875
55
"0000111001011", --0.112060546875
56
"0000111011100", --0.1162109375
57
"0000111001011", --0.112060546875
58
"0000110011011", --0.100341796875
59
"0000101010011", --0.082763671875
60
"0000011111110", --0.06201171875
61
"0000100001010" --0.06494140625
62
); --Koeffiziententabelle, von a_10 bis a_0
63
-- --Darstellung: signed 12 Bit Zahl
64
65
signal current_state : states := waitForADC; --Enthält den aktuellen Status, initialisiert mit "waitForADC"
66
67
signal filter_rdy_intern : std_logic := '0'; --Internes Signal für die fertige Filteroperation
68
69
signal data_read_ready : std_logic := '0'; --Daten einlesen fertig
70
signal DA_acknowledged : std_logic := '0'; --DA hat Daten erhalten
71
72
begin
73
74
--Schreiben der Statemachine
75
write_statemachine : process(clk, rst)
76
begin
77
--Reset aktiv low
78
if (rst = '0') then
79
current_state <= waitForADC;
80
--Signaländerung bei steigender Flanke des 125MHz Clocks
81
elsif (rising_edge(clk)) then
82
if (enable_data = '1' and data_read_ready = '0' ) then --Nur 1x lesen
83
84
current_state <= readData;
85
elsif (data_read_ready = '1' and filter_rdy_intern = '0') then
86
current_state <= filter;
87
elsif (filter_rdy_intern = '1' and data_acknowledged = '0') then
88
89
current_state <= shiftToDA;
90
elsif (data_acknowledged = '1') then
91
current_state <= waitForADC;
92
else
93
NULL;
94
end if;
95
end if;
96
end process write_statemachine;
97
98
--Durchführen der Operationen abhängig vom State
99
statemachine : process(clk)
100
variable sop : signed(25 downto 0); --Variable für Zwischenergebnis der Multiplikation, Darstellung 2.24
101
102
-- variable sop : signed(23 downto 0); --Variable für Zwischenergebnis der Multiplikation, Darstellung 24.0
103
104
variable counter_filter : integer range 0 to 20;
105
begin
106
if (rising_edge(clk)) then
107
case (current_state) is
108
when waitForADC =>
109
filter_rdy_intern <= '0';
110
data_read_ready <= '0';
111
sop := (others => '0');
112
counter_filter := 0;
113
when readData =>
114
x(0) <= "0" & x_in; --Neues Datum einlesen und auf Position 0 des Arrays abspeichern. 0 vorne Anhängen für Darstellung
115
116
data_read_ready <= '1';
117
when filter =>
118
counter_filter := counter_filter + 1;
119
if (counter_filter <= 9) then
120
sop := sop + coeff(counter_filter - 1) * signed(x(counter_filter - 1)); --Durchführung einer Multiplikation und einer Addition
Alex K. schrieb:> Der hochfrequente Anteil ist allerdings so gut wie gar nicht> unterdrückt.
sieht tatsächlich so aus, als würden die Eingangswerte fast unverändert
auf den Ausgang gegeben werden. Lass vielleicht versuchsweise mal den
IP-Generator von Vivado laufen und gib deine Filterkoeffizienten dort
ein. Wenn ich mich recht erinnere zeigt dir der IP-Generator an, welche
daraus resultierende Filterkurve er erwartet.
Alex K. schrieb:> vielleicht habe ich ja auch noch> Logikfehler im Code und ich checke nochmal das Zusammenspiel aller> Komponenten.
Du kannst deine Testbench nicht nur nutzen, um die Statemachine zu
testen. Du kannst sie auch tatsächlich Samples einlesen lassen (aus
einem Textfile), das Filter wirklich darüber berechnen lassen und die
Ergebniswerte wegschreiben. Vielleicht kannst du so nachvollziehen,
woran es hakt.
Ohne den Code jetzt im Detail durchgegangen zu sein: prüfe mal, ob du
jeden ADC-Wert tatsächlich nur einmal übernimmst oder ob du den
identischen Wert gleich mehrfach ins Filter reinschiebst. Ich
durchschaue ohne genauere Analyse deinen "Handshake" von enable_data und
data_read_ready nicht. Wenn du dort einen Fehler machst und den
identischen Wert mehrfach verwendest, könnte das deine Ergebnisse
erklären.
Achim S. schrieb:> Ohne den Code jetzt im Detail durchgegangen zu sein: prüfe mal, ob du> jeden ADC-Wert tatsächlich nur einmal übernimmst oder ob du den> identischen Wert gleich mehrfach ins Filter reinschiebst. Ich> durchschaue ohne genauere Analyse deinen "Handshake" von enable_data und> data_read_ready nicht. Wenn du dort einen Fehler machst und den> identischen Wert mehrfach verwendest, könnte das deine Ergebnisse> erklären.
Das Signal "enable_data" wird dann 1, wenn der ADC ein neues Sample
bereitstellt. "data_read_ready" wird vom Filter = 1 gesetzt, nachdem das
Sample eingelesen wurde. Da der Takt beim Filter wesentlich höher ist
dachte ich mir, das sei gerade sinnvoll um zu verhindern, dass derselbe
Wert mehrfach eingelesen wird. Ich werde das nochmal prüfen.
Achim S. schrieb:> sieht tatsächlich so aus, als würden die Eingangswerte fast unverändert> auf den Ausgang gegeben werden. Lass vielleicht versuchsweise mal den> IP-Generator von Vivado laufen und gib deine Filterkoeffizienten dort> ein. Wenn ich mich recht erinnere zeigt dir der IP-Generator an, welche> daraus resultierende Filterkurve er erwartet.
Werde ich ausprobieren.
Achim S. schrieb:> Du kannst deine Testbench nicht nur nutzen, um die Statemachine zu> testen. Du kannst sie auch tatsächlich Samples einlesen lassen (aus> einem Textfile), das Filter wirklich darüber berechnen lassen und die> Ergebniswerte wegschreiben. Vielleicht kannst du so nachvollziehen,> woran es hakt.
Das klingt sehr hilfreich, werde ich mir auch mal ansehen.
Um diesen Thread hier mal weiter auf dem aktuellen Stand zu halten:
Ich habe (vermutlich) die Fehlerquelle gefunden, die zu der 1:1
Signalübertragung führt:
Der Filter rechnet in der gezeigten Grafik in 21 Schritten das nächste
Ergebnis aus. Der Input (ganz oben) lautet X"001", als Binärdarstellung
"0000|0000|0001". Als Endergebnis des Filters (ganz unten) kommt heraus:
X"000105b", davon nehme ich nun die Information in der Mitte heraus, und
es kommt wieder X"001" heraus.
Jetzt bin ich mir noch unsicher, wie ich weitermache, da der DA Wandler
ja definitiv 12 Bit braucht. Die Auflösung lässt aber eine so geringe
Änderung nicht zu.
Achim S. schrieb:> Du kannst deine Testbench nicht nur nutzen, um die Statemachine zu> testen. Du kannst sie auch tatsächlich Samples einlesen lassen (aus> einem Textfile), das Filter wirklich darüber berechnen lassen und die> Ergebniswerte wegschreiben. Vielleicht kannst du so nachvollziehen,> woran es hakt.
Da bin ich aktuell noch dran
Alex K. schrieb:> es kommt wieder X"001" heraus.
Da muss auch so sein, der Fehler liegt woanders (siehe unten).
Schau dir den Inhalt von x(0..20) an, nachdem du ein erstes Sample am
Eingang angelegt hast. Eigentlich müsstest du nach einem Sample
- N Multiplikationen und Additonen ausführen (mit dem neuen Sample und
mit den N-1 zwischengespeicherten vorherigen Samples)
- genau einen Wert von x(20..0) überschreiben
- alle anderen Werte von x(20..0) um eine Position weiter schieben (weil
du den Wertespeicher als Schieberegister implementierst hast und nicht
als Ringpuffer).
Du hast aber nicht einen Wert von x(20..0) mit dem neuen Sample
überschrieben sondern alle N Werte von x(20..0). Dein Schieberegister
funktioniert nicht, das eine neue Sample wird auf alle Positionen von
x(...) geschrieben! Die vorherigen Samples gehen verloren.
Da du alle Werte deines Speichers mit dem neuen Wert überschreibst muss
(bei einem Tiefpass) auch genau der neue Wert als Ergebnis aus dem FIR
heraus kommen.
Achim S. schrieb:> Du hast aber nicht einen Wert von x(20..0) mit dem neuen Sample> überschrieben sondern alle N Werte von x(20..0). Dein Schieberegister> funktioniert nicht, das eine neue Sample wird auf alle Positionen von> x(...) geschrieben! Die vorherigen Samples gehen verloren.
Dann habe ich den FIR falsch verstanden... Das heißt, ich wollte das
tatsächlich so implementieren. Okay, also mit jedem neuen Sample nur
einen Wert überschreiben, allerdings trotzdem N+1 Multiplikationen und
Additionen durchführen, um einen Ausgangswert zu erhalten. Beim nächsten
Sample dann alles nach rechts verschieben und wiederum die Berechnungen
durchführen
Alex K. schrieb:> Dann habe ich den FIR falsch verstanden... Das heißt, ich wollte das> tatsächlich so implementieren.
Wenn du überall im Schieberegister den identischen Wert stehen hättest,
dann könntest du dir auch gleich das Schieberegister sparen. Ein
einzelnes Register mit dem aktuellen Wert würde dann reichen. Wenn dein
FIR-Tiefpass "über die letzten N Samples mitteln" soll, dann muss er
auch die letzten N Samples zur Berechnung zur Verfügung haben.
Alex K. schrieb:> Okay, also mit jedem neuen Sample nur> einen Wert überschreiben, allerdings trotzdem N+1 Multiplikationen und> Additionen durchführen, um einen Ausgangswert zu erhalten. Beim nächsten> Sample dann alles nach rechts verschieben und wiederum die Berechnungen> durchführen
Entweder so (per Schiebregister). Oder als Ringpuffer z.B. mit den
Samples im Blockram. Ich hatte ja oben schon mal empfohlen, den
FIR-Compiler deines Synthesetools zu verwenden und in dessen
Beschreibung bzw. im RTL-Schematic zu schauen, wie das umgesetzt wird.
Ich habe den Code jetzt nochmal komplett umgebaut und nun auch zum
Laufen bekommen. Analogen Input vom AD und analogen Output vom DA habe
ich als Bilder angehängt. Der gesamte Code des FIR sieht jetzt
folgendermaßen aus:
1
--
2
--Implementierung der Direktform eines FIR - Tiefpasses
3
--Kennwerte: Abtastrate: 50 kHz
4
--f_Durchlass = 0,8kHz
5
--f_stopp: 5kHz bei delta_s = 20dB Absenkung
6
--passband Ripple = 0,1db
7
8
libraryIEEE;
9
useIEEE.Std_logic_1164.all;
10
useIEEE.Numeric_Std.all;
11
useIEEE.math_real.all;
12
13
14
entityFIR_Testis
15
generic(N:integer:=20);--(Anzahl der Koeffizienten - 1)
16
port
17
(
18
x_in:instd_logic_vector(11downto0);--Input 12 Bit vom AD Wandler
19
clk:instd_logic;--Input Clk mit hoher Frequenz
20
rst:instd_logic;--Reset Active Low
21
enable_data:instd_logic;--next Sample vom ADC
22
data_acknowledged:instd_logic;--DA hat Daten erhalten
23
error_from_ADC:instd_logic;--Errorsignal vom ADC
24
25
filter_rdy:outstd_logic;--Signalisiere dem DA ready.
26
y:outstd_logic_vector(11downto0)--Output 12 Bit an den DA - Wandler
27
);
28
endFIR_Test;
29
30
architectureFIR_Test_archofFIR_Testis
31
32
typetap_lineisarray(0toN)ofstd_logic_vector(12downto0);--Typenerklärung: Array zum Verschieben
33
-- =(Zeitverzögern) des Inputs
34
35
typetableisarray(0toN)ofsigned(12downto0);--Typenerklärung: Array aus Filterkoeffizienten,
36
37
--States
38
typestatesis(
39
startup,
40
waitForADC,
41
readAndShift,
42
multiply,
43
add,
44
shiftToDA
45
);
46
47
signalx:tap_line:=(others=>(others=>'0'));
48
49
constantcoeff:table:=(
50
"1"&X"fcd",
51
"1"&X"ffd",
52
"0"&X"015",
53
"0"&X"03f",
54
"0"&X"07b",
55
"0"&X"0c4",
56
"0"&X"114",
57
"0"&X"161",
58
"0"&X"1a2",
59
"0"&X"1cc",
60
"0"&X"1db",
61
"0"&X"1cc",
62
"0"&X"1a2",
63
"0"&X"161",
64
"0"&X"114",
65
"0"&X"0c4",
66
"0"&X"07b",
67
"0"&X"03f",
68
"0"&X"015",
69
"1"&X"ffd",
70
"1"&X"fcd"
71
);--Koeffiziententabelle, von a_20 bis a_0
72
-- --Darstellung: signed 1.12 Bit Zahl
73
74
signalcurrent_state:states:=startup;--Enthält den aktuellen Status, initialisiert mit "startup"
ich gratuliere, und danke für die Rückmeldung.
Du hast offenbar auch die Timing-Probleme, die im Parallelthread
diskutiert wurden, in den Griff bekommen. Im Hinblick auf den
Einstiegscode aus den Beuth-Unterlagen hast du eine zielich steile
Lernkurve durchlaufen.
Wie im anderen Thread schon angemerkt wurde könnte der Code noch
performanter werden (weniger Ressourcen, weniger Stromverbrauch,
Verdopplung der Obergrenze der Taps), wenn du einen DSP-Core direkt
einsetzen würdest: die sind ja eigentlich genau dafür gemacht, einen
Multiplikation und Addition gemeinsam durchzuführen (was du derzeit auf
zwei States aufteilst und damit wahrscheinlich die Addition auf LUTs
umbiegst oder zwei dsp-Cores belegst). Statt des großen Schieberegisters
für die Samples wäre auch noch ein BRAM möglich. Und dann kämst du
wahrscheinlich ziemlich bei dem raus, was der FIR-Compiler erzeugt, wenn
du ihn einsetzt.
Wenn diese Punkte Inhalt deiner Bachelorarbeit sein sollten, könntest du
hier noch eine "vergleichende Studie" durchführen. Wenn es primär darum
geht, dass dieser Filter läuft, würde ich sagen: das hast du gut
erreicht (was ich in den ersten Tagen des Threads nicht unbedingt
erwartet hätte ;-)
Vielen Dank für die Hilfe, bezüglich der Bachelorarbeit geht es jetzt
allerdings noch weiter mit dem nächsten Teil, nämlich das Teil irgendwie
adaptiv zu bekommen, um einem gewissen Frequenzbereich zu folgen.
Allerdings muss ich mir hier erstmal überhaupt ein Konzept überlegen, wo
ich die Information über die aktuelle (Mitten-)Frequenz herbekomme und
wie ich die neuen Koeffizienten dann berechnen soll. Mal sehen, ob ich
da noch in den nächsten Wochen irgendwas zu Stande bekomme.
Alex K. schrieb:> nämlich das Teil irgendwie> adaptiv zu bekommen, um einem gewissen Frequenzbereich zu folgen.
ein simpler Ansatz um die Grenzfrequenz anzupassen besteht darin, die
Abtastrate anzupassen. Die Filtercharakteristik ist ja mit Bezug auf die
Abtastrate festgelegt. Wenn du die Abtastrate 20% änderst, ändert sich
die Grenzfrequenz (bezogen auf tatsächliche Frequenzen in Hz) um die
selben 20%. Wenn die Abtastrate am Limit von ADC/DAC liegt, besteht der
Freiheitsgrad natürlich nicht.
Alex K. schrieb:> Allerdings muss ich mir hier erstmal überhaupt ein Konzept überlegen, wo> ich die Information über die aktuelle (Mitten-)Frequenz herbekomme
Mit Mittenfrequenz meinst du die dominante Sigalfrequenz im unteren
Frequenzbereich? Dann könntest du das FPGA parallel zur Filterung
jeweils eine FFT berechnen lassen. Ist zwar ein wenig mit Kanonen auf
Spatzen geschossen um einen FIR-Filter anzupassen, aber so könntest du
schauen, bei welcher Frequenz die dominante Linie liegt. Spätestens das
würde ich aber nicht mehr "händisch" implementieren sondern durch den
Wizard von Vivado erzeugen lassen.(denn die FFT ist deutlich aufwändiger
als der FIR-Filter.)
Vom Aufwand her wäre es wahrscheinlich geringer, einfach diverse FIR mit
unterschiedlichen Koeffizienten nebeneinander zu implentieren. Wenn das
Ausgangssignal der FIR nur noch dein "Nutzsignal" enthält, kannst du
dort die Periodendauer des Nutzsignals direkt ausmessen und daraus
entscheiden, welchen der FIR-Ausgänge du zum DAC weiterleitest.
Alex K. schrieb:> und> wie ich die neuen Koeffizienten dann berechnen soll.
Wenn du einen FIR zur Laufzeit umkonfigurieren willst kannst du z.B.
verschiedene Koeffizientensätze vorab (mit deinem Matlab) berechnen und
alle in ein Blockram des FPGAs legen. Lass für jeden Koeffizientensatz
so viel Platz, wie der längste Koeffizientensatz benötigt. Und suche dir
dann zur Laufzeit den Koeffizientensatz aus, der dir für die aktuellen
Signale am passendsten erscheint. Das Auswählen des Koeffizientensatzes
erfolgt einfach über eine "Basisadresse" fürs Blockram, von der ab dein
counter_filter dann jeweils die aktuell zu nutzenden Koeffizienten
hochzählt.
Achim S. schrieb:> ein simpler Ansatz um die Grenzfrequenz anzupassen besteht darin, die> Abtastrate anzupassen. Die Filtercharakteristik ist ja mit Bezug auf die> Abtastrate festgelegt. Wenn du die Abtastrate 20% änderst, ändert sich> die Grenzfrequenz (bezogen auf tatsächliche Frequenzen in Hz) um die> selben 20%. Wenn die Abtastrate am Limit von ADC/DAC liegt, besteht der> Freiheitsgrad natürlich nicht.> Mit Mittenfrequenz meinst du die dominante Sigalfrequenz im unteren> Frequenzbereich? Dann könntest du das FPGA parallel zur Filterung> jeweils eine FFT berechnen lassen. Ist zwar ein wenig mit Kanonen auf> Spatzen geschossen um einen FIR-Filter anzupassen, aber so könntest du> schauen, bei welcher Frequenz die dominante Linie liegt. Spätestens das> würde ich aber nicht mehr "händisch" implementieren sondern durch den> Wizard von Vivado erzeugen lassen.(denn die FFT ist deutlich aufwändiger> als der FIR-Filter.)>> Vom Aufwand her wäre es wahrscheinlich geringer, einfach diverse FIR mit> unterschiedlichen Koeffizienten nebeneinander zu implentieren. Wenn das> Ausgangssignal der FIR nur noch dein "Nutzsignal" enthält, kannst du> dort die Periodendauer des Nutzsignals direkt ausmessen und daraus> entscheiden, welchen der FIR-Ausgänge du zum DAC weiterleitest.> Wenn du einen FIR zur Laufzeit umkonfigurieren willst kannst du z.B.> verschiedene Koeffizientensätze vorab (mit deinem Matlab) berechnen und> alle in ein Blockram des FPGAs legen. Lass für jeden Koeffizientensatz> so viel Platz, wie der längste Koeffizientensatz benötigt. Und suche dir> dann zur Laufzeit den Koeffizientensatz aus, der dir für die aktuellen> Signale am passendsten erscheint. Das Auswählen des Koeffizientensatzes> erfolgt einfach über eine "Basisadresse" fürs Blockram, von der ab dein> counter_filter dann jeweils die aktuell zu nutzenden Koeffizienten> hochzählt.
Das klingt nach sehr vielen wertvollen Infos, vielen Dank dafür! Ab
morgen mache ich mich da drann
Noch ein kleines Update: Ich habe jetzt angefangen, einen
Frequenzcounter zu implementieren. Ich erhoffe mir von dem Ergebnis des
Frequenzcounters dann, dass ich anhanddessen den korrekten
Koeffizientenvektor auswählen kann. Ich werde wohl den Weg gehen, mir
mehrere Koeffizienten vorab per Matlab berechnen zu lassen.
Bezüglich des Frequenzcounters bin ich blauäugig mit der Hoffnung
herangegangen, dass ich den Datenausgang per if statement mit einem
festen Wert vergleichen kann. Also z.B. mit dem Wert "1000|0000|0000".
Dabei ist natürlich zu beachten, dass pro Periode derselbe Wert 2x
auftaucht. Nur natürlich wird der Wert häufiger mal verpasst, weil nicht
jede Periode auch 2x genau diesen Wert enthält. Daran hänge ich jetzt
gerade und versuche, mir eine Lösung zu erarbeiten. Zumindest konnte ich
(laut Simulation) schonmal abfangen, dass dasselbe Sample mehrfach
gezählt wird. Den Counter habe ich jetzt auch einfach mal mit einer
Frequenz von 125MHz versorgt. Mein Counter Code folgt im Anschluss. Das
Freq_Signal wird dabei vom DA Wandler - Modul geliefert. Dieser
vergleicht die Samples mit dem Referenzwert. Stimmen Referenzwert und
der Wert des aktuellen Samples überein, wird Freq_signal auf 1 gesetzt.
Wenn der Counter voll ist, kann der Frequenzcounter ein entsprechendes
Signal weitergeben.
1
libraryIEEE;
2
useIEEE.STD_LOGIC_1164.ALL;
3
useIEEE.STD_LOGIC_ARITH.ALL;
4
useIEEE.STD_LOGIC_UNSIGNED.ALL;
5
6
entityfrequency_counteris
7
port
8
(
9
CLK_IN:instd_logic;--Clocksignal vom Board
10
RST:instd_logic;--Active Low Reset
11
FREQ_SIGNAL:instd_logic;--Daten zur Frequenzerfassung
12
ACK_ERROR_ADC:instd_logic;--Error Signal des AD Wandlers
13
14
COUNTER_FULL:outstd_logic--Signal bei Überlauf (Als Referenz für Frequenz des Signals)
Alex K. schrieb:> Nur natürlich wird der Wert häufiger mal verpasst, weil nicht> jede Periode auch 2x genau diesen Wert enthält.
Ein Stück robuster wäre hier, wenn du nicht das exakte Treffen des
Referenzwert detektierst sondern die Flanke durch den Referenzwert. D.h.
der letzte Wert war <= Referenzwert, der aktuelle Wert ist >
Referenzwert.
Das kannst du (gegen Rauschen und Oberschwingungen) auch mit einer
kleinen Hysterese versehen. D.h. sobald eine Flanke detektiert wurde,
muss erst Referenzwert + Delta erreicht werden, ehe die nächste Flanke
wieder gezählt wird. Wenn bei einer Flanke der Referenzwert mehrfach
übersprungen wird, hilft das, die echten Flanken von den Flanken
aufgrund von Rauschen zu unterscheiden.
Alex K. schrieb:> Das> Freq_Signal wird dabei vom DA Wandler - Modul geliefert.
Ein DA-Wandler Modul sollte den DA ansteuern - ansonsten ist sein Name
irreführend. Bau dir parallel dazu ein Flankendetektion Modul.
Alex K. schrieb:> --Da bei Sinus Werte immer 2x vorkommen, stellt der Counter quasi die> doppelte Frequenz dar
Das stimmt so nur bei reinem Sinus wenn der Referenzwert genau in der
Mitte liegt. Wenn er etwas assymetrisch liegt bekommst du mal eine zu
klein doppelte Frequenz, mal eine zu große doppelte Frequenz. Im Mittel
geht das dann wieder auf. Aber mit der Flankendetektion hast du auch
diese Problem nicht mehr in gleichem Maß. Wie gut es funktioniert wird
natürlich von deinem konkreten Signal abhängen - man kann immer ein
Signal konstruieren, bei dem diese Periodendauermessung nicht
funktioniert.
Was deinen Code angeht: ich verstehe nicht deine Aufteilung auf mehrere
Zählersignale. Aus meiner Sicht willst du doch folgendes tun:
- die Periodendauer einer Signalperiode soll gemessen werden (also die
Zeit von einer Flankendetektion zur nächsten Flankendetektion)
- takten solltest du das ganze mit der 125MHz Clock. Aber als
Zeitreferenz für die Periodendauer würde ich nicht das Taktsignal nutzen
sondern den Puls, der dir jeweils ein neues Sample anzeigt. (der kann
z.B. als Clock-Enable für deinen Zähler verwendet werden). Also brauchst
du einen Zähler, der breit genug ist, um die Anzahl von Samples pro
Signalperiode aufzunehmen. Ich sehe nicht, wie deine 3- und 4-Bit Zähler
das aufnehmen sollen.
Die Zählersteuerung ist recht einfach:
- die Zählerlogik reagiert immer nur ein mal pro Sample (d.h. der
Sample-Pulse wird als Clock-Enable für deinen Periodenzähler verwendet).
- Wenn von der Flankendetektion eine Flanke des Nutzsignals detektiert
wurde (darauf achten, dass die ebenfalls jedes Sample einmal den
Vergleich durchführt), dann wird der aktuelle Zählerstand abgespeichert.
Der gespeicherte Wert repräsentiert die Länge der zurückliegenden
Periode. Zudem wird der Zähler wird wieder auf Null gesetzt damit er die
Länge der kommenden Periode zählen kann.
- wenn keine Flanke detektiert wurde, dann zählt der Zähler in
Einserschritten nach oben.
Hans schrieb:> Beitrag "IEEE.STD_LOGIC_ARITH.ALL obsolete"
Ja, und dort steht konkret:
... schrieb:> 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;
Nur ist es so, dass Xilinx-Tools und andere nach wie vor bei
automatischer Synthese die Arth ausspucken.
Elbi schrieb:> Nur ist es so, dass Xilinx-Tools und andere nach wie vor bei> automatischer Synthese die Arth ausspucken.
Echt? Eben ausprobiert: beim automatischen Erstellen eines VHDL-Moduls
liefert sowohl die ISE 14.7 als auch Vivado 2014.4 folgendene Vorlage:
-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;
Das sind beides nicht die allerneusten Versionen der Xilinx-Software,
aber sie schlagen beide nicht mehr die IEEE.STD_LOGIC_ARITH.ALL vor.
Oder hattest du mit "automatischer Synthese" etwas anderes als das
automatische Erstellen eines Templates für ein VHDL-Modul gemeint?