Forum: FPGA, VHDL & Co. RISC V minmal CPU


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von chris_ (Gast)


Lesenswert?

Mich würde der Resourcenverbrauch der kleinsten RISC V Cpu 
interessieren.

Hier gibt es die "Potato CPU":
https://github.com/skordal/potato

Ist das die Minimalste? Wie gut passt die auf ein MAX1000?

von FrüherVogel (Gast)


Lesenswert?

Moin.
Zum MAX1000 kann ich dir leider nichts getestetes sagen. Aber einen 
RV32I kriegt man sicher drauf, zwei oder mehr schätze ich wird 
aufwendiger.

Minimalste Implementierung eines Risc-v halte ich aber mal nach 
schnellem Blick für unwahrscheinlich. Laut dem Repo implementiert der 
Prozessor RV32I, d. h. Es lassen sich schon mal 512 weitere FFs 
einsparen, wenn man auf RV32E umbaut. Außerdem frage ich mich, ob man 
mit weniger Pipelinestufen nicht auch noch etwas Platz durch weniger FFs 
spart.

von Strubi (Gast)


Lesenswert?

Moin,

mit rund 850 Xilinx-Slices muss man wohl fuer eine pipelined rv32i 
rechnen. Die Potato ist da nicht wirklich 'minimal'.
Tricksen kann man noch mit non-pipelined-Ansaetzen, wie es auch die 
picorv32 macht, da schafft man es meist so auf die Haelfte.

Wenn du strenge Anforderungen an Kompaktheit (incl. Opcodes) hast, 
wuerde ich zur ZPU-Architektur greifen. Da gibt's alles, was es bei 
RISC-V auch gibt, nur wird der GCC nur noch unter der Hand gewartet.

von chris_ (Gast)


Angehängte Dateien:

Lesenswert?

Strubi
>Tricksen kann man noch mit non-pipelined-Ansaetzen, wie es auch die
>picorv32 macht, da schafft man es meist so auf die Haelfte.

Interessanter Hinweis, danke.
https://github.com/cliffordwolf/picorv32

Leider ist die CPU in Verilog geschrieben. Ich bin nur mit VHDL 
unterwegs.

Bei der Potato-CPU scheint es Optionen zu geben, auf nicht 
laufzeitoptimierte Varianten umzuschalten.

FrüherVogel
>Es lassen sich schon mal 512 weitere FFs
>einsparen, wenn man auf RV32E umbaut.

Auch ein interessanter Hinweis. Wenn ich es richtig verstehe, hat die 
E-Version nur weniger Register, aber ansonsten den gleichen Befehlssatz.

Ist der angehängte der Richtige?

von Strubi (Gast)


Lesenswert?

chris_ schrieb:
> Leider ist die CPU in Verilog geschrieben. Ich bin nur mit VHDL
> unterwegs.

Wenn ich mich recht entsinne, sollte iverilog das nach VHDL uebersetzen 
koennen, allerdings muss man die Configurationsparameter manuell 
einfuegen.

Ansonsten findest du hier noch eine kompakte Variante in VHDL (ohne 
Support):

https://github.com/hackfin/MaSoCist/tree/opensource/hdl/riscv/pyrv32

Befehlssatz ist RV32I compliant, aber CSR/Debug/IRQ-Logik nicht nach 
Berkeley-Vorgabe implementiert, siehe auch

https://section5.ch/index.php/2019/09/27/risc-v-experiments/

von Cle (Gast)


Lesenswert?

Also wenn wir über minimale Implementierungen reden muss ich den 
bit-seriellen Serv von Olof Kindgren (RISC-V Ambassador) erwähnen:

Repo:
https://github.com/olofk/serv

Eine der vielen Präsentationen vom RISC-V Workshop (gibt auch irgendwo 
aktuellere):
https://www.youtube.com/watch?v=xjIxORBRaeQ

Dazu hat er den CoreScore eingeführt (https://corescore.store/) eine Art 
Benchmark für Place&Route und FPGAs (muss man natuerlich relativ sehen).

Wenn man die minimalen Resourcenverbrauch anschauen muss ist es 
natürlich gefährlich bei einer minimalen Implementierung die benötigte 
Peripherie zu vergessen. Ich habe selber mal einen kleinen RV32I core 
implementiert, mit Peripherie irgendwo um 1900 LUTs und 900 FFs auf 
7-Series (plus X-BRAM jenach benötigtem RAM/ROM wenn man das onboard 
machen will). Wobei viel das Routing zum BRAM Speicher und 
UART/Timer/Interrupt/... ausmacht. Der pure Core wäre vllt bei etwa 
1/3-1/2 davon.

PS: Ist es wichtig die CPU in VHDL zu haben? Wrappe die doch einfach in 
dein VHDL, falls du groß was daran ändern willst natürlich schwierig. 
Fast jede (gute) OpenSource Implementierung ist in Verilog.

von FrüherVogel (Gast)


Lesenswert?

chris_ schrieb:
> Wenn ich es richtig verstehe, hat die
> E-Version nur weniger Register, aber ansonsten den gleichen Befehlssatz.
>
> Ist der angehängte der Richtige?

Du verstehst richtig :)

Das Dokument sieht ziemlich richtig aus. Falls du was damit selber bauen 
willst, empfehle ich aber direkt zur offiziellen Spec zu greifen.

von lexi (Gast)


Lesenswert?

Wie wäre es mit dem **NEORV32**? https://github.com/stnolting/neorv32

Ist in VHDL geschrieben, hat ein Datenblatt [sic] und ein komplettes SoC 
mit Software-Libraries drumherum.

Alle Befehlssatzerweiterungen und Peripherieeinheiten können über 
generics konfiguriert bzw. ein-/ausgeschaltet werden.

Größenübersicht: 
https://github.com/stnolting/neorv32#fpga-implementation-results

Eine kleine Konfiguration des Prozessors passt sogar in ein Lattice 
ice40 FPGA 
(https://github.com/stnolting/neorv32#neorv32-processor---exemplary-fpga-setups).

von Tim  . (cpldcpu)


Lesenswert?

lexi schrieb:
> Wie wäre es mit dem **NEORV32**? https://github.com/stnolting/neorv32
>
> Ist in VHDL geschrieben, hat ein Datenblatt [sic] und ein komplettes SoC
> mit Software-Libraries drumherum.

Das sieht wirklich ziemlich vorbildlich aus. Da juckt es schon wieder in 
den Fingern, die FPGA-Boards doch noch einmal wieder auszupacken. Wenn 
doch nur die Toolchains nicht so nervig wären...

von chris_ (Gast)


Lesenswert?

Tim  . (cpldcpu)
07.03.2021 14:06

>Das sieht wirklich ziemlich vorbildlich aus. Da juckt es schon wieder in
>den Fingern, die FPGA-Boards doch noch einmal wieder auszupacken. Wenn
>doch nur die Toolchains nicht so nervig wären...

Ich hab's jetzt mal auf eine DE0-Nano mit dem hier probiert:
https://github.com/stnolting/neorv32/tree/master/boards/de0-nano-test-setup

Mit Quartus war es relativ problemlos: Ich musste nur die VHDL-Files ins 
Projekt ziehen, den richtigen Chipt des DE0-Nano vorher auswählen und 
die Pinkonfiguration anpassen.

( einfach ins qsf-File kopieren )
1
set_location_assignment PIN_R8 -to clk_i
2
set_location_assignment PIN_L3 -to gpio_o[7]
3
set_location_assignment PIN_B1 -to gpio_o[6]
4
set_location_assignment PIN_F3 -to gpio_o[5]
5
set_location_assignment PIN_D1 -to gpio_o[4]
6
set_location_assignment PIN_A11 -to gpio_o[3]
7
set_location_assignment PIN_B13 -to gpio_o[2]
8
set_location_assignment PIN_A13 -to gpio_o[1]
9
set_location_assignment PIN_A15 -to gpio_o[0]
10
set_location_assignment PIN_J15 -to rstn_i
11
set_location_assignment PIN_C3 -to uart0_txd_o
12
set_location_assignment PIN_A3 -to uart0_rxd_i

Beim DE0-Nano braucht man einen externen FDTI-Adapter für die serielle 
Schnittstelle und die in der Doku beschriebenen Baudrate von 19200 
stimmt nicht. Man muss 9600 Baud einstellen.

Das Blinkbeispiel in C kann man einfach mit "make" erzeugen:
https://github.com/stnolting/neorv32/tree/master/sw/example/blink_led

Allerdings will ich, um RISC-V richtig kennen zu lernen, reinen 
Assembler verwenden.
Ich kann zwar schon irgendwie kompilieren, aber blinken tut's noch 
nicht:

Beitrag "Re: RISC-V Gnu Assembler"

von lexi (Gast)


Lesenswert?

chris_ schrieb:
> Beim DE0-Nano braucht man einen externen FDTI-Adapter für die serielle
> Schnittstelle und die in der Doku beschriebenen Baudrate von 19200
> stimmt nicht. Man muss 9600 Baud einstellen.

Wenn du die Dateien selber zu einem Projekt hinzufügst, musst du die 
Taktrate über ein Generic einstellen:
1
CLOCK_FREQUENCY : natural := 50000000; -- clock frequency of clk_i in Hz

von lexi (Gast)


Lesenswert?

chris_ schrieb:
> Das Blinkbeispiel in C kann man einfach mit "make" erzeugen:
> https://github.com/stnolting/neorv32/tree/master/sw/example/blink_led
>
> Allerdings will ich, um RISC-V richtig kennen zu lernen, reinen
> Assembler verwenden.
> Ich kann zwar schon irgendwie kompilieren, aber blinken tut's noch
> nicht:

Im selben Verzeichnis gibt es auch eine reine ASM-Version zum blinken 
der LEDs (blink_led_in_asm.S). Die kann man per define anstelle der 
C-Version nehmen:
1
/** Use the custom ASM version for blinking the LEDs defined (= uncommented) */
2
#define USE_ASM_VERSION

von chris_ (Gast)


Lesenswert?

>Die kann man per define anstelle der C-Version nehmen:

Danke für den Hinweis. Ich habe schon alle möglichen Verrenkungen 
ausprobiert, um das ASM-File zu kompilieren. Wo ist das #define zu 
finden?

von chris_ (Gast)


Lesenswert?

>#define USE_ASM_VERSION

Hab's gefunden .. mitten im C-File.

Ich will das Ganze auf's Wesentliche reduzieren, aber in dem Beispiel 
wird die gesamte Compiler Initialisierung mitkompiliert. Zum Blinken 
einer LED müssen ein paar Zeilen Assembler ohne riesen C-Framework 
Overhead reichen.
Wieviel Bytes braucht man mit einem RISC-V, um eine LED sichtbar blinken 
zu lassen?

von lexi (Gast)


Lesenswert?

chris_ schrieb:
> Ich will das Ganze auf's Wesentliche reduzieren, aber in dem Beispiel
> wird die gesamte Compiler Initialisierung mitkompiliert. Zum Blinken
> einer LED müssen ein paar Zeilen Assembler ohne riesen C-Framework
> Overhead reichen.

Du musst erstmal herausfinden, wie man überhaupt die LED ansteuert. Wenn 
das Ausgangsregister irgendwo in den Adressraum eingeblendet ist, kann 
man das bestimmt ganz einfach machen. Ich würde das so versuchen:
1
  li x1, 123456 // Adresse des LED Ausgangsregister
2
3
start:
4
  li x2, 1000 // Wartezeit
5
6
loop: // Warteschleife
7
  addi x2, x2, -1 // x2 dekrementieren
8
  bne  x2, x0, loop // zu loop springen wenn x2 nicht null
9
10
  // memory-mapped LED toggeln
11
  lw   x2, 0(x1) // aktuellen Status der LED lesen
12
  xori x2, x2, 1 // invertieren
13
  sw   x2, 0(x1) // neuen Status schreiben
14
15
  j start

Allerdings wirst du wahrscheinlich noch zusätzlichen Code brauchen, um 
die CPU überhaupt erst mal zu initialisieren. Aber das ist natürlich 
absolut Hardware-spezifisch.

chris_ schrieb:
> Wieviel Bytes braucht man mit einem RISC-V, um eine LED sichtbar blinken
> zu lassen?

Wenn jede Anweisung 32-bit entspricht, dann wären das 8x4 = 32 Bytes. 
Verwendet man die komprimierten Instruktionen lässt sich das auf 16 
Bytes halbieren

von chris_ (Gast)


Lesenswert?

>Allerdings wirst du wahrscheinlich noch zusätzlichen Code brauchen, um
>die CPU überhaupt erst mal zu initialisieren.

Hmm ... ich würde denken, dass nur die Zeile 154-160 zur Initialisierung 
der IO notwendig ist:

https://github.com/stnolting/neorv32/blob/master/sw/common/crt0.S

Die Adresse des Outputport findet sich hier in Zeile 716:
https://github.com/stnolting/neorv32/blob/master/sw/lib/include/neorv32.h
1
#define GPIO_OUTPUT (*(IO_REG32 0xFFFFFF84UL))

von Andreas Rückert (Gast)


Lesenswert?

Evtl ist das noch interessant

https://github.com/maikmerten/spu32

https://youtu.be/LjN_SxblH2U

Läuft mit vga usw schon auf einem 15,- mini dev Board, wenn man einen 
passenden Shield drauf steckt

von chris_ (Gast)


Lesenswert?

> von lexi (Gast)
Hier ist dein Blink-programm mit einem Direktzugriff auf die 
Portadresse.
Die Warteschleifenzähler muss deutlich höher als 1000 sein, da der 
Prozessor mit 100MHz getaktet wird.
Das Programm funktioniert nur als Subroutine wenn sie vom Main 
aufgerufen wird. Es sieht also so aus, als wenn diese Architektur 
tatsächlich noch eine Initialisierung braucht. Ich tippe vorerst auf die 
Interruptvektoren....

1
.file  "blink_led_in_asm.S"
2
.section .text
3
.balign 4
4
.global blink_led_asm
5
6
blink_led_asm:
7
  li x1, 0xFFFFFF84 // Adresse des LED Ausgangsregister
8
9
start:
10
11
  li x2, 1000000 // Wartezeit
12
13
loop: // Warteschleife
14
15
  addi x2, x2, -1 // x2 dekrementieren
16
17
  bne  x2, x0, loop // zu loop springen wenn x2 nicht null
18
19
  // memory-mapped LED toggeln
20
21
  lw   x2, 0(x1) // aktuellen Status der LED lesen
22
23
  xori x2, x2, 0xAA // jedes zweite Bit invertieren (4 Leuchtidioden gleichzeitig)
24
25
  sw   x2, 0(x1) // neuen Status schreiben
26
27
  j start
28
29
30
.end

von chris_ (Gast)


Lesenswert?

>https://github.com/stnolting/neorv32/

Weiß jemand, wie man am besten vorgeht, wenn man bei dem Projekt eigene 
Peripherie hinzufügt?
Es mach ja wenig Sinn, nur vorgefertigte Module zu konfigurieren. Da 
könnte man gleich einen normalen, passen Mikrocontroller aussuchen.
Ich will einen Sigma-Delta-Wandler implementieren und der einfachste Weg 
scheint mir zu sein, die PWM-Unit "auszubeinen" und durch den 
Wandlercode zu ersetzen.

von lexi (Gast)


Lesenswert?

RISC-V is super dafür ausgelegt, um eigene Instruktionen hinzuzufügen. 
Wenn es aber eher um Peripherie geht, dann würde ich die klassich mit an 
das Businterface hängen.

Bei dem Projekt gibt es auch ein Modul für Anwenderhardware:

https://hackaday.io/project/174167-the-neorv32-risc-v-processor/log/189743-a-native-neopixel-interface-for-the-neorv32

Im Zweifel am besten mal auf github in den Diskussionen nachfragen.

von chris_ (Gast)


Lesenswert?

>Bei dem Projekt gibt es auch ein Modul für Anwenderhardware:

Ich habe mir mal die Anbindung in
1
neorv32_top.vhd

angesehen.

Dort sieht man, dass mittels der Konfigurationsflags das Modul für die 
Konfiguration freigeschaltet werden kann:
1
  neorv32_neoled_inst_false:
2
  if (IO_NEOLED_EN = false) generate
3
    neoled_rdata <= (others => '0');
4
    neoled_ack   <= '0';
5
    neoled_cg_en <= '0';
6
    neoled_irq   <= '0';
7
    neoled_o     <= '0';
8
  end generate;

Wenn man aber nach den Signalen des Moduls sucht, sind die kreuz und 
quer im  Toplevel verteilt

Bsp.: neoled_ack
1
  -- processor bus: CPU transfer ACK input --
2
  p_bus.ack <= (imem_ack or dmem_ack or bootrom_ack) or wishbone_ack or (gpio_ack or mtime_ack or uart0_ack or uart1_ack or
3
               spi_ack or twi_ack or pwm_ack or wdt_ack or trng_ack or cfs_ack or nco_ack or neoled_ack or sysinfo_ack);

Wenn ich das richtig sehe, bedeutet das für eigene Module, dass man an 
die ganzen Stellen etwas dazu bauen muss. Da fände ich ein einfacheres, 
besser entkoppeltes Interface praktischer.

von lexi (Gast)


Lesenswert?

chris_ schrieb:
>>Bei dem Projekt gibt es auch ein Modul für Anwenderhardware:

Ich meinte das "Custom Functions Subsystem". Schau einfach mal nach CFS 
ins der Doku bzw. im Code.

chris_ schrieb:
> Wenn man aber nach den Signalen des Moduls sucht, sind die kreuz und
> quer im  Toplevel verteilt

Der interne Bus erinnert stark an Wishbone. Das eigentliche 
Bus-Interconnect ist hier nicht als eigenes Modul instantiert, sonder 
eben direkt in der Top beschrieben. Bei dem Rückkanal zur CPU handelt es 
sich im Endeffekt nur um einige breite Oder-Gatter, die eben z.B. für 
das ACK an der von dir angemerkten Stelle implementiert sind.

von my 50 Cents (Gast)


Lesenswert?

Welche Anwendungsfälle deckt ihr denn mit solchen RISC CPUs ab? Das sind 
doch meist kleine Sächelchen die sich auch direkt realisieren ließen, 
oder?

von lexi (Gast)


Lesenswert?

my 50 Cents schrieb:
> Welche Anwendungsfälle deckt ihr denn mit solchen RISC CPUs ab? Das sind
> doch meist kleine Sächelchen die sich auch direkt realisieren ließen,
> oder?

In meinem Fall zum Beispiel, um "online" Bitstreams zu laden. Das kann 
man natürlich auch über eine State-Machine machen, aber ein Softcore ist 
- meiner Meinung nach - einfacher und deutlich flexibler. C-Entwicklung 
geht viiiiel schneller als VHDL-Entwicklung.

von chris_ (Gast)


Lesenswert?

my 50 Cents (Gast)
>Welche Anwendungsfälle deckt ihr denn mit solchen RISC CPUs ab?

Ich habe eine CPU-Emulation mit eigenem Befehlssatz und ich wollte mal 
sehen, wie schnell das mit dem RISCV Befehlssatz läuft.

Im Moment experimentiere ich aber nur mit dem NEO-Projekt und schaue, 
was man damit machen kann.
Als einfaches Beispiel könnte man einen reziproken Frequenzzähler 
basteln, so wie hier diskutiert:

Beitrag "Re: FPGA reziproker Frequenzzähler"

Man könnte einen eigenen Frequenzzählerblock machen und den via AXI an 
die CPU hängen.
Ich frage mich aber, ob ein 32Bit-Bus im FPGA nicht zu viel Resourcen 
verbraucht und man das Modul nicht besser über SPI anbindet.

von lexi (Gast)


Lesenswert?

chris_ schrieb:
> Ich frage mich aber, ob ein 32Bit-Bus im FPGA nicht zu viel Resourcen
> verbraucht und man das Modul nicht besser über SPI anbindet.

Also wenn es um eine Verbindung INNEHALB eines FPGAs geht, dann würde 
ich vom Gefühl her sagen, dass eine direkte parallele Verbindung weniger 
Resourcen braucht als eine serielle. Bei der seriellen must du erst 
serialisieren und dann in der Peripherie wieder de-serialisieren + 
Controller overhead...

Wenn man nur einen Controller mit ein wenig Peripherie verbinden will 
und man nicht unbedingt Gb/s zwischen Peripherie und Beschleuniger 
braucht, dann würde ich die über ein vereinfachtes Wishbone koppeln.

Antwort schreiben

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

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [vhdl]VHDL-Code[/vhdl]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.