【verilog开源鉴赏】DarkRISCV:Opensource RISC-V implemented from scratch in one night!
项目概述
DarkRISCV是一个开源项目,它完全从头开始用Verilog语言实现了一个基于RISC-V指令集的CPU核心。在guihub上收回了1.9K stars的超高点亮,项目的主体是以verilog编写完成的。
核设计部分在rtl文件夹,文件分类非常的简介清晰,分为了cache、pll、riscv、soc、uart五个部分,结构划分明了:
代码风格上来说,主要以大量的宏定义取代了可能我们更加常用的参数,当然这本身也是因为其中分支较多的情况,采用宏不失为更加简洁的规划方式:
`ifdef QMTECH_KINTEX7_K325
`define BOARD_ID 12
`define BOARD_CK_REF 50000000
`define BOARD_CK_MUL 20
`define BOARD_CK_DIV 4
`define XILINX7CLK 1
`define INVRES 1
`endif
我自己在项目里也比较喜欢这种一个芯片/系统使用同一套宏/参数文件的方式而不是层层传参,后者的维护代价更大且不易于直观的发现问题。因此除了cbb这类需要适配于各个模块的底层单元,我所交付的系统基本都采用了同意`include "xxx_param.vh"方式。
这个项目代码的宏使用确实比较多,可能会为代码阅读带来一定的干扰。此外他比较喜欢把多个信号写在同一个always块中,这本身并没有任何问题,不过结合宏分支以及缩进不对齐的小问题之后阅读时确实较为费力。比如说下面这段代码是darkriscvde的核心逻辑,整体是一个大的always块,我在尝试阅读时一直在努力排除干扰寻找缩进等级和逻辑关系:
always@(posedge CLK)
begin
`ifdef __THREADS__
RESMODE <= RES ? -1 : RESMODE ? RESMODE-1 : 0;
XRES <= |RESMODE;
`else
XRES <= RES;
`endif
`ifdef __3STAGE__
FLUSH <= XRES ? 2 : HLT ? FLUSH : // reset and halt
FLUSH ? FLUSH-1 :
`ifdef __INTERRUPT__
MRET ? 2 :
`endif
JREQ ? 2 : 0; // flush the pipeline!
`else
FLUSH <= XRES ? 1 : HLT ? FLUSH : // reset and halt
JREQ; // flush the pipeline!
`endif
`ifdef __INTERRUPT__
if(XRES)
begin
MTVEC <= 0;
MEPC <= 0;
MIP <= 0;
MIE <= 0;
end
else
if(MIP&&MIE&&JREQ)
begin
MEPC <= JVAL;
MIP <= 1;
MIE <= 0;
end
else
if(CSRW)
begin
case(XIDATA[31:20])
12'h305: MTVEC <= U1REG;
12'h341: MEPC <= U1REG;
12'h304: MIE <= U1REG;
endcase
end
else
if(MRET)
begin
MIP <= 0;
MIE <= 1;
end
else
if(INT==1&&MIE==1)
begin
MIP <= 1;
end
`endif
`ifdef __RV32E__
REGS[DPTR] <= XRES||DPTR[3:0]==0 ? 0 : // reset sp
`else
REGS[DPTR] <= XRES||DPTR[4:0]==0 ? 0 : // reset sp
`endif
HLT ? REGS[DPTR] : // halt
LCC ? LDATA :
AUIPC ? PCSIMM :
JAL||
JALR ? NXPC :
LUI ? SIMM :
MCC||RCC ? RMDATA:
`ifdef __MAC16X16__
MAC ? REGS[DPTR]+KDATA :
`endif
`ifdef __INTERRUPT__
CSRR ? CDATA :
`endif
REGS[DPTR];
`ifdef __3STAGE__
`ifdef __THREADS__
NXPC <= /*XRES ? `__RESETPC__ :*/ HLT ? NXPC : NXPC2[XMODE];
NXPC2[XRES ? RESMODE : XMODE] <= XRES ? `__RESETPC__ : HLT ? NXPC2[XMODE] : // reset and halt
JREQ ? JVAL : // jmp/bra
NXPC2[XMODE]+4; // normal flow
XMODE <= XRES ? 0 : HLT ? XMODE : // reset and halt
/*JAL*/ JREQ ? XMODE+1 : XMODE;
//XMODE==0/*&& IREQ*/&&JREQ ? 1 : // wait pipeflush to switch to irq
//XMODE==1/*&&!IREQ*/&&JREQ ? 0 : XMODE; // wait pipeflush to return from irq
`else
NXPC <= /*XRES ? `__RESETPC__ :*/ HLT ? NXPC : NXPC2;
NXPC2 <= XRES ? `__RESETPC__ : HLT ? NXPC2 : // reset and halt
`ifdef __INTERRUPT__
MRET ? MEPC :
MIE&&MIP&&JREQ ? MTVEC : // pending interrupt + pipeline flush
`endif
JREQ ? JVAL : // jmp/bra
NXPC2+4; // normal flow
`endif
`else
NXPC <= XRES ? `__RESETPC__ : HLT ? NXPC : // reset and halt
`ifdef __INTERRUPT__
MRET ? MEPC :
MIE&&MIP&&JREQ ? MTVEC : // pending interrupt + pipeline flush
`endif
JREQ ? JVAL : // jmp/bra
NXPC+4; // normal flow
`endif
PC <= /*XRES ? `__RESETPC__ :*/ HLT ? PC : NXPC; // current program counter
`ifndef __YOSYS__
if(EBRK)
begin
$display("breakpoint at %x",PC);
$stop();
end
if(!FLUSH && IDATA===32'dx)
begin
$display("invalid IDATA at %x",PC);
$stop();
end
if(LCC && !HLT && DATAI===32'dx)
begin
$display("invalid DATAI@%x at %x",DADDR,PC);
$stop();
end
`endif
end
真的不是我复制时有缩进符识别问题,在原网页阅读也是这样的。因此学习该项目还是需要沉下心慢慢梳理一番。
项目特点:
- RISC-V 指令集支持:DarkRISCV 实现了 RISC-V RV32E 指令集的大部分,以及 RV32I 指令集的大部分(不包括 csr 指令、e 扩展和 fence 指令)。
- 高性能:在超低功耗的 Xilinx Ultrascale+ KU040 FPGA 上可以运行至 250MHz(超频可达 400MHz),在便宜的 Spartan-6 FPGA 上可达 100MHz,甚至在小型的 Spartan-3E(如 XC3S100E)上也能运行。
- 高效率:大多数情况下,DarkRISCV 能够以每个指令一个时钟周期的效率运行(大约 71% 的时间)。
- 灵活的哈佛架构:便于集成缓存控制器、总线桥等。
- 兼容性:在 Xilinx 的多种 FPGA(包括 Spartan-3、Spartan-6、Spartan-7、Artix-7、Kintex-7 和 Kintex Ultrascale)上运行良好,也适用于一些 Altera 和 Lattice FPGA。
- GCC 支持:与 RISC-V 的 GCC 9.0.0 兼容,无需补丁。
- 资源占用:根据启用的特性和优化,核心本身使用 850 到 1500 个 LUTs(采用 LUT6 技术)。
- 可选特性:支持 RV32E、可选的 16x16 位 MAC 指令(用于数字信号处理)、粗粒度多线程(MT)等。
- 无流水线级间锁定:设计大胆,没有在流水线阶段之间设置锁定机制。
使用方法:
项目的README文件提供了详细的设置指南,包括如何克隆仓库、在MacOS上的预先设置指南、依赖安装(如Icarus Verilog、Xilinx ISE等)、编译和运行模拟等步骤。
项目介绍
以下为README部分介绍。
从头开始在一个晚上实现的开源RISC-V!
目录
引言
在2018年8月19日的一个神奇夜晚,从凌晨2点到早上8点,DarkRISCV 软核作为开源RISC-V指令集的概念验证开始了它的开发之旅。
尽管与其他RISC-V实现相比,代码量小且粗糙,但DarkRISCV拥有许多令人印象深刻的功能:
- 实现了RISC-V RV32E指令集的大部分
- 实现了RISC-V RV32I指令集的大部分(缺少csr*, e* 和 fence*)
- 在超规模KU040中可工作至250MHz(超频可达400MHz!)
- 在便宜的Spartan-6中可达100MHz,在小型Spartan-3E如XC3S100E中也适用!
- 大部分时间可以维持每个指令1个时钟周期(通常为71%的时间)
- 灵活的哈佛架构(易于集成缓存控制器、总线桥等)
- 在真实的Xilinx(Spartan-3、Spartan-6、Spartan-7、Artix-7、Kintex-7和Kintex超规模)中运行良好
- 与一些真实的Altera和Lattice FPGAs兼容
- 与RISC-V的gcc 9.0.0兼容(无需补丁!)
- 使用850-1500个LUTs(核心仅使用LUT6技术,取决于启用的功能和优化)
- 可选的RV32E支持(在LUT4 FPGAs中表现更好)
- 可选的16x16位MAC指令(用于数字信号处理)
- 可选的粗粒度多线程(MT)
- 流水线阶段之间没有互锁!
- BSD许可证:可以无限制地在任何地方使用!
一些额外的功能正在计划中或正在开发中:
- 中断控制器(测试中)
- 缓存控制器(测试中)
- GPIO和定时器(测试中)
- 带数据扰码器的SDRAM控制器
- 分支预测器(测试中)
- 以太网控制器(GbE)
- 多处理(SMP)
- 芯片网络(NoC)
- rv64i支持(并不像看起来那么容易...)
- 动态总线尺寸和大端支持
- 用户/超级用户模式
- 调试支持
- 未对齐的内存访问
- 8/16/32位总线桥
以及其他许多功能!
欢迎提出建议并愉快地进行黑客攻击!o/
历史
最初的想法是基于我早期的16位RISC处理器,并由简化的两阶段流水线组成,其中指令在第一个时钟周期从指令内存中获取,然后在第二个时钟周期中解码/执行。流水线没有互锁,重叠设计,这样DarkRISCV大部分时间都能达到每个指令一个时钟周期的性能,除了分支指令,其中在流水线刷新时会丢失一个时钟周期。当然,为了在单个时钟周期内执行块存储器的读操作,需要使用单相时钟与组合逻辑存储器或双相时钟与块存储器存储器,这样就不需要在这些情况下等待状态。
结果,代码非常紧凑,大约有三百行混淆但漂亮的Verilog代码。在许多激动人心的不眠之夜的工作和许多同事的帮助下,DarkRISCV达到了一个非常好的质量结果,以至于标准GCC编译的RV32I代码运行良好。
经过两年的开发,现在也有了一个使用单个时钟相位的三阶段流水线,这在解码和执行阶段之间实现了更好的分配。在这种情况下,指令在第一个时钟周期从块存储器中获取,在第二个时钟周期中解码,在第三个时钟周期中执行。
只要加载指令不能在单个时钟周期内从块存储器中加载数据,外部逻辑就会在IO操作中插入一个额外的时钟周期。此外,还有两个额外的时钟周期,以便在分支指令被采纳的情况下刷新流水线。流水线刷新的影响取决于编译器优化,但根据最新的测量,三阶段流水线版本可以达到每时钟指令数(IPC)0.7,小于两阶段流水线版本的测量IPC 0.85。
无论如何,有了三阶段流水线和其他一些昂贵的优化,DarkRISCV可以在低成本的Spartan-6中达到100MHz,这与两阶段流水线版本(通常为50MHz)相比,性能更高。
项目背景
DarkRISCV的主要动机是为一些围绕680x0/Coldfire家族的项目创建一个迁移路径。
尽管有许多680x0核心可用,但它们是围绕不同的概念和要求设计的,以至于我发现没有多少选项符合我的要求(超过50MIPS,大约1000LUTs)。目前的最佳选择,TG68,至少需要2400LUTs(通过移除MUL/DIV指令),并且在Spartan-6中运行速度高达40MHz。此外,TG68核心至少需要每个指令2个时钟周期,这意味着峰值性能为20MIPS。由于680x0指令过于复杂,这个结果已经相当不错了,目前可能是替代68000的最佳开源选择。
无论如何,它与我关于空间和性能的要求不匹配。作为调查的一部分,我测试了其他核心,但我发现没有多少选项像TG68那样好,我甚至开始设计一个risclized-68000核心,以寻找解决方案。
不幸的是,由于编译器要求(标准GCC),我发现没有多少方法可以减少空间并提高性能,以至于我开始调查非680x0核心。
经过对不同核心的大量测试,我发现了picorv32核心和围绕RISC-V的所有生态系统。picorv32是一个非常优秀的项目,可以在低成本的Spartan-6中达到150MHz的峰值。尽管大多数指令每个指令需要3或4个时钟周期,但picorv32在某些方面类似于68020,但运行速度为150MHz,提供高达50MIPS的峰值性能,这非常令人印象深刻。
尽管picorv32是直接替代680x0家族的一个很好的选择,但它的计算能力不足以替代一些Coldfire处理器(超过75MIPS)。
由于我有一些针对类似DSP应用的实验性16位RISC核心的良好经验,我开始编写DarkRISCV,只是为了检查复杂性水平并与我的risclized-68000进行比较。令我惊讶的是,在第一个晚上,我几乎映射了rv32i规范的所有指令,DarkRISCV开始以75MHz的速度正确执行第一条指令,并且每个指令只需要一个时钟周期,这不仅类似于快速而出色的68040,而且还可以击败一些Coldfires!哇!:)
在第一个晚上的工作成功后,我开始致力于解决硬件和软件实现中的小细节。
目录描述
尽管DarkRISCV只是一个小型处理器核心,但需要一个小型生态系统来测试该核心,包括与RISCV兼容的软件、对仿真的支持以及对外围设备的支持,以便处理器核心能够产生可观察的结果。每个元素都与类似的元素存储在目录中,以便顶层具有以下组织结构:
- README.md:顶层README文件(指向本文档)
- LICENSE:无限的自由!o/
- Makefile:表演从这里开始!
- src:测试固件的源代码(C语言的boot.c、main.c等)
- rtl:DarkRISCV核心和支持逻辑的源代码(Verilog)
- sim:用于测试rtl文件的仿真源代码(目前通过icarus进行)
- boards:不同开发板的支持和示例(目前通过Xilinx ISE进行)
- tmp:为空,但ISE将在这里创建许多文件)
安装说明:
步骤1:使用以下代码将DarkRISC存储库克隆到本地。 git clone github.com/darklife/da…
MacOS的预安装指南:
文档包含了所有依赖项以及安装这些依赖项的步骤,以便在MacOS上成功利用Darriscv生态系统。
从本质上讲,由于依赖项之一的Xilinx ISE 14.7设计套件目前不支持MacOS,因此无法在MacOS上利用生态系统。
为了解决这个问题,我们需要使用以下两种方法在MacOS上安装Linux/Windows:
a) WineSkin,这是一种Windows模拟器,可以本地运行Windows应用程序,但会截获并模拟Windows调用,直接映射到macOS。
b) VirtualBox(或VMware、Parallels等)以便运行完整的Windows操作系统或Linux,这似乎比WineSkin选项要好得多。
我采用了第二种方法,并安装了VMware Fusion来安装Linux Mint。请参阅我用于获取下载文件的以下链接。
依赖项:
-
Icarus Verilog a. Bison b. GNU c. G++ d. FLEX
-
Xilinx 14.7 ISE
Icarus Verilog安装:
步骤已为Linux操作系统简化。所有其他OS平台的完整步骤可在 iverilog.fandom.com/wiki/Instal… 上找到。
步骤1:从 ftp://ftp.icarus.com/pub/eda/verilog/ 下载Verilog的tar文件。始终安装最新版本。目前最新版本是Verilog-10.3。
步骤2:使用‘% tar -zxvf verilog-version.tar.gz’提取tar文件。
步骤3:使用‘cd Verilog-version’进入Verilog文件夹。这里是cd Verilog-10.3。
步骤4:检查是否安装了以下库:Flex、Bison、g++和gcc。如果没有,请在终端中使用‘sudo apt-get install flex bison g++ gcc’进行安装。安装完成后重启系统。
步骤5:在Verilog-10.3目录中运行以下命令
- ./configure
- 制作
- Sudo make install
步骤6:使用‘sudo apt-get install verilog’安装Verilog。
可选步骤:sudo apt-get install gtkwave
Xilinx安装:
按照以下YouTube视频进行完整安装。
注意:确保您的Linux中已安装libncurses库。
如果没有,请使用以下代码:
- 对于64位架构 a. Sudo apt-get install libncurses5 libncursesw-dev
- 对于32位架构 a. Sudo apt-get install libncurses5:i386
安装完所有先决条件后,转到根目录并运行以下代码:
cd darkrisc make(如有必要,请使用sudo)
顶级Makefile负责构建一切,但必须首先编辑,以便用户至少必须选择编译器路径和目标板。
默认情况下,顶级Makefile使用:
CROSS = riscv32-embedded-elf
CCPATH = /usr/local/share/gcc-$(CROSS)/bin/
ICARUS = /usr/local/bin/iverilog
BOARD = avnet_microboard_lx9
根据您的系统配置更新配置,输入make,希望一切都在正确的位置!您可能需要修复一些路径并在PATH环境变量中设置其他路径,但它最终会起作用。
当一切正确配置后,结果将如下所示:
# make
make -C src all CROSS=riscv32-embedded-elf CCPATH=/usr/local/share/gcc-riscv32-embedded-elf/bin/ ARCH=rv32e HARVARD=1
make[1]: Entering directory `/home/marcelo/Documents/Verilog/darkriscv/v38/src'
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-gcc -Wall -I./include -Os -march=rv32e -mabi=ilp32e -D__RISCV__ -DBUILD="\"Sat, 30 May 2020 00:55:20 -0300\"" -DARCH="\"rv32e\"" -S boot.c -o boot.s
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-as -march=rv32e -c boot.s -o boot.o
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-gcc -Wall -I./include -Os -march=rv32e -mabi=ilp32e -D__RISCV__ -DBUILD="\"Sat, 30 May 2020 00:55:20 -0300\"" -DARCH="\"rv32e\"" -S stdio.c -o stdio.s
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-as -march=rv32e -c stdio.s -o stdio.o
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-gcc -Wall -I./include -Os -march=rv32e -mabi=ilp32e -D__RISCV__ -DBUILD="\"Sat, 30 May 2020 00:55:21 -0300\"" -DARCH="\"rv32e\"" -S main.c -o main.s
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-as -march=rv32e -c main.s -o main.o
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-gcc -Wall -I./include -Os -march=rv32e -mabi=ilp32e -D__RISCV__ -DBUILD="\"Sat, 30 May 2020 00:55:21 -0300\"" -DARCH="\"rv32e\"" -S io.c -o io.s
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-as -march=rv32e -c io.s -o io.o
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-gcc -Wall -I./include -Os -march=rv32e -mabi=ilp32e -D__RISCV__ -DBUILD="\"Sat, 30 May 2020 00:55:21 -0300\"" -DARCH="\"rv32e\"" -S banner.c -o banner.s
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-as -march=rv32e -c banner.s -o banner.o
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-cpp -P -DHARVARD=1 darksocv.ld.src darksocv.ld
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-ld -Tdarksocv.ld -Map=darksocv.map -m elf32lriscv boot.o stdio.o main.o io.o banner.o -o darksocv.o
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-ld: warning: section `.data' type changed to PROGBITS
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-objdump -d darksocv.o > darksocv.lst
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-objcopy -O binary darksocv.o darksocv.text --only-section .text*
hexdump -ve '1/4 "%08x\n"' darksocv.text > darksocv.rom.mem
#xxd -p -c 4 -g 4 darksocv.o > darksocv.rom.mem
rm darksocv.text
wc -l darksocv.rom.mem
1016 darksocv.rom.mem
echo rom ok.
rom ok.
/usr/local/share/gcc-riscv32-embedded-elf/bin//riscv32-embedded-elf-objcopy -O binary darksocv.o darksocv.data --only-section .*data*
hexdump -ve '1/4 "%08x\n"' darksocv.data > darksocv.ram.mem
#xxd -p -c 4 -g 4 darksocv.o > darksocv.ram.mem
rm darksocv.data
wc -l darksocv.ram.mem
317 darksocv.ram.mem
echo ram ok.
ram ok.
echo sources ok.
sources ok.
make[1]: Leaving directory `/home/marcelo/Documents/Verilog/darkriscv/v38/src'
make -C sim all ICARUS=/usr/local/bin/iverilog HARVARD=1
make[1]: Entering directory `/home/marcelo/Documents/Verilog/darkriscv/v38/sim'
/usr/local/bin/iverilog -I ../rtl -o darksocv darksimv.v ../rtl/darksocv.v ../rtl/darkuart.v ../rtl/darkriscv.v
./darksocv
WARNING: ../rtl/darksocv.v:280: $readmemh(../src/darksocv.rom.mem): Not enough words in the file for the requested range [0:1023].
WARNING: ../rtl/darksocv.v:281: $readmemh(../src/darksocv.ram.mem): Not enough words in the file for the requested range [0:1023].
VCD info: dumpfile darksocv.vcd opened for output.
reset (startup)
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
vvvvvvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvv
rr vvvvvvvvvvvvvvvvvvvvvv
rr vvvvvvvvvvvvvvvvvvvvvvvv rr
rrrr vvvvvvvvvvvvvvvvvvvvvvvvvv rrrr
rrrrrr vvvvvvvvvvvvvvvvvvvvvv rrrrrr
rrrrrrrr vvvvvvvvvvvvvvvvvv rrrrrrrr
rrrrrrrrrr vvvvvvvvvvvvvv rrrrrrrrrr
rrrrrrrrrrrr vvvvvvvvvv rrrrrrrrrrrr
rrrrrrrrrrrrrr vvvvvv rrrrrrrrrrrrrr
rrrrrrrrrrrrrrrr vv rrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrrrr
INSTRUCTION SETS WANT TO BE FREE
boot0: text@0 data@4096 stack@8192
board: simulation only (id=0)
build: darkriscv fw build Sat, 30 May 2020 00:55:21 -0300
core0: darkriscv@100.0MHz with rv32e+MT+MAC
uart0: 115200 bps (div=868)
timr0: periodic timer=1000000Hz (io.timer=99)
Welcome to DarkRISCV!
> no UART input, finishing simulation...
echo simulation ok.
simulation ok.
make[1]: Leaving directory `/home/marcelo/Documents/Verilog/darkriscv/v38/sim'
make -C boards all BOARD=piswords_rs485_lx9 HARVARD=1
make[1]: Entering directory `/home/marcelo/Documents/Verilog/darkriscv/v38/boards'
cd ../tmp && xst -intstyle ise -ifn ../boards/piswords_rs485_lx9/darksocv.xst -ofn ../tmp/darksocv.syr
Reading design: ../boards/piswords_rs485_lx9/darksocv.prj
*** lots of weird FPGA related messages here ***
cd ../tmp && bitgen -intstyle ise -f ../boards/avnet_microboard_lx9/darksocv.ut ../tmp/darksocv.ncd
echo done.
done.
这意味着软件已经正确编译并且链接成功,模拟运行正确,并且FPGA构建产生了一个可以在你的FPGA开发板上通过执行make install加载的映像(假设你有一个FPGA开发板,并且,当然,你在开发板目录中有JTAG支持脚本)。
如果FPGA编程正确,并且UART连接到了终端仿真器,FPGA将配置为DarkRISCV,它将运行测试软件并产生以下结果:
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
vvvvvvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvv
rr vvvvvvvvvvvvvvvvvvvvvv
rr vvvvvvvvvvvvvvvvvvvvvvvv rr
rrrr vvvvvvvvvvvvvvvvvvvvvvvvvv rrrr
rrrrrr vvvvvvvvvvvvvvvvvvvvvv rrrrrr
rrrrrrrr vvvvvvvvvvvvvvvvvv rrrrrrrr
rrrrrrrrrr vvvvvvvvvvvvvv rrrrrrrrrr
rrrrrrrrrrrr vvvvvvvvvv rrrrrrrrrrrr
rrrrrrrrrrrrrr vvvvvv rrrrrrrrrrrrrr
rrrrrrrrrrrrrrrr vv rrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrrrr
INSTRUCTION SETS WANT TO BE FREE
boot0: text@0 data@4096 stack@8192
board: piswords rs485 lx9 (id=6)
build: darkriscv fw build Fri, 29 May 2020 23:56:39 -0300
core0: darkriscv@100.0MHz with rv32e+MT+MAC
uart0: 115200 bps (div=868)
timr0: periodic timer=1000000Hz (io.timer=99)
Welcome to DarkRISCV!
>
美丽的ASCII RISCV标志是由Andrew Waterman制作的![6]
只要构建成功,就可以开始进行更改,但我在使用软处理器工作时的建议是不要同时在硬件和软件上工作!这意味着最好先冻结硬件,只与软件工作,或者冻结软件,只与硬件工作。完全可以在两者上进行研究,但不要同时进行,否则在软硬件更改后,你会发现DarkRISCV处于非工作状态,而且你无法确定问题所在。
"src" 目录
src目录包含了测试固件的源代码,包括启动代码、主程序和辅助库。代码通过gcc编译,以生成一些辅助文件,例如:
- boot.c:启动过程的原始C代码
- boot.s:由gcc自动生成的C代码的汇编版本
- boot.o:由gcc自动生成的C代码的编译版本
当所有.o文件生成后,结果会被链接到一个darksocv.o ELF文件中,该文件用于生成darksocv.bin文件,该文件被转换为十六进制并分离到ROM和RAM文件中(由Verilog代码在blockRAMs中加载)。链接器还生成了一个包含生成代码完整列表的darksocv.lst,以及显示生成代码中所有函数和变量映射的darsocv.map。
固件的概念非常简单:
- boot.c包含启动代码
- main.c包含主应用程序代码(shell)
- banner.c包含riscv横幅
- stdio.c包含一个小版本的stdio
- io.c包含IO接口
可以通过编辑src/Makefile轻松添加额外的代码到编译中。
例如,为了添加一个Lempel-Ziv代码lz.c,需要让Makefile知道我们需要lz.s和lz.o:
OBJS = boot.o stdio.o main.o io.o banner.o lz.o
ASMS = boot.s stdio.s main.s io.s banner.s lz.s
SRCS = boot.c stdio.c main.c io.c banner.c lz.c
并在main.c中添加一个"lz"命令,这样就可以通过提示符调用该函数。或者,可以完全替换提供的固件,使用你自己的固件。
"sim" 目录
另一方面,仿真将显示一些波形,并可能在运行示例代码时检查DarkRISCV的操作。
DarkRISCV的主要仿真工具是Xilinx ISE 14.7的iSIM,但也支持通过sim目录中的Makefile中的Icarus模拟器(当检测到ICARUS符号时,关于Icarus的更改将激活)。我还包含了一个针对ModelSim的变通方案,正如我们的朋友HYF所指出的(当检测到MODEL_TECH符号时,关于ModelSim的更改将激活)。
仿真运行的固件与真实FPGA中的固件相同,但为了提高仿真性能,UART代码没有被模拟,因为115200bps需要大量的死亡仿真时间。
"rtl" 目录
"rtl" 目录包含了 DarkRISCV 核心以及一些辅助文件,例如 DarkSoCV(一个带有 ROM、RAM 和 IO 的小型片上系统)、DarkUART(一个用于调试的小型 UART)以及配置文件,其中可以启用和禁用在实现说明部分描述的一些特性。
"board" 目录
当前支持的板子有:
- id==0 仅模拟
- id==1 avnet_microboard_lx9
- id==2 xilinx_ac701_a200
- id==3 qmtech_sdram_lx16
- id==4 qmtech_spartan7_s15
- id==5 lattice_brevia2_lxp2
- id==6 piswords_rs485_lx9
- id==7 digilent_spartan3_s200
- id==8 aliexpress_hpc40gbe_k420
- id==9 qmtech_artix7_a35
- id==10 aliexpress_hpc40gbe_ku040
- id==11 papilio_duo_logicstart
- id==12 qmtech_kintex7_k325
- id==13 scarab_minispartan6-plus_lx9
- id==14 colorlighti9_ecp5-45f
- id==15 colorlighti5_ecp5-25f
- id==16 ulx3s_ecp5-85f
组织结构自我解释,目录名称中包含了厂商、板子和 FPGA 型号。每个 board 目录包含了在 Xilinx ISE 14.x 中打开的项目文件,以及构建针对该板子型号的 FPGA 镜像的 Makefile。尽管提供了一个 ucf 文件以生成包含 UART 和一些 LED 的完整构建,但 FPGA 并没有在任何特定配置中完全布线,您必须添加您将在您的 FPGA 板上使用的引脚。
无论如何,即使没有布线,构建总是可以为您提供关于 FPGA 利用率和时序(因为 UART 输出确保了完整的处理器必须在合成时确定)的良好估计。
由于支持的板子很多,没有办法每次都测试所有的板子,这意味着针对一个板子的更改可能会以错误的方式影响其他板子。
实现说明
[*本节保留供参考,但描述可能与当前代码不完全匹配]
由于我的目标是面向超低成本的 Xilinx Spartan-6 系列 FPGA,该项目目前基于 Linux 的 Xilinx ISE 14.7,这是最新可用的 ISE 版本。然而,没有明确引用 Xilinx 元素,所有的逻辑都是直接从 Verilog 推断出来的,这意味着该项目很容易移植到其他 FPGA 系列,也很容易移植到其他环境中,正如在 Lattice XP2 支持的案例中所观察到的。无论如何,请记住,某些 Verilog 结构可能在一些 FPGA 中工作不佳。
在上次更新中,我包含了一种在 x86 主机上测试固件的方法,这非常有帮助,因为可以与固件交互并快速修复一些明显的错误。当然,x86 代码不会运行 boot.c 代码,因为在 x86 上运行 RISCV 启动代码没有意义。
无论如何,当使用软核工作时,主要的建议是永远不要同时在硬件和软件上工作!从可能的最小软件配置开始,并冻结软件。当实现新的软件更新时,使用可能的最小硬件配置,并冻结硬件。
RV32I 规范本身非常令人印象深刻,易于实现(见 [1],第 16 页)。当然,有一些缺点,例如有趣的小端总线(与在 680x0 系列中发现的面向网络的大端总线相反),但经过一些实证测试,很容易让它工作。
这里有趣的信息是,在对 DarkRISCV 添加大端支持进行大量研究后,我找不到让 GCC 正确生成代码和数据的方法。
规范中的另一个缺点是缺乏延迟分支。尽管我理解从概念上讲它们是不好的,但它们是提高性能的好技巧。作为参考,DarkRISCV 中缺乏延迟分支或分支预测可能会降低 20% 到 30% 的性能,以至于实际测量的性能可能在每个指令 1.25 到 1.66 个时钟之间。
虽然分支预测实现起来并不复杂,但我发现实验性的多线程支持更有趣,因为可以利用分支中的空闲时间来切换处理器线程。无论如何,我将尝试调试分支预测代码以提高单线程性能。
核心支持 2 或 3 级流水线,尽管主要逻辑几乎相同,但它们的工作方式有很大的不同。仅供参考,以下部分反映了核心的历史演变,可能不反映当前的内核代码。
原始的 2 级流水线设计有一个关于 ROM 和 RAM 时序的小问题,即为了在两个时钟内预取和执行指令,并保持预取持续以每时钟 1 条指令的速率工作(在执行中也是如此),ROM 和 RAM 必须在下一个时钟之前响应。这意味着存储器必须是组合的,或者至少使用 2 相时钟。
对于 2 级流水线版本的第一种解决方案是默认解决方案,它使 DarkRISCV 作为一个伪 4 级流水线工作:
- 1/2 阶段用于指令预取(rom)
- 1/2 阶段用于静态指令解码(core)
- 1/2 阶段用于地址生成、寄存器读取和数据读写(ram)
- 1/2 阶段用于数据写入(寄存器写入)
从处理器的角度来看,只有 2 个阶段,从存储器的角度来看,也只有 2 个阶段。但它们处于不同的时钟相位。在正常情况下,这是不推荐的,因为降低了 2 倍的性能,但在 DarkRISCV 的情况下,性能总是受到有关指令执行的组合逻辑的限制。
第二种 2 级流水线解决方案是使用组合逻辑以便在下一个时钟边缘之前提供所需的结果,这样就有可能使用单相时钟。这个解决方案由指令和数据高速缓存组成,这样当操作数存储在基于 LUT 的组合高速缓存中时,处理器可以在没有额外等待状态的情况下执行存储器操作。然而,当操作数没有存储在缓存中时,为了从块存储器或外部存储器中获取操作数,会插入额外的等待状态。根据一些初步测试,具有 64 条直接映射指令的指令高速缓存可以达到 91% 的命中率。数据高速缓存的性能虽然不是很好(命中率只有 68%),但为了访问外部存储器并减少慢速 SDRAM 和 FLASH 的影响,这将是一个要求。
不幸的是,指令和数据高速缓存在 2 级流水线版本中不再工作,只有指令高速缓存在 3 级流水线中工作。问题可能与 HLT 信号和/或缓存存储器中的写字节使能有关。
使用高速缓存和 2 相时钟的表现都不是很好。这样,提供了一个 3 级流水线版本,以便使用单相时钟和块存储器。
在这种情况下,概念是将预取和解码分开,以便预取可以完全在块存储器端为指令总线完成。在不同的阶段进行解码,提供了额外的性能,而执行阶段几乎一直在一个时钟内工作,除了执行加载指令时。在这种情况下,外部存储器逻辑插入一个等待状态。然而,写操作在单个时钟内执行。
可以使用等待状态的解决方案在 2 级流水线版本中,但这会过多地降低性能。可以以相同的时钟运行所有版本,理论上每条指令的时钟数 (CPI)、清除流水线的分支所需的时钟数 (FLUSH) 和存储器等待状态 (WSMEM) 将是:
- 2 级管 w/ 2 相时钟:CPI=1,FLUSH=1,WSMEM=0:实际 CPI=~1.25
- 3 级管 w/ 1 相时钟:CPI=1,FLUSH=2,WSMEM=1:实际 CPI=~1.66
- 2 级管 w/ 1 相时钟:CPI=2,FLUSH=1,WSMEM=1,实际 CPI=~2.00
从经验上看,2 级流水线中 FLUSH 的影响约为 20%,在 3 级流水线中为 30%。实际影响取决于代码本身……在存储器访问中加载指令的等待状态的影响在 5% 到 10% 之间,同样,取决于代码。
然而,3 级流水线的时钟远好于 2 级流水线,特别是因为逻辑在解码和执行阶段之间的更好分布。
目前,在 Spartan-6 中,最昂贵的路径是核心数据侧的地址总线(连接到 RAM 和外设)。问题在于以下操作必须在单个时钟内完成:
- 生成 DADDR[31:0] = REG[SPTR][31:0]+EXTSIG(IMM[11:0])
- 根据操作数大小和 DADDR[1:0] 生成 BE[3:0]
在读取操作中,DATAI 路径还包括一个小型多路复用器,以便分离 RAM 和外设总线,以及分离不同的外设,这意味着随着外设数量和复杂性的增加,路径会增加。
当然,最佳性能设置使用 3 级流水线和整个逻辑中的单时钟相位(上升沿),这样 2 级流水线和双时钟相位将仅作为参考保留。
3 级流水线唯一的缺点是加载操作中多出一个等待状态,以及在采取分支时的流水线冲洗需要两个时钟。
仅作为参考,我记录了一些关于性能测量的细节:
当前固件示例在 3 级流水线版本中以 100MHz 时钟运行,经过验证的性能为 62 MIPS。理论 100MIPS 性能因加载指令中的额外等待状态而未达到 5%,因采取分支后的流水线冲洗而未达到 32%。另一方面,2 级流水线版本在相同时钟下运行,在经过验证的性能为 79MIPS。唯一的损失是由于采取分支后的流水线冲洗造成的 20%。
当然,流水线冲洗的影响也取决于软件,因为软件目前针对大小进行了优化。当使用 -O2 而不是 -Os 编译时,性能提高到 3 级流水线的 68MIPS,损失变为加载 6% 和流水线冲洗 25%。-O3 选项的结果是 67MIPS,最佳结果是 -O1 选项,在 3 级版本中产生了 70MIPS,在 2 级版本中产生了 85MIPS。
因此,如果性能是一个要求,src/Makefile 必须更改为使用 -O1 优化而不是默认的 -Os。
尽管 2 级版本比 3 级版本快 15%,但 3 级版本可以达到更好的时钟,因此将提供更好的性能。
关于流水线冲洗,由于 RISCV 不支持延迟分支,所以在采取分支后是必需的。解决这个问题的方法是实现一个分支缓存(分支预测器),这样核心就可以用最后的分支填充一个缓存,并可以预测未来的分支。在一些初始测试中,具有 4 个条目的分支预测器似乎达到了 60% 的命中率。
另一种可能性是利用冲洗时间来执行其他任务,例如处理中断。由于中断处理以及通常的线程需要清空当前的流水线以更改上下文,因此将中断/线程与流水线冲洗相匹配是有意义的!
使用 THREADING 选项可以测试此功能。
实现处于非常早期的开发阶段,不能正确处理初始 SP 和 PC。无论如何,它有效,并允许 main() 代码在 gets() 中停止,而中断处理以每秒超过 100 万个中断的速率更改 GPIO,而不影响执行,并且对性能的影响很小! :)
中断支持可以扩展到更完整的线程支持,但这需要在硬件和软件上进行一些技巧,以便用正确的 SP 和 PC 填充不同的线程。
中断处理使用围绕线程的概念,并且通过一些额外的努力,可能可以支持 4、8 或甚至 16 个线程。在这种情况下的缺点是寄存器库的大小增加,这解释了为什么 rv32e 是线程的有趣选项:有一半数量的寄存器,可以在核心中存储两个以上的线程。
目前,在 darkriscv 中切换上下文的时间是 3 级流水线中的两个时钟,这与流水线冲洗本身相匹配。在 100MHz 时,每秒的最大经验上下文切换次数约为 294 万。
注意:中断控制器目前仅与 gcc 的 -Os 标志一起工作!
关于新的 MAC 指令,它以非常初步的方式实现,使用 OPCDE 7'b1111111(这有效,但是一个非常糟糕的决定!)。我正在检查是否有可能使用 p.mac 指令,但目前该指令在 stdio.c 中的 mac() 函数中手动编码(即 darkriscv libc)。关于包含新指令并使其与 GCC 一起工作的详细信息可以在参考 [5] 中找到。
初步测试指出,正如预期的那样,性能下降到 90MHz,尽管有可能以非零时序分数在 100MHz 下运行并达到 100MMAC/s 的峰值性能,但小的 32 位累加器饱和得太快,需要额外的技巧以避免溢出。
乘法操作使用两个 16 位整数,结果与单独的 32 位寄存器相加,该寄存器作为累加器。由于操作始终是有符号的,并且信号始终使用 MSB 位,这意味着 15x15 乘法产生一个 30 位结果,该结果被加到一个 31 位值中,这意味着在仅两次 MAC 操作后就会发生溢出。
为了避免溢出,可以移位输入操作数。例如,在 G711 使用 u-law 编码的情况下,有效分辨率为 14 位(13 位整数和 1 位信号),这意味着将使用 13x13 位乘法并产生一个 26 位结果,将其加到一个 31 位整数中,足以在溢出之前运行 32xMAC 操作(在这种情况下,当 ACC 达到负值时):
# awk 'BEGIN { ACC=2**31-1; A=2**13-1; B=-A; for(i=0;ACC>=0;i++) print i,A,B,A*B,ACC+=A*B }'
0 8191 -8191 -67092481 2080391166
1 8191 -8191 -67092481 2013298685
2 8191 -8191 -67092481 1946206204
...
30 8191 -8191 -67092481 67616736
31 8191 -8191 -67092481 524255
32 8191 -8191 -67092481 -66568226
这个理论正确吗?我不确定,但看起来很好! :)
作为补充,在 stdio.c 中包含了对 GCC 函数的支持,这些函数涉及 32 位有符号和无符号整数的原生 *、/ 和 %(乘法、除法和模)操作,这意味着真正的 32x32 位操作产生 32 位结果。该代码源自一个旧的 68000 相关项目(就像 stdio.c 中的大多数代码),虽然不是很快,但我想它是工作的。随着 MAC 指令在语法和特性方面更好地定义,我认为可以优化乘法/除法/模以尝试使用它并提高性能。
这里有一些其他性能结果(仅综合,3 级版本)针对 ISE 中可用的其他 Xilinx 设备的速度等级 2:
- Spartan-6:100MHz(使用 gcc -O1 测量为 70MIPS)
- Artix-7:178MHz
- Kintex-7:225MHz
对于速度等级 3:
- Spartan-6:117MHz
- Artix-7:202MHz
- Kintex-7:266MHz
Kintex-7 理论上可以达到 gcc -O1 的 186MIPS。
这个性能是在没有激活 MAC 和 THREADING 的情况下达到的。多亏了 RV32E 选项,现在可以在 Spartan-3E 上进行综合,结果在低成本 100E 型号的情况下 LUT 占用率为 95%,时钟为 70MHz(仅综合和速度等级 5):
- Spartan-3E:70MHz
对于 2 级版本和速度等级 2,我们受到流水线冲洗的影响较小(20%),加载没有影响,并且由于使用 2 相时钟,时钟有一些影响:
- Spartan-6:56MHz(使用 -O1 测量为 47MIPS)
关于编译器性能,从启动到提示符,使用 3 级流水线核心在 100MHz 和没有中断的情况下,ROM 和 RAM 以 32 位字测量:
- gcc w/ -O3:t=289us ROM=876 RAM=211
- gcc w/ -O2:t=291us ROM=799 RAM=211
- gcc w/ -O1:t=324us ROM=660 RAM=211
- gcc w/ -O0:t=569us ROM=886 RAM=211
- gcc w/ -Os:t=398us ROM=555 RAM=211
由于 FPGA 中的 ROM 空间减少,-Os 是默认选项。
另一方面,关于 Vivado 的支持,可以将 Artix-7(Xilinx AC701 在 ise/boards 目录中可用)项目转换为 Vivado 并进行一些有趣的测试。转换中的唯一问题是 UCF 文件没有转换,这意味着必须创建一个新的 XDC 文件来描述引脚。
与 ISE 相比,Vivado 非常慢,并且需要大量的时间来进行综合并提供有关性能的最少反馈...... 但是经过几周的等待和大量的经验计算,我得到了速度等级 2 设备的一些数字:
- Artix7:147MHz
- Spartan-7:146MHz
和速度等级 3 设备的一个数字:
- Kintex-7:221MHz
尽管 Vivado 比 ISE 慢得多,并且与 ISE 相比对相同的 FPGA 显示出悲观的数字,我猜 Vivado 更现实,至少它支持新的 Spartan-7,它显示出非常好的数字(几乎和 Artix-7 一样!)。
这些值仅供参考。真实值取决于核心中的一些选项,例如流水线阶段的数量,存储器的连接方式等。基本上,最佳时钟由 3 级流水线版本达到(在 Spartan-6 中高达 100MHz),但它需要在加载指令中至少等待一个状态,并在采取分支时额外的两个时钟才能冲洗流水线。2 级流水线不需要额外的等待状态,只需要在采取分支时额外的一个时钟,但运行性能较低(56MHz)。
嗯,经过几年的研究,我的结论是分支预测解决了很多关于性能的问题。
开发工具
关于 gcc 编译器,我正在使用 RISC-V 的实验性 gcc 9.0.0。除了 -march=rv32i 之外,DarkRISCV 不需要任何补丁或更新。尽管 fence*、e* 和 crg* 指令没有实现,但 gcc 似乎没有使用这些指令,它们也不在核心中可用。
尽管可以使用官方 RISC-V 网站上提供的编译器集,我们的同事来自 lowRISC 项目指出了一种更聪明的构建工具链的方法:
基本上:
git clone --depth=1 git://gcc.gnu.org/git/gcc.git gcc
git clone --depth=1 git://sourceware.org/git/binutils-gdb.git
git clone --depth=1 git://sourceware.org/git/newlib-cygwin.git
mkdir combined
cd combined
ln -s ../newlib-cygwin/* .
ln -sf ../binutils-gdb/* .
ln -sf ../gcc/* .
mkdir build
cd build
../configure --target=riscv32-unknown-elf --enable-languages=c --disable-shared --disable-threads --disable-multilib --disable-gdb --disable-libssp --with-newlib --with-arch=rv32ima --with-abi=ilp32 --prefix=/usr/local/share/gcc-riscv32-unknown-elf
make -j4
make
make install
export PATH=$PATH:/usr/local/share/gcc-riscv32-unknown-elf/bin/
riscv32-unknown-elf-gcc -v
一切都会神奇地工作! :)
如果您没有成功构建编译器,对更改固件没有兴趣,或者只是对在 FPGA 上运行的 darkriscv 感到好奇,该项目包括编译好的 ROM 和 RAM,这样您可以检查所有派生的对象、源代码和编译器生成的相关文件,而无需编译任何东西。
最后,由于 DarkRISCV 还没有完全测试,有时与另一个稳定的参考进行代码执行比较是一个非常好的想法!
在这种情况下,我正在使用 picorv32 项目:
当我有时间时,我将尝试创建一个更有组织的support,以便轻松地在相同的缓存、内存和 IO 子系统中测试 DarkRISCV 和 picorv32,以便根据所需的功能选择核心,例如,使用 DarkRISCV 获得更多性能或使用 picorv32 获得更多功能。
关于软件,最复杂的问题是使内存设计与链接器布局相匹配。当然,这是一个 gcc 问题,甚至不是一个问题,事实上,这是软件人员在链接代码和数据时的工作方式!
在最简化的版本中,直接连接到 blockRAMs,DarkRISCV 是一个纯哈佛架构处理器,将需要将指令和数据块分开!
当激活缓存控制器时,缓存控制器为指令和数据提供单独的存储器,但提供了一个接口,用于更传统的冯诺依曼内存架构。
在这两种情况下,一个适当设计的链接器脚本(darksocv.ld)可能解决了问题!
当前链接器脚本中的内存映射如下:
- 0x00000000:4KB ROM
- 0x00001000:4KB RAM
此外,链接器将 IO 映射到以下位置:
- 0x80000000:UART 状态
- 0x80000004:UART 发送/接收缓冲区
- 0x80000008:LED 缓冲区
RAM 存储器包含 .data 区域、.bss 区域(在 .data 之后并用零初始化)、.rodada 以及 RAM 末尾的堆栈区域。
尽管 RISCV 被定义为 little-endian,但在 GCC 中更改配置似乎很容易。在这种情况下,假设所有变量都以 big-endian 格式存储。当然,更改需要核心本身的类似更改,这并不复杂,只要它只影响加载和存储指令。将来,我会尝试测试 GCC 和 darkriscv 的 big-endian 版本,以评估网络导向应用程序可能的性能提升! :)
最后,关于软件的最后一次更新包括了一个新的选项,可以构建一个 x86 版本,以帮助通过在 x86 上测试完全相同的固件来促进开发。
从初步的方式来看,可以使用以下配置构建 RV32E 的 gcc:
git clone --depth=1 git://gcc.gnu.org/git/gcc.git gcc
git clone --depth=1 git://sourceware.org/git/binutils-gdb.git
git clone --depth=1 git://sourceware.org/git/newlib-cygwin.git
mkdir combined
cd combined
ln -s ../newlib-cygwin/* .
ln -sf ../binutils-gdb/* .
ln -sf ../gcc/* .
mkdir build
cd build
../configure --target=riscv32-embedded-elf --enable-languages=c --disable-shared --disable-threads --disable-multilib --disable-gdb --disable-libssp --with-newlib --with-arch-rv32e --with-abi=ilp32e --prefix=/usr/local/share/gcc-riscv32-embedded-elf
make -j4
make
make install
export PATH=$PATH:/usr/local/share/gcc-riscv32-embedded-elf/bin/
riscv32-embedded-elf-gcc -v
目前,我没有找到让 GCC 为 RISCV 构建 big-endian 代码的简单方法。相反,简单的方法是直接在 IO 设备或内存区域中切换 endian。
由于在某些机器上构建 GCC 不那么容易,我将 GCC 工具的源代码和预编译的二进制集放在了一个公共共享中:
据我所记,它是在 Slackware Linux 或类似系统中编译的,反正在 Windows 10 w/ WSL 和其他类似 Linux 的环境中运行良好。
开发板
目前,支持以下板子:
- Avnet Microboard LX9:配备 100MHz 的 Xilinx Spartan-6 LX9
- XilinX AC701 A200:配备 90MHz 的 Xilinx Artix-7 A200
- QMTech SDRAM LX16:配备 100MHz 的 Xilinx Spartan-6 LX16
- QMTech NORAM S15:配备 100MHz 的 Xilinx Spartan-7 S15
- Lattice Brevia2 XP2:配备 50MHz 的 Lattice XP2-6
- Piswords RS485 LX9:配备 100MHz 的 Xilinx Spartan-6 LX9
- Digilent S3 Starter Board:配备 50MHz 的 Xilinx Spartan-3 S200
速度与板子中可用的时钟有关,不同的时钟可以通过编程时钟生成器生成。Spartan-6 出现在大多数板子中,核心在 ~100MHz 运行良好,无论主振荡器的频率(通常为 50MHz)如何。
所有基于 Xilinx 的板子通常支持 115200bps UART 控制台,一些用于调试的 LED 以及片上 4KB ROM 和 4KB RAM(以及 RESET 按钮以重新启动核心和用于示波器的 DEBUG 信号)。
在 QMTECH 板子的情况下,它不包括 JTAG 也不包括 UART/USB 端口,外部 USB/UART 转换器和低成本 JTAG 适配器可以轻松解决问题!
Lattice Brevia 由板载 50MHz 振荡器驱动,UART 运行在 115200bps,LED 和 DEBUG 端口连接到板载 LED。
虽然 Digilent Spartan-3 Starter Board,这是一个非常有用的板子,可以作为 LUT4 技术的参考,这样将来可以改进对其他低成本 LUT4 FPGA 的支持。
在软件方面,有一个小型 shell 可用,其中包含一些基本命令:
- clear:清除显示
- dump :转储 RAM 的一个区域
- led :更改 LED 寄存器(打开/关闭 LED)
- timer :更改定时器预分频器,这会影响中断率
- gpio :更改 GPIO 寄存器(更改 DEBUG 线路)
shell 的建议是提供一些基本的测试功能,可以提供有关当前硬件状态的 go/non-go 状态。
有用的内存区域:
- 4096:RAM 的开始(数据)
- 4608:RAM 的开始(数据)
- 5120:空白区域
- 5632:空白区域
- 6144:空白区域
- 6656:空白区域
- 7168:空白区域
- 7680:RAM 的末尾(堆栈)
由于 DarkRISCV 使用单独的指令和数据总线,无法转储 ROM 区域。然而,当激活 HARVARD 选项时,这种限制不存在,因为核心是以这种方式构建的:ROM 总线连接到双端口存储器的一个总线上,而 RAM 总线连接到同一双端口存储器的不同总线上。从 DarkRISCV 的角度来看,它们是完全分离和独立的总线,但实际上它们在相同的存储器区域,这使得数据总线可以更改存储代码的区域。有了这个功能,将来将可能从 FLASH 存储器创建可加载的代码! :)
FuseSoC 支持
去年圣诞节(2022)我们的同事 Lucas Teske 在 darkriscv 中添加了 FuseSoC 支持...... 我不太清楚它的工作原理以及如何使用它,但据说它可以自动处理构建工具!它是由 SERV 使用的相同工具,我过去用它添加了一些 kilocore 记录...... 要在 darkriscv 中使其工作,请尝试:
- fusesoc run --target=qmtech_artix7_a35 darklife:darkriscv:darksocv
目前,并非所有板子都真正得到支持。支持的板子有:
- Colorlight i9
- Colorlight i5
- Lattice iCE40 Devkit
- QMtech Artix 7 (Vivado)
从头开始创建 RISCV
我发现有些人对在一夜之间设计 RISC-V 处理器的可能性非常犹豫。当然,这并不像看起来那么容易,实际上,它需要很多经验、计划和运气。此外,处理器正确运行一些指令并在串行端口上放置一些垃圾并不真正意味着设计是完美的,相反,您将需要大量的调试时间来解决所有隐藏的问题。
以防万一,我发现了我的朋友(Lucas Teske)的一组在线视频,展示了从头开始设计 RISC-V 处理器的过程(包含 9 个视频的播放列表):
或者,twitch 上有原始视频:
- www.twitch.tv/videos/8409… Register bank (4h50)
- www.twitch.tv/videos/8456… Program counter and ALU (3h49)
- www.twitch.tv/videos/8467… ALU tests, CPU top level (3h47)
- www.twitch.tv/videos/8489… Computer problems and microcode planning (08h19)
- www.twitch.tv/videos/8508… instruction decode and execute - part 1/3 (08h56)
- www.twitch.tv/videos/8520… instruction decode and execute - part 2/3 (10h56)
- www.twitch.tv/videos/8580… instruction decode and execute - part 3/3 - SoC simulation (10h24)
- TBD tests in the Lattice FPGA
- TBD tests in the Lattice FPGA w/ LCD display
不幸的是,视频集目前仅提供葡萄牙语,并且有很多关于技术的并行讨论,包括修复 Teske 的笔记本电脑在线!我希望将来能够编辑视频集,并可能创建英语字幕。
关于处理器本身,它是一个面向微代码的概念,具有经典的 von neumann 架构,旨在更容易支持不同的 ISA。它与我们周围发现的传统 RISC 核心非常不同!此外,它包括一个非常好的开源工具生态系统,如 Icarus、Yosys 和 gtkWave!
尽管还没有完成(完成度 95%!),我认为这对于 RISC-V 设计非常有启发性:
- rv32e 指令集:非常精简(37 条)和非常正交的位模式(6 条)
- rv32e 寄存器集:16x32 位寄存器库和 32 位程序计数器
- rv32e ALU 用于 reg/imm 和 reg/reg 指令的基本操作
- rv32e 指令解码:非常容易理解,非常直接实现
- rv32e 软件支持:GCC 支持提供了一种简单的方法来生成代码并测试它!
Teske 的提议不是设计有史以来最快的 RISC-V 核心(我们已经有很多更快的核心,CPI ~ 1,例如 darkriscv、vexriscv 等),而是创建一个干净、可靠和全面的 RISC-V 核心。
您可以在以下存储库中查看代码:
学术论文和应用
以一种有趣的方式,DarkRISCV 出现在一些学术论文中,有时是进行比较,有时是作为实验室的小白鼠。
-
基于 RISC-V 的 256 位动态调度超长指令字 FPGA 设计与实现 -- 在这里我们发现了一个有趣的比较,比较了 DarkRISCV 与一个巨大的 8 路 VLIW 核心,以及 Kronos RISCV、PicoRV32 和 NEORV32。DarkRISCV 的好结果:IPC 0.71 的第二名和仅 1500LUTs 的第一名。 ieeexplore.ieee.org/iel7/628763…
-
ReCon:从比特流到盗版检测 -- 关于 IP 盗版检测的有趣论文,基本上是如何检测比特流内的 IP,他们使用 PicoRV32、OpenRISC 和 DarkRISCV 作为要检测的 IP。 homes.luddy.indiana.edu/lukefahr/pa…
-
用于空间系统的低成本容错 RISC-V 处理器 -- 在这里我们发现了一个有趣的比较,比较了低成本 RISCV 核心,但在这个案例中,DarkRISCV 与 PicoRV32、mRISCV、Ibex 和一个辐射加固的 RISCV 核心相比表现非常差。不确定工具和目标是什么,因为我没有访问论文,只是一些图片。 www.semanticscholar.org/paper/A-Low…
-
微处理器的故障分类和脆弱性分析 -- 没有太多信息,因为论文将在 2022 年发布,但摘要非常有趣,基本上是他们在 PicoRV32 和 DarkRISCV 中注入了大量故障,以看看会发生什么。 repository.tudelft.nl/islandora/o…
关于现实世界的应用,标准的嵌入式 C 代码通常可以在 DarkRISCV 上运行得很好。一些目前正在使用的应用程序示例:
- 微控制器编程器(JTAG 和其他复杂协议)
- 数据压缩/解压缩(LZ 流)
- 加密(RSA 和 SHA256,需要硬件加速器)
- 数字信号处理(需要 mac 指令)
在 RSA 的情况下,通过 IO(即不是 M 扩展,而是通过加载/存储指令访问的 IO 映射寄存器)简单地包含一个流水线化的 32x32 位乘法器,将 RSA 性能提高了 20 倍,与裸 RV32E 指令集相比。在 SHA256 的情况下,一个完整的 SHA256 加速器在硬件中并通过 IO 映射,可以将几秒钟的处理时间变为几毫秒。在这两种情况下,不需要在核心中集成复杂的指令,不需要等待 -- 核心和加速器可以并行工作。
性能比较
我尝试准备一个公平的性能比较,比较 DarkRISCV 和不同的 FPGA,但这并不像看起来那么容易!第一个问题是定位每个核心的 HDL 版本,因为工具需要 Verilog 或 VHDL 文件才能构建一些东西。第二个问题是决定编译什么:有许多不同的顶层块组合,有不同的外围设备和概念。在这种情况下,我只包括了核心。第三个问题涉及到核心配置:不容易或不清楚如何将它们配置为最小面积或最大速度,所以我对所有核心都使用了默认配置。简而言之,我以一种愚蠢的方式解决了问题(这反映了 95% 的现实情况)。
由于我有每个核心的单独构建,存在最终问题:如何分析结果并对核心进行排名。我的选择是计算在固定 FPGA 中可能的 MIPS 数量,在这种情况下,Kintex-7 K420 有 260600 个 6 输入的 LUTs。不同的核心将需要不同数量的 LUTs,只需将总数除以所需数量,我们就有了 FPGA 中的理论核心数量(峰值核心/FPGA)。此外,不同的核心具有不同的理论峰值 IPC。猜测的数字,并根据默认设置中的综合工具,将以不同的最大频率运行。只要我们知道每个时钟的指令数量和每秒的最大时钟数(最大频率),我们就有了每个核心每秒的最大指令数(峰值 MIPS)。只要我们也知道最大核心数量,我们就可以计算每个 FPGA 的最大峰值 MIPS。
以下是我认为比较不同核心的建议列表:
这些结果是否足够公平?从我的角度来看,只要条件相同,就是的。这些结果是否足够真实?在我看来,可能吧。至少有两个案例不匹配:DarkRISCV 在相同的 FPGA 中达到了 240MHz,当顶层在构建中时。不确定为什么核心只在较小的最大频率下得出结果,但关键是尝试找到一种比较不同核心的方法,所以没关系。第二个案例是 SERV,它显示最大频率为 367MHz,高达 1200 个核心/FPGA。实际上,我在那个 FPGA 中测试了多达 1000 个 SERV 核心,似乎有可能适合 1100 到 1200 个核心,作为多核层次结构优化的结果。坏消息是,每个核心的最大时钟几乎没有达到 128MHz。再次,关键是尝试找到一种比较不同核心的方法,所以没关系。
这些数字肯定会对未来的设计者非常有用,信息很清楚:保持简单!每个核心的面积越小,相同面积内的核心就越多,而且在某些情况下,意味着更好的性能。在大多数情况下,较小的面积也意味着更好的时钟性能,但这种情况下更好的时钟只有在 IPC 约为 1 时才有用,这并不容易保持。举个例子,DarkRISCV 可以真正达到 IPC ~ 1,但代码必须手工优化,顶层必须以没有 BRAM 延迟的方式更改。更一般地说,通过较少的努力,编译器和更标准的顶层可以保持 IPC 在 0.7 左右,这对大多数应用来说已经足够了。所以,保持你的期望非常非常低! :)
项目地址
转载自:https://juejin.cn/post/7373507761127145523