Ich wollte mal einen "generischen RAM" zusammenschreiben um zu sehen, ob
die Synthese dafür dann auch RAM-Blöcke des FPGA benutzt.
Leider bekomme ich dafür die berühmt-berüchtigte Fehlermeldung:
1
statement is not synthesizable since it does not hold its value under NOT(clock-edge) condition. VHDL-1242
So ganz verstehe ich aber nicht, was das Problem dabei ist … vielleicht
kann mir ja mal jemand auf die Sprünge helfen?
Port-Signale sollten selbst erklärend sein, wie beim üblichen RAM, nur
halt getaktet.
Versuchst du ein RAM zu bauen das wie ein separates IC einen
bidirektionalen Datenbus hat? So etwas gibt es innerhalb eines FPGAs
nicht und mich würde es überraschen wenn ein Synthesizer aus Dual-Port
RAM deine Beschreibung nachbauen kann. Aber zieh mal data <= Z in das if
rein denn das dürfte die Fehlermeldung bewirken.
Blechbieger schrieb:> Versuchst du ein RAM zu bauen das wie ein separates IC einen> bidirektionalen Datenbus hat?
Das war erstmal die Idee … spätestens, wenn man mal externen RAM
anschließen will, braucht man sowas ja – dachte ich mir. ;-)
> So etwas gibt es innerhalb eines FPGAs> nicht und mich würde es überraschen wenn ein Synthesizer aus Dual-Port> RAM deine Beschreibung nachbauen kann.
Interessanterweise scheint das zu klappen: LSE bescheinigt mir einen
Ressourcenverbrauch von einem Register und einem EBR-Block (sowie zwei
LUTs).
Synplify Pro braucht zwar 8 Register, implementiert aber den RAM auch
mit einem EBR-Block. Anbei der "Technology View" von Synplify.
> Aber zieh mal data <= Z in das if> rein denn das dürfte die Fehlermeldung bewirken.
Gut, ja, das hat's gebracht, danke!
Nun würde ich gern noch den Grund verstehen … warum darf ich die Zs nur
innerhalb des getakteten Blocks zuweisen?
Aber OK, den Datenbus aufzuteilen, ist natürlich gar kein Problem.
Interessant auf jeden Fall, dass die Synthese auch wirklich einen RAM
dafür benutzt und nicht etwa ein Grab voller Flipflops.
Jörg W. schrieb:> Blechbieger schrieb:>> Versuchst du ein RAM zu bauen das wie ein separates IC einen>> bidirektionalen Datenbus hat?>> Das war erstmal die Idee … spätestens, wenn man mal externen RAM> anschließen will, braucht man sowas ja – dachte ich mir. ;-)
Ein Modell eines externen RAM muss aber nicht synthetisierbar sein.
Jörg W. schrieb:> Nun würde ich gern noch den Grund verstehen … warum darf ich die Zs nur> innerhalb des getakteten Blocks zuweisen?
Der ganze Prozess ist getaktet (clk in der Sensitivity Liste) und wird
bei jeder TaktFLANKE ausgeführt. Für die steigende Flanke ist alles OK
aber bei fallender Flanke wird data immer auf Z gesetzt.
Hier vielleicht nicht zwingend notwendig aber in einer Simulation hätte
man den Fehler sehen können. Daher sind Testbenches immer zu empfehlen.
Blechbieger schrieb:> Der ganze Prozess ist getaktet (clk in der Sensitivity Liste) und wird> bei jeder TaktFLANKE ausgeführt. Für die steigende Flanke ist alles OK> aber bei fallender Flanke wird data immer auf Z gesetzt.
Danke für die Erklärung, leuchtet ein.
> Hier vielleicht nicht zwingend notwendig aber in einer Simulation hätte> man den Fehler sehen können. Daher sind Testbenches immer zu empfehlen.
Ja, wobei noch die Frage ist, ob ich das dort auch als "Fehler"
überhaupt erkannt hätte. Möglicherweise wäre es mir beim Drübergucken
als Feature durchgegangen.
Jörg W. schrieb:> Nun würde ich gern noch den Grund verstehen … warum darf ich die Zs nur> innerhalb des getakteten Blocks zuweisen?
Nehmen wir mal auseinander, was Du da bei der Synthese bestellt hast:
rising_edge(clk) ist definiert als
Wird also genau dann (und nur dann) für den Augenblick true, wenn eine
positive Taktflanke auftritt. Zu allen anderen Zeiten false.
1
data<="ZZZZZZZZ";
2
ifrising_edge(clk)then
3
ifce='0'then
4
ifwr='0'then
5
cells(ad)<=data;
6
elsifrd='0'then
7
data<=cells(ad);
8
endif;
9
endif;
10
endif;
11
...
Wenn wir das data <= "ZZZZZZZZ" mal kurz weglassen, bedeutet das, das in
eben jenem Augenblick das Register (oder eben in diesem Fall das RAM)
aktiv wird und kann entweder beschrieben oder ausgelesen werden. In
allen anderen "Augenblicken" behält es seinen Wert (speichert).
Jetzt hast Du noch eine asynchrone Signal-Zuweisung "dazugebastelt".
Wenn die Bedingung "rising_edge()" nicht erfüllt ist, soll High-Z
getrieben werden. Das widerspricht aber der "speicher"-Anweisung von
oben, die Synthese stellt fest, dass dein RAM gar keins mehr ist bzw.
maximal an der Taktflanke einen Spike (mit einem Wert, den es
"vergessen" hat und mit Hardware, die so was gar nicht kann) produzieren
könnte und meckert zurecht.
Marius W. schrieb:> Das erzeugt dir einen RAM mit genau 10 Bytes.
Ah ja, danke für den Hinweis. Hatte mich schon gewundert, warum die
Synthese nur 4 Adressleitungen produziert hat … ;-)
Ich habe es nicht getestet, es meusste aber in die Richtung vom meinem
Anhang gehen.
Die Z Zuweisungen im Prozesses fuer den Tristate und das Adress
Multiplexing ist nicht was du wolltest. Variablen werden erstmalig in
einem Prozess synthetisiert, behalten aber dann ueber die Laufzeit ihren
Wert.
Die Tristate Geschichte musst du ausserhalb des Prozesses machen.
Ebenso wuerde ich mir noch ueberlegen ob die wirklich seperate Rd und Wr
enable haben willst. Macht das Design robuster und falls doch unbedingt
separate Enables gewuenscht sind, wuerde ich das ein Layer darueber
packen.
Und vll. noch ein finaler Tipp: Nimm besser .vhd als Dateiendung. Das
ist deutlich verbreiteter.
Tobias B. schrieb:> Ich habe es nicht getestet, es meusste aber in die Richtung vom meinem> Anhang gehen.
Danke, werde das heute Abend mal durch den Simulator jagen (und meine
nun synthetisierte Fassung auch).
> Die Z Zuweisungen im Prozesses fuer den Tristate und das Adress> Multiplexing ist nicht was du wolltest. Variablen werden erstmalig in> einem Prozess synthetisiert, behalten aber dann ueber die Laufzeit ihren> Wert.
War mir im Prinzip klar, aber jetzt wo du's sagst: ich darf die Variable
nicht einfach nur initialisieren, sondern muss ihr stets neu den Wert
zuweisen, oder? Ansonsten habe ich da wohl ein Latch gezimmert …
> Die Tristate Geschichte musst du ausserhalb des Prozesses machen.
OK.
> Ebenso wuerde ich mir noch ueberlegen ob die wirklich seperate Rd und Wr> enable haben willst.
Hmm. Ich überlege gerade … Rd braucht man natürlich eigentlich nicht,
hatten ja "richtige" RAMs auch nicht. Allerdings hatten die ein /OE,
damit man die Ausgangstreiber aktivieren kann (also von Z nach
data_out). Das wäre wohl insgesamt sinnvoller – wenn man jetzt beim
bidirektionalen Datenbus bleibt.
> Und vll. noch ein finaler Tipp: Nimm besser .vhd als Dateiendung. Das> ist deutlich verbreiteter.
Da ich nie groß Windows benutzt habe, waren mir die Dateiendungen und
deren Länge immer so ziemlich egal. Bei mir gibt es auch JPEGs, die auf
.jpeg enden, TIFFs auf .tiff und so. Ja, ist natürlich 'ne Frage, wem
man das weiter gibt. Selbst Radiant ist da in sich nicht völlig
konsistent: sie bieten einem beim Neuanlegen einer VHDL-Datei an, ob man
sie auf .vhd oder .vhdl enden lassen will, aber wenn man eine
existierende Datei zum Design hinzufügen will, dann haben sie erstmal
nur *.v und *.vhd in der Auswahlmaske …
Jörg W. schrieb:> War mir im Prinzip klar, aber jetzt wo du's sagst: ich darf die Variable> nicht einfach nur initialisieren, sondern muss ihr stets neu den Wert> zuweisen, oder? Ansonsten habe ich da wohl ein Latch gezimmert …
Zum ersten Teil: Genau, nur initialisieren reicht nicht. Zum zweiten
Teil: Das ist in diesem Fall extremst tricky, weil du ja eine Variable
initialisierst, mit einem Signal das dynamisch ist. Eigentlich wuerde
ich erwarten, dass das Synthese Tool meckert, wenn du etwas
initialiseren willst und der Inhalt keine Konstante ist.
Jörg W. schrieb:> Hmm. Ich überlege gerade … Rd braucht man natürlich eigentlich nicht,> hatten ja "richtige" RAMs auch nicht. Allerdings hatten die ein /OE,> damit man die Ausgangstreiber aktivieren kann (also von Z nach> data_out). Das wäre wohl insgesamt sinnvoller – wenn man jetzt beim> bidirektionalen Datenbus bleibt.
Theoretisch ist es auch kein Problem, in der Praxis jedoch schon. In
deinem aktuellen Fall hast du mehr Freiheitsgrade als noetig und musst
aufpassen was im Fall beim gleichzeitig aktivierten Rd & Wr passiert.
Aktuell haettest du eine Bevorzugung von Wr im Prozess, also muss das
auch angepasst werden beim Tristate Buffer.
Das Ergebnis ist: Mehr Functional & Code Coverage bei der Verifizierung
ohne ein Gewinn an Funktionalitaet. Der fehlerfreiste Code ist immer
der, den man nicht schreiben muss. ;-)
Tobias B. schrieb:> Eigentlich wuerde ich erwarten, dass das Synthese Tool meckert, wenn du> etwas initialiseren willst und der Inhalt keine Konstante ist.
Wenn ich mir das Syntheseergebnis von SynplifyPro oben ansehe, dann
haben sie offenbar meine (nicht korrekt ausgedrückte) Intention
synthetisiert und diese Variable letztlich komplett weggelassen.
Eigentlich hatte ich sie auch nur eingeführt, weil ich es sinnvoll fand,
die Typumwandlung vom Vektor "addr" in den Array-Index (der "integer"
ist) nur an einer Stelle zu machen.
Das waere noch die letzte Falltuer die ich bei einem synthesetool
akzeptieren wuerde. Den Init-Wert von von dem Signal als Init-Wert fuer
die Variable nehmen. Da keine Zuweiseung mehr an die Variable erfolgt,
ist die Adresse praktisch eine Konstante mit Wert 0 und der RAM ist
genau Wort tief.
Jörg W. schrieb:> Eigentlich hatte ich sie auch nur eingeführt, weil ich es sinnvoll fand,> die Typumwandlung vom Vektor "addr" in den Array-Index (der "integer"> ist) nur an einer Stelle zu machen.
Der Sinn der Variablen ist voellig in Ordnung. Ist praktisch eine
Optimierung der Schreibweise, weil man die lange Cast-Verschachtelung
nur einmal niederschreiben muss.
Wuerde natuerlich auch mit einem Signal gehen, aber solange die Adresse
nur in dem Prozess gebraucht wird, ist das voellig in Ordnung, getreu
dem Motto: Keep local things local. :-)
Blechbieger schrieb:> Ein Modell eines externen RAM muss aber nicht synthetisierbar sein.
Aber er will ja synthetisieren.
Meine Frage: Warum soll man mit solchen Inferierten RAMs bauen und nimmt
nicht die echten, die angeboten werden?
M. W. schrieb:> Meine Frage: Warum soll man mit solchen Inferierten RAMs bauen und nimmt> nicht die echten, die angeboten werden?
Zwei Moeglichkeiten:
1.) Portierbarkeit. Gerade im Zusammenspiel mit Symplify macht das Sinn,
da ich davon ausgehe, dass Symplify mit dem gleichen Code auch fuer
unterschiedliche FPGAs entsprechenden RAM baut.
2.) Das vermute ich in diesem Fall: Lernen. :-)
Tobias B. schrieb:> Zwei Moeglichkeiten:
Ja, beide :-)
Es sollte insbesondere auch ein Test sein, ob die Synthese derartige
Konstrukte erkennt und dann auch tatsächlich durch "on-board RAM"
realisiert, und das haben sowohl Synplify als auch LSE demonstriert,
dass sie das können.
Wenn der Code mal final ist und du ihn hier reinstellst, kann ich ihn
auch mal noch in ISE, Vivado und Quartus synthetisieren. Dann haettest
du auch noch einen Vendor abhaengigen Vergleich. :-)
OK. Habe jetzt nochmal weitgehend deinen Vorschlag umgesetzt. Siehe da,
jetzt werden auch keine Register mehr gebraucht :)
Möchte das aber morgen erstmal simulieren und mir ansehen, ob's
eigentlich das ist, was ich mir vorgestellt habe. Dafür isses mir jetzt
schon etwas zu spät.
Danke jedenfalls schon mal an alle Mitdiskutanten!
Theoretisch gibt es den IEEE Std. 1076.6 'IEEE Standard for VHDL
RegisterTransfer Level (RTL) Synthesis', der auch einen Abschnitt zu RAM
und ROM enthält:
https://ieeexplore.ieee.org/servlet/opac?punumber=9308
Der kann zumindest als Anhaltspunkt dienen, was ein Synthesetool können
sollte.
Duke
Jörg W. schrieb:> Duke Scarring schrieb:>> Theoretisch gibt es den IEEE Std. 1076.6>> Hilft mir leider nicht viel, darauf habe ich keinen Zugriff (wie mir> scheint).
Das wäre (auch wenn Du drankämest - das Internet bietet da durchaus
alternative Möglichkeiten) nur begrenzt hilfreich: der Standard wurde
zurückgezogen, da muss sich keiner (mehr) dran halten.
(VHDL-1576) this construct is only supported in VHDL 1076-2008
Habe ich nicht ganz verstanden, alle VHDL-Tutorials benennen das
when-Schlüsselwort für nebenläufige Anweisungen als schon immer
vorhanden …
Wenn ich aber VHDL-2008 in den Einstellungen aktiviere, dann kackt die
LSE mit einer segmentation violation ab. :/ SynplifyPro geht aber.
Tobias B. schrieb:> Hast du diese Zeile in einem Prozess stehen?
Upps, ja. Das war natürlich nicht beabsichtigt. :/
OK, außerhalb des Prozesses geht es natürlich auch ohne VHDL-2008 – aber
LSE kackt trotzdem ab, sowie die Zeile drin ist.
Nun ja, es gibt ja noch SynplifyPro. :)
Kannst du mal das File posten, bei dem LSE den Geist aufgibt? Dann kann
ich hier auch mal probieren. LSE hat in der Tat ein paar Eigenheiten,
aber bei dem Tri-State versagen, das waere ein Unding. :-O
Tobias B. schrieb:> Kannst du mal das File posten, bei dem LSE den Geist aufgibt?
Ja klar.
Ich habe mal einen Supportrequest dafür aufgemacht.
Egal, wie fehlerhaft der Code sein mag, ein segfault ist nie eine
korrekte Antwort. ;-)
Tobias B. schrieb:> Macht auch hier im Diamond (unter Linux) Aerger.
Wird ja vermutlich das gleiche Binary für die Synthese sein.
In irgendeiner Ecke hatte ich mal das Wort "DiamondNG" gelesen, das
scheint das zu sein, als was das Programm mal ins Rennen gegangen ist.
Danach haben sie ihm dann mit "Radiant" lieber einen neuen Namen
gegeben.
Jep, das wird wohl wirklich so sein.
Und ich bekomm in keiner Variante dazu, dass der Segfault verschwindet.
Selbst wenn ich mich an das VHDL Template hlte klappt das nicht.
Bin mal gespannt was der Support dazu meint. In der Regel ist der
wirklich hilfsbereit, zumindest waren meine bisherigen Erfahrungen sehr
positiv.
OK, jetzt auch mal die ersten 2 Bytes in einer Testbench geschrieben und
gelesen. Sieht mir sinnvoll aus – OK, so sinnvoll, wie ein
bidirektionaler Bus innerhalb eines FPGA halt sein kann.
Wenn man das jetzt noch nach außen pinnt, könnte man wohl das FPGA als
teuren (und noch dazu getakteten) SRAM benutzen. :-)
Jörg W. schrieb:> OK, so sinnvoll, wie ein> bidirektionaler Bus innerhalb eines FPGA halt sein kann.
Ja, der Bus muss ja nicht FPGA intern sein. Wenn du die Ports sauber
nach aussen durchschleifst, dann bleibt der bi-direktionale Bus an den
FPGA IOs.
Es gibt pro und contra Argumente das so zu tun, ich mischel da auch
immer mal wieder durch, abhaengig davon welche Variante der einfacher zu
lesende Code ist.
Bei einem einfachen Speicher faellt es allerdings auch mir schwer da ein
gutes Argument zu finden. ;-)
Jörg W. schrieb:> Wenn man das jetzt noch nach außen pinnt, könnte man wohl das FPGA als> teuren (und noch dazu getakteten) SRAM benutzen. :-)
Vergleich mal die heutigen Preise von Dualport SRAM und einem FPGA.
In der letzten Firma hatten wir klassisch ein Dualport-RAM zwischen zwei
Prozessoren vorgesehen. Habe dann mal überschlagen wie viel BlockRAMs
ich dafür einsetzen könnte bzw. wie viel mehr ein grösserer FPGA
kostest, wenn es nicht reichen würde im Vergleich zu separatem
Dualport-RAM. (war ca. 2012):
1. Es gibt fasst nur noch Cypress das Dualport-RAMs baut
2. Damals gab es für den selben Preis einen FPGA der doppelt so viel
Speicher hat oder umgekehrt, ein FPGA kostet die Hälfte zum
vergleichbaren Dualport-RAM (ohne Entwicklungskosten) :-)
Geht natürlich nur, wenn man nicht aus ganz zwingenden Gründen wirklich
ein asynchrones RAM braucht. Das FPGA Dualport-RAM wird immer synchron
sein (wie die ganz schnellen Dualport-RAMs auch).
Christoph Z. schrieb:> Vergleich mal die heutigen Preise von Dualport SRAM und einem FPGA.
Na gut, ich hatte ja einen Singleport-RAM synthetisiert. Das war jetzt
mehr als Übung, um mit dem ganzen Tristate-Geraffel mal irgendwas
überhaupt anzustellen und ein Gefühl dafür zu bekommen. War ja auch gut
so, gerade Tobias' Hinweise dazu waren durchaus wertvoll in der
Lernkurve.
Aber interessant, dass ein FPGA, selbst wenn man große Teile davon brach
liegen lässt, am Ende billiger ist als ein dedizierter Dualport-RAM.
Dürfte an den Stückzahlen (und daraus folgend eher älterer
Silizium-Techologie) liegen: externe Dualport-RAMs werden wohl nur eine
Nische sein, FPGAs inzwischen ein Massenmarkt.
War ja mehr gedacht als interessante Anekdote zu deiner Aussage :-)
Jörg W. schrieb:> Dürfte an den Stückzahlen (und daraus folgend eher älterer> Silizium-Techologie) liegen: externe Dualport-RAMs werden wohl nur eine> Nische sein, FPGAs inzwischen ein Massenmarkt.
Hatte mich damals auch überrascht und bin zum Selben Schluss gekommen.
Gratis bekommt man dann auch noch JTAG für den Fertigungstest, Debug
Möglichkeiten, ja man kann sogar das RAM per Bitstream initialisieren.
Und kann nebenbei auch gleich die letzten übrig gebliebenen
Standard-Gatter ICs wegrationalisieren.
Tobias B. schrieb:> 2.) Das vermute ich in diesem Fall: Lernen. :-)
Positiv: ich habe nun in der Testbench auch noch procedures verstanden
und sogar mal eine for-Schleife verwendet. :) Damit wird sie angenehm
übersichtlich:
Das Thema Tri-State und RAM gab's hier schon mal:
Beitrag "Re: VHDL Grundlagen Ram"
Dort ging es auch darum, dass nicht alle Synthesetools die RAMs bei
jedem Hersteller gleich syntethisieren.
Bei Xilinx gab es speziell ein Dokument dafür, wie man Code schreiben
muß, damit das Synthesetool z.B. einen Block RAM erkennen kann.
Bei Lattice gibt es aber auch die Memory Guides, wo man sich die
RAM-Module rauskopieren kann und im VHDL-Code eine Instanz daraus bilden
kann.
Achja, deine Arrays kannst du auch mit der folgenden Zeile Code
initialisieren:
signal cells: ram_t := (others => ( others => '0') );
PCB schrieb:> Bei Lattice gibt es aber auch die Memory Guides, wo man sich die> RAM-Module rauskopieren kann und im VHDL-Code eine Instanz daraus bilden> kann.
Damit meinst du jetzt die speziellen Primitiven, die direkt EBR oder
SPRAM instanziieren? Die sind mir bewusst, ich wollte ja hier vor allem
sehen, ob/wie die Synthesetools auch von sich aus aus einem "generischen
RAM" das erkennen. Das hat ja recht gut funktioniert.
> Achja, deine Arrays kannst du auch mit der folgenden Zeile Code> initialisieren:> signal cells: ram_t := (others => ( others => '0') );
Danke, gute Idee.
Jörg W. schrieb:> Damit meinst du jetzt die speziellen Primitiven, die direkt EBR oder> SPRAM instanziieren? Die sind mir bewusst, ich wollte ja hier vor allem> sehen, ob/wie die Synthesetools auch von sich aus aus einem "generischen> RAM" das erkennen. Das hat ja recht gut funktioniert.
Er meint schon reines VHDL, Architektur unspezifisch. Z.B. in
https://www.xilinx.com/support/documentation/sw_manuals/xilinx10/books/docs/xst/xst.pdf
auf Seite 199.
Wie man an deinem Beispiel gut sieht, sind die Synthese Tools nicht hart
darauf angewiesen, dass ein RAM genau so beschrieben sein muss. Da hat
man deutlich mehr Freihheitsgrade und ein Array aus Vektoren ist da
schon fast alles was man braucht. :-)
Jörg W. schrieb:> Damit meinst du jetzt die speziellen Primitiven, die direkt EBR oder> SPRAM instanziieren? Die sind mir bewusst, ich wollte ja hier vor allem> sehen, ob/wie die Synthesetools auch von sich aus aus einem "generischen> RAM" das erkennen. Das hat ja recht gut funktioniert.
Hat das Synthesetool bei dir denn auch angezeigt, dass es eine RAM-Zelle
erkannt hat?
Weil ich selber gerade das Problem hatte: bei den ice40 FPGAs gibt es
keine verlässliche Angabe über die Timings der EBRs. Besonders beim
Auslesen der EBRs hatte ich Probleme. Der Leseport wird bei mir direkt
auf den IO gelegt und vom Timing her gabs bei einem Takt von ~25MHz
Probleme beim Empfänger.
Warum machst du nicht eigentlich nur eine READ/WRITE Leitung, wo du doch
noch ein Chip Enable hast?
@ Tobias:
Es stimmt zwar, dass ich zuerst die Coding Styles ansprach aber danach
meinte ich schon die Primitiven.
Man sollte sich meiner Meinung nach schon über die Struktur der
Primitiven Gedanken machen. Bei den ice40 FPGAs sind die EBRs z.B. nur
Pseudo Dual-Port RAMse, haben also nur einen Lese- und einen
Schreibport.
Und nur die 256x16Bit Primitive ist dort die Einzige, die eine
Bitmaskierung anbietet, weil sie die Standardprimitive ist, aus der die
anderen abgeleitet werden.
PCB schrieb:> Hat das Synthesetool bei dir denn auch angezeigt, dass es eine RAM-Zelle> erkannt hat?
Ja – sogar LSE, bevor es einen segfault geworfen hat. ;-)
> Warum machst du nicht eigentlich nur eine READ/WRITE Leitung, wo du doch> noch ein Chip Enable hast?
Das hatte Tobias ja weiter oben schon angemerkt.