Skip to content

Latest commit

 

History

25 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CAPD 大作业一

GitLab 设置

  1. 登录gitlab : http://8.152.206.202
  1. gitlab username 以及 昵称 默认不要更改
  2. 为使大家顺利开展课程学习,尤其是较为熟练地使用实验项目所需的硬件描述语言和基本工具,助教团队还编写了相关复(预)习材料:

git commit 规范: https://www.ruanyifeng.com/blog/2016/01/commit_message_change_log.html

Install S-IDE

  1. 个人笔记本或电脑配置要求

    • CPU : 4核 以上
    • 完成代码的编写的同时要保存必要的仿真波形文件,确保电脑硬盘有80GB以上的空闲空间 100GB最佳
    • 内存:8GB以上最佳
  2. Get Image

    • 本学期的组成原理研讨课需要在个人电脑中运行一个由我们教学团队定制并已安装本学期研讨课所需基本软硬件开发工具环境的Linux容器。该容器镜像文件名称为ucas-codos20250115.tar
  3. Install Doker and Debug Docker

  4. Deploying an Image

    • docker load -i ucas-codos20250115.tar
    • docker run -d --name cod-lab -p 3000:3000 -v serve-ide:/home/serve-ide ucas-cod-os:20250115
  5. 如果电脑开机后 运行container

    • docker start cod-lab
  6. container运行后 打开浏览器 http://localhost:3000 即可进入 S-IDE

  7. 同学们可阅读以下文档来熟悉我们提供的S-IDE集成开发环境,并掌握S-IDE和课程实践教学云平台配套使用的方法:http://8.152.206.202/ucas-cod-2021-dev/hello-side-and-gitlab 该文档包含以下内容:

    1. S-IDE的设置
    2. S-IDE Extensions 导入,使用扩展可以语法高亮并提供编译快捷键 alt text
    3. 使用扩展程序查看波形图教程
    4. 使用git clone账户仓库
      alt text

Practice-1-0 8bit加法器 组合逻辑电路设计

  1. 编写Verilog HDL代码,观察云平台上自动完成所有的FPGA开发及部署运行步骤
  2. 细节
    alt text

每次编写代码前 ,pull一下upstream 端的文件,保证本地仓库始终是最新commit
git pull upstream master && git switch develop && git merge master

1.Verilog HDL 代码编写(编写RTL代码Register Transfer Level)

  • 打开fpga/design/ucas-cod/hardware/sources/example/adder.v 编写代码
  • 补充bbit加法器代码 ,编辑commit message :feat(RTL_ADD):implement a 8bit unsigned adder logic alt text 2.verilog HDL编译仿真
  1. 在S-IDE中启动逻辑行为仿真(下面make命令封装了原本复杂的仿真过程和逻辑,实现一步到位)
    cd ~/cod-lab && make FPGA_PRJ=ucas-cod FPGA_BD=nf SIM_TARGET=example bhv_sim
上述命令解释:
make FPGA_PRJ=ucas-cod FPGA_BD=nf SIM_TARGET=example bhv_sim

make 是一个常用的自动化构建工具。它会查找项目中的 Makefile 文件,并执行其中的规则。

FPGA_PRJ=ucas-cod:设置一个变量,指定 FPGA 项目的名称或配置(例如,设置为 ucas-cod 项目)。

FPGA_BD=nf:设置一个变量,可能指定目标 FPGA 板卡或配置(nf 可能代表 Not Found 或某个特定的板卡代号)。

SIM_TARGET=example:指定本次仿真的目标模块或测试文件(根据您的文件结构,可能就是您刚刚修改的 adder.v 模块所对应的测试平台)。

bhv_sim:这是 make 命令要执行的目标 (Target),代表 Behavioral Simulation(行为仿真)。执行此目标会调用仿真工具(如 iverilog,如 PPT 所述),编译 Verilog 代码和 Testbench,并运行仿真。

alt text 2. 如果不适用make,原始的仿真流程如下:

  • 首先编写 verilog代码
  • 然后编写 verilog 激励文件(也就是testbench)
  • iverilog -o desiation.vvp verilog.v verilog_test.v编译生成可执行文件
  • 执行上面生成的可执行文件,会生成一个.vcd后缀的波形仿真文件
  • 可以使用Extension打开,也可以使用GtkWave打开查看波形图
  • 总结: 设计 --> 激励 ---> 编译 ----> 仿真执行---->波形分析 gtkwave 仿真结果:
  1. 竖直方向每一个信号都有一个对应的波形图,分别表示输入信号和输出信号。
  2. 这是8bit加法器所有的测试数据形成的波形图
  3. 波形图左侧的数据 是十六进制表示的测试数据 不是十进制 20 表示的是十进制的32 alt text

完整的工作流程 alt text

实验项目1 处理器核功能部件设计-Register File and ALU

alt text alt text
alt text
alt text

RegisterFile

注意语法兼容问题

  1. 代码:
`timescale 10 ns / 1 ns

`define DATA_WIDTH 32  // 数据位宽 32bit
`define ADDR_WIDTH 5  // 表示32个寄存器地址 2**5

module reg_file(
	input                       clk,  //时钟信号
	input  [`ADDR_WIDTH - 1:0]  waddr, //  write reg
	input  [`ADDR_WIDTH - 1:0]  raddr1, // read data reg 1
	input  [`ADDR_WIDTH - 1:0]  raddr2,  //read data reg 2
	input                       wen,    // write enable
	input  [`DATA_WIDTH - 1:0]  wdata,   // write back data
	output [`DATA_WIDTH - 1:0]  rdata1,    // output 1 data
	output [`DATA_WIDTH - 1:0]  rdata2    // output 2 data
);

	// TODO: Please add your logic design here
	/*
	two features:
	1. RegisterFile[5`d0] = 0  (forver)
	2. RegisterFile[5`d0] !<= data
 	*/
	// define register_file 
	localparam [`DATA_WIDTH - 1:0] ZERO = 0;  // 不能使用 `DATA_WIDTH{1`b0} 因为不兼容!!
	localparam RegisterFileLength = (1 << `ADDR_WIDTH) - 1;
 	reg [`DATA_WIDTH - 1:0] RegisterFile [0 : RegisterFileLength];
	// clk is posedge and wen is true    wdata(32bit data) into the reg_file 
	always @(posedge clk) begin
		if(wen && (|waddr)) begin
			RegisterFile[waddr] <= wdata;
		end
	end

	// output1 and 2
	//1. three mu operate
	// assign rdata1 = (raddr1 == {`ADDR_WIDTH{1`b0}})? {`DATA_WIDTH{1`b0}} : RegisterFile[raddr1];
	// assign rdata2 = (raddr2 == {`ADDR_WIDTH{1`b0}})? {`DATA_WIDTH{1`b0}} : RegisterFile[raddr2];
	//2. use logic operate  (Reductioin Operators)
	// if raddr bit exe OR result is 0 , raddr == 0
	assign rdata1 = (|raddr1)? RegisterFile[raddr1]:ZERO;
	assign rdata2 = (|raddr2)? RegisterFile[raddr2]:ZERO;

	
endmodule

alt text 2. 仿真: make FPGA_PRJ=ucas-cod FPGA_BD=nf SIM_TARGET=reg_file bhv_sim alt text 3. download fpga/sim_out/reg_file/dump.vcd 4. GTKwave 打开 dump.vcd 文件 alt text
5. 代码没问题以后push到develop branch 6. 然后pull request到master branch alt text
7. 推送以后无论是develop还是master CI/CD都会自动执行

ALU

理解关键


  1. 在计算机中,所有的数据都是二进制序列,没有有符号和无符号之分
  2. ALU 接收到的操作数,无论是正数、零还是负数,都以补码的二进制形式编码在 32 个比特位中。ALU 对这些位序列一视同仁,只根据 ALUop 信号进行位操作或加法运算。
  3. ALU 对操作数的处理都是补码形式的二进制位序列,不区分有符号和无符号。
  4. 无符号数相加Overflow是没有意义的,因为即使出现了溢出,但是数据还是有意义的,所以CarryOut来表示是否溢出
  5. 有符号数ADD操作Overflow判断:异号相加永不溢出,同号相加变号溢出
  6. 有符号数SUB操作overflow判断:异号相减变号溢出,同号相减不变号溢出
  7. 在减法中,减法借位是通过将被减数的补码取反加1得到的,所以减法借位的判断是:被减数的补码取反加1的结果是否为0。 ~CarryOut 就是借位(减法中定义为借位)
  8. 为什么在ALU中并不区分有符号数和无符号数,在判断溢出的时候还是将最高位看作是符号位?(换句话说为什么依靠最高位能判断溢出呢)
    ans:
  • 这依赖于补码的循环特性,比如 8bit补码中 0111 1111 表示有符号最大值127,127 + 1= 1000 0000 表示的不是128而是 0,这就是补码的循环特性。当溢出的时候最高位会变化
  • 正数溢出:从 $0 \rightarrow 1$ (正数加正数)
  • 负数溢出:从 $1 \rightarrow 0$ (负数加负数)

为什么减法的借位标志(Borrow Flag)等于加法的进位标志(Carry Out)的取反?

这是一个体系结构和数学的完美结合的问题。减法借位之所以能够转化为加法进位,是因为补码系统的精妙设计,使得减法和加法在环绕(Wrap-around)特性上形成了互补关系。

核心原理:补码系统的模运算

$N$ 位的补码系统中,任何数字的运算都是在 $2^N$ 这个模数下进行的。

  1. 减法的定义: $$A - B$$
  2. 补码的定义: $$-B = \text{Two's Complement of } B$$ $$-B = 2^N - B$$
  3. 硬件实现: 减法转化为加法: $$A - B \equiv A + (2^N - B) \quad (\text{mod } 2^N)$$

证明:借位和进位为什么互反

我们来看执行 $A + (\sim B + 1)$ 后的最终进位 $C_N$(在您的 32 位系统中是 $C_{32}$)。

1. 情况一:$A \ge B$(无借位)

从无符号数来看,减法 $A - B$ 不需要借位。

  • 运算: $A + (\sim B + 1)$
  • 结果: 因为 $A \ge B$,所以 $A$ 加上 $-B$ 的补码,其数值大于或等于 $2^N$
  • 进位 $C_N$ 必然产生环绕进位,即 $C_N = \mathbf{1}$
  • Borrow 标志: 由于无需借位,我们期望 Borrow 标志为 $\mathbf{0}$
  • 关系: $\text{Borrow} = \sim C_N$ (即 $0 = \sim 1$)

2. 情况二:$A < B$(有借位)

从无符号数来看,减法 $A - B$ 需要借位。

  • 运算: $A + (\sim B + 1)$
  • 结果: 因为 $A &lt; B$,所以 $A$ 加上 $-B$ 的补码,其数值小于 $2^N$
  • 进位 $C_N$ 不会产生环绕进位,即 $C_N = \mathbf{0}$
  • Borrow 标志: 由于需要借位,我们期望 Borrow 标志为 $\mathbf{1}$
  • 关系: $\text{Borrow} = \sim C_N$ (即 $1 = \sim 0$)

总结:

借位能够转换为加法进位,是因为在补码加法 $A + (\sim B + 1)$ 中:

  • 有进位 $C_{32}=1$,恰好对应了无需借位($A \ge B$)。
  • 无进位 $C_{32}=0$,恰好对应了需要借位($A < B$)。

因此,通过对最终加法进位 $C_{32}$ 取反,我们就能得到减法意义上的 借位标志 (Borrow Flag)。这使得 ALU 可以复用同一个加法器来完成两种截然不同的算术功能。

这正是您的 Verilog 代码中 CarryOutReg = ~Calc33bitResult_SUB[32]; 这一行代码的数学基础。


alt text
alt text
alt text
OveRflow

  1. 有符号数判断溢出: 两个整数相加得到负数,两个负数相加得到正数,符号位不同则溢出
  2. 无符号溢出:最高位进位到下一位(加法结果比被加数小,减法结果比被减数大)
    alt text
    ALLU
    alt text

为什么在代码中不能使用 - < >运算符? 硬件设计者不会为加法、减法、小于比较(SLT)分别设计独立的电路,而是通过控制信号(如加/减控制信号)来复用一套加法器硬件

为什么有符号数和无符号数能够使用一个加法器实现?? 因为补码系统将减法转化为加法,而且无符号加法和有符号加法的底层硬件操作是完全相同的。

运算类型 逻辑 硬件执行
无符号加法 A+B 执行二进制加法
有符号加法 A+B 执行二进制加法
有符号减法 A−B →A+(∼B+1),执行二进制加法
无符号减法 A−B →A+(∼B+1),执行二进制加法

加法器总是执行相同的二进制加法操作。它并不知道数据是有符号还是无符号。
既然硬件操作相同,那有符号和无符号运算的区别体现在哪里呢?答案在于:我们如何解释加法器的输出位和标志位。

标志位 在无符号运算中如何解释 在有符号运算中如何解释
Carry Out (进位) 无符号溢出。如果 C32​=1,表示结果超出了 232−1,结果错误。 无用。这个标志在有符号运算中没有意义。
Overflow (溢出) 无用。无符号运算不使用此标志。 有符号溢出。用于判断结果是否超出了 −231 到 231−1 的范围,结果错误。
Zero (全零) 通用。 只要结果是 0,无论是无符号还是有符号,都置位。 通用。 只要结果是 0,无论是无符号还是有符号,都置位。

  1. 所有数据都是纯粹的、无格式的二进制位序列。 数据的解释权完全取决于执行运算的指令和操作这些位的逻辑电路。
  2. ALU接收到的数据都是补码形式的二进制位序列。
  3. ALU 接收到的操作数,无论是正数、零还是负数,都以补码的二进制形式编码在 32 个比特位中。ALU 对这些位序列一视同仁,只根据 ALUop 信号进行位操作或加法运算。
  4. 比如如果A = 10 B = -5
A的原码 ::0000 0000 0000 0000 0000 0000 0000 1010
B的原码 ::1111 1111 1111 1111 1111 1111  1111 0101
B的补码:: 1111 1111 1111 1111 1111 1111 1111 1011
A+ B= 0000 0000 0000 0000 0000 0000 0101 = 5D
进位是1,一直进位到[31:4]全是0 CarryOut =1 


在计算 Overflow 标志时,ALU 的附加逻辑电路就是将最高位($A_{31}, B_{31}, S_{31}$)看作补码中的符号位来处理的。这是因为 Overflow 标志的定义就是用来检测有符号运算是否越界,而补码系统中的越界判断完全依赖于最高位符号位的变化。

二进制位序列本身即使是补码序列根本没有符号位的定义和区分,而是在判断溢出的时候将最高位看作是符号位处理
SUB溢出判断

  1. SUB操作A-B = A + (~B) + 1
  2. 看作是加法,那么加法溢出判断是:异号相加永不溢出,同号相加变号溢出
  3. 减法是通过将B转换为补码进进行相加,那么减法溢出判断就是:异号相减变号溢出,同号相减永不溢出

实验项目二 功能性Risc-V 处理器设计

alt text
目标:
对照risc-v手册,实现并调试自研单、多周期处理器
alt text
本次实验要实现下面指令:
alt text

单周期实验目标模块

注意
Risc-V的指令构成与MIPS指令并不一样:
I-Type 指令:
alt text
根据这个表来扩展立即数
alt text

查看文献总结37条指令执行公式:

  • 通过I-type格式的特殊化来实现常数位移操作(Shifts by a constant)。
    • rs1 是被操作数,imm 是位移量,shift mount 在 imm 的低 5 位中。
    • The right shift type is encoded in a high bit of the I-immediate.
    • 0 表示逻辑右移(逻辑右移是在高位补 0),1 表示算术右移(算术右移是在高位补符号位)。

I-type 指令执行公式:

  1. ADDI : rd = rs1 + imm (sign_extend);
  2. SLTI :rd = rs1 < imm (sign_extend)? 1 : 0;
  3. SLTIU: rd = rs1 < imm (unsign_extend)? 1 : 0;
  4. ANDI : rd = rs1 & imm (sign_extend);
  5. ORI : rd = rs1 | imm (sign_extend);
  6. XORI : rd = rs1 ^ imm (sign_extend);(rs1 ^ -1 会得到 ~rs1)
  7. SLLI : rd = rs1 << imm[4:0] (sign_extend);
  8. SRLI : rd = rs1 >> imm[4:0] (sign_extend);

right shift type is encoded in the high bit of the I-immediate.

  1. SRAI : rd = rs1 >>> imm[4:0] (sign_extend);

the original sign bit is copied into the vacated upper bits

U-type 指令执行公式:
10. LUI : rd = imm[31:12] << 12 (sign_extend);

LUI places the U-immediate value in the top 20 bits of the destination register rd, filling in the lowest 12 bits with zeros.

  1. AUIPC : rd = imm + PC (sign_extend); alt text

R-type : rd = rs1 op rs2 (sign_extend);

All operations read the rs1 and rs2 registers as source operands and write the result into register rd.The funct7 and funct3 fields select thetype of operation.

  1. ADD: rd = rs1 + rs2 (sign_extend);
  2. SUB: rd = rs1 - rs2 (sign_extend);
  3. SLT: rd = rs1 < rs2 (sign_extend)? 1 : 0;
  4. SLTU : rd = rs1 < rs2 (unsign_extend)? 1 : 0;
  5. AND: rd = rs1 & rs2 (sign_extend);
  6. OR: rd = rs1 | rs2 (sign_extend);
  7. XOR: rd = rs1 ^ rs2 (sign_extend);
  8. SLL: rd = rs1 << rs2[4:0] (sign_extend);
  9. SRL: rd = rs1 >> rs2[4:0] (sign_extend);
  10. SRA: rd = rs1 >>> rs2[4:0] (sign_extend);

SLL, SRL, and SRA perform logical left, logical right, and arithmetic right shifts on the value in register rs1 by the shift amount held in the lower 5 bits of register rs2.
J-type 指令执行公式:

  1. JAL : rd = PC + 4; Target PC = imm + PC (sign_extend);

通过 $imm$ 扩展到 32 位,然后左移 1 位,与当前 $PC$ 相加得到下一个 $PC$,就是目标跳转地址。
然后当前 $PC+4$ 存储在 $rd$ 寄存器中。

  1. JALR : rd = PC + 4; PC = (rs1 + imm)&~1 (sign_extend); I-type Instruction 间接跳转指令

B-type 指令执行公式(与 Zero 相关 ): 24. BEQ : if (rs1 == rs2) then PC = PC + imm else PC = PC + 4 (sign_extend and unsigned); 25. BNE : if (rs1 != rs2) then PC = PC + imm else PC = PC + 4 (sign and unsign_extend); 26. BLT : if (rs1 < rs2) then PC = PC + imm else PC = PC + 4 (sign_extend); 27. BGE : if (rs1 >= rs2) then PC = PC + imm else PC = PC + 4 (sign_extend; 28. BLTU : if (rs1 < rs2) then PC = PC + imm else PC = PC + 4 (unsign_extend); 29. BGEU : if (rs1 >= rs2) then PC = PC + imm else PC = PC + 4 (unsign_extend);

Note, BGT, BGTU, BLE, and BLEU can be synthesized by reversing the operands to BLT, BLTU, BGE, and BGEU, respectively.

Load and Store Instructions

Load and store instructions transfer a value between the registers and memory.
Loads are encoded in the I-type format and stores are S-type.
Loads copy a value from memory to register rd. Stores copy the value in register rs2 to memory.

$$\text{Effective Address} = rs1 + \text{SignExtend}(\text{12-bit offset})$$

  1. LW: rd = mem[rs1 + imm] ; 数据无需扩展
  2. LH: rd = mem[rs1 + imm] ; 数据符号扩展到32位
  3. LB: rd = mem[rs1 + imm] ; 符号扩展到32位
  4. LHU: rd = mem[rs1 + imm] ; 数据零扩展到32位
  5. LBU: rd = mem[rs1 + imm] ; 数据零扩展到32位
  6. SW: mem[rs1 + imm] = rs2 ; 数据无需扩展
  7. SH: mem[rs1 + imm] = rs2[15:0];
  8. SB: mem[rs1 + imm] = rs2[7:0] ;

simple-cpu 流程图

make FPGA_PRJ=ucas-cod FPGA_BD=nf SIM_TARGET=simple_cpu SIM_DUT=riscv32:single_cycle WORKLOAD=simple_test:basic:memcpy SIM_TOP=simple_cpu_test bhv_sim

多周期CPU处理器设计

lab_env.yml 编辑

variables:
  # TARGET_DESIGN can be "example", "alu", "reg_file", "simple_cpu", "custom_cpu"...
  TARGET_DESIGN: "custom_cpu"

  # CPU_ISA must be either "mips" or "riscv32"
  CPU_ISA: "riscv32"

  # SIM_DUT_TYPE can be "single_cycle", "multi_cycle", "turbo"
  SIM_DUT_TYPE: "multi_cycle"

完成单周期改多周期很简单,关键点就是增加中间寄存器,然后完成一个状态机!就可以了

make FPGA_PRJ=ucas-cod FPGA_BD=nf SIM_TARGET=custom_cpu SIM_DUT=riscv32:multi_cycle WORKLOAD=simple_test:basic:memcpy bhv_sim_verilator

先想清楚:单周期 和 多周期 到底差在哪
单周期每个周期:

  • PC 决定 Instruction
  • 组合逻辑:译码 → ALU → 访存 → 写回
  • 下个周期 PC+4 / 分支 / 跳转

多周一条指令:

  • 周期1:取指 (IF)
  • 周期2:译码+读寄存器(ID)
  • 周期3:执行/计算地址/判断分支(EX)
  • 周期4:访存(MEM,对 lw/sw)
  • 周期5:写回(WB,对需要写回的指令)

关键变化:

  1. 多几个“临时寄存器”,把各阶段结果存住

  2. 加一个 FSM 状态机,状态 = 现在在第几步(IF/ID/...)

  3. 控制信号由“opcode 决定” → “状态 + opcode 决定”

`timescale 10ns / 1ns

module custom_cpu(
	input         clk,
	input         rst,

	//Instruction request channel
	output [31:0] PC,    			// 你当前想取的指令地址
	output        Inst_Req_Valid,  // 你准备好发出取指请求
	input         Inst_Req_Ready,  // 外部指令存储器准备好接收该请求  

	//Instruction response channel
	input  [31:0] Instruction,  //32位指令
	input         Inst_Valid,		// 表示“Instruction 有效”
	output        Inst_Ready,  // 表示“我已经准备好接收指令”

	//Memory request channel
	output [31:0] Address,  //lw/sw 的地址 
	output        MemWrite, // 写请求
	output [31:0] Write_data,//sw 写入数据
	output [ 3:0] Write_strb,// 写使能,每个字节是否写 字节使能(sw、sb、sh)
	output        MemRead,  // 读请求
	input         Mem_Req_Ready, //表示可接收访问请求

	//Memory data response channel
	input  [31:0] Read_data,  //返回的数据
	input         Read_data_Valid,  //表示数据有效
	output        Read_data_Ready,  //你准备好接收

	input         intr,  

	output [31:0] cpu_perf_cnt_0,  //性能计数器
	output [31:0] cpu_perf_cnt_1,
	output [31:0] cpu_perf_cnt_2,
	output [31:0] cpu_perf_cnt_3,
	output [31:0] cpu_perf_cnt_4,
	output [31:0] cpu_perf_cnt_5,
	output [31:0] cpu_perf_cnt_6,
	output [31:0] cpu_perf_cnt_7,
	output [31:0] cpu_perf_cnt_8,
	output [31:0] cpu_perf_cnt_9,
	output [31:0] cpu_perf_cnt_10,
	output [31:0] cpu_perf_cnt_11,
	output [31:0] cpu_perf_cnt_12,
	output [31:0] cpu_perf_cnt_13,
	output [31:0] cpu_perf_cnt_14,
	output [31:0] cpu_perf_cnt_15,

	output [69:0] inst_retire
);

/* The following signal is leveraged for behavioral simulation, 
* which is delivered to testbench.
*
* STUDENTS MUST CONTROL LOGICAL BEHAVIORS of THIS SIGNAL.
*
* inst_retired (70-bit): detailed information of the retired instruction,
* mainly including (in order) 
* { 
*   reg_file write-back enable  (69:69,  1-bit),
*   reg_file write-back address (68:64,  5-bit), 
*   reg_file write-back data    (63:32, 32-bit),  
*   retired PC                  (31: 0, 32-bit)
* }
*
*/
  wire [69:0] inst_retire;
  


// TODO: Please add your custom CPU code here

endmodule

输入输出接口:Instruction Request Channel(指令请求通道)

  1. 只有当 Valid = 1 且 Ready = 1 时,取指请求真正发送出去
  2. assign Inst_Req_Valid = (state == S_IF); // 只在取指阶段发请求

Instruction Response Channel(指令响应通道)

  1. assign Inst_Ready = (state == S_IF); // 只在取指阶段接受指令

Memory Request Channel(数据访问请求)

  1. 全部都是 Valid/Ready 协议,不握手 testbench 不工作。
  2. assign MemRead = (state == S_MEM) && is_load;
  3. assign MemWrite = (state == S_MEM) && is_store;
  4. assign Address = ALUOut; // EX 阶段算出来的地址
  5. assign Write_data = B; // store 写入的数据

Memory Response Channel(数据响应)

  1. assign Read_data_Ready = (state == S_MEM) && is_load;

中断

  1. COD 多周期 CPU 通常不要求处理中断,可以忽略。

性能计数器

inst_retire(70bit 指令提交信息)非常重要!

{ 
  wb_enable (69:69),  
  wb_addr   (68:64), 
  wb_data   (63:32),  
  retired_PC (31:0)
}

必须在 每条指令“完成”(即写回 WB 阶段) 发一个有效 inst_retire。


单周期处理器中,由于要在一个周期内同时进行取指、译码、执行、访存、写回,所以分为指令内存和数据内存,但是在多周期的处理器中不需要同时的进行阶段,所以不需要再对内存进行指令和数据上的区分。 alt text


遇到的问题

  1. no file
    alt text
    alt text

  2. 编译成功但是仿真失败 Inst_ready

%Error: Exiting due to 2 warning(s)
make: [fpga/design/ucas-cod/scripts/hardware.mk:44: bhv_sim_verilator] Error 1 (ignored)
fpga/sim_out/custom_cpu/custom_cpu +DUMP="fpga/sim_out/custom_cpu/dump.fst" +INITMEM="software/workload/ucas-cod/benchmark/simple_test/advanced/riscv32/sim/bit.mem" +TRACE_FILE="software/workload/ucas-cod/benchmark/simple_test/advanced/riscv32/trace/bit.trace"
=================================================
ERROR: at                    00ns, PC = 0x00000000.
Yours:     Read_data_Ready = 0x0
Reference: Read_data_Ready = 0x1
=================================================
[0] %Error: custom_cpu_test.v:170: Assertion failed in TOP.custom_cpu_test
%Error: fpga/design/ucas-cod/hardware/sim/custom_cpu/multi_cycle/custom_cpu_test.v:170: Verilog $stop
Aborting...
Aborted (core dumped)
make: *** [fpga/design/ucas-cod/scripts/hardware.mk:45: bhv_sim_verilator] Error 134

这是在global_test中S_INIT状态Inst_Ready和Read_data_Ready都为1,所以原本设置的逻辑要修改为下面代码逻辑(增加S_INIT状态置1的条件):
assign Inst_Ready = (current_state == S_IW) || (current_state == S_INIT);assign Read_data_Ready = (current_state == S_RDW) || (current_state == S_INIT);
这是通过波形图分析出来的,请看波形图(001表示状态是INIT,002是IF,以此类推):

alt text

高速缓存cache设计

lab_env.yml 编辑

variables:
  # TARGET_DESIGN can be "example", "alu", "reg_file", "simple_cpu", "custom_cpu"...
  TARGET_DESIGN: "custom_cpu"

  # CPU_ISA must be either "mips" or "riscv32"
  CPU_ISA: "riscv32"

  # SIM_DUT_TYPE can be "single_cycle", "multi_cycle", "turbo"
  SIM_DUT_TYPE: "turbo"

编辑脚本文件

编译:make FPGA_PRJ=ucas-cod FPGA_BD=nf SIM_TARGET=custom_cpu SIM_DUT=riscv32:turbo WORKLOAD=simple_test:basic:memcpy bhv_sim_verilator alt text
fpga/design/ucas-cod/hardware/sources/custom_cpu/arch_options.tcl
首先将icache设置为1 ,先完成icache的,然后再加入dcache,再将dcache设置为1
编辑结果:

set cpu_freq 100
set icache 1
set dcache 0
set simple_dma 0
set dnn_acc 0

完成icache之后重新编辑:

set cpu_freq 100
set icache 1
set dcache 1
set simple_dma 0
set dnn_acc 0

修改下面代码文件:

  1. fpga/design/ucas-cod/hardware/sources/custom_cpu/riscv32/custom_cpu.v

  2. 编写Cache顶层设计代码:

    • fpga/design/ucas-cod/hardware/sources/custom_cpu/cache/icache_top.v

    • fpga/design/ucas-cod/hardware/sources/custom_cpu/cache/dcache_top.v

  3.  tag和data array的代码已实现,可自行查看修改:

    • fpga/design/ucas-cod/hardware/sources/custom_cpu/cache/tag_array.v

    • fpga/design/ucas-cod/hardware/sources/custom_cpu/cache/data_array.v

  4. 写好代码执行仿真的时候,每次仿真之前都要删除custom_cpu文件和verilator文件

详细代码见:

  1. icache: icache code

  2. dcache: dcache code

  3. custom_cpu: custom code

push代码以后会有报错,这写错误都是因为 no such files ,先不用管继续后面第四个项目:
alt text

alt text

这是因为修改了perf_cnt.h的宏魔法,这个文件不需要动。

cache pipline 结果如下,顺利通过:

alt text

实验项目四:定制Risc-v处理器设计--外设与性能计数器访问

make FPGA_PRJ=ucas-cod FPGA_BD=nf SIM_TARGET=custom_cpu SIM_DUT=riscv32:multi_cycle WORKLOAD=simple_test:basic:memcpy bhv_sim_verilator UART : 通用异步串行收发器

修改lab_env.ymal 文件:

variables:
  # TARGET_DESIGN can be "example", "alu", "reg_file", "simple_cpu", "custom_cpu"...
  TARGET_DESIGN: "custom_cpu"

  # CPU_ISA must be either "mips" or "riscv32"  
  CPU_ISA: "riscv32"     //这里pdf说的是用mips 但是不 依然是用riscv32

  # SIM_DUT_TYPE can be "single_cycle", "multi_cycle", "turbo"
  SIM_DUT_TYPE: "multi_cycle"

该实验需要修改的文件有:

  1. software/workload/ucas-cod/benchmark/simple_test/common/printf.c 实现UART控制器传送字符串的功能

  2. software/workload/ucas-cod/benchmark/simple_test/microbench/bench.c 实现性能计数器访问功能

  3. 修改代码:software/workload/ucas-cod/benchmark/simple_test/common/perf_cnt.c

  4. 数据结构定义: software/workload/ucas-cod/benchmark/simple_test/include/perf_cnt.h

修改好代码以后编译:
make FPGA_PRJ=ucas-cod FPGA_BD=nf OS=phy_os ARCH=riscv32 workload

push :

        modified:   software/workload/ucas-cod/benchmark/simple_test/common/perf_cnt.c
        modified:   software/workload/ucas-cod/benchmark/simple_test/common/printf.c
        modified:   software/workload/ucas-cod/benchmark/simple_test/include/perf_cnt.h
        modified:   software/workload/ucas-cod/benchmark/simple_test/microbench/bench.c

不要修改perf_cnt.h,可能你会看到少一个花括号,这其实是宏的魔法不要补充括号,否则就会报错:
alt text

由于我本地没有riscv64-unknown-elf-gcc,所以本地无法执行make FPGA_PRJ=ucas-cod FPGA_BD=nf OS=phy_os ARCH=riscv32 workload也就无法搞到对应的benchmark(hello microbench),索性直接push,线上编译然后再下载编译产物。

alt text

**CAPD 大作业二 : Gem5Intro **

lab2 实验操作手册:lab2 实验操作手册

lab2.1 hello gem5

按照使用手册构建容器,然后编译:scons build/X86/gem5.opt -j 6

alt text

alt text

质因数分解代码

About

中国科学院大学计算机体系结构实践作业

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages