Forum: Digitale Signalverarbeitung / DSP / Machine Learning 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.
von Alex K. (alexk99123)


Angehängte Dateien:

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.
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?

von Carl (Gast)


Lesenswert?

>Woran liegt das?

Am Vorzeichenfehler und der Abtastrate.

von Alex K. (alexk99123)


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.

von Achim S. (Gast)


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.

von Achim S. (Gast)


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.

von Alex K. (alexk99123)


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

: Bearbeitet durch User
von Carl (Gast)


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.

von Alex K. (alexk99123)


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?

von Achim S. (Gast)


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.

von Alex K. (alexk99123)


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.
1
##Clock signal  --> Intern vom Board: 125 MHz
2
set_property -dict { PACKAGE_PIN K17   IOSTANDARD LVCMOS33 } [get_ports { MAIN_clk }]; #IO_L12P_T1_MRCC_35 Sch=sysclk
3
create_clock -add -name sys_clk_pin -period 8.00 -waveform {0 4} [get_ports { MAIN_clk }];
4
5
##Switches  --> Reset auf Switch SW0
6
set_property -dict { PACKAGE_PIN G15   IOSTANDARD LVCMOS33 } [get_ports { MAIN_rst }]; #IO_L19N_T3_VREF_35 Sch=sw[0]
7
8
##Pmod Header JB (Zybo Z7-20 only)  --> ADC        
9
set_property -dict { PACKAGE_PIN U7    IOSTANDARD LVCMOS33     } [get_ports { MAIN_scl }]; #IO_L11P_T1_SRCC_13 Sch=jb_p[2]        
10
set_property -dict { PACKAGE_PIN V7    IOSTANDARD LVCMOS33     } [get_ports { MAIN_sda }]; #IO_L11N_T1_SRCC_13 Sch=jb_n[2]        
11
                                                                                                                    
12
##Pmod Header JC    --> DAC                                                                                                                  
13
set_property -dict { PACKAGE_PIN V15   IOSTANDARD LVCMOS33     } [get_ports { MAIN_SYNC_OUT }]; #IO_L10P_T1_34 Sch=jc_p[1]          
14
set_property -dict { PACKAGE_PIN W15   IOSTANDARD LVCMOS33     } [get_ports { MAIN_DATA_A }]; #IO_L10N_T1_34 Sch=jc_n[1]         
15
set_property -dict { PACKAGE_PIN T11   IOSTANDARD LVCMOS33     } [get_ports { MAIN_DATA_B }]; #IO_L1P_T0_34 Sch=jc_p[2]              
16
set_property -dict { PACKAGE_PIN T10   IOSTANDARD LVCMOS33     } [get_ports { MAIN_SCLK_OUT }]; #IO_L1N_T0_34 Sch=jc_n[2]

: Bearbeitet durch User
von Achim S. (Gast)


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.

von Alex K. (alexk99123)


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!

von Alex K. (alexk99123)


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
von Achim S. (Gast)


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).

von Alex K. (alexk99123)


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.

von Achim S. (Gast)


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.

von Alex K. (alexk99123)


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?

von Achim S. (Gast)


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.

von Alex K. (alexk99123)


Angehängte Dateien:

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.
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
121
122
                        x(counter_filter) <= x(counter_filter - 1); --Zeitverschiebung
123
124
                    else
125
                        filter_rdy_intern <= '1';
126
                    end if;
127
                when shiftToDA => 
128
                    y <= std_logic_vector(sop(23 downto 12));
129
                    filter_rdy <= '1';
130
            end case;
131
        else
132
            NULL;
133
        end if;
134
                                    
135
    end process statemachine;      
136
137
end FIR_Test_arch;

Für die State Machine Logik habe ich auch ein Simulationsfile erstellt:
1
----------------------------------------------------------------------------------
2
-- Company: 
3
-- Engineer: 
4
-- 
5
-- Create Date: 06.08.2019 14:33:09
6
-- Design Name: 
7
-- Module Name: sim_fir - sim_fir_arch
8
-- Project Name: 
9
-- Target Devices: 
10
-- Tool Versions: 
11
-- Description: 
12
-- 
13
-- Dependencies: 
14
-- 
15
-- Revision:
16
-- Revision 0.01 - File Created
17
-- Additional Comments:
18
-- 
19
----------------------------------------------------------------------------------
20
21
22
library IEEE;
23
use IEEE.Std_logic_1164.all;
24
use IEEE.Numeric_Std.all;
25
26
-- Uncomment the following library declaration if using
27
-- arithmetic functions with Signed or Unsigned values
28
--use IEEE.NUMERIC_STD.ALL;
29
30
-- Uncomment the following library declaration if instantiating
31
-- any Xilinx leaf cells in this code.
32
--library UNISIM;
33
--use UNISIM.VComponents.all;
34
35
entity sim_fir is
36
--  Port ( );
37
end sim_fir;
38
39
architecture sim_fir_arch of sim_fir is
40
    
41
    component FIR_Test is
42
        generic(N   :   integer := 10); --(Anzahl der Koeffizienten - 1)
43
        port
44
        (
45
            x_in    :   in std_logic_vector(11 downto 0);   --Input 12 Bit vom AD Wandler
46
            clk     :   in std_logic;                       --Input Clk mit hoher Frequenz
47
            rst     :   in std_logic;                       --Reset Active Low
48
            enable_data :   in  std_logic;                  --next Sample von ADC
49
            data_acknowledged   :   in std_logic;           --DA hat Daten erhalten
50
            
51
            filter_rdy  :   out std_logic;                  --Signalisiere dem DA ready.
52
            y       :   out std_logic_vector(11 downto 0)   --Output 12 Bit an den DA - Wandler
53
        );
54
    end component FIR_Test;
55
    
56
    signal sign_x_in    :   std_logic_vector(11 downto 0) := "100010001000";   --Input 12 Bit vom AD Wandler
57
    signal sign_clk     :   std_logic := '0';                       --Input Clk mit hoher Frequenz
58
    signal sign_rst     :   std_logic := '1';                       --Reset Active Low
59
    signal sign_enable_data :   std_logic := '0';                  --next Sample von ADC
60
    signal sign_data_acknowledged   :   std_logic := '0';           --DA hat Daten erhalten
61
    
62
    signal sign_filter_rdy  :   std_logic;                  --Signalisiere dem DA ready.
63
    signal sign_y       :   std_logic_vector(11 downto 0);   --Output 12 Bit an den DA - Wandler    
64
begin
65
    dut :   FIR_Test
66
    
67
    port map
68
    (
69
        x_in => sign_x_in,
70
        clk =>  sign_clk,
71
        rst =>  sign_rst,     
72
        enable_data=>sign_enable_data, 
73
        data_acknowledged=>sign_data_acknowledged,  
74
        
75
        filter_rdy=>sign_filter_rdy, 
76
        y=>sign_y    
77
    );
78
    
79
    clk_gen :   process
80
    begin
81
        wait for 4ns;
82
        sign_clk    <=  '1';
83
        wait for 4ns;
84
        sign_clk    <= '0';
85
    end process clk_gen;
86
    
87
    enable_gen  :   process
88
    begin
89
        wait for 20us;
90
        sign_enable_data <= '1';
91
        wait for 1us;     
92
        sign_enable_data <= '0';
93
    end process enable_gen;
94
    
95
    DA_ack_gen  :   process
96
    begin
97
        wait for 21us;
98
        sign_data_acknowledged <= '1';
99
        wait for 5us;     
100
        sign_data_acknowledged <= '0';
101
    end process DA_ack_gen;
102
    
103
end sim_fir_arch;

: Bearbeitet durch User
von Achim S. (Gast)


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.

von Alex K. (alexk99123)


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.

von Alex K. (alexk99123)


Angehängte Dateien:

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.
von Achim S. (Gast)


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.

von Alex K. (alexk99123)


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
von Achim S. (Gast)


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.

von Alex K. (alexk99123)


Angehängte Dateien:

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:
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 := 20); --(Anzahl der Koeffizienten - 1)
16
    port
17
    (
18
        x_in    :   in std_logic_vector(11 downto 0);   --Input 12 Bit vom AD Wandler
19
        clk     :   in std_logic;                       --Input Clk mit hoher Frequenz
20
        rst     :   in std_logic;                       --Reset Active Low
21
        enable_data :   in  std_logic;                  --next Sample vom ADC
22
        data_acknowledged   :   in std_logic;           --DA hat Daten erhalten
23
        error_from_ADC      :   in std_logic;           --Errorsignal vom ADC
24
        
25
        filter_rdy  :   out std_logic;                  --Signalisiere dem DA ready.
26
        y       :   out std_logic_vector(11 downto 0)   --Output 12 Bit an den DA - Wandler
27
    );
28
end FIR_Test;
29
30
architecture FIR_Test_arch of FIR_Test is
31
    
32
    type    tap_line is array(0 to N) of std_logic_vector(12 downto 0);    --Typenerklärung: Array zum Verschieben 
33
                                                                                -- =(Zeitverzögern) des Inputs
34
                                                                                    
35
    type    table is array(0 to N) of signed(12 downto 0);  --Typenerklärung: Array aus Filterkoeffizienten,                                         
36
                                                                                                                    
37
    --States
38
    type states is  (               
39
                        startup, 
40
                        waitForADC,
41
                        readAndShift,
42
                        multiply,
43
                        add,
44
                        shiftToDA     
45
                    );
46
    
47
    signal x    :   tap_line  := (others=>(others=>'0'));
48
49
    constant coeff  : 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
   signal current_state :   states := startup;   --Enthält den aktuellen Status, initialisiert mit "startup"
75
   
76
   
77
   --Debugging
78
   signal test_counter :   integer range 0 to (N + 1) := 0;
79
   signal test_result   :   signed(25 downto 0) := (others => '0');
80
   signal test_temp_result   :   signed(25 downto 0) := (others => '0');
81
 
82
begin
83
  
84
    --Durchführen der Operationen abhängig vom State
85
    statemachine    :   process(clk, rst)
86
    variable result    :   signed(25 downto 0);    --Variable für Zwischenergebnis der Multiplikation, Darstellung 2.24
87
    variable temp_result    :   signed(25 downto 0);       
88
    variable counter_filter :   integer range 0 to (N + 1) := 0;
89
90
    begin
91
        
92
        if (rst = '0') then
93
            current_state <= startup;
94
        --Signaländerung bei steigender Flanke des 125MHz Clocks
95
        elsif (rising_edge(clk)) then
96
            case (current_state) is
97
            
98
                --Alles zurücksetzen
99
                when startup   =>
100
                    result := (others => '0');
101
                    temp_result := (others => '0');
102
                    counter_filter := 0;
103
                    
104
                    if (error_from_ADC = '0') then
105
                        current_state <= waitForADC;     
106
                    end if;
107
                    
108
                --Auf nächstes Sample warten
109
                when waitForADC =>
110
                    counter_filter := 0;            --Zurücksetzen des Zählers
111
                    result := (others => '0');      --Zurücksetzen des Ergebnisspeichers
112
                    temp_result := (others => '0'); --Zurücksetzen des Zwischenspeichers
113
                    
114
                    if (enable_data = '1') then
115
                        current_state <= readAndShift;            
116
                    end if;
117
                    
118
                --Neues sample einlesen. Array wird von vorne
119
                --nach hinten aufgefüllt
120
                when readAndShift =>
121
                    x(0) <= "0" & x_in;
122
                    for i in N downto 1 loop
123
                        x(i)    <=  x(i-1);    
124
                    end loop;
125
                    
126
                    current_state <= multiply;
127
                      
128
                --Multiplikation mit Koeffizienten
129
                when multiply =>
130
--                    test_counter    <=  counter_filter;
131
--                    if (counter_filter = N) then   --Abbruchbedingung für Rechenoperationen  
132
--                        current_state <= shiftToDA;           
133
--                    else
134
                    temp_result :=  coeff(counter_filter) * signed(x(counter_filter));
135
--                    test_temp_result    <=  temp_result;
136
                    current_state <= add;
137
--                    end if;
138
                    
139
                --Aufaddieren der Zwischenergebnisse    
140
                when add =>
141
                    result := result + temp_result;
142
--                    test_result  <=  result;
143
                    
144
                    if (counter_filter = N) then
145
                        current_state <= shiftToDA;
146
                    else
147
                        current_state <= multiply;
148
                        counter_filter := counter_filter + 1;
149
                    end if;
150
                --Daten an DA übermitteln
151
                when shiftToDA =>
152
                    y <= std_logic_vector(result(23 downto 12));   --Ergebnis in 11 Bit Form für AD, 13 Bit nach rechts geshiftet
153
                    filter_rdy <= '1';          --DA auf Datenempfang vorbereiten
154
                    if (error_from_ADC = '1') then
155
                        current_state <= startup;
156
                    elsif (enable_data = '0' and data_acknowledged = '1') then
157
                        current_state <= waitForADC;
158
                    end if;
159
            end case;
160
        else
161
            NULL;
162
        end if;
163
    end process statemachine;      
164
165
end FIR_Test_arch;

von Achim S. (Gast)


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 ;-)

von Alex K. (alexk99123)


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.

von Achim S. (Gast)


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.

von Alex K. (alexk99123)


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

von Alex K. (alexk99123)


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.
1
library IEEE;
2
use IEEE.STD_LOGIC_1164.ALL;
3
use IEEE.STD_LOGIC_ARITH.ALL;
4
use IEEE.STD_LOGIC_UNSIGNED.ALL;
5
6
entity frequency_counter is
7
    port
8
        (
9
            CLK_IN          :   in  std_logic;  --Clocksignal vom Board
10
            RST             :   in  std_logic;  --Active Low Reset
11
            FREQ_SIGNAL     :   in  std_logic;  --Daten zur Frequenzerfassung
12
            ACK_ERROR_ADC   :   in  std_logic;  --Error Signal des AD Wandlers
13
            
14
            COUNTER_FULL    :   out std_logic   --Signal bei Überlauf (Als Referenz für Frequenz des Signals)
15
        );
16
end frequency_counter;
17
18
architecture frequency_counter_arch of frequency_counter is
19
20
    --Da bei Sinus Werte immer 2x vorkommen, stellt der Counter quasi die doppelte Frequenz dar
21
    signal sign_frequency_counter   :   integer range 0 to 10 := 0;    --3 Bit Zähler
22
    
23
    signal sign_freq_signal             :   std_logic := '0';
24
    signal sign_count_once              :   std_logic := '0';
25
       
26
begin
27
28
    count_frequency :   process(CLK_IN, RST)
29
    begin
30
        if (rising_edge(CLK_IN)) then
31
            sign_freq_signal    <=  FREQ_SIGNAL;
32
            
33
            if (RST = '0' or ACK_ERROR_ADC = '1') then
34
                sign_frequency_counter <= 0;    --Counter zurücksetzen    
35
                
36
            --Counter erhöhen bei steigender Flanke des Frequenzsignals,
37
            --allerdings nur 1x zählen
38
            elsif (sign_count_once = '0' and FREQ_SIGNAL = '1') then
39
                sign_count_once <=  '1';
40
                if (sign_frequency_counter = 9) then
41
                    COUNTER_FULL    <=  '1';
42
                    sign_frequency_counter  <=  0;
43
                else
44
                    sign_frequency_counter <= sign_frequency_counter + 1;
45
                    COUNTER_FULL    <= '0';
46
                end if;
47
                
48
            elsif (sign_count_once = '1' and FREQ_SIGNAL = '0') then
49
                sign_count_once <=  '0';
50
            end if;
51
        else
52
            NULL;
53
        end if;
54
    end process count_frequency;
55
56
end frequency_counter_arch;

von Achim S. (Gast)


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.

von Hans (Gast)


Lesenswert?

Alex K. schrieb:
> use IEEE.STD_LOGIC_ARITH.ALL;
> use IEEE.STD_LOGIC_UNSIGNED.ALL;

Beitrag "IEEE.STD_LOGIC_ARITH.ALL obsolete"

von Elbi (Gast)


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.

von Achim S. (Gast)


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]
  • [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.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

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