Forum: FPGA, VHDL & Co. Verständnisfrage: Warum regelt mein I-Regler nicht?


von Andreas F. (andgset)


Lesenswert?

Guten Morgen lieber Mikrocontroller.net,


ich baue gerade einen I-Regler im FPGA eines Zynq. Zum besseren 
rumspielen wird der Integrierbeiwert Ki über ein Register aus einer der 
CPUs des Zynq an den FPGA übergeben, als ganzzahliger Bruch der Form Ki 
= kin_i / kid_i.

Dies hat in Variante A) Probleme gemacht, d.h. die Regelgröße hat mit 
von Ki unabhängiger Amplitude und Frequenz geschwungen, weil die 
Stellgröße fröhlich vom oberem zu unterem Anschlagpunkt hin-und-her 
gesprungen ist, und zwar selbst für lächerlich kleine Werte für Ki wie 
Ki = 1 / 1e9. Variante B funktioniert nun wie gewünscht und ist 
obendrein verständlicher geschrieben.

Mich würde nun aber brennend interessieren wo bei Variante A) das 
Problem liegt. Weiterhin ist jede Art Feedback zum Programmierstil gerne 
willkommen.
1
entity pid_controller is
2
3
    generic (
4
        wio_g                  : integer := 16;
5
        wk_g                   : integer := 32
6
    );
7
    
8
    port (
9
    --! Input clock.
10
        clk_i                   : in std_logic;
11
12
        --! Coefficients of controller.
13
        kin_i                   : in unsigned (wk_g-1 downto 0);
14
        kid_i                   : in unsigned (wk_g-1 downto 0);
15
16
        uplim_i                 : in signed (wio_g-1 downto 0);
17
        lolim_i                 : in signed (wio_g-1 downto 0);
18
           
19
        cv_o                    : out signed (wio_g-1 downto 0)
20
    ....
21
    );
22
              
23
end;
24
25
architecture Behavioral of pid_controller is
26
    signal e                 : signed (wio_g downto 0) := (others => '0');
27
    signal esum               : signed (wio_g downto 0) := (others => '0');
28
    signal cvi                : signed (wio_g-1 downto 0) := (others => '0');
29
30
begin
31
  
32
  -- Variante A)
33
  
34
    calc_i_term_proc : process (clk_i)
35
        variable esum_v: signed (wio_g+wk_g downto 0);
36
    begin
37
        if rising_edge(clk_i) then
38
            esum_v := esum + (e * signed(kin_i)) / signed(kid_i);
39
            -- Limit sum upwards.
40
        if (esum_v > uplim_i) then
41
            esum_v := (others => uplim_i(uplim_i'left));
42
            esum_v(uplim_i'left downto 0) := uplim_i;
43
            -- Limit sum downwards.
44
        elsif (esum_v < lolim_i) then
45
            esum_v := (others => lolim_i(lolim_i'left));
46
            esum_v(lolim_i'left downto 0) := lolim_i;
47
        end if;
48
        -- Handle reset....
49
        if (rst = '1') then
50
            esum <= (others => '0');
51
            cvi  <= (others => '0');
52
        -- ...or forward to port.
53
        else 
54
            esum <= esum_v(wio_g downto 0);
55
            cvi  <= esum_v(wio_g-1 downto 0);
56
        end if;
57
    end if;         
58
    end process;
59
  
60
    -- Variante B)
61
  
62
    calc_i_term_proc : process (clk_i)
63
        variable esum_v: signed (wio_g+wk_g downto 0);
64
    begin
65
        if rising_edge(clk_i) then
66
            esum_v := esum + (e * signed(kin_i)) / signed(kid_i);
67
            -- Handle reset.
68
            if (rst = '1') then
69
                esum <= (others => '0');
70
                cvi  <= (others => '0');
71
            -- Limit output...
72
           else
73
               -- ...upwards...
74
               if (esum_v > uplim_i) then
75
                   cvi <= uplim_i;
76
               -- ...and downwards...
77
               elsif (esum_v < lolim_i) then
78
                   cvi <= lolim_i;
79
               -- ...or forward to port.
80
               else
81
                   cvi <= esum_v(wio_g-1 downto 0);
82
               end if;
83
           end if;
84
       end if;         
85
    end process;
86
  
87
  ....
88
end;

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


Lesenswert?

Andreas F. schrieb:
> Dies hat in Variante A) Probleme gemacht
In der Realität und/oder in der Simulation?

> Weiterhin ist jede Art Feedback zum Programmierstil gerne willkommen.
Du meinst den Beschreibungsstil...   ;-)
1
esum_v := esum + (e * signed(kin_i)) / signed(kid_i);
Hast du mal angesehen, wie diese Division umgesetzt wird? Was wird 
daraus tatsächlich gemacht? Wäre es sinnvoller, hier eine Multiplikation 
mit dem Kehrwert der Konstanten einzufügen?

Irgendwie kommt mir sowieso das "e" im gesamten Code so verlassen und 
nutzlos vor...

> Mich würde nun aber brennend interessieren wo bei Variante A) das
> Problem liegt.
Hast du nicht einfach mal in der Simulation nachgeschaut, wo da was 
schiefgeht? Passen da evtl irgenwelche Wortbreiten nicht? Das hatten wir 
kürzlich im Beitrag "rechnen mit unsigned"

von Andreas F. (andgset)


Lesenswert?

Lothar M. schrieb:
> In der Realität und/oder in der Simulation?

Ups, völlig vergessen. Das Simulation ist völlig in Ordnung, die 
Realität leider nicht.

Lothar M. schrieb:
> Du meinst den Beschreibungsstil...   ;-)

Wollte erst Konfigurationsstil schreiben, aber Beschreibungsstil war das 
Wort das ich gesucht hatte :-)

Lothar M. schrieb:
> Hast du mal angesehen, wie diese Division umgesetzt wird?

Nein, aber die Division ist in beiden Varianten ja gleich? Ich hatte 
eher die Vermutung dass ich bei der Variablen einen Denkfehler habe...

Lothar M. schrieb:
> Irgendwie kommt mir sowieso das "e" im gesamten Code so verlassen und
> nutzlos vor..

Das e ist der Fehler zwischen Regel- und Führungsgröße und wird in einem 
separaten Prozess richtig berechnet.

Lothar M. schrieb:
> Hast du nicht einfach mal in der Simulation nachgeschaut

s.o.

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


Lesenswert?

Hast du Constraints (wenigstens auf den Takt) gesetzt und dir mal den 
RTL Schaltplan angeschaut?

von Andreas F. (andgset)


Lesenswert?

Nein, im Prinzip hab ich Vivado aufgemacht un losgelegt.. Könnten 
fehlende Constraints das Problem sein?

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


Lesenswert?

Andreas F. schrieb:
> Könnten fehlende Constraints das Problem sein?
Ja  denn wenn dein Design nur 10MHz kann, du es aber mit 50MHz taktest, 
dann passiert sowas.
Deshalb auch meine Frage, wie die Division in Hardware umgesetzt wird.

Sag der Toolchain welchen Takt verwendest oder verwenden willst, und die 
sagt dir dann, ob sie es schafft.

von Andreas F. (andgset)


Lesenswert?

Könnte es bei Timingproblemen denn helfen eine zusätzliche Pipelinestufe 
einzubauen? D.h. den Prozess in 2 aufzuteilen, und im ersten z.B. die 
Multiplikation und im 2. die Division + Begrenzung durchzuführen?

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


Lesenswert?

Andreas F. schrieb:
> Könnte es bei Timingproblemen denn helfen eine zusätzliche Pipelinestufe
> einzubauen?
Könnte schon, aber dazu muss man erst wissen, ob es einer ist, und 
wo das Problem auftritt. Dann kann man überlegen, was zu tun ist. Und 
vorab wäre wie gesagt zuallererst interessant, wie die Division 
realisiert wird.

Stichworte dazu: "Statische Timinganalyse" und "kritischer Pfad".

> D.h. den Prozess in 2 aufzuteilen, und im ersten z.B. die
> Multiplikation und im 2. die Division + Begrenzung durchzuführen?
Allein das Aufteilen des Prozesses in 2 Prozesse ergibt noch nicht 
unbedingt ein Pipelining (du bist da offenbar zu sehr 
"softwarebasiert").
Oder andersrum: ein Pipelining mitsamt dem resultierenden einen Takt 
Latency (das Wort solltest du dir gleich mal merken, das kommt später 
nochmal) bekomme ich mit weniger Schreibarbeit über ien getaktetes 
Zeischenregister auch in 1 Prozess unter.

: Bearbeitet durch Moderator
von Andreas F. (andgset)


Lesenswert?

Lothar M. schrieb:
> Oder andersrum: ein Pipelining mitsamt dem resultierenden einen Takt
> Latency (das Wort solltest du dir gleich mal merken, das kommt später
> nochmal) bekomme ich mit weniger Schreibarbeit über ien getaktetes
> Zeischenregister auch in 1 Prozess unter.

Okay das leuchtet ein. Przesse dienen nur der Strukturierung der 
Beschreibung, demnach wäre es sogar schlechter den "I-Prozess" 
auseinanderzustückeln..

Lothar M. schrieb:
> Und
> vorab wäre wie gesagt zuallererst interessant, wie die Division
> realisiert wird.

Laut "RTL Analysis" ist die Division ein RTL_DIV Block. Ich vermute 
interessanter wäre aber wie dieser Block implementiert wird oder? Wie 
finde ich jetzt aber die Implementierng in dem riesigen Wirrwarr aus 
Netzen und Blöcken im Syntheseschaltplan? Den Netzen folgen? Danke für 
deine Gedult!

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


Lesenswert?

Andreas F. schrieb:
> Wie finde ich jetzt aber die Implementierng in dem riesigen Wirrwarr
> aus Netzen und Blöcken im Syntheseschaltplan? Den Netzen folgen?
Ja. du solltest sie "sogar" wieder erkennen. Denn weil VHDL eine 
Hardwarebeschreibungssprache ist, solltest/hast du damit ja deine 
Schaltung, die du letztlich willst, beschrieben.

> Laut "RTL Analysis" ist die Division ein RTL_DIV Block.
Und in diesem Block ist dieser kombinatorische Wirrwarr? Dann hast du 
eine kombinatorische Division hingelegt. Und die ist garantiert langsam.
Siehe den Beitrag "Division in VHDL"
und den Beitrag "Rechnen mit unsigned vs. signed und einer division"

von Andreas M. (amesser)


Lesenswert?

Welcher Takt geht denn in das System rein? Ich meine die Zeitkonstanten 
für den Regler hängen ja mit dem äußeren System zusammen, müssen also 
berechnet werden. Die muss man dann noch unter Berücksichtigung des 
VHDL-Integratortaktes quantisieren.

Meine Erfahrung ist dass gerade ein zu langsam eingestellter I-Regler 
zum Schwingen des Gesamtsystems führt?

von Andreas F. (andgset)


Lesenswert?

Andreas M. schrieb:
> Ich meine die Zeitkonstanten
> für den Regler hängen ja mit dem äußeren System zusammen, müssen also
> berechnet werden. Die muss man dann noch unter Berücksichtigung des
> VHDL-Integratortaktes quantisieren.

Genau, ist in https://rn-wissen.de/wiki/index.php?title=Regelungstechnik 
ganz gern beschrieben.

Mein Problem war tatsächlich wie von Lothar vermutet eine Timing Sache. 
Die Addition, Multiplikation, Division und anschließende Limitierung 
waren einfach zu viel für einen 1 clk Zyklus. Ich habe die 
Multiplikation jetzt in eine extra Pipelinestufe gepackt (nochmal danke 
für den Hinweis mit dem Prozess) und die Divisoren zusätzlich auf Zahlen 
der Form 2^n beschränkt mittels
1
 esum_v := esum + eraw / signed(to_unsigned(2**to_integer(kid_i),wk_g));

Der Synthesizer ist so schlau, dass er die Division dann als Bitshift 
implementiert.

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


Lesenswert?

Andreas M. schrieb:
> Welcher Takt geht denn in das System rein?
Das ist auch noch eine Frage, die prinzipiell zu klären ist.
Denn das FPGA wird ja z.B. mit einer Frequenz um 50MHz getaktet. Wenn 
das zu regelnde System viel langsamer ist, dann kann man z.B. eine 
32-Bit-Division ja locker auch auf 32 FPGA-Takte "verteilen" und ist 
immer noch schnell genug.

Andreas F. schrieb:
> Der Synthesizer ist so schlau, dass er die Division dann als Bitshift
> implementiert.
Allerdings wird da nichts geschoben, sondern einfach "abgeschnitten" 
oder besser noch "ignoriert" und gar nicht weiterverdrahtet.

Allerdings ist eben nicht jede Konstante automatisch ein 
Zweierexponent...

: Bearbeitet durch Moderator
von Andreas F. (andgset)


Lesenswert?

Lothar M. schrieb:
> Allerdings wird da nichts geschoben, sondern einfach "abgeschnitten"
> oder besser noch "ignoriert" und gar nicht weiterverdrahtet.

Was das Ganze noch besser macht :-)

Lothar M. schrieb:
> Allerdings ist eben nicht jede Konstante automatisch ein
> Zweierexponent...

Ja, aber für meine Zwecke lassen sie sich hinreichend genau in der Form 
N / 2^M darstellen.

Eine Frage kommt mir gerade noch: Die "Konstanten"
1
kin_i
 und
1
kid_i
 werden ja über Ports eingelesen. Ist es grundsätzlich ratsam bzw. guter 
Stil diese direkt in Flipflops zu buffern? Schließlich weiß man ja 
zunächst nicht wie lange die kombinatorische Signalkette ist die die 
Konstante in das Modul führt.

von Duke Scarring (Gast)


Lesenswert?

Andreas F. schrieb:
> Ist es grundsätzlich ratsam bzw. guter
> Stil diese direkt in Flipflops zu buffern? Schließlich weiß man ja
> zunächst nicht wie lange die kombinatorische Signalkette ist die die
> Konstante in das Modul führt.
Ja, ich mache das so. Auch meine Ausgangssignale laufen üblicherweise 
über Register. Bei größeren Designs hat es der Router dann etwas 
leichter beim Plazieren.

Duke

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.