www.mikrocontroller.net

Forum: FPGA, VHDL & Co. Wie zwei Taktsignale in einem Prozess verarbeiten?


Autor: Peterchen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich habe einen Zähler, der zwei Inputs hat, einen zum vorwärts- und 
einen zum Rückwärtszählen. Getrennt klappt alles wunderbar, aber sobald 
ich beide Inputs auswerten will, geht nichts mehr.
process (a,b)
  begin
  if (rising_edge(a)) then
  ...
  end if;
  if (rising_edge(b)) then
  ...
  end if;
end process

Dazu bekomme ich die Fehlermeldung "Bad synchronous description"

Und sowas
signal c: std_logic;
...
c <= a or b;
...
process (a,b)
  begin
  if (rising_edge(c)) then
     if (a = '1') then
     ...  -- a ging "high"
     else
     ...  -- b ging "high"
     end if;
  ...
  end if;
end process

Funktioniert auch nicht. Da geht er scheinbar immer in den else-Zweig.

Versuche ich es mit zwei Prozessen, kann ich den Output-Vector nicht 
beschreiben. Fehlermeldung "Multiple Drivers ...."

Wie kann man sowas in den Griff kriegen?

[Ich habe einen CoolRunner CPLD und das XST von Xilinx]

Danke!!!
 Peterchen

Autor: Jan M. (mueschel)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sowas ist mit der vorhandenen Logik in FPGAs & CPLDs einfach nicht 
moeglich.
Sind a und b wirklich echte Clocks aus einem Oszillator oder doch nur 
irgendwelche Signale?

Autor: Peterchen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Sind a und b wirklich echte Clocks aus einem Oszillator oder doch nur
> irgendwelche Signale?
Es sind echte Clocks, runtergeteilt von einem 1 MHz Oszillator, die über 
zwei Tasten ein- und ausgeschaltet werden können (mit einer 
UND-Verknüpfung).

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sehen wir uns das mal in Hardware an:
Mit
>    c <= a or b;
generierst du eine Flanke.
Und auf dieselbe Flanke fragst du den Zustand der Signale ab. Die FF im 
CPLD haben an der steigenden Flanke aber noch gar nichts vom 
Pegelwechsel mitbekommen und sehen immer noch die '0'. Deshalb wird 'a' 
an einer steigenden Flanke von 'a' immer '0' sein.


Was sollte/würde denn deiner Meinung nach passieren,
wenn beide Signale gleichzeitig den Zustand von 0 nach 1 wechseln?

Autor: Jan M. (mueschel)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Runtergeteilt? Mit einem Zaehler?

Normalerweise erzeugt man nicht zwei neue Takte, sondern ein 
Clock-Enable-Signal, und taktet die Logik weiterhin mit dem Takt aus dem 
Oszillator.
Dazu gibts hier in der Artikelsammlung auch die passenden 
Erlaeuterungen, siehe Taktung FPGA/CPLD

Autor: Schrotty (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das geht so nicht! Du musst dir immer vorstellen, dass du das auch in 
Hardware diskret aufbauen würdest. Und da hast auch kein Flipflop mit 2 
Takteingängen.

Ich würd es so lösen:

Systemtakt auf Zähler und dann zwei Stuerleitungen, die die Zählrichtung 
angeben. Wenn du das so machst, dann zählt er so lange rauf oder runter, 
wie du deine Taste gedrückt hältst.
Wenn du bei jedem Tastendruck nur ein Inkrement zählen willst, dann 
musst du dir ne Flankenerkennung für die Tasten bauen. Das geht recht 
einfach, indem du das Tastensignal durch zwei Register schiebst (die 
auch mit dem Systemtakt getaktet sind) und dann die beiden Register 
miteinander verlgeichst. (ist das eine High und das andere noch Low, 
dann hast z.B. ne steigende Flanke am Eingang)

Autor: Peterchen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Mit
>>    c <= a or b;
>generierst du eine Flanke.

Ich dachte ein Wechsel von a oder b von 0 nach 1 würde eine Flanke 
generieren. ist das nicht so?

> Was sollte/würde denn deiner Meinung nach passieren,
> wenn beide Signale gleichzeitig den Zustand von 0 nach 1 wechseln?
Dann sollte c auch von 0 nach 1 wechseln.

> Normalerweise erzeugt man nicht zwei neue Takte, sondern ein
> Clock-Enable-Signal, und taktet die Logik weiterhin mit dem Takt aus dem
> Oszillator.
Es ist der gleiche Takt, nur auf 2 verschiedenen Leitungen, den ich 
getrennt über 2 Taster zuführen kann.
Hintergrund: ich möchte mit der einen Taste ein 7-Segment Display 
hochzählen und mit der anderen soll es runterzählen.

Autor: Peterchen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Ich würd es so lösen:
> Systemtakt auf Zähler und dann zwei Stuerleitungen, die die Zählrichtung
> angeben. Wenn du das so machst, dann zählt er so lange rauf oder runter,
> wie du deine Taste gedrückt hältst.
Danke, das ist eine gute Idee. Das werde ich so machen!

Autor: Peterchen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ach, was mir noch einfällt: meine Zähler für die 7-segment Displays (es 
sind insgesmt 4) haben Ausgänge für Über- und Unterlauf, um das 
benachbarte Display anzutriggern. Wenn ich jetzt alle Zähler 
taktsynchron laufen lasse, dann müssen diese Ausgangssignale lang genug 
sein, aber auch nicht zu kurz, sonst würde das benachbarte Display gar 
nicht, oder zu weit zählen. Kann das zu Problemen führen?

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Ach, was mir noch einfällt: meine Zähler für die 7-segment Displays (es
> sind insgesmt 4) haben Ausgänge für Über- und Unterlauf, um das
> benachbarte Display anzutriggern.
Was willst du denn in welchem Coolrunner-CPLD machen?
Eine Art Ereignis-Zähler? Welche Zählfrequenz?
Welche 74xx-ICs würdest du dafür nehmen?
Hast du einen globalen Takt mit deutlich höherer Frequenz (z.B. 4MHz) 
als deine Ereignisse (z.B. 1MHz)?

Autor: Peterchen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Was willst du denn in welchem Coolrunner-CPLD machen?
Mal sehen. Erst mal ist es nur ein Spielzeug, um zu sehen, was man mit 
programmierbarer Logik alles anstellen kann.

> Hast du einen globalen Takt mit deutlich höherer Frequenz (z.B. 4MHz)
> als deine Ereignisse (z.B. 1MHz)?
Ich habe einen globalen Takt vom 1Mhz, den habe ich z.B. runtergeteilt 
um die die Displays zu multiplexen. Bei 1Mhz leuchten die LEDs zu lange 
nach. Dann hatte ich ihn nochmal runtergeteilt, für die beiden Up/Down 
Clocks für die Zähler. Eigentlich ist alles Taktgetrieben. 
Rückkopplungen und Schleifen sind doof, das habe ich schon gelernt.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Eigentlich ist alles Taktgetrieben.
Gut, dann brauchst du keine Flankenerkennung, weil an dieser Stelle 
eigentlich nichts von "aussen" ins CPLD hineinkommt. Du machst also 
"nur" Zähler, die auf Enable-Signale der vorhergehenden Stufe reagieren. 
Jedes dieser Enable-Signale darf dann natürlich auch nur 1 Takt lang 
aktiv sein.

Hier mal nur die Einer-Zählerstufe:
LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;
:
signal up1  : std_logic := '0'; -- Einer hochzählen
signal up1  : std_logic := '0'; -- Einer runterzählen
signal cnt1 : unsigned(3 downto 0) := 0;

signal up10  : std_logic := '0'; -- Zehner hochzählen
signal dn10  : std_logic := '0'; -- Zehner runterzählen
signal cnt10 : unsigned(3 downto 0) := 0;
:
: 
process begin
   wait until rising_edge(clk); -- auf die nächste Taktflanke warten
   dn10 <= '0';             -- Defaultwert Enable=0
   if (dn1='1') then        -- herunterzählen ist gewünscht
      if (cnt1="0000") then -- Zählerstand = 0 --> Unterlauf
         cnt1 <= "1001";    -- laden mit 9
         dn10 <= '1';       -- Enable aktivieren
      else
         cnt1 <= cnt1 - 1;  -- sonst runterzählen
      end if;
   end if;

   up10 <= '0';             -- Defaultwert Enable=0
   if (up1='1') then        -- hochzählen ist gewünscht
      if (cnt1="1001") then -- Zählerstand = 9 --> Überlauf
         cnt1 <= "0000";    -- laden mit 0
         up10 <= '1';       -- Enable aktivieren
      else
         cnt1 <= cnt1 + 1;  -- sonst hochzählen
      end if;
   end if;
end process;


Diese Zeile:
   wait until rising_edge(clk); -- auf die nächste Taktflanke warten
sorgt dafür, dass das Genze garantiert ein synchrones Design wird. Sie 
ersetzt die Sensitivity-List komplett, und deshalb kann es auch keine 
unvollständige Sens-List mehr geben.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Eigentlich ist alles Taktgetrieben.
Du verstehst unter "taktgetrieben" aber schon, dass es im ganzen Design 
nur diesen einen 1MHz-Takt gibt? Das wäre ein synchrones Design.

Alles Andere wäre Käse (auf jeden Fall für einen Anfänger... ;-)

Wenn du mehrere Takte hast, hast du ein asynchrones Design.
Dann kann ich dir nur die Daumen drücken... :-(

Autor: Peterchen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Wenn du mehrere Takte hast, hast du ein asynchrones Design.
> Dann kann ich dir nur die Daumen drücken... :-(
Ich habe mehrere Takte, aber alle sind von der 1Mhz "main"-Clock 
abgeleitet. Das ist dann wohl auch ein synchrones Design.

Übrigens, Danke für deinen Code. Leider habe ich ihn erst jetzt gesehen 
und  gestern Abend habe ich mir schon was gebaut, das prima 
funktioniert. Das sieht so aus:
LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;

ENTITY count_10 IS
  PORT
  (
    up_enable : in std_logic;               -- count down input
    down_enable : in std_logic;              -- count up input   
    clk : in std_logic;                  -- system clock
    value : out std_logic_vector (3 downto 0);   -- output value
    ov : out std_logic;                   -- overflow signal
    uv : out std_logic                    -- underflow signal
  );
END count_10;

ARCHITECTURE synth1 OF count_10 IS
  BEGIN

  process (clk, up_enable, down_enable)
  type state_t is (idle, ovset, uvset);
  variable counter : unsigned(3 downto 0);
  variable state : state_t;
  begin
    if (rising_edge(clk)) then                  -- system clock goes high
      case state is
        
        when idle => if (up_enable = '1') then      -- count up
                  counter := counter + 1;
                  if (counter = "1010") then    -- overflow?
                    counter := "0000";      -- reset counter
                    ov <= '1';            -- set overflow signal
                    state := ovset;    
                  end if;
                 end if;
                 if (down_enable = '1') then    -- count down
                  counter := counter - 1;      
                  if (counter = "1111") then    -- underflow?
                    counter := "1001";      -- reset counter
                    uv <= '1';            -- set underflow signal
                    state := uvset;
                  end if;
                 end if;
        
        when ovset => ov <= '0';              -- reset overflow signal on next clock cycle
                  state := idle;
                  
        when uvset => uv <= '0';              -- reset underflow signal on next clock cycle
                  state := idle;
      end case;
    end if;
    value <= std_logic_vector(counter);            -- send counter value to parent
  end process;
  
END synth1;
[Das Ganze sieht hier etwas zerfetzt aus. Ich hoffe ihr könnt es 
trotzdem lesen]

ov und uv sind die beiden Impulse, die das benachbarte 7-Segment Display 
ansteuern, um entweder rauf- oder runterzuzählen. Sie werden dort als 
up_enable und down_enable benutzt. Beim ersten Segment kommen up_enable 
und down_enable entweder direkt von zwei Tasten (=Zählgeschwindigkeit 
abhängig von clk), oder von einer anderen entity, die aus einem 
Tastendruck einen Einzelimpuls mach (=pro Tatendruck wird eins rauf- 
oder runtergezählt).

Das ganze funktioniert zwar prächtig, aber vielleicht habt ihr trotzdem 
ein paar Verbesserungsvorschläge für mich...?

Ach ja: großes Lob an Euch. Das Forum ist spitze!

  Peterchen

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das Problem an deinem Code ist, wenn ein up oder down-Impuls in den 
ovset odr uvset-Zuständen kommt, dann wird er nicht gezählt. Denn diese 
Zählimpulse sind ja per Definiton nur 1 Taktzyklus lang gültig.

Mein Vorschlag: Lass die Zustände ganz raus.
    if (rising_edge(clk)) then                  -- system clock goes high
         ov <= '0';            -- Default inaktiv
         uv <= '0';            -- Default inaktiv
         if (up_enable = '1') then      -- count up
             counter := counter + 1;
             if (counter = "1010") then    -- overflow?
                 counter := "0000";      -- reset counter
                 ov <= '1';            -- set overflow signal
             end if;
         end if;
         if (down_enable = '1') then    -- count down
             counter := counter - 1;      
             if (counter = "1111") then    -- underflow?
                counter := "1001";      -- reset counter
                uv <= '1';            -- set underflow signal
             end if;
         end if;
      end if; -- clk
Auch so sind ov und uv jeweils genau 1 Takt lang aktiv.


          if (counter = "1010") then    -- overflow?
          :
          if (counter = "1111") then    -- underflow?
Damit beschreibst du eigentlich einen Glitch, denn der counter muß nach 
deiner Definition erst einen ungültigen Zustand (10 oder -1) erreichen, 
bevor du was machst.
Besser wäre, die verbotenen Zustände nie zu erreichen:
   
         :
         if (up_enable = '1') then       -- count up
             if (counter = "1001") then  -- max cnt erreicht?
                 counter := "0000";           -- ja: reset counter
                 ov <= '1';                   -- set overflow signal
             else                        -- sonst hochzählen
                 counter := counter + 1;
             end if;
         end if;
         :


In meinen Augen ein Schönheitsfehler:
Deine Variablen counter und state sind speichernd, das kann u.U. 
verwirrend sein.

Autor: Peterchen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Mein Vorschlag: Lass die Zustände ganz raus.
Danke für den Code. Das werde ich mal so probieren.

> Damit beschreibst du eigentlich einen Glitch, denn der counter muß nach
> deiner Definition erst einen ungültigen Zustand (10 oder -1) erreichen,
> bevor du was machst.
Schon, aber der Glitch kommt im Ausgangsvektor ja nicht an, weil die 
Variable erst am Ende des Prozesses rüberkopiert wird. Wäre es so, dann 
würde ich bei  geringer Taktfrequenz z.B. ein A sehen, was aber nicht 
passiert.

Ach ja: bei langsamer Tasktfrequenz ist übrigens erst eine Änderung von 
9 nach 0 sichtbar, bevor das Nachbar-Display (im nächsten Takt) 
weitergezählt wird. Das fällt bei hoher Frequenz nicht auf, ist aber 
trotzdem unschön. Gibt es dafür eine Erklärung? Eigentlich reagiert das 
Design doch auf die "Rising Edge", d.h. beide Displays müssten sich 
gleichzeitig ändern. Aber vielleicht habe ich an anderer Stelle noch 
einen Fehler drin.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Gibt es dafür eine Erklärung? Eigentlich reagiert das Design doch auf
> die "Rising Edge", d.h. beide Displays müssten sich gleichzeitig ändern.
Sieh dir deinen Code an.
Erst zählst du die niedrigere Stelle hoch, und wenn es einen Über- oder 
Unterlauf gibt, dann gibst du das Signal an die nächsthöhere Stelle. Und 
erst mit dem nächsten Takt kann diese Stelle den Übertrag auswerten.

Dein Übertrag ist quasi immer einen Taktzyklus zu spät :-/
Das nennt sich Latency.

Ein Tipp: mach dein Design so, dass der Systemtakt z.B. immer 1 MHz ist. 
Dann siehst du die Latency nicht mehr (weil ja nur noch 1 us Verzögerung 
zwischen den Stellen ist).

BTW
Dein Design ist nicht so richtig synchron, denn du taktest es offenbar 
mit dem Zähltakt.
Synchron wäre es, wenn du (wie z.B. ein uC) sehr schnell intern 
arbeitest, und alle externen Signale (einschliesslich deinem Zähltakt) 
mit dieser internen Taktfrequenz bearbeitest.


EDIT
Das wäre ein Würg-Around für das Latency-Problem:
         ov <= '0';            -- Default inaktiv
         :
         if (up_enable = '1') then       -- count up
             if (counter = "1000") then  -- max cnt-1 erreicht?
                 ov <= '1';                   -- set overflow signal
             end if;
             if (counter = "1001") then  -- max cnt erreicht?
                 counter := "0000";           -- ja: reset counter
             else                        -- sonst hochzählen
                 counter := counter + 1;
             end if;
         end if;
         :
Hier wird praktisch der Übertrag einen Takt zu früh gesetzt, damit wird 
die Latency umgangen. Aber das ist schon fast hässlich... :-/

Autor: Peterchen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Und erst mit dem nächsten Takt kann diese Stelle den Übertrag auswerten.
Das war der entscheidende Hinweis! Vielen Dank noch mal.

Autor: Peterchen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hier ist meine endgültige Version. Der Prozess braucht noch nicht mal 
eigene Signale oder Variablen:
LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;

ENTITY count_10 IS
  PORT
  (
    up_enable : in std_logic;                 -- count down input
    down_enable : in std_logic;                -- count up input   
    clk : in std_logic;                    -- system clock
    value : buffer std_logic_vector (3 downto 0) := "0000";   -- output value
    ov : out std_logic;                     -- overflow signal
    uv : out std_logic                      -- underflow signal
  );
END count_10;

ARCHITECTURE synth1 OF count_10 IS

  BEGIN

  process 
  begin
    wait until clk = '1';
    uv <= '0';
    ov <= '0';
    if (up_enable = '1') then
      if (value = "1001") then
        value <= "0000";
        ov <= '1';
      else
        value <= std_logic_vector(unsigned(value) + 1);
      end if;
    end if;
    if (down_enable = '1') then
      if (value = "0000") then
        value <= "1001";
        uv <= '1';
      else
        value <= std_logic_vector(unsigned(value) - 1);
      end if;
    end if;
  end process;
  
END synth1;


Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Der Prozess braucht noch nicht mal eigene Signale oder Variablen...
Ja, schon aber dafür einen buffer.
Leg für den Zähler besser ein Signal an, das du dem Ausgang dann 
zuweist. Eben so, wie es der Rest der Welt auch macht  ;-)

>  wait until clk = '1';
Krass. Das habe ich jetzt auch noch nie gesehen :-o
Gibt das wirklich einen getakteten Prozess?
Formal korrekt wäre das:
wait until rising_edge(clk);
Das geht, so mache ich das auch...

EDIT:
MAN FASST ES NICHT: der macht da tatsächlich ein FF draus    :-O
Aber die Simulation geht schief, weil der Prozess immer wieder neu 
durchgerechnet wird, solange clk='1' ist. Und nicht nur an der 
steigenden Taktflanke.

Autor: Peterchen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>>  wait until clk = '1';
> Krass. Das habe ich jetzt auch noch nie gesehen :-o
> Gibt das wirklich einen getakteten Prozess?
> Formal korrekt wäre das:
> wait until rising_edge(clk);
> Das geht, so mache ich das auch..
OK, das werde ich noch abändern. Mich hats auch erstaunt daß es 
funktioniert. Eigentlich müsste er immer wieder durch den Prozess 
sausen, so lange clk auf '1' steht.

> Aber die Simulation geht schief...
Bei mir gehen Simulationen fast immer schief, weil ich z.B. vergesse, 
Signale zu initialisieren. Der CoolRunner setzt beim Starten sowieso 
alles auf 0. Aber auf den CPLD ist es ja schnell draufgeflashed. Gibt es 
eigentlich bessere Simulationstools als das im ISE WebPack?

Autor: Nephilim (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Modelsim von mentor kann man da nur empfehlen. inner xilinx starter 
edition sogar kostenlos erhältlich. muss man sich nur bei xilinx für 
registrieren.

Autor: Peterchen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Modelsim von mentor kann man da nur empfehlen. inner xilinx starter
> edition sogar kostenlos erhältlich. muss man sich nur bei xilinx für
> registrieren.

Das ist gut. Ich dachte schon, ich muß mir irgendwelche virenbehafteten 
Cracks runterladen. ;-)

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [vhdl]VHDL-Code[/vhdl]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.