
Récemment, un article a été publié sur Habr sur la façon d'expérimenter avec l'architecture RISC-V sans le coût du matériel. Mais que faire si vous faites cela sur le tableau de débogage? Rappelez-vous des mèmes sur le générateur de jeu: environ 20 cases à cocher dans le style "Les graphiques ne sont pas pires que la crise", "Vous pouvez voler des corovans" et le bouton "Générer". Le générateur RocketChip SoC est organisé à peu près de la même manière, seulement il n'y a pas de fenêtre avec des coches, mais un code Scala et un peu d'assembleur et de fichiers Make. Dans cet article, je vais montrer à quel point il est facile de porter ce RocketChip de son Xilinx natif vers Altera / Intel.
AVERTISSEMENT: l' auteur n'est pas responsable de la carte «brûlée» - regardez attentivement comment vous configurez les broches, ce que vous connectez physiquement, etc. Eh bien, respectez également les consignes de sécurité. Vous ne devriez pas penser que puisque tout est connecté via USB, c'est définitivement sans danger pour une personne: comme je l'ai compris dans mes expériences précédentes, même si vous travaillez avec une carte USB, vous n'avez toujours pas à toucher la batterie chauffante avec votre pied, car la différence de potentiel ... Oh oui , Je ne suis pas un ingénieur en plisovode ou en électronique - je suis juste un programmeur Scala.
Pour autant que je sache, la plate-forme initiale de débogage de RocketChip était le FPGA Xilinx. A en juger par le référentiel, que nous allons bientôt cloner, il a également été porté sur Microsemi. Quelque part, j'ai entendu parler de l'utilisation d'Altera, mais je n'ai pas vu la source. Il s'est avéré que ce n'était pas un gros problème: à partir du moment où nous avons reçu la carte et commencé à étudier le référentiel SiFive Freedom vers un système «sans mémoire» (c'est-à-dire n'ayant que des registres de processeur, BootROM et des registres mappés en mémoire) «microcontrôleur» 32 bits (bien que ce soit déjà ce que quelque chose que le nanocontrôleur s'avère ...) 3 jours de repos et 4 soirs de semaine se sont écoulés, et il aurait fallu encore moins s'il m'était venu immédiatement de définir définir SYNTHESIS
globalement.
Matériaux
Pour commencer - une liste de matériaux au sens large du terme. Nous avons besoin du logiciel suivant:
- RocketChip - contient le processeur lui-même et l'environnement d'assemblage (y compris, décrit plusieurs sous-référentiels). Capable de créer RocketChip avec le processeur dans l'ordre du noyau Rocket.
- SiFive Freedom - obligatoire pour diverses cartes de débogage - nous y ajouterons la prise en charge de notre
- rocket-tools - outils de construction de code et de débogage pour les architectures RISC-V 32 et 64 bits
- openocd-riscv - Port OpenOCD pour travailler sur JTAG avec les noyaux RISC-V (au moins RocketChip)
- IntelliJ IDEA Community pour l'édition du code Scala, qui est la majorité dans RocketChip
- La chaîne d'outils pré-assemblée de SiFive peut également être utile, un lien vers lequel j'ai vu dans l' article déjà mentionné
Ce dont le fer a besoin:
- Kit Zeowaa d'Aliexpress: carte avec Cyclone 4 EP4CE115 et (clone?) USB Blaster
- RaspberryPi comme débogueur JTAG pour soft core
La machine hôte est supposée être un ordinateur relativement puissant avec Ubuntu et Quartus Lite 18 installés.
En fait, il y a une option pour lancer dans le cloud Amazon sur leurs instances FPGA à partir du même SiFive, appelé FireSim , mais ce n'est pas si intéressant, Oui, et les LED sont mal visibles . De plus, dans ce cas, vous devrez spécifier votre clé API sur l'instance de contrôle pour démarrer d'autres machines virtuelles, et vous devez en prendre grand soin , sinon, selon les rumeurs, vous pouvez vous réveiller un jour avec une dette de dizaines de milliers de dollars ...
Nous étudions la situation
Pour commencer, j'ai pris le projet de test read_write_1G
du fournisseur de la carte et j'ai essayé d'y ajouter les sources requises. Pourquoi ne pas en créer un nouveau? Parce que je suis nouveau, et dans ce projet, les noms des broches ont déjà été mappés. Donc, vous devez prendre le code source quelque part. Pour ce faire, nous aurons besoin du référentiel de freedom
déjà spécifié (à ne pas confondre avec freedom-e-sdk
). Pour obtenir au moins quelque chose, nous allons collecter rocket-tools
selon les instructions (lancer littéralement deux scripts et beaucoup d'attente), puis exécuter
RISCV=$(pwd)/../rocket-tools make -f Makefile.e300artydevkit verilog mcs
Le verilog
cible verilog
un énorme fichier Verilog avec des sources de processeur, et mcs
compilera BootROM. Pas besoin de s'inquiéter de l'échec de mcs
- nous n'avons tout simplement pas Xilinx Vivado, donc la BootROM compilée ne peut pas être convertie au format dont Vivado a besoin.
Via l'élément de menu Projet Quartus -> Ajouter / Supprimer des fichiers dans le projet ... ajoutez freedom/builds/e300artydevkit/sifive.freedom.everywhere.e300artydevkit.E300ArtyDevKitConfig.v
, définissez l'entité de niveau supérieur: E300ArtyDevKitFPGAChip sur l'onglet Général et lancez la compilation (éventuellement , la liste d'auto-complétion d'entité de niveau supérieur n'apparaîtra qu'après la première compilation). En conséquence, nous obtenons des tonnes d'erreurs nous informant de l'absence de modules AsyncResetReg
, IOBUF
, etc. S'il n'y a aucune erreur, vous avez oublié de modifier l'entité de niveau supérieur. Si vous fouillez la source, vous pouvez trouver directement le fichier AsyncResetReg.v
, mais IOBUF
est une liaison avec le noyau IP de Xilinx . Tout d'abord, ajoutez freedom/rocket-chip/src/main/resources/vsrc/AsyncResetReg.v
. Et plusarg_reader.v
sera également ajouté.
Exécutez la compilation et obtenez une autre erreur:
Error (10174): Verilog HDL Unsupported Feature error at plusarg_reader.v(18): system function "$value$plusargs" is not supported for synthesis
En principe, des constructions non synthétisées peuvent être attendues d'un fichier avec un nom similaire.
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
Comme nous pouvons le voir, ce module lit probablement les options de simulation à partir de la ligne de commande, et lors de sa synthèse, il renvoie simplement la valeur par défaut. Le problème est que notre projet ne définit pas de définition nommée SYNTHESIS
. Il serait possible d' ifdef
`define SYNTHESIS
sur la ligne précédente juste avant l' ifdef
, puis passer une demi-semaine à essayer de comprendre pourquoi le noyau ne démarre pas (et après tout, l'infection est synthétisée ...). Ne répétez pas mes erreurs, mais ouvrez simplement à nouveau les propriétés du projet et dans l'onglet Paramètres du compilateur-> Verilog HDL Input , définissez la macro SYNTHESIS
, et au moins 1, pas dans <NONE>
(ligne vide).
Ici! Quartus jure désormais des liaisons manquantes - il est temps de configurer un projet dans Idea et de commencer le portage.
Connaître le projet
Nous indiquons l'idée du projet d'importation, spécifions le chemin d'accès au référentiel de freedom
, indiquons le type de projet sbt, vérifions l'utilisation du shell sbt pour les importations, pour les cases à cocher des builds. Ici, le conte de fées se termine, semble-t-il, mais il ne trouve pas l'idée d'un demi-projet - tous les codes source sont marqués en rouge. Sur la base des informations d'ici , j'ai obtenu la procédure suivante:
- ouvrir la fenêtre du shell sbt
- entrer
clean
- appuyez sur le bouton de redémarrage vert à gauche
- après avoir redémarré sbt, entrez la première commande
++2.12.4
, basculant ainsi tous les sous-projets vers Scala version 2.12.4, puis nous exécuterons la compile
- cliquez sur Actualiser tous les projets sbt Idée
- PROFIT!, Maintenant le rétroéclairage fonctionne correctement
Jusqu'à présent, je vais essayer de monter le projet en quelque sorte, au moins en mode semi-manuel. Soit dit en passant, quelqu'un, dites- quartus_ipcreate
, est-ce que quartus_ipcreate
est dans Lite Edition? Nous allons créer des variantes IP manuellement pour l'instant, et les liaisons ne seront sur Scala que sous la forme d'une BlackBox.
Dans ce cas, nous nous intéressons à la hiérarchie de répertoires suivante:
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
Vous devez également ajouter un Makefile similaire à Makefile.e300artydevkit
, quelque chose comme ceci:
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
Liaisons
Pour commencer, nous mettons en œuvre cet IOBUF
- ce ne sera guère difficile. A en juger par le code Scala, il s'agit d'un module qui contrôle la «jambe» physique (balle?) Du microcircuit: il peut être allumé, peut être allumé ou peut être complètement désactivé. Dans la partie droite de la fenêtre Quartus, nous entrerons «IOBUF» dans le catalogue IP et obtiendrons immédiatement un composant nommé ALTIOBUF
. Définissons un nom pour le fichier de variantes, sélectionnez «En tant que tampon bidirectionnel». Après cela, un module appelé iobuf
apparaîtra dans notre projet:
// ... 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 // ...
Écrivons un module blackbox pour cela:
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 } }
Nous décrivons le Verilogue inout
type Analog
, et la méthode desiredName
permet de changer le nom de classe du module. Ceci est particulièrement important car nous générons des classeurs, pas des implémentations.
Nous avons également besoin de BootROM - pour cela, nous créons une variante de la ROM: 1-PORT (mots 2048 x 32 bits, seul registre d'adresse, créez le port rden
). Nous créons un bloc avec le nom rom
, car alors nous devrons écrire un adaptateur pour l'interface que la classe ROMGenerator
: ROMGenerator
au lieu de me
et oe
manquant de nous (il est toujours attaché à 1):
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
Un autre problème est immédiatement découvert: les fichiers hex générés par le collecteur, pour une raison quelconque, s'avèrent incompatibles avec Quartus. Après une légère recherche sur le sujet des fichiers Intel HEX (Intel était bien avant l'achat de cet Intel Altera, si je comprends bien), nous arrivons à une telle commande qui convertit les fichiers binaires en HEX:
srec_cat -Output builds/zeowaa-e115/xip.hex -Intel builds/zeowaa-e115/xip.bin -Binary -Output_Block_Size 128
Par conséquent, notre Makefile sera un peu transformé:
Texte masqué 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
Il bouge ^ W est synthétisé
Donc, le projet dans son ensemble est synthétisé, maintenant la chose la plus intéressante est le débogage. Le chat est d'abord autorisé à entrer dans la maison, et peut-être le JTAG dans le microcontrôleur. Créons un système de débogage presque minimal: BootROM pour démarrer, GPIO pour clignoter les voyants et JTAG pour comprendre pourquoi rien ne se charge et ne clignote pas. Par analogie avec E300ArtyDevKit
créez un package contenant quatre fichiers. Tout d'abord
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) }) )
La description est pour la plupart copiée et tronquée à partir de l'E300: nous demandons en quoi consiste notre noyau et où il se trouvera dans l'espace d'adressage. Veuillez noter que bien que nous n'ayons pas de RAM (c'est le TinyConfig
par défaut dans TinyConfig
), il y a en plus un espace d'adressage 32 bits!
Il existe également un fichier contenant une certaine quantité de passe-partout.
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 {
En fait, la disposition de notre "carte mère" est en trois (simples) fichiers simples:
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 } }
Comme vous pouvez le voir, vous pouvez écrire des générateurs dans Chisel dans un style fonctionnel (un cas très simple est illustré ici). Mais vous pouvez écrire et clairement chaque fil:
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))
Pourquoi est-il divisé en trois fichiers? Eh bien, tout d'abord, c'était comme ça dans le prototype :) La logique de séparation de Shell
de FPGAChip
, apparemment, était que Shell est une description de l'interface avec le monde extérieur: quelles conclusions nous avons sur un tableau spécifique (et comment elles seront affichées sur conclusions de la puce!), et FPGAChip
est dicté par le fait que nous voulons entrer dans un SoC spécifique. Eh bien, la plate-forme est séparée de façon assez logique: remarque: ZeowaaShell
(et, par conséquent, la Platform
- Platform
) est un RawModule
, en particulier, ils n'ont pas d' clock
implicite et de reset
- cela est naturel pour «câbler la carte», mais peu pratique pour le travail (et, probablement, chargé d’erreurs délicates avec des domaines fréquentiels prolifiques). Eh bien, Platform est déjà un module Chisel ordinaire, dans lequel vous pouvez décrire en toute sécurité les registres, etc.
Jtag
Quelques mots sur la configuration de JTAG. Comme j'avais déjà le RaspberryPi 3 Model B +, la solution évidente était d'essayer en quelque sorte d'utiliser son GPIO. Heureusement, tout a déjà été implémenté avant nous : dans le nouveau OpenOCD, il y a une description de l' interface/sysfsgpio-raspberrypi.cfg
, avec laquelle vous pouvez dire au débogueur de se connecter via le bloc (TCK = 11, TMS = 25, TDI = 10, TDO = 9, et GND congé comme exercice) - brochage ici .
De plus, en prenant Freedom.cfg comme base à partir du riscv-tests
, j'ai obtenu ce qui suit:
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"
Pour fonctionner, vous aurez besoin du port riscv-openocd construit sous ARM, donc le cadeau n'est pas passé au lieu de la version pré-assemblée de SiFive, vous devez cloner le référentiel et collecter:
./configure --enable-remote-bitbang --enable-sysfsgpio
Si quelqu'un sait comment exécuter un bitbang distant, vous n'aurez peut-être pas besoin de créer un port personnalisé pour ARM ...
En conséquence, nous lançons depuis la racine sur 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
Après vous être connecté sur SSH avec la redirection de port 3333, en remplaçant l'adresse IP souhaitée:
ssh -L 3333:127.0.0.1:3333 ubuntu@192.168.1.104
Maintenant sur l'hôte, vous pouvez exécuter GDB sous l'architecture riscv32 :
$ ../../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 ?? ()
Nous ignorerons un débogage aveugle du JTAG non collant car la macro SYNTHESIS
est activement utilisée dans le fichier généré principal, et rembobinerons la situation à l'état "JTAG est accroché, mais les voyants ne clignotent pas".
Premier code
Comme nous l'avons vu dans le Makefile, le code de bootrom bootrom/xip/xip.S
Maintenant, cela ressemble à ceci:
// 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
Je comprends qu'il doit y avoir une arborescence de périphériques (le contenu du fichier dtb) à lire par le système d'exploitation, mais quel type de système d'exploitation existe-t-il sans RAM. Par conséquent, remplacez-le temporairement avec audace par des ampoules clignotantes:
Texte masqué .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
Ce code a été développé de manière itérative, alors veuillez m'excuser pour l'étrange numérotation des registres. De plus, j'ai honnêtement étudié l'assembleur RISC-V en utilisant la méthode «compiler un morceau de code dans un fichier objet, démonter, voir». Il y a quelques années, lorsque j'ai lu un livre sur l'électronique, il parlait de la programmation d'ATTiny en langage assembleur. "Voici les choses ennuyeuses et la routine, probablement", pensai-je, mais maintenant, apparemment, l' effet d'un magasin suédois est apparu : casier processeur assemblé à partir de pièces de rechange, même l'assembleur semble être natif et intéressant. À la suite de l'exécution de ce code, la LED allumée doit «fonctionner» à gauche ou à droite, selon le bouton enfoncé.
Nous commençons ... Et rien: toutes les lumières sont allumées, elles ne répondent pas aux boutons. Connectons-nous via JTAG: programme counter = 0x00000000 - en quelque sorte, tout est triste. Mais à 0x64002000
nous avons des registres GPIO disponibles:
Essayons de les déplacer manuellement:
(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
Alors ... Une des LED s'est éteinte ... Et sinon 0x1
, mais 0x5
... C'est vrai, maintenant les LED sont allumées à travers une seule. Il est également devenu clair qu'ils doivent être inversés, mais vous n'avez pas besoin d'écrire dans le registre 0x00 - vous devez lire à partir de là.
(gdb) x/x 0x64002000 0x64002000: 0x00000030 // (gdb) x/x 0x64002000 0x64002000: 0x00000020 // (gdb) x/x 0x64002000 0x64002000: 0x00000010 // (gdb) x/x 0x64002000 0x64002000: 0x00000000
Les grands registres mappés en mémoire sont mis à jour sans démarrer le processeur, vous ne pouvez pas appuyer sur cont + Ctrl-C à chaque fois - une bagatelle, mais agréable.
Mais pourquoi ne tournons-nous pas en boucle, mais sommes-nous à $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
QU'EST-CE QUE LE Pokémon ??? Je n'ai pas écrit de telles instructions, ils m'ont jeté! Examinons de plus près:
(gdb) x/10x 0x10000 0x10000: 0xb7270064 0x9305f000 0x13061000 0x93060003 0x10010: 0x13080001 0x93080002 0x23ac0702 0x23a4b700 0x10020: 0x23a0c700 0x23aab700
D'un autre côté, que devrait-il y avoir?
$ ../../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) ...
Comme vous pouvez le voir, Quartus a honnêtement mis les mêmes mots que dans le fichier d'initialisation, mais a changé leur endianité. Vous pouvez chercher sur Google pendant longtemps comment le résoudre, mais je suis programmeur , les béquilles sont à nous, donc je vais juste réécrire
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
Donc, on collectionne, on commence, ça ne brille pas. 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
...