Forum: FPGA, VHDL & Co. CIC Additionsknoten


von Marcel D. (diablokiller999)


Angehängte Dateien:

Lesenswert?

Hi Leutz!
Ich habe einen CIC Filter in VHDL implementiert, der an sich auch ganz 
gut funktioniert. Nur habe ich mir die Frage gestellt, ob die 
Implementierung richtig ist oder ich einen Denkfehler im Ablauf der 
Addition habe.

In der angehängten Zeichnung sieht man die Additionsknoten und die 
Register / Verzögerer. Momentan habe ich es so implementiert, dass ich 
die Additionen in einem Takt gemeinsam abarbeite, womit mir das aktuelle 
Ergebnis des vorherigen Knotens also nicht für die Berechnung des 
nachfolgenden Knotens zur Verfügung steht.
1
Integrator : process(nReset, iClk)
2
begin
3
  if (nReset = '0') then
4
    nCICTemp        <= (others => '0');
5
    nCICExpand      <= (others => '0');
6
    nIntegration    <= (others => (others => '0'));
7
    nNewValue       <= '0';
8
    nCount          <= x"000000";
9
    nNewValue      <= '0';  
10
    nDecTrigger     <= '0';   
11
    stateInt        <= idle;
12
  elsif (iClk = '1' and iClk'event) then
13
    nNewValue <= iNewValue;       
14
    case stateInt is
15
      when idle =>
16
                nDecTrigger <= '0';
17
        if nNewValue /= iNewValue and iNewValue = '0' then --trigger on the falling edge of iNewValue                      
18
                    stateInt    <= get_value;  
19
                end if;
20
                
21
            when get_value =>
22
                if (nShift = 0) then
23
                    stateInt    <= idle;  
24
                else                    
25
                    if (INPUT_SIGNED = 1) then
26
                        nCICTemp <= signed(iCIC);
27
                    else
28
                        nCICTemp <= signed(iCIC + x"8000");
29
                    end if;
30
                    stateInt    <= fill;            
31
                end if;
32
            when fill =>
33
               stateInt <= int1;
34
               nCICExpand  <= (others => nCICTemp(nCICTemp'left));
35
               nCICExpand(BIT_WIDTH-1 downto 0) <= signed(nCICTemp);   
36
               nIntegration(0) <= nCICExpand + nIntegration(0);
37
               nIntegration(1) <= nIntegration(0) + nIntegration(1); 
38
               nIntegration(2) <= nIntegration(1) + nIntegration(2);
39
               nIntegration(3) <= nIntegration(2) + nIntegration(3);
40
               nCOUNT <= nCount + '1';
41
               if nCOUNT = nDecimationFactor - '1' then
42
                  nCOUNT <= x"000000";
43
                  nDecTrigger <= '1';
44
               end if;
45
           when others =>
46
               stateInt <= idle;
47
    end case;
48
  end if;
49
end process Integrator;
50
51
Comb : process(nReset, iClk)
52
begin
53
    if (rising_edge(iClk)) then
54
        if (nReset = '0') then
55
            nCombIn         <= (others => (others => '0'));
56
            nCombDelay      <= (others => (others => '0'));
57
            nCombOut        <= (others => '0');
58
            stateComb       <= idle;
59
            oNewValue       <= '0';
60
            oCIC            <= (others => '0');
61
        else
62
            case stateComb is
63
                when idle =>
64
                    if (nShift = 0) then
65
                        oCIC <= iCIC;
66
                        oNewValue <= iNewValue;
67
                    else 
68
                        oNewValue <= '0';
69
                        if(nDecTrigger = '1') then
70
                            stateComb <= calc1;
71
                        end if;
72
                    end if;
73
                when calc1 =>
74
                    nCombIn(0) <= nIntegration(3);
75
                    nCombIn(1) <= nCombIn(0) - nCombDelay(0);
76
                    nCombDelay(0) <= nCombIn(0);
77
                    nCombIn(2) <= nCombIn(1) - nCombDelay(1);
78
                    nCombDelay(1) <= nCombIn(1);
79
                    nCombIn(3) <= nCombIn(2) - nCombDelay(2);
80
                    nCombDelay(2) <= nCombIn(2);
81
                    stateComb <= output;
82
                    nCombOut <= nCombIn(3) - nCombDelay(3);
83
                    nCombDelay(3) <= nCombIn(3);
84
                when output =>
85
                    stateComb <= idle;
86
                    oNewValue <= '1';
87
                    if (INPUT_SIGNED = 1) then
88
                        oCIC <= std_logic_vector(nCombOut(BIT_WIDTH-1+nShift downto nShift));
89
                    else
90
                        oCIC <= std_logic_vector(nCombOut(BIT_WIDTH-1+nShift downto nShift))- x"8000";
91
                    end if;
92
                when others =>
93
                    stateComb <= idle;
94
            end case;
95
        end if;
96
    end if;
97
end process Comb;

Alternativ wäre es aber auch denkbar, die Berechnung der Additionsknoten 
in je einem Takt zu bearbeiten und somit das Ergebnis dem nachfolgenden 
Additionsknoten zur Verfügung zu stellen, dieser muss dann nicht aufs 
nächste Sample warten um den neuen Wert zu verrechnen:
1
Integrator : process(nReset, iClk)
2
begin
3
  if (nReset = '0') then
4
    nCICTemp        <= (others => '0');
5
    nCICExpand      <= (others => '0');
6
    nIntegration    <= (others => (others => '0'));
7
    nNewValue       <= '0';
8
    nCount          <= x"000000";
9
    nNewValue      <= '0';  
10
    nDecTrigger     <= '0';   
11
    stateInt        <= idle;
12
  elsif (iClk = '1' and iClk'event) then
13
    nNewValue <= iNewValue;       
14
    case stateInt is
15
      when idle =>
16
                nDecTrigger <= '0';
17
        if nNewValue /= iNewValue and iNewValue = '0' then --trigger on the falling edge of iNewValue                      
18
                    stateInt    <= get_value;  
19
                end if;
20
                
21
            when get_value =>
22
                if (nShift = 0) then
23
                    stateInt    <= idle;  
24
                else                    
25
                    if (INPUT_SIGNED = 1) then
26
                        nCICTemp <= signed(iCIC);
27
                    else
28
                        nCICTemp <= signed(iCIC + x"8000");
29
                    end if;
30
                    stateInt    <= fill;            
31
                end if;
32
            when fill =>
33
                stateInt <= int1;
34
                nCICExpand  <= (others => nCICTemp(nCICTemp'left));
35
        nCICExpand(BIT_WIDTH-1 downto 0) <= signed(nCICTemp);   
36
      when int1 =>
37
        stateInt <= int2;
38
        nIntegration(0) <= nCICExpand + nIntegration(0);        
39
      when int2 =>    
40
         stateInt <= int3;
41
         nIntegration(1) <= nIntegration(0) + nIntegration(1); 
42
      when int3 => 
43
         stateInt <= int4;  
44
         nIntegration(2) <= nIntegration(1) + nIntegration(2);
45
      when int4 =>
46
         stateInt <= idle;
47
         nIntegration(3) <= nIntegration(2) + nIntegration(3);
48
         nCOUNT <= nCount + '1';
49
         if nCOUNT = nDecimationFactor - '1' then
50
             nCOUNT <= x"000000";
51
             nDecTrigger <= '1';
52
         end if;
53
      when others =>
54
        stateInt <= idle;
55
    end case;
56
  end if;
57
end process Integrator;
58
59
Comb : process(nReset, iClk)
60
begin
61
    if (rising_edge(iClk)) then
62
        if (nReset = '0') then
63
            
64
            nCombIn         <= (others => (others => '0'));
65
            nCombDelay      <= (others => (others => '0'));
66
            nCombOut        <= (others => '0');
67
            stateComb       <= idle;
68
            oNewValue       <= '0';
69
            oCIC            <= (others => '0');
70
        else
71
            case stateComb is
72
                when idle =>
73
                    if (nShift = 0) then
74
                        oCIC <= iCIC;
75
                        oNewValue <= iNewValue;
76
                    else 
77
                        oNewValue <= '0';
78
                        if(nDecTrigger = '1') then
79
                            nCombIn(0) <= nIntegration(3);
80
                            stateComb <= calc1;
81
                        end if;
82
                    end if;
83
                when calc1 =>
84
                    stateComb <= calc2;
85
                    nCombIn(1) <= nCombIn(0) - nCombDelay(0);
86
                    nCombDelay(0) <= nCombIn(0);
87
                when calc2 =>
88
                    stateComb <= calc3;
89
                    nCombIn(2) <= nCombIn(1) - nCombDelay(1);
90
                    nCombDelay(1) <= nCombIn(1);
91
                when calc3 =>
92
                    stateComb <= calc4;
93
                    nCombIn(3) <= nCombIn(2) - nCombDelay(2);
94
                    nCombDelay(2) <= nCombIn(2);
95
                when calc4 =>
96
                    stateComb <= output;
97
                    nCombOut <= nCombIn(3) - nCombDelay(3);
98
                    nCombDelay(3) <= nCombIn(3);
99
                when output =>
100
                    stateComb <= idle;
101
                    oNewValue <= '1';
102
                    if (INPUT_SIGNED = 1) then
103
                        oCIC <= std_logic_vector(nCombOut(BIT_WIDTH-1+nShift downto nShift));
104
                    else
105
                        oCIC <= std_logic_vector(nCombOut(BIT_WIDTH-1+nShift downto nShift))- x"8000";
106
                    end if;
107
                when others =>
108
                    stateComb <= idle;
109
            end case;
110
        end if;
111
    end if;
112
end process Comb;

Nun ist jedoch die Frage, welche Herangehensweise richtig ist. Oder 
macht das keinen Unterschied?

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


Lesenswert?

Marcel D. schrieb:
> Nun ist jedoch die Frage, welche Herangehensweise richtig ist.
> Oder macht das keinen Unterschied?
Die zweite Lösung ist halt generell langsamer, weil ja jeder Schritt 
nacheinander gemacht wird.

Im Resourcenverbrauch wirst du durch die zusätzliche FSM im zweiten Fall 
auch ein wenig schlechter, denn du brauchst ja trotzdem alle einzelnen 
Addierer. Nur tun die eben die meiste Zeit nichts...

> Nun ist jedoch die Frage, welche Herangehensweise richtig ist.
Mach die Nummer 1. Und wenn die zu langsam ist, dann denk über Pipelinig 
nach. Das funktioniert aber nicht wie die Lösung 2, sondern eher wie die 
Lösung 1 mit zwischengeschalteten Puffern.

von Martin O. (ossi-2)


Lesenswert?

Wenn man das CIC-Filter so implementiert, wie in Deinem Bild des ersten 
Posts gezeigt, liegen (was die Signalverzögerung angeht) alle Addierer 
in Reihe. Wenn Du die Addierer 32 Bit breit machst und M=3 stufig 
arbeitest sind das 32*6=192 Bits, und alle Additionen müssen in einem 
Takt des CIC Filters erledigt sein. Das kann schnell problematisch 
werden.

Wenn es nichts ausmacht, dass das Signal 2M=6 Takte verzögert am Ausgang 
rauskommt, kannst Du so arbeiten, wie von Dir vorgeschlagen, und die 
jeweils nächste Stufe bekommt das Resultat der vorherigen Stufe einen 
Takt später. Das entspricht dem Einbau von z^-1 Gliedern hinter jeder 
Stufe.

Dein Bild zeigt übrigens nicht die übliche Form eines CIC Filters.
Normal erfolgt das Downsampling um R zwischen den Integrate und den Comb 
Anteilen. Dann braucht man die Comb Filter auch nur seltener berechnen.

Ich blick durch Deine Implementation nicht wirklich durch, ich habe den 
Eindruck, dass Du mit Hilfe von State-Maschinen Schritte in der 
Reihenfolge festlegen willst.

Hier Meine Implementation mit N=2, D=1, R einstellbar.
1
module cicTwoStageV01
2
 #(parameter width=16) 
3
 (input                  cic_clk_i       ,
4
  input                  inp_strobe_i    , // Integrate Taktfreigabe
5
  input                  out_strobe_i    , // Comb Taktfreigabe
6
  input signed [width-1:0]      inp_data_i      ,  
7
  output signed [width-1:0]     out_data_o
8
  );
9
10
11
reg signed [width-1:0] CICint1 ;
12
reg signed [width-1:0] CICint2 ;
13
always @(posedge cic_clk_i) begin
14
  if ( inp_strobe_i ) begin  CICint1 <= CICint1 + inp_data_i ;  end 
15
  end
16
17
always @(posedge cic_clk_i) begin
18
  if (  inp_strobe_i ) begin  CICint2 <= CICint2 + CICint1 ;   end
19
  end
20
21
reg signed [width-1:0] CICstore1a ;
22
reg signed [width-1:0] CICstore1b ; 
23
reg signed [width-1:0] CICdiff1 ;  
24
reg signed [width-1:0] CICstore2a ;
25
reg signed [width-1:0] CICstore2b ; 
26
reg signed [width-1:0] CICdiff2 ;  
27
28
always @(posedge cic_clk_i) begin  
29
  if ( out_strobe_i ) begin
30
    CICdiff1 <= CICint2-CICstore1b ;
31
    CICstore1b <= CICstore1a ;
32
    CICstore1a <= CICint2 ;
33
    end
34
  end
35
  
36
always @(posedge cic_clk_i) begin  
37
  if ( out_strobe_i ) begin
38
    CICdiff2 <= CICdiff1-CICstore2b ;
39
    CICstore2b <= CICstore2a ;
40
    CICstore2a <= CICdiff1 ;
41
    end
42
  end
43
44
assign out_data_o = CICdiff2 ; 
45
46
endmodule

: Bearbeitet durch Moderator
von Marcel D. (diablokiller999)


Angehängte Dateien:

Lesenswert?

Mir ging es eher um eine allgemeine Frage über die Berechnung und ob 
diese zeitgleich an allen Knoten oder hintereinander durchgeführt werden 
muss. In meiner Implementierung sind die nCombIn die Subtraktionsknoten, 
die nCombDelay die vorwärts gerichteten Verzögerungselemente. Ich nutze 
3 Prozesse um die Config, Integrator und Comb parallel ablaufen zu 
lassen. Das ganze Teil soll ein 4 Stage CIC zur Dezimierung sein, den 
kompletten Code packe ich mal als Anhang bei.

Die Grundüberlegung war die, dass die Ergebnisse der Berechnung bei 
einzelnen SM-Stages ja weitaus schneller passieren, als sie eigentlich 
angedacht sind. Beim FIR oder IIR werden ja ebenfalls alle 
Multiplikationen und Additionen parallel ausgeführt, deswegen auch meine 
beiden Implementierungen. Funktionieren tun sie beide, nur stellt sich 
mir die Frage was richtig(er) ist :3

von Marcel D. (diablokiller999)


Angehängte Dateien:

Lesenswert?

Ich gehe momentan davon aus, dass diese Implementierung (#2) die 
richtige ist. Bei einem Dezimationsfaktor von 4 bekomme ich nach 4 
Werten eine Änderung am Ausgang, in der anderen Variante erst nach 20.

von Weltbester FPGA-Pongo (Gast)


Lesenswert?

Wozu brauchst Du hier eine State Machine?
Ein CIC hat seinen Vorteil einzig in der resourcenschonenden 
Implementierung. Addieren, Differenzieren. Alle Verrenkungen drum herum 
gehören da raus und blähen nur auf.

von Marcel D. (diablokiller999)


Lesenswert?

Also alles in einzelne Prozesse auslagern wie Ossi es gemacht hat?

von Weltbester FPGA-Pongo (Gast)


Lesenswert?

Marcel D. schrieb:
> Also alles in einzelne Prozesse auslagern wie Ossi es gemacht hat?

Ganz sicher nicht. Wozu? Ist Dir klar, was Prozesse sind und wie sie 
wirken? Das Verlagern von Code in unterschiedliche Prozesse hat 
ergebnistechnisch in der Regel gar keine Wirkung und wenn, dann keine 
gute.

Bläht nur den Code auf.

Wir sind aber an einer anderen Stelle, nämlich dem Aufblähen der 
Funktion!

Daher nochmals meine Frage: Wozu eine state machine und ein stückweises 
Berechnen mit den calc stages?

Der Code zeigt mir wieder mal, dass hier nicht parallel gedacht wird 
sondern eine Implementierung eines sequenziellen Ablaufs wie man ihn in 
C laufen lassen müsste, übersetzt wurde.

Softwareentwickler sollten sich von FPGAs fernhalten.

von Marcel D. (diablokiller999)


Lesenswert?

Weltbester FPGA-Pongo schrieb im Beitrag #5325604:
> Daher nochmals meine Frage: Wozu eine state machine und ein stückweises
> Berechnen mit den calc stages?

Um sicherzustellen, dass die Berechnung des vorherigen Knotens 
abgeschlossen ist und ich das Ergebnis der nachfolgenden Einheit zur 
Verfügung stellen kann, das ganze in einer getakteten Umgebung weil ich 
asynchrones Verhalten vermeiden will? Ich habe 10 Takte für die 
Berechnung bis ein neuer Wert kommt und mit dem as fast as possible 
Ansatz bin ich beim fitten leider schon auf die Schnauze gefallen.

Weltbester FPGA-Pongo schrieb im Beitrag #5325604:
> Der Code zeigt mir wieder mal, dass hier nicht parallel gedacht wird
> sondern eine Implementierung eines sequenziellen Ablaufs wie man ihn in
> C laufen lassen müsste, übersetzt wurde.

Für mich sieht der Aufbau eines CICs ziemlich seriell aus, könntest es 
mir ja erklären.

Weltbester FPGA-Pongo schrieb im Beitrag #5325604:
> Softwareentwickler sollten sich von FPGAs fernhalten.
Danke für diese Offenbarung, werde sofort meinen Job kündigen und alle 
Leute in meiner Umgebung warnen, niemals mit FPGAs anzufangen wenn sie 
schon mal eine Programmiersprache abseits von VHDL oder Verilog 
nutzten...

: Bearbeitet durch User
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.