
Kürzlich wurde auf Habré ein Artikel darüber veröffentlicht, wie man mit der RISC-V-Architektur ohne Hardwarekosten experimentieren kann. Aber was ist, wenn Sie dies auf dem Debugboard tun? Erinnern Sie sich an Memes über den Spielgenerator: Etwa 20 Häkchen im Stil von „Grafiken sind nicht schlechter als die Krise“, „Sie können Corovans ausrauben“ und der Schaltfläche „Generieren“. Der RocketChip SoC-Generator ist ungefähr gleich angeordnet, nur gibt es kein Fenster mit Häkchen, sondern einen Scala-Code und ein bisschen Assembler- und Make-Dateien. In diesem Artikel werde ich zeigen, wie einfach es ist, diesen RocketChip von seinem nativen Xilinx auf Altera / Intel zu portieren.
HAFTUNGSAUSSCHLUSS: Der Autor ist nicht für die "verbrannte" Karte verantwortlich. Achten Sie genau darauf, wie Sie die Pins konfigurieren, was Sie physisch verbinden usw. Beachten Sie auch die Sicherheitsvorkehrungen. Sie sollten nicht denken, dass, da alles über USB angeschlossen ist, dies für eine Person definitiv sicher ist: Wie ich in meinen vorherigen Experimenten verstanden habe, müssen Sie den Heizungsakku auch dann nicht mit dem Fuß berühren, wenn Sie mit einer USB-Karte arbeiten, da der potenzielle Unterschied ... Oh ja Ich bin bei weitem kein professioneller Plisovode- oder Elektronikingenieur - ich bin nur ein Scala-Programmierer.
Soweit ich weiß, war das Xilinx-FPGA die erste Plattform für das Debuggen von RocketChip. Dem Repository nach zu urteilen, das wir bald klonen, wurde es auch nach Microsemi portiert. Irgendwo hörte ich von der Verwendung von Altera, sah aber die Quelle nicht. Wie sich herausstellte, ist dies kein großes Problem: Von dem Moment an, als wir das Board erhalten und mit dem Studium des SiFive Freedom- Repositorys begonnen haben, bis hin zu einem funktionierenden 32-Bit-Mikrocontroller (der nur Prozessorregister, BootROM- und speicherabgebildete Register enthält) (obwohl dies bereits der Fall ist) etwas, das der Nanocontroller herausstellt ...) 3 Tage frei und 4 Wochen sind vergangen, und es hätte noch weniger SYNTHESIS
wenn ich sofort die Definition von SYNTHESIS
global definiert SYNTHESIS
.
Material
Für den Anfang - eine Liste von Materialien im weiteren Sinne des Wortes. Wir benötigen folgende Software:
- RocketChip - enthält den Prozessor selbst und die Assembly-Umgebung (einschließlich mehrerer Sub-Repositorys). Kann RocketChip mit Rocket Core-Prozessor in der richtigen Reihenfolge erstellen.
- SiFive Freedom - bindend für verschiedene Debug-Boards - dort werden wir Unterstützung für unsere hinzufügen
- Raketen-Tools - Tools zum Erstellen von Code und Debuggen für 32- und 64-Bit-RISC-V-Architekturen
- openocd-riscv - OpenOCD- Port für die Arbeit an JTAG mit RISC-V-Kerneln (mindestens RocketChip)
- IntelliJ IDEA Community zum Bearbeiten von Scala-Code, der in RocketChip die Mehrheit darstellt
- Die vormontierte Toolchain von SiFive kann ebenfalls nützlich sein, ein Link , den ich im bereits erwähnten Artikel gesehen habe
Was wird aus Eisen benötigt:
- Zeowaa-Kit von Aliexpress: Board mit Cyclone 4 EP4CE115 und (Klon?) USB Blaster
- RaspberryPi als JTAG-Debugger für Softcore
Es wird angenommen, dass der Host-Computer ein relativ leistungsfähiger Computer ist, auf dem Ubuntu und Quartus Lite 18 installiert sind.
Eigentlich gibt es eine Option, um in der Amazon Cloud auf ihren FPGA-Instanzen von demselben SiFive namens FireSim zu starten , aber es ist nicht so interessant, Ja, und die LEDs sind schlecht sichtbar . Außerdem müssen Sie in diesem Fall Ihren API-Schlüssel auf der Steuerinstanz angeben, um andere virtuelle Maschinen zu starten, und Sie müssen sich sehr darum kümmern , da Sie sonst Gerüchten zufolge eines Tages mit einer Verschuldung von Zehntausenden von Dollar aufwachen können ...
Wir untersuchen die Situation
Zu Beginn habe ich das read_write_1G
vom Board-Anbieter genommen und versucht, die erforderlichen Quellen hinzuzufügen. Warum nicht ein neues erstellen? Weil ich neu bin und in diesem Projekt die Namen der Pins bereits zugeordnet wurden. Sie müssen also den Quellcode von irgendwoher nehmen. Dazu benötigen wir das bereits angegebene freedom
Repository (nicht zu verwechseln mit freedom-e-sdk
). Um zumindest etwas zu bekommen, werden wir rocket-tools
gemäß den Anweisungen sammeln (buchstäblich zwei Skripte starten und viel warten) und dann ausführen
RISCV=$(pwd)/../rocket-tools make -f Makefile.e300artydevkit verilog mcs
Das Ziel- verilog
generiert eine riesige Verilog-Datei mit Prozessorquellen für uns, und mcs kompiliert BootROM. Sie müssen sich keine Sorgen machen, dass mcs
ausfällt. Wir haben nur kein Xilinx Vivado, sodass das kompilierte BootROM nicht in das von Vivado benötigte Format konvertiert werden kann.
Über den Menüpunkt Quartus Project -> Dateien in Project hinzufügen / entfernen ... freedom/builds/e300artydevkit/sifive.freedom.everywhere.e300artydevkit.E300ArtyDevKitConfig.v
hinzufügen freedom/builds/e300artydevkit/sifive.freedom.everywhere.e300artydevkit.E300ArtyDevKitConfig.v
, setzen Sie die Entität der obersten Ebene: E300ArtyDevKitFPGAChip auf der Registerkarte Allgemein und starten Sie die Kompilierung (möglicherweise) wird die Liste der automatischen Vervollständigung von Entitäten der obersten Ebene erst nach der ersten Kompilierung angezeigt. Infolgedessen erhalten wir AsyncResetReg
Fehler, die uns über das Fehlen von AsyncResetReg
, IOBUF
Modulen usw. IOBUF
. Wenn keine Fehler vorliegen, haben Sie vergessen, die Entität der obersten Ebene zu ändern. Wenn Sie die Quelle AsyncResetReg.v
, können Sie die Datei IOBUF
direkt finden, aber IOBUF
ist eine Bindung an den IP-Core von Xilinx . freedom/rocket-chip/src/main/resources/vsrc/AsyncResetReg.v
. plusarg_reader.v
wird plusarg_reader.v
hinzugefügt.
Führen Sie die Kompilierung aus und erhalten Sie einen weiteren Fehler:
Error (10174): Verilog HDL Unsupported Feature error at plusarg_reader.v(18): system function "$value$plusargs" is not supported for synthesis
Im Prinzip könnten nicht synthetisierte Konstrukte von einer Datei mit einem ähnlichen Namen erwartet werden.
plusarg_reader.v // See LICENSE.SiFive for license details. //VCS coverage exclude_file // No default parameter values are intended, nor does IEEE 1800-2012 require them (clause A.2.4 param_assignment), // but Incisive demands them. These default values should never be used. module plusarg_reader #(parameter FORMAT="borked=%d", DEFAULT=0) ( output [31:0] out ); `ifdef SYNTHESIS assign out = DEFAULT; `else reg [31:0] myplus; assign out = myplus; initial begin if (!$value$plusargs(FORMAT, myplus)) myplus = DEFAULT; end `endif endmodule
Wie wir sehen können, liest dieses Modul wahrscheinlich die Simulationsoptionen von der Befehlszeile und gibt bei der Synthese einfach den Standardwert zurück. Das Problem ist, dass unser Projekt keine Definition mit dem Namen SYNTHESIS
definiert. Es wäre möglich, `define SYNTHESIS
in die vorherige Zeile direkt vor dem ifdef
und dann eine halbe Woche lang zu versuchen zu verstehen, warum der Kernel nicht startet (und schließlich wird die Infektion synthetisiert ...). Wiederholen Sie meine Fehler nicht, sondern öffnen Sie einfach die Projekteigenschaften erneut und definieren Sie auf der Registerkarte Compiler-Einstellungen-> Verilog HDL-Eingabe das Makro SYNTHESIS
und mindestens 1, nicht in <NONE>
(leere Zeile).
Hier! Jetzt schwört Quartus auf fehlende Bindungen - es ist Zeit, ein Projekt in Idea einzurichten und mit der Portierung zu beginnen.
Das Projekt kennenlernen
Wir teilen die Idee des Importprojekts mit, geben den Pfad zum freedom
Repository an, geben den Typ des sbt-Projekts an, aktivieren die Kontrollkästchen sbt-Shell für Importe verwenden und Builds. Hier endet das Märchen anscheinend, aber es findet nicht die Idee eines Halbprojekts - alle Quellcodes sind rot markiert. Basierend auf den Informationen von hier habe ich das folgende Verfahren erhalten:
- Öffnen Sie das Fenster der sbt-Shell
clean
eingeben- Drücken Sie die grüne Neustart-Taste links
- Geben Sie nach dem Neustart von sbt den ersten Befehl
++2.12.4
und wechseln Sie dabei alle Teilprojekte zu Scala Version 2.12.4. Anschließend führen Sie compile
- Klicken Sie auf Alle sbt-Projekte aktualisieren
- PROFIT !, Jetzt funktioniert die Hintergrundbeleuchtung richtig
Bisher werde ich versuchen, das Projekt irgendwie zusammenzusetzen, zumindest im halbmanuellen Modus. Übrigens, jemand, sagen Sie quartus_ipcreate
, ist quartus_ipcreate
in der Lite Edition? Wir werden IP-Variationen vorerst manuell erstellen und die Bindungen werden nur in Form einer BlackBox auf Scala sein.
In diesem Fall interessiert uns folgende Verzeichnishierarchie:
fpga-shells () | +-src/main/scala | +- ip/intel <-- IP Variations | | | +- Intel.scala | +- shell/intel <-- | +- ZeowaaShell.scala src/main/scala | +- everywhere.e300artydevkit <-- "" , | +- zeowaa/e115 <-- "" SoC | +- Config +- FPGAChip +- Platform +- System
Sie müssen auch ein Makefile hinzufügen, das Makefile.e300artydevkit ähnelt.
Makefile.zeowaa-e115:
# See LICENSE for license details. base_dir := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))) BUILD_DIR := $(base_dir)/builds/zeowaa-e115 FPGA_DIR := $(base_dir)/fpga-shells/intel MODEL := FPGAChip PROJECT := sifive.freedom.zeowaa.e115 export CONFIG_PROJECT := sifive.freedom.zeowaa.e115 export CONFIG := ZeowaaConfig export BOARD := zeowaa export BOOTROM_DIR := $(base_dir)/bootrom/xip rocketchip_dir := $(base_dir)/rocket-chip sifiveblocks_dir := $(base_dir)/sifive-blocks include common.mk
Bindungen
Zunächst implementieren wir diesen IOBUF
- es wird kaum schwierig. Nach dem Scala-Code zu urteilen, ist dies ein Modul, das das physische „Bein“ (Ball?) Der Mikroschaltung steuert: Es kann eingeschaltet, eingeschaltet oder ganz ausgeschaltet werden. Im rechten Teil des Quartus-Fensters geben wir "IOBUF" in den IP-Katalog ein und erhalten sofort eine Komponente mit dem Namen ALTIOBUF
. Lassen Sie uns einen Namen für die Variationsdatei festlegen und "Als bidirektionaler Puffer" auswählen. Danach erscheint in unserem Projekt ein Modul namens iobuf
:
// ... module obuf ( datain, oe, dataout); input [0:0] datain; input [0:0] oe; output [0:0] dataout; wire [0:0] sub_wire0; wire [0:0] dataout = sub_wire0[0:0]; obuf_iobuf_out_d5t obuf_iobuf_out_d5t_component ( .datain (datain), .oe (oe), .dataout (sub_wire0)); endmodule // ...
Schreiben wir ein Blackbox-Modul dafür:
package ip.intel import chisel3._ import chisel3.core.{Analog, BlackBox} import freechips.rocketchip.jtag.Tristate class IOBUF extends BlackBox { val io = IO(new Bundle { val datain = Input(Bool()) val dataout = Output(Bool()) val oe = Input(Bool()) val dataio = Analog(1.W) }) override def desiredName: String = "iobuf" } object IOBUF { def apply(a: Analog, t: Tristate): IOBUF = { val res = Module(new IOBUF) res.io.datain := t.data res.io.oe := t.driven a <> res.io.dataio res } }
Wir beschreiben den Verilogue- inout
Typ Analog
, und mit der desiredName
Methode können desiredName
den Klassennamen des Moduls ändern. Dies ist besonders wichtig, da wir Bindemittel generieren, keine Implementierungen.
Wir brauchen auch BootROM - dafür erstellen wir eine Variation des ROM: 1-PORT (2048 x 32-Bit-Wörter, nur Adressregister, erstellen Sie den rden
Port). Wir erstellen einen Block mit dem Namen rom
, weil wir dann einen Adapter für die Schnittstelle schreiben müssen, die die ROMGenerator
Klasse ROMGenerator
: ROMGenerator
anstelle von me
und oe
fehlt bei uns (es ist immer noch an 1 angehängt):
BootROM.v:
module BootROM( input wire [10:0] address, input wire clock, input wire me, input wire oe, output wire [31:0] q ); rom r( .address(address), .clock(clock), .rden(me), .q(q) ); endmodule
Ein weiteres Problem wird sofort entdeckt: Die vom Kollektor generierten Hex-Dateien erweisen sich aus irgendeinem Grund als nicht kompatibel mit Quartus. Nach einem kurzen Googeln zum Thema Intel HEX-Dateien (Intel war, wie ich es verstehe, lange vor dem Kauf dieses Intel Altera) kommen wir zu einem solchen Befehl, der Binärdateien in HEX konvertiert:
srec_cat -Output builds/zeowaa-e115/xip.hex -Intel builds/zeowaa-e115/xip.bin -Binary -Output_Block_Size 128
Daher wird unser Makefile ein wenig verändert:
Versteckter Text base_dir := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))) BUILD_DIR := $(base_dir)/builds/zeowaa-e115 FPGA_DIR := $(base_dir)/fpga-shells/intel MODEL := FPGAChip PROJECT := sifive.freedom.zeowaa.e115 export CONFIG_PROJECT := sifive.freedom.zeowaa.e115 export CONFIG := ZeowaaConfig export BOARD := zeowaa export BOOTROM_DIR := $(base_dir)/bootrom/xip rocketchip_dir := $(base_dir)/rocket-chip sifiveblocks_dir := $(base_dir)/sifive-blocks all: verilog $(MAKE) -C $(BOOTROM_DIR) clean romgen || true srec_cat -Output $(BUILD_DIR)/xip.hex -Intel $(BUILD_DIR)/xip.bin -Binary -Output_Block_Size 128 include common.mk
Es bewegt sich ^ W wird synthetisiert
Das Projekt als Ganzes ist also synthetisiert, jetzt ist das Debuggen das Interessanteste. Die Katze darf zuerst ins Haus und vielleicht die JTAG in den Mikrocontroller. Lassen Sie uns ein fast minimales System zum Debuggen erstellen: BootROM zum Booten, GPIO zum Blinken der LEDs und JTAG, um zu verstehen, warum nichts geladen wird und nicht blinkt. Erstellen Sie in Analogie zu E300ArtyDevKit
ein Paket mit vier Dateien. Erstens,
Config.scala:
class DefaultZeowaaConfig extends Config ( new WithNBreakpoints(2) ++ new WithNExtTopInterrupts(0) ++ new WithJtagDTM ++ new TinyConfig ) class Peripherals extends Config((site, here, up) => { case PeripheryGPIOKey => List( GPIOParams(address = BigInt(0x64002000L), width = 6) ) case PeripheryMaskROMKey => List( MaskROMParams(address = 0x10000, name = "BootROM")) }) class ZeowaaConfig extends Config( new Peripherals ++ new DefaultZeowaaConfig().alter((site, here, up) => { case JtagDTMKey => new JtagDTMConfig ( idcodeVersion = 2, idcodePartNum = 0xe31, idcodeManufId = 0x489, debugIdleCycles = 5) }) )
Die Beschreibung wurde größtenteils vom E300 kopiert und abgeschnitten: Wir fragen, woraus unser Kernel besteht und wo er im Adressraum liegen wird. Bitte beachten Sie, dass wir zwar keinen RAM haben (dies ist die Standardeinstellung in TinyConfig
), aber einen Adressraum von 32 Bit haben!
Es gibt auch eine Datei, die eine bestimmte Menge Boilerplate enthält.
System.scala:
class System(implicit p: Parameters) extends RocketSubsystem with HasPeripheryMaskROMSlave with HasPeripheryDebug with HasPeripheryGPIO { override lazy val module = new SystemModule(this) } class SystemModule[+L <: System](_outer: L) extends RocketSubsystemModuleImp(_outer) with HasPeripheryDebugModuleImp with HasPeripheryGPIOModuleImp {
Tatsächlich besteht das Layout unseres "Motherboards" aus drei (bisher) einfachen Dateien:
Platform.scala:
class PlatformIO(implicit val p: Parameters) extends Bundle { val jtag = Flipped(new JTAGIO(hasTRSTn = false)) val jtag_reset = Input(Bool()) val gpio = new GPIOPins(() => new BasePin(), p(PeripheryGPIOKey)(0)) } class Platform(implicit p: Parameters) extends Module { val sys = Module(LazyModule(new System).module) override val io = IO(new PlatformIO) val sjtag = sys.debug.systemjtag.get sjtag.reset := io.jtag_reset sjtag.mfr_id := p(JtagDTMKey).idcodeManufId.U(11.W) sjtag.jtag <> io.jtag io.gpio <> sys.gpio.head }
FPGAChip.scala:
class FPGAChip(override implicit val p: Parameters) extends ZeowaaShell { withClockAndReset(cpu_clock, cpu_rst) { val dut = Module(new Platform) dut.io.jtag.TCK := jtag_tck dut.io.jtag.TDI := jtag_tdi IOBUF(jtag_tdo, dut.io.jtag.TDO) dut.io.jtag.TMS := jtag_tms dut.io.jtag_reset := jtag_rst Seq(led_0, led_1, led_2, led_3) zip dut.io.gpio.pins foreach { case (led, pin) => led := Mux(pin.o.oe, pin.o.oval, false.B) } dut.io.gpio.pins.foreach(_.i.ival := false.B) dut.io.gpio.pins(4).i.ival := key1 dut.io.gpio.pins(5).i.ival := key2 } }
Wie Sie sehen können, können Sie Generatoren in Chisel in einem funktionalen Stil schreiben (ein sehr einfacher Fall wird hier gezeigt). Aber Sie können und klar jeden Draht schreiben:
ZeowaaShell.scala object ZeowaaShell { class MemIf extends Bundle { val mem_addr = IO(Analog(14.W)) val mem_ba = IO(Analog(3.W)) val mem_cas_n = IO(Analog(1.W)) val mem_cke = IO(Analog(2.W)) val mem_clk = IO(Analog(2.W)) val mem_clk_n = IO(Analog(2.W)) val mem_cs_n = IO(Analog(2.W)) val mem_dm = IO(Analog(8.W)) val mem_dq = IO(Analog(64.W)) val mem_dqs = IO(Analog(8.W)) val mem_odt = IO(Analog(2.W)) val mem_ras_n = IO(Analog(1.W)) val mem_we_n = IO(Analog(1.W)) } } class ZeowaaShell(implicit val p: Parameters) extends RawModule { val clk25 = IO(Input(Clock())) val clk27 = IO(Input(Clock())) val clk48 = IO(Input(Clock())) val key1 = IO(Input(Bool())) val key2 = IO(Input(Bool())) val key3 = IO(Input(Bool())) val led_0 = IO(Output(Bool())) val led_1 = IO(Output(Bool())) val led_2 = IO(Output(Bool())) val led_3 = IO(Output(Bool())) val jtag_tdi = IO(Input(Bool())) val jtag_tdo = IO(Analog(1.W)) val jtag_tck = IO(Input(Clock())) val jtag_tms = IO(Input(Bool())) val uart_rx = IO(Input(Bool())) val uart_tx = IO(Analog(1.W))
Warum ist es in drei Dateien aufgeteilt? Nun, erstens war es im Prototyp so :) Die Logik der Trennung von Shell
und FPGAChip
bestand anscheinend darin, dass Shell eine Beschreibung der Schnittstelle zur Außenwelt ist: Welche Schlussfolgerungen haben wir auf einem bestimmten Board (und wie werden sie angezeigt FPGAChip
Chip Schlussfolgerungen!), und FPGAChip
wird durch die Tatsache diktiert, dass wir in einen bestimmten SoC FPGAChip
wollen. Nun, die Plattform ist ganz logisch getrennt: Hinweis: ZeowaaShell
(und daher Platform
) ist ein RawModule
, insbesondere haben sie keine implizite clock
und kein reset
- dies ist natürlich für das „Verdrahten der Platine“, aber für die Arbeit unpraktisch (und wahrscheinlich für die Arbeit) voller kniffliger Fehler mit produktiven Frequenzbereichen). Nun, Platform ist bereits ein gewöhnliches Meißelmodul, in dem Sie Register usw. sicher beschreiben können.
Jtag
Ein paar Worte zur Konfiguration von JTAG. Da ich bereits das RaspberryPi 3 Model B + hatte, bestand die offensichtliche Lösung darin, irgendwie zu versuchen, sein GPIO zu verwenden. Zum Glück ist bereits alles vor uns implementiert : In der neuen OpenOCD gibt es eine Beschreibung der interface/sysfsgpio-raspberrypi.cfg
, mit der Sie dem Debugger mitteilen können, dass er eine Verbindung über den Block herstellen soll (TCK = 11, TMS = 25, TDI = 10, TDO = 9 und GND als Übung verlassen) - Pinbelegung hier .
Ausgehend von riscv-tests
aus dem Repository von riscv-tests
erhielt ich Folgendes:
adapter_khz 10000 source [find interface/sysfsgpio-raspberrypi.cfg] set _CHIPNAME riscv jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x20e31913 set _TARGETNAME $_CHIPNAME.cpu target create $_TARGETNAME riscv -chain-position $_TARGETNAME -rtos riscv init halt echo "Ready for Remote Connections"
Um zu arbeiten, benötigen Sie daher den unter ARM erstellten riscv-openocd- Port Das Werbegeschenk ging nicht vorbei Anstelle der vormontierten Version von SiFive müssen Sie das Repository klonen und Folgendes sammeln:
./configure --enable-remote-bitbang --enable-sysfsgpio
Wenn jemand weiß, wie man Remote-Bitbang ausführt, müssen Sie möglicherweise keinen benutzerdefinierten Port für ARM erstellen ...
Infolgedessen starten wir von der Wurzel auf Malinka
root@ubuntu:~/riscv-openocd# ./src/openocd -s tcl -f ../riscv.tcl Open On-Chip Debugger 0.10.0+dev-00614-g998fed1fe-dirty (2019-06-03-10:27) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html adapter speed: 10000 kHz SysfsGPIO nums: tck = 11, tms = 25, tdi = 10, tdo = 9 SysfsGPIO nums: swclk = 11, swdio = 25 Info : auto-selecting first available session transport "jtag". To override use 'transport select <transport>'. Info : SysfsGPIO JTAG/SWD bitbang driver Info : JTAG and SWD modes enabled Warn : gpio 11 is already exported Warn : gpio 25 is already exported Info : This adapter doesn't support configurable speed Info : JTAG tap: riscv.cpu tap/device found: 0x20e31913 (mfg: 0x489 (SiFive, Inc.), part: 0x0e31, ver: 0x2) Info : datacount=1 progbufsize=16 Info : Disabling abstract command reads from CSRs. Info : Examined RISC-V core; found 1 harts Info : hart 0: XLEN=32, misa=0x40001105 Info : Listening on port 3333 for gdb connections Ready for Remote Connections Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections
Nachdem Sie sich dort auf SSH mit Portweiterleitung 3333 angemeldet haben, ersetzen Sie die gewünschte IP:
ssh -L 3333:127.0.0.1:3333 ubuntu@192.168.1.104
Jetzt können Sie auf dem Host GDB unter der riscv32-Architektur ausführen :
$ ../../rocket-tools/bin/riscv32-unknown-elf-gdb -q (gdb) target remote :3333 Remote debugging using :3333 warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x00000000 in ?? ()
Wir werden das blinde Debuggen des nicht klebenden JTAG überspringen, da das SYNTHESIS
Makro in der generierten SYNTHESIS
aktiv verwendet wird, und die Situation auf den Status "JTAG ist angeschlossen, aber die Lichter blinken nicht" zurückspulen.
Erster Code
Wie wir im Makefile gesehen haben, stammt der Code für das bootrom/xip/xip.S
Jetzt sieht es so aus:
// See LICENSE for license details. // Execute in place // Jump directly to XIP_TARGET_ADDR .section .text.init .option norvc .globl _start _start: csrr a0, mhartid la a1, dtb li t0, XIP_TARGET_ADDR jr t0 .section .rodata dtb: .incbin DEVICE_TREE
Ich verstehe, dass es einen Gerätebaum (den Inhalt der dtb-Datei) geben muss, um vom Betriebssystem gelesen zu werden, aber welche Art von Betriebssystem gibt es ohne RAM. Ersetzen Sie es daher mutig vorübergehend durch eine blinkende Glühbirne:
Versteckter Text .section .text.init .option norvc .globl _start _start: li a5, 0x64002000 li a1, 0x0F li a2, 0x01 li a3, 0x30 li a6, 0x10 li a7, 0x20 sw zero, 0x38(a5) // iof_en sw a1, 0x08(a5) // output_en sw a2, 0x00(a5) // value sw a1, 0x14(a5) // drive sw a3, 0x04(a5) // input_en // a0 <- timer // a1 <- 0x0F // a2 <- [state] // a3 <- 0x30 // a4 <- [buttons] // a5 <- [gpio addr] // a6 <- 0x10 // a7 <- 0x20 loop: li a4, 0x1000 add a0, a0, a4 bgtu a0, zero, loop lw a4, 0x00(a5) // value beq a4, a6, plus beq a4, a7, minus j store plus: srai a2, a2, 1 beq a2, zero, pzero j store pzero: li a2, 0x08 j store minus: slli a2, a2, 1 beq a2, a6, mzero j store mzero: li a2, 0x01 store: sw a2, 0x0c(a5) // value j loop
Dieser Code wurde iterativ entwickelt. Bitte entschuldigen Sie die seltsame Nummerierung der Register. Außerdem habe ich den RISC-V-Assembler ehrlich mit der Methode „Kompilieren eines Codeteils in eine Objektdatei, Zerlegen, siehe“ studiert. Als ich vor einigen Jahren ein Elektronikbuch las, ging es darum, ATTiny in Assemblersprache zu programmieren. "Hier sind wahrscheinlich die langweiligen Dinge und die Routine", dachte ich, aber jetzt ist anscheinend die Wirkung eines schwedischen Geschäfts aufgetreten : Schließfach Prozessor aus Ersatzteilen zusammengebaut, scheint sogar der Assembler native und interessant zu sein. Infolge der Ausführung dieses Codes sollte die leuchtende LED je nach gedrückter Taste nach links oder rechts „laufen“.
Wir fangen an ... Und nichts: Alle Lichter sind an, sie reagieren nicht auf die Tasten. Verbinden wir uns über JTAG: Programmzähler = 0x00000000 - irgendwie ist alles traurig. Bei 0x64002000
stehen jedoch GPIO-Register zur Verfügung:
Versuchen wir, sie manuell zu verschieben:
(gdb) set variable *0x64002038=0 (gdb) set variable *0x64002008=0xF (gdb) set variable *0x64002000=0x1 (gdb) set variable *0x64002014=0xF (gdb) set variable *0x6400200c=0x1
Also ... Eine der LEDs ging aus ... Und wenn nicht 0x1
, sondern 0x5
... 0x5
, jetzt leuchten die LEDs durch eine. Es wurde auch klar, dass sie invertiert werden müssen, aber Sie müssen nicht in das 0x00-Register schreiben - Sie müssen von dort lesen.
(gdb) x/x 0x64002000 0x64002000: 0x00000030 // (gdb) x/x 0x64002000 0x64002000: 0x00000020 // (gdb) x/x 0x64002000 0x64002000: 0x00000010 // (gdb) x/x 0x64002000 0x64002000: 0x00000000
Tolle, speicherabgebildete Register werden aktualisiert, ohne den Prozessor zu starten. Sie können nicht jedes Mal cont + Ctrl-C drücken - eine Kleinigkeit, aber nett.
Aber warum drehen wir uns nicht in einer Schleife, sondern bei $pc=0x0000000
?
(gdb) x/10i 0x10000 0x10000: addi s1,sp,12 0x10002: fsd ft0,-242(ra) 0x10006: srli a4,a4,0x21 0x10008: addi s0,sp,32 0x1000a: slli t1,t1,0x21 0x1000c: lb zero,-1744(a2) 0x10010: nop 0x10012: addi a0,sp,416 0x10014: c.slli zero,0x0 0x10016: 0x9308
WAS IST DAS Pokemon ??? Ich habe solche Anweisungen nicht geschrieben, sie haben mich geworfen! Schauen wir uns das genauer an:
(gdb) x/10x 0x10000 0x10000: 0xb7270064 0x9305f000 0x13061000 0x93060003 0x10010: 0x13080001 0x93080002 0x23ac0702 0x23a4b700 0x10020: 0x23a0c700 0x23aab700
Auf der anderen Seite, was sollte da sein?
$ ../../rocket-tools/bin/riscv32-unknown-elf-objdump -d builds/zeowaa-e115/xip.elf builds/zeowaa-e115/xip.elf: elf32-littleriscv .text: 00010054 <_start>: 10054: 640027b7 lui a5,0x64002 10058: 00f00593 li a1,15 1005c: 00100613 li a2,1 10060: 03000693 li a3,48 10064: 01000813 li a6,16 10068: 02000893 li a7,32 1006c: 0207ac23 sw zero,56(a5) # 64002038 <__global_pointer$+0x63ff0770> 10070: 00b7a423 sw a1,8(a5) 10074: 00c7a023 sw a2,0(a5) 10078: 00b7aa23 sw a1,20(a5) 1007c: 00d7a223 sw a3,4(a5) ...
Wie Sie sehen können, hat Quartus ehrlich gesagt dieselben Wörter in die Initialisierungsdatei eingefügt, aber deren Endianness geändert. Sie können lange googeln, wie Sie das Problem lösen können, aber ich bin Programmierer . Krücken gehören uns, daher schreibe ich sie einfach neu
BootROM.v module BootROM( input wire [10:0] address, input wire clock, input wire me, input wire oe, output wire [31:0] q ); wire [31:0] q_r; rom r( .address(address), .clock(clock), .rden(me), .q(q_r) ); assign q[31:24] = q_r[7:0]; assign q[23:16] = q_r[15:8]; assign q[15:8] = q_r[23:16]; assign q[7:0] = q_r[31:24]; endmodule
Also sammeln wir, wir fangen an, es leuchtet nicht. JTAG: $pc
- , = 4, , output_en
: set variable *0x64002008=0xF
, c
(continue) — ! , . -, , … , output_en
.
:
Total logic elements 17,370 / 114,480 ( 15 % ) Total registers 8357 Total pins 16 / 281 ( 6 % ) Total virtual pins 0 Total memory bits 264,000 / 3,981,312 ( 7 % ) Embedded Multiplier 9-bit elements 4 / 532 ( < 1 % )
Chisel
Fortsetzung folgt...