我有一个原装 hifive1 板。对于原始板,入门指南是这样说的:
HiFive1 板在 SPI 闪存 (0x20000000) 的开头带有一个可修改的引导加载程序。在该程序执行结束时,核心跳转到代码的主用户部分 0x20400000。
对于 rev b 板,它是这样说的:
HiFive1 Rev B 板在 SPI 闪存 (0x20000000) 的开头带有一个可修改的引导加载程序。在该程序执行结束时,核心跳转到代码的主用户部分 0x20010000。
两个芯片都显示 0x80000000 用于 ram 和 0x20000000 用于(外部)闪存。假设这是他们将闪存放在 rev B 板上的接口。
第一个程序。
novectors.s
.globl _start
_start:
lui x2,0x80004
jal notmain
sbreak
j .
.globl dummy
dummy:
ret
notmain.c
void dummy ( unsigned int );
int notmain ( void )
{
unsigned int ra;
for(ra=0;;ra++) dummy(ra);
return(0);
}
内存映射
MEMORY
{
ram : ORIGIN = 0x80000000, LENGTH = 0x4000
}
SECTIONS
{
.text : { *(.text*) } > ram
.rodata : { *(.rodata*) } > ram
.bss : { *(.bss*) } > ram
}
构建
riscv32-none-elf-as -march=rv32i -mabi=ilp32 novectors.s -o novectors.o
riscv32-none-elf-gcc -march=rv32i -mabi=ilp32 -Wall -O2 -nostdlib -nostartfiles -ffreestanding -c notmain.c -o notmain.o
riscv32-none-elf-ld novectors.o notmain.o -T memmap -o notmain.elf
riscv32-none-elf-objdump -D notmain.elf > notmain.list
riscv32-none-elf-objcopy notmain.elf -O binary notmain.bin
理论上你可以使用 riscv32-whatever-whatever(riscv32-unknown-elf 等)。因为这段代码足够通用。另请注意,我使用的是最小的 rv32i,您可能可以使用 rv32imac。
检查反汇编:
Disassembly of section .text:
80000000 <_start>:
80000000: 80004137 lui x2,0x80004
80000004: 010000ef jal x1,80000014 <notmain>
80000008: 00100073 ebreak
8000000c: 0000006f j 8000000c <_start+0xc>
80000010 <dummy>:
80000010: 00008067 ret
80000014 <notmain>:
80000014: ff010113 addi x2,x2,-16 # 80003ff0 <notmain+0x3fdc>
80000018: 00812423 sw x8,8(x2)
8000001c: 00112623 sw x1,12(x2)
80000020: 00000413 li x8,0
80000024: 00040513 mv x10,x8
80000028: fe9ff0ef jal x1,80000010 <dummy>
8000002c: 00140413 addi x8,x8,1
80000030: ff5ff06f j 80000024 <notmain+0x10>
作为 rv32i,它都是 32 位指令,这很好。该程序旨在加载到 ram 中并使用调试器在那里运行,我使用 openocd 和 telnet in。
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
然后
halt
load_image notmain.elf
resume 0x80000000
在远程登录窗口中。
然后你可以再次停下来。
80000024: 00040513 mv x10,x8
80000028: fe9ff0ef jal x1,80000010 <dummy>
8000002c: 00140413 addi x8,x8,1
80000030: ff5ff06f j 80000024 <notmain+0x10>
您可以检查 x8 或 x10 以查看其是否有效:
resume
halt
并再次检查它们应该增加的寄存器。第一个程序运行,继续。
第二个程序使用此链接器脚本:
内存映射
MEMORY
{
rom : ORIGIN = 0x20010000, LENGTH = 0x4000
ram : ORIGIN = 0x80000000, LENGTH = 0x4000
}
SECTIONS
{
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.bss : { *(.bss*) } > ram
}
检查反汇编。
Disassembly of section .text:
20010000 <_start>:
20010000: 80004137 lui x2,0x80004
20010004: 010000ef jal x1,20010014 <notmain>
20010008: 00100073 ebreak
2001000c: 0000006f j 2001000c <_start+0xc>
20010010 <dummy>:
20010010: 00008067 ret
20010014 <notmain>:
20010014: ff010113 addi x2,x2,-16 # 80003ff0 <notmain+0x5fff3fdc>
20010018: 00812423 sw x8,8(x2)
2001001c: 00112623 sw x1,12(x2)
20010020: 00000413 li x8,0
20010024: 00040513 mv x10,x8
20010028: fe9ff0ef jal x1,20010010 <dummy>
2001002c: 00140413 addi x8,x8,1
20010030: ff5ff06f j 20010024 <notmain+0x10>
它似乎与位置无关,因此它应该与其他链接描述文件一样工作,但最好使用正确的地址。
我的笔记说:
flash protect 0 64 last off
program notmain.elf verify
resume 0x20010000
现在您应该能够重置或重启电路板,以不重置(或如果您愿意)的方式与 openocd 连接,然后您不需要加载它应该运行的任何内容然后引导加载程序在该地址启动您的引导加载程序(正如他们提到的那样跳转到它)。检查 r8 或 r10(此 abi 的 r10 是传递的第一个参数,因此即使您的 gcc 使用 r8 以外的其他东西构建,r10 仍应反映计数器)resume、halt、reg、resume、halt、reg ...
在 0x20000000 覆盖他们的引导加载程序之前,我会转储它并确保您有一个好的副本,或者他们的网站上可能有一个副本。然后您可以将链接描述文件更改为 0x20000000。在我亲自这样做之前,我会反汇编并检查他们的引导加载程序,并找出它在做什么,是否值得保留等等。他们的文字说“可修改”
我在 hifive1 板上切了我的 risc-v 牙齿,但已经转向 sim 开源内核,hifive 板非常昂贵。我还做了一个最小的 pcb 并放下了一些 5 个零件,只会用完 ram 等,但是我的板太小了,我没有回去再试一次,他们的论坛上很少支持 pcb 工作和他们的文档留下了一些不足之处。
关键是那里有许多内核,您可以使用 verilator 或其他方式进行模拟并查看正在发生的一切,并且您不能变砖也不能冒烟,因为它是一个模拟。
注意 rv32ic
riscv32-none-elf-as -march=rv32ic -mabi=ilp32 novectors.s -o novectors.o
riscv32-none-elf-gcc -march=rv32ic -mabi=ilp32 -Wall -O2 -nostdlib -nostartfiles -ffreestanding -c notmain.c -o notmain.o
riscv32-none-elf-ld novectors.o notmain.o -T memmap -o notmain.elf
riscv32-none-elf-objdump -D notmain.elf > notmain.list
riscv32-none-elf-objcopy notmain.elf -O binary notmain.bin
你可以看到它尽可能使用压缩指令
20010000 <_start>:
20010000: 80004137 lui x2,0x80004
20010004: 00a000ef jal x1,2001000e <notmain>
20010008: 9002 ebreak
2001000a: a001 j 2001000a <_start+0xa>
2001000c <dummy>:
2001000c: 8082 ret
2001000e <notmain>:
2001000e: 1141 addi x2,x2,-16
20010010: c422 sw x8,8(x2)
20010012: c606 sw x1,12(x2)
20010014: 4401 li x8,0
20010016: 8522 mv x10,x8
20010018: 3fd5 jal 2001000c <dummy>
2001001a: 0405 addi x8,x8,1
2001001c: bfed j 20010016 <notmain+0x8>
编写自己的模拟器也很容易。取决于你想如何分阶段学习这个平台。掌握指令集与工具链与特定芯片及其外围设备的成本是多少。
您肯定想要来自 riscv.org 的 risc-v 文档,该文档与内核支持的版本、大量内部内核寄存器和内容以及指令集相匹配。如果您想做自己的事情,还有相关芯片的入门和芯片文档。如果你想玩他们的沙盒并使用一些第三方库,那么你需要学习他们的沙盒并在他们的沙盒中玩,而不是自己做。看起来你想做自己的事。
请注意,我使用的是来自 gnu 主线源的当前版本的 gcc/binutils,手工构建。
riscv32-none-elf-gcc --version
riscv32-none-elf-gcc (GCC) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
riscv32-none-elf-as --version
GNU assembler (GNU Binutils) 2.32
Copyright (C) 2019 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or later.
This program has absolutely no warranty.
This assembler was configured for a target of `riscv32-none-elf'.
上面的代码在几年前对原始的 hifive1 工作得很好,这种风格往往适用于 gnu 的主要版本,我已经使用这个工具链来对抗其他 riscv 内核,所以即使你的较旧,它仍然可以工作。最重要的是将arch(-march)与内核支持的指令集相匹配,或者至少一个子集rv32i应该被所有内核支持,压缩和乘法等并不总是支持。
我的第一块板的 openocd 配置文件
adapter_khz 10000
interface ftdi
ftdi_device_desc "Dual RS232-HS"
ftdi_vid_pid 0x0403 0x6010
ftdi_layout_init 0x0008 0x001b
ftdi_layout_signal nSRST -oe 0x0020 -data 0x0020
set _CHIPNAME riscv
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x10e31913
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME riscv -chain-position $_TARGETNAME
$_TARGETNAME configure -work-area-phys 0x80000000 -work-area-size 10000 -work-area-backup 1
flash bank onboard_spi_flash fespi 0x20000000 0 0 0 $_TARGETNAME
init
openocd -f riscv.cfg 在一个终端/窗口中,然后在另一个终端/窗口中 telnet localhost 4444。
现在,就您所询问的 gnu 汇编器的细微差别而言,请查看 gnu 汇编器,或者更好地使用尽可能少的汇编器/工具链特定的东西,因为它可能会改变和/或有一天您可能会改变工具。 YMMV
gnu 工具无法通过墙上的洞来识别此板。您告诉 gnu 工具有关处理器核心架构的信息,并在链接器脚本中告诉内存映射。您的代码,直接或间接(如果您使用其他人的引导程序和链接器脚本)必须与处理器内核的引导属性相匹配,无论是来自 sifive 的 risc-v 还是某些 arm 内核或 mips 或 x86 等。
向量表与否,在某个地址执行等。在上述情况下,它们的引导加载程序会跳转到 0x20010000,因此您需要将第一条指令放在 0x20010000 处,这是通过将该指令作为引导源中的第一条指令来完成的,如果未在链接器脚本中指定,则首先将该对象放在 ld 命令行上,然后在尝试在硬件上运行之前检查反汇编以确认它是否有效。
我使用的 riscv 内核没有向量表,为了重置它们只是从某个地址开始执行。因此,如果您没有预引导加载程序跳转到您,您将使用相同的方法。对于不是 risc-v 的其他架构,如果是跳转到地址事物与向量表事物,则板/平台程序的构建会有所不同。
现在说,如果您使用的是他们的沙箱,那么这是一个沙箱问题而不是 gnu 工具链问题。
在他们的文档中,板文档和/或网站表明 rev b 板使用 FE310-G002 文档中的 FE310-G002 芯片,您可以找到内存映射。它还表明这是一个 risc-v 架构,然后您可以访问 riscv.org 基金会并获取该架构的文档,该文档会告诉您它是如何启动的。回到 FE310-G002,它会告诉您从 MSEL 引脚启动的过程。您需要检查原理图。所以现实是他们的文档确实告诉你如何通过提供你需要提供给 gnu 的信息来表明这是一个引导加载程序。
也就是说……需要/需要进行一些实验。可以/很容易地编写一个简单的位置无限循环,为 0x00000000 构建,但根据他们的文档在 0x20010000 加载,并与 openocd 一起检查程序计数器,看看它是否真的基于 0x20010000。由此您可以假设,最终在出厂时,无论选择何种 MSEL,该板都会通过其引导加载程序进入您的引导加载程序。
嗯:
上电时,内核的复位向量为 0x1004。
它进一步指出了每个 MSEL 带选项的不同第一指令地址。因此,如果您要接管他们的引导加载程序并根据文档将其替换为您自己的引导加载程序,您将链接到 0x20000000 并在那里拥有入口点。
编辑
刚拿到我的 rev b 板。
您可以查看入门指南以了解如何指定板
使用他们的沙箱。但这不是必需的,如果您有 (gnu)
支持 rv32i 或更多您可以构建的 rv32imac 的工具链
没有其他外部依赖项的程序。
工具链本身不知道一块板与另一块板,一块芯片与另一块板。
五个文档说:
HiFive1 Rev B 板在 SPI 闪存 (0x20000000) 开始时附带一个可修改的引导加载程序。在该程序执行结束时,核心跳转到代码的主用户部分 0x20010000。
这就是我们需要的关键信息,加上内存映射中 0x80000000 0x4000 字节 sram 部分的内存地址空间。
novectors.s
.globl _start
_start:
lui x2,0x80004
jal notmain
j .
.globl dummy
dummy:
ret
.globl PUT32
PUT32:
sw x11,(x10)
ret
.globl GET32
GET32:
lw x10,(x10)
ret
notmain.c
void PUT32( unsigned int, unsigned int);
unsigned int GET32 ( unsigned int );
void dummy ( unsigned int );
#define GPIOBASE 0x10012000
#define GPIO_VALUE (GPIOBASE+0x00)
#define GPIO_INPUT_EN (GPIOBASE+0x04)
#define GPIO_OUTPUT_EN (GPIOBASE+0x08)
#define GPIO_PORT (GPIOBASE+0x0C)
#define GPIO_PUE (GPIOBASE+0x10)
#define GPIO_OUT_XOR (GPIOBASE+0x40)
int notmain ( void )
{
unsigned int rx;
PUT32(GPIO_OUTPUT_EN,(1<<19)|(1<<21)|(1<<22));
PUT32(GPIO_PORT,(1<<19)|(1<<21)|(1<<22));
PUT32(GPIO_OUT_XOR,0);
while(1)
{
PUT32(GPIO_PORT,(1<<19)|(1<<21)|(1<<22));
for(rx=0;rx<2000000;rx++) dummy(rx);
PUT32(GPIO_PORT,0);
for(rx=0;rx<2000000;rx++) dummy(rx);
}
return(0);
}
内存映射
MEMORY
{
rom : ORIGIN = 0x20010000, LENGTH = 0x1000
ram : ORIGIN = 0x80000000, LENGTH = 0x4000
}
SECTIONS
{
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.bss : { *(.bss*) } > ram
}
构建
riscv32-none-elf-as -march=rv32imac -mabi=ilp32 novectors.s -o novectors.o
riscv32-none-elf-gcc -march=rv32imac -mabi=ilp32 -Wall -O2 -nostdlib -nostartfiles -ffreestanding -c notmain.c -o notmain.o
riscv32-none-elf-ld novectors.o notmain.o -T memmap -o notmain.elf
riscv32-none-elf-objdump -D notmain.elf > notmain.list
riscv32-none-elf-objcopy notmain.elf -O ihex notmain.hex
riscv32-none-elf-objcopy notmain.elf -O binary notmain.bin
现在理论上你可以使用他们谈论的 riscv64-unknown-elf,即使他们想为 rv32 而不是 rv64 构建。我也可以试试。
notmain.list
Disassembly of section .text:
20010000 <_start>:
20010000: 80004137 lui x2,0x80004
20010004: 010000ef jal x1,20010014 <notmain>
20010008: a001 j 20010008 <_start+0x8>
2001000a <dummy>:
2001000a: 8082 ret
2001000c <PUT32>:
2001000c: c10c sw x11,0(x10)
2001000e: 8082 ret
20010010 <GET32>:
20010010: 4108 lw x10,0(x10)
20010012: 8082 ret
20010014 <notmain>:
20010014: 1141 addi x2,x2,-16
20010016: c04a sw x18,0(x2)
20010018: 10012937 lui x18,0x10012
2001001c: 00890513 addi x10,x18,8 # 10012008 <_start-0xfffdff8>
20010020: 006805b7 lui x11,0x680
20010024: c606 sw x1,12(x2)
20010026: c226 sw x9,4(x2)
20010028: c422 sw x8,8(x2)
2001002a: 37cd jal 2001000c <PUT32>
2001002c: 00c90513 addi x10,x18,12
20010030: 006805b7 lui x11,0x680
20010034: 3fe1 jal 2001000c <PUT32>
20010036: 04090513 addi x10,x18,64
2001003a: 4581 li x11,0
2001003c: 001e84b7 lui x9,0x1e8
20010040: 37f1 jal 2001000c <PUT32>
20010042: 0931 addi x18,x18,12
20010044: 48048493 addi x9,x9,1152 # 1e8480 <_start-0x1fe27b80>
20010048: 006805b7 lui x11,0x680
2001004c: 854a mv x10,x18
2001004e: 3f7d jal 2001000c <PUT32>
20010050: 4401 li x8,0
20010052: 8522 mv x10,x8
20010054: 0405 addi x8,x8,1
20010056: 3f55 jal 2001000a <dummy>
20010058: fe941de3 bne x8,x9,20010052 <notmain+0x3e>
2001005c: 4581 li x11,0
2001005e: 854a mv x10,x18
20010060: 3775 jal 2001000c <PUT32>
20010062: 4401 li x8,0
20010064: 8522 mv x10,x8
20010066: 0405 addi x8,x8,1
20010068: 374d jal 2001000a <dummy>
2001006a: fe941de3 bne x8,x9,20010064 <notmain+0x50>
2001006e: bfe9 j 20010048 <notmain+0x34>
在您尝试将程序加载到设备上之前,请务必检查一下,我们所需的入口代码,novectors.s 的第一条指令对于该板/芯片(出厂引导加载程序)需要位于 0x20010000。确实如此。
notmain.hex
:020000042001D9
:1000000037410080EF00000101A082800CC1828096
:100010000841828041114AC0372901101305890027
:10002000B705680006C626C222C4CD371305C9002D
:10003000B7056800E13F130509048145B7841E0038
:10004000F137310993840448B70568004A857D3F3C
:10005000014422850504553FE31D94FE81454A85F0
:1000600075370144228505044D37E31D94FEE9BF31
:0400000520010000D6
:00000001FF
将 notmain.hex 复制到已安装的 HiFive 媒体。现在这花了我一两个小时试图找出我开始时的十六进制文件,在这里,它不起作用。下载了他们挖掘的 sdk,发现了一个 elf2hex,但这是一个糟糕的切线,它出现在 fpga 工作中。想通了,他们所做的就是 riscv...objcopy -O ihex 就像我一样,再试一次。现在它可以工作了。我收到一个fail.txt,说它以前无法连接到cpu。不知道我做了什么或没做什么来完成这项工作。
理论上你可以剪切粘贴上面的hex文件,然后保存复制。为什么没有人有一个示例 hex 文件,你必须正确安装 75 个特殊的东西并运行构建,而不是在这里提供一个带有中间文件的完整示例。我当然会在这个平台的示例中这样做。或者至少是上面那个。
而不是他们的彩虹 LED 闪烁模式,以上将使其以常规速率闪烁“白色”。
注意 LED 位于 rev a 板上的相同 GPIO 线上,引导加载程序位于与 rev b 0x20010000 不同的地址 0x20400000 上。因此,可以为 rev a board 构建一个相同的 memmap 更改。
如果您或读者想要回到一个 rev a(如果他们有一个),那么它是一个修改过的 openocd,在撰写本文时它位于 github riscv 用户 riscv-openocd 项目中。正常的 ./bootstrap、./configure、make 来获取工具,在 tcl 目录中有上面显示的 riscv openocd 配置文件
interface ftdi
ftdi_device_desc "Dual RS232-HS"
ftdi_vid_pid 0x0403 0x6010
是关键,rev2 board lsusb:
Bus 001 Device 018: ID 1366:1051 SEGGER
并且没有命中 openocd 配置文件中的那些 pid/vid 值。导致阅读更多入门手册。