查看: 718|回复: 0
收起左侧

从零开始学RISC-V之邮箱和邮件的秘密

[复制链接]

  离线 

  • TA的每日心情
    奋斗
    2021-3-3 12:32
  • 签到天数: 10 天

    [LV.3]

    发表于 2020-8-23 22:16:57 | 显示全部楼层 |阅读模式

    有人预言,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代码为例,需要经过如下步骤才能成为可执行文件:


    • 预处理:将代码中的各种文件包含,宏定义,以及注释等内容做替换或者展开,生成一份“明文”式的代码,这个时候还是高级语言的代码,只不过看起来就完整一些。你可以理解成把论文的参考文献直接贴在论文后面,预处理之前的参考文献链接,变成了处理之后的参考文献全文。

    • **编译:**属于整个程序构建的核心,这一步会将预处理后的代码文件,转化成汇编代码文件。这种文件是早期计算机程序形式的一种。这一过程是将高级编程语言的代码转换成低级语言代码,它与计算机指令集强相关。这里,人类还能从中大致看出源程序的一些影子,也就是说我们还能看懂部分这转换后的代码。从此处开始,后面的代码就完全脱离了人类的思维了。那个时候的产物就只有电脑能认识了。

    • **汇编:**将之前生成的汇编代码转化成机器码。

    • 链接: 将机器码做最后的规整,包括代码的重定位之类的,生成最终的可执行文件。



    那么具体到本项目,按如下过程操作:


    • 进入xf100/verify/riscv-tools/riscv-tests/isa目录。在该目录下你将看到各种官方的测试样例。此处我们仅关心一个例子rv32ui-p-add,它在rv32ui目录下。

    • 修改该路径下的Makefile文件


      • 修改35行的RISCV_PREFIX,这个参数用于定位gcc-riscv工具链。你需要将其正确指向工具链的存放位置,就是第二节里的那个软连接。如果该参数错误,会导致生成时出现如下错误:make: xxx/bin/riscv-nuclei-elf-gcc: Command not found



    1. make: xxx/bin/riscv-nuclei-elf-gcc: Command not found
    复制代码

    • 删除第79~88行,并修改78行的-march=rv32i。此处修改一是为了节省生成可执行文件的时间(毕竟我们只关注一个测试样例);其次是指定可执行文件所支持的指令集。由于xf100仅作为一个简单的核,因此不支持MACFD这一系列特性。为避免可执行文件中出现xf100不支持的指令,需要修改这个参数。

    • 在该路径下执行生成可执行文件命令


    1. sh clean.sh  // 删除之前生成的文件
    2. sh regen.sh  // 重新生成可执行文件
    复制代码


    如果中间没有任何错误,经过一段时间后,你将会在该路径下的generate文件夹下看到生成的文件。其对应的汇编dump文件如下图所示:


    国内芯片技术交流-从零开始学RISC-V之邮箱和邮件的秘密risc-v单片机中文社区(1)


    更多关于指令如何被识别的知识,请参见这里


    设计一个简单的IRAM

    RAM是一种掉电易丢失的存储器。一种常见的仿真模型是使用基于DFF触发器的一维数组来构建RAM的仿真模型。数组的下标就是RAM访问的地址,数组的每个元素记录的就是需要读写的内容。就本项目来讲,我们对IRAM做如下约束:


    • IRAM的读写数据位宽为32bit,即我们不支持16bit位宽的读写访问。由于xf100仅支持RV32I指令集,因此这个设置是合理的。
    • IRAM地址位宽设置为16bit,即访问空间从0~65535字节。
    • IRAM不存在写访问,由于IRAM只存储指令,也没必要对其存储内容进行修改,因此该设置也是合理的。
    • 读IRAM的数据在当拍就可以输出。这当然是一种理想的状况,尽管存在与实际RAM的工作状态不相符的缺点,但是xf100是一个简单的入门级项目,没必要在此处花费精力。就RAM模型来讲,只要符合其存储数据的基本应用需求即可。我们的重点在于厘清核内部的原理机制,此处简单实用即可。


    基于以上设定,一个简单版本的IRAM的实现代码如下所示:


    1. ///
    2. // 首先我们定义一个带负边沿复位(rst_n)和更新使能(lden)的D触发器。
    3. module xf100_gnrl_dfflr #(
    4.   parameter DW = 32
    5. )(
    6.   input clk,
    7.   input rst_n,
    8.   input          lden,
    9.   input [DW-1:0] din,
    10.   input [DW-1:0] dout
    11. );

    12. reg [DW-1:0] dout_r;
    13. always @ (posedge clk or negedge rst_n) begin
    14.   if (~rst_n) begin
    15.       dout_r <= 1'b0;
    16.   end else if (lden) begin
    17.       dout_r <= din;
    18.   end
    19. end

    20. assign dout = dout_r;
    21. endmodule

    22. ///
    23. // 然后根据前述D触发器,构建一个32位宽的,深度可配置的RAM
    24. module gnrl_sram #(
    25.   parameter DW=32, //数据位宽(Data Width)固定不变
    26.   parameter AW=8 ,
    27.   parameter DP=256 // 深度可配置
    28. )(
    29.   input           clk,
    30.   input           rst_n,
    31.   input           cs ,
    32.   input           wen,
    33.   input [DW-1:0]  din,
    34.   input [AW-1:0]  addr,
    35.   output [DW-1:0] dout
    36. );

    37. // DFF数组,用于模拟RAM的存储区
    38. reg [7:0] ram_r [DP-1:0];

    39. 写端口实现,在IRAM中不会使用到
    40. wire [DW-1:0] ram_nxt = din;
    41. wire ram_wen = cs  & wen;
    42. xf100_gnrl_dfflr #(8) ram_dfflr_0
    43.     (clk, rst_n, ram_wen,ram_nxt[07:00], ram_r[addr+0]);
    44. xf100_gnrl_dfflr #(8) ram_dfflr_1
    45.     (clk, rst_n, ram_wen,ram_nxt[15:08], ram_r[addr+1]);
    46. xf100_gnrl_dfflr #(8) ram_dfflr_2
    47.     (clk, rst_n, ram_wen,ram_nxt[23:16], ram_r[addr+2]);
    48. xf100_gnrl_dfflr #(8) ram_dfflr_3
    49.     (clk, rst_n, ram_wen,ram_nxt[31:24], ram_r[addr+3]);

    50. 读端口实现,数据在读请求有效的当拍就被输出。
    51. wire ram_ren = cs & ~wen;

    52. assign dout [07:00]= ram_r[addr+0] & {DW{ram_ren}};
    53. assign dout [15:08]= ram_r[addr+1] & {DW{ram_ren}};
    54. assign dout [23:16]= ram_r[addr+2] & {DW{ram_ren}};
    55. assign dout [31:24]= ram_r[addr+3] & {DW{ram_ren}};

    56. endmodule

    57. 例化一个IRAM,大小为16384*32bit
    58. module xf100_inst_ram_16384x32 #(
    59.   parameter DW=32,
    60.   parameter AW=14,
    61.   parameter DP=16384
    62. )(
    63.   input           clk,
    64.   input           rst_n,
    65.   input           cs ,
    66.   input           wen,
    67.   input [DW-1:0]  din,
    68.   input [AW-1:0]  addr,
    69.   output [DW-1:0] dout
    70. );

    71. gnrl_sram  #(
    72.   .DW(DW),
    73.   .AW(AW),
    74.   .DP(DP)
    75. ) u_gnrl_inst_ram (
    76.   .clk     (clk  ),
    77.   .rst_n   (rst_n),
    78.   .cs      (cs   ),
    79.   .wen     (wen  ),
    80.   .din     (din  ),
    81.   .addr    (addr ),
    82.   .dout    (dout )
    83. );

    84. endmodule
    复制代码


    至此,IRAM设计结束。后续在仿真时,只需要使用readmemh函数初始化该IRAM,就可以满足IFU取指的需求了。具体内容后续会有介绍。关于IRAM的仿真,将在下一节IFU的设计完成之后一起进行。


    总结一下
    • 所有的高级语言写的代码,都需要通过编译工具做一道转化手续,这个看起来复杂,实际上也真的很复杂。
    • IRAM就是一个存储指令的只读存储器,掉电易丢失,只处理IFU发送过来的读请求信号。
    • 数据在读请求信号有效的当拍就可以输出。但是这个行为并不很好,因为实际的RAM器件的时序并不是如此理想。
    • IRAM的设计以符合要求为原则,大小可配置方便后续进一步优化设计。

    本篇完,感谢关注:RISC-V单片机中文网





    上一篇:BPI-K210 RISC-V架构AI 物联网开发板,采用Kendryte K210 芯片方案
    下一篇:RISC-V SoC FPGA架构为Linux带来了实时性
    RISCV作者优文
    全球首家只专注于RISC-V单片机行业应用的中文网站
    回复

    使用道具 举报

    高级模式
    B Color Image Link Quote Code Smilies

    本版积分规则

    关闭

    RISC-V单片机中文网上一条 /2 下一条



    版权及免责声明|RISC-V单片机中文网 |网站地图

    GMT+8, 2024-3-29 14:25 , Processed in 0.421424 second(s), 48 queries .

    快速回复 返回顶部 返回列表