Parte 1: RISC-V / RocketChip em um habitat não natural

Configurando o RocketChip

Recentemente, foi publicado um artigo na Habr sobre como experimentar a arquitetura RISC-V sem o custo de hardware. Mas e se você fizer isso no quadro de depuração? Lembre-se de memes sobre o gerador de jogos: cerca de 20 marcas de seleção no estilo "Os gráficos não são piores que a crise", "Você pode roubar corovans" e o botão "Gerar". O gerador RocketChip SoC é organizado aproximadamente da mesma maneira, apenas não há uma janela com marcas de seleção, mas um código Scala e um pouco de montador e arquivos Make. Neste artigo, mostrarei como é fácil portar esse RocketChip do Xilinx nativo para Altera / Intel.


AVISO LEGAL: o autor não é responsável pela placa “queimada” - observe cuidadosamente como você configura os pinos, o que você fisicamente conecta etc. Bem, observe também as precauções de segurança. Você não deve pensar que, como tudo está conectado via USB, é definitivamente seguro para uma pessoa: como eu entendi nas minhas experiências anteriores, mesmo se você trabalha com uma placa USB, ainda não precisa tocar a bateria de aquecimento com o pé, porque a diferença de potencial ... Ah, sim , Eu não sou quase um plisovode profissional ou engenheiro eletrônico - sou apenas um programador Scala.


Até onde eu entendi, a plataforma inicial para depuração do RocketChip era o FPGA da Xilinx. A julgar pelo repositório, que estamos clonando em breve, ele também foi portado para o Microsemi. Em algum lugar, ouvi falar sobre o uso de Altera, mas não vi a fonte. Como se viu, esse não é um grande problema: desde o momento em que recebemos a placa e começamos a estudar o repositório SiFive Freedom até um “sem memória” (isto é, tendo apenas registradores de processador, BootROM e registradores mapeados na memória) “microcontrolador” de 32 bits (embora isso já seja o que algo que o nanocontrolador produz ...) 3 dias de folga e 4 noites de semana se passaram, e levaria menos ainda se viesse imediatamente a mim definir definir SYNTHESIS globalmente.


Materiais


Para iniciantes - uma lista de materiais no sentido amplo da palavra. Precisamos do seguinte software:


  • RocketChip - contém o próprio processador e o ambiente de montagem (incluindo, descreve vários sub-repositórios). Capaz de criar o RocketChip com o processador em ordem do núcleo da Rocket.
  • SiFive Freedom - obrigatório para várias placas de depuração - lá adicionaremos suporte para nossos
  • rocket-tools - ferramentas para construção de código e depuração para arquiteturas RISC-V de 32 e 64 bits
  • openocd-riscv - Porta OpenOCD para trabalhar em JTAG com kernels RISC-V (pelo menos RocketChip)
  • Comunidade IntelliJ IDEA para editar o código Scala, que é a maioria no RocketChip
  • A cadeia de ferramentas pré-montada do SiFive também pode ser útil, um link para o qual eu vi no artigo já mencionado

O que é necessário do ferro:


  • Kit Zeowaa da Aliexpress: placa com Cyclone 4 EP4CE115 e (clone?) USB Blaster
  • RaspberryPi como depurador JTAG para soft core

A máquina host é considerada um computador relativamente poderoso com o Ubuntu e o Quartus Lite 18 instalados.


Na verdade, existe uma opção para iniciar na nuvem Amazon em suas instâncias FPGA a partir do mesmo SiFive, chamado FireSim , mas não é tão interessante, Sim, e os LEDs estão pouco visíveis . Além disso, nesse caso, você precisará especificar sua chave de API na instância de controle para iniciar outras máquinas virtuais e cuidar muito dela , caso contrário, de acordo com os rumores, um dia você poderá acordar com uma dívida de dezenas de milhares de dólares ...


Estudamos a situação


Para começar, peguei o projeto de teste read_write_1G do fornecedor do conselho e tentei adicionar as fontes necessárias. Por que não criar um novo? Porque eu sou novo, e neste projeto os nomes dos pinos já foram mapeados. Então, você precisa pegar o código fonte de algum lugar. Para fazer isso, precisaremos do repositório de freedom já especificado (não confunda com o freedom-e-sdk ). Para obter pelo menos alguma coisa, coletaremos rocket-tools acordo com as instruções (literalmente, lançando dois scripts e muita espera) e, em seguida, executaremos


 RISCV=$(pwd)/../rocket-tools make -f Makefile.e300artydevkit verilog mcs 

O verilog destino gerará um enorme arquivo Verilog com fontes de processador para nós, e o mcs compilará o BootROM. Não precisa se preocupar com o fato de o mcs falhar - simplesmente não temos o Xilinx Vivado, portanto o BootROM compilado não pode ser convertido no formato que o Vivado precisa.


Através do item de menu Projeto Quartus -> Adicionar / Remover Arquivos no Projeto ... adicione freedom/builds/e300artydevkit/sifive.freedom.everywhere.e300artydevkit.E300ArtyDevKitConfig.v , defina a entidade de nível superior: E300ArtyDevKitFPGAChip na guia Geral e inicie a compilação (possivelmente , a lista de preenchimento automático da entidade de nível superior será exibida somente após a primeira compilação). Como resultado, recebemos IOBUF erros informando sobre a ausência dos módulos AsyncResetReg , IOBUF etc. Se não houver erros, você esqueceu de alterar a entidade de nível superior. Se você vasculhar a fonte, poderá encontrar diretamente o arquivo AsyncResetReg.v , mas o IOBUF é uma ligação ao núcleo IP do Xilinx . Primeiro, adicione freedom/rocket-chip/src/main/resources/vsrc/AsyncResetReg.v . E plusarg_reader.v também será adicionado.


Execute a compilação e obtenha outro erro:


 Error (10174): Verilog HDL Unsupported Feature error at plusarg_reader.v(18): system function "$value$plusargs" is not supported for synthesis 

Em princípio, construções não sintetizadas podem ser esperadas de um arquivo com um nome semelhante.


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 

Como podemos ver, este módulo provavelmente lê as opções de simulação na linha de comando e, quando sintetizado, simplesmente retorna o valor padrão. O problema é que nosso projeto não define define chamado SYNTHESIS . Seria possível ifdef `define SYNTHESIS na linha anterior logo antes do ifdef e passar meia semana tentando entender por que o kernel não inicia (e, afinal, a infecção é sintetizada ...). Não repita meus erros, mas simplesmente abra as propriedades do projeto novamente e, na guia Configurações do compilador-> Verilog HDL Input , defina a macro SYNTHESIS e pelo menos 1, não em <NONE> (linha vazia).


Aqui! Agora, o Quartus jura por falta de ligações - é hora de configurar um projeto no Idea e começar a portar.


Conhecendo o projeto


Contamos a ideia do projeto Importar, especificamos o caminho para o repositório Freedom, indicamos o tipo de projeto sbt, verificamos o uso do shell sbt para importações e para as caixas de seleção Builds. Aqui, o conto de fadas termina, ao que parece, mas não encontra a idéia de um meio projeto - todos os códigos-fonte estão marcados em vermelho. Com base nas informações daqui , obtive o seguinte procedimento:


  • abra a janela shell sbt
  • entre clean
  • pressione o botão verde Reiniciar à esquerda
  • após reiniciar o sbt, digite o primeiro comando ++2.12.4 , alternando todos os subprojetos para o Scala versão 2.12.4, e executaremos a compile
  • clique em Atualizar todos os projetos sbt Idea
  • LUCRO !, agora a luz de fundo funciona corretamente

Até agora, tentarei de alguma forma montar o projeto, pelo menos no modo semi-manual. A propósito, alguém, diga- quartus_ipcreate , é o quartus_ipcreate na Lite Edition? Por enquanto, criaremos variações de IP manualmente, e as ligações estarão apenas no Scala na forma de BlackBox.


Nesse caso, estamos interessados ​​na seguinte hierarquia de diretórios:


 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 

Você também precisa adicionar um Makefile semelhante ao Makefile.e300artydevkit , algo como isto:


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 

Ligações


Para começar, implementamos esse IOBUF - dificilmente será difícil. A julgar pelo código Scala, este é um módulo que controla a “perna” (bola?) Física do microcircuito: pode ser ligado, pode ser ligado ou pode ser completamente desligado. À direita da janela do Quartus, inseriremos “IOBUF” no Catálogo de IP e obteremos imediatamente um componente chamado ALTIOBUF . Vamos definir um nome para o arquivo de variação, selecione "Como um buffer bidirecional". Depois disso, um módulo chamado iobuf aparecerá em nosso projeto:


 // ... 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 // ... 

Vamos escrever um módulo de caixa preta para ele:


 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 } } 

Descrevemos o Verilogue inout tipo Analog , e o método desiredName permite alterar o nome da classe do módulo. Isso é especialmente importante porque geramos ligantes, não implementações.


Também precisamos do BootROM - para isso, criamos uma variação da ROM: 1-PORT (palavras de 2048 x 32 bits, apenas registro de endereço, crie a porta rden ). Criamos um bloco com o nome rom , porque temos que escrever um adaptador para a interface que a classe ROMGenerator : ROMGenerator vez de me e oe faltando em nós (ainda está anexado a 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 

Mais um problema é descoberto imediatamente: os arquivos hexadecimais gerados pelo coletor, por algum motivo, acabam sendo incompatíveis com o Quartus. Depois de pesquisar um pouco sobre o tópico dos arquivos Intel HEX (a Intel demorou muito antes da compra deste Intel Altera, como eu o entendo), chegamos a um comando que converte arquivos binários em HEX:


 srec_cat -Output builds/zeowaa-e115/xip.hex -Intel builds/zeowaa-e115/xip.bin -Binary -Output_Block_Size 128 

Portanto, nosso Makefile será transformado um pouco:


Texto oculto
 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 

Move ^ W é sintetizado


Portanto, o projeto como um todo é sintetizado, agora a coisa mais interessante é a depuração. O gato é primeiro autorizado a entrar em casa, e talvez o JTAG no microcontrolador. Vamos criar um sistema quase mínimo para depuração: BootROM para inicializar, GPIO para piscar LEDs e JTAG para entender por que nada está carregando e não está piscando. Por analogia com o E300ArtyDevKit crie um pacote com quatro arquivos. Em primeiro lugar


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) }) ) 

A descrição é na maioria das vezes copiada e truncada do E300: perguntamos em que consiste nosso kernel e onde ele estará no espaço de endereço. Observe que, embora não tenhamos RAM (essa é a TinyConfig padrão no TinyConfig ), há um espaço de endereço, além disso, de 32 bits!


Há também um arquivo que carrega uma certa quantidade de clichê.
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 { // Reset vector is set to the location of the mask rom val maskROMParams = p(PeripheryMaskROMKey) global_reset_vector := maskROMParams(0).address.U } 

Na verdade, o layout da nossa "placa-mãe" está em três (até agora) arquivos 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 } } 

Como você pode ver, você pode escrever geradores no Chisel em um estilo funcional (um caso muito simples é mostrado aqui). Mas você pode escrever e claramente todos os fios:


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)) // Internal wiring val cpu_clock = Wire(Clock()) val cpu_rst = Wire(Bool()) val jtag_rst = Wire(Bool()) withClockAndReset(cpu_clock, false.B) { val counter = Reg(UInt(64.W)) counter := counter + 1.U cpu_rst := (counter > 1000.U) && (counter < 2000.U) jtag_rst := (counter > 3000.U) && (counter < 4000.U) } cpu_clock <> clk25 } 

Por que é dividido em três arquivos? Bem, em primeiro lugar, era assim no protótipo :) A lógica de separar o Shell do FPGAChip , aparentemente, era que o Shell é uma descrição da interface para o mundo exterior: que conclusões temos em um quadro específico (e como serão exibidas no conclusões de chip!) e o FPGAChip é ditado pelo fato de que queremos inserir um SoC específico. Bem, o Platform é separado de maneira lógica: nota: o ZeowaaShell (e, portanto, o Platform ) é um RawModule , em particular, eles não têm um clock implícito e são reset - isso é natural para "conectar a placa", mas inconveniente para o trabalho (e, provavelmente, repleto de erros complicados com domínios de frequência prolíficos). Bem, o Platform já é um módulo Chisel comum, no qual você pode descrever com segurança registros, etc.


Jtag


Algumas palavras sobre como configurar o JTAG. Como eu já tinha o RaspberryPi 3 Modelo B +, a solução óbvia era tentar, de alguma forma, usar seu GPIO. Felizmente, tudo já foi implementado antes de nós : no OpenOCD novo, há uma descrição da interface/sysfsgpio-raspberrypi.cfg , com a qual você pode interface/sysfsgpio-raspberrypi.cfg ao depurador que se conecte através do bloco (TCK = 11, TMS = 25, TDI = 10, TDO = 9 e GND sair como exercício) - pinagem aqui .


Além disso, tomando o Freedom.cfg como base do repositório riscv-tests , obtive o seguinte:


 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" 

Para funcionar, você precisará da porta riscv-openocd criada no ARM, portanto o brinde não passou em vez da versão pré-montada do SiFive, você deve clonar o repositório e coletar:


 ./configure --enable-remote-bitbang --enable-sysfsgpio 

Se alguém souber executar o bitbang remoto, talvez não seja necessário criar uma porta personalizada para o ARM ...


Como resultado, lançamos a partir da raiz em 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 

Depois de efetuar login no SSH com o encaminhamento de porta 3333, substituindo o IP desejado:


 ssh -L 3333:127.0.0.1:3333 ubuntu@192.168.1.104 

Agora, no host, você pode executar o GDB sob a arquitetura 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 ?? () 

Iremos pular algumas depurações cegas do JTAG não aderente devido ao fato de que a macro SYNTHESIS é usada ativamente no arquivo principal gerado e rebobinar a situação para o estado "JTAG está conectado, mas as luzes não piscam".


Primeiro código


Como vimos no Makefile, o código para bootrom é obtido no bootrom/xip/xip.S Agora fica assim:


 // 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 

Entendo que deve haver uma árvore de dispositivos (o conteúdo do arquivo dtb) para ser lida pelo sistema operacional, mas que tipo de sistema operacional existe sem RAM. Portanto, substitua-o temporariamente com ousadia por lâmpadas intermitentes:


Texto oculto
  .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 

Esse código foi desenvolvido iterativamente; portanto, desculpe-me pela estranha numeração dos registros. Além disso, estudei honestamente o montador RISC-V usando o método "compilar um pedaço de código em um arquivo de objeto, desmontar, ver". Quando li um livro de eletrônica há alguns anos, ele falou sobre a programação do ATTiny na linguagem assembly. "Aqui estão as coisas chatas e a rotina, provavelmente", pensei, mas agora, aparentemente, o efeito de uma loja sueca apareceu : armário processador montado a partir de peças de reposição, até o montador parece ser nativo e interessante. Como resultado da execução desse código, o LED aceso deve “correr” para a esquerda ou direita, dependendo de qual botão for pressionado.


Começamos ... E nada: todas as luzes estão acesas, elas não respondem aos botões. Vamos nos conectar via JTAG: contador de programa = 0x00000000 - de alguma forma tudo está triste. Mas em 0x64002000 , temos registros GPIO disponíveis:


GPIOCtrlRegs.scala
 // See LICENSE for license details. package sifive.blocks.devices.gpio object GPIOCtrlRegs { val value = 0x00 val input_en = 0x04 val output_en = 0x08 val port = 0x0c val pullup_en = 0x10 val drive = 0x14 val rise_ie = 0x18 val rise_ip = 0x1c val fall_ie = 0x20 val fall_ip = 0x24 val high_ie = 0x28 val high_ip = 0x2c val low_ie = 0x30 val low_ip = 0x34 val iof_en = 0x38 val iof_sel = 0x3c val out_xor = 0x40 } 

Vamos tentar movê-los manualmente:


 (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 

Então ... Um dos LEDs apagou ... E se não for 0x1 , mas 0x5 ... Está certo, agora os LEDs estão acesos através de um. Também ficou claro que eles precisam ser invertidos, mas você não precisa gravar no registro 0x00 - é necessário ler a partir daí.


 (gdb) x/x 0x64002000 0x64002000: 0x00000030 //    (gdb) x/x 0x64002000 0x64002000: 0x00000020 //   (gdb) x/x 0x64002000 0x64002000: 0x00000010 //   (gdb) x/x 0x64002000 0x64002000: 0x00000000 

Ótimos registros de mapeamento de memória são atualizados sem iniciar o processador; você não pode pressionar cont + Ctrl-C todas as vezes - um pouco, mas é legal.


Mas por que não estamos girando em um loop, mas estamos em $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 

Qual é o Pokemon ??? Eu não escrevi essas instruções, eles me jogaram! Vamos dar uma olhada:


 (gdb) x/10x 0x10000 0x10000: 0xb7270064 0x9305f000 0x13061000 0x93060003 0x10010: 0x13080001 0x93080002 0x23ac0702 0x23a4b700 0x10020: 0x23a0c700 0x23aab700 

Por outro lado, o que deveria estar lá?


 $ ../../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) ... 

Como você pode ver, o Quartus honestamente colocou as mesmas palavras que estavam no arquivo de inicialização, mas mudou sua capacidade de endianismo. Você pode pesquisar por um longo tempo no Google como resolvê-lo, mas eu sou programador , as muletas são nossas, então, apenas reescrevo


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 

Então, nós coletamos, começamos, não brilha. 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


...

Source: https://habr.com/ru/post/pt455391/


All Articles