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

从零开始写RISC-V处理器【3】硬件篇(1)

[复制链接]

  离线 

  • TA的每日心情
    拍拍
    2022-6-27 11:09
  • 签到天数: 25 天

    [LV.4]

    发表于 2020-9-18 08:13:43 | 显示全部楼层 |阅读模式

    有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    本帖最后由 sky 于 2020-9-18 09:12 编辑

    国内芯片技术交流-从零开始写RISC-V处理器【3】硬件篇(1)risc-v单片机中文社区(1)

    4.1 tinyriscv整体框架
    硬件篇主要介绍tinyriscv的verilog代码设计。

    tinyriscv整体框架如图2-1所示。

    国内芯片技术交流-从零开始写RISC-V处理器【3】硬件篇(1)risc-v单片机中文社区(2)
    图2-1 tinyriscv整体框架

    可见目前tinyriscv已经不仅仅是一个内核了,而是一个小型的SOC,包含一些简单的外设,如timer、uart_tx等。

    tinyriscv SOC输入输出信号有两部分,一部分是系统时钟clk和复位信号rst,另一部分是JTAG调试信号,TCK、TMS、TDI和TDO。

    上图中的小方框表示一个个模块,方框里面的文字表示模块的名字,箭头则表示模块与模块之间的的输入输出关系。

    下面简单介绍每个模块的主要作用。

    jtag_top:调试模块的顶层模块,主要有三大类型的信号,第一种是读写内存的信号,第二种是读写寄存器的信号,第三种是控制信号,比如复位MCU,暂停MCU等。

    pc_reg:PC寄存器模块,用于产生PC寄存器的值,该值会被用作指令存储器的地址信号。

    if_id:取指到译码之间的模块,用于将指令存储器输出的指令打一拍后送到译码模块。

    id:译码模块,纯组合逻辑电路,根据if_id模块送进来的指令进行译码。当译码出具体的指令(比如add指令)后,产生是否写寄存器信号,读寄存器信号等。由于寄存器采用的是异步读方式,因此只要送出读寄存器信号后,会马上得到对应的寄存器数据,这个数据会和写寄存器信号一起送到id_ex模块。

    id_ex:译码到执行之间的模块,用于将是否写寄存器的信号和寄存器数据打一拍后送到执行模块。

    ex:执行模块,纯组合逻辑电路,根据具体的指令进行相应的操作,比如add指令就执行加法操作等。此外,如果是lw等访存指令的话,则会进行读内存操作,读内存也是采用异步读方式。最后将是否需要写寄存器、写寄存器地址,写寄存器数据信号送给regs模块,将是否需要写内存、写内存地址、写内存数据信号送给rib总线,由总线来分配访问的模块。

    div:除法模块,采用试商法实现,因此至少需要32个时钟才能完成一次除法操作。

    ctrl:控制模块,产生暂停流水线、跳转等控制信号。

    clint:核心本地中断模块,对输入的中断请求信号进行总裁,产生最终的中断信号。

    rom:程序存储器模块,用于存储程序(bin)文件。

    ram:数据存储器模块,用于存储程序中的数据。

    timer:定时器模块,用于计时和产生定时中断信号。目前支持RTOS时需要用到该定时器。

    uart_tx:串口发送模块,主要用于调试打印。

    gpio:简单的IO口模块,主要用于点灯调试。

    spi:目前只有master角色,用于访问spi从机,比如spi norflash。

    4.2 寄存器
    PC寄存器模块所在的源文件:rtl/core/pc_reg.v

    PC寄存器模块的输入输出信号如下表所示:

    国内芯片技术交流-从零开始写RISC-V处理器【3】硬件篇(1)risc-v单片机中文社区(3)

    PC寄存器模块代码比较简单,直接贴出来:

    国内芯片技术交流-从零开始写RISC-V处理器【3】硬件篇(1)risc-v单片机中文社区(4)

    第3行,PC寄存器的值恢复到原始值(复位后的值)有两种方式,第一种不用说了,就是复位信号有效。第二种是收到jtag模块发过来的复位信号。PC寄存器复位后的值为CpuResetAddr,即32’h0,可以通过改变CpuResetAddr的值来改变PC寄存器的复位值。

    第6行,判断跳转标志是否有效,如果有效则直接将PC寄存器的值设置为jump_addr_i的值。因此可以知道,所谓的跳转就是改变PC寄存器的值,从而使CPU从该跳转地址开始取指。

    第9行,判断暂停标志是否大于等于Hold_Pc,该值为3’b001。如果是,则保持PC寄存器的值不变。这里可能会有疑问,为什么Hold_Pc的值不是一个1bit的信号。因为这个暂停标志还会被if_id和id_ex模块使用,如果仅仅需要暂停PC寄存器的话,那么if_id模块和id_ex模块是不需要暂停的。当需要暂停if_id模块时,PC寄存器也会同时被暂停。当需要暂停id_ex模块时,那么整条流水线都会被暂停。

    第13行,将PC寄存器的值加4。在这里可以知道,tinyriscv的取指地址是4字节对齐的,每条指令都是32位的。

    4.3 通用寄存器
    通用寄存器模块所在的源文件:rtl/core/regs.v

    一共有32个通用寄存器x0~x31,其中寄存器x0是只读寄存器并且其值固定为0。通用寄存器的输入输出信号如下表所示:

    国内芯片技术交流-从零开始写RISC-V处理器【3】硬件篇(1)risc-v单片机中文社区(5)

    注意,这里的寄存器1不是指x1寄存器,寄存器2也不是指x2寄存器。而是指一条指令里涉及到的两个寄存器(源寄存器1和源寄存器2)。一条指令可能会同时读取两个寄存器的值,所以有两个读端口。又因为jtag模块也会进行寄存器的读操作,所以一共有三个读端口。

    读寄存器操作来自译码模块,并且读出来的寄存器数据也会返回给译码模块。写寄存器操作来自执行模块。

    先看读操作的代码,如下:

    国内芯片技术交流-从零开始写RISC-V处理器【3】硬件篇(1)risc-v单片机中文社区(6)

    可以看到两个寄存器的读操作几乎是一样的。因此在这里只解析读寄存器1那部分代码。
    第5行,如果是读寄存器0(x0),那么直接返回0就可以了。

    第8行,这涉及到数据相关问题。由于流水线的原因,当前指令处于执行阶段的时候,下一条指令则处于译码阶段。由于执行阶段不会写寄存器,而是在下一个时钟到来时才会进行寄存器写操作,如果译码阶段的指令需要上一条指令的结果,那么此时读到的寄存器的值是错误的。比如下面这两条指令:

    国内芯片技术交流-从零开始写RISC-V处理器【3】硬件篇(1)risc-v单片机中文社区(7)

    第二条指令依赖于第一条指令的结果。为了解决这个数据相关的问题就有了第8~9行的操作,即如果读寄存器等于写寄存器,则直接将要写的值返回给读操作。

    第11行,如果没有数据相关,则返回要读的寄存器的值。

    下面看写寄存器操作,代码如下:

    国内芯片技术交流-从零开始写RISC-V处理器【3】硬件篇(1)risc-v单片机中文社区(8)

    第5~6行,如果执行模块写使能并且要写的寄存器不是x0寄存器,则将要写的值写到对应的寄存器。

    第7~8行,jtag模块的写操作。

    CSR寄存器模块(csr_reg.v)和通用寄存器模块的读、写操作是类似的,这里就不重复了。

    4.4 取指

    目前tinyriscv所有外设(包括rom和ram)、寄存器的读取都是与时钟无关的,或者说所有外设、寄存器的读取采用的是组合逻辑的方式。这一点非常重要!

    tinyriscv并没有具体的取指模块和代码。PC寄存器模块的输出pc_o会连接到外设rom模块的地址输入,又由于rom的读取是组合逻辑,因此每一个时钟上升沿到来之前(时序是满足要求的),从rom输出的指令已经稳定在if_id模块的输入,当时钟上升沿到来时指令就会输出到id模块。

    取到的指令和指令地址会输入到if_id模块(if_id.v),if_id模块是一个时序电路,作用是将输入的信号打一拍后再输出到译码(id.v)模块。

    4.5译码

    译码模块所在的源文件:rtl/core/id.v

    译码(id)模块是一个纯组合逻辑电路,主要作用有以下几点:

    1.根据指令内容,解析出当前具体是哪一条指令(比如add指令)。

    2.根据具体的指令,确定当前指令涉及的寄存器。比如读寄存器是一个还是两个,是否需要写寄存器以及写哪一个寄存器。

    3.访问通用寄存器,得到要读的寄存器的值。

    译码模块的输入输出信号如下表所示:

    国内芯片技术交流-从零开始写RISC-V处理器【3】硬件篇(1)risc-v单片机中文社区(9)

    以add指令为例来说明如何译码。下图是add指令的编码格式:

    国内芯片技术交流-从零开始写RISC-V处理器【3】硬件篇(1)risc-v单片机中文社区(10)

    可知,add指令被编码成6部分内容。通过第1、4、6这三部分可以唯一确定当前指令是否是add指令。知道是add指令之后,就可以知道add指令需要读两个通用寄存器(rs1和rs2)和写一个通用寄存器(rd)。下面看具体的代码:

    国内芯片技术交流-从零开始写RISC-V处理器【3】硬件篇(1)risc-v单片机中文社区(11)


    第1行,opcode就是指令编码中的第6部分内容。

    第3行,`INST_TYPE_R_M的值为7’b0110011。

    第4行,funct7是指指令编码中的第1部分内容。

    第5行,funct3是指指令编码中的第4部分内容。

    第6行,到了这里,第1、4、6这三部分已经译码完毕,已经可以确定当前指令是add指令了。

    第7行,设置写寄存器标志为1,表示执行模块结束后的下一个时钟需要写寄存器。

    第8行,设置写寄存器地址为rd,rd的值为指令编码里的第5部分内容。

    第9行,设置读寄存器1的地址为rs1,rs1的值为指令编码里的第3部分内容。

    第10行,设置读寄存器2的地址为rs2,rs2的值为指令编码里的第2部分内容。

    其他指令的译码过程是类似的,这里就不重复了。译码模块看起来代码很多,但是大部分代码都是类似的。

    译码模块还有个作用是当指令为加载内存指令(比如lw等)时,向总线发出请求访问内存的信号。这部分内容将在总线一节再分析。

    译码模块的输出会送到id_ex模块(id_ex.v)的输入,id_ex模块是一个时序电路,作用是将输入的信号打一拍后再输出到执行模块(ex.v)。





    上一篇:从零开始写RISC-V处理器【2】浅谈Verilog
    下一篇:从零开始写RISC-V处理器【4】硬件篇(2)
    RISCV作者优文
    全球首家只专注于RISC-V单片机行业应用的中文网站
    回复

    使用道具 举报

    高级模式
    B Color Image Link Quote Code Smilies

    本版积分规则

    关闭

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



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

    GMT+8, 2024-3-29 07:40 , Processed in 0.448766 second(s), 48 queries .

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