Forum: FPGA, VHDL & Co. Kombinatorische Logik aus Prozess auslagern


von Michael S. (decimad)


Lesenswert?

Hallo Leute,

mein Verständnis von VHDL-Prozessen wird gerade hart in Frage gestellt.
Folgende Situation:
Ich habe einen Quad-Port-Speicher mit 2 Schreib- und 2 Leseports.
Nun habe ich 4 Eingangsports für Daten, die aber nicht immer Daten 
liefern, es gibt eine Maske in der verzeichnet ist, welche der 4 Ports 
gerade Daten schreiben wollen und auch wohin. 2 Ports sind "Master", 
also wenn sie schreiben wollen, dann dürfen sie auch, die anderen 2 sind 
Slave und werden reingequetscht, wenn der Master nur einen Port 
blockiert oder gar keinen.
Im Prinzip ein 4 auf 2 Mux mit 2 priorisierten Kanälen, wobei die 
Reihenfolge keine Rolle spielt.
Also Beispiel:
Maske: (master1 master2 slave1 slave2): "1010"
führt zu: master1 schreibt auf port1; slave 1 schreibt auf den freien 
port2 + zustand "slave2 muss noch geschrieben werden".

Nun war der Prozess-code so ähnlich wie folgender:
1
process
2
-- eine menge variablen zum bookkeping der schreib-zuordnungen
3
begin
4
   wait until rising_edge(clock);
5
6
   -- eine Menge_ zuordnungs-if-then-else-gedöns
7
   -- exemplarisch:
8
   if some = '0' then
9
      value1 := registers(read_src1) + registers(read_src2);
10
      value2 := 2623;
11
      write_dest1 := 4;
12
      write_dest2 := 6;
13
   else if ...... then
14
15
   else
16
      value1 := slave1;
17
      write_dest1 := slave1_dest;
18
      value2 := slave2;
19
      write_dest2 := slave2_dest;
20
   end if;
21
22
   registers(write_dest1) <= value1;
23
end process;

Ich weiß eigentlich gar nicht so genau wie das überhaupt funktionieren 
konnte, dass man "im gleichen Takt" das Ergebnis aus einer Berechnung 
mit Operanden aus einer Lese-Operation vom Register direkt wieder 
reinschreiben kann (beziehungsweise wie VHDL das semantisch sieht, was 
ich da geschrieben habe), auf jeden Fall war das Ergebnis zur nächsten 
Taktflanke im Register und kann direkt weiterverwendet werden (ich denke 
mal, dass der Speicher irgendwie den Schreib-Port zum Leseport 
durchschleift, falls man im gleichen Takt auf dieselbe Addresse schreibt 
und von ihr liest, und das ergebnis eigentlich erst zur nächsten 
Taktflanke geschrieben wird, obwohl die Schreib-werte schon zwischen den 
Taktflanken bereitstehen).

Auf jeden Fall jetzt das Problem: Die Logik für die Zuordnung von 
Berechnungsresultaten, Konstanten und Slave-Werten auf die Schreibports 
war dermaßen kompliziert, dass ich keine "geschlossene" Funktion angeben 
konnte, sondern Variablen mehrfach überschreiben musste etc. Also diese 
Logik hat den kompletten Prozess-Code einfach nur unleserlich gemacht.

Nun habe ich mir einen Kombinatorischen Block gemacht, der diese 
Zuordnung übernehmen soll:
1
entity write_controller_4x2 is
2
generic(
3
  bus_width : integer := 16;
4
  index_width : integer := 4
5
);
6
port(
7
   -- takt zum zustandspeichern bei belegten kanälen
8
  clock : in std_logic;
9
10
   -- eingänge
11
  master_in1   : in std_logic_vector( bus_width-1 downto 0 );
12
  master_dest1 : in std_logic_vector( index_width-1 downto 0 );
13
  
14
  master_in2 : in std_logic_vector( bus_width-1 downto 0 );
15
  master_dest2 : in std_logic_vector( index_width-1 downto 0 );
16
  master_mask: in std_logic_vector( 1 downto 0 );
17
  
18
  slave_in1   : in std_logic_vector( bus_width-1 downto 0 );
19
  slave_dest1 : in std_logic_vector( index_width-1 downto 0 );
20
  slave_in2   : in std_logic_vector( bus_width-1 downto 0 );
21
  slave_dest2 : in std_logic_vector( index_width-1 downto 0 );
22
  slave_mask  : in std_logic_vector( 1 downto 0 );
23
  
24
  -- ausgänge
25
  write_channel1 : out std_logic_vector( bus_width-1 downto 0 );
26
  write_channel2 : out std_logic_vector( bus_width-1 downto 0 );
27
  write_dest1    : out std_logic_vector( index_width-1 downto 0 );
28
  write_dest2    : out std_logic_vector( index_width-1 downto 0 );
29
  
30
  write_mask     : out std_logic_vector( 1 downto 0 );
31
  
32
  slave_busy : out std_logic
33
);
34
end write_controller_4x2;

In dem Block ist eine Weiche zum Umleiten der Slave Ports und dahinter 2 
2x1 Muxe, die jeweil Slave oder Master-Port auswählen. Dazu noch ein 
Zustandsprozess, um Slave-Operationen auf mehrere Takte verteilen zu 
können und eine große Funktion, die anhand der Eingangswerte und dem 
aktuellen Zustand berechnet, wie die Weiche und die Muxe geschaltet 
werden müssen (ich könnte auch einmal ein Blockschaltbild davon malen, 
wenn das hilft).

Okay, so weit so gut. Das Problem ist, dass ich jetzt gerne hätte, dass 
dieser kombinatorische Mux-Kontroll-Block genauso benutzt wird, wie es 
der Prozess "damals" intern auch gemacht hat.

Wenn ich jetzt folgendes schreibe (zusammengestaucht um auf den Punkt zu 
kommen):
1
  signal master1 ...;
2
  signal master2 ...;
3
  signal slave1 ...;
4
  signal slave2 ...;
5
  signal write1 ...;
6
  signal write2 ...;
7
8
begin
9
mux_ctrl: entity work.write_controller_4x2
10
  port map(
11
     master1 => master1,
12
     master2 => master2,
13
     slave1  => slave1,
14
     slave2  => slave2,
15
     out1    => write1,
16
     out2    => write2
17
  );
18
  
19
  process begin
20
     wait until rising_edge( clock );
21
    
22
     master1 <= registers(4); -- schreibt in einen puffer-register :(
23
     master2 <= registers(6); -- das auch :(
24
25
     registers(2) <= out1;
26
     registers(3) <= out2;
27
  end process;
... dann werden jetzt für master1 und master2 Register eingefügt und das 
Ergebnis ist somit erst 2 Takte später in den Registern.
Ich hätte nun aber gerne, dass der Parser irgendwie erkennt, dass 
master1 und out1 kombinatorisch miteinander verbunden sein sollen und 
keine Register eingefügt werden, genauso wie es war, als die Logik, die 
ich in write_controller_4x2 ausgelagert habe, noch in dem 
Prozess-Quelltext stand (und dort ja auch kombinatorisch umgesetzt 
werden musste, um semantisch das richtige zu berwerkstelligen, zwischen 
zwei Taktflanken).

Das einzige das mir jetzt noch einfiele, wäre es, diese Kontrollogik 
nicht als kombinatorischen Block umzusetzen, sondern als Funktion, die 
ich ja direkt im Kontext des Prozesses aufrufen kann, sodass der 
VHDL-Parser weiß, was ich zu erreichen versuche.

Es würde mich freuen, wenn mir jemand von euch aufklären könnte, ob man 
kombinatorische Entities irgendwie "in den Kontext eines Prozesses" 
ziehen kann oder ob Funktionen die einzige Lösung sind (oder ob 
Funktionen auch keine Lösung sind und ich überhaupt mal gar keine Ahnung 
habe, nach Hause gehen sollte um an Sudokus zu trainieren O.O)!

Puh, viel Text - tut mir Leid :( Wenn ich zur Illustrationen 
Blockschaltbilder bereitstellen soll, weil meine Erklärungen dürftig 
sind, bitte Bescheid geben!

Viele Grüße,
Deci

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


Lesenswert?

Michael S. schrieb:
> Das einzige das mir jetzt noch einfiele, wäre es, diese Kontrollogik
> nicht als kombinatorischen Block umzusetzen, sondern als Funktion, die
> ich ja direkt im Kontext des Prozesses aufrufen kann, sodass der
> VHDL-Parser weiß, was ich zu erreichen versuche.
Vorneweg: ich kapiere nicht so ohne weiteres, was dein Problem ist, aber 
ich habe den Eindruck, dass du da irgendwas "programmieren" willst, so 
wie du einen uC "programmieren" würdest.
Tritt mal einen Schritt zurück und überleg dir, wie du das mit 
Hardwarekomponenten (RAM, MUX, Kombinatorik) aufbauen würdest. Und das 
beschreibst du dann. Dafür ist VHDL da...

> Wenn ich zur Illustrationen Blockschaltbilder bereitstellen soll
Es wird dir und uns helfen, einen Überblick über das zu bekommen, was du 
da machen willst.

> oder ob Funktionen die einzige Lösung sind
Funktionen sind toll für die Simulation. Aber bei der Synthese sollte 
sich die Komplexität der Funktion in engen Grenzen halten (so in etwa 
auf dem Niveau rising_edge() oder to_integer()), denn der Synthesizer 
wird dir gnadenlos Hardware aus jeder einzelnen "aufgerufenen" Funktion 
basteln.

von Ingenieur (Gast)


Lesenswert?

Ich rate immer zu Folgendem:

Konzept
Blockdiagramm
Zeitablauf

dann, frühestens dann die Schaltung machen.

Das gilt auch fürs Darstellen,

So kann sich keiner ein Bild machen was Du überhaupt möchtest.

von Michael S. (decimad)


Angehängte Dateien:

Lesenswert?

Hallo!

Jau, also ich hab schon Zeichnungen gemacht, aber die waren in keiner 
Form, dass man sie hier hätte zeigen können.

bild1 zeigt den Aufbau, den ich erreichen möchte.

bild2 zeigt das grundlegende Problem, das ich hier habe. Wie man im 
unteren Teil sehen kann, lässt sich mit einem monolithischen Prozess der 
getaktete Speicher inklusive berechneter Rückführung realisieren, alles 
in einem Takt (statt direkt die Signale zuzuweisen, könnte man auch über 
variablen mit ihnen rumrechnen (=> Kombinatorik auf dem Signalpfad)).
Wenn man nun aber den monolithischen Prozess auseinanderdröseln möchte 
(aber trotzdem noch Prozess, um den synchronen RAM zu beschreiben), dann 
fügt er einem vor dem "äußeren" "ausgelagerten" "modularisierten" 
kombinatorischen Schluss ein Flipflop (was normalerweise ja auch 
gewünscht ist). Aber ich möchte vor der Kombinatorik (zwischen read1 und 
w1 in der Zeichnung) keinen Flipflop. Der Synthetisierer soll sich 
denken, dass alles was zwischen read1 und w1 kommt eigentlich eine 
kombinatorische Berechnung ist, die man auch gleich in den Prozess hätte 
schreiben können.

Ich habe von dem monolithischen Prozess wie im unteren Teil zu dem 
modularisierten Modell wie im oberen Teil umgebaut, habe jetzt aber 
"Latenz" von 2 Takten, eben wegen des Flipflops, das mit "Ausgängen" von 
synchronen Prozessen einhergeht. Ich könnte damit leben, weil jetzt der 
ganze Quelltext wesentlich besser zu lesen ist, aber ich weiß ja, dass 
der monolithische Prozess das in einem Takt schafft, warum sollte es 
also nicht auch gehen, wenn man den Projektaufbau etwas angenehmer 
gestaltet.

PS: Entschuldigt bitte die Zeichnungsqualiät, das ist das beste, was ich 
aus dem Grafiktablett rausholen konnte :D

von Michael S. (decimad)


Lesenswert?

Oje, ich habe doch tatsächlich Zyklusse geschrieben... Oje, oje! 
hochrotanlauf Es ist spät und ich bin erschöpft rausred

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


Lesenswert?

Wen du in einem (oder durch einen) Prozess keine Flipflops erzeugen 
willst, dann darfst du ihn nicht takten. Oder andersrum: jede 
Signalzuweisung in einem getakteten Prozess ergibt Flipflops...

von Michael S. (decimad)


Lesenswert?

Danke Lothar!

Also ich habe jetzt mal das Schreiben im getakteten Prozess und das 
lesen kombinatorisch außerhalb des Prozessen umgesetzt (so etwas hatte 
ich nicht gewagt zu versuchen, obwohl Xilinx ja so den Distributed Ram 
beschreibt, den ich hier ja offenbar benötige). Und im Simulator 
funktioniert's!
Jetzt frage ich mich aber: wird das auch synthetisiert funktionieren? 
Ich beschreibe hier ja quad-port distributed ram mit 2x write und 2x 
read. In den Xilinx-Unterlagen kann ich so eine Konfiguration gar nicht 
finden, trotzdem sagt mir Xst, es hätte quad-port distributed ram 
synthetisiert.

Und eine weitere Frage: Xst hat auch einen Maximaltakt bestimmen können 
der genau dem entspricht, was ich mit dem monolithischen Prozess hatte 
und mir ist nicht direkt klar, wie? Ich habe ja jetzt einen äußeren 
kombinatorischen Kreisschluss definiert, ohne da bestimmte Zeiten 
vorzugeben.
Muss Xst die Sache so behandeln, dass synchrone Prozesse immer einen 
stabilen Eingang zu Taktflanken haben, sodass sich aus diesem Umstand 
ergibt, dass Lesen+Berechnen innerhalb eines Zyklusses vonstatten gehen 
muss, weil das Ergebnis Eingang eines getakteten Prozesses ist?

Viele Grüße,
Deci

von Michael S. (decimad)


Lesenswert?

Okay, weiter unten beim Routing kommen folgende Warnmeldungen:

----------------------
WARNING:Par:288 - The signal reg/Mram_registers2_RAMB_DPO has no load. 
PAR will not attempt to route this signal.
WARNING:Par:288 - The signal reg/Mram_registers2_RAMD_O has no load. 
PAR will not attempt to route this signal.
WARNING:Par:288 - The signal reg/Mram_registers1_RAMB_DPO has no load. 
PAR will not attempt to route this signal.
WARNING:Par:288 - The signal reg/Mram_registers1_RAMD_O has no load. 
PAR will not attempt to route this signal.
WARNING:Par:288 - The signal reg/Mram_registers14_RAMB_DPO has no load. 
PAR will not attempt to route this signal.
WARNING:Par:288 - The signal reg/Mram_registers14_RAMD_O has no load. 
PAR will not attempt to route this signal.
WARNING:Par:288 - The signal reg/Mram_registers11_RAMB_DPO has no load. 
PAR will not attempt to route this signal.
WARNING:Par:288 - The signal reg/Mram_registers11_RAMD_O has no load. 
PAR will not attempt to route this signal.
WARNING:Par:288 - The signal reg/Mram_registers13_RAMB_DPO has no load. 
PAR will not attempt to route this signal.
WARNING:Par:288 - The signal reg/Mram_registers13_RAMD_O has no load. 
PAR will not attempt to route this signal.
----------------------

Da hier A und C als Indizes fehlen, interpretiere ich das jetzt so, dass 
er für meine 2 read 2 write Register einen Speicher aus zwei "2 read 1 
write" zusammenbaut (also insgesamt "4 read 2 write", wobei ich nur 2 
reads angeschlossen habe (die er auf A und C legt) und somit 2 read 
ports brachliegen (B und D)?
Oder ist das ein Anzeichen dafür, dass das, was ich wollte, einfach 
nicht richtig synthetisiert wurde?
Wenn die brachliegen, könnte ich auf der rechten Seite ja ohne große 
weitere kosten (außer halt all den Leitungen) einfach direkt 4 Kanäle 
rausführen, hrmmmm. Aber ist dann halt sehr speziell auf die Architektur 
ausgelegt.

von Schlumpf (Gast)


Lesenswert?

Grundsätzlich gilt, wie Lothar ja auch schon sagte, dass alles, was in 
einem getakteten Prozess zugewiesen wird, zu einem Register führt.
Alles, was in einem Prozess außerhalb der Abfrage der Taktflanke steht, 
wird rein Kombinatorisch umgesetzt. Genauso, wie wenn du es ganz 
außerhalb eines Prozesses beschreiben würdest.
Ob du innerhalb eines Prozesses Q<=A and B schreibst, oder außerhalb, 
ist egal.
Es wird eine kombinatorische Und-Verknüpfung daraus.
Schreibst du das aber innerhalb einer Abfrage auf die Taktflanke (was 
nur innerhalb eines Prozesses möglich ist), dann wird ein Register 
daraus, an dessen Eingang ein UND vorgeschaltet ist.

von Michael S. (decimad)


Lesenswert?

Ich glaube, nun sollte mir ein Großteil klar sein, bezüglich der 
Prozesse. Ich hatte komplett vergessen, dass man Prozesse auch rein 
kombinatorisch verwenden kann.
Wobei ich bei nicht-getakteten Prozessen auf die Sensitivity-Liste 
aufpassen muss, leider. Dabei finde ich die Syntax in Prozess-Blöcken 
sehr viel schöner als die außerhalb, aber immer auch die 
Sensitivity-Liste mit-aktualisieren für den kombinatorischen Prozess 
wird auf Dauer wohl nerven. Mir stellt sich sowieso die Frage, warum man 
da in VHDL Syntax-mäßig zweigleisig fährt.

Schade auch, dass ich keine Chipscope-Lizenz besitze, im Moment kann ich 
das Design (vor allem im Bezug auf die fragliche Speicherumsetzung) 
nicht im FPGA austesten, weil es einfach noch keine vernünftige Ausgabe 
ermöglicht. Also weiterbasteln und simulieren und hoffen :D

Sei's drum, vielen Dank für eure Erklärungen!

Viele Grüße,
Deci

PS: Schlumpf: die im anderen Thread noch offene Frage, wegen der 
kombinatorischen Busy-Rückführung werde ich leider nicht mehr austesten 
können, weil sich währenddessen an dieser Baustelle hier so viel getan 
hat, dass da nix vergleichbares mehr ist, zumal ich bislang zu bequem 
war, ein repo für das Projekt einzurichten -.- Möchte deshalb nicht den 
Thread pushen, drum hier die Erwähnung.

von Schlumpf (Gast)


Lesenswert?

Hallo Michael,

das mit der Sensitivity-List ist halt leider so in VHDL, auch wenn´s 
nervt.
Der XEmacs Editor kann die aber automatisch vervollständigen. Ein 
mächtiger Editor, aber die Bedienung ist eher Geschmackssache.

Was die Synthese aus deinem Code macht, kannst du auch im RTL-View 
anschauen. Dort bekommst du die Info, wie dein Code in Logik umgesetzt 
wird. Der Technology-View zeigt dir dann, wie diese Logik auf den FPGA 
abgebildet wird. Nach der Synthese kannst du dir auch noch die komplette 
Netzliste des Designs als VHDL-Code erzeugen lassen und wieder in deinen 
Simulator schieben. Dann siehst du, ob das, was du beschrieben hast, 
auch wirklich so funktioniert (inklusive aller Timinginformationen). 
Stichwort hierzu: "Backannotation".
Oder sind diese Funktionen bei Xilinx kostenpflichtig?

Zu dem anderen Thread: Kein Problem :-)

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


Lesenswert?

Schlumpf schrieb:
> inklusive aller Timinginformationen
Das macht aber nur dann Sinn, wenn auch das Simulationsmodell 
Timinginformationen enthält. Normalerweise sinnvoll gesetzte 
Timing-Constraints (wenigstens die Taktperiode), denn die muss man 
sowieso machen. Man kann sich nicht drauf verlassen, dass jedesmal schon 
was Passendes rauskommen wird...

von Michael S. (decimad)


Lesenswert?

Hallo!
Die 100MHz für das Nexys3-Board sind schon in der Begleit-UCF 
eingetragen, sowei ich das beurteilen kann.
Aber ich glaube, sobald ich die Kombinatorik aus einem Prozess ausbaue, 
muss ich per Hand sagen, welche kombinatorischen Pfade welche Zeit 
brauchen dürfen, oder nicht? Der Synthetisierer kann ja nicht wissen, ob 
ich der Kombinatorik vielleicht 2 Takte zum auswerten gönnen möchte, 
oder?
Hingegen, wenn ich das alles in einem getakteten Prozess mache, dann 
weiß der Synthetisierer ja implizit, was Sache ist?

Bin da noch etwas planlos, aber bin glaube ich auf dem besten Weg, mir 
vorstellen zu können, was ich da so in etwa angeben muss. Nur wie gibt 
man zeitliche constraints für offene Netzwerke an, das geht ja 
wahrscheinlich am einfachsten, wenn man das Netzwerk in eine Komponente 
auslagert und irgendwie angibt, dass die Ausgangs-Ports nach einer 
maximalen Zeit stabil sein sollen, nachdem man eine beliebige 
Kombination der Eingangsports verändert hat. Da wird sich ja bestimmt 
einen Pfad berechnen lassen, der irgendwie alle dominiert, den man als 
"repräsentativ" für diesen Kombinatorik-Block bezeichnen könnte 
(Zusammensetzung aus Gatter-Anzahl und aufsummierter Leitungslänge 
irgendwie).

Viele Grüße,
Deci

von greg (Gast)


Lesenswert?

Michael S. schrieb:
> Der Synthetisierer kann ja nicht wissen, ob
> ich der Kombinatorik vielleicht 2 Takte zum auswerten gönnen möchte,
> oder?

Das Signal erst nach zwei Taktzyklen lesen wäre die offensichtliche 
Möglichkeit, das sollte die Synthese auch merken. Alles andere hat 
sowieso das Potential, Glitches zu erzeugen, oder?

von Christoph Z. (christophz)


Lesenswert?

greg schrieb:
> Michael S. schrieb:
>> Der Synthetisierer kann ja nicht wissen, ob
>> ich der Kombinatorik vielleicht 2 Takte zum auswerten gönnen möchte,
>> oder?
>
> Das Signal erst nach zwei Taktzyklen lesen wäre die offensichtliche
> Möglichkeit, das sollte die Synthese auch merken. Alles andere hat
> sowieso das Potential, Glitches zu erzeugen, oder?

Hatte auch gehofft, das mein Synthesizer (Symplify Pro) merkt, dass um 
den kombinatorischen Block herum ein Enable-Signal Signal mit 2-Takten 
verzögerung läuft.
Leider nicht, es sind für die Pfade durch den kombinatorischen Block 
zusätzliche Constraints nötige, damit der Syntesizer das korrekte Timing 
kennt (eine MultiCylcle Constraint).

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.