登录
注册
开源
企业版
高校版
搜索
帮助中心
使用条款
关于我们
开源
企业版
高校版
私有云
模力方舟
AI 队友
登录
注册
轻量养虾,开箱即用!低 Token + 稳定算力,Gitee & 模力方舟联合出品的 PocketClaw 正式开售!点击了解详情
代码拉取完成,页面将自动刷新
捐赠
捐赠前请先登录
取消
前往登录
扫描微信二维码支付
取消
支付完成
支付提示
将跳转至支付宝完成支付
确定
取消
Watch
不关注
关注所有动态
仅关注版本发行动态
关注但不提醒动态
1
Star
4
Fork
3
RV少年 (RV4Kids)
/
NutShell
代码
Issues
3
Pull Requests
0
Wiki
统计
流水线
服务
质量分析
Jenkins for Gitee
腾讯云托管
腾讯云 Serverless
悬镜安全
阿里云 SAE
Codeblitz
SBOM
我知道了,不再自动展开
更新失败,请稍后重试!
移除标识
内容风险标识
本任务被
标识为内容中包含有代码安全 Bug 、隐私泄露等敏感信息,仓库外成员不可访问
单周期处理器介绍 [讲座] 7月12日 负责人: 薛臻
已完成
#I4SPC9
袁德俊
拥有者
创建于
2022-02-07 15:54
<p><img width="39%" src="https://images.gitee.com/uploads/images/2022/0207/155219_26e9e813_5631341.png" title="1 2022-02-07 15-40-59 的屏幕截图.png"> <img width="39%" src="https://images.gitee.com/uploads/images/2022/0207/155240_37cb0b55_5631341.png" title="2 2022-02-07 15-41-03 的屏幕截图.png"></p> # 单周期RISCV处理器介绍 薛臻 (2021/7/12) - RISCV64I 指令集架构 - 单周期 CPU 代码框架 - 一条指令的执行过程 - 经典五级流水线 - 单周期代码的使用指南 > 1. 嗯,各位同学大家好,我是薛臻。我来介绍一下单周期 RISC-V 的示例代码。在介绍示例代码之前,我会先简要介绍一下 RISCV64I 指令集架构,接着解释一条指令,也就是 addi 指令,在示例代码中的执行过程,然后简要介绍一下经典五级流水线和单周期代码的使用指南。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155255_ca7c1c01_5631341.png" title="3 2022-02-07 15-41-21 的屏幕截图.png"></p> - RISC 指令集 - **六种** 指令格式 - RISCV64I 寄存器 - PC:程序计数器,存放下一条指令的地址 - 32个通用寄存器,1个值恒为0的x0寄存器 - 小端存储  RISCV 指令格式 > 2. 首先,RISC-V 是 RISC 指令集,具有规整的编码格式,RISC-V有 **六种** 的指令格式。因为固定的32位长度可以发现,在所有的指令格式中,源寄存器 rs1 rs2 和目标寄存器 rd 都固定在相同的位置,简化了指令译码。这体现了 RISC 指令集的优势。大家可以看到,在所有指令中,立即数 imm 的符号位( _高位_ )总是第31位。  RISCV 寄存器 > RISCV64I 有一个程序计数器 PC,用来存放下一条指令的地址,这个应该所有 cpu 都会有的一个寄存器吧,并且有32个64位的通用寄存器 X0到X31,其中X0的值恒为零。除此之外,RISCV 指令集为小端存储模式,也就是低位存放在低地址,高位存放在高地址。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155310_a0c42ccd_5631341.png" title="4 2022-02-07 15-42-17 的屏幕截图.png"></p> - 取指阶段:从内存中获取当前 PC 中对应的指令,同时 PC+4 - 译码阶段:解析指令,获得源/目的操作数等信息 - 执行阶段:指令对应的运算等 ``` Single cycle riscv cpu ├── defines.v // 存放常用的变量 ├── exe_stage.v // 执行阶段 ├── id_stage.v // 译码阶段 ├── if_stage.v // 取指阶段 ├── inst.bin // 指令文件 ├── regfile.v // riscv 寄存器 ├── rvcpu-test.cpp // verilator 的仿真文件 └── rvcpu.v // riscvCPU ``` > 3. 在这个单周期的示例代码中,我实现了三个阶段,分别是取指阶段、译码阶段和执行阶段。这三个阶段分别对应于 if_stage.v、id_stage.v、exe_stage.v,三个文件就是三个模块,除此之外 defines.v 里存放了一些常用的变量。inst.bin 是指令文件,regfile.v 是32个通用寄存器,cpp 文件用于在 verilator 中进行仿真,rvcpu,是顶层模块。  > 从这个波形图中可以看出一条指令进来,这条指令执行的是 0+1 的加法操作,在同一个时钟周期内就得到了运算结果。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155323_f42d6214_5631341.png" title="5 2022-02-07 15-43-02 的屏幕截图.png"></p> > 4. 接下来,我以 inst.bin 中第一条指令 addi 为例,详细解释一下这条指令,在示例代码中的执行过程。我先说明具体做法,然后再结合代码进行介绍。首先,Addi 指令是一条 I 型指令,不同字段具有特定含义。比如高12位表示立即数 imm,7-11位是 目标寄存器 rd 的地址,15到19位是源寄存器 rs1 的地址等等。 **取指** - **从内存取出指令** ,同时 PC+4 > 在 RISC-V cpu工作的过程中,首先cpu从内存中取出这条指令,这里是从 inst.bin 这个文件中,同时给 程序计数器 PC +4,这是取指阶段; **译码** - **解析指令** 0x0010_0093 - imm: 1 - rs1: x0 - Func3: 000 - rd: x1 - opcode: 0010011 > 接下来进入译码阶段,译码模块解析指令,得到这条指令的这些字段:也就是 立即数 imm, rs1, Func3 和 opcode 字段。 - **识别指令** - opcode&Func3 = addi - **确定指令类型/操作码** - I型指令 - 加法操作码 INST_ADD - **获取操作数来源** - I型指令: - op1——寄存器 - op2——立即数 > 5. 接下来,根据 opcode 和 Func3 字段识别出这是一条 addi 指令。得到这个关键信息后,进而可以确定这是一条 I 行指令,并且它执行加法操作,从而将其指令操作码确定为加法操作码。那既然现在已经知道了,这是一条I行指令。那么它的操作数来源就理所当然的确定下来了,那就是操作数 op1 来自于寄存器,操作数 op2 来自于立即数, - **产生寄存器 读/写信号** - r_ena1 = 1 - r_addr1 = 0 - r_ena2 = 0 - w_ena = 1 - w_addr = 1 > 同时对应的寄存器使能,与读写相关信号也被确定下来。在这条 addi 指令中,读口一的使能信号 r_ena1 = 1,表示可读,并且读地址为 s1,也就是 r_addr1=0 。读口二的使能信号 r_ena2=0,不允许读。写使能信号为一 w_ena=1,表示可写。 - **获取操作数 op1, op2** **执行** - **根据指令操作码,执行相应操作** - INST_ADD: - 进行加法计算 op1+op2 > 6. 此时可以通过读寄存器和立即数的符号扩展,分别得到操作数一 op1 和操作数二 op2,他们被传送到执行模块用来进行运算。执行模块,根据接收到的指令操作码来进行相应的操作。在这条指令中,操作码是加法操作码,因此执行加法操作也就是 0+1,得到的结果为 1。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155340_1abb2edd_5631341.png" title="6 2022-02-07 15-45-06 的屏幕截图.png"></p> **取指** if_stage.v ``` always@( posedge clk ) begin if( rst == 1'b1 ) begin pc <= `ZERO_WORD ; end else begin pc <= pc + 4; end end ``` > 7. 嗯对应代码,我们再来看一遍。首先在取值模块中,每过一个时钟周期,程序计数计PC的值加四。 **译码:解析指令** id_stage.v ``` assign opcode = inst[6 : 0]; assign rd = inst[11 : 7]; assign func3 = inst[14 : 12]; assign rs1 = inst[19 : 15]; assign imm = inst[31 : 20]; ``` > 进入译码模块后,译码模块通过取出指令的不同字段,实现了对指令的解析。 **译码:识别指令** ``` wire inst_addi = ~opcode[2] & ~opcode[3] & opcode[4] & ~opcode[5] & ~opcode[6] & ~func3[0] & ~func3[1] & ~func3[2]; ``` > 然后根据 opcode 高5位和 Func3 确定这是一条addi 指令。不考察 opcode 的低两位是因为在 RISC-V 中所有 opcode 的低两位均为 11。 **译码:确定类型/操作码** ``` // arith inst: 10000; logic: 01000; // load-store: 00100; j: 00010; sys: 000001 assign inst_type[4] = ( rst == 1'b1 ) ? 0 : inst_addi; assign inst_opcode[0] = ( rst == 1'b1 ) ? 0 : inst_addi; assign inst_opcode[1] = ( rst == 1'b1 ) ? 0 : 0; assign inst_opcode[2] = ( rst == 1'b1 ) ? 0 : 0; assign inst_opcode[3] = ( rst == 1'b1 ) ? 0 : 0; assign inst_opcode[4] = ( rst == 1'b1 ) ? 0 : inst_addi; assign inst_opcode[5] = ( rst == 1'b1 ) ? 0 : 0; assign inst_opcode[6] = ( rst == 1'b1 ) ? 0 : 0; assign inst_opcode[7] = ( rst == 1'b1 ) ? 0 : 0; ``` > 因为这是一条 addi 指令,所以可以得到这是一条 I 型指令,并且其指令操作码为加法操作码。在这个示例代码中,加法操作码为 11,这是我自己规定的,你也可以规定别的值,只要保证不同操作的操作码不同即可。 **译码:产生寄存器读/写信号** ``` assign rs1_r_ena = ( rst == 1'b1 ) ? 0 : inst_type[4]; assign rs1_r_addr = ( rst == 1'b1 ) ? 0 : ( inst_type[4] == 1'b1 ? rs1 : 0 ); assign rs2_r_ena = 0; assign rs2_r_addr = 0; assign rd_w_ena = ( rst == 1'b1 ) ? 0 : inst_type[4]; assign rd_w_addr = ( rst == 1'b1 ) ? 0 : ( inst_type[4] == 1'b1 ? rd : 0 ); ``` > 因为这是一条I型指令,因此寄存器读口1可读,读口2不可读,写口使能为1,因此寄存器读口, **译码:获取操作数** ``` assign op1 = ( rst == 1'b1 ) ? 0 : ( inst_type[4] == 1'b1 ? rs1_data : 0 ); assign op2 = ( rst == 1'b1 ) ? 0 : ( inst_type[4] == 1'b1 ? { {52{imm[11]}}, imm } : 0 ); ``` > 8. 因此译码模块通过寄存器的读数据和立即数的符号扩展,得到操作数1和操作数2。 **执行** ``` case( inst_opcode ) `INST_ADD: begin rd_data = op1 + op2; end default: begin rd_data = `ZERO_WORD; end endcase ``` > 在执行阶段,执行模块发现接收到的指令操作码为加法操作码,于是执行加法计算,从而得到计算结果。 - 单周期 RISCV CPU:在一个周期内得到执行结果 - 取出指令后,其译码、执行过程均为组合逻辑 - 思考:如果是五级流水线,得到执行结果需要几个时钟周期?需要怎样添加时序逻辑? > 需要强调一下,这是一个单周期的 RISCV cpu,他在取出指令后,译码和执行过程均为组合逻辑,因此在一个周期内就得到了执行结果。那在一生一芯中,我们要求实现五级流水线,那么,在这种情况下,得到执行结果需要几个周期?应该怎样添加时序逻辑呢?这个留给大家去思考。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155352_fa2a8789_5631341.png" title="7 2022-02-07 15-46-42 的屏幕截图.png"></p> - **IF: 取指** 将指令从存储器中读出 - **ID: 译码** 翻译指令,得到源/目的操作数的相关信息 - **EX: 执行** 执行指令对应的操作 - **MEM: 访存** 对存储器进行读/写 - **WB: 写回** 将执行结果写入通用寄存器 流水线:通过提高 CPU 中 **各个部件的利用率** ,提高其工作效率 > 9. 接下来简要介绍一下经典的五级流水线,他的五个阶段分别是取指、译码、执行、访存和写回。其中,取指是将指令从存储器中读出,译码阶段翻译指令,从而得到原操作数和目的操作数的相关信息。执行阶段用来执行指令对应的操作,比如刚才的加法。访存实现对存储器的读写,而写回阶段则将执行结果写入通用寄存器。在处理器中,流水线通过提高 **各个部件的利用率** 来提高工作效率。  > 10. 嗯,举个例子,对于指令1、指令2和指令3,在顺序执行的模式下,每个指令需要五个周期,那么三条指令就需要15个周期。如果使用流水线的执行模式,那么三条指令,只需要七个周期就可以完成,加速比是15除以7达到2倍多。在顺序执行时,取指、译码、执行、访存、写回依次进行。每个周期只有一个部件在进行工作。而在流水线模式下,指令1在执行阶段时,同时指令2在译码,同时指令3在取值。所以说流水线是通过提高cpu中部件的利用率来提高工作效率的。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155409_dbf25ce0_5631341.png" title="8 2022-02-07 15-47-50 的屏幕截图.png"></p> **单周期代码使用指南** - 只实现了取指、译码和执行阶段, **请同学们完成访存和写回阶段** - 添加时序逻辑, **实现五级流水线** - **思考:** - **数据相关:** RAW(写后读),WAR(读后写),WRW(写后写)哪种需要关注? - **控制相关:** 分支指令如何处理? - 流水线 **暂停(stall)** 、流水线 **删除(kill)** 怎样实现? - 单周期代码仅供参考 **实现访存、写回阶段后,将寄存器写使能、写地址和写数据逐级下传到写回阶段** **id_stage.v** ``` assign rd_w_ena = ( rst == 1'b1 ) ? 0 : inst_type[4]; assign rd_w_addr = ( rst == 1'b1 ) ? 0 : ( inst_type[4] == 1'b1 ? rd : 0 ); ``` **exe_stage.v** ``` output reg [`REG_BUS]rd_data ``` **功能验证流程** - 实现新指令 - 在AM上编译得到.bin测试文件 - Verilator 仿真验证 - Difftest 调试 > 11. 最后我说明一下单周期代码的使用指南。这个示例代码只实现了取指、译码和执行, **请同学们完成访存和写回阶段** 的实现。刚才在介绍五级流水线的时候说过,写回阶段会将计算结果写回到寄存器中,所以在同学们实现了访存和写回模块中,请将寄存器读使能、读地址和读数据信号向后逐级下传,传到写回阶段,并在写回阶段进行寄存器的写操作。还有,请在在适当的地方添加时序逻辑, **实现五级流水线** 。最后请 **思考** 在三种数据相关中,我们需要重点关注哪一种,是写后读读后写还是写后写?另外再遇到分支和跳转指令时应该怎样处理,什么情况下需要流水线 **暂停** 和流水线 **删除** ?嗯,他们应该怎样实现?最后,最后需要说明一下,单周期示例代码,仅供参考,欢迎大家一起讨论。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155419_3fe37d5b_5631341.png" title="9 2022-02-07 15-48-47 的屏幕截图.png"></p> 好,谢谢大家。
<p><img width="39%" src="https://images.gitee.com/uploads/images/2022/0207/155219_26e9e813_5631341.png" title="1 2022-02-07 15-40-59 的屏幕截图.png"> <img width="39%" src="https://images.gitee.com/uploads/images/2022/0207/155240_37cb0b55_5631341.png" title="2 2022-02-07 15-41-03 的屏幕截图.png"></p> # 单周期RISCV处理器介绍 薛臻 (2021/7/12) - RISCV64I 指令集架构 - 单周期 CPU 代码框架 - 一条指令的执行过程 - 经典五级流水线 - 单周期代码的使用指南 > 1. 嗯,各位同学大家好,我是薛臻。我来介绍一下单周期 RISC-V 的示例代码。在介绍示例代码之前,我会先简要介绍一下 RISCV64I 指令集架构,接着解释一条指令,也就是 addi 指令,在示例代码中的执行过程,然后简要介绍一下经典五级流水线和单周期代码的使用指南。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155255_ca7c1c01_5631341.png" title="3 2022-02-07 15-41-21 的屏幕截图.png"></p> - RISC 指令集 - **六种** 指令格式 - RISCV64I 寄存器 - PC:程序计数器,存放下一条指令的地址 - 32个通用寄存器,1个值恒为0的x0寄存器 - 小端存储  RISCV 指令格式 > 2. 首先,RISC-V 是 RISC 指令集,具有规整的编码格式,RISC-V有 **六种** 的指令格式。因为固定的32位长度可以发现,在所有的指令格式中,源寄存器 rs1 rs2 和目标寄存器 rd 都固定在相同的位置,简化了指令译码。这体现了 RISC 指令集的优势。大家可以看到,在所有指令中,立即数 imm 的符号位( _高位_ )总是第31位。  RISCV 寄存器 > RISCV64I 有一个程序计数器 PC,用来存放下一条指令的地址,这个应该所有 cpu 都会有的一个寄存器吧,并且有32个64位的通用寄存器 X0到X31,其中X0的值恒为零。除此之外,RISCV 指令集为小端存储模式,也就是低位存放在低地址,高位存放在高地址。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155310_a0c42ccd_5631341.png" title="4 2022-02-07 15-42-17 的屏幕截图.png"></p> - 取指阶段:从内存中获取当前 PC 中对应的指令,同时 PC+4 - 译码阶段:解析指令,获得源/目的操作数等信息 - 执行阶段:指令对应的运算等 ``` Single cycle riscv cpu ├── defines.v // 存放常用的变量 ├── exe_stage.v // 执行阶段 ├── id_stage.v // 译码阶段 ├── if_stage.v // 取指阶段 ├── inst.bin // 指令文件 ├── regfile.v // riscv 寄存器 ├── rvcpu-test.cpp // verilator 的仿真文件 └── rvcpu.v // riscvCPU ``` > 3. 在这个单周期的示例代码中,我实现了三个阶段,分别是取指阶段、译码阶段和执行阶段。这三个阶段分别对应于 if_stage.v、id_stage.v、exe_stage.v,三个文件就是三个模块,除此之外 defines.v 里存放了一些常用的变量。inst.bin 是指令文件,regfile.v 是32个通用寄存器,cpp 文件用于在 verilator 中进行仿真,rvcpu,是顶层模块。  > 从这个波形图中可以看出一条指令进来,这条指令执行的是 0+1 的加法操作,在同一个时钟周期内就得到了运算结果。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155323_f42d6214_5631341.png" title="5 2022-02-07 15-43-02 的屏幕截图.png"></p> > 4. 接下来,我以 inst.bin 中第一条指令 addi 为例,详细解释一下这条指令,在示例代码中的执行过程。我先说明具体做法,然后再结合代码进行介绍。首先,Addi 指令是一条 I 型指令,不同字段具有特定含义。比如高12位表示立即数 imm,7-11位是 目标寄存器 rd 的地址,15到19位是源寄存器 rs1 的地址等等。 **取指** - **从内存取出指令** ,同时 PC+4 > 在 RISC-V cpu工作的过程中,首先cpu从内存中取出这条指令,这里是从 inst.bin 这个文件中,同时给 程序计数器 PC +4,这是取指阶段; **译码** - **解析指令** 0x0010_0093 - imm: 1 - rs1: x0 - Func3: 000 - rd: x1 - opcode: 0010011 > 接下来进入译码阶段,译码模块解析指令,得到这条指令的这些字段:也就是 立即数 imm, rs1, Func3 和 opcode 字段。 - **识别指令** - opcode&Func3 = addi - **确定指令类型/操作码** - I型指令 - 加法操作码 INST_ADD - **获取操作数来源** - I型指令: - op1——寄存器 - op2——立即数 > 5. 接下来,根据 opcode 和 Func3 字段识别出这是一条 addi 指令。得到这个关键信息后,进而可以确定这是一条 I 行指令,并且它执行加法操作,从而将其指令操作码确定为加法操作码。那既然现在已经知道了,这是一条I行指令。那么它的操作数来源就理所当然的确定下来了,那就是操作数 op1 来自于寄存器,操作数 op2 来自于立即数, - **产生寄存器 读/写信号** - r_ena1 = 1 - r_addr1 = 0 - r_ena2 = 0 - w_ena = 1 - w_addr = 1 > 同时对应的寄存器使能,与读写相关信号也被确定下来。在这条 addi 指令中,读口一的使能信号 r_ena1 = 1,表示可读,并且读地址为 s1,也就是 r_addr1=0 。读口二的使能信号 r_ena2=0,不允许读。写使能信号为一 w_ena=1,表示可写。 - **获取操作数 op1, op2** **执行** - **根据指令操作码,执行相应操作** - INST_ADD: - 进行加法计算 op1+op2 > 6. 此时可以通过读寄存器和立即数的符号扩展,分别得到操作数一 op1 和操作数二 op2,他们被传送到执行模块用来进行运算。执行模块,根据接收到的指令操作码来进行相应的操作。在这条指令中,操作码是加法操作码,因此执行加法操作也就是 0+1,得到的结果为 1。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155340_1abb2edd_5631341.png" title="6 2022-02-07 15-45-06 的屏幕截图.png"></p> **取指** if_stage.v ``` always@( posedge clk ) begin if( rst == 1'b1 ) begin pc <= `ZERO_WORD ; end else begin pc <= pc + 4; end end ``` > 7. 嗯对应代码,我们再来看一遍。首先在取值模块中,每过一个时钟周期,程序计数计PC的值加四。 **译码:解析指令** id_stage.v ``` assign opcode = inst[6 : 0]; assign rd = inst[11 : 7]; assign func3 = inst[14 : 12]; assign rs1 = inst[19 : 15]; assign imm = inst[31 : 20]; ``` > 进入译码模块后,译码模块通过取出指令的不同字段,实现了对指令的解析。 **译码:识别指令** ``` wire inst_addi = ~opcode[2] & ~opcode[3] & opcode[4] & ~opcode[5] & ~opcode[6] & ~func3[0] & ~func3[1] & ~func3[2]; ``` > 然后根据 opcode 高5位和 Func3 确定这是一条addi 指令。不考察 opcode 的低两位是因为在 RISC-V 中所有 opcode 的低两位均为 11。 **译码:确定类型/操作码** ``` // arith inst: 10000; logic: 01000; // load-store: 00100; j: 00010; sys: 000001 assign inst_type[4] = ( rst == 1'b1 ) ? 0 : inst_addi; assign inst_opcode[0] = ( rst == 1'b1 ) ? 0 : inst_addi; assign inst_opcode[1] = ( rst == 1'b1 ) ? 0 : 0; assign inst_opcode[2] = ( rst == 1'b1 ) ? 0 : 0; assign inst_opcode[3] = ( rst == 1'b1 ) ? 0 : 0; assign inst_opcode[4] = ( rst == 1'b1 ) ? 0 : inst_addi; assign inst_opcode[5] = ( rst == 1'b1 ) ? 0 : 0; assign inst_opcode[6] = ( rst == 1'b1 ) ? 0 : 0; assign inst_opcode[7] = ( rst == 1'b1 ) ? 0 : 0; ``` > 因为这是一条 addi 指令,所以可以得到这是一条 I 型指令,并且其指令操作码为加法操作码。在这个示例代码中,加法操作码为 11,这是我自己规定的,你也可以规定别的值,只要保证不同操作的操作码不同即可。 **译码:产生寄存器读/写信号** ``` assign rs1_r_ena = ( rst == 1'b1 ) ? 0 : inst_type[4]; assign rs1_r_addr = ( rst == 1'b1 ) ? 0 : ( inst_type[4] == 1'b1 ? rs1 : 0 ); assign rs2_r_ena = 0; assign rs2_r_addr = 0; assign rd_w_ena = ( rst == 1'b1 ) ? 0 : inst_type[4]; assign rd_w_addr = ( rst == 1'b1 ) ? 0 : ( inst_type[4] == 1'b1 ? rd : 0 ); ``` > 因为这是一条I型指令,因此寄存器读口1可读,读口2不可读,写口使能为1,因此寄存器读口, **译码:获取操作数** ``` assign op1 = ( rst == 1'b1 ) ? 0 : ( inst_type[4] == 1'b1 ? rs1_data : 0 ); assign op2 = ( rst == 1'b1 ) ? 0 : ( inst_type[4] == 1'b1 ? { {52{imm[11]}}, imm } : 0 ); ``` > 8. 因此译码模块通过寄存器的读数据和立即数的符号扩展,得到操作数1和操作数2。 **执行** ``` case( inst_opcode ) `INST_ADD: begin rd_data = op1 + op2; end default: begin rd_data = `ZERO_WORD; end endcase ``` > 在执行阶段,执行模块发现接收到的指令操作码为加法操作码,于是执行加法计算,从而得到计算结果。 - 单周期 RISCV CPU:在一个周期内得到执行结果 - 取出指令后,其译码、执行过程均为组合逻辑 - 思考:如果是五级流水线,得到执行结果需要几个时钟周期?需要怎样添加时序逻辑? > 需要强调一下,这是一个单周期的 RISCV cpu,他在取出指令后,译码和执行过程均为组合逻辑,因此在一个周期内就得到了执行结果。那在一生一芯中,我们要求实现五级流水线,那么,在这种情况下,得到执行结果需要几个周期?应该怎样添加时序逻辑呢?这个留给大家去思考。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155352_fa2a8789_5631341.png" title="7 2022-02-07 15-46-42 的屏幕截图.png"></p> - **IF: 取指** 将指令从存储器中读出 - **ID: 译码** 翻译指令,得到源/目的操作数的相关信息 - **EX: 执行** 执行指令对应的操作 - **MEM: 访存** 对存储器进行读/写 - **WB: 写回** 将执行结果写入通用寄存器 流水线:通过提高 CPU 中 **各个部件的利用率** ,提高其工作效率 > 9. 接下来简要介绍一下经典的五级流水线,他的五个阶段分别是取指、译码、执行、访存和写回。其中,取指是将指令从存储器中读出,译码阶段翻译指令,从而得到原操作数和目的操作数的相关信息。执行阶段用来执行指令对应的操作,比如刚才的加法。访存实现对存储器的读写,而写回阶段则将执行结果写入通用寄存器。在处理器中,流水线通过提高 **各个部件的利用率** 来提高工作效率。  > 10. 嗯,举个例子,对于指令1、指令2和指令3,在顺序执行的模式下,每个指令需要五个周期,那么三条指令就需要15个周期。如果使用流水线的执行模式,那么三条指令,只需要七个周期就可以完成,加速比是15除以7达到2倍多。在顺序执行时,取指、译码、执行、访存、写回依次进行。每个周期只有一个部件在进行工作。而在流水线模式下,指令1在执行阶段时,同时指令2在译码,同时指令3在取值。所以说流水线是通过提高cpu中部件的利用率来提高工作效率的。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155409_dbf25ce0_5631341.png" title="8 2022-02-07 15-47-50 的屏幕截图.png"></p> **单周期代码使用指南** - 只实现了取指、译码和执行阶段, **请同学们完成访存和写回阶段** - 添加时序逻辑, **实现五级流水线** - **思考:** - **数据相关:** RAW(写后读),WAR(读后写),WRW(写后写)哪种需要关注? - **控制相关:** 分支指令如何处理? - 流水线 **暂停(stall)** 、流水线 **删除(kill)** 怎样实现? - 单周期代码仅供参考 **实现访存、写回阶段后,将寄存器写使能、写地址和写数据逐级下传到写回阶段** **id_stage.v** ``` assign rd_w_ena = ( rst == 1'b1 ) ? 0 : inst_type[4]; assign rd_w_addr = ( rst == 1'b1 ) ? 0 : ( inst_type[4] == 1'b1 ? rd : 0 ); ``` **exe_stage.v** ``` output reg [`REG_BUS]rd_data ``` **功能验证流程** - 实现新指令 - 在AM上编译得到.bin测试文件 - Verilator 仿真验证 - Difftest 调试 > 11. 最后我说明一下单周期代码的使用指南。这个示例代码只实现了取指、译码和执行, **请同学们完成访存和写回阶段** 的实现。刚才在介绍五级流水线的时候说过,写回阶段会将计算结果写回到寄存器中,所以在同学们实现了访存和写回模块中,请将寄存器读使能、读地址和读数据信号向后逐级下传,传到写回阶段,并在写回阶段进行寄存器的写操作。还有,请在在适当的地方添加时序逻辑, **实现五级流水线** 。最后请 **思考** 在三种数据相关中,我们需要重点关注哪一种,是写后读读后写还是写后写?另外再遇到分支和跳转指令时应该怎样处理,什么情况下需要流水线 **暂停** 和流水线 **删除** ?嗯,他们应该怎样实现?最后,最后需要说明一下,单周期示例代码,仅供参考,欢迎大家一起讨论。 <p><img width="69%" src="https://images.gitee.com/uploads/images/2022/0207/155419_3fe37d5b_5631341.png" title="9 2022-02-07 15-48-47 的屏幕截图.png"></p> 好,谢谢大家。
评论 (
4
)
登录
后才可以发表评论
状态
已完成
待办的
进行中
已完成
已关闭
负责人
未设置
标签
未设置
标签管理
里程碑
未关联里程碑
未关联里程碑
Pull Requests
未关联
未关联
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
未关联
分支 (
-
)
标签 (
-
)
开始日期   -   截止日期
-
置顶选项
不置顶
置顶等级:高
置顶等级:中
置顶等级:低
优先级
不指定
严重
主要
次要
不重要
参与者(1)
Scala
1
https://gitee.com/RV4Kids/NutShell.git
git@gitee.com:RV4Kids/NutShell.git
RV4Kids
NutShell
NutShell
点此查找更多帮助
搜索帮助
Git 命令在线学习
如何在 Gitee 导入 GitHub 仓库
Git 仓库基础操作
企业版和社区版功能对比
SSH 公钥设置
如何处理代码冲突
仓库体积过大,如何减小?
如何找回被删除的仓库数据
Gitee 产品配额说明
GitHub仓库快速导入Gitee及同步更新
什么是 Release(发行版)
将 PHP 项目自动发布到 packagist.org
评论
仓库举报
回到顶部
登录提示
该操作需登录 Gitee 帐号,请先登录后再操作。
立即登录
没有帐号,去注册