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


von Peterchen (Gast)


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.
1
process (a,b)
2
  begin
3
  if (rising_edge(a)) then
4
  ...
5
  end if;
6
  if (rising_edge(b)) then
7
  ...
8
  end if;
9
end process

Dazu bekomme ich die Fehlermeldung "Bad synchronous description"

Und sowas
1
signal c: std_logic;
2
...
3
c <= a or b;
4
...
5
process (a,b)
6
  begin
7
  if (rising_edge(c)) then
8
     if (a = '1') then
9
     ...  -- a ging "high"
10
     else
11
     ...  -- b ging "high"
12
     end if;
13
  ...
14
  end if;
15
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

von Jan M. (mueschel)


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?

von Peterchen (Gast)


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).

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


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?

von Jan M. (mueschel)


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

von Schrotty (Gast)


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)

von Peterchen (Gast)


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.

von Peterchen (Gast)


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!

von Peterchen (Gast)


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?

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


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)?

von Peterchen (Gast)


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.

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


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:
1
LIBRARY ieee;
2
USE ieee.std_logic_1164.ALL;
3
USE ieee.numeric_std.ALL;
4
:
5
signal up1  : std_logic := '0'; -- Einer hochzählen
6
signal up1  : std_logic := '0'; -- Einer runterzählen
7
signal cnt1 : unsigned(3 downto 0) := 0;
8
9
signal up10  : std_logic := '0'; -- Zehner hochzählen
10
signal dn10  : std_logic := '0'; -- Zehner runterzählen
11
signal cnt10 : unsigned(3 downto 0) := 0;
12
:
13
: 
14
process begin
15
   wait until rising_edge(clk); -- auf die nächste Taktflanke warten
16
   dn10 <= '0';             -- Defaultwert Enable=0
17
   if (dn1='1') then        -- herunterzählen ist gewünscht
18
      if (cnt1="0000") then -- Zählerstand = 0 --> Unterlauf
19
         cnt1 <= "1001";    -- laden mit 9
20
         dn10 <= '1';       -- Enable aktivieren
21
      else
22
         cnt1 <= cnt1 - 1;  -- sonst runterzählen
23
      end if;
24
   end if;
25
26
   up10 <= '0';             -- Defaultwert Enable=0
27
   if (up1='1') then        -- hochzählen ist gewünscht
28
      if (cnt1="1001") then -- Zählerstand = 9 --> Überlauf
29
         cnt1 <= "0000";    -- laden mit 0
30
         up10 <= '1';       -- Enable aktivieren
31
      else
32
         cnt1 <= cnt1 + 1;  -- sonst hochzählen
33
      end if;
34
   end if;
35
end process;


Diese Zeile:
1
   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.

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


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... :-(

von Peterchen (Gast)


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:
1
LIBRARY ieee;
2
USE ieee.std_logic_1164.ALL;
3
USE ieee.numeric_std.ALL;
4
5
ENTITY count_10 IS
6
  PORT
7
  (
8
    up_enable : in std_logic;               -- count down input
9
    down_enable : in std_logic;              -- count up input   
10
    clk : in std_logic;                  -- system clock
11
    value : out std_logic_vector (3 downto 0);   -- output value
12
    ov : out std_logic;                   -- overflow signal
13
    uv : out std_logic                    -- underflow signal
14
  );
15
END count_10;
16
17
ARCHITECTURE synth1 OF count_10 IS
18
  BEGIN
19
20
  process (clk, up_enable, down_enable)
21
  type state_t is (idle, ovset, uvset);
22
  variable counter : unsigned(3 downto 0);
23
  variable state : state_t;
24
  begin
25
    if (rising_edge(clk)) then                  -- system clock goes high
26
      case state is
27
        
28
        when idle => if (up_enable = '1') then      -- count up
29
                  counter := counter + 1;
30
                  if (counter = "1010") then    -- overflow?
31
                    counter := "0000";      -- reset counter
32
                    ov <= '1';            -- set overflow signal
33
                    state := ovset;    
34
                  end if;
35
                 end if;
36
                 if (down_enable = '1') then    -- count down
37
                  counter := counter - 1;      
38
                  if (counter = "1111") then    -- underflow?
39
                    counter := "1001";      -- reset counter
40
                    uv <= '1';            -- set underflow signal
41
                    state := uvset;
42
                  end if;
43
                 end if;
44
        
45
        when ovset => ov <= '0';              -- reset overflow signal on next clock cycle
46
                  state := idle;
47
                  
48
        when uvset => uv <= '0';              -- reset underflow signal on next clock cycle
49
                  state := idle;
50
      end case;
51
    end if;
52
    value <= std_logic_vector(counter);            -- send counter value to parent
53
  end process;
54
  
55
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

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


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.
1
    if (rising_edge(clk)) then                  -- system clock goes high
2
         ov <= '0';            -- Default inaktiv
3
         uv <= '0';            -- Default inaktiv
4
         if (up_enable = '1') then      -- count up
5
             counter := counter + 1;
6
             if (counter = "1010") then    -- overflow?
7
                 counter := "0000";      -- reset counter
8
                 ov <= '1';            -- set overflow signal
9
             end if;
10
         end if;
11
         if (down_enable = '1') then    -- count down
12
             counter := counter - 1;      
13
             if (counter = "1111") then    -- underflow?
14
                counter := "1001";      -- reset counter
15
                uv <= '1';            -- set underflow signal
16
             end if;
17
         end if;
18
      end if; -- clk
Auch so sind ov und uv jeweils genau 1 Takt lang aktiv.


1
          if (counter = "1010") then    -- overflow?
2
          :
3
          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:
1
   
2
         :
3
         if (up_enable = '1') then       -- count up
4
             if (counter = "1001") then  -- max cnt erreicht?
5
                 counter := "0000";           -- ja: reset counter
6
                 ov <= '1';                   -- set overflow signal
7
             else                        -- sonst hochzählen
8
                 counter := counter + 1;
9
             end if;
10
         end if;
11
         :


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

von Peterchen (Gast)


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.

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


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:
1
         ov <= '0';            -- Default inaktiv
2
         :
3
         if (up_enable = '1') then       -- count up
4
             if (counter = "1000") then  -- max cnt-1 erreicht?
5
                 ov <= '1';                   -- set overflow signal
6
             end if;
7
             if (counter = "1001") then  -- max cnt erreicht?
8
                 counter := "0000";           -- ja: reset counter
9
             else                        -- sonst hochzählen
10
                 counter := counter + 1;
11
             end if;
12
         end if;
13
         :
Hier wird praktisch der Übertrag einen Takt zu früh gesetzt, damit wird 
die Latency umgangen. Aber das ist schon fast hässlich... :-/

von Peterchen (Gast)


Lesenswert?

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

von Peterchen (Gast)


Lesenswert?

Hier ist meine endgültige Version. Der Prozess braucht noch nicht mal 
eigene Signale oder Variablen:
1
LIBRARY ieee;
2
USE ieee.std_logic_1164.ALL;
3
USE ieee.numeric_std.ALL;
4
5
ENTITY count_10 IS
6
  PORT
7
  (
8
    up_enable : in std_logic;                 -- count down input
9
    down_enable : in std_logic;                -- count up input   
10
    clk : in std_logic;                    -- system clock
11
    value : buffer std_logic_vector (3 downto 0) := "0000";   -- output value
12
    ov : out std_logic;                     -- overflow signal
13
    uv : out std_logic                      -- underflow signal
14
  );
15
END count_10;
16
17
ARCHITECTURE synth1 OF count_10 IS
18
19
  BEGIN
20
21
  process 
22
  begin
23
    wait until clk = '1';
24
    uv <= '0';
25
    ov <= '0';
26
    if (up_enable = '1') then
27
      if (value = "1001") then
28
        value <= "0000";
29
        ov <= '1';
30
      else
31
        value <= std_logic_vector(unsigned(value) + 1);
32
      end if;
33
    end if;
34
    if (down_enable = '1') then
35
      if (value = "0000") then
36
        value <= "1001";
37
        uv <= '1';
38
      else
39
        value <= std_logic_vector(unsigned(value) - 1);
40
      end if;
41
    end if;
42
  end process;
43
  
44
END synth1;

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


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:
1
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.

von Peterchen (Gast)


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?

von Nephilim (Gast)


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.

von Peterchen (Gast)


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. ;-)

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.