Forum: FPGA, VHDL & Co. CPLD-Problem: moving average


von S. K. (Gast)


Angehängte Dateien:

Lesenswert?

Hallo an Alle,

Ich habe ein Problem bei einer 'moving average' Berechnung.
Ich benutze ein CPLD (Coolrunner II ,XC2C256) als eine Art Multiplexer
um auf die Ausgabe zweier 14 Bit ADC zugreifen zu können. Das simple 
durchschleifen der ADC Werte und umschalten von mehreren Eingangskanälen 
funktioniert auch soweit.
Da ich jetzt noch ein paar Makrozellen frei habe dachte ich daran, noch 
eine Mittelung einzubauen. Jedoch klappt die Berechnung nicht.

Vielleicht könnte mal jemand darüberschauen mit etwas mehr Erfahrung:


(die funktionierenden Teile des Programms hab ich rausgenommen)
1
library IEEE;
2
use IEEE.STD_LOGIC_1164.ALL;
3
use IEEE.NUMERIC_STD.ALL;
4
5
6
entity analoginput is
7
    Port (   
8
          RESET : in STD_LOGIC;
9
        
10
          IN_REFRESH_DIRECT : in  STD_LOGIC;
11
          IN_REFRESH_MEAN  : in  STD_LOGIC;
12
          IN_ADR : in  STD_LOGIC_VECTOR (3 downto 0);
13
          IN_SET_ADR : in  STD_LOGIC;
14
          OUT_DATA : out  STD_LOGIC_VECTOR (13 downto 0);
15
        --ADC
16
          ADC1_DATA : in  STD_LOGIC_VECTOR (13 downto 0);
17
          ADC1_CLK : out  STD_LOGIC;
18
          ADC2_DATA : in  STD_LOGIC_VECTOR (13 downto 0);
19
          ADC2_CLK : out  STD_LOGIC;
20
  
21
        --GLOBAL
22
          CLK : in  STD_LOGIC);
23
end analoginput;
24
25
26
architecture Behavioral of analoginput is
27
  --Signal zum Takten
28
    type ZUSTAENDE is (Z0, Z1, Z2, Z3, Z4, Z5); -- Aufzählungstyp
29
    signal ZUSTAND, FOLGEZUSTAND: ZUSTAENDE;
30
  --SIGNAL ZUR FLANKENERKENNUNG VON IN_SET_ADR
31
    signal SIG_SET_ADR : STD_LOGIC_VECTOR (1 downto 0) := "01" ;
32
  --SIGNAL ZUR FLANKENERKENNUNG VON IN_REFRESH_xxxxx
33
    signal SIG_IN_REFRESH : STD_LOGIC_VECTOR (3 downto 0) := "0000" ;
34
  --FF für ADRESSE
35
    signal SIG_ADR : STD_LOGIC_VECTOR (3 downto 0) := "0000" ;
36
  --FF für OUT_DATA
37
    signal SIG_OUT_DATA : STD_LOGIC_VECTOR (13 downto 0) := "00000000000000" ;
38
  --FF für ADC_DATA
39
    signal SIG_ADC1_DATA : STD_LOGIC_VECTOR (13 downto 0) := "00000000000000" ;
40
    signal SIG_ADC2_DATA : STD_LOGIC_VECTOR (13 downto 0) := "00000000000000" ;
41
  --SIGNALE für Mittelung
42
43
44
    signal SIG_ADC_MEAN_SUM   : STD_LOGIC_VECTOR (15 downto 0) :=  "0000000000000000" ; 
45
    signal SIG_ADC_MEAN    : STD_LOGIC_VECTOR (13 downto 0) :=  "00000000000000" ;  
46
    signal SIG_ADC_MEAN_VALUE_1   : STD_LOGIC_VECTOR (13 downto 0) :=  "00000000000000" ;
47
    signal SIG_ADC_MEAN_VALUE_2   : STD_LOGIC_VECTOR (13 downto 0) :=  "00000000000000" ;
48
    signal SIG_ADC_MEAN_VALUE_3   : STD_LOGIC_VECTOR (13 downto 0) :=  "00000000000000" ;
49
    signal SIG_ADC_MEAN_VALUE_4   : STD_LOGIC_VECTOR (13 downto 0) :=  "00000000000000" ;
50
51
begin
52
53
54
PROCESS_adc_zustand: process(CLK,RESET)
55
begin
56
  if rising_edge(CLK) then
57
    if RESET = '1' then
58
      ZUSTAND <= Z0;
59
    else
60
      ZUSTAND <= FOLGEZUSTAND;
61
    end if;
62
  end if;
63
end process;
64
65
PROCESS_adc: process(CLK)
66
begin
67
  if rising_edge(CLK) then
68
    CASE ZUSTAND IS
69
     WHEN  z0  =>  
70
            ADC1_CLK <= '1';
71
            ADC2_CLK <= '1';
72
            SIG_ADC1_DATA <= SIG_ADC1_DATA;
73
            SIG_ADC2_DATA <= SIG_ADC2_DATA;          
74
75
              SIG_ADC_MEAN <= SIG_ADC_MEAN_SUM(15 downto 2) ;  --zuweisung mean 
76
              SIG_ADC_MEAN_SUM <= SIG_ADC_MEAN_SUM ;
77
              SIG_ADC_MEAN_VALUE_1 <= SIG_ADC_MEAN_VALUE_1;  --begin: speicher
78
              SIG_ADC_MEAN_VALUE_2 <= SIG_ADC_MEAN_VALUE_2;
79
              SIG_ADC_MEAN_VALUE_3 <= SIG_ADC_MEAN_VALUE_3;
80
              SIG_ADC_MEAN_VALUE_4 <= SIG_ADC_MEAN_VALUE_4; --end: speicher
81
82
            FOLGEZUSTAND <= z1;
83
     WHEN  z1  =>  
84
            ADC1_CLK <= '1';
85
            ADC2_CLK <= '1';
86
            SIG_ADC1_DATA <= SIG_ADC1_DATA;
87
            SIG_ADC2_DATA <= SIG_ADC2_DATA;
88
              
89
              SIG_ADC_MEAN <= SIG_ADC_MEAN;
90
              SIG_ADC_MEAN_SUM <= STD_LOGIC_VECTOR(unsigned(SIG_ADC_MEAN_SUM) - unsigned(('0' & '0' & SIG_ADC_MEAN_VALUE_4))) ;        --Subtrahiere letzten Wert
91
              SIG_ADC_MEAN_VALUE_1 <= SIG_ADC_MEAN_VALUE_1;  --begin: speicher
92
              SIG_ADC_MEAN_VALUE_2 <= SIG_ADC_MEAN_VALUE_2;
93
              SIG_ADC_MEAN_VALUE_3 <= SIG_ADC_MEAN_VALUE_3;
94
              SIG_ADC_MEAN_VALUE_4 <= SIG_ADC_MEAN_VALUE_4; --end: speicher
95
              
96
            FOLGEZUSTAND <= z2;
97
     WHEN  z2  =>  
98
            ADC1_CLK <= '1';
99
            ADC2_CLK <= '1';
100
            SIG_ADC1_DATA <= SIG_ADC1_DATA;
101
            SIG_ADC2_DATA <= SIG_ADC2_DATA;
102
            
103
              SIG_ADC_MEAN <= SIG_ADC_MEAN;
104
              SIG_ADC_MEAN_SUM <= SIG_ADC_MEAN_SUM;
105
              SIG_ADC_MEAN_VALUE_1 <= SIG_ADC_MEAN_VALUE_1;  --begin: speicher
106
              SIG_ADC_MEAN_VALUE_2 <= SIG_ADC_MEAN_VALUE_2;
107
              SIG_ADC_MEAN_VALUE_3 <= SIG_ADC_MEAN_VALUE_3;
108
              SIG_ADC_MEAN_VALUE_4 <= SIG_ADC_MEAN_VALUE_4; --end: speicher
109
              
110
            FOLGEZUSTAND <= z3;
111
     WHEN  z3  =>  
112
            ADC1_CLK <= '0';
113
            ADC2_CLK <= '0';
114
            SIG_ADC1_DATA <= SIG_ADC1_DATA;
115
            SIG_ADC2_DATA <= SIG_ADC2_DATA;
116
                
117
              SIG_ADC_MEAN <= SIG_ADC_MEAN;
118
              SIG_ADC_MEAN_SUM <= SIG_ADC_MEAN_SUM;
119
              SIG_ADC_MEAN_VALUE_1 <= SIG_ADC_MEAN_VALUE_1;  --begin: speicher
120
              SIG_ADC_MEAN_VALUE_2 <= SIG_ADC_MEAN_VALUE_2;
121
              SIG_ADC_MEAN_VALUE_3 <= SIG_ADC_MEAN_VALUE_3;
122
              SIG_ADC_MEAN_VALUE_4 <= SIG_ADC_MEAN_VALUE_4; --end: speicher
123
              
124
            FOLGEZUSTAND <= z4;
125
     WHEN  z4  =>  --Hier ist die Zuweisung
126
            ADC1_CLK <= '0';
127
            ADC2_CLK <= '0';
128
            SIG_ADC1_DATA <= (not ADC1_DATA(13) & ADC1_DATA(12 downto 0));
129
            SIG_ADC2_DATA <= (not ADC2_DATA(13) & ADC2_DATA(12 downto 0));
130
131
              SIG_ADC_MEAN <= SIG_ADC_MEAN;
132
              SIG_ADC_MEAN_SUM <= SIG_ADC_MEAN_SUM;
133
              if SIG_ADR(3) = '1' then  --wenn > 7 dann ADC2            --begin: speicher
134
                SIG_ADC_MEAN_VALUE_1 <= ( not ADC2_DATA(13) & ADC2_DATA(12 downto 0));  
135
              else
136
                SIG_ADC_MEAN_VALUE_1 <= ( not ADC1_DATA(13) & ADC1_DATA(12 downto 0));  
137
              end if;
138
              SIG_ADC_MEAN_VALUE_2 <= SIG_ADC_MEAN_VALUE_1;
139
              SIG_ADC_MEAN_VALUE_3 <= SIG_ADC_MEAN_VALUE_2;
140
              SIG_ADC_MEAN_VALUE_4 <= SIG_ADC_MEAN_VALUE_3; --end: speicher
141
142
            FOLGEZUSTAND <= z5;
143
     WHEN  z5  =>  
144
            ADC1_CLK <= '0';
145
            ADC2_CLK <= '0';
146
            SIG_ADC1_DATA <= SIG_ADC1_DATA;
147
            SIG_ADC2_DATA <= SIG_ADC2_DATA;
148
              
149
              SIG_ADC_MEAN <= SIG_ADC_MEAN;
150
              SIG_ADC_MEAN_SUM <= STD_LOGIC_VECTOR(unsigned(SIG_ADC_MEAN_SUM) + unsigned(('0' & '0' & SIG_ADC_MEAN_VALUE_1))) ;      ---    addiere neuen Wert
151
              SIG_ADC_MEAN_VALUE_1 <= SIG_ADC_MEAN_VALUE_1;  --begin: speicher
152
              SIG_ADC_MEAN_VALUE_2 <= SIG_ADC_MEAN_VALUE_2;
153
              SIG_ADC_MEAN_VALUE_3 <= SIG_ADC_MEAN_VALUE_3;
154
              SIG_ADC_MEAN_VALUE_4 <= SIG_ADC_MEAN_VALUE_4; --end: speicher
155
              
156
            FOLGEZUSTAND <= z0;              
157
    END CASE;
158
  end if;
159
end process;
160
161
162
163
164
165
166
167
--IN_REFRESH GIBT LETZTEN GEMESSENEN WERT VON CHANNEL IN_ADR AUS (UNGEMITTELT ODER GEMITTELT)
168
PROCESS_READ_in_refresh : process(CLK,RESET)
169
begin
170
  if rising_edge(CLK) then
171
    if RESET = '1' then
172
      SIG_IN_REFRESH <= "0000";
173
    else
174
      SIG_IN_REFRESH(0) <= IN_REFRESH_DIRECT;
175
      SIG_IN_REFRESH(1) <= SIG_IN_REFRESH(0);
176
      SIG_IN_REFRESH(2) <= IN_REFRESH_MEAN;
177
      SIG_IN_REFRESH(3) <= SIG_IN_REFRESH(2);
178
    end if;
179
  OUT_DATA <= SIG_OUT_DATA;
180
  end if;
181
end process;
182
183
184
PROCESS_in_refresh : process(CLK,RESET)
185
begin
186
  if rising_edge(CLK) then
187
    if RESET = '1' then
188
      SIG_OUT_DATA <= "00000000000000";
189
    else  
190
      CASE SIG_IN_REFRESH IS
191
          WHEN "0001" =>      --REFRESH DIRECT
192
                if SIG_ADR(3) = '1' then  --wenn > 7 dann ADC2 
193
                  SIG_OUT_DATA <= SIG_ADC2_DATA;
194
                else                --ansonsten ADC1
195
                  SIG_OUT_DATA <= SIG_ADC1_DATA;
196
                end if;
197
          WHEN "0100" =>      --REFRESH MEAN                        --xxxx
198
                SIG_OUT_DATA <= SIG_ADC_MEAN;
199
          WHEN OTHERS =>
200
            SIG_OUT_DATA <= SIG_OUT_DATA;
201
      END CASE;
202
    end if;
203
  end if;
204
end process;
205
206
end Behavioral;


Hier noch etwas Erklärung zum Code:
- Der zustandsautomat liefert den Takt für den ADC und liest die 
gemessenen Daten in Z4 ein

- Den FIFO Speicher bilden die vier Signale:
1
signal SIG_ADC_MEAN_VALUE_1   : STD_LOGIC_VECTOR (13 downto 0) :=  "00000000000000" ;
2
    signal SIG_ADC_MEAN_VALUE_2   : STD_LOGIC_VECTOR (13 downto 0) :=  "00000000000000" ;
3
    signal SIG_ADC_MEAN_VALUE_3   : STD_LOGIC_VECTOR (13 downto 0) :=  "00000000000000" ;
4
    signal SIG_ADC_MEAN_VALUE_4   : STD_LOGIC_VECTOR (13 downto 0) :=  "00000000000000" ;

- Die Summe um den Mittelwert zu berechnen steht in:
1
 signal SIG_ADC_MEAN_SUM   : STD_LOGIC_VECTOR (15 downto 0) :=  "0000000000000000" ;
In Z1 wird der viert-letzte Wert von der Summe wieder entfernt, in Z5 
wird der als letztes gemessene Wert hinzuaddiert. Das Ergebnis wird in 
Z0 an
1
signal SIG_ADC_MEAN    : STD_LOGIC_VECTOR (13 downto 0) :=  "00000000000000" ;
übergeben.


Um etwas konkreter zu fragen:
Ist die Addition in Zustand Z5, bzw die Subtraktion in Z1 korrekt?
Ist die Zuweisung des FIFO's richtig ?

Kann mir dabei Bitte jemand helfen ?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

S. K. schrieb:
> codefrage.txt
Meine VHDL-Datein heißen hinten raus *.vhdl
Dann klappt das auch mit dem Syntax-Highlighting...

> Ist die Addition in Zustand Z5, bzw die Subtraktion in Z1 korrekt?
Der Simulator ist der Debugger einer VHDL-Beschreibung...
Was sagt die Simulation?
Hast du eine Testbench?

von S. K. (Gast)


Lesenswert?

Lothar Miller schrieb:
>> codefrage.txt
> Meine VHDL-Datein heißen hinten raus *.vhdl
> Dann klappt das auch mit dem Syntax-Highlighting...

Hier ist ebenfalls der gekürzte Code enthalten.....



Lothar Miller schrieb:
>> Ist die Addition in Zustand Z5, bzw die Subtraktion in Z1 korrekt?
> Der Simulator ist der Debugger einer VHDL-Beschreibung...
> Was sagt die Simulation?
> Hast du eine Testbench?

Naja, eigentlich dachte ich , dass die Berechnung so Simpel wäre, dass 
ich mir das Einarbeiten in Testbench/Simulator ersparen könnte....
Na dann muss ich wohl doch,

trotzdem mal danke,

stephan

von Purzel H. (hacky)


Lesenswert?

Mir schein das ganze viel zu aufwendig, auch wenn ich wenig ahnung von 
HDL habe. Ich weiss nur wie man's in code macht. In Hardware sollte es 
noch viel einfacher sein. Schau mal den code an :
Http://www.ibrtses.com/embedded/exponential.html , im Prinzip nur 
schieben und addieren.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

S. K. schrieb:
> dass ich mir das Einarbeiten in Testbench/Simulator ersparen könnte.
Du kannst doch schon VHDL, dann ist eine Testbench ja nichts Neues...
Siehe dort im Tutorial ab Seite 13:
http://www.lothar-miller.de/s9y/archives/81-Xilinx-ISE-Step-by-Step.html

von S.K. (Gast)


Lesenswert?

Danke nochmal,
habe mir gestern mal ein Tutorial von Xilinx angesehen und ein wenig 
ausprobiert. Ist doch wie immer: Wenn man sich mal ransetzt gehts 
schneller als zunächst gedacht.

Es sieht aus als wäre da ein Timingproblem, das mir den 
Zustandsautomaten aus der Reihe wirft.


mfg stephan

von Schlumpf (Gast)


Lesenswert?

Ohne mir deinen Code genauer angeschaut zu haben, ist deine Methode der 
Beschreibung etwas ungünstig, da hier, so wie es zumindest auf den 
ersten Blick aussieht, jede Menge Register entstehen, die du vermutlich 
gar nicht willst, oder benötigst.

Du beschreibst z.B. in einem CLK-Prozess die Zuweisung von FOLGEZUSTAND 
so, dass daraus ein Register entsteht.

In einem anderen Prozess beschreibst du ZUSTAND auch in einem 
CLK-Prozess, so dass auch hier ein Register entsteht.

Wenn ich das jetzt richtig überblicke, dann entsteht für FOLGEZUSTAND 
und ZUSTAND jeweils ein Register. Und es dauert daher immer 2 Takte, bis 
ZUSTAND den eigentlichen Folgezustand angenommen hat.

Solche Konstrukte hast du noch an anderen Stellen. Daher könnte es gut 
sein, dass da Dinge passieren, die du dir vielleicht anders vorgestellt 
hast.

Empfehlenswert ist meiner Ansicht nach folgende Methode:

Alle Register werden in EINEM Clocked-Process beschrieben.
Die ganze Kombinatorik dazwischen wird entweder komplett concurrend oder 
in einem rein kombinatorischen Prozess beschrieben.
Das ist übersichtlicher und man vermeidet, dass Register entstehen, die 
man eigentlich gar nicht im Kopf hatte..

von Falk B. (falk)


Lesenswert?

@S. K. (Gast)

>Da ich jetzt noch ein paar Makrozellen frei habe dachte ich daran, noch
>eine Mittelung einzubauen. Jedoch klappt die Berechnung nicht.

Ein paar? Wieviel denn?

Eine EINFACHSTE Mittlung über N Samples ist ein einfacher Akkumulator. 
Addieren und Speichern, nach N Samples Reset bzw Laden mit dem nächsten 
Sample. Deine Überschrift spricht aber von moving average, das ist was 
anderes. Dafür braucht man mehrere Speicher für die alten Samples. Das 
frisst deinen CPLD Ruck Zuck auf. Selbst als IIR Filter wird das SEHR 
eng.
1
process(clk)
2
begin
3
  if rising_edge(clk) then
4
    akku <= akku + in_data;
5
    cnt = cnt +1;
6
    if cnt=25 then
7
      cnt <= 0;
8
      akku <= in_data;
9
      out_data  <= akku;
10
    end if;
11
  end if;
12
end process;

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Falk Brunner schrieb:
> Eine EINFACHSTE Mittlung über N Samples ist ein einfacher Akkumulator.
Ich würde da eher ein RC-Glied nachbilden: den neuen Wert auf den 
Akkumulator aufaddieren, und gleichzeitig den Mittelwert abziehen...
Sowas wie das hier also:
http://www.lothar-miller.de/s9y/categories/21-Filter
Der eigentliche Trick: bei geeigneter Wahl der Filtertiefe (als 
Zweierpotenz) ist keine wirkliche Division nötig...

Und hier mit Filtertiefe 32 in VHDL:
1
    akku <= akku - akku/32 + in_data   when rising_edge(clk);
2
    out_data  <= akku/32;

von Tachjen (Gast)


Lesenswert?

Lothar Miller schrieb:
> Der eigentliche Trick: bei geeigneter Wahl der Filtertiefe (als
> Zweierpotenz) ist keine wirkliche Division nötig...

Ja, nur so ist es eigentlich auch Sinnvoll. Dann ist es nur noch eine 
Schiebeoperation.

Eigentlich ist das Ganze doch nur ein FIFO mit N Werten des ADCs.
Bei jedem neuen Wert des ADCs wird der älteste Wert von SUM_ADC (also 
SUM_ADC(N) ) entfernt, der neue Wert ins FIFO geschoben und gleichzeitig 
noch zu SUM_ADC addiert.
Dann noch SUM_ADC ld(N) mal nach rechts schieben und der gleitende 
Mittelwert ist fertig.

Oder sehe ich das falsch (zumindest mache ich das in C so).

von Tachjen (Gast)


Lesenswert?

Tachjen schrieb:
> SUM_ADC(N)

FIFO(N) sollte es heißen.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Tachjen schrieb:
> Dann ist es nur noch eine Schiebeoperation.
Bei Hardware nicht mal das:
es ist nur eine einmalige Umverdrahtung zum Synthesezeitpunkt...

> Eigentlich ist das Ganze doch nur ein FIFO mit N Werten des ADCs.
Nein, es ist ein Filter, der (wie ein RC-Glied) alte Werte nie 
"vergisst". Sie können höchstens so unbedeutend werden, dass sie nicht 
mehr mitmachen dürfen. Aber es wird nicht der "älteste" Wert abgezogen 
wie bei gleictenden Mittelwert, sondern eben der Mittelwert der letzten 
n Werte...

von Tachjen (Gast)


Lesenswert?

Lothar Miller schrieb:
> Bei Hardware nicht mal das:
> es ist nur eine einmalige Umverdrahtung zum Synthesezeitpunkt...

:-) stimmt. Ich denke noch zu sehr C

von Falk B. (falk)


Lesenswert?

@ Lothar Miller (lkmiller) (Moderator) Benutzerseite

>http://www.lothar-miller.de/s9y/categories/21-Filter

Das ist ja tricky! Ein FIR Filter ohne großen Speicher!
Wie sieht denn die Sprungantwort bzw. der Frequenzgang von so einem Ding 
aus im Vergleich zum echten FIR bzw. einfachen Akku? Klar, der einfache 
Akku spuckt nur alle N Samples einen neuen Wert aus, nicht sehr 
dynamisch.

von der Gast (Gast)


Lesenswert?


von der Gast (Gast)


Lesenswert?

Behandelt obiges PT1 Filter.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Falk Brunner schrieb:
> Das ist ja tricky!
Blöderweise nicht von mir... ;-)
Ich habe das irgendwann Ende der 80er Jahre des letzten Jahrtausends 
gefunden (als Rechenzeit und Speicher noch was kostete). Aber in 
Hardware lässt sich das eben supersimpel um- und einsetzen.

Falk Brunner schrieb:
> Wie sieht denn die Sprungantwort bzw. der Frequenzgang von so einem Ding
> aus
Wie im verlinkten Dokument zu sehen: eine E-Funktion wie beim RC-Glied. 
Nur bei negativen Zahlen muss man mit der Schieberei ein wenig 
aufpassen, denn z.B. ein (-7)/2 ist nicht das selbe wie ein (-7)>>1 ...

von Lattice User (Gast)


Lesenswert?

Falk Brunner schrieb:
> @ Lothar Miller (lkmiller) (Moderator) Benutzerseite
>
>>http://www.lothar-miller.de/s9y/categories/21-Filter
>
> Das ist ja tricky! Ein FIR Filter ohne großen Speicher!

Nur ist das kein FIR, sondern ein IIR mit Skalierung des Outputs. Seinen 
Zweck erfüllt es natürlich.

von NopNop (Gast)


Lesenswert?

Nur ist der IIR Filter immer stabil?

von S.K. (Gast)


Lesenswert?

Hallo an Alle,

Zunächst mal danke für die Hilfe, denn zwischenzeitlich hats auch schon 
geklappt (Immer diese Fehler ,die sich nach dem "aufräumen" 
einschleichen). Die moving average Methode habe ich zufällig ausgewählt, 
habe mich aber auch nicht weiter umgesehen welche Filtermethoden es 
sonst noch gibt. Jetzt bin ich umgestiegen auf das nachgebildete 
RC-Glied.......

Zu der Frage wieviele Makrozellen noch frei sind:

   Ohne die Mittelung belege ich ca 100 Makrozellen von 256, also
   jedemenge Platz zum Spielen.....




Schlumpf schrieb:
> Wenn ich das jetzt richtig überblicke, dann entsteht für FOLGEZUSTAND
> und ZUSTAND jeweils ein Register. Und es dauert daher immer 2 Takte, bis
> ZUSTAND den eigentlichen Folgezustand angenommen hat.

Ja genau das ist passiert,

Schlumpf schrieb:
> Alle Register werden in EINEM Clocked-Process beschrieben.
> Die ganze Kombinatorik dazwischen wird entweder komplett concurrend oder
> in einem rein kombinatorischen Prozess beschrieben.

Was bedeutet 'komplett concurrend' ?


Hier mal ein beispiel ob ich das jetzt richtig verstanden habe ,was mit 
'rein kombinatorisch' gemeint ist:

1
 -- INPUT ist der Wert der gemittelt wqerden soll
2
.
3
.
4
.
5
6
signal SUMME_NEU ...
7
signal SUMME_ALT ...
8
signal MITTELWERT ...
9
begin
10
11
12
13
14
rein_kombinatorisch : process(SUMME_ALT,INPUT)
15
begin
16
    SUMME_NEU <= SUMME_ALT - SUMME_ALT/32 + INPUT
17
end process;
18
19
Clocked : process(CLK)
20
begin
21
    if rising_edge(CLK) then
22
         MITTELWERT <= SUMME_NEU/32;
23
         SUMME_ALT <= SUMME_NEU;
24
    end if;
25
end process;


Jetzt wird die Summe doch 'immer' (kombinatorisch) berechnet aber nur 
bei steigender Flanke von CLK weiterverwendet. Sehe ich das richtig bzw 
geht das eleganter ?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

S.K. schrieb:
> Jetzt wird die Summe doch 'immer' (kombinatorisch) berechnet aber nur
> bei steigender Flanke von CLK weiterverwendet. Sehe ich das richtig bzw
> geht das eleganter ?
Klar, so wie ich das oben in meinem Zweizeiler gemacht habe. Auch dort 
wird "immer" die Summe berechnet (weil der Addierer ja "immer" da ist) 
und mit der steigenden Flanke gespeichert. Nur hast du zusätzlich noch 
eine Registerebene und damit einen Takt Latency eingeführt. Das ginge 
aber auch so:
1
    akku <= akku - akku/32 + in_data   when rising_edge(clk);
2
    out_data  <= akku/32               when rising_edge(clk);
Oder konservativer so:
1
    process begin
2
       wait until rising_edge(clk);
3
       akku <= akku - akku/32 + in_data;
4
       out_data  <= akku/32;
5
    end process;
Oder noch konservativer so:
1
    process (clk) begin
2
       if rising_edge(clk) then
3
          akku <= akku - akku/32 + in_data;
4
          out_data  <= akku/32;
5
       end if;
6
    end process;
Aber das in zwei Prozesse aufzuteilen (von denen der eine mit dem 
Addierer zudem vollkommen unnötig ist) ist absurd. Denn das hier:
1
rein_kombinatorisch : process(SUMME_ALT,INPUT)
2
begin
3
    SUMME_NEU <= SUMME_ALT - SUMME_ALT/32 + INPUT;
4
end process;
Kann funktionsgleich durch diese Concurrent-Beschreibung ersetzt werden:
1
    SUMME_NEU <= SUMME_ALT - SUMME_ALT/32 + INPUT;

von Schlumpf (Gast)


Lesenswert?

Hallo S.K.

So wie du es beispielhaft dargestellt hast, ist das, was ich meinte, 
richtig verstanden.

Ein Prozess beschreibt nur die Register, also das, was bei der 
Taktflanke gespeichert wird.

Der andere Prozess beschreibt die Kombinatorik zwischen den Registern.

Genaugenommen hast du jetzt in deinem Beispiel im Clocked-Process noch 
eine kombinatorische Verknüpfung "verbuddelt" (SUMME_NEU/32), welche 
man, wenn man die Trennung ganz strikt halten will, auch noch in den 
kombinatorischen Prozess ziehen könnte, aber dann müsste man noch ein 
zusätzliches Signal einbauen. Da muss man dann einfach abwägen, wie 
wichtig einem die absolut strikte Trennung ist.
Ich für meinen Teil halte das in meinen Designs sehr restriktiv und 
hätte ein Signal MITTEL_KOMB eingeführt, welchem ich im Kombinatorischen 
Prozess SUMME_NEU/32 zugewisen hätte. Und im Clocked-Process hätte ich 
dann MITTELWERT <= MITTEL_KOMB zugewiesen.
Aber das ist dann schon sehr haarspalterisch ;-)


Mit "komplett concurrent" meinte ich, dass man bei einfacher Logik für 
die Kombinatorik gar keinen Separaten Prozess benötigt, sondern einfach 
nur rein concurrent die Zuweisungen machen kann.

In deinem Beispiel würde das so ausshen:
1
Clocked : process(CLK)
2
begin
3
    if rising_edge(CLK) then
4
         MITTELWERT <= SUMME_NEU/32;
5
         SUMME_ALT <= SUMME_NEU;
6
    end if;
7
end process;
8
9
SUMME_NEU <= SUMME_ALT - SUMME_ALT/32 + INPUT

von S. K. (Gast)


Lesenswert?

So, habs jetzt verstanden und alles läuft wie gewollt.
Danke nochmal an alle!

mfg Stephan

von Michael W. (Gast)


Lesenswert?

Schlumpf , so macht man das eigentlich nicht, weil so ständig falsch 
gerundet wird.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Markus Wagner schrieb:
> so macht man das eigentlich nicht, weil so ständig falsch gerundet wird.
Warum? Und was sind die Auswirkungen?

von Schlumpf (Gast)


Lesenswert?

Lothar Miller schrieb:
> Markus Wagner schrieb:
>> so macht man das eigentlich nicht, weil so ständig falsch gerundet wird.
> Warum? Und was sind die Auswirkungen?

Versteh ich auch nicht, was er damit meint...

von J. S. (engineer) Benutzerseite


Lesenswert?

Mal abgesehen von der Rundungsthematik scheint der Code ein Grundproblem 
zu haben:

SIG_ADC_MEAN_VALUE_1 <= SIG_ADC_MEAN_VALUE_1;  --begin: speicher
SIG_ADC_MEAN_VALUE_2 <= SIG_ADC_MEAN_VALUE_2;
SIG_ADC_MEAN_VALUE_3 <= SIG_ADC_MEAN_VALUE_3;
SIG_ADC_MEAN_VALUE_4 <= SIG_ADC_MEAN_VALUE_4;

Werte auf sich selber zu kopieren ist nicht so der Bringer. Auch in 
FPGAs nicht. Ich denke das sollte mal die Schiebeoperation werden, oder?

SIG_ADC_MEAN_VALUE_1 <= Input_Vaue_primary;  -- der neue Wert
SIG_ADC_MEAN_VALUE_2 <= SIG_ADC_MEAN_VALUE_1;
SIG_ADC_MEAN_VALUE_3 <= SIG_ADC_MEAN_VALUE_2;
SIG_ADC_MEAN_VALUE_4 <= SIG_ADC_MEAN_VALUE_3;

Dann kann man einfach Addieren und durch 4 teilen- wobei man vorher 2 
addiert, womit wir jetzt doch beim Runden wären: Ich sehe sehr häufig, 
dass bei INT Variablen / Signalen munter durchdividiert wird, ohne 
vorher zu runden, wodurch man den doppelten Fehler bekommt.

Zu dem zweiten Vorschlag:

Das ist ein träges IIR-ähnliches Filter, das ebenfalls nicht ganz 
korrekt gerundet wird: Wenn mit 32 dividiert werden soll, wäre zuvor 16 
zu addieren, damit der Fehler geringer wird. Am Besten ist es immer, 
eine hochaufgelöste Summe mitzuführen, bei der gar nicht dividert werden 
muss bzw. statistisch (toogled) zu runden und zu dividieren:

SUMME_DIF <= ( SUMME_NEU + Teiler/4 + toogle*Teiler/2 ) / Teiler
SUMME_NEU <= SUMME_NEU - SUMME_DIF + INPUT;

Wobei beides in einem Takt erfolgen muss und "Teiler" hier wie immer ein 
Binärwert ist. Einen IIR halte ich aber nicht für zweckmässig und das 
wäre auch kein gleitender Wert, wie der TE möchte:

Besser ist, einen sauberen FIR einzufügen, mit dem man direkt fenstert! 
Das Fenster kann "Ungefähr-Cos-Bogen" sein, das reicht in der Regel. 
Summe der Gewichtungen wieder als Binärwert:

Pseudo Code:

IF clockedge
  data8 = data7
  data7 = data6
  ...
  data2 = data1
  data1 = input

  term1 = data1 + data8
  term2 = data2 + data7
  term3 = data3 + data6
  term4 = data4 + data5

  sum = 1 x term1 + 3 x term2 + 5 x term 3 + 7 x term 4 + 16
  erg = sum / 32
END IF

Ein bischen mehr HF-Dämpfung bekommt man mit:

  sum = (2 x term1 + 3 x term2 + 5 x term 3 + 6 x term 4 + 16) / 32

von Schlumpf (Gast)


Lesenswert?

Jürgen Schuhmacher schrieb:
> Werte auf sich selber zu kopieren ist nicht so der Bringer. Auch in
> FPGAs nicht.

Was ist daran verkehrt, wenn man den Ausgang eines Registers auf den 
Eingang des Registers zurückkoppelt?

von Lattice User (Gast)


Lesenswert?

Schlumpf schrieb:
> Jürgen Schuhmacher schrieb:
>> Werte auf sich selber zu kopieren ist nicht so der Bringer. Auch in
>> FPGAs nicht.
>
> Was ist daran verkehrt, wenn man den Ausgang eines Registers auf den
> Eingang des Registers zurückkoppelt?

Es braucht einen zusätzlichen Multiplexer.
Ohne Rückkopplung wird das Synthesetool in der Regel das über 
ClockEnable des Registers regeln.
Kann natürlich sein, dass der Synthesier auch den Multiplexer 
wegoptimiert und durch ClockEnable ersetzt.

von J. S. (engineer) Benutzerseite


Lesenswert?

Schlumpf schrieb:
> Was ist daran verkehrt, wenn man den Ausgang eines Registers auf den
>
> Eingang des Registers zurückkoppelt?

Es ist eine nutzlose Aktion. Es tut sich ja nichts. Warum sollte man das 
formulieren?

Im Übrigen war meine Anmerkung ja die, dass das funktionell falsch ist. 
Irgendwo müssen die Register ja mit Werten gefüttert werden. Das und nur 
das sollte man formulieren.

von Schlumpf (Gast)


Lesenswert?

Natürlich sind wir uns einig, dass ein reines Zurückkoppeln des Ausgangs 
auf den Eingang sinnlos ist und funktionell so nichts bringt.

Wenn aber der "übliche" Fall eintritt und der Registerausgang anhand 
eines bestimmten Systemzustandes geändert wird, so muss entweder das 
Register für alle anderen Fälle deaktiviert werden (clk-enable) oder mit 
dem Ausgang selbst "gefüttert" werden.
Und dann hat man entweder die Möglichkeit, sich über den 
"default-Zustand" auszuschweigen, oder diesen zu beschreiben.
Beschreibt man ihn nicht, so überlässt man der Synthese, was sie daraus 
macht. Das kann dann ein Multiplexer, clock-enable, oder im dümmsten 
Fall vielleicht sogar ein Latch sein.
Beschreibt man es, dann kann man wenigstens sicher sein, dass kein Latch 
entsteht.
Man müsste sich das mal genau anschauen, was die einzelnen Synthesizer 
aus unterschiedlichen Beschreibungen zusammenbauen.

Vermutlich könnte es schon darauf hinauslaufen, dass es etwas 
ressourcenschonender ist, das Register nicht zurückzukoppeln.

von Markus F. (Gast)


Lesenswert?

Lothar Miller schrieb:
> Falk Brunner schrieb:
>
>> Das ist ja tricky!
> Blöderweise nicht von mir... ;-)
> Ich habe das irgendwann Ende der 80er Jahre des letzten Jahrtausends
> gefunden
Das haben viele gefunden, ist eine naheliegende Idee und bei genauer 
Betrachung nichts anderes, als eine Kondensatorladekurve. Wird in der 
SW-Landschaft seit den 60ern eingesetzt, seit sie zum Mond geflogen 
sind.

Was mich mehr bewegt ist die Frage, ab das ein "moving average" ist und 
ob das der Aufgabe angemessen ist. Was einfach zu programmieren ist, 
muss nicht gut sein für die App. Avarage heisst bei mir "Mittelwert" 
aber es ist kein Mittelwert von irgendwas sondern eine Summe, bei der 
das jüngste Element am meisten zu sagen hat, und das älteste am 
wenigsten. Das Problem dieser Methode ist, dass sogar das allererste 
Element noch schwach drinsteckt, obwohl das keine Aussage mehr machen 
sollte. Eine verbesserte Methode wäre, zwei solcher "Summen" 
mitzuschleppen, diese wechselseitig zu resetten, sodass sie nur für 
einen bestimmten Zeitraum gelten und dies beide zu mitteln.


Schlumpf schrieb:
> Wenn aber der "übliche" Fall eintritt und der Registerausgang anhand
> eines bestimmten Systemzustandes geändert wird, so muss entweder das
> Register für alle anderen Fälle deaktiviert werden (clk-enable) oder mit
> dem Ausgang selbst "gefüttert" werden.

Seit wann denn das? Ist das bei FPGAs so? Ist das nicht so, dass a 
automatisch ein enable erzeugt wird, für den IF-WHEN-case, der relevant 
ist und ansonsten das enable low ist?

So haben wir das gelernt und so habe ich das immer gemacht.

Meinungen?

von Schlumpf (Gast)


Lesenswert?

Mark F. schrieb:
> Schlumpf schrieb:
>> Wenn aber der "übliche" Fall eintritt und der Registerausgang anhand
>> eines bestimmten Systemzustandes geändert wird, so muss entweder das
>> Register für alle anderen Fälle deaktiviert werden (clk-enable) oder mit
>> dem Ausgang selbst "gefüttert" werden.
>
> Seit wann denn das? Ist das bei FPGAs so? Ist das nicht so, dass a
> automatisch ein enable erzeugt wird, für den IF-WHEN-case, der relevant
> ist und ansonsten das enable low ist?

Du beschreibst doch in deiner Anmerkung genau das Gleiche, was ich auch 
gesagt habe (Register wird mit clock enable deaktiviert, wenn es nicht 
geändert werden soll). Daher verstehe ich deine Frage "Seit wann denn 
das?" nicht so richtig.

Ich habe gerade mal eine simples Konstrukt auf verschiedene Arten 
beschrieben (ohne Rückkopplung des Registers auf sich selbst, mit 
Rückkopplung, ein-Prozess-Methode, zwei-Prozess-Methode,..) und jedesmal 
spuckte die Synthese das identische Ergebnis aus:

Die Steuerung des Registers erfolgte in allen Fällen über clk-enable.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Schlumpf schrieb:
> Die Steuerung des Registers erfolgte in allen Fällen über clk-enable.
Ja, zum Glück erkennt die Synthese in den meisten Schreibweisen, was 
eigentlich gemeint war...
http://www.lothar-miller.de/s9y/archives/1-Clock-Enable-in-einer-ISE-VHDL-Beschreibung.html

Mark F. schrieb:
>> Ich habe das irgendwann Ende der 80er Jahre des letzten Jahrtausends
>> gefunden
> Das haben viele gefunden, ist eine naheliegende Idee und bei genauer
> Betrachung nichts anderes, als eine Kondensatorladekurve.
Ich meine, genau das schon geschrieben zu haben, denn
Lothar Miller schrieb:
>>>>> Ich würde da eher ein RC-Glied nachbilden
Aber seis drum, Wiederholung schadet nicht.

> Das Problem dieser Methode ist, dass sogar das allererste Element
> noch schwach drinsteckt, obwohl das keine Aussage mehr machen sollte.
Es ist kein Problem, wenn einem dieses Verhaltens bewusst ist. Denn 
der älteste Wert verliert exponentiell an Bedeutung. In der Realität ist 
er bei einem 8 Bit Wert nach gut 5 tau (im Beispiel mit der Filtertiefe 
32 also nach 160 Abtastungen) nicht mehr signifikant wirksam.

> Eine verbesserte Methode wäre, zwei solcher "Summen"
> mitzuschleppen, diese wechselseitig zu resetten, sodass sie nur für
> einen bestimmten Zeitraum gelten und dies beide zu mitteln.
Anderer Aufwand ergibt andere Ergebnisse, womit sich dann letztlich 
wieder mal die Frage stellt:
> ob das der Aufgabe angemessen ist.

von Markus F. (Gast)


Lesenswert?

Lothar Miller schrieb:
> einem 8 Bit Wert nach gut 5 tau

Stimmt schon, aber je nach Datenbestand ist es wenig zielführend, 
Methusaleminformationen mitzuschleppen. Bei der BV kümmert es mich 
wenig, was ein Pixel 100 Werte weiter links für eine FArbe hatte.

Es gibt ausserm noch ein Problem! Auch wenn gerundet wird, entstehen 
jedesmal Fehler:

Beispiel:

Die Werte 74 und 77 bei *15/16: Die konvergieren nach wenigen Schritten 
zum selben Wertanteil in der Endsumme. Laut Excel ab Wert 37

Die Werte 73 und 77 halten dagegen ihren 6%-Abstand zu einander und 
haben kurzfristig sogar mal eine Differenz von fast 9%. Ein einziges 
Digitit führt also zu einem chaotischen Verhalten das den Endwert 
komplett anders beeinflusst. Er hilft dann dem anderen Term in der 
Endsumme mitunter in die eine oder andere Richtung gerundet zu werden, 
wodurch sich das noch verstärken kann.

Nimm Dir mal ein Excel und spiele das mal durch. Du wirst Dich wundern!

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.