Forum: FPGA, VHDL & Co. 4-Bit CPU in VHDL - Synchronisationsproblem


von Felix (Gast)


Lesenswert?

Hallo,

ich bin gerade dabei ein 4-Bit CPU in LOAD-STORE Akkumulator Architektur 
zu implementieren. Es ist mein erster Versuch, mehrere VHDL Komponenten 
miteinander zu verschalten.

Die Komponenten selbst sind:
 - Kontrolleinheit (mit PC)
 - Adressierungseinheit mit einem Adressregister
 - Speicher (Xilinx IP Core)
 - ALU

Dadurch ergeben sich folgende Pfade:
 Control <-> Adress <-> Memory
 Control <-> ALU
 ALU <-> Adress

(Vielleicht wichtig zu erwähnen: die Datenbusse von der Adresseinheit 
zur Control und Adresseinheit zur ALU sind dieselben, quasi verzweigt)

Nun zu meinem eigentlichen Problem:
Wenn ich nun an der Kontrolleinheit eine Adresse zum Auslesen der 
nächsten Instruktion/Datum anlege, so liegt die ausgelesene 
Instruktion/Datum an der Kontrolleinheit und an der ALU einen halben 
Takt später an (nur Verhaltens-Simulation).

Zur Kompensation hört die ALU auf die fallende Taktflanke des Clks, die 
Kontrolleinheit auf die steigende. Auf die weise funktioniert das 
Speichern von Daten in den Akkumulator gut. Ist das bisher schon falsch 
gelöst oder noch ok?

Das eigentliche Problem besteht nun darin, Daten vom Akkumulator in den 
Speicher zu schreiben. Der Akkumulator besitzt 8-Bit, der Bus zur 
Adresseinheit nur 4. Die Steuereinheit muss also über ein Steuersignal 
das hochwertige/niederwertige Nibble des Akkus multiplexen. Da das 
Steuersignal also 2 Takte anliegt, die ALU aber auf die fallenden Flanke 
des Clks hört, liegt das Chip Enable Signal für die Speichereinheit 
(Dauer: auch 2 Takte) einen halben Takt zu kurz an ...

Ich gehe mal davon aus, dass meine Lösung in irgendeiner Weise falsch 
ist. Deshalb meine Frage: wie synchronisiere ich mehrere Komponenten 
korrekt? :)

Vielen Dank für eure Überlegungen,
F.

P.S: Ich habe keinen Code gepostet, da alle Komponenten zusammen etwas 
zuviel ist ...

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


Lesenswert?

> liegt die ausgelesene Instruktion/Datum an der Kontrolleinheit
> und an der ALU einen halben Takt später an (nur Verhaltens-Simulation).
Wie das? Woher kommt hier die Verzögerungs-Zeit?
Oder ist da eine Komponente, die auch schon auf den fallenden Takt 
reagiert?

> Zur Kompensation hört die ALU auf die fallende Taktflanke des Clks, die
> Kontrolleinheit auf die steigende. Auf die weise funktioniert das
> Speichern von Daten in den Akkumulator gut.
> Ist das bisher schon falsch gelöst oder noch ok?
Sehr unsauber.
Solche takt-versetzten Designs kann man machen, wenn das Gerät schon in 
der Auslieferung ist, und das dann die einzige Rettung vor einem 
Hardware-Redesign ist.

> Der Akkumulator besitzt 8-Bit...
Dann ist das m.E. ein 8-Bit-Core mit gemultiplextem Datenbus.

Zip das Projekt mal zusammen, und poste das.
Dann hat die Raterei ein Ende ;-)

von Felix (Gast)


Angehängte Dateien:

Lesenswert?

Hi Lothar,

erstmal danke für deine Hilfsbereitschaft.

In dem gezippten Projekt befindet sich bislang nur mal der Code zur 
Adresseinheit (da das problem scheinbar darin ensteht), der Xilinx IP 
RAM Core und eine Testbench.
Falls du mehr benötigst, poste ich es nach.


Danke im Voraus,
F.

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


Lesenswert?

Ich habe noch nicht den ganzen Code angsehen, aber das ist mir schon 
gleich aufgefallen:
1
  -- durchschalten von DataFromMemory
2
  Data_through:process(DataFromMemory)
3
  begin
4
    DToControl <= DataFromMemory ;
5
  end process ;
liesse sich ohne Prozess leichter, schneller, schöner, kompakter und 
weniger fehlerträchtig als Concurrent Statement beschreiben:
1
   -- durchschalten von DataFromMemory
2
   DToControl <= DataFromMemory ;

Mit dieser Schreibweise:
1
-- multiplexen von daten(PC von Control, AKK von ALU) zur speichereinheit
2
write_datareg: process(MemEn, RW )
3
begin
4
   if (MemEn = '1') then
5
      if (RW = '1') then
6
         if (SelectSource = '0') then
7
            DataToMemory <= PCOut ;
8
         else
9
            DataToMemory <= AccOut ;
10
         end if ;
11
      end if ;
12
   end if ;
13
end process ;
bekommst du ein Latch, und das ist in einem FPGA fast sowas wie ein 
Schimpfwort.

Weil zudem deine Sensitivity-List nicht komplett ist, es also statt
1
write_datareg: process(MemEn, RW)
richtigerweise
1
write_datareg: process(MemEn, RW, SelectSource)
heissen müsste, ist deine Simulation auf jeden Fall falsch  :-o

Mein Tipp: auch hier Concurrent beschreiben.
1
-- multiplexen von daten(PC von Control, AKK von ALU) zur speichereinheit
2
DataToMemory <= PCOut  when (MemEn='1' and RW='1' and SelectSource='0') else
3
                AccOut when (MemEn='1' and RW='1' and SelectSource='1') 
4
                DataToMemory;
Hier sieht man auch schön das unschöne Latch, denn wenn keine andere 
Bedingung zutrifft, wird der Wert von DataToMemory einfach nur gehalten.



EDIT:
Ein paar Zeilen weiter dann die überdefinierte Sens-List.
1
write_addr_out:process(Clk, Reset, MemEn, SelectSource, AdressBusIn)
2
    :
3
begin
4
   if (Reset = '1') then
5
     :
6
   elsif (Clk = '1' and Clk'event) then
7
     :
8
   end if ;      
9
end process ;
Hier sind MemEn, SelectSource, AdressBusIn unnötig, da nur eine 
Änderung von Reset und/oder Clk etwas am Ergebnis des Prozesses 
bewirken.
Denn wenn sich eines der drei anderen Signale ändert, dann wird sowieso 
bis zum nächsten Takt gewartet, bis ein neuer Zustand berechnet wird.

Am besten baust du das auch gleich auf synchronen Reset um, das ist für 
den Syntesizer wesentlich einfacher (such mal hier im Forum zum Thema 
synchroner und asynchroner Reset), und dann ist nur noch der Clk in der 
Sensitivity-List  ;-)
1
write_addr_out:process(Clk)
2
    :
3
begin
4
   if rising_edge(Clk) then
5
     :
6
      if (Reset = '1') then
7
        :
8
      end if ;      
9
   end if ;      
10
end process ;

von Felix (Gast)


Lesenswert?

Hi Lothar,

ich danke dir für deine Hilfe, vorallem für die Tips bzgl. 
Sensitivity-Lists.
Das Latch habe ich dann auch erkannt (bzw. das Xilinx Tool hat es mir im 
Report gemeldet), als ich eine Timing-Simulation durchführen wollte.

Allerdings durchschaue ich noch nicht, wieso ich Anweisungen wie
1
DataToMemory <= PCOut after del when (MemEn='1' and RW='1' and SelectSource='0') else
2
              AccOut after del when (MemEn='1' and RW='1' and SelectSource='1') else
3
             "0000" after del ;

Concurrent machen muss. Was ist so schlecht an einem eigenen Prozess 
(einmal abgesehen von meiner unvollständigen Sens-List ;-))?

Viele Grüsse,
F.

P.S: Das Delay von einem halben Takt kam in der Testbench auf. Der Reset 
war zur fallenden Clk-Flanke beendet, und sofort startete das MemEn...

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


Lesenswert?

Felix wrote:
> Allerdings durchschaue ich noch nicht, wieso ich Anweisungen wie ...
> Concurrent machen muss. Was ist so schlecht an einem eigenen Prozess?
Musst du nicht, es ist aber viel kompakter und (dadurch automatisch) 
übersichtlicher ;-)
Und das Latch (igitt) siehst du schon beim Schreiben, nicht erst bei der 
Synthese. Ein Tipp: so ein Latch ist wirklich nichts, was man in einem 
FPGA brauchen kann, weil es ein transparentes Latch (als Komponente) im 
FPGA einfach nicht gibt. Statt dessen wird ein FF so beschaltet, dass es 
sich wie ein Latch verhält. Probiers einfach mal aus ;-)

> P.S: Das Delay von einem halben Takt kam in der Testbench auf. Der Reset
> war zur fallenden Clk-Flanke beendet, und sofort startete das MemEn...
Was wäre passiert, wenn der Reset (noch wesentlich ungünstiger) gerade 
um die steigende Flanke deaktiviert worden wäre? Richtig: in der Praxis 
ist das eine Setup/Hold-Zeit Verletzung.
Dazu ist nur zu sagen, dass der Reset das asynchrone Signal 
schlechthin ist. Und jedes asynchrone Signal gehört sauber (über 
mindestens 2 FF) einsynchronisiert und dann erst im FPGA verwendet. So 
is das ;-)

von hotline (Gast)


Lesenswert?

Dein Problem mit dem "halben Takt später" hat folgenden Grund:

Du hast ein synchrones Design gebaut, das bei einer steigenden 
Taktflanke etwas liefert. In deiner Testbench startet dein clk bei 0 zum 
Zeitpunkt 0. Dann wartest du eine Taktperiode, bis du etwas anlegst. 
Eine halbe Periode später geht clk auf 1 (steigende Flanke), eine 
weitere halbe Periode später auf 0 (fallende Flanke). Du legst in deiner 
Testbench also etwas zur fallenden Flanke an, worauf dein Design zur 
steigenden Flanke reagiert. Dein Design macht das also wahrscheinlich 
schon richtig. Das Anlegen könnte auch zu jedem anderen beliebigen 
Zeitpunkt (als der fallenden Flanke) geschehen, dein Design würde immer 
zur steigenden Flanke arbeiten.

Wenn du zum Beispiel in der TB als Initialisierungswert für clk:= '1' 
nimmst, sieht das ganze wahrscheinlich eher so aus, wie du es dir 
vorstellst.
Weiterhin das Problem was Lothar erwähnt hat: das zeitgleiche 
anlegen/abschalten der Signale zur Flanke. Für die Verhaltenssimulation 
hilft hier z.B.
wait for 1 ps;
oder ähnliches ganz am Anfang des stim_proc.

von Felix (Gast)


Lesenswert?

Ja, das habe ich auch festgestellt. Zur Kompensation verwende ich nach 
dem Reset ein
1
wait until rising_edge(Clk) ;
2
 :
3
MemEn <= '1' after clk_period/5 ns ;
4
 :

Aber mal eine ganz andere Frage:
Wie sieht eigentlich die Simulation in der realen Praxis aus? Wird da 
nur auf Verhaltensebene simuliert und darauf geachtet, dass für die 
Synthese die Timing Constraints eingehalten werden? Oder wird auch mit 
Post-Route Simulation gearbeitet?

Viele Grüsse,
F.

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


Lesenswert?

Felix wrote:
> Ja, das habe ich auch festgestellt. Zur Kompensation verwende ich nach
> dem Reset ein
1
 wait until rising_edge(Clk) ;
2
  :
3
 MemEn <= '1' after clk_period/5 ns ;
4
  :
Es ist die falsche Vorgehensweise, eine Testbench so hinzuschreiben, 
dass das Design durchkommt (also in der Testbench auf eine Flanke des 
Taktsignals zu warten). Denn dann wird dein Design auch in der Realität 
nur unter diesen Rahmenbedingungen funktionieren.
D.h. du musst (von aussen) garantieren, dass der Reset mit zum richtigen 
Zeitpunkt weggeht, und auch deine anderen Signale zum richtigen 
Zeitpunkt angelegt werden. So ist ja auch deine simulation ;-)

Weil du das in der Praxis nicht kannst, müssen alle externen 
asynchronen Signale (incl. Reset) einsynchronisiert werden.

> Aber mal eine ganz andere Frage:
> Wie sieht eigentlich die Simulation in der realen Praxis aus? Wird da
> nur auf Verhaltensebene simuliert und darauf geachtet, dass für die
> Synthese die Timing Constraints eingehalten werden?
Sich vorher ausführlich Gedanken zum Design zu machen reicht i.A. aus. 
Denn du kannst eine Testbench nie so schreiben, dass alle Grenzfälle aus 
der Praxis abgedeckt sind.

>Oder wird auch mit Post-Route Simulation gearbeitet?
Wie gesagt: vorher überlegen und sinnvolle Constraints setzen.
In der statischen Timinganalyse stehen dann noch ein paar Zahlen, die 
eine aussage zu den Design-reserven zulassen.

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.