RISC-V软核的设计
——基于STM32+iCE40的电赛训练平台
Catalog
1. RISC-V简介 3
2. RISC-V特色 3
1) 完全开源 3
2) 架构简单 3
3) 易于移植*nix 3
4) 模块化设计 3
5) 完整的工具链 3
6) 开源实现 4
3. RV32I的相关基础知识 5
4. 代码编写与验证 8
1) RV32I指令分类 8
2) 寄存器规划 8
3) lowRV32I框图 9
4) lowRV32I的程序文件 10
5) lowRV32I的程序验证 11
5. 致谢 11
6. 附录 11
1) Testbench如下 11
2) TOP模块代码如下 12
RISC-V简介
RISC-V(读作“RISC-FIVE”)是基于精简指令集计算(RISC)原理建立的开放指令集架构(ISA),V表示为第五代RISC(精简指令集计算机),表示此前已经有四代RISC处理器原型芯片。每一代RISC处理器都是在同一人带领下完成,那就是加州大学伯克利分校的David A. Patterson教授。与大多数ISA相反,RISC-V ISA可以免费地用于所有希望的设备中,允许任何人设计、制造和销售RISC-V芯片和软件。图1展示了此前的四代RISC处理器原型芯片。它虽然不是第一个开源的指令集(ISA),但它很重要,因为它是第一个被设计成可以根据具体场景、可以选择适合的指令集的指令集架构。基于RISC-V指令集架构可以设计服务器CPU,家用电器cpu,工控cpu和用在比指头小的传感器中的cpu。
RISC-V特色
完全开源
对指令集使用,RISC-V基金会不收取高额的授权费。开源采用宽松的BSD协议,企业完全自由免费使用,同时也容许企业添加自有指令集拓展而不必开放共享以实现差异化发展。
架构简单
RISC-V架构秉承简单的设计哲学。体现为:在处理器领域,主流的架构为x86与ARM架构。x86与ARM架构的发展的过程也伴随了现代处理器架构技术的不断发展成熟,但作为商用的架构,为了能够保持架构的向后兼容性,其不得不保留许多过时的定义,导致其指令数目多,指令冗余严重,文档数量庞大,所以要在这些架构上开发新的操作系统或者直接开发应用门槛很高。而RISC-V架构则能完全抛弃包袱,借助计算机体系结构经过多年的发展已经成为比较成熟的技术的优势,从轻上路。RISC-V基础指令集则只有40多条,加上其他的模块化扩展指令总共几十条指令。 RISC-V的规范文档仅有145页,而“特权架构文档”的篇幅也仅为91页。
易于移植*nix
现代操作系统都做了特权级指令和用户级指令的分离,特权指令只能操作系统调用,而用户级指令才能在用户模式调用,保障操作系统的稳定。RISC-V提供了特权级指令和用户级指令,同时提供了详细的RISC-V特权级指令规范和RISC-V用户级指令规范的详细信息,使开发者能非常方便的移植linux和unix系统到RISC-V平台。
模块化设计
RISC-V架构不仅短小精悍,而且其不同的部分还能以模块化的方式组织在一起,从而试图通过一套统一的架构满足各种不同的应用场景。用户能够灵活选择不同的模块组合,来实现自己定制化设备的需要,比如针对于小面积低功耗嵌入式场景,用户可以选择RV32IC组合的指令集,仅使用Machine Mode(机器模式);而高性能应用操作系统场景则可以选择譬如RV32IMFDC的指令集,使用Machine Mode(机器模式)与User Mode(用户模式)两种模式。
完整的工具链
对于设计CPU来说,工具链是软件开发人员和cpu交互的窗口,没有工具链,对软件开发人员开发软件要求很高,甚至软件开发者无法让cpu工作起来。在cpu设计中,工具链的开发是一个需要巨大工作量的工作。如果用RISC-V来设计芯片,芯片设计公司不再担心工具链问题,只需专注于芯片设计,RISC-V社区已经提供了完整的工具链,并且RISC-V基金会持续维护该工具链。当前RISC-V的支持已经合并到主要的工具中,比如编译工具链gcc, 仿真工具qemu等
开源实现
BOOM: Christopher Celio的RV64乱序处理器实现。Chisel, BSD Licensed。[GitHub][Doc]
BottleRocket: RV32IMC微处理器。Chisel, Apache Licensed。 [GitHub]
bwitherspoon: RV32微处理器。SystemVerilog, ISC Licensed。[GitHub]
Clarvi: 剑桥大学教学用RISC-V处理器。SystemVerilog, BSD Licensed。[GitHub]
F32: 针对FPGA的RV32微处理器,VHDL,BSD Licensed。[GitHub]
GRVI: Gray Research LLC. 针对FPGA优化的RV32微处理器,commercial licensed。[Web]
Hummingbird E200. 二级流水线,目标替代Cortex-M0/8051, Verilog, Apache 2.0 licensed。[GitHub]
invicta: 一级流水线的RV32微处理器。Verilog,BSD Licensed。[GitHub]
Kamikaze: RV32微处理器。Verilog,MIT Liencensed。[GitHub]
KCP53000: Samuel A. Falvo II的RV64处理器实现。Verilog, MPL Licensed。[GitHub]
nanorv32: 2机流水线的RV32实现。Verilog, GPLv2 Licensed。[GitHub]
OpenV: 支持RV32的开源微处理器,Verilog,MIT Licensed,OnChipUIS,来源于哥伦比亚的Universidad Industrial de Santander。[GitHub]
ORCA: 支持RV32的开源微处理器,VHDL,BSD Licensed,VectorBlox。[Github]
PicoRV32: Clifford Wolf设计的(针对FPGA)RV32微处理器,Verilog,ISC Licensed。[GitHub]
Potato: 针对FPGA的RV32微处理器。VHDL,BSD Licensed。[GitHub]
RI5CY:支持RV32的开源微处理器
• PULPino: SystemVerilog,Solderpad Licensed, 来源于苏黎世理工和博洛尼亚大学的PULP项目。[GitHub][Web]
River: GNSS Senor Ltd.基于Rocket架构开发的RV64处理器。VHDL, BSD Licensed。[GitHub]
Rocket: 支持RV64/32的开源处理器
• Rocket-Chip: Chisel,BSD Licensed, Free chips project, UC Berkeley分离的开源工程。[GitHub]
• Freedom: Chisel,Apache Licensed, SiFive, UC Berkeley分离的初创企业。[GitHub][Web]
• lowRISC:Chisel+SystemVerilog,Solderpad Licensed, 从剑桥大学发起的非盈利组织。[GitHub][Web]
• RoCC: the Rocket customized coprocessor interface 和Rocket处理器紧密互联的的协处理器接口。[BSG]
RV12: RoaLogic的RV32微处理器。Verilog, RoaLogic non-commercial Licensed。[GitHub]
SCR1: Syntacore的RV32开源微处理器。SystemVerilog,Solerpad Licensed。[GitHub]
SHAKTI:印度IIT-Madras的RISC-V处理器系列,Bluespec, BSD Licensed。[Bitbucket]
Sodor: 教学用的RISC-V处理器。Chisel, BSD Licensed。[GitHub]
uRV: 针对FPGA的RV32微处理器。Verilog,LGPLv3 Licensed.[ohwr]
VexRiscv: 用SpinalHDL编写的针对FPGA的RV32微处理器。SpinalHDL, MIT Licensed。[GitHub]
YARVI: Tommy Thorn设计的RV32I微处理器,Verilog,GPL2v Licensed。[GitHub]
RV32I的相关基础知识
图 2.1是RV321 基础指令集的一页图形表示。对于每幅图,将有下划线的字母从左到
右连接起来,即可组成完整的 RV321 指令集。对于每一个图,集合标志8内列举了指令的
所有变体,变体用加下划线的字母或下划线字符 表示。特别的,下划线字符_表示对于此
指令变体不需用字符表示。
图2.2显示了六种基本指令格式,分别是:用于寄存器-寄存器操作的R类型指令,用于短立即数和访存 load 操作的I型指令,用于访存 store 操作的S型指令,用于条件跳转操作的B 类型指令,用于长立即数的U型指令和用于无条件跳转的J型指令。
图2.3 使用图2.2 的指令格式列出了图 2.1中出现的所有 RV321 指令的操作码。即使是指令格式也能从一些方面说明 RISC-V 更简洁的 ISA 设计能提高性能功耗比。首先,指令只有六种格式,并且所有的指令都是 32 位长,这简化了指令解码。ARM-32,还有更典型的x86-32 都有许多不同的指令格式,使得解码部件在低端实现中偏昂贵,在中高端处理器设计中容易带来性能挑战。第二,RISC-V 指令提供三个寄存器操作数,而不是像×86-32一样,让源操作数和目的操作数共享一个字段。当一个操作天然就需要有三个不同的操作数,但是 ISA 只提供了两个操作数时,编译器或者汇编程序程序员就需要多使用一条move(搬运)指令,来保存目的寄存器的值。第三,在RISC-V 中对于所有指令,要读写的寄存器的标识符总是在同一位置,意味者在解码指令之前,就可以先开始访问寄存器。在许多其他的 ISA 中,某些指令字段在部分指令中被重用作为源目的地,在其他指令中又被作为目的操作数 (例如,ARM-32和 MIPS-32)。因此,为了取出正确的指令字段,我们需要时序本就可能紧张的解码路径上添加额外的解码逻辑,使得解码路径的时序更为紧张。第四,这些格式的立即数字段总是符号扩展,符号位总是在指令中最高位。这意味着可能成为关键路径的立即数符号扩展,可以在指令解码之前进行。
代码编写与验证
RV32I指令分类
先将指令集分类,相似指令在FPGA内部在系统的模块实现。
寄存器规划
寄存器完全按照官方规定设置x0~x31,pc共32个寄存器。其中x0恒为0.
lowRV32I框图
由于首次接触RV32软核编写,能力有限,水平较低,所以一切从简,故取名lowRV32I。框图如下。
lowRV32I的程序文件
Address:0 Data:10000000000000000000010100110111; //LUI X10,0X8000_0
Address:1 Data:01000000000000000000010110110111; //LUI X11,0X4000_0
Address:2 Data:00100000000000000000011000110111; //LUI X12,0X2000_0
Address:3 Data:00010000000000000000011010110111; //LUI X13,0X1000_0
Address:4 Data:00000000000000000000011100110111; //LUI X14,0X0
Address:5 Data:00000000000000000001011110110111; //LUI X15,0X8000_0
Address:6 Data:00000000101000000010000000100011; //SW X10,0X000(X0)
Address:7 Data:00000000000101110000011100010011; //ADDI X14,X14,0X001
Address:8 Data:11111110111101110110110011100011; //BITU X14,X15,-8
Address:9 Data:00000000000000000000011100110111; //LUI X14,0X0
Address:10 Data:00000000101100000010000000100011; //SW X11,0X000(X0)
Address:11 Data:00000000000101110000011100010011; //ADDI X14,X14,0X001
Address:12 Data:11111110111101110110110011100011; //BITU X14,X15,-8
Address:13 Data:00000000000000000000011100110111; //LUI X14,0X0
Address:14 Data:00000000110000000010000000100011; //SW X12,0X000(X0)
Address:15 Data:00000000000101110000011100010011; //ADDI X14,X14,0X001
Address:16 Data:11111110111101110110110011100011; //BITU X14,X15,-8
Address:17 Data:00000000000000000000011100110111; //LUI X14,0X0
Address:18 Data:00000000110100000010000000100011; //SW X13,0X000(X0)
Address:19 Data:00000000000101110000011100010011; //ADDI X14,X14,0X001
Address:20 Data:11111110111101110110110011100011; //BITU X14,X15,-8
Address:21 Data:11111010111101110110011011100011; //BITU X14,X15,-84
lowRV32I的程序验证
仿真结果如下图
符合代码预期。
对应代码段
Address:0 Data:10000000000000000000010100110111; //LUI X10,0X8000_0
Address:1 Data:01000000000000000000010110110111; //LUI X11,0X4000_0
Address:2 Data:00100000000000000000011000110111; //LUI X12,0X2000_0
Address:3 Data:00010000000000000000011010110111; //LUI X13,0X1000_0
Address:4 Data:00000000000000000000011100110111; //LUI X14,0X0
Address:5 Data:00000000000000000001011110110111; //LUI X15,0X8000_0
Address:6 Data:00000000101000000010000000100011; //SW X10,0X000(X0)
Address:7 Data:00000000000101110000011100010011; //ADDI X14,X14,0X001
Address:8 Data:11111110111101110110110011100011; //BITU X14,X15,-8
致谢
非常感谢硬禾团队提供的这次学习交流的活动,让我有机会也有动力去学习新的技能,培养自己的兴趣。
同时,感谢群里的各位大佬分享自己的心得体会,这让我受益匪浅!
谢谢!
附录
Testbench如下
// TOOL: vlog2tf
// DATE: Wed Mar 15 03:14:09 2023
// TITLE: Lattice Semiconductor Corporation
// MODULE: core
// DESIGN: core
// FILENAME: lowrv32i_tf.v
// PROJECT: lowrv32i
// VERSION: 2.0
// This file is auto generated by Radiant
`timescale 1 ns / 10 ps
// Define Module for Test Fixture
module core_tf();
// Inputs
reg clk_i;
reg rst_n_i;
// Outputs
wire [3:0] gpio_o;
// Bidirs
// Instantiate the UUT
// Please check and add your parameters manually
core UUT (
.clk_i(clk_i),
.rst_n_i(rst_n_i),
.gpio_o(gpio_o)
);
// Initialize Inputs
// You can add your stimulus here
initial begin
clk_i = 0;
rst_n_i = 0;
#100;
rst_n_i = 1;
end
always
begin
#10;
clk_i = ~clk_i;
end
endmodule // core_tf
TOP模块代码如下
module core
#(
parameter cpu_width = 32 //
)
(
input wire clk_i,
//input wire[cpu_width - 1:0] Instr_c,
input wire rst_n_i,
output wire[4 - 1:0] gpio_o
//input wire W_en_i
//input wire[cpu_width - 1:0] W_Rd_data_i
);
wire[cpu_width - 1:0] rs1_c,rs2_c,imm_c,rd_c,pc_c,Instr_c;
wire[14:12] funct3_c;
wire funct7_c,zero_o,clk_c;
wire[6:2] opcode_c;
wire[4:0] rs1_a_c,rs2_a_c,rd_a_c;
sys_clk UUT (
.clk_i(clk_i),
.sys_clk_o(clk_c)
);
alu ALU (
.rs1_i(rs1_c),
.rs2_i(rs2_c),
.imm_i(imm_c),
.pc_i(pc_c),
.funct3_i(funct3_c),
.funct7_i(funct7_c),
.opcode_i(opcode_c),
.rd_o(rd_c),
.zero_o(zero_o)
);
imm_gen IMM (
.instr_i(Instr_c),
.imm_o(imm_c)
);
pc PC (
.clk_i(clk_c),
.rst_n_i(rst_n_i),
.rs1_i(rs1_c),
.imm_i(imm_c),
.opcode_i(opcode_c),
.zero_i(zero_o),
.pc_o(pc_c)
);
reg32 REG (
.clk_i(clk_c),
.rst_n_i(rst_n_i),
.Rs1_i(rs1_a_c),
.Rs2_i(rs2_a_c),
.Rd_i(rd_a_c),
.W_en_i(1'b1),
.W_Rd_data_i(rd_c),
.R_rs_data1_o(rs1_c),
.R_rs_data2_o(rs2_c)
);
test_instr_rom rom (
.rst_n_i(rst_n_i),
.address_i(pc_c[6:2]),
.data_o(Instr_c)
);
decode decode (
.instr_i(Instr_c),
.rs1_a_o(rs1_a_c),
.rs2_a_o(rs2_a_c),
.rd_a_o(rd_a_c),
.funct3_o(funct3_c),
.funct7_o(funct7_c),
.opcode_o(opcode_c)
);
Dram_GPIO GPIO (
.clk_i(clk_c),
.rst_n_i(rst_n_i),
.address_i(rd_c),
.data_i(rs2_c),
.opcode_i(opcode_c),
.gpio_n_o(gpio_o)
);
endmodule
资源使用如下
Lattice Mapping Report File for Design Module 'lowrv32_impl_1'
Target Vendor: LATTICE
Target Device: iCE40UP5KSG48
Target Performance: High-Performance_1.2V
Mapper: version Radiant (64-bit) 1.0.0.350.6
Mapped on: Wed Mar 15 09:56:54 2023
Design Information
Command line: map lowrv32_impl_1_syn.udb
C:/Users/root/Desktop/abc/lowrv32/source/impl_1.pdc -o lowrv32_impl_1.udb
-gui
Design Summary
Number of slice registers: 512 out of 5280 (10%)
Number of I/O registers: 0 out of 117 (0%)
Number of LUT4s: 1865 out of 5280 (35%)
Number of logic LUT4s: 1074
Number of inserted feedthru LUT4s: 481
Number of ripple logic: 155 (310 LUT4s)
Number of IO sites used: 6 out of 39 (15%)
Number of IO sites used for general PIOs: 6
Number of IO sites used for I3Cs: 0 out of 2 (0%)
Number of IO sites used for PIOs+I3Cs: 6 out of 36 (17%)
(note: If I3C is not used, its site can be used as general PIO)
Number of IO sites used for OD+RGB IO buffers: 0 out of 3 (0%)
Number of DSPs: 0 out of 8 (0%)
Number of I2Cs: 0 out of 2 (0%)
Number of High Speed OSCs: 0 out of 1 (0%)
Number of Low Speed OSCs: 0 out of 1 (0%)
Number of RGB PWM: 0 out of 1 (0%)
Number of RGB Drivers: 0 out of 1 (0%)
Number of SCL FILTERs: 0 out of 2 (0%)
Number of SRAMs: 0 out of 4 (0%)
Number of WARMBOOTs: 0 out of 1 (0%)
Number of SPIs: 0 out of 2 (0%)
Number of EBRs: 0 out of 30 (0%)
Number of PLLs: 0 out of 1 (0%)
Number of Clocks: 1
Net clk_c_c: 497 loads, 497 rising, 0 falling (Driver: Port clk_i)
Number of Clock Enables: 15
Net REG/regs[1]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[2]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[3]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[4]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[5]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[6]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[7]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[8]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[9]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[10]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[11]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[12]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[13]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[14]_0_sqmuxa: 32 loads, 32 SLICEs
Net REG/regs[15]_0_sqmuxa: 32 loads, 32 SLICEs
Number of LSRs: 1
Net rst_n_i_c_iso_i: 497 loads, 497 SLICEs
Top 10 highest fanout non-clock nets:
Net rst_n_i_c_iso_i: 497 loads
Net ALU.rd_muxA.rd_o32_s1: 287 loads
Net ALU.N_13: 241 loads
Net G_191: 240 loads
Net G_189: 227 loads
Net rst_n_i_c: 190 loads
Net G_190: 130 loads
Net G_192: 106 loads
Net G_188: 103 loads
Net G_195: 90 loads
Number of warnings: 0
Number of errors: 0