mikrocontroller.net

Forum: Digitale Signalverarbeitung / DSP Ausgangssignal eines FPGA basierten Tiefpasses


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Alex K. (alexk99123)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
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.
--
--Implementierung der Direktform eines FIR - Tiefpasses
--Kennwerte: Abtastrate: 50 kHz
--f_Durchlass = 0,8kHz
--f_stopp: 5000kHz bei delta_s = 40dB Absenkung
--passband Ripple = 0,1db

library IEEE;
use IEEE.Std_logic_1164.all;
use IEEE.Numeric_Std.all;


entity FIR_Test is
    generic(N   :   integer := 26); --Anzahl der Koeffizienten = Ordnung des FIR Filters
    port
    (
        x_in    :   in std_logic_vector(11 downto 0);   --Input 1.11
        clk     :   in std_logic;
        y       :   out std_logic_vector(11 downto 0)   --Output 1.11
    );
end FIR_Test;

architecture FIR_Test_arch of FIR_Test is
    
    type    tap_line is array(0 to N) of std_logic_vector(11 downto 0);    --Typenerklärung: Array zum Verschieben 
                                                                           -- =(Zeitverzögern) des Inputs
    type    table is array(0 to N) of signed(11 downto 0);  --Typenerklärung: Array aus Filterkoeffizienten
    
    signal x    :   tap_line;
    constant coeff  : table:= (
                                X"FF0",
                                X"FEF",
                                X"FEC",
                                X"FEC",
                                X"FF3",
                                X"002",
                                X"01B",
                                X"03D",
                                X"065",
                                X"091",
                                X"0BA",
                                X"0DB",
                                X"0F2",
                                X"0FA",
                                X"0F2",
                                X"0DB",
                                X"0BA",
                                X"091",
                                X"065",
                                X"03D",
                                X"01B",
                                X"002",
                                X"FF3",
                                X"FEC",
                                X"FEC",
                                X"FEF",
                                X"FF0"
                               );  --Koeffiziententabelle, von a_26 bis a_0
                                   --Darstellung: signed 1.11
begin

    
    process(clk)
    variable sop    :   signed(23 downto 0);    --Variable für Zwischenergebnis der Multiplikation, Darstellung 2.22
    begin
        if rising_edge(clk) then
            for i in N downto 1 loop
                x(i) <= x(i - 1);   --(Zeit-)Verschieben des Inputs
            end loop;
            x(0) <= x_in;                               --Neues Datum einlesen
            y <= std_logic_vector(sop(22 downto 11));   --Truncate: Erstes Vorzeichenbit und die folgenden 11
                                                        --Nachkommastellen übernehmen, um wieder 1.11 Format zu haben
        end if;
        sop := X"000000";   --Zurücksetzen des Zwischenergebnisses
        for i in 0 to N loop
            sop := sop + coeff(i) * signed(x(i));
        end loop;
    end process;        

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?

Autor: Carl (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Woran liegt das?

Am Vorzeichenfehler und der Abtastrate.

Autor: Alex K. (alexk99123)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Alex K. (alexk99123)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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).
for i in 0 to N loop
     sop := sop + coeff(i) * signed(x(i));
end loop;
Damit meinst du den Teil?

: Bearbeitet durch User
Autor: Carl (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>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.

Autor: Alex K. (alexk99123)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Alex K. (alexk99123)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.
##Clock signal  --> Intern vom Board: 125 MHz
set_property -dict { PACKAGE_PIN K17   IOSTANDARD LVCMOS33 } [get_ports { MAIN_clk }]; #IO_L12P_T1_MRCC_35 Sch=sysclk
create_clock -add -name sys_clk_pin -period 8.00 -waveform {0 4} [get_ports { MAIN_clk }];

##Switches  --> Reset auf Switch SW0
set_property -dict { PACKAGE_PIN G15   IOSTANDARD LVCMOS33 } [get_ports { MAIN_rst }]; #IO_L19N_T3_VREF_35 Sch=sw[0]

##Pmod Header JB (Zybo Z7-20 only)  --> ADC        
set_property -dict { PACKAGE_PIN U7    IOSTANDARD LVCMOS33     } [get_ports { MAIN_scl }]; #IO_L11P_T1_SRCC_13 Sch=jb_p[2]        
set_property -dict { PACKAGE_PIN V7    IOSTANDARD LVCMOS33     } [get_ports { MAIN_sda }]; #IO_L11N_T1_SRCC_13 Sch=jb_n[2]        
                                                                                                                    
##Pmod Header JC    --> DAC                                                                                                                  
set_property -dict { PACKAGE_PIN V15   IOSTANDARD LVCMOS33     } [get_ports { MAIN_SYNC_OUT }]; #IO_L10P_T1_34 Sch=jc_p[1]          
set_property -dict { PACKAGE_PIN W15   IOSTANDARD LVCMOS33     } [get_ports { MAIN_DATA_A }]; #IO_L10N_T1_34 Sch=jc_n[1]         
set_property -dict { PACKAGE_PIN T11   IOSTANDARD LVCMOS33     } [get_ports { MAIN_DATA_B }]; #IO_L1P_T0_34 Sch=jc_p[2]              
set_property -dict { PACKAGE_PIN T10   IOSTANDARD LVCMOS33     } [get_ports { MAIN_SCLK_OUT }]; #IO_L1N_T0_34 Sch=jc_n[2]              

: Bearbeitet durch User
Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Alex K. (alexk99123)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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!

Autor: Alex K. (alexk99123)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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).

Autor: Alex K. (alexk99123)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Alex K. (alexk99123)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Alex K. (alexk99123)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
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.
--
--Implementierung der Direktform eines FIR - Tiefpasses
--Kennwerte: Abtastrate: 50 kHz
--f_Durchlass = 0,8kHz
--f_stopp: 5kHz bei delta_s = 20dB Absenkung
--passband Ripple = 0,1db

library IEEE;
use IEEE.Std_logic_1164.all;
use IEEE.Numeric_Std.all;
use IEEE.math_real.all;


entity FIR_Test is
    generic(N   :   integer := 10); --(Anzahl der Koeffizienten - 1)
    port
    (
        x_in    :   in std_logic_vector(11 downto 0);   --Input 12 Bit vom AD Wandler

        clk     :   in std_logic;                       --Input Clk mit hoher Frequenz

        rst     :   in std_logic;                       --Reset Active Low
        enable_data :   in  std_logic;                  --next Sample von ADC

        data_acknowledged   :   in std_logic;           --DA hat Daten erhalten
        
        filter_rdy  :   out std_logic;                  --Signalisiere dem DA ready.

        y       :   out std_logic_vector(11 downto 0)   --Output 12 Bit an den DA - Wandler
    );
end FIR_Test;

architecture FIR_Test_arch of FIR_Test is
    
    type    tap_line is array(0 to (N - 1)) of std_logic_vector(12 downto 0);    --Typenerklärung: Array zum Verschieben 
                                                                                -- =(Zeitverzögern) des Inputs
                                                                                    
    type    table is array(0 to N) of signed(12 downto 0);  --Typenerklärung: Array aus Filterkoeffizienten,
                                                            
                                                                                                                    
    --States
    type states is  (                
                        waitForADC,
                        readData,
                        filter,
                        shiftToDA     
                    );
    

    constant coeff  : table:= (
                                "0000100001010",    --0.06494140625
                                "0000011111110",    --0.06201171875
                                "0000101010011",    --0.082763671875
                                "0000110011011",    --0.100341796875
                                "0000111001011",    --0.112060546875
                                "0000111011100",    --0.1162109375
                                "0000111001011",    --0.112060546875
                                "0000110011011",    --0.100341796875
                                "0000101010011",    --0.082763671875
                                "0000011111110",    --0.06201171875
                                "0000100001010"     --0.06494140625
                               );  --Koeffiziententabelle, von a_10 bis a_0
--                                   --Darstellung: signed 12 Bit Zahl
                                   
   signal current_state :   states := waitForADC;   --Enthält den aktuellen Status, initialisiert mit "waitForADC"

   signal filter_rdy_intern :   std_logic := '0';   --Internes Signal für die fertige Filteroperation

   signal data_read_ready   :   std_logic := '0';   --Daten einlesen fertig
   signal DA_acknowledged   :   std_logic := '0';   --DA hat Daten erhalten
   
begin
    
    --Schreiben der Statemachine
    write_statemachine  :   process(clk, rst)
    begin
        --Reset aktiv low
        if (rst = '0') then
            current_state <= waitForADC;
        --Signaländerung bei steigender Flanke des 125MHz Clocks
        elsif (rising_edge(clk)) then
            if (enable_data = '1' and data_read_ready = '0' ) then  --Nur 1x lesen

                current_state <= readData;
            elsif (data_read_ready = '1' and filter_rdy_intern = '0') then
                current_state <= filter;
            elsif (filter_rdy_intern = '1' and data_acknowledged = '0') then

                current_state   <=  shiftToDA;
            elsif (data_acknowledged = '1') then
                current_state <= waitForADC;
            else
                NULL;
            end if;
        end if;
    end process write_statemachine;  
    
    --Durchführen der Operationen abhängig vom State
    statemachine    :   process(clk)
    variable sop    :   signed(25 downto 0);    --Variable für Zwischenergebnis der Multiplikation, Darstellung 2.24

--    variable sop    :   signed(23 downto 0);    --Variable für Zwischenergebnis der Multiplikation, Darstellung 24.0

    variable counter_filter :   integer range 0 to 20;
    begin
        if (rising_edge(clk)) then
            case (current_state) is
                when waitForADC =>
                    filter_rdy_intern <= '0';
                    data_read_ready   <=  '0';
                    sop := (others => '0');
                    counter_filter := 0;
                when readData =>
                    x(0) <= "0" & x_in;   --Neues Datum einlesen und auf Position 0 des Arrays abspeichern. 0 vorne Anhängen für Darstellung

                    data_read_ready <= '1';         
                when filter =>
                    counter_filter := counter_filter + 1;
                    if (counter_filter <= 9) then                         
                        sop := sop + coeff(counter_filter - 1) * signed(x(counter_filter - 1)); --Durchführung einer Multiplikation und einer Addition

                        x(counter_filter) <= x(counter_filter - 1); --Zeitverschiebung

                    else
                        filter_rdy_intern <= '1';
                    end if;
                when shiftToDA => 
                    y <= std_logic_vector(sop(23 downto 12));
                    filter_rdy <= '1';
            end case;
        else
            NULL;
        end if;
                                    
    end process statemachine;      

end FIR_Test_arch;

Für die State Machine Logik habe ich auch ein Simulationsfile erstellt:
----------------------------------------------------------------------------------
-- Company: 
-- Engineer: 
-- 
-- Create Date: 06.08.2019 14:33:09
-- Design Name: 
-- Module Name: sim_fir - sim_fir_arch
-- Project Name: 
-- Target Devices: 
-- Tool Versions: 
-- Description: 
-- 
-- Dependencies: 
-- 
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
-- 
----------------------------------------------------------------------------------


library IEEE;
use IEEE.Std_logic_1164.all;
use IEEE.Numeric_Std.all;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;

-- Uncomment the following library declaration if instantiating
-- any Xilinx leaf cells in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity sim_fir is
--  Port ( );
end sim_fir;

architecture sim_fir_arch of sim_fir is
    
    component FIR_Test is
        generic(N   :   integer := 10); --(Anzahl der Koeffizienten - 1)
        port
        (
            x_in    :   in std_logic_vector(11 downto 0);   --Input 12 Bit vom AD Wandler
            clk     :   in std_logic;                       --Input Clk mit hoher Frequenz
            rst     :   in std_logic;                       --Reset Active Low
            enable_data :   in  std_logic;                  --next Sample von ADC
            data_acknowledged   :   in std_logic;           --DA hat Daten erhalten
            
            filter_rdy  :   out std_logic;                  --Signalisiere dem DA ready.
            y       :   out std_logic_vector(11 downto 0)   --Output 12 Bit an den DA - Wandler
        );
    end component FIR_Test;
    
    signal sign_x_in    :   std_logic_vector(11 downto 0) := "100010001000";   --Input 12 Bit vom AD Wandler
    signal sign_clk     :   std_logic := '0';                       --Input Clk mit hoher Frequenz
    signal sign_rst     :   std_logic := '1';                       --Reset Active Low
    signal sign_enable_data :   std_logic := '0';                  --next Sample von ADC
    signal sign_data_acknowledged   :   std_logic := '0';           --DA hat Daten erhalten
    
    signal sign_filter_rdy  :   std_logic;                  --Signalisiere dem DA ready.
    signal sign_y       :   std_logic_vector(11 downto 0);   --Output 12 Bit an den DA - Wandler    
begin
    dut :   FIR_Test
    
    port map
    (
        x_in => sign_x_in,
        clk =>  sign_clk,
        rst =>  sign_rst,     
        enable_data=>sign_enable_data, 
        data_acknowledged=>sign_data_acknowledged,  
        
        filter_rdy=>sign_filter_rdy, 
        y=>sign_y    
    );
    
    clk_gen :   process
    begin
        wait for 4ns;
        sign_clk    <=  '1';
        wait for 4ns;
        sign_clk    <= '0';
    end process clk_gen;
    
    enable_gen  :   process
    begin
        wait for 20us;
        sign_enable_data <= '1';
        wait for 1us;     
        sign_enable_data <= '0';
    end process enable_gen;
    
    DA_ack_gen  :   process
    begin
        wait for 21us;
        sign_data_acknowledged <= '1';
        wait for 5us;     
        sign_data_acknowledged <= '0';
    end process DA_ack_gen;
    
end sim_fir_arch;

: Bearbeitet durch User
Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Alex K. (alexk99123)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Alex K. (alexk99123)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
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

Beitrag #5933394 wurde vom Autor gelöscht.
Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Alex K. (alexk99123)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

: Bearbeitet durch User
Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Alex K. (alexk99123)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
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:
--
--Implementierung der Direktform eines FIR - Tiefpasses
--Kennwerte: Abtastrate: 50 kHz
--f_Durchlass = 0,8kHz
--f_stopp: 5kHz bei delta_s = 20dB Absenkung
--passband Ripple = 0,1db

library IEEE;
use IEEE.Std_logic_1164.all;
use IEEE.Numeric_Std.all;
use IEEE.math_real.all;


entity FIR_Test is
    generic(N   :   integer := 20); --(Anzahl der Koeffizienten - 1)
    port
    (
        x_in    :   in std_logic_vector(11 downto 0);   --Input 12 Bit vom AD Wandler
        clk     :   in std_logic;                       --Input Clk mit hoher Frequenz
        rst     :   in std_logic;                       --Reset Active Low
        enable_data :   in  std_logic;                  --next Sample vom ADC
        data_acknowledged   :   in std_logic;           --DA hat Daten erhalten
        error_from_ADC      :   in std_logic;           --Errorsignal vom ADC
        
        filter_rdy  :   out std_logic;                  --Signalisiere dem DA ready.
        y       :   out std_logic_vector(11 downto 0)   --Output 12 Bit an den DA - Wandler
    );
end FIR_Test;

architecture FIR_Test_arch of FIR_Test is
    
    type    tap_line is array(0 to N) of std_logic_vector(12 downto 0);    --Typenerklärung: Array zum Verschieben 
                                                                                -- =(Zeitverzögern) des Inputs
                                                                                    
    type    table is array(0 to N) of signed(12 downto 0);  --Typenerklärung: Array aus Filterkoeffizienten,                                         
                                                                                                                    
    --States
    type states is  (               
                        startup, 
                        waitForADC,
                        readAndShift,
                        multiply,
                        add,
                        shiftToDA     
                    );
    
    signal x    :   tap_line  := (others=>(others=>'0'));

    constant coeff  : table:= (
                                "1" & X"fcd",
                                "1" & X"ffd",
                                "0" & X"015",
                                "0" & X"03f",
                                "0" & X"07b",
                                "0" & X"0c4",
                                "0" & X"114",
                                "0" & X"161",
                                "0" & X"1a2",
                                "0" & X"1cc",
                                "0" & X"1db",
                                "0" & X"1cc",
                                "0" & X"1a2",
                                "0" & X"161",
                                "0" & X"114",
                                "0" & X"0c4",
                                "0" & X"07b",
                                "0" & X"03f",
                                "0" & X"015",
                                "1" & X"ffd",
                                "1" & X"fcd"
                               );  --Koeffiziententabelle, von a_20 bis a_0
--                                 --Darstellung: signed 1.12 Bit Zahl
                                   
   signal current_state :   states := startup;   --Enthält den aktuellen Status, initialisiert mit "startup"
   
   
   --Debugging
   signal test_counter :   integer range 0 to (N + 1) := 0;
   signal test_result   :   signed(25 downto 0) := (others => '0');
   signal test_temp_result   :   signed(25 downto 0) := (others => '0');
 
begin
  
    --Durchführen der Operationen abhängig vom State
    statemachine    :   process(clk, rst)
    variable result    :   signed(25 downto 0);    --Variable für Zwischenergebnis der Multiplikation, Darstellung 2.24
    variable temp_result    :   signed(25 downto 0);       
    variable counter_filter :   integer range 0 to (N + 1) := 0;

    begin
        
        if (rst = '0') then
            current_state <= startup;
        --Signaländerung bei steigender Flanke des 125MHz Clocks
        elsif (rising_edge(clk)) then
            case (current_state) is
            
                --Alles zurücksetzen
                when startup   =>
                    result := (others => '0');
                    temp_result := (others => '0');
                    counter_filter := 0;
                    
                    if (error_from_ADC = '0') then
                        current_state <= waitForADC;     
                    end if;
                    
                --Auf nächstes Sample warten
                when waitForADC =>
                    counter_filter := 0;            --Zurücksetzen des Zählers
                    result := (others => '0');      --Zurücksetzen des Ergebnisspeichers
                    temp_result := (others => '0'); --Zurücksetzen des Zwischenspeichers
                    
                    if (enable_data = '1') then
                        current_state <= readAndShift;            
                    end if;
                    
                --Neues sample einlesen. Array wird von vorne
                --nach hinten aufgefüllt
                when readAndShift =>
                    x(0) <= "0" & x_in;
                    for i in N downto 1 loop
                        x(i)    <=  x(i-1);    
                    end loop;
                    
                    current_state <= multiply;
                      
                --Multiplikation mit Koeffizienten
                when multiply =>
--                    test_counter    <=  counter_filter;
--                    if (counter_filter = N) then   --Abbruchbedingung für Rechenoperationen  
--                        current_state <= shiftToDA;           
--                    else
                    temp_result :=  coeff(counter_filter) * signed(x(counter_filter));
--                    test_temp_result    <=  temp_result;
                    current_state <= add;
--                    end if;
                    
                --Aufaddieren der Zwischenergebnisse    
                when add =>
                    result := result + temp_result;
--                    test_result  <=  result;
                    
                    if (counter_filter = N) then
                        current_state <= shiftToDA;
                    else
                        current_state <= multiply;
                        counter_filter := counter_filter + 1;
                    end if;
                --Daten an DA übermitteln
                when shiftToDA =>
                    y <= std_logic_vector(result(23 downto 12));   --Ergebnis in 11 Bit Form für AD, 13 Bit nach rechts geshiftet
                    filter_rdy <= '1';          --DA auf Datenempfang vorbereiten
                    if (error_from_ADC = '1') then
                        current_state <= startup;
                    elsif (enable_data = '0' and data_acknowledged = '1') then
                        current_state <= waitForADC;
                    end if;
            end case;
        else
            NULL;
        end if;
    end process statemachine;      

end FIR_Test_arch;

Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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 ;-)

Autor: Alex K. (alexk99123)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Alex K. (alexk99123)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Alex K. (alexk99123)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity frequency_counter is
    port
        (
            CLK_IN          :   in  std_logic;  --Clocksignal vom Board
            RST             :   in  std_logic;  --Active Low Reset
            FREQ_SIGNAL     :   in  std_logic;  --Daten zur Frequenzerfassung
            ACK_ERROR_ADC   :   in  std_logic;  --Error Signal des AD Wandlers
            
            COUNTER_FULL    :   out std_logic   --Signal bei Überlauf (Als Referenz für Frequenz des Signals)
        );
end frequency_counter;

architecture frequency_counter_arch of frequency_counter is

    --Da bei Sinus Werte immer 2x vorkommen, stellt der Counter quasi die doppelte Frequenz dar
    signal sign_frequency_counter   :   integer range 0 to 10 := 0;    --3 Bit Zähler
    
    signal sign_freq_signal             :   std_logic := '0';
    signal sign_count_once              :   std_logic := '0';
       
begin

    count_frequency :   process(CLK_IN, RST)
    begin
        if (rising_edge(CLK_IN)) then
            sign_freq_signal    <=  FREQ_SIGNAL;
            
            if (RST = '0' or ACK_ERROR_ADC = '1') then
                sign_frequency_counter <= 0;    --Counter zurücksetzen    
                
            --Counter erhöhen bei steigender Flanke des Frequenzsignals,
            --allerdings nur 1x zählen
            elsif (sign_count_once = '0' and FREQ_SIGNAL = '1') then
                sign_count_once <=  '1';
                if (sign_frequency_counter = 9) then
                    COUNTER_FULL    <=  '1';
                    sign_frequency_counter  <=  0;
                else
                    sign_frequency_counter <= sign_frequency_counter + 1;
                    COUNTER_FULL    <= '0';
                end if;
                
            elsif (sign_count_once = '1' and FREQ_SIGNAL = '0') then
                sign_count_once <=  '0';
            end if;
        else
            NULL;
        end if;
    end process count_frequency;

end frequency_counter_arch;

Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Hans (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Alex K. schrieb:
> use IEEE.STD_LOGIC_ARITH.ALL;
> use IEEE.STD_LOGIC_UNSIGNED.ALL;

Beitrag "IEEE.STD_LOGIC_ARITH.ALL obsolete"

Autor: Elbi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.