有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
本帖最后由 皋陶 于 2020-8-26 18:10 编辑
文章目录- 从零开始学RISC-V之邮箱和邮件的秘密
- 背景介绍
- 可执行文件的生成
- 设计一个简单的IRAM
- 总结一下
背景介绍
当一个应用软件被编译成可执行代码后,这种二进制数据会保存在各种不同类型的存储器中。对于xf100项目来讲,这个存储器就是指令RAM(简称IRAM,下同)。注意由于RAM在掉电后会丢失内部保存的数据,因此在实际应用中真实的程序会保存在ROM中,器件在上电之初会将该代码搬运到RAM中并按照设定的起始地址开始工作。如果将指令比喻为邮件的话,那么指令RAM就是专用的邮箱。
基于以上介绍,本节内容将围绕指令存储器展开。它包含两个任务:首先是生成一个实际的RISC-V可执行文件,我们将看到具体的risv指令,并分析cpu是如何识别该指令的。然后将会基于逻辑门设计一个SRAM模型。这两个任务的完成与否将直接关系到IFU(Instruction Fetch Unit,取指令单元)的功能。因此需要重视。
可执行文件的生成我们常见的编程语言,基本上越好读懂,越高级,也就越抽象。所有的高级语言,需要被计算机认识,必须有一套翻译工具,就是编译,链接等等。以最常见的C代码为例,需要经过如下步骤才能成为可执行文件:
预处理:将代码中的各种文件包含,宏定义,以及注释等内容做替换或者展开,生成一份“明文”式的代码,这个时候还是高级语言的代码,只不过看起来就完整一些。你可以理解成把论文的参考文献直接贴在论文后面,预处理之前的参考文献链接,变成了处理之后的参考文献全文。 **编译:**属于整个程序构建的核心,这一步会将预处理后的代码文件,转化成汇编代码文件。这种文件是早期计算机程序形式的一种。这一过程是将高级编程语言的代码转换成低级语言代码,它与计算机指令集强相关。这里,人类还能从中大致看出源程序的一些影子,也就是说我们还能看懂部分这转换后的代码。从此处开始,后面的代码就完全脱离了人类的思维了。那个时候的产物就只有电脑能认识了。 **汇编:**将之前生成的汇编代码转化成机器码。 链接: 将机器码做最后的规整,包括代码的重定位之类的,生成最终的可执行文件。
那么具体到本项目,按如下过程操作:
- make: xxx/bin/riscv-nuclei-elf-gcc: Command not found
复制代码
- 删除第79~88行,并修改78行的-march=rv32i。此处修改一是为了节省生成可执行文件的时间(毕竟我们只关注一个测试样例);其次是指定可执行文件所支持的指令集。由于xf100仅作为一个简单的核,因此不支持MACFD这一系列特性。为避免可执行文件中出现xf100不支持的指令,需要修改这个参数。
- sh clean.sh // 删除之前生成的文件
- sh regen.sh // 重新生成可执行文件
复制代码
如果中间没有任何错误,经过一段时间后,你将会在该路径下的generate文件夹下看到生成的文件。其对应的汇编dump文件如下图所示:
更多关于指令如何被识别的知识,请参见这里
设计一个简单的IRAM
RAM是一种掉电易丢失的存储器。一种常见的仿真模型是使用基于DFF触发器的一维数组来构建RAM的仿真模型。数组的下标就是RAM访问的地址,数组的每个元素记录的就是需要读写的内容。就本项目来讲,我们对IRAM做如下约束:
- IRAM的读写数据位宽为32bit,即我们不支持16bit位宽的读写访问。由于xf100仅支持RV32I指令集,因此这个设置是合理的。
- IRAM地址位宽设置为16bit,即访问空间从0~65535字节。
- IRAM不存在写访问,由于IRAM只存储指令,也没必要对其存储内容进行修改,因此该设置也是合理的。
- 读IRAM的数据在当拍就可以输出。这当然是一种理想的状况,尽管存在与实际RAM的工作状态不相符的缺点,但是xf100是一个简单的入门级项目,没必要在此处花费精力。就RAM模型来讲,只要符合其存储数据的基本应用需求即可。我们的重点在于厘清核内部的原理机制,此处简单实用即可。
基于以上设定,一个简单版本的IRAM的实现代码如下所示:
- ///
- // 首先我们定义一个带负边沿复位(rst_n)和更新使能(lden)的D触发器。
- module xf100_gnrl_dfflr #(
- parameter DW = 32
- )(
- input clk,
- input rst_n,
- input lden,
- input [DW-1:0] din,
- input [DW-1:0] dout
- );
- reg [DW-1:0] dout_r;
- always @ (posedge clk or negedge rst_n) begin
- if (~rst_n) begin
- dout_r <= 1'b0;
- end else if (lden) begin
- dout_r <= din;
- end
- end
- assign dout = dout_r;
- endmodule
- ///
- // 然后根据前述D触发器,构建一个32位宽的,深度可配置的RAM
- module gnrl_sram #(
- parameter DW=32, //数据位宽(Data Width)固定不变
- parameter AW=8 ,
- parameter DP=256 // 深度可配置
- )(
- input clk,
- input rst_n,
- input cs ,
- input wen,
- input [DW-1:0] din,
- input [AW-1:0] addr,
- output [DW-1:0] dout
- );
- // DFF数组,用于模拟RAM的存储区
- reg [7:0] ram_r [DP-1:0];
- 写端口实现,在IRAM中不会使用到
- wire [DW-1:0] ram_nxt = din;
- wire ram_wen = cs & wen;
- xf100_gnrl_dfflr #(8) ram_dfflr_0
- (clk, rst_n, ram_wen,ram_nxt[07:00], ram_r[addr+0]);
- xf100_gnrl_dfflr #(8) ram_dfflr_1
- (clk, rst_n, ram_wen,ram_nxt[15:08], ram_r[addr+1]);
- xf100_gnrl_dfflr #(8) ram_dfflr_2
- (clk, rst_n, ram_wen,ram_nxt[23:16], ram_r[addr+2]);
- xf100_gnrl_dfflr #(8) ram_dfflr_3
- (clk, rst_n, ram_wen,ram_nxt[31:24], ram_r[addr+3]);
- 读端口实现,数据在读请求有效的当拍就被输出。
- wire ram_ren = cs & ~wen;
- assign dout [07:00]= ram_r[addr+0] & {DW{ram_ren}};
- assign dout [15:08]= ram_r[addr+1] & {DW{ram_ren}};
- assign dout [23:16]= ram_r[addr+2] & {DW{ram_ren}};
- assign dout [31:24]= ram_r[addr+3] & {DW{ram_ren}};
- endmodule
- 例化一个IRAM,大小为16384*32bit
- module xf100_inst_ram_16384x32 #(
- parameter DW=32,
- parameter AW=14,
- parameter DP=16384
- )(
- input clk,
- input rst_n,
- input cs ,
- input wen,
- input [DW-1:0] din,
- input [AW-1:0] addr,
- output [DW-1:0] dout
- );
- gnrl_sram #(
- .DW(DW),
- .AW(AW),
- .DP(DP)
- ) u_gnrl_inst_ram (
- .clk (clk ),
- .rst_n (rst_n),
- .cs (cs ),
- .wen (wen ),
- .din (din ),
- .addr (addr ),
- .dout (dout )
- );
- endmodule
复制代码
至此,IRAM设计结束。后续在仿真时,只需要使用readmemh函数初始化该IRAM,就可以满足IFU取指的需求了。具体内容后续会有介绍。关于IRAM的仿真,将在下一节IFU的设计完成之后一起进行。
总结一下
- 所有的高级语言写的代码,都需要通过编译工具做一道转化手续,这个看起来复杂,实际上也真的很复杂。
- IRAM就是一个存储指令的只读存储器,掉电易丢失,只处理IFU发送过来的读请求信号。
- 数据在读请求信号有效的当拍就可以输出。但是这个行为并不很好,因为实际的RAM器件的时序并不是如此理想。
- IRAM的设计以符合要求为原则,大小可配置方便后续进一步优化设计。
本篇完,感谢关注:RISC-V单片机中文网
|