Forum: FPGA, VHDL & Co. Zwei Prozesse nutzen einen Vektor. Geht das so?


von Reinhard J. (rvj)


Lesenswert?

Hallo zusammen,

ich stehe noch ganz am Anfang mit VHDL und möchte einen Encoder-Zähler 
realisieren. Ausgangspunkt soll der Drehgeber Code von 
mikrocontroller.net werden. Ich werte dann einfach die Ausgaben "ce = 
Encoder hat sich geändert", "up_downfür die Richtung" und "error" aus, 
um einen 24-bit-Zähler t_cnt1 zu bedienen. Diesen Zähler möchte ich 
sporadisch mit einem AVR abfragen. Folgendes habe ich mir überlegt:

1. Zur Abfrage nutze ich drei Eingänge
1
 GET0  : : in  std_logic;
2
 GET1  : : in  std_logic;
3
 GET2  : : in  std_logic;

2. Bei GET0='1' fordert der AVR das erste Byte der 24 Bit Zahl. Ich 
kopiere nun den 24 bit Zähler t_cnt1 in den 24-bit-buffer t_cnt2, die 
wie folgt beschrieben sind:
1
 signal t_cnt1  : signed(23 downto 0); 
2
 signal t_cnt2  : signed(23 downto 0);
Jetzt kann ich auf die über GET1 und GET2 gestellten Anfragen reagieren 
und Byte für Byte die Ausgabe mit dem in t_cnt2 eingefrorenen Wert 
bedienen.
1
-- Output Process
2
  process (clk)                                      
3
  begin
4
    if (rising_edge(clk)) then
5
      if GET0 = '1' then                      -- bei GET0 = '1' den ganzen Vektor t_cnt1 nach Vektor ...
6
        t_cnt2(23 downto 0)   <=  t_cnt1(23 downto 0);  -- ... t_cnt2 kopieren und niederwertigstes Byte von ... 
7
        COUNT(7 downto 0)     <=  t_cnt2( 7 downto 0);   -- ... t_cnt2 auf die Ausgabe COUNT legen
8
      elsif GET1 = '1' then                    -- bei GET1 = '1' mittleres Byte von t_cnt2 auf die ...
9
        COUNT(7 downto 0)     <=  t_cnt2(15 downto 8);   -- ... Ausgabe COUNT legen
10
      elsif GET2 = '1' then                    -- bei GET2 = '1' höchswertiges Byte von t_cnt2 auf die ...
11
        COUNT(7 downto 0)     <=  t_cnt2(23 downto 16);  -- ... Ausgabe COUNT legen
12
      else
13
        null;
14
      end if;
15
    end if;
16
  end process;
3. Den Zähler pflege ich in einem eigenen Prozess, der auch den Zähler 
Reset bewerkstelligt.
1
-- Counter  Process
2
  process (ce, RESET)                -- reagiert auf ce (change encoder) und RESET
3
  begin                      -- bei RESET - clear (others => '0') setzt alle Ausgaben auf '0', ...
4
    if (RESET = '1') then            -- ... clear (others => '1') setzt alle Ausgaben auf '1', mit dem Setzen ... 
5
      t_cnt1 <= (others => '1');    -- ... der 24 Bit Zahl auf '1' und dem Setzen des hochwertigsten Bits ... 
6
      t_cnt1(23)   <= '0';        -- ... auf '0'gehe ich in die Mitte des 24 Bit Wertebereichs
7
    elsif (rising_edge(clk)) then    -- Auswertung von ce bei jedem clock cycle
8
      if ce='1' then
9
        if (up_down = '1') then 
10
          t_cnt1 <= t_cnt1 + 1;
11
        else 
12
          t_cnt1 <= t_cnt1 - 1;
13
        end if;
14
      end if;
15
    end if;
16
  end process;

Das sind alles noch unverbundenene Prozessentwürfe und ich frage mich ob 
ich nicht einen grundsätzlichen Fehler eingebaut habe.

In einem CPLD darf ich nicht sequentiell denken. Es passiert alles 
parallel. Beide Prozesse nutzen t_cnt1. Sollte ich nicht besser alles in 
einen Prozess legen, damit nicht zwei Prozesse an einem Vektor 
rumschrauben? Oder gibt es einen Kniff in solchen Fällen.

Die Frage ist vielleicht eigenartig, ich kann sie mir aber weder durch 
Recherche im Forum und Google noch durch Überlegen beantworten. 
Vielleicht klappt die Synthese und es läuft mit einem ganz fiesen 
sporadischen Fehler. Weiß jemand Rat?

Gruß und Danke
Reinhard

von Logiker (Gast)


Lesenswert?

Hast Du eine Ausbilding in Hardwareentwicklung?

Man macht das so, dass man jeden Ausgang nur einmal an einem Ort 
beschreibt und eine Funktion aufstellt, die den Wert in Abhängigkeit der 
Eingänge beschreibt.

von Reinhard J. (rvj)


Lesenswert?

Eine Hardwareausbildung steht mir nicht im Weg.

von Georg A. (georga)


Lesenswert?

>  Beide Prozesse nutzen t_cnt1.

Nutzen!=Nutzen.

"Lesen" ist völlig unproblematisch, solange das synchron (mit demselben 
Takt+Flanke) wie die (eine) Zuweisung passiert. Nachdem du sicher 
gelesen hast, dass man als Anfänger mehr als einen Takt tunlichst 
vermeiden sollte, sollte das ohnehin immer der Fall sein. Dein 
Code-Beispiel fällt da drunter. Da das zweimal der identische Takt ist, 
kannst du aber gleich alles in einen Prozess stecken. Mehrere Prozesse 
machen deine VHDL-Unerfahrenheit nicht wett ;)

Schwieriger wirds bei Zuweisungen auf dasselbe Signale (Tip: lass es 
für den Anfang erst mal sein...). Da ist es dann auch egal, wenn die 
Prozesse unterschiedliche Teile des Vektors nutzen. Die Zuweisung an 
einen Teil ("Slice") ist implizit immer eine Zuweisung an alles. Ohne 
weitere Vorkehrungen kommen dann im Simulator die X auf allen Bits raus 
und man sucht sich dämlich. Da kann man mit der Defaultzuweisung auf 'Z' 
für alle Bits in beiden Prozessen arbeiten, d.h. nach dem if-rising 
gleich ohne Bedingung die 'Z'-Zuweisung. Das geht aber in der realen HW 
auch nicht mehr, wenn das unterschiedliche Takte sind, ergo lass es ;)

BTW: in dem zweiten process muss CLK in der sensitivity-list stehen und 
nicht ce.

: Bearbeitet durch User
von Matthias (Gast)


Lesenswert?

Der eine counter t1 wird nur in dem einen Prozess  beschrieben im 
anderen nur gelesen. Das passt soweit. Im unteren Prozess ist die 
sensitiviy list falsch (clk statt ce).
Ist dir bewusst, dass dein wert um einen Takt verzögert an COUNT 
ankommt?

Interessant ist auch wie dein interface zum Kontroller realisiert ist.

von VHDL hotline (Gast)


Lesenswert?

Dein AVR zieht das GET0/1/2 wahrscheinlich für eine ganze Weile (aus 
FPGA clk-Sicht). Bei GET0='1' wird dann kontinuerlich t_cnt2 durch 
t_cnt1 überschrieben (und entsprechend einen Takt später COUNT). Da wird 
also nichts eingefroren.

Dort solltest du besser auf eine Flanke von GET0 reagieren, also

GET0_q <= GET0;
if (GET0_q = '1' and GET0 = '0') then ...

dann wird das nur einmal zur steigenden Flanke von GET0 ausgeführt. Bei 
GET1 und GET2 geht es pegelsensitiv.

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


Lesenswert?

VHDL hotline schrieb im Beitrag #3858077:
> GET0_q <= GET0;
> if (GET0_q = '1' and GET0 = '0') then ...
> dann wird das nur einmal zur steigenden Flanke von GET0 ausgeführt.
Aber aufpassen: hier wird ein nicht synchronisiertes Signal (GET0) 
verwendet. Das kann böse ins Auge gehen! Hier trifft dann genau der Fall 
dort in der Mitte zu:
http://www.lothar-miller.de/s9y/categories/35-Einsynchronisieren

Reinhard J. schrieb:
> Diesen Zähler möchte ich sporadisch mit einem AVR abfragen.
Du solltest auch unbedingt das machen, was Atmel bei den 16Bit Timern 
auch tut: beim Lesen des "untersten" Bytes die oberen in 
Schattenregister abspeichern und die dann ausgeben. Denn sonst wird du 
"ab und zu" seltsam "zappelnde" Werte haben. Denn wenn sich während des 
Auslesens der Geber weiterdreht und der Zähler byteweise überläuft, dann 
gibt es seltsame Sprünge. Mal angenommen, du liest zuerst das LSB, dann 
das mittlere Byte, dann das MSB, dann kann dir sowas passieren:
1
AVR   MSB ... LSB        24-Bit-Wert
2
   
3
1     x12 
4
2         x34 
5
3             xff    --> 1234ff
6
4     x12 
7
5         x34 
8
6             xff    --> 1234ff
9
7     x12 
10
8         x34 
11
Zähler zählt hoch --> Überlauf --> neuer Zählerwert = x123500
12
9             x00    --> 1235ff  <<-!!!!! falscher Wert zusammengesetzt
13
10    x12 
14
11        x35 
15
12            x00    --> 123500
16
13    x12 
17
14        x35 
18
Zähler zählt runter --> Unterlauf --> neuer Zählerwert = x1234ff
19
15            xff    --> 1235ff  <<-!!!!! falscher Wert zusammengesetzt 
20
16    x12 
21
17        x34 
22
18            xff    --> 1234ff

: Bearbeitet durch Moderator
von Reinhard J. (rvj)


Lesenswert?

Hallo Zusammen!

Erst mal herzlichen Dank für die Antworten.

Hier poste ich erst mal eine Antwort auf die von mir ursprünglich etwas 
nebulös formulierte Frage. Ich arbeite mich nämlich alleine in das Thema 
ein. Angefangen habe ich mit dem Buch Free Range VHDL was kostenlos als 
pdf auf dem Web zur Verfügung steht. In VHDL hingekriegt habe ich 
bislang lediglich einen Addierer zweier Encoder Signale und will nun den 
Zähler bauen.

Dann folgen ein paar Kommentare zu Euren Hinweisen, wobei ich manches 
nicht verstanden habe. Ich denke aber drüber nach, in der Hoffnung, dass 
mein Verständnis besser wird.

Zum Schluss stelle ich den derzeitigen Stand vor und zur Diskussion, 
falls jemand Interesse hat. Ich muss das aber nochmal durchsehen. Ich 
lade den Stand nur hoch, damit Interessierte den Fortschritt verfolgen 
können.

Ich werde meinen Fortschritt mitteilen, weise aber darauf hin, dass ich 
hin und wieder längere Pausen einlege. Bei Interesse schaltet die email 
Benachrichtigung ein.

Herzlichen Dank an alle die meinen Beitrag gelesen oder gar kommentiert 
haben. Ohne dieses Forum wäre ich ziemlich aufgeschmissen. Im gesamten 
FPGA Forum finde ich viele nützliche Threads.

Gruß Reinhard

1. Mein Denkfehler und ein anderer didaktischer Ansatz
------------------------------------------------------
Folgende Überlegung lag der Anfrage zugrunde:
Wenn ich zwei Entitys beschreibe und die dann als Komponenten verbinde, 
so stehen diese als Hardware zur Verfügung. Die ganzen Vorgänge laufen 
parallel ab. Das bedeutet es spielt keine Rolle, welche Logik zuerst 
beschrieben wird, mit Ausnahme von dem was innerhalb eines Process 
passiert. Nun dachte ich, wenn zu einem Zeitpunkt des Flankenanstiegs 
der clock Entity_1 ein Ausgangssignal erzeugt, wie kann Entity_2 im 
allerselben Moment, quasi auf die fantastillionste Sekunde genau den 
Ausgang von Entity_1 verarbeiten. Wie steht es um die Kausalität eines 
solchen Systems?
Hier scheint mein Denkfehler zu liegen. Besser ich betrachte nicht einen 
Zeitpunkt, sondern die Zeitspanne zwischen zwei Flankenanstiegen. Dann 
löst sich das Problem von selbst. Kann sein, dass mein Gedankengang für 
manche verborgen bleibt, nur muss ich mir als Autodidakt meine Didaktik 
selbst entwickeln. Dieses wäre nun also geklärt.

2. Meine Anmerkungen
--------------------
@Logiker: Meine Elektronikkenntnisse habe ich mir selbst angeeignet. 
Startpunkt war ein Asuro. Deine Erklärung konnte ich nicht verstehen. 
Für mich als Anfänger ist sie wohl zu knapp. War bestimmt auch nicht so 
einfach zu erraten, wo bei mir der Schuh drückte. In Punkt 1 unten habe 
ich versucht mir selbst eine Brücke zu bauen. Auf alle Fälle Danke für 
die ultraschnelle Antwort.

@Georg: Ich denke, dass ich das mit den Zuweisungen nun verbessert habe. 
Ich lass das Ändern eines Signals aus zwei Prozessen sein (siehe Punkt 
3). Speziellen Dank für den für Anfänger verständlichen Rat.

@Matthias: Stimmt. Ich glaube auch das korrigiert zu haben  (siehe Punkt 
3). Falls das Projekt weiter geht, lade ich den AVR Code hoch. Ich weise 
darauf hin, dass bei mir manche Projekte Monate dümpeln und dann weiter 
laufen.

@Hotline: Stimmt das fiel mir beim Überarbeiten auch auf. Jetzt mache 
ich das anders und zwar so, dass nur noch einmal t_cnt2 mit t_cnt1 bei 
GET0 überschrieben wird. Ich nutze hierfür die Signale StateOfGet0, 
StateOfGet1 und StateOfGet2 als flags. Zur Abfrage werden dann die 
Signale GET0, GET1 und GET2 abwechselnd von 0 nach 1 und von 1 nach 0 
geschaltet. Schau Dir es einfach unten an.

@Lothar: Deinen ersten Hinweis habe ich nicht verstanden. Ich denke 
aber, dass mein jetziger Process womöglich auch hier robuster ist. Der 
zweite Hinweis hat sich möglicherweise auch erledigt, da ich bei GET0 
den kompletten Zähler durch Kopieren einfriere und diesen dann 
Byte-weise lese. Nach dem Kopieren setze ich den Zähler zurück. der AVR 
muss dann den Zählwert zum vorherigen Wert addieren. Da ich die Werte 
oft genug beim CPLD abhole, dürfte der Überlauf hier kein Problem 
darstellen. Wenn, dann muss ich auf der AVR Seite Obacht geben.

3. Derzeitiger Stand des Entwurfs
---------------------------------
Momentan sieht der Prozess etwas anders aus. Die Abfragen GET0, GET1 und 
GET2 werden nun getoggelt und der Zähler t_cnt1 nach jedem Kopiervorgang 
nach t_cnt2 zurückgesetzt. Ich muss das aber nochmal durchsehen!
1
----------------------------------------------------------------------------------
2
-- Company: 
3
-- Engineer: 
4
-- 
5
-- Create Date:    02:42:28 10/26/2014 
6
-- Design Name: 
7
-- Module Name:    Counter - Behavioral 
8
-- Project Name: 
9
-- Target Devices: 
10
-- Tool versions: 
11
-- Description: 
12
--
13
-- Dependencies: 
14
--
15
-- Revision: 
16
-- Revision 0.01 - File Created
17
-- Additional Comments: 
18
--
19
----------------------------------------------------------------------------------
20
library IEEE;
21
use IEEE.STD_LOGIC_1164.ALL;
22
23
-- Uncomment the following library declaration if using
24
-- arithmetic functions with Signed or Unsigned values
25
use IEEE.NUMERIC_STD.ALL;
26
27
-- Uncomment the following library declaration if instantiating
28
-- any Xilinx primitives in this code.
29
--library UNISIM;
30
--use UNISIM.VComponents.all;
31
32
entity Counter is
33
    Port (   clk           : in  std_logic;             -- Systemtakt
34
        GET0           : in  std_logic;             -- Get first byte
35
        GET1           : in  std_logic;             -- Get second byte
36
        GET2           : in  std_logic;             -- Get third byte
37
        EncChange       : in  std_logic;             -- Encoder has changed signal
38
        UpOrDown       : in  std_logic;             -- Direction indicator
39
        RESET           : in  std_logic;             -- Reset
40
            COUNT          : out std_logic_vector (7 downto 0)      -- Output of one byte of the snapshot
41
      );
42
end Counter;
43
44
architecture Behavioral of Counter is
45
  signal t_cnt1  : std_logic_vector(20 downto 0); 
46
  signal t_cnt2   : std_logic_vector(20 downto 0); 
47
  signal StateOfGet0 : std_logic;
48
  signal StateOfGet1 : std_logic;
49
  signal StateOfGet2 : std_logic;
50
begin
51
-- Counter Process: Der AVR wechselt die GET0, GET1 und GET2 Signale (toggle). Die Signale ...
52
-- ... StateOfGet0, StateOfGet1 und StateOfGet2 sorgen dafuer, dass die Aktionen nur ...
53
-- ... einmal behandelt werden.
54
55
  CounterHandling: process (clk) is  -- reagiert auf clk
56
  begin                                          -- GET0, GET1 und GET2 werden ...
57
    if ( rising_edge(clk) ) then                        -- ... vom AVR getoggelt
58
      if EncChange = '1' then                          -- change encoder vorrangig behandeln
59
60
        if ((UpOrDown = '1') and (GET0 = StateOfGet0)) then    -- keine Leseanforderung
61
          t_cnt1 <= std_logic_vector( unsigned(t_cnt1) + 1); 
62
          
63
        elsif ((UpOrDown = '1') and (GET0 = StateOfGet0)) then  -- keine Leseanforderung
64
          t_cnt1 <= std_logic_vector( unsigned(t_cnt1) - 1); 
65
        
66
        elsif ((UpOrDown = '1') and (GET0 /= StateOfGet0)) then  -- Leseauffordeung und change encoder 
67
          t_cnt1 <= std_logic_vector( unsigned(t_cnt1) + 1);   -- update des Zaehlers (plus)
68
          t_cnt2(20 downto 0)   <=  t_cnt1(20 downto 0);      -- Kopie des Zaehlers
69
          COUNT(7 downto 0)     <=  t_cnt2( 7 downto 0);       -- erstes Byte lesen
70
          t_cnt1      <= (others => '1');                -- reset t_cnt1
71
          t_cnt1(20)   <= '0';
72
          StateOfGet0 <= GET0;                        -- Flagsignal update
73
          
74
        elsif ((UpOrDown = '0') and (GET0 /= StateOfGet0)) then  -- Leseauffordeung und change encoder
75
          t_cnt1 <= std_logic_vector( unsigned(t_cnt1) - 1);    -- update des Zaehlers (minus)
76
          t_cnt2(20 downto 0)   <=  t_cnt1(20 downto 0);       -- Kopie des Zaehlers
77
          COUNT(7 downto 0)     <=  t_cnt2( 7 downto 0);       -- erstes Byte lesen
78
          t_cnt1      <= (others => '1');                -- reset t_cnt1
79
          t_cnt1(20)   <= '0';
80
          StateOfGet0 <= GET0;                        -- Flagsignal update
81
          
82
        end if;
83
84
      else                                      -- clock cycle ohne encoder change
85
86
        if ( GET0 /= StateOfGet0 ) then                   -- GET0 will be toggled
87
          t_cnt2(20 downto 0)   <=  t_cnt1(20 downto 0);      -- Kopie des Zaehlers 
88
          COUNT(7 downto 0)     <=  t_cnt2( 7 downto 0);       -- erstes Byte lesen
89
          t_cnt1      <= (others => '1');                -- reset t_cnt1
90
          t_cnt1(20)   <= '0';
91
          StateOfGet0  <= GET0;                      -- Flagsignal update
92
93
        elsif ( GET1 /= StateOfGet1 ) then                
94
          COUNT(7 downto 0)     <=  t_cnt2(15 downto 8);       -- zweites Byte lesen
95
          StateOfGet1  <= GET1;                      -- Flagsignal update
96
97
        elsif ( GET2 /= StateOfGet2 ) then    
98
          COUNT             <= (others => '0');             -- COUNT auf Null stellen
99
          COUNT(4 downto 0)     <=  t_cnt2(20 downto 16);      -- 5 Bit lesen
100
          StateOfGet2  <= GET2;                      -- Flagsignal update
101
102
        elsif (RESET = '1') then                 
103
          t_cnt1      <= (others => '1');      
104
          t_cnt1(20)   <= '0';                
105
106
        end if;
107
      end if;
108
    end if;
109
  
110
  end process CounterHandling;
111
 
112
end Behavioral;

von Reinhard J. (rvj)


Lesenswert?

Nachträge:
a) In dem Code oben ist der zweite if falsch. Es muss so heißen:
1
        if ((UpOrDown = '1') and (GET0 = StateOfGet0)) then    -- keine Leseanforderung
2
          t_cnt1 <= std_logic_vector( unsigned(t_cnt1) + 1); 
3
          
4
        elsif ((UpOrDown = '0') and (GET0 = StateOfGet0)) then  -- keine Leseanforderung
5
          t_cnt1 <= std_logic_vector( unsigned(t_cnt1) - 1);

b) Habs es ausprobiert und bin wieder auf der Fehlersuche. Kurz, es 
klappt nicht. Fragt sich nur wo es nicht geht. CPLD oder AVR?

von Georg A. (georga)


Lesenswert?

> Nun dachte ich, wenn zu einem Zeitpunkt des Flankenanstiegs
> der clock Entity_1 ein Ausgangssignal erzeugt, wie kann Entity_2 im
> allerselben Moment, quasi auf die fantastillionste Sekunde genau den
> Ausgang von Entity_1 verarbeiten. Wie steht es um die Kausalität eines
> solchen Systems?

Sieh es einfach mal so, dass die Reaktion auf die steigende Flanke zwar 
quasi unendlich schnell, aber doch mit einer Zeit >0 passiert (und nicht 
=0). Aus dem Grund wird eben nicht schon der Ausgang einer zeitgleich 
getakteten Entity verarbeitet. VHDL-Simulatoren müssen das ja auch 
"nachbauen". Zuerst wird festgestellt, welche Prozesse ausgelöst wurden. 
Dann werden sie sequentiell ausgeführt, aber die Signal-Zuweisungen erst 
noch "aufgehoben" und nicht gleich gespeichert. Erst wenn alle Prozesse 
durch sind, werden die gespeicherten Zuweisungen zurückgeschrieben.

In der Praxis ist das mit der Kausalität aber auch nicht so einfach, 
wenn der Takt entweder nicht überall gleichzeitig ankommt (Clock-Skew), 
oder der Ausgang eines FFs so schnell reagiert, dass er beim nächsten 
FF-Eingang noch die Hold-Zeit verletzt. Nicht umsonst macht man so einen 
Aufstand mit der Taktverteilung in ICs...

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


Lesenswert?

Reinhard J. schrieb:
1
   COUNT(7 downto 0)     <=  t_cnt2( 7 downto 0);
Das geht lesbarer so, denn COUNT hat eh' 8 Bit:
1
   COUNT                 <=  t_cnt2( 7 downto 0);

> UpOrDown
Das ist mal so ein richtig verwirrender Name. Wenn das Signal Up_nDown 
hieße, dann wüsste ich, dass es bei '1' hoch geht, und bei '0' runter. 
Wenn das Signal nur Up hieße, dann wüsste ich das auch. Aber bei 
UpOrDown: kann ich mir das raussuchen, ob es bei '1' rauf oder runter 
geht? Oder bedeutet dieses Signal, dass sich überhaupt was tut, also 
"Rauf oder Runter". Aber wofür gäbe es dann das EncChange auch noch?

Reinhard J. schrieb:
> @Lothar: Deinen ersten Hinweis habe ich nicht verstanden.
Arbeite daran. Du wirst sonst noch OFT, SEHR OFT darüber stolpern.
> Ich denke
> aber, dass mein jetziger Process womöglich auch hier robuster ist.
Falsch gedacht. Auch hier sind alle GETx nicht einsynchronisiert in 
einem Zustandsautomaten verwendet.

Reinhard J. schrieb:
> Nun dachte ich, wenn zu einem Zeitpunkt des Flankenanstiegs der clock
> Entity_1 ein Ausgangssignal erzeugt, wie kann Entity_2 im allerselben
> Moment, quasi auf die fantastillionste Sekunde genau den Ausgang von
> Entity_1 verarbeiten. Wie steht es um die Kausalität eines solchen
> Systems?
Der FPGA-Hersteller garantiert dir, dass die Taktverteilung im ganzen 
FPGA so ist, dass sich nach einer Taktflanke die Ausgänge mehrerer 
hintereinandergeschalteter Flipflops so ändern, dass jedes Flipflop der 
Wert, der vor der Taktflanke am Eingang anlag, zum Ausgang durchgibt. 
Es kann keinen "Fall-Trough" irgendeines Signals geben.
Man hat dafür einen Namen: "Synchrones Design". Das funktioniert so: mit 
jeder Taktflanke übernimmt jedes Flipflop den Wert, der zu diesem 
Zeitpunkt am D-Eingang anliegt und übergibt ihn an den Ausgang. Danach 
herrscht im kompletten FPGA hecktische Unruhe, weil die Kombinatorik an 
den Flipflop-Ausgängen neue Werte ermittelt. Dieser Vorgang muss 
rechtzeitig vor der nächsten Taktflanke fertig sein, dass das Flipflop 
mit dem folgenden Takt stabile Daten übernehmen kann. Dann beginnt das 
Spiel von vorn.

> Hier scheint mein Denkfehler zu liegen. Besser ich betrachte nicht einen
> Zeitpunkt, sondern die Zeitspanne zwischen zwei Flankenanstiegen. Dann
> löst sich das Problem von selbst.
Nein, du verdrängst es nur in die Ecke "zum Glück kann das kaum 
passieren".
Lies dir mal die KOMPLETTE Seite von meinem Link durch. Und vor Allem: 
du MUSST unbedingt verstehen, was ich da meine. Solange das nicht der 
Fall ist, werden deine Designs nicht zuverlässig laufen...

>  Kurz, es klappt nicht.
Was ist "es"? WAS klappt nicht? Was erwartest du und was passiert? Was 
sagt die Simulation?


> Der zweite Hinweis hat sich möglicherweise auch erledigt, da ich bei
> GET0 den kompletten Zähler durch Kopieren einfriere und diesen dann
> Byte-weise lese.
"Einfrieren" ist zum Glück das falsche Wort, du machst irgendwie kurios 
eine Kopie.
> Nach dem Kopieren setze ich den Zähler zurück.
Und das ist Murks. Wirst du aber noch merken.
Einen Quadraturdecoder lässt man unberührt laufen und liest ihn nur! 
Dann kann man in der Software einfach per Subtraktion die Differenz zum 
zuletzt verarbeiteten Wert ermitteln und damit arbeiten. Denn sonst hast 
du auch noch in der Software eine Asynchronität mit eingebaut. Und Alles 
wird immer komplizierter und unbeherrschbarer...

: Bearbeitet durch Moderator
von Reinhard J. (rvj)


Lesenswert?

Hallo Georg, hallo Lothar,
erstmal herzlichen Dank für Eure Hilfe.
@Georg: Deine Denkweise werde ich mir zu eigen machen.
@Lothar: Habe Deinen Artikel gedruckt und arbeite mich durch.
Ich berichte den Fortschritt, sobald ich weiter fortfahre.
Gruß Reinhard

von Reinhard J. (rvj)


Lesenswert?

Hallo Lothar,

nachdem Studium Deines Textes ist mir eins noch klarer geworden: Mit 
VHDL ist man in wirklich in der Hardware angekommen. Soweit war Logikers 
Nachfrage nach meinen Hardwarekenntnissen berechtigt. Bislang dachte ich 
mit Assembler ganz nah an der Maschine zu sitzen. Mit VHDL gestalte ich 
die Maschine. Das verdeutlichten mir Deine Artikel ganz und ganz.

Da mir nur eingeschränkte Hardwarekenntnisse zur Verfügung stehen, kann 
ich nicht behaupten, dass Deine Ausführungen hundertprozentig bei mir 
angekommen sind. Aber ich stehe am Anfang und mit viel Schwung werde ich 
das hoffentlich nach und nach auf die Reihe kriegen. Ich sehe es mal 
positiv: Nach dem Studium Deiner Seite hat der Beginn eines Andenkens 
stattgefunden. Auf Deinem Blog steht viel interessantes, was mir wie ich 
hoffe nach und nach verständlich wird. Vielen Dank auch für diese Seite. 
Dem Lobesthread auf Mikrcontroller.net schließe ich mich an. Seiten wie 
lothar-miller.de und mikrocontroller.net tun mehr für die Bildung als 
manches Hochschulförderprogramm!

Nun zum vagen, d.h. nicht unbedingt von mir erfassten. Asynchrone 
Eingänge, also solche deren Auftreten kein Bezug zum Takt haben sollen 
nach Deinen Ausführungen vermittels zweier Flip-Flops einsynchronisiert 
werden. Für die Landbevölkerung heißt das in VHDL zweimal in anderen 
std_logic ablegen. Kann man das so mal lax festhalten?

Bei meinem Tun sind ganz offensichtlich GET0, GET1, GET2 und RESET 
asynchron. Diese Signale werden ja vom AVR irgendwann abgesondert und 
stehen in keinem Bezug zur clock. Ich habe - bitte nicht über die 
Variablennamen meckern, sie sind erst mal schnell ersonnen - den Prozess 
wie folgt beschrieben:
1
architecture Behavioral of Counter is
2
  signal t_cnt1       : std_logic_vector(15 downto 0); 
3
  signal t_cnt2       : std_logic_vector(15 downto 0); 
4
  signal StateOfGet0  : std_logic;
5
  signal StateOfGet1  : std_logic;
6
  signal StateOfGet2  : std_logic;
7
  signal GET0         : std_logic;
8
  signal GET0mitte    : std_logic;
9
  signal GET1         : std_logic;
10
  signal GET1mitte    : std_logic;
11
  signal GET2         : std_logic;
12
  signal GET2mitte    : std_logic;
13
  signal RESET        : std_logic;
14
  signal RESETmitte   : std_logic;
15
16
begin
17
18
Sync: process begin -- Einsynchronisieren
19
   wait until rising_edge(clk);
20
    GET0mitte   <= GET0in;
21
    GET0        <= GET0mitte;
22
    GET1mitte   <= GET1in;
23
    GET1        <= GET1mitte;
24
    GET2mitte   <= GET2in;
25
    GET2        <= GET2mitte;
26
    RESETmitte  <= RESETin;
27
    RESET       <= RESETmitte;
28
  end process Sync;
29
-- und so weiter

Ist das im Sinne des von Dir verfassten Artikels?

Muss ich die Encoder nicht auch einsynchronisieren?

Was mir so gar nicht einleuchtet ist folgendes. Du schreibst:
>> Nach dem Kopieren setze ich den Zähler zurück.
>Und das ist Murks. Wirst du aber noch merken. ...
Ja was spricht den dagegen den Encoder nur schrittweise zu zählen? Nach 
jedem Schritt wird der Zählerstand festgehalten und dann in aller Ruhe 
ausgelesen. Gibt es auch hierzu einen lehrreichen link?

Abschließend nochmals Danke und Gruß

Reinhard

PS. wie schaffen es viele Forumsleser sich so rasch in fremden Code 
einzulesen? Ich kann das nicht.

von Schlumpf (Gast)


Lesenswert?

Du kannst das Synchronisieren auch ein wenig eleganter beschreiben

GET nicht als std_logic, sondern als std_logic_vector mit 2 Bit
Also
1
signal GET0         : std_logic_vector(1 downto 0);

und dann im synchronen Prozess:
1
GET0 <= GET0(0) & GET0in;

Das einsynchronisierte Signal ist dann GET0(1)

Mit jedem Takt übernimmt GET0(1) den Wert von GET0(0) und GET0(0) den 
Wert von GET0in.

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


Lesenswert?

Reinhard J. schrieb:
> Ist das im Sinne des von Dir verfassten Artikels?
Ja, aber wie Schlumpf gesagt hat: Ich verwende dafür immer ein 
Schieberegister. Der Vorteil dabei ist, dass ich auf dieses Signal dann 
ganz leicht eine Flankenerkennung ansetzen kann. Siehe den Code im 
Beitrag "Re: Erfahrung mit SPI Slave und Spartan 6 FPGA?"

Schlumpf schrieb:
> GET0 <= GET0(0) & GET0in;
> Das einsynchronisierte Signal ist dann GET0(1)
Bei Frequenzen unter 200MHz reicht eigentlich schon ein einziges 
Flipflop aus. Aaaaaber wehe, wenn der Synthesizer Register Duplication 
macht, weil zu viele Lasten (Gattereingänge oder weitere Flipflops) am 
Ausgang dieses einen Flipflops angeschlossen werden müssen (Stichwort: 
Fanout). Sicherer ist es daher, wenn zwei Flipflops hintereinnander 
kommen und das zweite als Einsynchronisiert betrachtet wird.

Für mich würde das in deinem Fall (mit überschaubar vielen Lasten am 
ersten Flipflop) also so aussehen:
1
architecture Behavioral of Counter is
2
  signal t_cnt1       : integer range 0 to 65535; -- nimm eingeschränkte Integer zum Zählen!
3
  signal t_cnt2       : integer range 0 to 65535;
4
  signal srGET0       : std_logic_vector(1 downto 0); 
5
  signal srGET1       : std_logic_vector(1 downto 0); 
6
  signal srGET2       : std_logic_vector(1 downto 0); 
7
8
begin
9
    -- Concurrent statt Prozess --> kurz und kompakt ;-)
10
    srGET0   <= srGET0(0) & GET0in when rising edge(clk);
11
    srGET1   <= srGET1(0) & GET1in when rising edge(clk);
12
    srGET2   <= srGET2(0) & GET2in when rising edge(clk);
13
14
    process begin
15
      wait until rising_edge(clk);
16
      if srGET0="01" then -- steigende Flanke am GET0
17
          -- tu was
18
    ...

: Bearbeitet durch Moderator
von Reinhard J. (rvj)


Lesenswert?

Hallo!

habe mich wieder ein wenig drangesetzt und Lothars Ratschlag:
1
  signal t_cnt1       : integer range 0 to 65535; -- nimm eingeschränkte Integer zum Zählen!
2
  signal t_cnt2       : integer range 0 to 65535;

umgesetzt. Leider krieg ich dann den integer nicht in zwei Bytes 
transportiert.

Ich versuche einen slice und eine Zuweisung auf std_logic_vector mit
1
COUNT  <= std_logic_vector(t_cnt2(7 downto 0));
und dann für das zweite Byte
1
COUNT  <= std_logic_vector(t_cnt2(15 downto 8));

Leider kriege ich nun

ERROR:HDLParsers:3270 ... Line 92. t_cnt2 is not an array slice prefix.
ERROR:HDLParsers:822  ... Wrong slice type for t_cnt2.

Weiß jemand Rat?

Gruß Reinhard

von Schlumpf (Gast)


Lesenswert?

Schau mal auf Lothar´s Seite. Der hat da eine super Übersicht zur 
Konvertierung von Datentypen in VHDL
http://www.lothar-miller.de/s9y/index.php?serendipity[action]=search&serendipity[fullentry]=1&serendipity[searchTerm]=cast&serendipity[searchButton]=%3E

Der Integer muss erst noch auf Bits "abgebildet" werden, bevor du ihn 
slicen kannst. Das geschieht mit den Konvertierungen wie auf Lothar´s 
Seite beschrieben unter Angabe der Anzahl der Bits, die diesen Wert 
repräsentieren.

Also aus Integer wird zuerst ein "unsigned" oder "signed" mit 
entsprechender Anzahl von Bits. Das könntest du jetzt bereits slicen. 
Wenn du aber auf std_logic_vetor abbilden willst, dann musst du eben 
noch auf std_logic_vector casten.

von Reinhard J. (rvj)


Lesenswert?

Danke Schlumpf.
Ich druck mir das aus und versuche es zu verstehen.
Gruß Reinhard

von Schumpf (Gast)


Lesenswert?

Reinhard J. schrieb:
> Ich druck mir das aus

Gut so :-) Ich würde mal gerne wissen, wieviele Leute sich diese 
kompakte Übersicht schon ausgedruckt haben..

von Reinhard J. (rvj)


Lesenswert?

Hallo Schlumpf,

das Ausdrucken hat sich gelohnt. Ich finde die Grafik super, obwohl ich 
den Unterschied zwischen Konvertierung und Cast noch nicht erfasst habe 
- ich grübel noch ein bisschen drüber nach.

Jetzt bekomme ich keine Schimpfe vom Parser mehr. Hier der Auszug meines 
schrittweisen Übergangs, d.h. erst Konvertierung und dann Cast.

Hier die Definitionen:
1
entity Counter is
2
    Port (  clk       : in  std_logic;                     -- Systemtakt
3
            GET0in    : in  std_logic;                     -- Get first byte
4
            GET1in    : in  std_logic;                     -- Get second byte
5
            EncChange : in  std_logic;                     -- Encoder has changed signal
6
            UpOrDown  : in  std_logic;                     -- Direction indicator
7
            RESETin   : in  std_logic;                     -- Reset
8
            COUNT     : out std_logic_vector (7 downto 0)  -- Output of one byte of the snapshot
9
      );
10
end Counter;
und dann in der architecture
1
   signal t_cnt1        : integer range -32768 to 32767; -- nimm eingeschränkte Integer zum Zählen!
2
   signal t_cnt2        : std_logic_vector (15 downto 0);
3
   signal t_cnt2_signed : signed(15 downto 0);
Nun das schrittweise Umsetzen und Lesen des LSB
1
  t_cnt2_signed <= to_signed(t_cnt1, 16);
2
  t_cnt2 <= std_logic_vector(t_cnt2_signed);
3
  COUNT  <=  t_cnt2( 7 downto 0);            -- LSB lesen
und des MSB
1
  COUNT <= t_cnt2(15 downto 8);           -- MSB lesen

Und jetzt die wichtige Frage:
Ist das dann ein 16 Bit Integer vom Typ int16_t für den AVR?

Nochmals ganz herzlichen Dank für all die Tipps.
Gruß Reinhard

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


Lesenswert?

Reinhard J. schrieb:
> obwohl ich den Unterschied zwischen Konvertierung und Cast noch nicht
> erfasst habe
Sieh dir die entsprechenden Funktionen in der numeric_std an. Du wirst 
den Unterschied leicht erkennen...

von Schlumpf (Gast)


Lesenswert?

Reinhard J. schrieb:
> Ist das dann ein 16 Bit Integer vom Typ int16_t für den AVR?

Soviel ich weiss, arbeitet der AVR little endian. Also passt es.

von Reinhard J. (rvj)


Lesenswert?

Hallo Schlumpf, hallo Lothar,

mein Urlaub geht zu Ende und nun wird es einige Zeit dauern bis ich das 
Projekt fertigstellen kann. Bislang läuft das noch nicht richtig.

Mein VHDL habe ich nun mit unsigned also
1
--  signal t_cnt1        : integer range -32768 to 32767; -- nimm eingeschränkte Integer zum Zählen!
2
  signal t_cnt1        : integer range 0 to 65535; -- nimm eingeschränkte Integer zum Zählen!
3
  signal t_cnt2        : std_logic_vector (15 downto 0);
4
--  signal t_cnt2_signed  : signed(15 downto 0);
5
  signal t_cnt2_unsigned  : unsigned(15 downto 0);
Ich setze dann bei Reset und beim Auslesen
1
          t_cnt1 <= 32767;

Weter oben kam die Frage auf, wie ich die Anbindung an den AVR gestalte. 
Das will ich nun auch erläutern.

Hardwaremäßig habe ich zwei Kanäle, also zwei CPLDs, auf Lochraster mit 
einem Atmega32 verbunden. Die COUNT Ausgänge der CPLDs sind an PIND bzw. 
an PINC des Atmega32 angeschlossen. GET0 und GET1 des ersten CPLD hängen 
an PORTB.0 und PORTB.1, die des zweiten CPLD an PORTB.5 und PORTB.6. Die 
Resets werden über Taster bedient, die für CPLD1 an PINB.3 und für CPLD2 
an PINA.6 hängen. Der Atmega32 gibt dann die Resets über PORTB.4 an 
CPLD1 und über PORTA.7 an CPLD2 weiter. Sowie ich eine elektronische 
Zeichnung erstellt habe, lade ich diese hoch.

Unten steht der derzeitige AVR C-Code, welcher noch nicht richtig läuft. 
Für die Interessierten poste ich ihn dennoch. Ich werde sowie ich hier 
weitermache den Fortschritt berichten, sage aber schon jetzt, dass das 
dauern kann.

In meiner Woche Urlaub habe ich eine Menge von den Beiträgen in diesem 
Thread gelernt, was mir das Wichtigste war, und ich bedanke mich bei 
allen insbesondere aber bei Lothar und Schlumpf aufs herzlichste.

Mittlerweile steht mir ein Xilinx 3AN Starter Kit zur Verfügung. Keine 
Ahnung wie man damit loslegt. Das Lernen steht erst mal im Vordergrund 
und es kann sein, dass ich die Versuche mit dem Board diesem Projekt 
vorziehe, da mir jede Menge Kenntnisse zu VHDL fehlen. Ich denke ich 
eröffne einen Thread, falls ich keinen Einstieg finde. Die AVRs hatte 
ich mit myAVR kennengelernt und da gab es ein kleines Heft. In dem 
Xilinx 3AN Starter Kit liegt nichts Schriftliches. Muss mal sehen, was 
die auf Ihrer web-Seite zur Verfügung stellen.

Viele Grüße
Reinhard

PS
@Schlumpf: Kennst Du Peyo? Mein Favorit ist "Der Astronautenschlumpf".
1
void ResetCPLD( uint8_t CPLDNumber)
2
{
3
  if (CPLDNumber == 1)
4
  {
5
    // CPLD1
6
    // Clear the GET-signal bits on Port B
7
    PORTB &= ~(1 << 0);    // Clear GET0
8
    PORTB &= ~(1 << 1);    // Clear GET1
9
10
    // Reset CPLD1 - i.e. Set the CPLD1 counter to 0
11
    PORTB &= ~(1 << 4);    // Clear RESET for CPLD1
12
    PORTB |= (1 << 4);    // set bit 4 in PORTB - Reset the CPLD1
13
    _delay_ms(1);
14
    PORTB &= ~(1 << 4);    // clear bit 4 in PORTB
15
  }
16
  else
17
  {
18
    // CPLD2
19
    // Clear the GET-signal bits on Port B
20
    PORTB &= ~(1 << 5);    // Clear GET0
21
    PORTB &= ~(1 << 6);    // Clear GET1
22
        
23
    // Reset CPLD2 - i.e. Set the CPLD2 counter to 0
24
    PORTA &= ~(1 << 7);    // Clear RESET for CPLD2
25
    PORTA |= (1 << 7);    // set bit 7 in PORTA - Reset the CPLD2
26
    _delay_ms(1);
27
    PORTA &= ~(1 << 7);    // clear bit 7 in PORTA
28
  }
29
}
30
31
32
void UpdateDisplay( uint8_t  iCPLDNumber, 
33
        uint8_t  *iGet0State, 
34
        volatile uint8_t *iGet0Port, 
35
        uint8_t iGet0Pin, 
36
        uint8_t *iGet1State, 
37
        volatile uint8_t *iGet1Port, 
38
        uint8_t iGet1Pin,
39
        volatile uint8_t *iDataInputPort,
40
        int32_t  *int32DisplayValue,
41
        int iDisplayColumnStart,
42
        int iDisplayRowStart)
43
{
44
  char  strDisplayValue[10];      // 8 characters plus \0
45
  int16_t  int16CounterValue = 0;
46
  int32_t int32CounterValue = 0;
47
      
48
  // Initialize string
49
  strDisplayValue[0] = ' ';
50
  strDisplayValue[1] = ' ';
51
  strDisplayValue[2] = ' ';
52
  strDisplayValue[3] = ' ';
53
  strDisplayValue[4] = ' ';
54
  strDisplayValue[5] = ' ';
55
  strDisplayValue[6] = ' ';
56
  strDisplayValue[7] = ' ';
57
  strDisplayValue[8] = ' ';
58
  strDisplayValue[9] = '\0';
59
  
60
  uint8_t lsb;
61
  uint8_t msb;
62
  
63
  if(*iGet0State == 0)
64
  {
65
    *iGet0Port |= (1 << iGet0Pin);    // Set GET0
66
    *iGet0State = 1;                  // Update State
67
  }
68
  else
69
  {
70
    *iGet0Port &= ~(1 << iGet0Pin);   // Clear GET0
71
    *iGet0State = 0;                  // Update State
72
  }
73
  _delay_ms(1);
74
  lsb = *iDataInputPort;              // Receive the least significant byte
75
76
  if(*iGet1State == 0)
77
  {
78
    *iGet1Port |= (1 << iGet1Pin);    // Set GET1
79
    *iGet1State = 1;                  // Update State
80
  }
81
  else
82
  {
83
    *iGet1Port &= ~(1 << iGet1Pin);   // Clear GET1
84
    *iGet1State = 0;                  // Update State
85
  }
86
  _delay_ms(10);
87
  msb = *iDataInputPort;              // Receive the most significant byte
88
  _delay_ms(1);
89
90
  int16_t combined = (lsb << 8) | msb;
91
  int32_t combined32 = (int32_t)combined - 32767;
92
93
  int32_t_itoa(combined32, strDisplayValue, sizeof(strDisplayValue));
94
    
95
  lcd_setcursor( iDisplayColumnStart, iDisplayRowStart );
96
  lcd_string(strDisplayValue);
97
  wdt_reset();
98
}
99
100
void CheckReset(  volatile uint8_t *iResetInPort,
101
      uint8_t iResetInPin,
102
      volatile uint8_t *iResetOutPort,
103
      uint8_t iResetOutPin,
104
      int32_t  *int32DisplayValue)
105
{
106
  // Check Reset PIN and React
107
  if ( ( *iResetInPort & ( 1 << iResetInPin ) ) ==  0b00000000 )
108
  {
109
    *iResetOutPort |= (1 << iResetOutPin);    // set bit 
110
    *int32DisplayValue = 0;
111
    _delay_ms(10);
112
    *iResetOutPort &= ~(1 << iResetOutPin);   // clear bit 
113
  }
114
}
115
116
117
int main(void)
118
{  
119
  
120
  uint8_t iCPLD1No = 1;
121
  uint8_t iCPLD1Get0Pin = 0;
122
  uint8_t iCPLD1Get1Pin = 1;
123
  uint8_t  iCPLD1StateGET0 = 0;
124
  uint8_t  iCPLD1StateGET1 = 0;  
125
  int32_t  int32CPLD1DisplayValue = 0;
126
  uint8_t iCPLD1DisplayRowStart = 1;
127
  uint8_t iCPLD1DisplayColumnStart = 6;
128
129
  uint8_t iCPLD2No = 2;
130
  uint8_t iCPLD2Get0Pin = 5;
131
  uint8_t iCPLD2Get1Pin = 6;
132
  uint8_t  iCPLD2StateGET0 = 0;
133
  uint8_t  iCPLD2StateGET1 = 0;
134
  int32_t  int32CPLD2DisplayValue = 0;
135
  uint8_t iCPLD2DisplayRowStart = 2;
136
  uint8_t iCPLD2DisplayColumnStart = 8;
137
138
    
139
   init();             // PORT and PIN initializations
140
  
141
  lcd_init();            
142
  
143
  lcd_clear();
144
  lcd_setcursor( 0, 1 );
145
  lcd_string("Ch. 1: ");
146
147
  lcd_setcursor( 0, 2 );
148
  lcd_string("Ch. 2: ");
149
150
  ResetCPLD(1);
151
  ResetCPLD(2);
152
  
153
  wdt_enable(WDTO_2S);
154
  
155
    while(1)
156
    {
157
    wdt_reset();
158
    
159
    UpdateDisplay(  iCPLD1No,      // iCPLDNumber
160
        &iCPLD1StateGET0,          // *iGet0State
161
        &PORTB,                    // *Get0Port
162
        iCPLD1Get0Pin,             // iGet0Pin
163
        &iCPLD1StateGET1,          // *iGet1State
164
        &PORTB,                    // *iGet1Port
165
        iCPLD1Get1Pin,             // iGet1Pin
166
        &PIND,                     // *iDataInputPort
167
        &int32CPLD1DisplayValue,   // int32DisplayValue
168
        iCPLD1DisplayColumnStart,  // iDisplayColumnStart
169
        iCPLD1DisplayRowStart );   // iDisplayRowStart
170
    
171
    
172
    CheckReset(  &PINB,            // *iResetInPort,
173
        (uint8_t) 3,               // iResetInPin,
174
        &PORTB,                    // *iResetOutPort,
175
        (uint8_t) 4,               // iResetOutPin,
176
        &int32CPLD1DisplayValue ); 
177
178
    UpdateDisplay(  iCPLD2No,      // iCPLDNumber
179
        &iCPLD2StateGET0,          // *iGet0State
180
        &PORTB,                    // *Get0Port
181
        iCPLD2Get0Pin,             // iGet0Pin
182
        &iCPLD2StateGET1,          // *iGet1State
183
        &PORTB,                    // *iGet1Port
184
        iCPLD2Get1Pin,             // iGet1Pin
185
        &PINC,                     // *iDataInputPort
186
        &int32CPLD2DisplayValue,   // int32DisplayValue
187
        iCPLD2DisplayColumnStart,  // iDisplayColumnStart
188
        iCPLD2DisplayRowStart );   // iDisplayRowStart
189
    
190
    CheckReset(  &PINA,            // *iResetInPort,
191
        (uint8_t) 6,               // iResetInPin,
192
        &PORTA,                    // *iResetOutPort,
193
        (uint8_t) 7,               // iResetOutPin,
194
        &int32CPLD2DisplayValue ); 
195
    }   
196
}

von Schlumpf (Gast)


Lesenswert?

Schlumpf schrieb:
> Soviel ich weiss, arbeitet der AVR little endian. Also passt es.

Ich habe total ignoriert, dass du dir die Werte über die 8-Bit GPIOs des 
AVR reinnuckelst. Da spielt dann die Endianess des Controllers auch gar 
keine Rolle.
Eigentlich bist du mit deiner Vorgehensweise schon auf halben Weg, ein 
anständiges Businterface zu implementieren. Denn GETxy ist quasi eine 
Adresse und COUNT ist der Datenbus.
Wenn du es jetzt noch geschickt anstellst, brauchst du gar nicht so 
viele Pins am Controller verbraten.

Für ein "richtiges" Businterface würdest du folgendes benötigen
CE-Signal zum Auswählen des CPLDs
Adressbus (In deinem Fall reichen A0..A2)
Datenbus (mit Tri-State-Ausgang)
Und das war´s eigentlich schon. Auf OE kannst du verzichten, da du das 
Deaktivieren der Datentreiber auch über CE bewerkstelligen kannst und 
nachdem du nichts schreiben willst, kannst du auf das WE auch 
verzichten.
Eventuell brauchst du noch ein ALE-Signal, wenn du einen gemultiplexten 
Bus verwendest.

Klingt jetzt vielleicht bisschen verwirrend, wenn du dich damit noch nie 
befasst hast, aber wäre in deinem Fall die elegantere Lösung, um an die 
Daten in deinen CPLDs ranzukommen.

Vorallem brauchst du dann in deinem C-Code nur einen Pointer auf die 
gewünschte Adresse anlegen und darin steht dann direkt der Wert. Du 
musst also nicht erst "manuell" Ports einlesen, sondern die Werte stehen 
dann quasi direkt in der Variable.

Aber so wie du das vor hast, geht das natürlich auch. Ist nur ein 
bisschen umständlicher.

Falls es dich interessiert, deine CPLDs an das Businterface deines 
Controllers zu legen, bekommst du hier auch sicher Hilfe.

von Reinhard J. (rvj)


Lesenswert?

Hallo Schlumpf, hallo Lothar,

erinnert ihr Euch noch an das Projekt? Ich habe wieder Zeit gefunden, um 
mich damit zu beschäftigen.

Die Kommunikation habe ich wie oben beschrieben realisiert. Um es 
vorwegzunehmen: Es klappt noch nicht.

Ich lasse mir die Encoderzählwerte auf einem LCD vom AVR anzeigen. 
Leider liefen die Werte auch ohne dass Encodersignale am CPLD variiert 
wurden. Darum habe ich versucht vermittels einer Eingrenzung den Fehler 
zu lokalisieren.

In der architecture habe ich
1
architecture Behavioral of Counter is
2
   signal t_cnt1        : integer range 0 to 65535; -- nimm eingeschränkte Integer zum Zählen!
3
   signal t_cnt2        : std_logic_vector (15 downto 0);
4
5
   signal StateOfGet0   : std_logic;
6
   signal StateOfGet1   : std_logic;
7
8
   signal GET0         : std_logic;
9
   signal GET0mitte     : std_logic;
10
   signal GET1         : std_logic;
11
   signal GET1mitte     : std_logic;
12
   signal RESET       : std_logic;
13
   signal RESETmitte     : std_logic;
14
  
15
   signal t_cnt2_unsigned  : unsigned(15 downto 0);


alles wird einsynchronisiert
1
-- einsynchronisieren
2
   GET0mitte   <= GET0in;
3
   GET0       <= GET0mitte;
4
   GET1mitte   <= GET1in;
5
   GET1       <= GET1mitte;

Und nun der Test

1. Test
-------
1
if ( rising_edge(clk) ) then
2
    COUNT <=  "11101000";  
3
end if;
Da das Programm zweimal liest und zweimal die gleichen Byte zu einem 
int16_t zusammenbaut entsteht 0b1110100011101000 was dezimal 59624 
entspricht. Das wird korrekt angezeigt. Der AVR scheint alles korrekt zu 
erledigen.

2. Test
-------
1
if ( rising_edge(clk) ) then
2
    t_cnt1 <= 100;
3
    t_cnt2_unsigned <= to_unsigned(t_cnt1, 16);
4
    t_cnt2 <= std_logic_vector(t_cnt2_unsigned);
5
    COUNT  <=  t_cnt2( 7 downto 0);
6
end if;
100 das ist 0b01100100. Wieder wird zweimal gelesen und zweimal die 
gleichen Byte zu einem int16_t zusammengebaut und es entsteht 
0b110010001100100 wasn dezimal 25700 entspricht. Auch das wird korrekt 
angezeigt.

3. Test
-------
1
if ( rising_edge(clk) ) then
2
    if ( GET0 /= StateOfGet0 ) then
3
        StateOfGet0  <= GET0;
4
        t_cnt1 <= 100;
5
        t_cnt2_unsigned <= to_unsigned(t_cnt1, 16);
6
        t_cnt2 <= std_logic_vector(t_cnt2_unsigned);
7
        COUNT  <=  t_cnt2( 7 downto 0);
8
    elsif ( GET1 /= StateOfGet1 ) then
9
        StateOfGet1  <= GET1;
10
        t_cnt1 <= 50;
11
        t_cnt2_unsigned <= to_unsigned(t_cnt1, 16);
12
        t_cnt2 <= std_logic_vector(t_cnt2_unsigned);
13
        COUNT  <=  t_cnt2( 7 downto 0);
14
    end if;
15
end if;
Der AVR toggelt zuerst GET0 und liest das LSB das sind 100 bzw. 
0b01100100. Nun wird GET1 getoggelt und die 50 werden gelesen, was 
0b00110010 entspricht, und ins MSB gepackt wird. Nach dem Zusammenbau 
des int16_t haben wir also 0b0011001001100100 und das sind 12900. Hier 
fängt die Anzeige an zu spinnen.

Sie zeigt folgende Zahlen in loser Reihenfolge
13107: 0b0011 0011 0011 0011
25907: 0b0110 0101 0011 0011
und einige Zahlen mehr und nur ab und zu 12900.

Meine Frage lautet nun können die Signale GET0 und GET1 so wie ein 
Schalter prellen und mir die Kommunikation zerstören?

Hat jemand eine Idee. Ich überlege weitere Tests.

Schon jetzt Danke für alle Tipps.

Gruß Reinhard

von Schlumpf (Gast)


Lesenswert?

Welcome back ;-)

Du hast da einen bösen Denkfehler drin.

Dieses Konstrukt baut dir 4 Registerstufen.
1
if ( rising_edge(clk) ) then
2
    t_cnt1 <= 100;
3
    t_cnt2_unsigned <= to_unsigned(t_cnt1, 16);
4
    t_cnt2 <= std_logic_vector(t_cnt2_unsigned);
5
    COUNT  <=  t_cnt2( 7 downto 0);
6
end if;
Es dauert also 4 Takte, bis die 100 dann endlich auf COUNT ausgegeben 
werden.
Das merkst du bei Test1 und Test2 nicht, da die Werte immer die gleichen 
sind.

Bei Test3 holt dich das dann allerdings ein.
1
if ( GET0 /= StateOfGet0 ) then
ist nur einen Takt lang "erfüllt". Mit diesem einen Takt wird dann alles 
weitere ausgeführt.
1
StateOfGet0  <= GET0;  --  aktueller Zustand von GET0 wird gespeichert
2
t_cnt1 <= 100;  -- 100 wird in t_cnt1 geschrieben
ABER in den folgenden Zeilen beschreibst du t_cnt2_unsigned nicht mit 
dem in der vorangegengenen Zeile zugewiesenen t_cnt1, sondern mit dem 
Wert der VOR der letzten Zuweisung von t_cnt1 gespeichert wurde.
Und der war vermutlich der Wert, der beim Zugriff über GET1 übernommen 
wurde, da du in beiden Zugriffen mit t_cnt1 arbeitest.

Und mit dem nächsten Takt passiert nichts. Gar nichts. Denn dann ist 
keine der beiden IF-Bedingungen erfüllt. Somit "wandert" der Wert nicht 
weiter durch die Register.

Nehmen wir mal an, du hättest nur folgendes geschrieben:
1
if ( rising_edge(clk) ) then
2
    if ( GET0 /= StateOfGet0 ) then
3
        StateOfGet0  <= GET0;
4
        t_cnt1 <= 100;
5
        t_cnt2_unsigned <= to_unsigned(t_cnt1, 16);
6
        t_cnt2 <= std_logic_vector(t_cnt2_unsigned);
7
        COUNT  <=  t_cnt2( 7 downto 0);
8
    end if;
9
end if;
Dann müsstest du GET0 vier mal toggeln lassen, bis die 100 an COUNT 
ausgegeben werden.
Da du aber deine Registerbänke bei deinem Zugriff mittels GET1 immer 
wieder überschreibst, kommt alles durcheinander.

von Reinhard J. (rvj)


Lesenswert?

Hallo Schlumpf,

herzlichsten Dank für die Antwort!

Das lässt mir einfach keien Ruhe. Möchte das Projekt nur ungern aufgeben 
bevor ich mich an mein Spartan 3AN board setze. Bestimmt brauche ich da 
auch jede Menge Hilfe.

Deine Antwort muss ich noch mehrfach lesen, denn bis jetzt habe ich sie 
nicht wirlich begriffen, wie der nachfolgende Test zeigt.

Ich habe jetzt folgendes probiert:
1
if ( rising_edge(clk) ) then
2
   if ( GET0 /= StateOfGet0 ) then
3
      t_cnt1          <= 100;
4
      t_cnt2_unsigned <= to_unsigned(t_cnt1, 16);
5
      t_cnt2          <= std_logic_vector(t_cnt2_unsigned);
6
      COUNT        <=  t_cnt2( 7 downto 0);
7
      StateOfGet0     <= GET0;
8
   end if;
9
   if ( GET1 /= StateOfGet1 ) then
10
      t_cnt1          <= 50;
11
      t_cnt2_unsigned <= to_unsigned(t_cnt1, 16);
12
      t_cnt2          <= std_logic_vector(t_cnt2_unsigned);
13
      COUNT        <=  t_cnt2( 7 downto 0);
14
      StateOfGet1     <= GET1;
15
   end if;
16
else
17
   null;
18
end if;

Jetzt schieße ich das Bit erst am Ende um und es geht trotzdem nicht.

Müsste es jetzt nicht gehen, da ich die Bedingung bis zum Ende aufrecht 
erhalte?

Ich lese mir Deine Hinweise morgen früh noch ein paarmal durch. 
Vielleicht komme ich dann weiter. Wir werden sehen.

VHDL lernen braucht wohl Ausdauer. Ist kniffelig, aber macht Schpass.

Danke und Gruß
Reinhard

von Schlumpf (Gast)


Lesenswert?

Reinhard J. schrieb:
> Müsste es jetzt nicht gehen, da ich die Bedingung bis zum Ende aufrecht
> erhalte?

Nein, die Reihenfolge der Zeilen ist hier egal ;-)
Das ist VHDL. Du beschreibst eine Hardware und keinen sequenziellen 
Ablauf an Befehlen.
Das ist das Erste, was du begreifen musst. Wenn da mal der Groschen 
gefallen ist, dann geht´s besser ;-)
1
if ( rising_edge(clk) ) then
2
    if ( A /= B ) then
3
        B <= A;
4
        x <= 100;
5
        y <= x;
6
        z <= y;
7
        Q <= z
8
    end if;
9
end if;

Damit beschreibst du ein Register B, x, y, z und Q, welche alle mit clk 
getaktet sind, und alle ihre Eingänge nur dann übernehmen, wenn der 
Ausgang von Register B und das Signal A ungleich sind.
Weiterhin beschreibst du, dass am Eingang des B-Registers das Signal A 
anliegt, am Eingang des x-Registers liegt 100 an, am Eingang des 
y-Registers liegt der Ausgang des x-Registers an und so weiter...

Zeichne mal den Schaltplan dazu und dann wirst du sehen, warum das nicht 
das tut, was du dir vorstellst.

von rvj (Gast)


Lesenswert?

Hallo Schlumpf,

mit dem Schaltplan zeichen habe ich es noch nicht so. Das muss ich 
lernen. Die Xilinx ISE zeichnet mir ja so eine Übersicht mit so 
Blackboxen, in die Signale rein- und rausgehen. Aber Du meinst bestimmt 
eine Zeichnung, welche die architecture feiner auflöst.

Deine Antwort leuchtet mir ein und ich wusste das auch. Nur hatte ich 
vergessen die process Statements mit reinzukopieren. Ich habe gelesen, 
dass die Statements eines process sequentiell abgearbeitet werden. Darum 
verstehe ich nicht, warum es nicht klappt.

Falls das mit der sequentiellen Abarbeitung innerhalb eines process 
stimmt, kannst Du mir bitte auch noch erklären wie die Abarbeitung 
zeitlich verläuft. Unten habe ich nun den process komplett eingefügt. 
Bisher nahm ich an, der ganze process wird in einem Takt abgespult. 
Anscheinend trifft das nicht zu. Mir fehlen da noch einige 
Hardware-Kenntnisse und ich hoffe das bald beheben zu können.

Braucht es für die Abarbeitung der unten gezeigten Statements fünf clock 
cycles oder funktioniert ein process völlig anders?

Falls eine sequentielle Abarbeitung in fünf clock cycles geschieht, 
müsste es nicht so sein, dass die if-Bedingungen erst mit Umknipsen der 
StateOfGet0- und StateOfGet1-Bits nicht mehr betreten werden? Mit 
anderen Worten: Wenn die statements nach und nach abgekaspert werden, 
dann muss der process doch funktionieren. Nur das tut er nicht.

Hab wirklich vielen Dank für die Bemerkung zu den concurrent statements. 
Mir macht aber derzeit die Abarbeitung der processes mehr Kopfweh. Bei 
einem Controller ist das halbwegs klar, da steht die Adresse des 
nächsten Befehls im Program Counter und damit gehts nach der 
Bearbeitung, d.h. je nach Op-Code nach einer festen Anzahl von clock 
cycles weiter. Wie ist das aber in Hardware? Ich bin ratlos.

Ohne derart hilfreiche Recken wie Lothar und Dich käme ich in Sachen 
VHDL gar nicht weiter.

Herzliche Grüße
Reinhard
1
CounterHandling: process (clk) is  -- reagiert auf clk
2
begin
3
if ( rising_edge(clk) ) then
4
   if ( GET0 /= StateOfGet0 ) then
5
      t_cnt1          <= 100;                                  -- 1
6
      t_cnt2_unsigned <= to_unsigned(t_cnt1, 16);              -- 2
7
      t_cnt2          <= std_logic_vector(t_cnt2_unsigned);    -- 3
8
      COUNT        <=  t_cnt2( 7 downto 0);                    -- 4
9
      StateOfGet0     <= GET0;                                 -- 5
10
   end if;
11
   if ( GET1 /= StateOfGet1 ) then
12
      t_cnt1          <= 50;                                   -- 1
13
      t_cnt2_unsigned <= to_unsigned(t_cnt1, 16);              -- 2
14
      t_cnt2          <= std_logic_vector(t_cnt2_unsigned);    -- 3
15
      COUNT        <=  t_cnt2( 7 downto 0);                    -- 4
16
      StateOfGet1     <= GET1;                                 -- 5
17
   end if;
18
else
19
   null;
20
end if;
21
end process CounterHandling;

von Schlumpf (Gast)


Lesenswert?

rvj schrieb:
> mit dem Schaltplan zeichen habe ich es noch nicht so. Das muss ich
> lernen.

Das ist aber die Grundlage, um überhaupt mit VHDL arbeiten zu können.
Mit VHDL beschreibt man eine Schaltung und programmiert nicht. Wenn man 
aber von Schaltungstechnik keine Ahnung hat, dann wird man auch daran 
scheitern, eine Schaltung in VHDL beschreiben zu wollen :-(

Aber zu deinem "Problem":
Ein Process wird so interpretiert, dass die letzte Zuweisung auf ein 
Signal "gilt". Alles davor wird quasi überschrieben. Und jetzt kommt´s: 
Die eigentliche "Übernahme" erfolgt beim Verlassen des Process.

Und nochmal: Der Process "läuft" nicht im FPGA ab, sondern er ist eine 
Methode, wie durch bestimmte syntaktische Konstrukte und Regeln das 
Verhalten einer Schaltung modelliert wird. Der Synthesizer interpretiert 
daraus dann, wie die Schaltung aussehen muss.
Auf der echten Hardware gibt es keinen Process mehr, es wird auch kein 
Process verlassen, es werden auch keine Signale zu einem bestimmten 
Zeitpunkt aktualisiert etc..
Jegliche sequenzielle Funktion muss in echter Hardware über Register 
beschrieben werden.
Also auch ein Process ohne clock wird in Hardware vollkommen concurrent 
abgebildet.
Es spielt also keine Rolle, ob du einen Zusammenhang concurrent oder in 
einem ungetakteten Process beschreibst. Solange du den gleichen 
Zusammenhang beschreibst, macht die Synthese immer eine flache, 
kombinatorische Verknüpfung draus. In einem Process hast du nur andere 
syntaktische Elemente als in einem concurrent Statement.

Beispiel:
1
process (EN, D)
2
begin
3
  if EN = '1' then
4
    Q <= D;
5
  else
6
    Q <= 'Z';
7
  end if;
8
end process;
ist exakt das gleiche wie:
1
Q <= D when EN = '1' else 'Z';
Beide Konstrukte führen dazu, dass die Synthese einen Tristate-Buffer 
instanziiert, den Eingang an D, den Ausgang an Q und den high-aktiven 
Enable-Eingang an EN anschließt.

von Duke Scarring (Gast)


Lesenswert?

Du hast ja vieles richtig geschrieben, aber der letzte Punkt hier stimmt 
nicht:

Schlumpf schrieb:
> Auf der echten Hardware gibt es keinen Process mehr, es wird auch kein
> Process verlassen, es werden auch keine Signale zu einem bestimmten
> Zeitpunkt aktualisiert etc.
Natürlich gibt es einen bestimmten Zeitpunkt, zu dem die Signale 
aktualisiert werden. Die Taktflanke bestimmt den Zeitpunkt, zu dem ein 
Speicherelement (FlipFlop, Register) die Werte am Eingang übernimmt und 
am Ausgang zur Verfügung stellt.

Wenn es keine Flanke gibt, wird eine kombinatorische Schaltung 
synthetisiert.

Duke

von peter (Gast)


Lesenswert?

He..., bau dir mal diese Baugruppen mit VHDL als Componenten:

and   N-input AND gate
nand  N-input NAND gate
or    N-input OR gate
nor   N-input NOR gate
xor   N-input XOR gate
xnor  N-input XNOR gate

not    N-output inverter
buf    N-output buffer.
bufif0 Tri-state buffer, Active low en
bufif1 Tri-state buffer, Active high en
notif0 Tristate inverter, Low en
notif1 Tristate inverter, High en

Dann schaust du im RTL-Viewer wie sie aussehen und machst deinen 
Schaltplan
So habe ich es in Verilog gemacht mit Quartus zb.

Danach habe ich eine Schaltplan aus den Gattern und Tristates gezeichnet
und in Verilog umgesetzt, es ist ein Minirechner geworden.

Gruss

von peter (Gast)


Angehängte Dateien:

Lesenswert?

Hier sind die Baugruppen, die ich im RTL-Viewer habe von Quartus.
Bei mir in Verilog sind es Module , bei dir in VHDL sind es Componenten.
Ich habe mir noch viele andere Module entworfen.

Diese fertigen Module lade ich ein und verdrahte die mit "wire", fertig 
ist die Schaltung.

GRuss

von Schlumpf (Gast)


Lesenswert?

Duke Scarring schrieb:
> Natürlich gibt es einen bestimmten Zeitpunkt, zu dem die Signale
> aktualisiert werden. Die Taktflanke bestimmt den Zeitpunkt, zu dem ein
> Speicherelement (FlipFlop, Register) die Werte am Eingang übernimmt und
> am Ausgang zur Verfügung stellt.

Richtig, aber es gibt keinen Process auf der HW. Es gibt eine 
Process-Beschreibung, die ein Register erzeugt, das sich so verhält, wie 
du geschrieben hast. Es gibt aber auch ungetaktete Prozesse, die dann zu 
kombinatorischer Logik führen.
Aber auf was ich raus wollte ist, dass es eben auf der HW nicht einfach 
ein "sequenzieller" Ablauf abgebildet wird, nur weil in einem Process 
die Zuweisungen sequenziell untereinander stehen.
Vielleicht etwas verwirrend von mir formuliert.

von Schlumpf (Gast)


Lesenswert?

Ich denke, du hast dich auf "den bestimmten Zeitpunkt" bezogen, den es 
natürlich gibt, wenn man einen getakteten Process verwendet.
In dem Kontext als ich diese Aussage machte, ging es aber darum, dass in 
einem Process die Signale erst am Ende des Process aktualisiert werden.
Darauf bezog ich mich. Ich wollte darstellen, dass es diesen Zeitpunkt 
"Ende des Process" schlichtweg nicht gibt. Anders als in Software, wo es 
sehr wohl den Zeitpunkt gibt, wenn ein Programm oder eine Funktion ihr 
Ende erreicht hat.

Dieses Übernahme der Daten am Ende des Process gibt es nur in der 
Beschreibung. In "echt" wird daraus entweder eine kombinatorische 
Verknüpfung die (abgesehen von Signallaufzeiten) keinen Anfang und kein 
Ende hat und somit auch keinen Übernahmezeitpunkt. Oder es wird ein 
Register gebildet, das den Übernahmezeitpunkt mit der aktiven Taktflanke 
hat. Aber auch dieses Register wartet nicht auf das Ende des Process.

Ich denke, das ist jedem so sonnenklar, der Erfahrung mit HDL hat, aber 
ein Anfänger, der vielleicht sogar vorher Software geschrieben hat, tut 
sich da sicher nicht ganz leicht.

Das Problem ist einfach das Verständnis, dass eine sequenzielle 
Beschreibung (also Codezeile unter Codezeile) eben nicht zu einem 
sequenziellen Ablauf im Chip führt, sondern sequenzielle Abläufe nur 
durch Register gebildet werden können, die dann aber auch dediziert 
beschrieben werden müssen.

Und nur der Vollständigeit halber (Reinhard, bitte das am Besten nicht 
lesen). Natürlich kann man auch concurrent sequenzielles Verhalten 
beschreiben, aber das würde dann nur über Signallaufzeiten im Chip 
funktionieren, was absoluter Murks ist.

von Duke Scarring (Gast)


Lesenswert?

@Schlumpf:
Du hast das schon gut gemacht, mit Deiner Darstellung. Meine Bemerkung 
war wirklich etwas spitzfindig und in diesem Kontext nicht richtig.

Duke

von Schlumpf (Gast)


Lesenswert?

Duke Scarring schrieb:
> Du hast das schon gut gemacht, mit Deiner Darstellung. Meine Bemerkung
> war wirklich etwas spitzfindig und in diesem Kontext nicht richtig.

Ich habe sie nicht als spitzfindig empfunden.

Mir gab es nur nochmal nen Impuls nachzuschauen, ob ich mich vielleicht 
missverständlich ausgedrückt habe.
Ist doch gut, wenn es solche Kommentare gibt. Wenn man etwas erklärt, 
was einem selbst klar ist, kann es schon passieren, dass man sich 
ungeschickt ausdrückt, so dass es missverständlich sein kann.

Also alles prima ;-)

Habe beim Schreiben erst selbst gemerkt, dass es gar nicht so einfach 
ist, den Unterschied zwischen sequenzieller Beschreibung (also Process) 
und tatsächlich sequenzieller Hardware für einen Anfänger plastisch 
darzustellen.

von rvj (Gast)


Lesenswert?

Hallo Schlumpf,

nach mehrfacher Lesung Deiner Kommentare versuche ich eine naive 
Vorgangsbeschreibung.

Schlumpf schrieb am 12.11. um 16:50
> In dem Kontext als ich diese Aussage machte, ging es aber darum,
> dass in einem Process die Signale erst am Ende des Process
> aktualisiert werden.

Nehmen wir mal an wir haben folgendes:
1
CounterHandling: process (clk) is  -- reagiert auf clk
2
begin
3
if ( rising_edge(clk) ) then
4
      B <= A;  -- 1
5
      C <= B;  -- 2
6
      D <= C;  -- 3
7
      StateOfGet0 <= GET0; -- 4
8
end if;
9
end process CounterHandling;

Zum Zeitpunkt t=0 seien
A  "00000001"
B  "00000000"
C  "00000000"
D  "00000000"
GET0        '1'
StateOfGet0 '0'

Jetzt gehen wir taktweise durch unter der Annahme, dass sich A und GET0 
über dem gesamten betrachteten Zeitverlauf nicht ändern.

nach erstem Takt hat D das was in C stand übernommen, C was in B stand 
und B was in A stand.
A  "00000001"
B  "00000001"
C  "00000000"
D  "00000000"
GET0        '1'
StateOfGet0 '1'

nach zweitem Takt gehts genauso weiter
A  "00000001"
B  "00000001"
C  "00000001"
D  "00000000"
GET0        '1'
StateOfGet0 '1'

und nach drittem Takt ist in D endlich "00000001" angekommen.
GET0 '1'
A  "00000001"
B  "00000001"
C  "00000001"
D  "00000001"
GET0        '1'
StateOfGet0 '1'

Kann es sein dass es so ist?

Wenn dem so ist, dann habe ich mich bei meinem Trick mit StateOfGet0 
(siehe  12.11.2014 11:03) selbst aus dem if ausgesperrt (siehe unten). 
Eigentlich will ich erreichen, dass die 100 nur einmal kopiert wird und 
nicht während der gesamten Zeitdauer in der GET0 einen bestimmten Wert 
annimmt.
1
if ( GET0 /= StateOfGet0 ) then
2
   t_cnt1          <= 100;
3
   t_cnt2_unsigned <= to_unsigned(t_cnt1, 16);
4
   t_cnt2          <= std_logic_vector(t_cnt2_unsigned);
5
   COUNT           <=  t_cnt2( 7 downto 0);
6
   StateOfGet0     <= GET0;
7
end if;

Kann frühestens morgen Abend weiter machen. Versuch macht klug! Ich 
werde berichten.

Danke an alle für die Diskussion! Extra Dank an Schlumpf!

Gruß Reinhard

von Schlumpf (Gast)


Lesenswert?

rvj schrieb:
> Kann es sein dass es so ist?

Exakt so ist es!

rvj schrieb:
> Wenn dem so ist, dann habe ich mich bei meinem Trick mit StateOfGet0
> (siehe  12.11.2014 11:03) selbst aus dem if ausgesperrt (siehe unten).

Exakt so ist es ;-)
Und dabei ist es egal, ob StateOfGet0 <= GET0; oben, unten oder in der 
Mitte steht.

rvj schrieb:
> Eigentlich will ich erreichen, dass die 100 nur einmal kopiert wird und
> nicht während der gesamten Zeitdauer in der GET0 einen bestimmten Wert
> annimmt.

Das dachte ich mir. Und im Prinzip machst du das ja, indem du deine 
Übernahme nur mit dem Takt machst, bei dem GET0 seinen Zustand wechselt.
ABER du machst dann folgenden Fehler:
Du schiebst deinen übernommenen Wert dann nochmal durch mehrere 
Register. Und diese Register übernehmen ihren Wert aber auch nur, wenn 
GET0 seinen Zustand wechselt. Es Verhält sich dann mit dem Wert so, wie 
du oben selbst dargestellt hast. Es wird jedesmal in das nachfolgende 
Register übernommen, wenn ein Takt kommt UND GET0 bei diesem Takt seinen 
Zustand gewechselt hat.

rvj schrieb:
> Danke an alle für die Diskussion! Extra Dank an Schlumpf!

Sehr gerne geschehen.

Ich schätze dich mal so ein, dass du mit ein paar Schubsern in die 
richtige Richtung selber draufkommen willst, oder? :-)

von Reinhard J. (rvj)


Lesenswert?

Hallo Schlumpf, Peter, Duke und Lothar,

Dank Eurer Hilfe komme ich der Lösung näher.

Schlumpf schrieb:
> Ich schätze dich mal so ein, dass du mit ein paar Schubsern in die
> richtige Richtung selber draufkommen willst, oder? :-)

Exakt so ist es :-)

Jetzt läuft der Wert nicht mehr, bleibt aber auch nicht ruhig stehen. 
Das heißt er wechselt manchmal auf was falsches und zeigt dann wieder 
die richtige Zahl an. Das muss ich mir noch mal anschauen. Ich probiere 
also weiter und berichte.

Unten gibts schon mal den Stand. Behandle nur noch die Zuweisung und den 
state-update im process. Die Augabe wird außerhalb durchgeführt.

Habe das heute Abend ganz schnell probiert. Dauert bestimmt wieder bis 
zum Wochenende bis ich weitermachen kann.

Viele Grüße
Reinhard
1
entity Counter is
2
    Port ( clk       : in  std_logic;                     -- Systemtakt
3
           GET0in    : in  std_logic;                     -- Get first byte
4
           GET1in    : in  std_logic;                     -- Get second byte
5
           EncChange : in  std_logic;                     -- Encoder has changed signal
6
           UpOrDown  : in  std_logic;                     -- Direction indicator
7
           RESETin   : in  std_logic;                     -- Reset
8
           COUNT     : out std_logic_vector (7 downto 0)  -- Output of one byte of the snapshot
9
         );
10
end Counter;
11
12
architecture Behavioral of Counter is
13
        signal t_cnt1          : integer range 0 to 65535; -- nimm eingeschränkte Integer zum Zählen!
14
        signal t_cnt2_unsigned  : unsigned(15 downto 0);
15
        signal t_cnt2           : std_logic_vector (15 downto 0);        
16
        signal StateOfGet0      : std_logic;
17
        signal StateOfGet1      : std_logic;
18
        signal GET0             : std_logic;
19
        signal GET0mitte        : std_logic;
20
        signal GET1             : std_logic;
21
        signal GET1mitte        : std_logic;
22
        signal RESET            : std_logic;
23
        signal RESETmitte       : std_logic;
24
    
25
begin
26
-- einsynchronisieren
27
        GET0mitte   <= GET0in;
28
        GET0        <= GET0mitte;
29
        GET1mitte   <= GET1in;
30
        GET1        <= GET1mitte;
31
        RESETmitte  <= RESETin;
32
        RESET       <= RESETmitte;
33
34
-- Ausgabe mit convert und cast
35
        t_cnt2_unsigned <= to_unsigned(t_cnt1, 16);
36
        t_cnt2 <= std_logic_vector(t_cnt2_unsigned);
37
        COUNT <=  t_cnt2( 7 downto 0);
38
39
        CounterHandling: process (clk) is  -- reagiert auf clk
40
        begin                      
41
           if ( rising_edge(clk) ) then
42
             if ( GET0 /= StateOfGet0 ) then
43
                 t_cnt1 <= 100;
44
                 StateOfGet0  <= GET0;
45
             end if;
46
             if ( GET1 /= StateOfGet1 ) then
47
                 t_cnt1 <= 50;
48
                 StateOfGet1  <= GET1;
49
             end if;
50
           else
51
             null;
52
           end if;
53
54
        end process CounterHandling;
55
 
56
end Behavioral;

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


Lesenswert?

Reinhard J. schrieb:
1
  t_cnt2_unsigned <= to_unsigned(t_cnt1, 16);
2
  t_cnt2 <= std_logic_vector(t_cnt2_unsigned);
Metamorphose vom tcnt1 zum tcnt2? Seis drum, das geht natürlich auch 
kürzer:
1
t_cnt2 <= std_logic_vector(to_unsigned(t_cnt1,16));

> Behandle nur noch die Zuweisung und den state-update im process. Die
> Augabe wird außerhalb durchgeführt.
Das ist egal, es ergibt die gleiche Hardware! Jede Anweisung ausserhalb 
ist ganz einfach in einen Prozess überführbar. Das hier:
1
   t_cnt2 <= std_logic_vector(to_unsigned(t_cnt1,16));
Kann man ohne jegliche Hardwareänderung auch so schreiben:
1
   process (t_cnt1) begin
2
     t_cnt2 <= std_logic_vector(to_unsigned(t_cnt1,16));
3
   end process;

Und zuletzt: schmeiß diese beiden Zeilen unbedingt raus:
1
           else
2
             null;

: Bearbeitet durch Moderator
von Lattice User (Gast)


Lesenswert?

1
-- einsynchronisieren
2
        GET0mitte   <= GET0in;
3
        GET0        <= GET0mitte;
4
        GET1mitte   <= GET1in;
5
        GET1        <= GET1mitte;
6
        RESETmitte  <= RESETin;
7
        RESET       <= RESETmitte;

Ohne Clock ist das nur Umverdrahtung und kein Einsynchronisieren.

von Schlumpf (Gast)


Lesenswert?

Reinhard, du wirst halt einfach immer Schiffbruch erleiden, solange du 
dir nicht die Grundkenntnisse der digitalen Schaltungstechnik aneignest.
Ganz ohne VHDL, sondern mit echten Schaltplänen und Schaltungen.
Erst wenn du dirch da so einigermaßen auskennst, wirst du verstehen, wo 
hier die Probleme sind.

von rvj (Gast)


Lesenswert?

Hallo Schlumpf!
Das sehe ich ein. Kennst Du gut geschriebene Literatur, welche Du 
empfehlen kannst?
Gruß Reinhard

von Schlumpf (Gast)


Lesenswert?

Ich hoffe, du hast das nicht böse aufgefasst. So war es bestimmt nicht 
gemeint.
Aber es bringt nichts, wenn wir von Register und kombinatorischer Logik 
reden, und du nicht weisst, was das ist.
Denn mit VHDL beschreibt man genau solche Strukturen. Es ist keine 
Programmiersprache, auch wenn es auf den ersten Blick so aussehen mag. 
Man beschreibt reale Hardware. Und dazu muss man eine Vorstellung von 
realer Hardware haben.
Natürlich wird dir eine Lösung gelingen, dass in deinem konkreten Fall 
alles so funktioniert, wie du es dir vorgestellt hast. Du kannst dazu 
auch alle syntaktischen Go´s und NoGo´s lernen und kommst so vielleicht 
auch zum Ziel. Aber solange die Vorstellung dessen fehlt, was am Ende 
dabei herauskommt, ist es doch total unbefriedigend und auch nicht 
hilfreich bei der Fehlersuche, wenn mal was nicht klappt.

Konkrete Literatur kann ich dir ad hoc gerade nicht empfehlen. Aber 
Begriffe zum Googlen wäre mal:

"Grundlagen Digitale Schaltungstechnik"
"UND-Gatter"
"OR-Gatter"
"NAND-Gatter"
"NOR-Gatter"
"Register"
"D-FF"
"Daten-FlipFlop"
"Latch"
"Tri-State-Buffer"
z.B. hier:
http://www.elektronik-kompendium.de/sites/dig/index.htm

Du solltest zumindest auf dem Stand sein, dass du die kombinatorischen 
Grundelemente kennst und verstehst, was ein D-FF oder Register ist und 
wie sie funktionieren.
Dann folgen Begriffe wie "Setup-Zeit", "Latency", "Synchrones Design", 
"Statemachine", "Schieberegister", "Counter", "Multiplexer", "LUT".

Wenn du sagst, dass du mit mehr als zwei oder drei der genannten 
Begriffe nichts anfangen kannst, dann fehlen dir einfach die Grundlagen, 
um überhaupt mit VHDL irgendwas Sinnvolles erreichen zu können. Und 
vorallem, zu verstehen, warum dein VHDL-Design funktioniert, oder eben 
auch nicht.

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


Lesenswert?

"VHDL Synthese" von Reichardt&Schwarz ist immer noch gut...

von Schlumpf (Gast)


Lesenswert?

Lothar Miller schrieb:
> "VHDL Synthese" von Reichardt&Schwarz ist immer noch gut...

Defintiv ein gutes Buch. Aber meines Erachtens kein Buch zum Erlernen 
der Grundlagen der Digitaltechnik.
Was ein Register wird, wird in dem Buch als Grundwissen bereits 
vorausgesetzt, oder?

von Schlumpf (Gast)


Lesenswert?

Schlumpf schrieb:
> Was ein Register wird,

Was ein Register IST... :-)

von Reinhard J. (rvj)


Lesenswert?

Hallo zusammen, hallo Schlumpf, hallo Lothar,

erst mal Danke für die Grundlagentipps. Ich beherzige diese. Zu Eurer 
Info: Ich habe die fünfzig schon überschritten, vor langer Zeit Mechanik 
studiert und mit Elektronik und Programmierung so gar nichts am Hut 
gehabt. Irgendwann musste ich mal Matlab lernen und dann kam noch C/C++, 
Python und dies und jenes dazu. Aus purem Zufall habe ich mir dann einen 
ASURO gekauft, das ist so ein kleiner Fahrroboter auf AVR Basis. Der hat 
mich dann gar nicht so interessiert, wohl aber der Atmega8 der da drauf 
ist. Dann begann ich mit kleinen AVR Experimenten, die ich in Assembler 
programmiert habe. Blöd war immer, dass der AVR ein kleines bisschen zu 
lahm war für meine Encodersachen und da habe ich beim Lesen der 
mikrocontroller.net Seiten festgestellt, dass es CPLDs gibt. Jetzt finde 
ich das Thema derart faszinierend, dass ich mich viele Abende mit den 
Sachen aus Interesse und Spaß beschäftige. Natürlich gibt es auch noch 
andere Dinge und so kommt es, dass meine Basteleien manchmal wochenlang 
ruhen - insbesondere bei schönem Wetter.

Zur Erinnerung: Ich baue einen Encoderzähler mit einem Xilinx CPLD 
XC9572XL auf, welcher auf eine Leseaufforderung eines AVR den Zähler an 
8 Pins ausgibt. Da das alles nicht so hingehauen hat, habe ich erst mal 
zum Lernen und Testen ein Byte geschickt, was klappte und dann zwei 
Byte, die von dem AVR zu einem int16_t zusammengebastelt wurden, was 
nicht klappte.

Habe an diesem Wochenende den Thread nochmal gelesen und einiges 
geändert:

Lattice User schrieb am 14.11.2014 11:31
> Ohne Clock ist das nur Umverdrahtung und kein Einsynchronisieren.
Hab ich geändert.

Dann habe ich noch die Zahl in ein unsigned gepackt - da schreibt man 
die Bits hin. Mir als Anfänger hilft es die Bits wirklich zu sehen.

Ich schicke erst das lsb "0000000001010111", also 0x57, und dann das msb 
"0000000001100101", also 0x65. Das gibt dann den uint16_t 0x6557, was 
25943 macht. Und was soll ich sagen, genau das zeigt mein AVR im LCD an.

Ich bin begeistert! Den zugehörigen Code habe ich unten angefügt.

Mein weiteres Vorgehen sieht nun so aus:

1. Ich folge Eurem Rat und lese mich in das Thema ein. CPLDs und FPGAs 
erfordern Hardware-Kenntnisse, die ich mir aneignen muss. Ich beginne 
mit Schlumpfs Empfehlung und schaue mir später Lothars Literaturtipp an. 
Falls jemand weitere Vorschläge für mich hat, würde ich mich freuen.
2. Parallel bastele ich an dem kleinen Projekt weiter und berichte.

Herzliche Grüße

Reinhard


Mein uint Transport sieht auf dem CPLD nun so aus:
1
 
2
architecture Behavioral of Counter is
3
--  signal t_cnt1          : integer range 0 to 65535; -- nimm eingeschränkte Integer zum Zählen!
4
  signal t_cnt1          : unsigned(15 downto 0); --   Zahlenbereich: 0 bis 2**16-1
5
  signal t_cnt2_unsigned  : unsigned(15 downto 0);
6
  signal t_cnt2          : std_logic_vector (15 downto 0);
7
8
  signal StateOfGet0   : std_logic;
9
  signal StateOfGet1   : std_logic;
10
11
  signal GET0         : std_logic;
12
  signal GET0mitte     : std_logic;
13
  signal GET1         : std_logic;
14
  signal GET1mitte     : std_logic;
15
  signal RESET       : std_logic;
16
  signal RESETmitte     : std_logic;
17
    
18
begin
19
20
  CounterHandling: process (clk) is  -- reagiert auf clk
21
  begin                      
22
    -- einsynchronisieren
23
    GET0mitte   <= GET0in;
24
    GET0       <= GET0mitte;
25
    GET1mitte   <= GET1in;
26
    GET1       <= GET1mitte;
27
    RESETmitte   <= RESETin;
28
    RESET      <= RESETmitte;
29
30
    if ( rising_edge(clk) ) then
31
      if ( GET0 /= StateOfGet0 ) then
32
        t_cnt1 <= "0000000001010111";  
33
        StateOfGet0  <= GET0;
34
      end if;
35
      if ( GET1 /= StateOfGet1 ) then
36
        t_cnt1 <= "0000000001100101";
37
        StateOfGet1  <= GET1;
38
      end if;
39
    end if;
40
41
    t_cnt2_unsigned <= t_cnt1;
42
    t_cnt2 <= std_logic_vector(t_cnt2_unsigned);
43
    COUNT  <=  t_cnt2( 7 downto 0);
44
    
45
  end process CounterHandling;
46
 
47
end Behavioral;

Auf dem AVR mach ich dann folgendes
1
  int32_t combined32;
2
3
  uint8_t lsb;
4
  uint8_t msb;
5
6
  union{
7
    uint8_t a[2];
8
    uint16_t b;
9
  }x;
10
  
11
  uint8_t iCPLD1Get0Pin = 0;
12
  uint8_t iCPLD1Get1Pin = 1;
13
  uint8_t  iCPLD1StateGET0 = 0;
14
  uint8_t  iCPLD1StateGET1 = 0;  
15
16
    
17
   init();             // PORT and PIN initializations
18
  
19
  lcd_init();            
20
  
21
  lcd_clear();
22
  lcd_setcursor( 0, 1 );
23
  lcd_string("Ch. 1: ");
24
25
  lcd_setcursor( 0, 2 );
26
  lcd_string("Ch. 2: ");
27
28
  ResetCPLD(1);
29
  
30
  wdt_enable(WDTO_2S);
31
  
32
    while(1)
33
    {
34
    wdt_reset();
35
  
36
    char  strDisplayValue[8];    // 8 characters plus \0
37
38
    // Initialize string
39
    strDisplayValue[0] = ' ';
40
    strDisplayValue[1] = ' ';
41
    strDisplayValue[2] = ' ';
42
    strDisplayValue[3] = ' ';
43
    strDisplayValue[4] = ' ';
44
    strDisplayValue[5] = ' ';
45
    strDisplayValue[6] = ' ';
46
    strDisplayValue[7] = 0; //'\0 ';
47
    
48
    // Get lsb
49
    if(iCPLD1StateGET0 == 0)
50
    {
51
      PORTB |= (1 << iCPLD1Get0Pin);    // Set GET0
52
      iCPLD1StateGET0 = 1;        // Update State
53
    }
54
    else
55
    {
56
      PORTB &= ~(1 << iCPLD1Get0Pin);    // Clear GET0
57
      iCPLD1StateGET0 = 0;        // Update State
58
    }
59
    _delay_ms(1);
60
    lsb = PIND;
61
62
    // Get msb
63
    if(iCPLD1StateGET1 == 0)
64
    {
65
      PORTB |= (1 << iCPLD1Get1Pin);    // Set GET1
66
      iCPLD1StateGET1 = 1;        // Update State
67
    }
68
    else
69
    {
70
      PORTB &= ~(1 << iCPLD1Get1Pin);    // Clear GET1
71
      iCPLD1StateGET1 = 0;        // Update State
72
    }
73
    _delay_ms(1);
74
    msb = PIND;
75
76
    // Combine and cast
77
    x.a[0] = lsb; 
78
    x.a[1] = msb; 
79
80
    combined32 = (int32_t)x.b; 
81
    
82
    int32_t_itoa(combined32, strDisplayValue, sizeof(strDisplayValue));
83
84
    lcd_setcursor( 0, 2 );
85
    lcd_string("Klappts?");
86
87
    lcd_setcursor( 1, 1 );
88
    lcd_string(strDisplayValue);
89
    wdt_reset();
90
    _delay_ms(1);
91
  }   
92
}

von Schlumpf (Gast)


Lesenswert?

Hallo Reinhard,

nur ganz kurz:
Hier wird NICHT einsynchronisiert, sondern nur "umverdrahtet".
Hat Lattice-User auch schonmal weiter oben erwähnt
1
    -- einsynchronisieren
2
    GET0mitte   <= GET0in;
3
    GET0       <= GET0mitte;
4
    GET1mitte   <= GET1in;
5
    GET1       <= GET1mitte;
6
    RESETmitte   <= RESETin;
7
    RESET      <= RESETmitte;

Und eine Konvertierung von unsigned nach unsigned ist auch nicht 
unbedingt sinnvoll ;-)
1
    t_cnt2_unsigned <= t_cnt1;

von Schlumpf (Gast)


Lesenswert?

Was ich noch sagen wollte:
Es bringt wirklich nichts für das Verständnis, wenn du jetzt so weit den 
Code runterkürzt, bis irgendwas tut, und dann wieder langsam den Rest 
dazu baust. Solange du nicht verstehst, welche Hardware-Strukturen du 
mit deinem Code erzeugst, ist dieses Vorgehen wirklich sinnlos.

Fass mal in Textform zusammen, was du erreichen willst und wie du es 
dann in Hardware realsieren würdest.
Vielleicht kommst du dann sogar drauf, dass es nicht so geschickt ist, 
mit den Flanken von GET0 und GET1 die Umschaltung zu machen, sondern mit 
statischen Pegeln. Dadurch sparst du Ressourcen und Latenz.
Und dann wirst du draufkommen, dass es sowieso ausreicht, wenn du nur 
mit GET0 arbeitest und auf GET1 vollkommen verzichten kannst.

Ich gebe dir mal nen Tipp, wie man es deutlich einfacher machen könnte:

- Flankenerkennung des GET-Signals (steigende Flanke erkennen), indem 
man GET durch ein Schieberegister schiebt und die benachbarten Register 
vergleicht.
- Mit diesem Signal (flanke_erkannt) das 16-Bit Datenregister enablen, 
welches den aktuellen Zählerstand kopiert.
- Multiplexer über synchronisiertes GET gesteuert, welcher bei GET = '1' 
die unteren 8 Bit und bei GET0 = '0' die oberen 8 Bit des Datenregisters 
auf den COUNT-Port ausgibt.

Ressourcen:
2-3 Register zur Synchronisation von GET.
16 Register, um Zählerstand zwischenzuspeichern.
Ne Handvoll LUT zur Flankenerkennung und für den Ausgangsmultiplexer.

Wenn dir diese Struktur klar ist, dann kann man das ganz einfach in HDL 
umsetzen. Dann ist es nur noch notwendig zu wissen, durch welche 
syntaktischen Konstrukte man den Synthsizer dazu bringt, genau das zu 
bauen, was oben beschrieben ist.

Wie du siehst, ist hier ein gewisses Verständnis von Hardware einfach 
erforderlich.

Dein Zugriff auf dem µC sieht dann so aus:
GET = 0
GET = 1
Lesen von Low-Byte
Get = 0
Lesen von High-Byte

von Reinhard J. (rvj)


Lesenswert?

Hallo Schlumpf!
Danke für Deine ausführlichen Tipps. Wie bereits geschrieben: Ich werde 
sie beherzigen und dieses Projekt zurückstellen. Es ist ein Lernprojekt, 
was man vielleicht auch mal nutzen kann oder auch nicht.
Ich lese mich mal ein und mache bei Bedarf den einen oder anderen Thread 
auf. Erst mal folge ich weiter Deinem Link und schaue dann was ich noch 
so zum Lesen finde. Irgendwann mache ich hier weiter und ich hoffe, dass 
ich dann Deine Anregungen umsetzen kann.
Nochmals Tausend Dank.
Herzliche Grüße Reinhard

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.