Forum: FPGA, VHDL & Co. Genesys 2 (Xilinx Kintex 7): Audio-Codec Implementierung und Filteranbindung


von Mario A. (itse_me_mario)



Lesenswert?

Hier die neuen Binaries. Einmal mit sleep(0.0001) und einmal ohne sleep. 
:) Morgen geht´s weiter! :) Vielen Dank Gustl!!

: Bearbeitet durch User
von Gustl B. (-gb-)


Angehängte Dateien:

Lesenswert?

Mario A. schrieb:
> und einmal ohne sleep.

Naja, da müsstest du natürlich länger laufen lassen. Deine Aufnahmen 
sind sonst extrem kurze Zeit weil die Schleife sehr schnell durchläuft. 
Vielleicht solltest du die Aufnahme auch nicht an Hand der 
Schleifendurchläufe beenden sondern an Hand der empfangenen Datenmenge.

Das sähe so aus für die while-Schleife:

Aufnahmedauer_in_Sekunden = 1
...
while len(sr) < 14*32000*Aufnahmedauer_in_Sekunden:
...

dann nimmt der also bei
Aufnahmedauer_in_Sekunden = 1
so lange auf bis
14*32000*1 = 448000 Bytes empfangen wurden.

Mario A. schrieb:
> Vielen Dank Gustl!!

Bitte, viel Erfolg!

Mario A. schrieb:
> einmal ohne sleep

Auch da sind periodisch Fehler drinnen, seltsam. Muss ich mir mal 
genauer durchsimulieren.

: Bearbeitet durch User
von Gustl B. (-gb-)


Angehängte Dateien:

Lesenswert?

So, mir war langweilig^^ nein, ich möchte nur gucken ob nicht in dem 
Code von mir ein Fehler steckt. Daher habe ich jetzt das Python in VHDL 
übersetzt und in die Simulation gesteckt, das sieht jetzt so aus:

ADC erzeugt Sweep -> I2S -> FIR -> I2S -> DAC

Und der UART gibt die ungefilterten und die gefilterten Werte aus mit 8 
MBaud in Paketen mit je 14 Bytes.

In der Testbench wenden die Werte mit einem zweiten UART eingelesen, in 
einem Schieberegister (wie im Python) geschoben, die LSBs geprüft, 
Pakete erkannt, die Abtastwerte A,B,C,D zusammengebaut aus den Bits der 
14 Bytes eines Pakets und dann werden die in ein Textfile UART.txt unter
.\Forum_Audio\Forum_Audio.sim\sim_1\behav\xsim
geschrieben. Die kann man dann mit dem Python im Anhang plotten.
Wenn Fehler auftauchen wird das reportet.
(Ein Fehler wäre, wenn 14 Bytes nach einem korrekten Paket kein 
korrektes Byte erkannt wird.)

Tja weil Simulieren lange dauert habe ich nur 100 ms simuliert. Und da 
war kein Fehler drinnen.

Im Anhang Projekt als .zip, Testbench, Python um das erzeugte UART.txt 
zu plotten, Bildchen vom Plot.

: Bearbeitet durch User
von Mario A. (itse_me_mario)



Lesenswert?

>>Und da war kein Fehler drinnen.
Danke Gustl! Die Simulation läuft gerade. Also läuft bei der Decodierung 
mit Python noch etwas falsch?

Ich habe jetzt nochmal Aufnahmen vom linken und rechten Kanal gemacht 
(jeweils 10 Sekunden) und habe das Ergebnis geplottet. Fehler treten 
weiterhin auf laut Python.

Rechts: 319048 17247
Links:  319048 17248

Warum sich die Plots (vor dem Filter) unterscheiden weiß ich noch nicht.

Ich teste jetzt mal mein Filter und setze alle Koeffizienten auf 1.

: Bearbeitet durch User
von Mario A. (itse_me_mario)



Lesenswert?

Hab jetzt mal mein Filter simuliert:

16 Koeffizienten
alle Koeffizienten sind 1
Eingangswert ist konstant 1

Man sieht sehr schön, wie sich die Werte ändern. Es scheint allerdings 
so zu sein, dass die Werte zu langsam berechnet werden. Wie man sieht 
dauert die Berechnung 2x s_new_sample an. Daraus ergibt sich dann 
natürlich eine Abtastfrequenz von nur 16 kHz statt 32 kHz.
Ich check mal was ich tun kann.

: Bearbeitet durch User
von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

Hab jetzt zus. eine FSM und 2 parallele Datenregister eingebaut.
Die Berechnung scheint zu funktionieren. Meine FSM muss ich allerdings 
noch anpassen, da die Berechnung zu oft ausgeführt wird (siehe 
FilterMitFSM_Zoom).

Ich bin gerade am überlegen wie ich es am besten mache.
Mein Signal s_new_sample ist 1 / (8,192 MHz) auf 1. Mit dem Signal würde 
ich eine FSM starten, welche zuerst ein Signal für das Laden meines 
Parallelregister mit dem neuen Filtereingangswert generiert, danach ein 
Signal für die Berechnung der Multiply Adder Blöcke generiert und 
abschließend ein Signal für das Laden des Parallelregisters generiert.

brd_clk ist in dem Fall 100 MHz. Am sinnvollsten wäre es wohl, wenn ich 
den Puls hphone_valid (=s_new_sample) verkürzen würde.

fsm_manage_data : fsm_fir_manage_data
    PORT MAP (
                  ck              => brd_clk,
                  new_data_f      => hphone_valid,
                  load_data_f     => s_load_data,
                  start_calc_f    => s_start_calc,
                  write_data_f    => s_write_data
        );


reg_par_read_data : reg_par
   generic map(reg_width => 24)
   port map (
               ck       =>     brd_clk,
               ld_par   =>     s_load_data,
               data_in  =>     datain,
               data_out =>     s_filter_data
            );

reg_par_write_data : reg_par
   generic map(reg_width => 24)
   port map (
               ck       =>     brd_clk,
               ld_par   =>     s_write_data,
               data_in  =>     s_filtered_data,
               data_out =>     firout
            );

: Bearbeitet durch User
von Gustl B. (-gb-)


Angehängte Dateien:

Lesenswert?

Mario A. schrieb:
> Also läuft bei der Decodierung
> mit Python noch etwas falsch?

Könnte sein, glaube ich aber nicht.

Ich habe das jetzt mal für 100 ms simuliert und mir die empfangenen 
Bytes vom UART in die Datei UART.bin schreiben lassen.
Mit dem Python kannst du das dekodieren, das stellt keine Fehler fest.

Wo die Fehler herkommen weiß ich also nicht, könnte an deinem VHDL 
liegen, an irgendwelchen seltsam konfigurierten Takten, und eben am UART 
selbst, also an der Hardware.

Du kannst ja mal deine AudioInterface.vhd und den CLK_I2S.xci von deinem 
MMCM/PLL hochladen. Gerne auch das CLK_I2S_clk_wiz.v

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

Hallo Gustl, hab dir mal die Dateien angehängt. Ich verwende den 
Clocking Wizard. Aktuell mach ich mir aus den 200 MHz noch 100 MHz aber 
an sich könnte man es auch bei 200 MHz belassen. Dein Projekt von 
gestern (mit den 2 UARTs) läuft auch bei mir, inklusive der zugeh. 
Python-Datei. Danke!

Ich bin gerade dabei etwas bei meinem FIR-Filter zu testen. Ich versuche 
mal ein Clock Domain Crossing einzubauen. Das Problem sind die 
unterschiedlichen Takte in meinen Filter-Dateien. Ich kann gerne noch 
das Projekt hochladen.

von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Ich versuche mal ein Clock Domain Crossing einzubauen.

Das ist gar nicht sooo einfach. Aber es gibt dazu ein paar XPMs und du 
kannst für deine Daten einfach einen FIFO verwenden der mit zwei 
unabhängigen Takten arbeitet. Ein Takt zum Schreiben und ein anderer 
Takt zu Lesen.

Mario A. schrieb:
> Ich kann gerne noch das Projekt hochladen.

Das mit dem MMCME2_ADV den dir der Clock Wizard erzeugt/einverdrahtet 
hat sieht OK aus und das mit dem UART ebenfalls.

Du könntest jetzt tatsächlich noch versuchen nicht mit Python und 
pySerial zu lesen sondern direkt über die D2XX API aus einem C Programm 
heraus. Geht vermutlich auch mit Matlab oder Python diese API zu 
verwenden.

Und falls du ein Oszilloskop hast das UART dekodieren kann:
Das kann dir vermutlich auch anzeigen wenn da Fehler drinnen sind wegen 
zu langen oder zu kurzen Bits auf der Leitung oder falschen Start/Stopp 
Bedingungen.
Sonst spiele auch gerne etwas mit den pySerial Parametern herum
https://pythonhosted.org/pyserial/shortintro.html

Und hier die pySerial API:
https://pythonhosted.org/pyserial/pyserial_api.html

: Bearbeitet durch User
von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

>>Das ist gar nicht sooo einfach.
Habs schon fast befürchtet. :)

>>du kannst für deine Daten einfach einen FIFO verwenden der mit zwei
unabhängigen Takten arbeitet. Ein Takt zum Schreiben und ein anderer
Takt zu Lesen.
Verwendest du hierfür den FIFO Generator von Xilinx?
Habe ich dann mein Timingproblem (wie beschrieben) nicht trotzdem noch? 
Ich habe dir mal mein "Basis"-Projekt ohne CDC angehängt, damit du´s 
besser nachvollziehen kannst. s_new_sample ist für 1/8,192 MhHz '1' und 
wird alle 1/32 kHz gesetzt.


>>direkt über die D2XX API aus einem C Programm heraus.
>>spiele auch gerne etwas mit den pySerial Parametern herum
Ja das klingt nach einem guten Plan! Ich notier mir das gleich für 
später. Zuerst möchte ich noch mein Filter zum laufen bringen. :) Das 
nervt mich schon zu lange... Mein erster Versuch mit CDC funktioniert 
leider nicht. Ich les mich morgen weiter ein.

: Bearbeitet durch User
von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Verwendest du hierfür den FIFO Generator von Xilinx?

Ja.

Mario A. schrieb:
> Habe ich dann mein Timingproblem (wie beschrieben) nicht trotzdem noch?

Kommt drauf an weilche Timing Probleme, da gibt es ja mehrere viele.
Normalerweise hast du zwei unabhängige Taktdomänen und willst Daten von 
einer in die andere bringen. Da schreibst du also mit Takt A in der 
einen Domäne in das FIFO und in der anderen Domäne liest du mit Takt B 
aus dem FIFO.
Da kann es auch Probleme geben, wenn du mehr Daten schreibst als du auf 
der anderen Seite liest, dann läuft der FIFO voll und du verlierst 
Daten.
Und dann solltest du die Takte noch constrainen, also dem Vivado sagen, 
dass die Takte voneinander unabhängig sind wenn sie es auch sind.

Wenn aber deine Takte gar nicht echt verschieden sind, sondern aus der 
gleichen Quelle stammen und nur ganzzahlige Vielfache sind, dann kannst 
du die Taktübergänge auch einfacher gestalten.

Tja aus dem Code wird mir nicht klar zwischen welchen Takten du welche 
Daten austauschen möchtest. Kannst du das mal erklären?

Mario A. schrieb:
> Ja das klingt nach einem guten Plan! Ich notier mir das gleich für
> später.

Ist nicht sehr komplex, das sind nur ein paar wenige Funktionen die du 
brauchst um vom UART zu lesen und der 
https://www.ftdichip.com/Support/Documents/ProgramGuides/D2XX_Programmer%27s_Guide(FT_000071).pdf 
enthält sehr viele funktionierende Beispiele.

Edit:
Ich kann dein Projekt nicht bauen weil zwei .coe Dateien fehlen.

: Bearbeitet durch User
von Gustl B. (-gb-)


Lesenswert?

So, die .coe Dateien habe ich hier im Thread gefunden.
Ich kann dein Projekt bauen, aber nicht für den dicken Kintex, denn 
dafür habe ich keine Vivado Lizenz. Aber ich habe das jetzt mal für 
einen Artix gebaut. Und es gibt Timingprobleme zwischen den beiden 
erzeugten Takten:

clk_out1 => s_clk_100_MHz,
clk_out2 => s_clk_8_192_MHz_MCLK,

Tja was hilft? Signale die die Grenze überschreiten einsynchronisieren 
und false-path constraint setzen oder eben dual clock FIFO verwenden.
Welche Signale das sind siehst du im Implemented Timing Report.

---------------------------------------------------------

Aber ich habe noch eine Frage an die Profis:

Zwei Taktdomänen mit den Takten A und B. Jetzt will ich in Domäne A 
erkennen ob ein Bit in Domäne B gesetzt ist. Also takte ich das Bit in 
Domäne A ein und prüfe dann das eigetaktete Signal.

Aber wie ist das wenn ich Daten mit Valid Signal abgreifen will?
Also wieder Domäne A und B. Das Valid Signal mit Daten werden in B 
erzeugt. Ich takte also das Valid Signal in A ein und prüfe das 
eingetaktete Valid.
Was mache ich mit den Daten? Wenn die Daten mit dem Valid synchron 
erscheinen und danach weiterhin anliegen, dann müsste es doch genügen 
nur das Valid einzutakten. Ist das eingetaktete Valid in A dann gesetzt 
kann ich die Daten aus B in A direkt übernehmen und zwar weil die Daten 
aus B dann schon für mehrere Takte von A stabil anliegen. Oder ist das 
falsch und ich muss auch die Daten aus B eintakten?

So wie ich ds bisher verstanden habe passiert Metastabilität nur, wenn 
die Daten zur Taktflanke nicht definiert high oder low sind. Wenn ich 
also in der HDL sicherstellen kann, dass die Daten zur Flanke schon eine 
Zeit lang anliegen (weil das Valid einen Takt früher abgefragt wird und 
die Daten mehrere Takte gesetzt werden) dann sollte man die Daten auch 
ohne Eintakten sampeln können.

Danke!

: Bearbeitet durch User
von Mario A. (itse_me_mario)



Lesenswert?

>>Kommt drauf an welche Timing Probleme
Timingproblem ist vllt das falsche Wort. Ich möchte die Berechnung mit 
s_new_sample starten (s_new_sample = hphone_valid). s_new_sample ist 
1/8,192MHz lang auf '1' und das periodisch alle 1/32kHz. Anschließend 
soll das Ergebnis zum nächsten 32 kHz Takt ausgegeben werden. Das sollte 
dann eigtl. das Filterverhalten für eine Abtastrate von 32 kHz 
widerspiegeln.

Ich könnte ja an sich den einen FIFO mit einem 32 kHz Takt laden und den 
zweiten (Ausgeben der Werte) mit einem verschobenen/negierten 32 kHz 
Takt. Ich denke das geht aber auch einfacher.


>>Und dann solltest du die Takte noch constrainen
Das war eigtl. im XDC. Vllt hast du´s übersehen, oder meinst du was 
anderes?
set_clock_groups -asynchronous -group [get_clocks *clk_out1*] -group 
[get_clocks *clk_out2*]

Grad noch gesehen: Beim Clocking Wizard sollten es nur 2 Ausgänge sein 
(100 MHz und 8,192 MHz)

Wenn ich die FSM, die Parallelregister und die Multiply Adderblöcke mit 
8,192 MHz versorge, benötigt die Berechnung die doppelte Zeit.
Mit 16,384 MHz ist es dasselbe.

Ich habe jetzt die FSM und die Parallelregister mit 8,192 MHz versorgt 
und die Multiply Adder Blöcke mit 16,384 MHz. Laut Testbench scheint es 
nun zu funktionieren (siehe FIR_Testbench).

Den 8,192 MHz Takt erzeuge ich mir nun direkt aus dem 16,384 MHz Takt 
vom Clocking Wizard. Die beiden Clocks muss ich dann nicht extra als 
asynchron setzen, oder? Mein Clocking Wizard erzeugt mir nun einen 100 
MHz Takt und einen 16,394 MHz Takt.

Ich werd es nachher mal auf dem Board testen. :)

: Bearbeitet durch User
von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Timingproblem ist vllt das falsche Wort. Ich möchte die Berechnung mit
> s_new_sample starten (s_new_sample = hphone_valid).

Das macht keine Timingprobleme, du verwendest das nur als Enable Signal. 
Kannst du machen. Aber verwende s_new_sample nicht als Takt.
Den FIFO oder ein CDC brauchst du da nicht.

Mario A. schrieb:
> Den 8,192 MHz Takt erzeuge ich mir nun direkt aus dem 16,384 MHz Takt
> vom Clocking Wizard. Die beiden Clocks muss ich dann nicht extra als
> asynchron setzen, oder? Mein Clocking Wizard erzeugt mir nun einen 100
> MHz Takt und einen 16,394 MHz Takt.

Nun ähm ... doch, die sind asynchron wenn die Flanken nicht schön 
übereinander liegen. Guck dir an was deine PLL/MMCM genau macht.
clk_wiz_0_clk_wiz.v sagt:

  MMCME2_ADV
  #(.BANDWIDTH            ("OPTIMIZED"),
    .CLKOUT4_CASCADE      ("FALSE"),
    .COMPENSATION         ("ZHOLD"),
    .STARTUP_WAIT         ("FALSE"),
    .DIVCLK_DIVIDE        (1),
    .CLKFBOUT_MULT_F      (4.750),
    .CLKFBOUT_PHASE       (0.000),
    .CLKFBOUT_USE_FINE_PS ("FALSE"),
    .CLKOUT0_DIVIDE_F     (9.500),
    .CLKOUT0_PHASE        (0.000),
    .CLKOUT0_DUTY_CYCLE   (0.500),
    .CLKOUT0_USE_FINE_PS  ("FALSE"),
    .CLKOUT1_DIVIDE       (116),
    .CLKOUT1_PHASE        (0.000),
    .CLKOUT1_DUTY_CYCLE   (0.500),
    .CLKOUT1_USE_FINE_PS  ("FALSE"),
    .CLKOUT2_DIVIDE       (19),
    .CLKOUT2_PHASE        (0.000),
    .CLKOUT2_DUTY_CYCLE   (0.500),
    .CLKOUT2_USE_FINE_PS  ("FALSE"),
    .CLKIN1_PERIOD        (5.000))

Die externen 200 MHz werden mit 4.75 multipliziert ( = 950 MHz) und dann 
einmal durch 9.5 geteilt, das ergibt die 100 MHz.
Und einmal durch 116 geteilt. Das ergibt die 8.192 MHz ( = 8.189655172 
MHz).
Wenn du dir 16.394 MHz erzeugen lässt, dann wird vermutlich durch 58 
geteilt, muss aber nicht sein, es könnte auch vorher der Multiplikator 
verändert werden.
Jedenfalls sind deine 8.192 MHz (oder 16.394 MHz) asynchron zu den 100 
MHz.

Ich würde empfehle sehr nur einen Takt im System zu haben, also die 100 
MHz oder vielleicht sogar die 200 MHz die ja extern reinkommen. Ja, du 
brauchst die 8.192 MHz für das I2S, da kommst du nicht drum rum so wie 
dein I2S geschrieben ist, und da musst du dann auch ein Clock Domain 
Crossing machen. Aber nur zwischen deinem I2S Transmitter und dem Rest 
im FPGA. Die Filterei und so würde ich alles mit einem Systemtakt laufen 
lassen und gegebenenfalls Enable Signale verwenden die du ja vom I2S 
Transmitter sowieso bekommst. Das s_new_sample könntest du wunderbar als 
so ein Signal verwenden - aber eben nicht als Takt.

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

>>die sind asynchron wenn die Flanken nicht schön übereinander liegen.
Ich meinte den 8,192 und 16,384 MHz Takt. Den 8,192 MHz Takt baue ich 
mir aus dem 16,384 MHz Takt vom Clocking Wizard. Die Ausgänge vom 
Clocking Wizard (100 MHz und 16,384 MHz) setze ich weiterhin asynchron 
zueinander.


>>da musst du dann auch ein Clock Domain Crossing machen.
Es scheint jetzt auch ohne zu funktionieren. Die Frequenzgänge sehen gut 
aus. Die Koeffizienten sind Konstanten im Code.
Das mit dem CDC klingt aber sehr interessant. Wäre sicher die schönere 
Lösung. Danke für den Tipp! Könnte ich auch anschließend noch 
implementieren.

Ich schreibe jetzt mal mein Projekt noch ein klein wenig um und füge den 
Block Memory Generator wieder ein. Dann würde ich mich dem Einlesen der 
Daten vom UART mit C widmen wie du es beschrieben hast. :) Hierfür 
nochmal danke!
>>direkt über die D2XX API aus einem C Programm heraus.

: Bearbeitet durch User
von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Es scheint jetzt auch ohne zu funktionieren.

In der Simulation schon, da gibt es keine Metastabilitäten. Und mit dem 
Auge siehst du an der Signalform nicht ob mal ein Abtastwert fehlt.

Mario A. schrieb:
> Ich schreibe jetzt mal mein Projekt noch ein klein wenig um und füge den
> Block Memory Generator wieder ein.

Viel Erfolg!

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

>>In der Simulation schon, da gibt es keine Metastabilitäten.
Kann ich das auch mit einer Timinganalyse in Vivado sehen/ermitteln?

>>Ich würde empfehle sehr nur einen Takt im System zu haben
Ok, ich könnte ja mal alles mit den 200 MHz machen (bis auf das I2s).

>>Ja, du brauchst die 8.192 MHz für das I2S [...] und da musst du dann auch ein 
Clock Domain Crossing machen. Aber nur zwischen deinem I2S Transmitter und dem 
Rest im FPGA.
Ja das könnte ich versuchen! Ich sehe mir das CDC nochmal genauer an.

>>[...] und gegebenenfalls Enable Signale verwenden die du ja vom I2S
Transmitter sowieso bekommst.

>>Das s_new_sample könntest du wunderbar als so ein Signal verwenden - aber eben 
nicht als Takt.
Ich habe s_new_sample nicht als Takt verwendet. Vllt meinst du was 
anderes? Also z. B. aus s_new_sample mit meiner FSM (mit 200 MHz Takt) 
ein Signal mit kürzerer Pulsdauer machen und dieses dann für die 
Multiply Adder Blöcke verwenden?

Vielen Dank dir!

von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Kann ich das auch mit einer Timinganalyse in Vivado sehen/ermitteln?

Weiß ich nicht.

Mario A. schrieb:
> Ok, ich könnte ja mal alles mit den 200 MHz machen (bis auf das I2s).

Das könntest du tun. Wobei dann nur 5 ns zwischen den Takten sind. Ich 
würde eine Frequenz verwenden die so hoch wie nötig ist, aber auch so 
niedrig wie möglich. Prinzipiell kannst du für Alles die 8.192 MHz 
verwenden, aber das würde beim UART probleme machen. Aber 200 MHz sind 
vielleicht schon unnötig hoch. 100 MHz finde ich da völlig OK, dann ist 
da schön Zeit für die Kombinatorik.
Ist aber natürlich eigentlich egal solange das Timing eingehalten wird. 
Höherer Takt führt aber zu höherer Leistungsaufnahme.

Mario A. schrieb:
> Ich sehe mir das CDC nochmal genauer an.

Ja, wobei das da recht einfach ist. Es reicht die Takte asynchron zu 
setzen und das s_new_sample mit dem Systemtakt einzutakten. In die 
andere Richtung taktet der I2S Transmitter schon sein Valid Flag ein.

Mario A. schrieb:
> Ich habe s_new_sample nicht als Takt verwendet.

Genau, das passt schon. Ich wollte nur sagen, dass du das auch nicht als 
Takt verwenden solltest.

Mario A. schrieb:
> Also z. B. aus s_new_sample mit meiner FSM (mit 200 MHz Takt)
> ein Signal mit kürzerer Pulsdauer machen und dieses dann für die
> Multiply Adder Blöcke verwenden?

Wenn du das willst, dann kannst du ja die steigende Flanke von 
s_new_sample verwenden - aber nicht als Takt!
Quasi so:

signal s_new_sample_sr: std_logic_vector(1 downto 0):="00";

Dann getaktet mit den 200 MHz:

s_new_sample_sr <= s_new_sample_sr(0) & s_new_sample;

Und dann kannst du die Flanke erkennen:

if s_new_sample_sr = "01" then
...
Da kommt alles rein was passieren soll wenn eine steigende Flanke von 
s_new_sample_sr da war.
...
end if;

Man verwendet dann s_new_sample_sr quasi als Enable.

Edit:
Hast du in deinem Timingreport auch Fehler oder wird das Timing erfüllt?

: Bearbeitet durch User
von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

Hallo Gustl, danke für deine Antwort!

Ich hab gerade mal mein Projekt (ohne CDC!) umgeschrieben auf 21 Bit 
Koeffizienten und dann ist mir auch ein Fehler aufgefallen (siehe 
Fehler.png). Beim Projekt mit den 16 Bit Koeffizienten tritt dieser 
Fehler in der Simulation nicht auf. Das spricht also auch gegen diese 
Variante.


Eine Frage Gustl: Hast du in deinem Post jetzt 2 Methoden beschrieben? 
1x mit FSM und 1x ohne? Bin mir gerade nicht ganz sicher.

>>Ja, wobei das da recht einfach ist. Es reicht die Takte asynchron zu
setzen und das s_new_sample mit dem Systemtakt einzutakten.
Also FSM raus, s_new_sample eintakten und als Start für meine Multiply 
Adder Blöcke verwenden?


Also so z.B. (Version mit meiner FSM)?

In AudioInterface:
NEW_SAMPLE_PROC : process (s_clk_100_MHz)
    begin
        if (s_clk_100_MHz'event and s_clk_100_MHz = '1') then

            s_new_sample_sr <= s_new_sample_sr(0) & s_new_sample;
            s_begin_filter <= '0';

            if s_new_sample_sr = "01" then
                s_begin_filter <= '1';
            end if;
        end if;
end process;

s_begin_filter dient dann als Eingang für meine beiden 
links-/rechtsseitigen Filter. Darin befindet sich noch die FSM und die 
Parallelregister, die alle mit 100 MHz getaktet sind.
Hier aber zusätzlich noch CDC einbauen, oder nicht?

Ich seh mir gerade ein Video zum CDC an. :)


>>Hast du in deinem Timingreport auch Fehler oder wird das Timing erfüllt?
Ich habe auf Report Timing Summary geklickt. Design Timing Summary zeigt 
nichts verdächtiges an. Nur Check Timing (wie im vorherigen Post zu 
sehen).

: Bearbeitet durch User
von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Hast du in deinem Post jetzt 2 Methoden beschrieben?
> 1x mit FSM und 1x ohne? Bin mir gerade nicht ganz sicher.

Ne, ich habe das unabhängig von der FSM beschrieben. Ich weiß nicht 
genau was deine FSM machen soll.

Wenn es so ist:
Du verwendest einen 200 MHz Takt, bekommst aber nur selten/manchmal 
einen neuen Abtastwert zusammen mit s_new_sample = '1', dann ist es 
sinnvoll das Filter und alle Berechnungen nicht in jedem Takt rechnen zu 
lassen, sondern nur wenn ein neuer Abtastwert reinkommt.
Wenn deine Filterung sogar mehrere Takte des schnellen Taktes braucht, 
dann kannst du s_new_sample verwenden um einen "Durchlauf" durch ein 
seriell berechnetes Filter anzustoßen.

Und da würde ich das s_new_sample eben verwenden wie beschrieben. Also 
eintakten und dann auswerten ob da eine steigende Flanke vorliegt mit 
"01".
Das mit deinem Zusatzsignal s_begin_filter hast du richtig gemacht, das 
ist bei steigender Flanke von s_new_sample (also kurz danach) für einen 
Takt des schneellen Taktes '1'.

von Mario A. (itse_me_mario)


Lesenswert?

>>Ich weiß nicht genau was deine FSM machen soll.
Also die FSM hat nur, wenn new_sample = 1 ist, die Filtereingangsdaten 
in ein paralleles Datenregister geladen. 1 Takt später hat es dann die 
Berechnung mit den Multiply Addern gestartet und noch 1 Takt später hat 
es wieder ein paralleles Datenregister (mit den dann gefilterten Daten) 
geladen.

>>sondern nur wenn ein neuer Abtastwert reinkommt.
Ja das möchte ich auch. Alle 32 kHz habe ich einen neuen Wert und dann 
soll die Berechnung natürlich nur 1x durchgeführt werden.

>>Wenn deine Filterung sogar mehrere Takte des schnellen Taktes braucht
Eigtl. sollte die Berechnung nur 1 Takt brauchen.

>>Also eintakten und dann auswerten ob da eine steigende Flanke vorliegt mit
"01".
Okay dann muss ich das jetzt nur noch eintakten, danke!

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

So hätte ich das CDC jetzt eingebunden/angewendet 
(https://www.youtube.com/watch?v=eyNU6mn_-7g):

NEW_SAMPLE_PROC : process (s_clk_100_MHz)
    begin
        if rising_edge(s_clk_100_MHz) then
            s_new_sample_sr <= s_new_sample_sr(0) & r2_Data;
            s_begin_filter <= '0';

            if s_new_sample_sr = "01" then
                s_begin_filter <= '1';
            end if;
        end if;
end process;


-- Crossing Data from Slow to Fast Domain (8.192 MHz to 100 MHz)
CDC_FAST_CLOCK_PROC : process (s_clk_100_MHz)
    begin
        if rising_edge(s_clk_100_MHz) then
            r1_Data <= s_new_sample; --r1_Data is metastable
            r2_Data <= r1_Data; -- r2_Data is stable!
         end if;
end process;

Die Simulation sieht gut aus. Allerdings haben auch ohne CDC beide 
Varianten mit 16 und 21 Bit Koeffizienten in der Simulation gut 
ausgesehen. Alleine die Flankenerkennung hat geholfen. Aber CDC sollte 
ja trotzdem rein. Ich hoffe das passt jetzt. Danke Gustl!

: Bearbeitet durch User
von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Okay dann muss ich das jetzt nur noch eintakten, danke!

Mario A. schrieb:
> s_new_sample_sr <= s_new_sample_sr(0) & s_new_sample;

Das ist schon das Eintakten.

Mario A. schrieb:
> r1_Data <= s_new_sample; --r1_Data is metastable
>             r2_Data <= r1_Data; -- r2_Data is stable!

Das macht eigentlich nichts anders als das Schieben von s_new_sample_sr.

Mario A. schrieb:
> Die Simulation sieht gut aus. Allerdings haben auch ohne CDC beide
> Varianten mit 16 und 21 Bit Koeffizienten in der Simulation gut
> ausgesehen.

Genau, in der Simulation ändert sich da nix.

Mario A. schrieb:
> Ich hoffe das passt jetzt. Danke Gustl!

Bitte, viel Erfolg!

von Mario A. (itse_me_mario)


Lesenswert?

>>Das macht eigentlich nichts anders als das Schieben von s_new_sample_sr.
Oh stimmt das entspricht ja schon dem CDC... Danke! Dann kann ich meins 
wieder rausnehmen, bzw. mein CDC ohne deins testen.

von Mario A. (itse_me_mario)


Lesenswert?

Der Frequenzgang mit dem NEW_SAMPLE_PROC (s. oben) scheint leider doch 
nicht zu stimmen. Ich schau´s mir morgen nochmal an...

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

@Gustl
Das Filter mit deiner Methode sieht in der Simulation sehr gut aus. Wenn 
ich aber die für eine Abtastraste von 32 kHz entworfenen Koeffizienten 
nehme, ist der Frequenzgang wieder verschoben. Erst wenn ich die 
Filterkoeffizienten für eine Abtastrate von 16 kHz entwerfe stimmt der 
Frequenzgang mit den Koeffizienten überein.

Es sieht in der Simulation so aus, dass der gefilterte Wert erst 
verzögert rauskommt. Ich sehe mir das nachher noch an, indem ich wieder 
alle Koeffizienten auf 1 und den Eingang auch auf 1 setze.

Entweder ich versuche die Berechnung der Multiply Adder Blöcke mit der 
doppelten Frequenz durchzuführen, oder ich baue vllt doch einen FIFO 
ein? Wenn ich die Multiply Adder mit einem anderen Clock versorgen 
möchte (z.B. 100 MHz) und alles andere mit 50 MHz läuft, sollte ich in 
FIRFilter_...vhd auch eine Flankenerkennung einbauen, damit der Multiply 
Adder nur einmal gestartet wird, oder?

Die Flankenerkennung in AudioInterface.vhd würde so bleiben (nur der 
Takt ändert sich zu 50 MHz).

Den 50 MHz Takt hätte ich mir jetzt intern aus dem 100 MHz Takt erzeugt:

process(s_clk_100_MHz)
   begin
      if rising_edge(s_clk_100_MHz) then
         s_clk_50_MHz <= not s_clk_50_MHz;
      end if;
   end process;

@Gustl Wie würdest du da vorgehen? Und kannst du vllt (bitte) mal einen 
Blick drüber werfen? :) Vielen Dank im Voraus!

von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Es sieht in der Simulation so aus, dass der gefilterte Wert erst
> verzögert rauskommt.

Das ist ganz normal, das ist ja ein FIR.

Zum Anhang:
Gen2_FIR.zip (39,2 MB, 1 Downloads)

Es reicht im Projektordner der .srcs Unterordner und die .xpr Datei. Der 
Rest wird erzeugt. Die beiden .coe Dateien sollten eigentlich ebenfalls 
im .srcs Ordner irgendwo beheimatet sein.

Dann zur Simulation:
Da hast du "Disabled Sources" das brauchst du eigentlich nicht. Du 
kannst ja auswählen mit Rechtsklick > "Set as Top" welche der 
Testbenchdateien das Toplevel der Simulation seien soll.

----------------------------------------------------

Mario A. schrieb:
> @Gustl Wie würdest du da vorgehen? Und kannst du vllt (bitte) mal einen
> Blick drüber werfen? :) Vielen Dank im Voraus!

Das mache ich gerade. Für mich sieht das teilweise unnötig kompliziert 
aus. Klar, man kann für jede Kleinigkeit (ich meine reg_par) eine 
Komponente schreiben, das am Ende führt das zu eher mehr 
Unübersichtlichkeit wie ich finde. Auch dein fsm_fir_manage_data ist 
zwar nett, aber wie ich finde deutlich zu kompliziert. Das sind bei dir 
ohne Leerzeilen vielleicht 50 Zeilen Code, mit FSM, concurrent 
Statements, beide Sauce und scharf.

Aber was macht das eigentlich?
new_sample kommt rein, ist also für einen Takt '1' und danach werden der 
Reihe nach s_load_data, s_start_calc, s_write_data je für einen Takt auf 
'1' gesetzt.

Da kann man auch ein 3-Bit-Register nehmen das man mit new_sample = '1' 
auf "001" setzt und in jedem Takt um 1 nach links schiebt. Man kann auch 
in jedem Takt schieben und eben auch gleich new_sample hineinschieben:
1
signal state_reg: std_logic_vector(2 downto 0):="000";
2
...
3
process begin
4
wait until rising_edge(clk);
5
   state_reg <= state_reg(1 downto 0) & new_sample;
6
   s_load_data <= state_reg(0);
7
   s_start_calc <= state_reg(1);
8
   s_write_data <= state_reg(2);
9
end process;

Und dafür braucht es dann auch wirklich keine eigene Komponente.

Ich schreibe dir das jetzt mal etwas um wie ich das bauen würde. Ja, ich 
lasse dir die xbip_multadd drinnen auch wenn man das schön mit * und + 
schreiben könnte (-:

Edit:
Weil das ja für links und rechts zwei identische Filter-Hardwaren^^ 
werden sollen würde ich die auch nicht unterschiedlich benennen sondern 
das dann bei der Instanziierung machen. Die unterscheiden sich ja am 
Ende nur durch das .coe File das ins BRAM geladen wird.

Die reg_par hast du als CDC eingebaut?
Ja, kann man machen, geht auch einfacher. Und ich bin mir gar nicht 
sicher ob du die brauchst, denn es sollte reichen wenn du sicherstellst, 
dass sich die Daten am Eingang während der Filterei nicht ändern.

Und zu den s_load_data, s_start_calc, s_write_data von oben:
Das kann man auch über einen Zähler machen den von 0 bis 2 zählt. Da 
spart man noch ein Bit.

: Bearbeitet durch User
von Mario A. (itse_me_mario)


Lesenswert?

>>Das ist ganz normal, das ist ja ein FIR.
1 Takt Verzögerung schon. :) Da war glaub ich ein Fehler im anderen 
(Vergleichs)Projekt.

>>Das mache ich gerade.
Danke!

>>Für mich sieht das teilweise unnötig kompliziert aus.
Da geb ich dir vollkommen Recht, vieles könnte man einfacher lösen.

>>Ja, ich lasse dir die xbip_multadd drinnen auch wenn man das schön mit * und + 
schreiben könnte (-:
Das ist halt schön für die Generate-Anweisung im Zusammenhang mit der 
transponierten Direktform. ;) Spricht halt direkt die DSP-Slices an.

>>Weil das ja für links und rechts zwei identische Filter-Hardwaren
Ja ich wollt es erst mal allgemein machen (falls ich mal verschiedene 
Filter gleichzeitig testen will). Danke dir schon mal! Ich setz mich 
morgen früh gleich wieder ran.

: Bearbeitet durch User
Beitrag #6479367 wurde vom Autor gelöscht.
von Gustl B. (-gb-)


Angehängte Dateien:

Lesenswert?

Mario A. schrieb:
> Das ist halt schön für die Generate-Anweisung im Zusammenhang mit der
> transponierten Direktform. ;) Spricht halt direkt die DSP-Slices an.

Ja, stimmt, aber heutzutage gilt eigentlich generell:

Ein Compiler/Synthesizer kann besser optimieren als der Mensch.

Ein Profi ist da vermutlich noch besser, mag ein, aber mein Fazit daraus
ist:

Schreibe deinen Code so, dass den der Compiler/Synthesizer verstehen
kann und achte dann vor allem auf Lesbarkeit und Wartbarkeit. Was nützt
der kryptischste C Code oder das verschachteltste VHDL Konstrukt wenn
man das auch viel lesbarer hätte schreiben können und am Ende doch exakt
der gleiche ASM/Netlist bei rauskommt?
Da hat man eigentlich eher verloren weil der kryptische Code deutlich
mehr Denkleistung erfordert um ihn zu verstehen.

Mario A. schrieb:
> Ja ich wollt es erst mal allgemein machen (falls ich mal verschiedene
> Filter gleichzeitig testen will).

Dafür hast du ja die beiden .coe Dateien. Du brauchst also nur zwei
unterschiedliche BRAM Blöcke.

-------------------------------------------------------------------

So, ich habe das jetzt mal umgeschrieben ohne die Funktion zu ändern und
danach simuliert mit dem externen ADC und DAC in der Testbench.
Und das sieht gut aus!

Dein Filter hat seine Grenzfrequenz grob bei 1,5*f_sample und das sieht
man auch in der Simulation, ich habe 0,5*f_sample nach ca. 32 ms,
1,5*f_sample ist also bei ca. 9.6 ms und in der Tat, das würde passen.

Im Anhang jetzt mal die umgeschriebenen Dateien und Bildchen.

Oh und die IEEE.STD_LOGIC_UNSIGNED.ALL habe ich herausoperiert.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;

Genügt völlig.

: Bearbeitet durch User
von Mario A. (itse_me_mario)



Lesenswert?

Erst mal vielen vielen Dank Gustl!
Ich habe deine Dateien eingebunden und den Bitstream generiert. Der 
Frequenzgang ist leider wieder verschoben (siehe Anhang). In ähnlicher 
Weise wie du es jetzt auch gelöst hast, hab ich das gestern Abend auch 
mal probiert (nur mit zus. FSM und Parallelregister). Ergebnis war 
folglich dasselbe.

Ich weiß nicht ob es an den Koeffizienten liegt... Mit dem FIR Compiler 
und denselben Koeffizienten stimmt der Frequenzgang, sollte also passen. 
Was auch komisch ist, ist diese Erhöhung im Frequenzgang bei höheren 
Frquenzen.
Der FIR Compiler verwendet zus. einen FIFO. Denkst du das würde in dem 
Fall helfen? FIR Compiler mit und ohne FIFO liefert dasselbe Ergebnis.

Macht der FIR Compiler irgendwas anders? Ansonsten akzeptier ich jetzt 
einfach, dass mein Filter die Abtastrate nochmal halbiert... Ist da 
irgendwo eine zus. Abtastung drin? Ich könnte es auch mal so versuchen, 
wie du vorgeschlagen hast (mit * und + in einem Process). Sollte aber 
eigtl. daran nichts ändern.

Zur Logik:
Ich multipliziere eine Q0.23 Festkommazahl mit einer Q0.15 
Festkommazahl. An sich müsste da eine Zahl im 2.38 Format rauskommen (2 
Vorzeichenbits und 38 Bits für den Nachkommaanteil). Wenn ich dann 38 
downto 15 entnehme sollte es eigentlich stimmen. Hätte auch mal 39 
downto 16 und 37 downto 14 versucht, aber das bringt nichts.

>>Ein Compiler/Synthesizer kann besser optimieren als der Mensch.
Das stimmt schon, ja.

>>Dafür hast du ja die beiden .coe Dateien. Du brauchst also nur zwei
unterschiedliche BRAM Blöcke.
Ich meinte mit unterschiedlicher Anzahl an Bits für die Koeffizienten 
und unterschiedlicher Filterordnung. Ansonsten ja. :)

Vielen Dank dir!

von Mario A. (itse_me_mario)



Lesenswert?

Hallo Gustl,

ich habe jetzt mal schnell ein altes "serielles Filter" implementiert 
und getestet. Damit stimmt der Frequenzgang.

Dann macht mir wohl mein Multiply Adder Block irgendwas kaputt. 
Wahrscheinlich hat der eine Verzögerung. Vllt einen FIFO einbauen?

Hättest du die Berechnung mit + und * in einem Process gemacht, oder 
hättest du auch Blöcke mit Generate erzeugt?

Ich könnte auch mal versuchen die DSP Slices direkt anzusprechen über 
die Makros im IP Catalog.

Meine Einstellungen für den Multiply Adder waren A:B - P Latency -1 und 
C - P Latency -1.
Ich hab das mal in 0 und 0 geändert. Hat auch nichts gebracht. Vielen 
Dank im Voraus!

von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Der Frequenzgang ist leider wieder verschoben (siehe Anhang).

Da sind ziemlich viele Bildchen im Angang und ich verstehe nich welches 
wozu gehört und was zeigen soll.

Mario A. schrieb:
> Mit dem FIR Compiler
> und denselben Koeffizienten stimmt der Frequenzgang, sollte also passen.

Genau, die passen.

Mario A. schrieb:
> Was auch komisch ist, ist diese Erhöhung im Frequenzgang bei höheren
> Frquenzen.

Stimmt, aber da weiß ich nicht wo das her kommt.

Mario A. schrieb:
> Der FIR Compiler verwendet zus. einen FIFO. Denkst du das würde in dem
> Fall helfen? FIR Compiler mit und ohne FIFO liefert dasselbe Ergebnis.

Nein, den FIFO brauchst du nicht.

Mario A. schrieb:
> Macht der FIR Compiler irgendwas anders?

Keine Ahnung, vermutlich baut der das aber anders als du.

Mario A. schrieb:
> Ansonsten akzeptier ich jetzt
> einfach, dass mein Filter die Abtastrate nochmal halbiert...

Hm, also ich kann das in der Simulation nicht sehen.

Mario A. schrieb:
> Ist da irgendwo eine zus. Abtastung drin?

Sehe ich auch nicht.

Mario A. schrieb:
> Ich könnte es auch mal so versuchen,
> wie du vorgeschlagen hast (mit * und + in einem Process). Sollte aber
> eigtl. daran nichts ändern.

Genau. Es sieht dann aber übersichtlicher aus und erleichtert vielleicht 
die Fehlersuche.

Mario A. schrieb:
> Ich meinte mit unterschiedlicher Anzahl an Bits für die Koeffizienten
> und unterschiedlicher Filterordnung. Ansonsten ja. :)

Dafür hast du doch die Generics. Ja, die xbip_multadd hast du dir mit 
festen Werten generieren lassen, aber auch die kannst du "nativ" 
einbinden:
1
   DSP48E1 #(
2
      // Feature Control Attributes: Data Path Selection
3
      .A_INPUT("DIRECT"),               // Selects A input source, "DIRECT" (A port) or "CASCADE" (ACIN port)
4
      .B_INPUT("DIRECT"),               // Selects B input source, "DIRECT" (B port) or "CASCADE" (BCIN port)
5
      .USE_DPORT("FALSE"),              // Select D port usage (TRUE or FALSE)
6
      .USE_MULT("MULTIPLY"),            // Select multiplier usage ("MULTIPLY", "DYNAMIC", or "NONE")
7
      .USE_SIMD("ONE48"),               // SIMD selection ("ONE48", "TWO24", "FOUR12")
8
      // Pattern Detector Attributes: Pattern Detection Configuration
9
      .AUTORESET_PATDET("NO_RESET"),    // "NO_RESET", "RESET_MATCH", "RESET_NOT_MATCH" 
10
      .MASK(48'h3fffffffffff),          // 48-bit mask value for pattern detect (1=ignore)
11
      .PATTERN(48'h000000000000),       // 48-bit pattern match for pattern detect
12
      .SEL_MASK("MASK"),                // "C", "MASK", "ROUNDING_MODE1", "ROUNDING_MODE2" 
13
      .SEL_PATTERN("PATTERN"),          // Select pattern value ("PATTERN" or "C")
14
      .USE_PATTERN_DETECT("NO_PATDET"), // Enable pattern detect ("PATDET" or "NO_PATDET")
15
      // Register Control Attributes: Pipeline Register Configuration
16
      .ACASCREG(1),                     // Number of pipeline stages between A/ACIN and ACOUT (0, 1 or 2)
17
      .ADREG(1),                        // Number of pipeline stages for pre-adder (0 or 1)
18
      .ALUMODEREG(1),                   // Number of pipeline stages for ALUMODE (0 or 1)
19
      .AREG(1),                         // Number of pipeline stages for A (0, 1 or 2)
20
      .BCASCREG(1),                     // Number of pipeline stages between B/BCIN and BCOUT (0, 1 or 2)
21
      .BREG(1),                         // Number of pipeline stages for B (0, 1 or 2)
22
      .CARRYINREG(1),                   // Number of pipeline stages for CARRYIN (0 or 1)
23
      .CARRYINSELREG(1),                // Number of pipeline stages for CARRYINSEL (0 or 1)
24
      .CREG(1),                         // Number of pipeline stages for C (0 or 1)
25
      .DREG(1),                         // Number of pipeline stages for D (0 or 1)
26
      .INMODEREG(1),                    // Number of pipeline stages for INMODE (0 or 1)
27
      .MREG(1),                         // Number of multiplier pipeline stages (0 or 1)
28
      .OPMODEREG(1),                    // Number of pipeline stages for OPMODE (0 or 1)
29
      .PREG(1)                          // Number of pipeline stages for P (0 or 1)
30
   )
31
   DSP48E1_inst (
32
      // Cascade: 30-bit (each) output: Cascade Ports
33
      .ACOUT(ACOUT),                   // 30-bit output: A port cascade output
34
      .BCOUT(BCOUT),                   // 18-bit output: B port cascade output
35
      .CARRYCASCOUT(CARRYCASCOUT),     // 1-bit output: Cascade carry output
36
      .MULTSIGNOUT(MULTSIGNOUT),       // 1-bit output: Multiplier sign cascade output
37
      .PCOUT(PCOUT),                   // 48-bit output: Cascade output
38
      // Control: 1-bit (each) output: Control Inputs/Status Bits
39
      .OVERFLOW(OVERFLOW),             // 1-bit output: Overflow in add/acc output
40
      .PATTERNBDETECT(PATTERNBDETECT), // 1-bit output: Pattern bar detect output
41
      .PATTERNDETECT(PATTERNDETECT),   // 1-bit output: Pattern detect output
42
      .UNDERFLOW(UNDERFLOW),           // 1-bit output: Underflow in add/acc output
43
      // Data: 4-bit (each) output: Data Ports
44
      .CARRYOUT(CARRYOUT),             // 4-bit output: Carry output
45
      .P(P),                           // 48-bit output: Primary data output
46
      // Cascade: 30-bit (each) input: Cascade Ports
47
      .ACIN(ACIN),                     // 30-bit input: A cascade data input
48
      .BCIN(BCIN),                     // 18-bit input: B cascade input
49
      .CARRYCASCIN(CARRYCASCIN),       // 1-bit input: Cascade carry input
50
      .MULTSIGNIN(MULTSIGNIN),         // 1-bit input: Multiplier sign input
51
      .PCIN(PCIN),                     // 48-bit input: P cascade input
52
      // Control: 4-bit (each) input: Control Inputs/Status Bits
53
      .ALUMODE(ALUMODE),               // 4-bit input: ALU control input
54
      .CARRYINSEL(CARRYINSEL),         // 3-bit input: Carry select input
55
      .CLK(CLK),                       // 1-bit input: Clock input
56
      .INMODE(INMODE),                 // 5-bit input: INMODE control input
57
      .OPMODE(OPMODE),                 // 7-bit input: Operation mode input
58
      // Data: 30-bit (each) input: Data Ports
59
      .A(A),                           // 30-bit input: A data input
60
      .B(B),                           // 18-bit input: B data input
61
      .C(C),                           // 48-bit input: C data input
62
      .CARRYIN(CARRYIN),               // 1-bit input: Carry input signal
63
      .D(D),                           // 25-bit input: D data input
64
      // Reset/Clock Enable: 1-bit (each) input: Reset/Clock Enable Inputs
65
      .CEA1(CEA1),                     // 1-bit input: Clock enable input for 1st stage AREG
66
      .CEA2(CEA2),                     // 1-bit input: Clock enable input for 2nd stage AREG
67
      .CEAD(CEAD),                     // 1-bit input: Clock enable input for ADREG
68
      .CEALUMODE(CEALUMODE),           // 1-bit input: Clock enable input for ALUMODE
69
      .CEB1(CEB1),                     // 1-bit input: Clock enable input for 1st stage BREG
70
      .CEB2(CEB2),                     // 1-bit input: Clock enable input for 2nd stage BREG
71
      .CEC(CEC),                       // 1-bit input: Clock enable input for CREG
72
      .CECARRYIN(CECARRYIN),           // 1-bit input: Clock enable input for CARRYINREG
73
      .CECTRL(CECTRL),                 // 1-bit input: Clock enable input for OPMODEREG and CARRYINSELREG
74
      .CED(CED),                       // 1-bit input: Clock enable input for DREG
75
      .CEINMODE(CEINMODE),             // 1-bit input: Clock enable input for INMODEREG
76
      .CEM(CEM),                       // 1-bit input: Clock enable input for MREG
77
      .CEP(CEP),                       // 1-bit input: Clock enable input for PREG
78
      .RSTA(RSTA),                     // 1-bit input: Reset input for AREG
79
      .RSTALLCARRYIN(RSTALLCARRYIN),   // 1-bit input: Reset input for CARRYINREG
80
      .RSTALUMODE(RSTALUMODE),         // 1-bit input: Reset input for ALUMODEREG
81
      .RSTB(RSTB),                     // 1-bit input: Reset input for BREG
82
      .RSTC(RSTC),                     // 1-bit input: Reset input for CREG
83
      .RSTCTRL(RSTCTRL),               // 1-bit input: Reset input for OPMODEREG and CARRYINSELREG
84
      .RSTD(RSTD),                     // 1-bit input: Reset input for DREG and ADREG
85
      .RSTINMODE(RSTINMODE),           // 1-bit input: Reset input for INMODEREG
86
      .RSTM(RSTM),                     // 1-bit input: Reset input for MREG
87
      .RSTP(RSTP)                      // 1-bit input: Reset input for PREG
88
   );

Ist aber eher unübersichtlich. Ich würde da lieber * und + schreiben.

von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Wahrscheinlich hat der eine Verzögerung. Vllt einen FIFO einbauen?

Ne, FIFO brauchst du da nicht. Aber du kannst ja nach dem start der 
Filterei ein paar Takte warten bis du das Ergebnis abholst.

Mario A. schrieb:
> Hättest du die Berechnung mit + und * in einem Process gemacht, oder
> hättest du auch Blöcke mit Generate erzeugt?

+ und *

Mario A. schrieb:
> Ich könnte auch mal versuchen die DSP Slices direkt anzusprechen über
> die Makros im IP Catalog.

Siehe letzter Post:
Kann man machen wenn man irgendwas optimieren will und glaubt besser als 
der Synthesizer zu sein, würde ich aber an der Stelle nicht machen.

von Mario A. (itse_me_mario)


Lesenswert?

Kurz zu den Screenshots:
Wenn 16kHz dahinter steht, sind wurden die Koeffizienten für eine 
Abtastrate von 16 kHz verwendet.

Wenn Xilinx dabei steht wurde der FIR Compiler verwendet.


>>Dafür hast du doch die Generics.
Ja man könnte es generischer schreiben, das stimmt schon. Bei untersch. 
Anzahl an Koeffizienten, Länge, etc, muss ich auch die Signale MAX und 
die Adressen für die Block Memory Generatoren anpassen. Kann man alles 
machen.

>>Aber du kannst ja nach dem start der Filterei ein paar Takte warten bis du das 
Ergebnis abholst.
Das könnte ich mal versuchen.
Edit: Ich hab es mal mit
signal state_reg: std_logic_vector(9 downto 0):="0000000000";
alias s_write_data : std_logic is state_reg(9);
und
...
state_reg <= state_reg(8 downto 0) & new_sample;
versucht. Das liefert auch das selbe (falsche Ergebnis). Vllt eine noch 
größere Verzögerung verwenden?

: Bearbeitet durch User
von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Ich hab es mal mit
> signal state_reg: std_logic_vector(9 downto 0):="0000000000";
> alias s_write_data : std_logic is state_reg(9);
> und
> ...
> state_reg <= state_reg(8 downto 0) & new_sample;
> versucht. Das liefert auch das selbe (falsche Ergebnis). Vllt eine noch
> größere Verzögerung verwenden?

Genau der Versuch ist gut, bringt aber hier nichts, weil die 
Multiplikation nur einen Takt braucht. Also eine zusätzliche Verzögerung 
ist nicht nötig.

Ich würde das mal alles mit * und + schreiben und normal signed als 
Datentyp verwenden. Wenn das funktioniert kannst du das immer noch Stück 
für Stück zurückbauen.

von Mario A. (itse_me_mario)


Lesenswert?

Das serielle Filter mit * und + scheint ja zu funktionieren (siehe 
FIR_Seriell.zip).

von -gb- (Gast)


Lesenswert?

Dann passt doch alles. Du kannst das auch parallel oder als Pipeline 
schreiben.

von Mario A. (itse_me_mario)


Lesenswert?

>>Du kannst das auch parallel oder als Pipeline schreiben.
Ein FIR mit Pipeline hast du eh schon mal hochgeladen wenn ich mich 
nicht täusche.

Parallel mit der Generate-Anweisung meinst du? Würdest du dafür die 
Berechnung der der einzelnen Taps mit * und + auslagen und dann wieder 
Arrays verwenden wie mit den Multiply Addern? Mit einem zus. Enable?

Pseudocode für die ausgelagerte Komponente:

component MAC_Component
clk : in
enable : in
INPUT : in
COE : in
Carry : in
Output : out

Logik:
if rising_edge(clk) then
   if enable = '1' then
       Output = INPUT * COE + Carry
   end if;

end if;


Danke Gustl!

von -gb- (Gast)


Lesenswert?

Mario A. schrieb:
> Parallel mit der Generate-Anweisung meinst du?

Dieses generate ist nur Schreibersparnis. Ja kann man machen, würde ich 
auch machen.

Mario A. schrieb:
> Würdest du dafür die Berechnung der der einzelnen Taps mit * und +
> auslagen

Wozu denn diesen kurzen Code

Mario A. schrieb:
> if rising_edge(clk) then
>    if enable = '1' then
>        Output = INPUT * COE + Carry
>    end if;

In eine extra Komponente auslagern? Das macht es doch nur 
unübersichtlich. Nein, ich würde da nix auslagern.

von Mario A. (itse_me_mario)


Lesenswert?

Alles klar, danke. Ich versuche das am Montag! :) Hab leider erst dann 
wieder Zugriff auf das FPGA. Bin gespannt, ob der Frequenzgang dann 
stimmt.

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

Ich habe jetzt mal meinen alten PC aufgesetzt und ein paar Simulationen 
gemacht.

Ich habe es jetzt doch erst einmal mit dem Auslagern in eine Komponente 
versucht (siehe MAC_Instruction.vhd). Als s_filtered_data erhalte ich 
damit allerdings nur XXXX...XXX. Vielleicht liegt es auch an den 
unterschiedlich großen Datentypen (24 Bit * 16 Bit + 48 Bit). Außerdem 
habe ich für die Komponente Signed-Vektoren verwendet und die 
Koeffizienten entsprechend deklariert. Umwandlungen von std_logic_vector 
nach signed habe ich direkt bei der Initialisierung innerhalb der 
Generate-Anweisung mit signed(...) durchgeführt.
Sollte ich vielleicht vor der Berechnung für alle Signale (A, B, C) eine 
Vorzeichenerweiterung machen und diese gleich groß machen (48 Bit)?

Ich habe auch mal s_write_data verzögert auf '1' gesetzt. Hat leider 
auch nicht geholfen.

ABER:
Ohne Auslagern erhalte ich auch ohne zusätzliche Verzögerung Werte (der 
Code ist in FIRFilter.vhd enthalten). Ob die Werte stimmen muss ich erst 
noch prüfen. Hast du das in etwa so gemeint Gustl? Also diese Variante 
der Generate-Anweisung? :) Ich ändere jetzt mal die Koeffizienten und 
die Eingangsdaten zu 1.

von Gustl B. (-gb-)


Angehängte Dateien:

Lesenswert?

Mario A. schrieb:
> Hast du das in etwa so gemeint Gustl?

Grob so habe ich das gemeint. Wobei ich das mit Loop machen würde. Ich 
habe dir jetzt mal ein Minimalbeispiel geschrieben mit Testbench.

von Mario A. (itse_me_mario)


Lesenswert?

Also die Versionen aus dem vorherigen Post funktionieren laut Analog 
Discovery 2 beide und liefern den richtigen Frequenzgang. :)

>>Ich habe dir jetzt mal ein Minimalbeispiel geschrieben mit Testbench.
Das habe ich auch gleich noch implementiert und es funktioniert 
ebenfalls. :) Danke!

Also alles ohne Multiply Adder Block funktioniert nun. ;)

von Gustl B. (-gb-)


Lesenswert?

Wunderbar, dann auf zu größeren Aufgaben^^

von Mario A. (itse_me_mario)


Lesenswert?

Genau. :D Danke dir! Erst mal noch ein paar Programme umschreiben und 
dann checken, wie ich die Daten vom UART mit C einlesen kann. :)

: Bearbeitet durch User
von Mario A. (itse_me_mario)



Lesenswert?

Ich habe gerade noch die Programme umgeschrieben und die UART-Anbindung 
für die eigens erstellten Filter ergänzt.
Die Aufnahmen mit dem Xilinx-FIR und DEINEN Koeffizienten liefern ein 
schönes Ergebnis, welches auch mit dem Frequenzgang des AD2 
übereinstimmt.

Mit meinen Koeffizienten sehen die aufgenommenen Daten auch mit dem FIR 
Compiler nicht ganz so gut aus. Ich weiß nicht, ob diese Erhöhung da 
sein dürfte? Überschwingen an den Kanten ja, aber das scheint schon sehr 
groß zu sein. Zumindest sieht man das nicht im Frequenzgang des Analog 
Discoverys. Der Frequenzgang mit dem Analog Discovery sieht an sich gut 
aus.

Ich habe die Daten mit den "alten" Python-Skripten aufgenommen und 
decodiert. Im Anschluss an die Anbindung des UART-Moduls an mein Filter 
wollte ich mit der Aufnahme der Daten in "C" beginnnen.

: Bearbeitet durch User
von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Mit meinen Koeffizienten sehen die aufgenommenen Daten auch mit dem FIR
> Compiler nicht ganz so gut aus.

Welches der Bildchen ist das?
Filterdaten_XilinxFIR.PNG

Mario A. schrieb:
> Ich weiß nicht, ob diese Erhöhung da
> sein dürfte? Überschwingen an den Kanten ja, aber das scheint schon sehr
> groß zu sein.

Was bedeutet denn groß? Ist ist noch deutlich kleiner 1 dB. Das kann man 
normalerweise im Filterdesigner einstellen wie flach das Passband sein 
soll.

von Duke Scarring (Gast)


Angehängte Dateien:

Lesenswert?

Mario A. schrieb:
> Ich weiß nicht, ob diese Erhöhung da
> sein dürfte?
Ca. 15% Überschwingen. Es gibt bestimmt irgendwo eine Übersicht wieviele 
Taps man benötigt, um einen bestimmtes Ripple/Überschwingen nicht zu 
überschreiten.

> Zumindest sieht man das nicht im Frequenzgang des Analog Discoverys.
Vielleicht liegt es am Antialiasing-Filter?
Wie ist denn die maximale Samplerate bzw. Bandbreite vom Analog 
Discovery?
Und welche Samplerate verwendest Du? Und bei welcher Frequenz liegen 
Pass- und Stop-Band von Deinem Filter?

Duke

von -gb- (Gast)


Lesenswert?

Die Daten zu diesem Bildchen hat er digital im FPGA hinter dem Filter 
abgegriffen. Am Eingang lag also ein Sinus Sweep an. Die Amplitude des 
Sweeps war konstant wie man am zweiten Plot im Bild sehen kann.

Diese Überhöhung kommt also wirklich durch das Filter. Da wären jetzt 
die Koeffizienten interessant.

von Gustl B. (-gb-)



Lesenswert?

So, jetzt habe ich mal die Koeffizienten aus deinen .coe Dateien geholt, 
in pyfda geladen und Bildchen gemacht. Wie man sieht geht das schon 
deutlich nach oben in Richtung Grenzfrequenz.

von Mario A. (itse_me_mario)



Lesenswert?

>>Es gibt bestimmt irgendwo eine Übersicht
Werd ich mal recherchieren. :)

Ich verwende eine Abtastrate von 32 kHz (siehe 
MATLAB_filterDesigner.png).
Apass: 1 dB
Astop: 80 dB

Also die Aufnahme FrequenzgangLinkerKanal.png und 
FilterdatenXilinxFIR.png sollten an sich die beiden Werte haben.
Der Matlab filterDesigner zeigt auch eine KLEINE Erhöhung.

Am AD2 sollte es nicht scheitern 
(https://reference.digilentinc.com/_media/reference/instrumentation/analog-discovery-2/ad2_rm.pdf).

Network analyzer – Bode, Nyquist, Nichols transfer
diagrams of a circuit. Range: 1Hz to 10MHz

- Waveform generator drives circuits with swept sine waves up to 10 MHz
- Input waveforms settable from 1 Hz to 10 MHz, with 5 to 1000 steps
- Settable input amplitude and offset
- Analog input records response at each frequency
- Response magnitude and phase delay displayed in Bode, Nichols, or 
Nyquist formats


Oh gerade gesehen, dass du was geschrieben hast Gustl. War während 
meines Posts unterwegs. :D Danke!

Hm laut deinem Bildchen müsste es ja dann mehrmals rauf und runter 
gehen. Nach Filterdaten_XilinxFIR.png tut es das aber nicht.

: Bearbeitet durch User
von -gb- (Gast)


Lesenswert?

In einem deiner Filter und im Matlab Screenshot geht es auch nur einmal 
hoch.

von Mario A. (itse_me_mario)



Lesenswert?

Stimmt jetzt sehe ich es erst. Das sind ja 2 verschiedene im Bild von 
Gustl! :) Dachte erst das eine war nur gezoomt. Danke!

Hab jetzt den rechten Kanal nochmal betrachtet. Es scheint schon zu 
passen. Ich habe jetzt auch einen viel kleineren Bereich mit dem AD2 
betrachtet und man kann nun auch dort die Schwingungen gut erkennen.

Den linken Kanal hab ich auch nochmal gezoomt aufgezeichnet. Scheint 
alles zu passen! Dankeschön.

: Bearbeitet durch User
von Gustl B. (-gb-)


Lesenswert?

Ja, sieht gut aus, aber die Amplitude könntest du dir nochmal angucken.

24 Bit signed geht von -2**23 bis +2**23-1, also von -8388608 bis 
+8388607. Bei dir sieht das Signal vor und nach dem Filter um ca. den 
Faktor 8 zu niedrig. Aber es könnte auch sein, dass du von aussen analog 
nur einen sehr kleinen Pegel hineinfütterst.

Du darfst an den Eingang deines ADCs 0.568*3.3V = 1.8744 V_pp anlegen. 
Im Schaltplan 
https://reference.digilentinc.com/_media/reference/pmod/pmodi2s2/pmodi2s2_sch.pdf 
ist ein Spannungsteiler vor dem AC-gekoppelten Eingang. Du kannst also 
tatsächlich ca. 3.7 V_pp anlegen um den ADC voll auszusteuern.

Edit:
In deinen Screenshots steht was von 100 mV Amplitude, da kannst du 
deutlich nach oben gehen wenn du den ADC voll aussteuern möchtest.

: Bearbeitet durch User
von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

>>In deinen Screenshots steht was von 100 mV Amplitude
Genau, ansonsten sieht es so aus wie in Amplitude_1V.png. Dasselbe 
Problem habe ich auch, wenn ich ein Filter auf dem DSP implementiere. 
Die analogen Eingänge des AD-Wandlers werden sonst übersteuert.

von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Die analogen Eingänge des AD-Wandlers werden sonst übersteuert.

Eben das sollte ja bis ca. 3.7 V_pp nicht passieren laut ADC Datenblatt 
und Schaltung.

Wie sieht denn das Signal über UART aus wenn du da einen 1 V oder 2 V 
Sinus reinfütterst?

Edit:
Was auch auffällt ist, dass diese Software WaveForms ziemlich mies ist. 
Deine damit aufgenommenen Spektren sehen alles andere als gut aus, die 
Skalierung der y-Achse ist für mich unklar und rechts im Bildchen bei 
den höheren Frequenzen sieht das Spektrum immer ziemlich kaputt aus.
Deine Hardware die du mit WaveForms verwendest, also das Analog 
Discovery 2 sollte eigentlich gut genug aus, trotzdem sieht das seltsam 
aus, fast so, als würde die Abtastrate nicht reichen um das Signal 
korrekt zu erfassen.

Hast dieses WaveForms auch einen Oszi Modus? Dann könntest du den 
Eingang sweepen und den Ausgang mit dem Oszi angucken. Oder vielleicht 
geht das auch in dem Bode Modus. Der Spektrumanalyser ist dafür eher 
schlecht zu gebrauchen wenn du den Eingang sweepst und der aber das mit 
einer FFT berechnet.
Wenn du den Spektrumanalyzer verwenden willst, dann gebe doch mal ein 
Rauschen auf den Eingang. So ein 2 V-pp weißes Rauschen. Da müsste man 
dann mit jedem Spektrumanalyzer das Filter schön erkennen können.

: Bearbeitet durch User
von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

Ich habe mal den linken Kanal (1000/2000 Filter) für eine Amplitude von 
1V aufgenommen. Diesen deutlichen Überschwinger sieht man dann nicht 
mehr.


>>Hast dieses WaveForms auch einen Oszi Modus?
Hat es an sich. Ich habe einen BNC-Adapter drauf. Um damit auf die 
Audioanschlüsse des PMOD I2S Adapters zu kommen, habe ich noch ein 
zusätzliches Bauteil zwischen BNC-Adapter und FPGA.
https://reference.digilentinc.com/reference/instrumentation/bnc-adapter-board/reference-manual
https://reference.digilentinc.com/_media/reference/instrumentation/bnc-adapter-board/discovery_bnc_sch.pdf

von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Diesen deutlichen Überschwinger sieht man dann nicht
> mehr.

Es könnte sein, dass das clippt, das würdest du sehen wenn du mal 
reinzoomst. Sonst sieht das auch nicht so toll aus. Im Stoppband hast du 
schon deutliche Störungen drinnen. Die sollten da nicht sein wenn du 
beim Sweep unter f_sample/2 bleibst.

Aber du kannst ja mal das mit dem Rauschen probieren und du könntest 
deinen Frequenzbereich auch mit ein paar festen Frequenzen durchmessen. 
Also von 0 Hz bis 10 kHz in 1 kHz Schritten oder feiner. Wenn du da eine 
feste Frequenz mit z. B. 5 kHz anlegst solltest du nicht die Störungen 
sehen die du im Sweep siehst. Wenn die trotzdem da sind dann würde ich 
sagen passt was nicht.

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

Irgendwie klappt das gerade nicht so, wie ich mir das vorstelle. Also, 
dass ich einen Sinus durchschicke und den Ausgang mit dem Scope messe.
Ich hätte dazu mal die Eingangsdaten ungefiltert wieder ausgegeben 
(s_unfiltered_data anstatt s_filtered_data an i2s_data_interface 
angeschlossen).
Ob ich Wavegen aktiviere oder nicht macht keinen Unterschied. Ich muss 
mich da noch einlesen. Ich glaube ich habs falsch angeschlossen.

: Bearbeitet durch User
von Mario A. (itse_me_mario)



Lesenswert?

Jetzt siehts besser aus. Eine Amplitude von 1V hat einen schöneren 
Verlauf zur Folge als eine Amplitude von 0,1V (weniger Rauschen).

Ich habe auch mal einen 1 kHz und einen 1 Hz Puls durchgeschickt und 
eine Sinusweep von 1Hz bis 16kHz.

Auf den Bilden sind Signale ohne und mit Filter zu sehen.

Auf einmal funktionierts. Vllt hat es sich vorhin irgendwie aufgehängt.

: Bearbeitet durch User
von Gustl B. (gustl_b)


Lesenswert?

Hm. Ich wäre damit noch lange nicht zufrieden. Das ist ein Audio ADC und 
kein schlechter 8Bit ADC in einem billigen uC.
Aber ich weiß auch nicht wie du den Signalgenerator mit dem Audio PMOD 
verbunden hast und so. Die Massen hast du verbunden? Auch keine langen 
ungeschirmten Kabel die die Kabel zu deinem UART Modul kreuzen?

Ich hätte da jetzt einen zumindest für den menschlichen Betrachter 
glatten Sinus erwartet.

Wir wissen aber auch nicht wie gut dieser Sinusgenerator ist. Du 
könntest also mal den Ausgang des Generators direkt mit dem Eingang des 
Oszis verbinden und gucken wie das Signal da aussieht.

von Mario A. (itse_me_mario)



Lesenswert?

Hallo Gustl,
also vom PMOD gehts über 2 Klinkenstecker auf BNC-Buchsen (ca. 25cm 
lang) und die gehen mittels Koax-Kabel auf den BNC-Adapter.

Die Masse des UART-Moduls ist mit dem PMOD-Massepin verbunden. Beim 
PMOD-Adapter selbst sind sowieso auch die Massen angeschlossen. Die 
Leitungen zum UART-Modul sind nur einfache Arduinoleitungen, ca. 20cm 
lang. Das ist sicher nicht ideal.

Programm läuft und die Eingangsdaten werden ungefiltert wieder 
ausgegeben. Anschlussmethode V2.
Anlegen eines 1kHz Sinus´ an Channel 2: 
Anschluss_V2_1kHz_Sinus_anChannel2
Anlegen eines 1kHz Sinus´ an Channel 1: 
Anschluss_V2_1kHz_Sinus_anChannel1
Manchmal sieht der Sinus auch ideal aus... Z.B. nach dem Laden des 
Programms. Außerdem zeigt es auch ohne Programm einen Sinus an. Leider 
habe ich kein "richtiges" Oszilloskop zur Hand.

Programm läuft und die Eingangsdaten werden ungefiltert wieder 
ausgegeben. Anschlussmethode V1.
Anlegen eines 1kHz Sinus´ an Channel 2: 
Anschluss_V1_1kHz_Sinus_anChannel2
Anlegen eines 1kHz Sinus´ an Channel 1: 
Anschluss_V1_1kHz_Sinus_anChannel1

Wenn ich CH1 und CH3 direkt verbinde und einen 1kHz Sinus anlege zeigt 
es irgendwie gerade gar nichts an (Anschluss V3). Irgendwie spinnt das 
AD2 gerade. Mal funktionierts, mal wieder nicht...




Ich würde mich jetzt dann mal den periodischen Fehlern bei der 
UART-Aufnahme widmen.
Kann ich beim Lesen mit

...
while len(sr) < 14*32000*Record_Time_in_Seconds:
  data = ser.read(1000000)
  if len(data) > 0:
    sr.extend(data)
ser.close()
...

bei data = ser.read(1000000) nicht auch eine kleinere Zahl verwenden? 
Ich probier das mal.

Mit data = ser.read(1000) scheint es zu passen (siehe Errorcount.png). 
4480933 Bytes/14 Bytes = 320066,6429 Pakete

: Bearbeitet durch User
von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> also vom PMOD gehts über 2 Klinkenstecker auf BNC-Buchsen (ca. 25cm
> lang) und die gehen mittels Koax-Kabel auf den BNC-Adapter.

Dann zeichne das mal mit der Masseverbindung. Also dein AnalogDiscovery 
sollte ebenfalls eine Masseverbindung zum FPGA Board haben. Idealerweise 
eine Masseverbindung zu jedem Massekontakt in der Klinkenbuchse.

Mario A. schrieb:
> Die Masse des UART-Moduls ist mit dem PMOD-Massepin verbunden. Beim
> PMOD-Adapter selbst sind sowieso auch die Massen angeschlossen. Die
> Leitungen zum UART-Modul sind nur einfache Arduinoleitungen, ca. 20cm
> lang. Das ist sicher nicht ideal.

Das ist digitales Zeug, und das sieht ja bei dir gut aus, das Analoge 
sieht komisch aus.

Mario A. schrieb:
> Manchmal sieht der Sinus auch ideal aus... Z.B. nach dem Laden des
> Programms. Außerdem zeigt es auch ohne Programm einen Sinus an.

Das Bildchen das hier den Sinus zeigt sieht nicht gut aus. Nur die 
zweite Periode ist OK.

Zu der Schaltung aus AnschlussGrundlage_V3.png würde mich noch ein Bild 
vom Signal interessieren. Also mit dem AnalogDiscovery einen Sinus 
erzeugen und gleich wieder mit dem AnalogDiscovery angucken. Der sollte 
dann gut aussehen.

Mario A. schrieb:
> Irgendwie spinnt das
> AD2 gerade. Mal funktionierts, mal wieder nicht...

Das ist nicht gut.

Mario A. schrieb:
> kleinere Zahl verwenden?
> Ich probier das mal.

Klar geht das. Du solltest für maximalen Durchsatz und keinen 
Datenverlust aber den gesamten Empfangspuffer auslesen. Wie groß der ist 
weiß ich nicht, daher habe ich da bisher immer eine große Zahl 
verwendet.

Mario A. schrieb:
> Mit data = ser.read(1000) scheint es zu passen (siehe Errorcount.png).
> 4480933 Bytes/14 Bytes = 320066,6429 Pakete

Genau das sieht fehlerfrei aus. Sollte aber bei größeren Zahlen wie 
ser.read(100000) gleich bleiben. Es könnte nur sein, dass wenn die Zahl 
zu klein wird Fehler kommen. Sprich: Größere Zahlen haben keinen 
Nachteil, aber zu kleine schon. Da ich die Grenze nicht kenne habe ich 
große Zahlen verwendet.

von Mario A. (itse_me_mario)


Lesenswert?

>>Also dein AnalogDiscovery sollte ebenfalls eine Masseverbindung zum FPGA Board 
haben. Idealerweise eine Masseverbindung zu jedem Massekontakt in der 
Klinkenbuchse.
Danke für die Antwort! Also das AD2 ist theoretisch über den PMOD 
I2S-Adapter mit GND verbunden. Die Klinkenstecker sollten eine 
Masseverbindung haben (https://de.wikipedia.org/wiki/Klinkenstecker). 
Ich kann aber noch mit einer extra Leitung vom AD2 auf einen PMOD GND 
Pin des FPGAs gehen.


>>Zu der Schaltung aus AnschlussGrundlage_V3.png würde mich noch ein Bild
vom Signal interessieren.
Das hat vorhin irgendwie nicht funktioniert. Ich probier das morgen 
Vormittag nochmal und lad es dann hoch. Dann schließe ich auch den GND 
Pin vom AD2 noch zus. an den PMOD an. :)

von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Also das AD2 ist theoretisch über den PMOD
> I2S-Adapter mit GND verbunden. Die Klinkenstecker sollten eine
> Masseverbindung haben (https://de.wikipedia.org/wiki/Klinkenstecker).

Ja, das ist richtig, aber wie genau hast du den Klinkenstecker mit dem 
AnalogDiscovery verbunden?

Mario A. schrieb:
> Ich probier das morgen
> Vormittag nochmal und lad es dann hoch. Dann schließe ich auch den GND
> Pin vom AD2 noch zus. an den PMOD an. :)

Sehr gut!

Jedenfalls würde ich tatsächlich erstmal diese analogen Probleme lösen, 
das sieht nämlich nicht gut aus. Daten über UART bekommst du jetzt 
fehlerfrei, das ist super.

Interessant wäre auch mal wenn du einen Sinus erzeugst, z. B. 1 kHz und 
dann anguckst wie der ungefiltert im FPGA aussieht. Also über UART die 
Daten ausliest und nicht die ganze Zeit plottest, sondern nur einen 
kleinen Aussschnitt, damit man den Signalverlauf gut sehen kann. Du 
kannst auch die Daten vom UART hier hochladen.
Dann könnte man nämlich Schlüsse ziehen ob das Einlesen mit dem ADC oder 
das Ausgeben über den DAC die Fehler verursacht.

von Mario A. (itse_me_mario)


Lesenswert?

Von Klinke gehts auf BNC. Die Massen sind miteinander verbunden.

Hab jetzt gerade nochmal CH1 und CH3 verbunden. Das Scope zeigt leider 
nichts an, wenn ich einen 1kHz Sinus anlege... Das hat es aber schon mal 
gemacht. Aber eigtl. hab ich nur ein paar Koaxkabel hin- und 
hergesteckt. Da dürfte nichts kaputt gehen. Hab leider gerade nicht mal 
ein Multimeter da...

>>Interessant wäre auch mal wenn du einen Sinus erzeugst, z. B. 1 kHz und
dann anguckst wie der ungefiltert im FPGA aussieht.
Sobald das mit dem Scope funktioniert mach ich das mal.

von Mario A. (itse_me_mario)


Lesenswert?

Ich implementiere jetzt mal ein adaptives Filter auf einem 
Mikrocontroller. Zunächst mal ohne Echtzeitanbindung. Bei Interesse kann 
ich das auch gerne hier posten.

Dann will ich den Algorithmus (LMS vllt auch NLMS) auf dem FPGA 
implementieren.

Für das FPGA will ich die Daten zunächst aus einem txt-File mit einer 
Testbench einlesen und die Daten nach dem Filter wieder in ein txt-File 
schreiben.

von Mario A. (itse_me_mario)


Lesenswert?

Ich hab jetzt mal einen LMS und NLMS Algorithmus in C implementiert 
(erst mal offline). Dafür habe ich aus Matlab die notwendigen Signale 
für das adaptive Filter - welche ich als .wav-Dateien zur Verfügung 
hatte - in ein Textfile geschrieben. Jede Zeile in meinen Textfiles 
enthält nun einen Double-Wert. Die Signale wurden allerdings mit 16 kHz 
aufgenommen. Diese muss ich also noch Upsamplen, da mein PMOD I2S 
Adapter erst ab 32 kHz arbeitet.

Idee: Spracherkenner (z. B. Auto)
Musik wird abgespielt und der Fahrer möchte währenddessen sein 
Fahrerassistenzsystem per Sprache bedienen. Also der Lautsprecher gibt 
das Musiksignal aus und ein Mikrofon nimmt quasi das Signalgemisch aus 
Musik (verändert über die Übertragungsfunktion des Raumes) und Sprache 
auf. Ziel: Musik aus Signalgemisch filtern.

Die verwendeten Signale waren 1x das Referenzsignal (nur Musik) und 1x 
das Signalgemisch bestehend aus Musik und Sprache unter Einbeziehung 
einer zus. Übertragunsfunktion (des Raums). Ziel des adaptiven Filters 
ist es nun das (veränderte) Musiksignal aus dem Signalgemisch weitgehend 
zu minimieren, sodass am Ende die Sprache erkannt werden kann. Annahme: 
Musik und Sprache sind unkorreliert und die Musiksignale vor und nach 
der Veränderung sind korreliert.

Die Daten aus den Textdateien habe ich dann mit fscanf für jede 
Iteration in eine Variable eingelesen (da es je nach Signal teilweise 
ziemlich viele Werte sind). Für kleinere/kürzere Signale könnte man 
natürlich ein Array anlegen.
Die berechneten Daten habe ich dann mit fprintf wieder in ein Textfile 
geschrieben und ich kann diese nun mithilfe von Matlab auswerten (z. B. 
ERLE-Kurven).

Eine Festkommarealisierung des LMS und NLMS in C muss ich auch noch 
machen.


Ich seh´ mir jetzt zwischenzeitlich mal an, wie ich die Daten in einer 
Testbench einlesen kann.
Meine Eingangsdaten, die ich für das den C-Algorithmus verwendet habe 
waren vom Typ Double. Für die Verarbeitung im FPGA habe ich diese nun 
mit dem Faktor 2^15 multipliziert, damit ich die Daten im Q15-Format 
habe. Anschließend will ich diese nun in der Testbench Abtastwert für 
Abtastwert einlesen. Am liebsten wärs mir, wenn ich diese direkt als 
Ganzzahl (mit Vorzeichen) einlesen kann. Aber ich könnte die Ganzzahlen 
auch zuvor in eine Binärzahl umwandeln und dann einlesen.

@Gustl
Weißt du zufällig, wo das ganze Zeug um: 
file_open(disp_in,"disp_in.dta",READ_MODE); gut dokumentiert ist? Ich 
hab meine alten Unterlagen gerade nicht vor Ort. Vielen Dank im Voraus!

: Bearbeitet durch User
von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Weißt du zufällig, wo das ganze Zeug um:
> file_open(disp_in,"disp_in.dta",READ_MODE); gut dokumentiert ist?

Du kannst die Datei auch implizit öffnen lassen:

file DATEINAME: DATEITYP open read_mode is "DATEINAME";

Und dann musst du dir aussuchen wie deine Daten in der Datei stehen.
VHDL kann von Hause aus Dateien lesen und schreiben. Hier ein 
Minimalbeispiel, das kopiert die Datei IN.bin nach OUT.bin und gibt den 
Wert der einzelnen Bytes während der Simulation aus.
1
entity sim_io is
2
end sim_io;
3
4
architecture Behavioral of sim_io is
5
6
type byte_file is file of character;
7
file bytefile_in: byte_file open read_mode is "IN.bin";
8
file bytefile_out: byte_file open write_mode is "OUT.bin";
9
10
begin
11
12
process
13
  variable byte: character;
14
begin
15
  while not endfile(bytefile_in) loop
16
    read(bytefile_in, byte);
17
    write(bytefile_out, byte);
18
    report "ByteValue: " & integer'image(character'pos(byte));
19
    wait for 10 ns;
20
  end loop;
21
  report "Done";
22
  wait;
23
end process;
24
25
end Behavioral;

(Nein, am Anfang muss hier wirklich keine Bibliothek stehen. Was aber 
seltsam ist:
integer'image
funktioniert hier ohne die numeric_std.)

Wenn du da lesbaren Text in der Datei stehen haben möchtest, dann guck 
dir mal die Beiden

use std.textio.all;
use IEEE.std_logic_textio.all;

an.

: Bearbeitet durch User
von Gustl B. (-gb-)


Lesenswert?

Und weil mich das interessiert hat hier noch eine Beschreibung die Text 
(Integers) kopiert und schön in Spalten formatiert.
Das kommt klar mit:
- Unterschiedlich vielen Zahlen je Zeile,
- Leerzeilen.

Es macht als Beispiel aus

IN.txt
1
0 1
2
-23 456
3
4
7890 123 -4567890

die Datei

OUT.txt
1
         0         1
2
       -23       456
3
4
      7890       123  -4567890

mit je 10 Zeichen und rechtsbündig je Zahl.
1
use std.textio.all;
2
3
entity sim_io_txt is
4
end sim_io_txt;
5
6
architecture Behavioral of sim_io_txt is
7
8
file textfile_in: text open read_mode is "IN.txt";
9
file textfile_out: text open write_mode is "OUT.txt";
10
11
begin
12
13
process
14
  variable line_in: line;
15
  variable line_out: line;
16
  variable number_in: integer;
17
  variable number_in_valid: boolean;
18
  variable number_out: integer;
19
begin
20
  while not endfile(textfile_in) loop
21
    number_in_valid := TRUE;
22
    readline(textfile_in, line_in);
23
    while number_in_valid loop
24
      read(line_in, number_in, number_in_valid);
25
      exit when not number_in_valid;
26
      report "Wert: " & integer'image(number_in);
27
      number_out := number_in;
28
      write(line_out, number_out, right, 10);
29
    end loop;
30
    writeline(textfile_out ,line_out);
31
    wait for 10 ns;
32
    report "Next Line";
33
  end loop;
34
  report "Done";
35
  wait;
36
end process;
37
38
end Behavioral;

Das braucht wirklich nur diese eine Bibliothek.
Viel Spaß!

: Bearbeitet durch User
von Mario A. (itse_me_mario)


Lesenswert?

Hallo Gustl, vielen Dank für die sehr sehr schnelle Rückmeldung und das 
tolle Beispiel! Das probiere ich morgen nach der Arbeit gleich mal aus. 
:)

Meine Textdateien enthalten eh Integerwerte. Die kann ich dann direkt 
einlesen und dann muss ich noch schauen, ob ich diese wieder in 
std_logic_vector´en umwandle für die Berechnung. :) Klingt alles sehr 
vielversprechend! Danke dir! :)

von Mario A. (itse_me_mario)


Lesenswert?

Hallo Gustl, ich hab dein Beispiel eingebunden: Funktioniert 
einwandfrei, danke! :) Morgen geht´s weiter. :) Dann schau ich mir an, 
wie ich die Daten vom txt-File in datain schreibe und die gefilterten 
Daten wieder in ein txt-File schreibe.

: Bearbeitet durch User
von Mario A. (itse_me_mario)


Lesenswert?

Ich hab jetzt eine Kleinigkeit geändert, sodass ich std_logic_vectors 
als Input und Output habe. Die Methode von dir funktioniert super Gustl!

Für die Umwandlung der Datentypen ist diese Grafik echt sehr 
anschaulich: https://www.bitweenie.com/listings/vhdl-type-conversion/

...
process
    ...
    begin
      while not endfile(textfile_in) loop
        number_in_valid := TRUE;
        readline(textfile_in, line_in);
        while number_in_valid loop
          read(line_in, number_in, number_in_valid);
          exit when not number_in_valid;
          report "Wert: " & integer'image(number_in);
          s_datain <= std_logic_vector(to_signed(number_in, 24));
          number_out := to_integer(signed(s_firout));
          write(line_out, number_out, right, 20); --10);
        end loop;
        writeline(textfile_out ,line_out);
        wait for 10 ns; -- entspricht quasi "Abtastrate"
        report "Next Line";
      end loop;
      report "Done";
      wait;
end process;

Den Code kann ich dann später in ähnlicher Form für meine ersten 
Berechnungen mit dem adaptiven Filter verwenden. :)

von Gustl B. (-gb-)


Lesenswert?

Mario A. schrieb:
> Für die Umwandlung der Datentypen ist diese Grafik echt sehr
> anschaulich: https://www.bitweenie.com/listings/vhdl-type-conversion/

Exakt, die erklärt eigentlich wunderbar wie man die numeric_std benutzen 
sollte.

Lob und Anerkennung! Weiter so!

von Mario A. (itse_me_mario)


Lesenswert?

Vielen Dank wieder mal für deine Hilfe. :) Jetzt gibts erst mal noch 
bissl Theorie zum adaptiven Filter und dann wird ein erstes Filter in 
VHDL implementiert. :)

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,
ich bin gerade dabei den LMS-Code im Anhang in VHDL zu implementieren. 
Ich habe jetzt mal versucht den C-Algorithmus direkt 1zu1 in VHDL 
umzusetzen.

Ich habe mein Modul "LMS_Filter.vhd" nun statt meinem 
FIRFilter.vhd-Modul eingebunden. In AudioInterface ändert sich dadurch 
nicht viel.

Mein Problem sind die verschiedenen Datenformate:
Eingangsdaten sind 24 Bit
Ausgangsdaten sind 24 Bit
Koeffizienten sind 16 Bit

Ich hätte die Daten im Q0.15 bzw. im Q0.23 Format interpretiert. Also 1 
VZ-Bit und 15 bzw. 23 Nachkommabits (VZ, 1/2, 1/4, 1/8, ...).
Q0.15 * Q0.15 --> Q2.30 --> für Q0.15 --> 30 downto 15 nehmen
Q0.15 * Q0.23 --> Q2.38 --> für Q0.15 --> 38 downto 15 nehmen


In der Schleife mit arr_w_acc kann ich mir allerdings nicht die 
richtigen Daten mit 38 downto 15 extrahieren, da ich dann einen 
Syntaxfehler habe: "Error: Cannot index the result of a type conversion"

Eigentlich wollte ich Folgendes in einer Zeile machen:
   acc <= acc   + Q0.15 *Q0.23 * Q0.23
-> acc <= acc   + Q2.38(38 downto 23) * Q0.23
-> acc <= acc   + Q0.15 * Q0.23
-> acc <= acc   + Q2.38
-> acc <= Q2.38 + Q2.38 -- acc = 47..0 -> 8 Guardbits


Jetzt hätte ich mal alles auf einmal berechnet:
acc <= acc   + Q0.15 * Q0.23 * Q0.23
acc <= Q3.61 + Q3.61  -- acc = 71..0 -> 8 Guardbits

Darf ich das einfach so machen? Es sind ja jetzt quasi 3 Vorzeichenbits. 
Leider habe ich noch keine genauere Beschreibung zur Fixed-Point 
Arithmetik gefunden.

Wie hättet ihr den Algorithmus implementiert? Findet ihr das zu 
umständlich?
Vielen Dank im Voraus und einen schönen 3. Advent! :)

Ich schreibe dazu jetzt mal eine passende Testbench. :)

von Gustl B. (-gb-)


Lesenswert?

Moin,
ich kann dir da leider nicht helfen. Mit so Filtereisachen bin ich nicht 
sehr vertraut.

Mario A. schrieb:
> Ich schreibe dazu jetzt mal eine passende Testbench. :)

Das ist die Idee schlechthin! Mach das und guck dir an wie sich deine 
Beschreibung verhält.

Sonst warte hier auf kompetente Hilfe, aber lange Threads werden weniger 
häufig gelesen. Es spricht nichts dagegen einen neuen Thread zu diesem 
speziellen Spezialthema aufzumachen.

Viel Erfolg!

von Mario A. (itse_me_mario)


Lesenswert?

Hallo Gustl,

kein Problem! :) Vllt trotzdem eine Frage an dich: Hättest du den 
Algorithmus in ähnlicher Weise implementiert? Oder wärst du das ganz 
anders angegangen? Hättest du dieses ganze Q... Zahlenformatzeug 
überhaupt verwendet? Oder einfach z.B. Integers verwendet?

>>Das ist die Idee schlechthin!
Bin dabei. :)

>>einen neuen Thread zu diesem speziellen Spezialthema aufzumachen.
Ja das wäre vermutlich wirklich am geschicktesten! :) Vielen Dank dir!

von Gustl B. (-gb-)


Lesenswert?

Mir sind da noch zu viele Dinge unklar. w, N und BETA fallen irgendwie 
aus der Luft?!
Ich würde das erstmal in einer normalen Sprache schreiben. Bei mir wäre 
das Python. Oder nimm den Code und tippe den ab. Und dann guck dir an 
wie sich die Zahlen verhalten. Vielleicht geht das alles mit Integer. 
Wenn du das in einer Hochsprache hast, dann kannst du das auch in VHDL 
übersetzen. Als Zwischenschritt kann man das ja in der Hochsprache so 
schreiben, dass da Takte simuliert werden. Also eine Schleife die quasi 
den Takt darstellt und innerhalb darfst du nur auf Dinge zugreifen, die 
im vorherigen Schleifenzugriff gesetzt wurden. Außer du würdest 
Variablen im VHDL später verwenden. Kann man machen, ist auch nicht 
weiter schwer, aber man muss sich immer klar sein was man da gerade 
macht und wie sich das in Hardware verhalten wird. Da gibt es große 
Unterschiede zwischen Signal und Variable.
Und dann gibt es den Unterschied zwischen der einfachen Simulation und 
der Hardware. Man kann problemlos in VHDL sehr komplexe Ausdrücke 
hinschreiben. Das sieht dann auch in der Simulation gut aus, aber in der 
Hardware kann das schlicht zu lange dauern dass das Timing nicht 
geschafft wird.

-------------------------------------------------
Falls du Variablen verwenden solltest:
Variablen verhalten sich, soweit ich weiß, nur wie Drähte. Die haben 
also kein Speicherverhalten. Während Signale auch speichern können wenn 
man sie entsprechend verwendet.

Braucht man überhaupt Variablen?
Nein. Man kann alles mit Signalen schreiben, es wird aber dann 
vielleicht sehr unübersichtlich. Und man muss das dann ggf. so 
schreiben, dass kein Speicherverhalten auftritt.

------------------------------------------------
Ja ... also wenn du mir beschreibst was der Code aus dem Foto machen 
soll und mir beschreibst wo welche Buchstaben/Variablen in dem Code 
herkommen und welchen Typ die haben (steht leider nicht dabei) dann 
würde ich mal versuchen den nachzubasteln. Aktuell ist mir da noch zu 
vieles unklar.

von Mario A. (itse_me_mario)



Lesenswert?

Hallo Gustl,

Danke für die ganzen Tipps! Ich versuch das umzusetzen.

Ich hab den LMS und NLMS schon mal probeweise in C geschrieben. Ich 
hänge dir mal die .c Datei an. Ich hab dafür erst mal Codeblocks 
verwendet. Ich lese die Daten (Achtung, das sind 544000 Werte!) über 
eine Textdatei ein und schreibe die gelesenen Daten wieder in eine 
txt-Datei.

Nochmal die Idee dahinter: Spracherkenner (z. B. Auto)
Musik wird abgespielt und der Fahrer möchte währenddessen sein
Fahrerassistenzsystem per Sprache bedienen. Also der Lautsprecher gibt
das Musiksignal x aus und ein Mikrofon nimmt quasi das Signalgemisch z 
aus
Musik (verändert über die Übertragungsfunktion des Raumes) und Sprache
auf. Ziel: Musik aus Signalgemisch filtern.
Ich habe dir mal eine Zeichnung mit vielen Erklärungen gemacht.

Die verwendeten Signale sind 1x das Referenzsignal x (nur Musik: 
XData_FULL.txt) und 1x das Signalgemisch z bestehend aus Musik und 
Sprache unter Einbeziehung einer zus. Übertragunsfunktion (des Raums: 
ZData_FULL.txt). Ziel des adaptiven Filters ist es nun das (veränderte) 
Musiksignal aus dem Signalgemisch weitgehend zu minimieren, sodass am 
Ende die Sprache erkannt werden kann. Hört man sich nachher das Signal e 
(also den Fehler) an, ist im Idealfall nur noch die Sprache zu hören.
Annahme:
Musik und Sprache sind unkorreliert und die Musiksignale vor und nach
der Veränderung sind korreliert.

Erst mal mit float/double:
XData_FULL.txt: Musik ohne Übertragungsfkt. des Raums
ZData_FULL.txt: Gemisch von Sprache und Musik (inklusive 
Übertragungsfkt. des Raums)

Als Festkommarealisierung (diese muss ich allerdings noch weiter 
überprüfen: Ich arbeite parallel daran :D ):
DataX_All_Q15_Fixed.txt: --> vorherige txt-File-Daten mit 2^15 
multiplizieren (für Q0.15)
DataZ_All_Q15_Fixed.txt: --> vorherige txt-File-Daten mit 2^15 
multiplizieren (für Q0.15)

Am leichtesten ist erst Mal main_LMS_Float.c zu verstehen.

Die Auswertung der txt-Dateien mache ich dann in MATLAB.
LMS-Algorithmus und NLMS-Algorithmus möchte ich jetzt dann eben noch auf 
dem FPGA zum laufen bringen. :)

Wenn noch irgendwas unklar ist, meld dich einfach! :) Vllt schaff ich es 
morgen, dass ich auch noch einen spezifischeren Thread für die 
Festkommarealisierung aufmache. Das ist schon sehr speziell. :D Vielen 
Dank dir schon mal!

: Bearbeitet durch User
von Mario A. (itse_me_mario)


Lesenswert?

Ich muss gerade auf ein neues UART-Modul warten... Es scheint den 
Transport nicht überlebt zu haben. Wenn ich das Modul früher an den 
Computer angesteckt habe, ging der Lüfter des FPGAs an, auch wenn es 
ausgeschaltet war. Das macht es jetzt nicht mehr. Daten werden keine 
mehr aufgenommen... :( Am USB-Kabel liegt es nicht. Auch mit einem 
zweiten hat es nicht funktioniert.

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

ich habe ein LMS-Filter in VHDL implementiert (siehe Anhang). Die ERLE 
Kurven sehen sehr gut aus:

ERLE = 10*log10(Pd/Pe)

Pd ist die Leistung des gewünschten Signals und Pe die Leistungs des 
Fehlersignals. Das Fehlersignal wird mit e = d-y berechnet. y ist mein 
berechneter FIR-Filterausgang.

Ich habe verschiedene LMS-Filter implementiert (16/24 Bit Daten, 16/24 
Bit Koeffizienten) und alle funktionieren (im Anhang ist ein Filter mit 
16 Bit Koeffizienten und 24 Bit Daten). Ich lese jeweils 32000 Werte für 
x und d ein und berechne daraus dann e (und y). Die Werte für e und y 
schreibe ich dann wieder in ein txt-File. Da es 32000 Werte sind und die 
Abtastrate 32kHz beträgt, muss die Simulation 1s lang laufen, wenn alle 
Werte berechnet werden sollen (das dauert... :D). An dieser Stelle 
nochmal danke an @Gustl. :) Die Dateien für d und x im Anhang müssen 
hierfür in den sim Ordner kopiert werden.

Das i2s_data_interface liefert alle 32kHz neue Werte (die ich aber 
gerade nicht verwende). Außerdem liefert es alle 32kHz ein '1' Signal. 
Die steigende Flanke davon verwende ich, um mein LMS-Filter zu starten.



Mein nächstes Ziel ist es nun ein NLMS-Filter zu implementieren.

Meine Koeffizienten w wurden bisher folgendermaßen (von der Logik her) 
im Laufe der Zeit verändert (allerdings als Fixed-Point):

for (i = N-1; i >= 0; i--)
{
 w[i] += (BETA*e*x[i]);  // update filter weights
 if(i!=0)
 {
  x[i] = x[i-1];      // shift data in delay line
 }
}

Nun sollen diese wie folgt berechnet werden:
for (i = N-1; i >= 0; i--)
{
  w[i] += ( ((BETA*e*x[i])/input_signal_power) );  // update filter 
weights
  if(i!=0)
  {
   x[i] = x[i-1];      // shift data in delay line
  }
}

Wobei input_signal_power Folgendes ist:
for(j=0; j<N;j++)
{
 input_signal_power = input_signal_power + (x[j] * x[j]);
}

Ich wollte mich mal erkundigen, wie ihr die Division durch 
input_signal_power durchführen würdet und ob ihr Tipps habt? Die 
Division mit "/" funktioniert ja leider lediglich in der Simulation. Ich 
würde es erst mal mit dem Divider Generator aus dem IP Catalog 
versuchen. :)
Ich würde mich sehr freuen, falls jemand einen Tipp hat. :) Bin gerade 
dabei meine FSM anzupassen. Ich denke ich werde mit Generate mehrere 
Divider-Blöcke erzeugen, damit die Division parallel berechnet werden 
können (für jeden Filterkoeffizienten brauch ich eine Division).

Vielen Dank im Voraus!

: Bearbeitet durch User
von -gb- (Gast)


Lesenswert?

Mario A. schrieb:
> Ich wollte mich mal erkundigen, wie ihr die Division durch
> input_signal_power durchführen würdet und ob ihr Tipps habt?

Moin!

Ist der Divisor konstant?

Wenn ja, dann kannst du die Division umwandeln in eine Multiplikation 
und eine Division durch eine Zweierpotenz.
Dabei wandelst du den Bruch 1/Divisor in einen Bruch um, bei dem im 
Nenner eine Zweierpotenz steht.

Beispiel:
Statt *1/3 kannst du schreiben *1365/2^12
Und die Division durch eine Zweierpotenz kannst du einfach hinschreiben.

Du kannst auch eine beliebige Division hinschreiben, Vivado baut dir 
dann einen kombinatorischen Dividierer ein.

Wenn sich der Divisor ändert, dann kannst du das entweder in 
Kombinatorik rechnen lassen, oder auch als Pipeline. Da bekommst du zwar 
Latenz dazu, aber hast weiterhin in jedem Takt ein Ergebnis. Wenn du die 
32 kSample/s verwendest, also nur 32k mal in der Sekunde ein Ergebnis 
brauchst, dann hast du bei 100 MHz Systemtakt für jede Division 3125 
Takte Zeit.

von Mario A. (itse_me_mario)


Lesenswert?

Hi, danke für deine Antwort! Also der Divisor ändert sich leider mit 
jedem Abtastwert. Dann wäre der Divider Generator wohl gut geeignet.

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

ich hab jetzt mal versucht das NLMS-Filter zu implementieren. Leider 
bisher ohne Erfolg. Problematisch ist auch die Zeit für die Berechnung. 
Die Simulation dauert nochmal deutlich länger als für den LMS 
Algorithmus... Ich vermute dass bei der Division irgendwas schief geht. 
Das Projekt habe ich mal angehängt. Über Tipps würde ich mich sehr 
freuen. :) Vielen Dank im Voraus! Gruß Mario :)

von Gustl B. (-gb-)


Lesenswert?

Ich finde das sehr spannend, habe aber leider nicht die Zeit das 
komplett zu verstehen.

Dass die Simulation dauert fände ich noch OK, IPs brauchen oft viel 
Rechenzeit und auch in deinem Code wird ja doch viel gerechnet.

Um zu gucken ob deine VHDL Lösung funktioniert empfehle ich generell 
diesen Weg:

1. Erzeuge dir einen Signalverlauf, also Wertetabelle.
2. Berechne das Ergebnis deines Filters in einer Programmiersprache.
3. Schreibe eine Testbench die exakt diese Wertetabelle aus 1. deinem 
VHDL füttert.
4. Schreibe ebenfalls in einer Testbench das Ergebnis deines VHDL 
Filters wieder in eine Datei.
5. Vergleiche die beiden Ergebnisse in einer Programmiersprache.

Ich empfehle wirklich das std.textio Package und Python. So mache ich 
das mit meiner Filterei.

Statt Wertetabelle kannst du auch den Sinusgenerator für das I2S 
verwenden wenn das reicht und dann eben die Signalform in der Simulation 
angucken.

Viel Erfolg!

von Mario A. (itse_me_mario)


Lesenswert?

Hallo Gustl,

danke für die Tipps! So in etwa mache ich das aktuell auch. Den LMS und 
NLMS habe ich davor schon in Matlab und C implementiert (Fest- und 
Gleitkomma). Genau dieselben Daten für x und d lese ich nun in der 
Testbench ein. Ich seh mir immer die ERLE Kurven an, da diese auch den 
berechneten Fehler e enthalten. Ich hoffe, dass ich am Wochenende wieder 
vermehrt dazukomme. Gerade steht leider viel anderes Zeug an. Gruß Mario 
:)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.