Verständnisfrage: Warum regelt mein I-Regler nicht?

OP #5634607
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;
Moderator (Firma: Titel) Persönliche Seite #5634684
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"
OP #5634837
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.
Moderator (Firma: Titel) Persönliche Seite #5635346
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.
Moderator (Firma: Titel) Persönliche Seite #5636060
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.
OP #5636104
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!
Moderator (Firma: Titel) Persönliche Seite #5636150
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"
#5636280
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?
OP #5636326
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.
Moderator (Firma: Titel) Persönliche Seite #5636330
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...
OP #5637630
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.
Gast #5638282
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

Antwort schreiben

Eine eigene Frage? Dieser Thread ist über 7 Jahre alt. Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.

Neuen Thread starten

Eine Lösung, Ergänzung oder Rückmeldung zu diesem Thread? Dann antworte hier.

Formatierung
  • [c]C-Code[/c]
  • [vhdl]VHDL-Code[/vhdl]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] – Link zu einem Artikel
  • Verweis auf einen anderen Beitrag: Rechtsklick auf dessen Beitrags-ID (#8078628), „Adresse kopieren“, in den Text einfügen

Mehr zur Formatierung

Längeren Sourcecode als Anhang, nicht in den Text. JPG nur für Fotos und Scans; Zeichnungen und Screenshots als PNG oder GIF. Siehe Bildformate.

Schon registriert? Anmelden

Wird nicht angezeigt. Wir schicken dir einen Link; sobald du damit bestätigst, erscheint dein Beitrag.

Mit dem Abschicken legst du einen kostenlosen Account an und erkennst die Nutzungsbedingungen und die Datenschutzerklärung an.