Es geht um die mehrfache Instanziierung eines softcore mit verschiedenen
Programmen. Bei den softcores, mit denen ich bisher zu tun hatte
(OpenRISC mor1kx, RISC-V neorv32) wird das Programm als VHDL-Konstante
in einen BRAM initialisiert. Man kompiliert also die ganze uC-source
und den Befehlsspeicher mit der Initialisierung. Von Haus aus ist es
zumindest bei den zweien anscheinend nicht vorgesehen, mehrere Instanzen
des uC mit verschiedenen Befehlsspeichern zu haben (siehe z.B. [1],[2]).
Idealerweise müsste man theoretisch nur einmal die uC-source (für Sim
[Modelsim] / Syn [Vivado]) kompilieren und mehrere Befehlsspeicher
generisch einbinden.
Meine Frage geht nun in die Richtung best practices, Anpassungen
vorzunehmen, um mehrere softcore-Instanzen zu unterstützen. Bzw.
eigentlich genauer: Wie kann ich möglichst aufwandsarm und sauber
verschiedene Befehlsspeicher einbinden?
Meine Lösungsansätze bisher gehen in drei Richtungen.
#########
Zum einen wäre die Trennung über "namespaces", sprich man kompiliert
jeweils die komplette uC-Source mit Befehlsspeicher in verschiedene
libraries. Die sourcen muss man jeweils so umschreiben, dass z.B. statt
"library neorv32" jeweils "library work" in den uC-sourcen verwendet
wird und eine lib pro uC-Instanz zur Compilezeit festgelegt wird.
Sim: Modelsim kommt damit klar; Syn: Vivado will nicht eine Datei selben
Namens in verschiedene libraries packen(?)
Vorteil: minimal invasiv bzgl. uC-source; Nachteil: man muss die
uC-sourcen mehrmals in verschiedene libraries kompilieren
#########
Eine andere Möglichkeit ist, den Befehlsspeicher halbwegs generisch
umzuschreiben. Entweder über den HDL-Weg, dass verschiedene
Befehlsspeicher bzw. VHDL-Konstanten inkludiert werden oder über den
"make"-Weg, wo man vor Sim/Syn mehrere passende Dateien erzeugt und
Ersetzungen vornimmt.
HDL-Weg: zwar schöner, aber man muss die Änderungen dann immer wieder
reinpatchen, wenn man sich Änderungen des softcore aus upstream holt;
weiterhin müssen neben HDL tlw. auch die mitgelieferten Tools angepasst
werden, die aus dem C-Kompilat den HDL-Befehlsspeicher erzeugen (oder
das Resultat dieser Tools muss während des Bauprozesses nochmal
nach-prozessiert werden)
make-Weg: fricklig, aber man muss die sourcen nicht direkt anfassen; man
bestimmt von extern, welche uC-Instanz welches Programm bekommt, was
schön ist, da man extern ja auch erstmal den C-Compiler anwirft und den
output dann irgendwohin zuordnet, an genau dieser einen Stelle im
workflow und nicht verteilt über makefile und HDL
#########
Für die Synthese habe ich den Weg, per Vivado-tcl-Befehlen den
BRAM-Inhalt im Bitstream zu ändern. Dafür muss der Befehlsspeicher
umgeschrieben werden, dass er nicht mehr inferiert wird, sondern per
Primitiven instanziiert. Die Programmzuordnung für mehrere Instanzen
würde damit funktionieren, eine Initialisierung per immer gleicher
default-Konstante ist erstmal egal. Um dieses Prinzip für die Simulation
zu übernehmen, bräuchte es dann aber einen dritten workflow (neben
default HDL-Konstante und Vivado tcl), was ich unpraktisch finde.
Den BRAM-Update-Weg würde ich in diesem Thread sogar explizit außen vor
lassen, da der etwas per Technologie-Workflow speziell löst, was ich
eigentlich per HDL allgemein lösen wollen würde.
#########
Hat jemand Ideen, wie man die beschriebene mehrfache Instanziierung am
besten macht? Vielleicht gibt es ja bei anderen softcores dafür schon
gute Lösungen?
Beispiel für neorv32:
[1]
https://github.com/stnolting/neorv32/blob/main/rtl/core/neorv32_application_image.vhd
[2]
https://github.com/stnolting/neorv32/blob/main/rtl/core/mem/neorv32_imem.default.vhd
Ich glaube ich verstehe deinen Aufbau nicht so ganz... Wie sieht denn
dein Multicore-Setup generell aus? Also wie reden die einzelnene Kerne
miteinander?
Hängen die alle an einem Bus und haben Zugriff auf den gleichen
Adressraum? Oder kommunizieren die über Mailboxen/FIFOs/Whatever und
jeder Kern hat seinen eigenen Programmspeicher?
Lexxi schrieb:> Wie sieht denn> dein Multicore-Setup generell aus? Also wie reden die einzelnene Kerne> miteinander?
Das sind mehrere unabhängige softcores in der FPGA fabric. Die können,
müssen aber nicht miteinander reden. Bzgl. Befehls-/Datenspeicher und
Adressraum hat jeder seinen eigenen.
Die Anwendung/Architektur ist für mein Problem nicht unbedingt wichtig.
Mir geht es darum, wie ich mehrere dieser softcores mit verschiedener SW
vom workflow her am besten in einem FPGA-Design instanziiert kriege.
Dann würde ich die Kern-ID per Generic bis zum Programmspeicher
herunterreichen und dann darüber die jeweile "Memory-Init-Datei"
auswählen. Die einzelnen init-Files vorher per Skript erzeugen lassen
und als progmem_core_xx.vhd (xx = Kern ID) in das Projekt einfügen.
Dazu müsste man aber natürlich die Codebasis ändern... Ist vielleicht
nicht die beste Idee, aber so würde ich das erstmal versuchen.
Das ist sogar noch besser. Man müsste dann nur einen Kommunikation
zwischen den Kernen ermöglichen, damit mat den Zugriff aufs Flash
koordinieren kann (egal ob es ein globales SPI Module gibt oder jeder
Kern sein eigenes hat).
Wenn Flash keine Option ist und du noch ordentlich viel Speicher im FPGA
hast, könntest du auch alle Programme zu einem Paket kombinieren, in
einem großen globalen Speicher ablegen und dann holt sich jeder Kern von
da seinen Code - also kopiert den von da in den Kern-lokalen Speicher.
Softcore ist anlocken mit leckeren Erdbeeren. Hardcore ist mit der
Peitsche. Das funktioniert nur bei nicht hormongesteuerten µC nicht.
Lexxi schrieb:> Wenn Flash keine Option ist und du noch ordentlich viel Speicher im FPGA> hast, könntest du auch alle Programme zu einem Paket kombinieren, ...
Du könntest das Programm komprimiert ablegen im Flash. Jeder Kern holt
sich dann sein Programm entpackt in den RAM. Die Häufigkeit in den Flash
zu schreiben ist bekanntlich limitiert.
Als klassische State of the Art gilt u.A. die
BF561-Doppelkern-Architektur. Da sollte man sich möglichst nah dran
orientieren, wenn man nicht in exotische Prozessorgefilde abdriften
will.
Die Frage ist immer, welcher Kern mit welchem Daten austauschen muss.
Jeweils zwei können über Shared Dual-Port-RAM Daten austauschen, wenn
Peripherie im Spiel ist, empfiehlt sich Autobuffer-DMA, wo nur noch
Deskriptorenlisten unterhalten werden müssen. Dann muss man die
FIFO-Bufferqueues nur noch richtig in der Software 'verzahnen'.
Was die SW angeht: per Linkerscript alloziert man die gesamte Software
inkl Libraries einfach in ein ELF-Binary, ein Loader muss dann die
Adressbereiche in die entsprechenden CPU-Bänke mappen. Das ELF kannst du
mit div. bestehenden Lösungen in VHDL für die einzelnen Bänke umwandeln,
so dass ein vorinitialiertes TDP-Memory bei rauskommt (portabel). Ein
CPU-Kern ist bei mir immer Master-Controller und lädt die anderen. Und
um das alles auf Jahre hinweg robust unterhaltbar zu halten:
Make-Lösung.
Ist ein bisschen die Frage, wieviele CPU-Kerne du da brauchst, ob der
neorv32 die Performance bringt oder das eher auf einen Flaschenhals
rausläuft und du andere Architekturen nehmen musst.
Danke für eure Antworten.
Lexxi schrieb:> Dann würde ich die Kern-ID per Generic bis zum Programmspeicher> herunterreichen und dann darüber die jeweile "Memory-Init-Datei"> auswählen.
Ja, im Prinzip eine Anpassung der HDL wie im Eingangspost beschrieben
mit allen damit verbundenen Vor- und Nachteilen.
uwe schrieb:> Jeder bekommt einen Bootloader und lädt sich aus einem Flash sein> Programm.Lexxi schrieb:> alle Programme zu einem Paket kombinieren, in> einem großen globalen Speicher ablegen und dann holt sich jeder Kern von> da seinen Code - also kopiert den von da in den Kern-lokalen Speicher.
Da ist kein Flash. Im einfachsten Fall soll es funktionieren, dass das
Design per JTAG in den SRAM-basierten FPGA geladen wird und keine
externe Peripherie benötigt.
Fitzebutze schrieb:> Als klassische State of the Art gilt u.A. die> BF561-Doppelkern-Architektur.
Ich möchte ja keine neue Architektur. Die Kerne brauchen noch nicht mal
miteinander zu kommunizieren. Die sollen im einfachsten Fall nur ein
paar einigermaßen flexible FSMs implementieren oder einen bestimmten
performance-unkritischen Algorithmus bereitstellen.
Dabei finde ich es gerade vorteilhaft, wenn die Cores alle unabhängig
voneinander sind, keine elf-files kombiniert werden müssen, ich die
Standard-Tools des softcore verwenden kann usw. .
Auf was ich hoffe, ist etwas Richtung Kniff in VHDL, vielleicht ein
geschickter wrapper für den Befehlsspeicher (ohne die eigentlichen
sourcen anfassen zu müssen), oder irgendwas mit generic packages, eine
minimale Anpassung der sourcen usw. . Ich bin ja bestimmt nicht der
erste, der z.B. einen Softcore einfach zweimal für unabhängige Aufgaben
in einem FPGA-Design einsetzen möchte. Die HDL-Beschreibung bzw. der
default-workflow bei den o.g. cores verhindert das aber leider und ich
suche einen best practice-Weg, das zu ändern.
Wenn du den neorv32 verwendest kannst du da ja mal auf GitHub im "Forum"
fragen. Vielleicht hat einer eine Idee oder die können dein Feature
direkt auf die TODO-Liste setzen.
VHDL hotline schrieb im Beitrag #7169146:
> Auf was ich hoffe, ist etwas Richtung Kniff in VHDL, vielleicht ein> geschickter wrapper für den Befehlsspeicher (ohne die eigentlichen> sourcen anfassen zu müssen), oder irgendwas mit generic packages, eine> minimale Anpassung der sourcen usw. . Ich bin ja bestimmt nicht der> erste, der z.B. einen Softcore einfach zweimal für unabhängige Aufgaben> in einem FPGA-Design einsetzen möchte. Die HDL-Beschreibung bzw. der> default-workflow bei den o.g. cores verhindert das aber leider und ich> suche einen best practice-Weg, das zu ändern.
Ja, gemacht haben das schon einige. Wenn es aber nur um ROM-Generierung
geht, hat dein Problem kaum noch mit Multicore-Architektur zu tun,
sondern nur, wie du elegant das ROM als HDL generierst. Das wird hier
mit Python-Skripten aus dem Opensource-Fundus
(https://github.com/hackfin/MaSoCist) generiert. Aus dem ELF werden
damit ROM-Files in VHDL die als generics übergeben werden. Damit wird
auch das komplette Multicore-System (4 Kerne) gebaut und simuliert. Die
Makefiles (an Linux kconfig angelehnt) muss man sich für seine Zwecke
anpassen und seinen Wunsch-Core ev. selber anbinden. Wenn die Cores
komplett unabhängig voneinander laufen sollen, spielt es auch nicht die
grosse Rolle, welche CPU-Architektur man auswählt.
Fitzebutze schrieb:> Ja, gemacht haben das schon einige. Wenn es aber nur um ROM-Generierung> geht, hat dein Problem kaum noch mit Multicore-Architektur zu tun,> sondern nur, wie du elegant das ROM als HDL generierst.
Genau, die Suche nach diesem eleganten Weg ist die Intention hier.
Fitzebutze schrieb:> Das wird hier> mit Python-Skripten aus dem Opensource-Fundus> (https://github.com/hackfin/MaSoCist) generiert.
Danke, schau ich mir mal an.
VHDL hotline schrieb im Beitrag #7168597:
> Zum einen wäre die Trennung über "namespaces", sprich man kompiliert> jeweils die komplette uC-Source mit Befehlsspeicher in verschiedene> libraries. Die sourcen muss man jeweils so umschreiben, dass z.B. statt> "library neorv32" jeweils "library work" in den uC-sourcen verwendet> wird und eine lib pro uC-Instanz zur Compilezeit festgelegt wird.
Meinst du damit nicht so etwas wie die VHDL configurations?
Also dort, wo man angeben kann, welche genaue Instanz jetzt inferiert
werden soll für einen Component. Damit sollte auch der Synthesizer klar
kommen.
Christoph Z. schrieb:> Meinst du damit nicht so etwas wie die VHDL configurations?> Also dort, wo man angeben kann, welche genaue Instanz jetzt inferiert> werden soll für einen Component. Damit sollte auch der Synthesizer klar> kommen.
Du hast recht, mit configurations könnte das klappen. Die hatte ich gar
nicht auf dem Schirm, zwar irgendwann im Studium mal gehört aber seitdem
nie produktiv benutzt.
Danke für den Tipp!
Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.
Wichtige Regeln - erst lesen, dann posten!
Groß- und Kleinschreibung verwenden
Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang