Wie zwei Taktsignale in einem Prozess verarbeiten?

Gast #1124479
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
Gast #1124501
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).
Moderator (Firma: Titel) Persönliche Seite #1124507
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?
Gast #1124519
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)
Gast #1124521
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.
Gast #1124532
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!
Gast #1124580
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?
Moderator (Firma: Titel) Persönliche Seite #1124739
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)?
Gast #1124805
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.
Moderator (Firma: Titel) Persönliche Seite #1124846
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.
Moderator (Firma: Titel) Persönliche Seite #1124931
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... :-(
Gast #1125430
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
Moderator (Firma: Titel) Persönliche Seite #1125465
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.
Gast #1125502
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.
Moderator (Firma: Titel) Persönliche Seite #1125520
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... :-/
Gast #1126670
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;
Moderator (Firma: Titel) Persönliche Seite #1126696
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.
Gast #1126741
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?
Gast #1126836
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

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren