yatcpu实验报告

计算机组成原理课程实验报告

yatcpu源仓库 https://github.com/hrpccs/2022-fall-yatcpu-repo

完整的答案代码 https://github.com/CJL-sysu/2022-fall-yatcpu-repo

实验1-单周期CPU

InstructionFetch取址

填空代码

1
2
3
4
5
6
7
// lab1(InstructionFetch)
pc := pc + 4.U //指令的地址加4
//处理jump
when(io.jump_flag_id) {
pc := io.jump_address_id
}
// la1(InstructionFetch) end

测试用例

该测试用例测试取址操作的正确性,指定程序入口地址为entry=0x1000,随后进行100次循环,每次循环都随机确定当前指令是否为跳转指令jump

  • 如果不是跳转指令,则检查指令地址是否正常+4
  • 如果是跳转指令,则检查指令地址是否跳转到entry

波形图

image-20231029103609402

可以观察到图中信号io_instruction_address[31:0]的变化,在时钟的上升沿到来时,随机地跳转到0x00001000或比原来的值大4的值,和预期相符

InstructionDecoder译码

填空代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// lab1(InstructionDecode)
//补充为 io.ex_aluop2_source、io.memory_read_enable、io.memory_write_enable、io.wb_reg_write_source 四个控制信号赋值的代码
//io.ex_aluop2_source
io.ex_aluop2_source := Mux(
opcode === InstructionTypes.L || opcode === InstructionTypes.I || opcode === Instructions.jalr
|| opcode === Instructions.jal || opcode === InstructionTypes.S || opcode === InstructionTypes.B ||
opcode === Instructions.lui || opcode === Instructions.auipc,
ALUOp2Source.Immediate,
ALUOp2Source.Register
)
//io.memory_read_enable
io.memory_read_enable := Mux(
opcode === InstructionTypes.L , 1.U(1.W) , 0.U(1.W)
)
//io.memory_write_enable
io.memory_write_enable := Mux(
opcode === InstructionTypes.S , 1.U(1.W) , 0.U(1.W)
)
//io.wb_reg_write_source
io.wb_reg_write_source:=RegWriteSource.ALUResult //默认等于0
when(opcode === InstructionTypes.RM || opcode === InstructionTypes.I
|| opcode === Instructions.lui || opcode === Instructions.auipc){
io.wb_reg_write_source := RegWriteSource.ALUResult
}
.elsewhen(opcode === InstructionTypes.L){
io.wb_reg_write_source := RegWriteSource.Memory
}
.elsewhen(opcode === Instructions.jal || opcode === Instructions.jalr){
io.wb_reg_write_source := RegWriteSource.NextInstructionAddress
}
// lab1(InstructionDecode) end

测试用例

测试用例总共测试了三条不同种类的指令,分别是S型、luiadd 指令

对于S型指令

测试的指令为0x00a02223L,对应二进制编码00000000101000000010001000100011

期望结果为aluop1_sourceRegisteraluop2_sourceImmediate

image-20231027073124564

31-25 24-20 19-15 14-12 11-7 6-0
imm[11:5] rs2 rs1 func3 imm[4:0] opcode
0000000 01010 00000 010 00100 0100011

期待regs_reg1_read_address为rs1的编号,即0,regs_reg2_read_address为rs2的编号,即10

对于lui指令

lui指令的功能是将16位立即数填充到目标寄存器的高16位,所以第一个操作数数取零号寄存器,即regs_reg1_read_address取0,ex_aluop1_source取Register,第二个操作数取立即数,ex_aluop2_source取Immediate

对于add指令

该指令属于R型,所以ex_aluop1_sourceex_aluop2_source都选取Register

波形图

image-20231029191847038

入图,2ps,4ps,6ps时分别执行了上述三条指令

填空部分是为io.ex_aluop2_sourceio.memory_read_enableio.memory_write_enableio.wb_reg_write_source 四个控制信号赋值的代码

  • 对于io.ex_aluop2_source,前两条指令aluop2_source选立即数,对应波形图上的高电平。第三条指令aluop2_source选寄存器,对应波形图上的低电平
  • 对于io.memory_read_enable,三条指令都不需要读内存,波形图上该信号始终为低电平
  • 对于io.memory_write_enable,只有第一条指令需要写内存,对应波形图上的高电平,另外两条指令不需要写内存,对应波形图上的低电平
  • 对于io.wb_reg_write_source,第一条指令写寄存器的使能为低电平,不需要考虑该信号的值。第二、三条指令需要写寄存器,而且写寄存器的数据都来自ALU计算结果,该信号都是00

Execute执行

填空代码

1
2
3
4
5
6
7
8
9
10
// lab1(Execute)
//为 ALU 的输入端口赋值
alu.io.func := alu_ctrl.io.alu_funct
alu.io.op1 := Mux(
io.aluop1_source === 1.U, io.instruction_address, io.reg1_data
)
alu.io.op2 := Mux(
io.aluop2_source === 1.U, io.immediate, io.reg2_data
)
// lab1(Execute) end

测试用例

测试用例分别执行addbeq两种操作

  • 对于add,测试程序循环了101次,每次循环都生成两个随机数让ALU执行加法操作,检验运算是否正确
  • 对于beq,测试程序分别检验了两个寄存器操作数相等和不等时,跳转使能if_jump_flag和跳转地址if_jump_address的值是否正确

波形图

image-20231029195723312

如图,因为alu.io.funcalu_ctrl.io.alu_funct是直接相连的,在波形图中它们的值始终相同

io_op1io_op2是传递给ALU运算的操作数,图中红色游标处正在执行加法运算,经检验,0EBC9B5C+12856ECB=21420A27,说明ALU的计算是正确的

RegisterFile寄存器组

测试用例

  1. read the written content

该测试用例先向一号寄存器写入0xDEADBEEFL,然后再从一号寄存器读出数据,检验寄存器组的读写功能

  1. x0 always be zero

该测试用例向0号寄存器写入数据0xDEADBEEFL,然后检验0号寄存器的数据是否保持为0

  1. read the writing content

该测试用例的作用是测试寄存器的赋值是在时钟上升沿到来时进行的,如果没有时钟信号,赋值不会进行

timescope块的意义是测量代码块的执行时间,timescope块外的代码不会被计入性能测试中

CPUTest

填空代码

1
2
3
4
5
6
7
ex.io.instruction := inst_fetch.io.instruction
ex.io.instruction_address := inst_fetch.io.instruction_address
ex.io.reg1_data := regs.io.read_data1
ex.io.reg2_data := regs.io.read_data2
ex.io.immediate := id.io.ex_immediate
ex.io.aluop1_source := id.io.ex_aluop1_source
ex.io.aluop2_source := id.io.ex_aluop2_source

测试用例

  1. 运行fibonacci.asmbin,递归计算斐波那契数列第10个元素的值,检验是否为55
  2. 运行quicksort.asmbin,执行快速排序。如果快排运行成功,内存地址从4开始应当存储了一个长度为10的数组,值为0到9
  3. 运行sb.asmbin,检验寄存器的读写功能是否正确

波形图(lab1CPUTest)

calculate recursively fibonacci(10)

这个测试用例使用递归算法计算了斐波那契数列第10项

image-20240116092330845

100003ps时io.mem_debug_read_address设置为4, 读得io.mem_debug_read_data==37H

37H=3*16+7=55, 与期望相符

quicksort 10 numbers

这个测试用例使用快速排序将0~9这10个打乱的数从小到大排列

image-20240116092809768

从100004ps开始, mem_debug_read_data依次输出了按照从小到大排好的0~9, 说明快速排序程序的结果是正确的

store and load single byte

这个测试用例检验了寄存器存数,取数的能力

image-20240116093601366

在2ns时(这时候程序已经运行完毕)读取1,5,6三个寄存器的值,

  • 1号寄存器为15EFH
  • 5号寄存器为DEADBEEFH
  • 6号寄存器为EFH

和预期相符

对实验指导的改进建议

网站实验一的电路图错误

image-20231029211429761

图中红圈圈出的Mux有连线错误,0应当接Reg1RD,1应当接InsAddr

实验2-中断

Execute

指令说明

以下翻译自非特权级手册第九章

image-20231104213955177

符号说明:以下用[csr]表示在CSR寄存器组中编号为csr的寄存器中存储的值

(rd)表示在通用寄存器组中编号为rd的寄存器中存储的值

  • CSRRW (Atomic Read/Write CSR) :用于交换CSR寄存器的值和通用寄存器的值

    func3=001

    如果rd不是0号寄存器,则先将[csr]零扩展到XLEN位,并把它写到rd中,即(rd)=(零扩展)[csr];然后[csr]=(rs1)

    如果rd是0号寄存器,则只执行[csr]=(rs1)

  • CSRRS (Atomic Read and Set Bits in CSR):读取CSR寄存器的值

    func3=010

    先将[csr]零扩展到XLEN位,并把它写到rd中,即(rd)=(零扩展)[csr]

    如果rs1不是0号寄存器,执行按位或运算[csr]=[csr]|(rs1)

  • CSRRC (Atomic Read and Clear Bits in CSR):

    func3=011

    先将[csr]零扩展到XLEN位,并把它写到rd中,即(rd)=(零扩展)[csr]

    如果rs1不是0号寄存器,执行按位与运算[csr]=[csr]&~(rs1)

  • CSRRWI (Atomic Read/Write CSR Immediate)

    func3=101

    如果rd不是0号寄存器,则先将[csr]零扩展到XLEN位,并把它写到rd中,即(rd)=(零扩展)[csr];然后[csr]=(零扩展)rs1

    如果rd是0号寄存器,则只执行[csr]=rs1

  • CSRRSI (Atomic Read and Set Bits in CSR Immediate):读取CSR寄存器的值

    func3=110

    先将[csr]零扩展到XLEN位,并把它写到rd中,即(rd)=(零扩展)[csr]

    如果rs1不是0,执行按位或运算[csr]=[csr]|(零扩展)rs1

  • CSRRCI (Atomic Read and Clear Bits in CSR Immediate):

    func3=111

    先将[csr]零扩展到XLEN位,并把它写到rd中,即(rd)=(零扩展)[csr]

    如果rs1不是0,执行按位与运算[csr]=[csr]&~(零扩展)rs1

代码填空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// lab2(CLINTCSR)
val rs1 = io.instruction(19,15)
io.csr_reg_write_data := 0.U
when(opcode === Instructions.csr){
when(funct3 === InstructionsTypeCSR.csrrw){
io.csr_reg_write_data := io.reg1_data
}
.elsewhen(funct3 === InstructionsTypeCSR.csrrs){
io.csr_reg_write_data := Mux(rs1 =/= 0.U, io.csr_reg_read_data .| (io.reg1_data) ,io.csr_reg_read_data)
}
.elsewhen(funct3 === InstructionsTypeCSR.csrrc){
io.csr_reg_write_data := Mux(rs1 =/= 0.U, io.csr_reg_read_data .& (~io.reg1_data) ,io.csr_reg_read_data)
}
.elsewhen(funct3 === InstructionsTypeCSR.csrrwi){
io.csr_reg_write_data := rs1
}
.elsewhen(funct3 === InstructionsTypeCSR.csrrsi){
io.csr_reg_write_data := Mux(rs1 =/= 0.U, io.csr_reg_read_data .| ((0.U(27.W))##rs1) ,io.csr_reg_read_data)
}
.elsewhen(funct3 === InstructionsTypeCSR.csrrci){
io.csr_reg_write_data := Mux(rs1 =/= 0.U, io.csr_reg_read_data .& ((0x7ffffffL.U(27.W))##(~rs1)) ,io.csr_reg_read_data)
}
}
//lab2 end

测试用例和波形图

image-20231108202304153

测试用例检验了其中四条指令csrrci,csrrsi,csrrw,csrrs的功能是否正确

  • 2ps 处:

    func3为111, 对应指令CSRRCI,

    指令为 30047073H ,取其中15~19位为rs1,rs1=01000B,不是0号寄存器,所以执行按位与运算[csr]=[csr]&~(零扩展)rs1

    [csr]=00001888H,

    (零扩展)rs1=0000 0000 0000 0000 0000 0000 000 01000 B

    ~(零扩展)rs1=1111 1111 1111 1111 1111 1111 111 10111 B= FFFF FFF7H

    [csr]&~(零扩展)rs1=00001880H,与io_csr_reg_write_data相符

  • 4ps 处:

    func3为110,对应指令CSRRSI,

    指令为 30046073H,取其中15~19位为rs1,rs1=01000B,不是0号寄存器,执行按位或运算[csr]=[csr]|(零扩展)rs1

    [csr]=00001880H,

    (零扩展)rs1=0000 0000 0000 0000 0000 0000 000 01000 B =0000 0008H

    [csr]|(零扩展)rs1= 0000 1888H,与io_csr_reg_write_data相符

  • 6ps 处:

    func3为001,对应指令CSRRW,

    指令为 30051073H,取其中15~19位为rs1,rs1=01010B,不是0号寄存器,执行[csr]=(rs1)= 0000 1888H,与io_csr_reg_write_data相符

  • 8ps 处:

    func3为010,对应指令CSRRS

    指令为 3000 2573H,取其中15~19位为rs1,rs1=00000B,是0号寄存器,

    所以csr_reg_write_data=csr_reg_read_data=0000 1888H,与io_csr_reg_write_data相符

CSR和CLINT

MSTATUS寄存器

image-20231104172457837

代码填空

CLINT.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//lab2(CLINTCSR)
//val interrupt_enable =
when(io.interrupt_flag =/= InterruptStatus.None && interrupt_enable) { // =/=表示不等
io.csr_bundle.mstatus_write_data := io.csr_bundle.mstatus(31,13)### 3.U(2.W) ### io.csr_bundle.mstatus(10,4) ### 0.U(1.W) ### io.csr_bundle.mstatus(2,0)//特权级别设置为M-mode//修改MIE位,关中断
io.csr_bundle.mepc_write_data := instruction_address //保存下一条指令的地址
io.csr_bundle.mcause_write_data := Mux(io.interrupt_flag(0),0x80000007L.U,0x8000000BL.U)//io.interrupt_flag(0)为1表示由timer产生的外部中断,对应mcause的值0x80000007L.U,否则是uart造成的软件中断
io.csr_bundle.direct_write_enable := true.B
io.interrupt_assert := true.B
io.interrupt_handler_address := io.csr_bundle.mtvec
}.elsewhen(io.instruction === InstructionsRet.mret) {
io.csr_bundle.mstatus_write_data := io.csr_bundle.mstatus(31,13)### 3.U(2.W) ### io.csr_bundle.mstatus(10,4) ### 1.U(1.W) ### io.csr_bundle.mstatus(2,0)//特权级别设置为M-mode//修改MIE位,开中断
io.csr_bundle.mepc_write_data := io.csr_bundle.mepc //保持原样
io.csr_bundle.mcause_write_data := io.csr_bundle.mcause
io.csr_bundle.direct_write_enable := true.B
io.interrupt_assert := true.B
io.interrupt_handler_address := io.csr_bundle.mepc
}.otherwise {
io.csr_bundle.mstatus_write_data := io.csr_bundle.mstatus //保持原样
io.csr_bundle.mepc_write_data := io.csr_bundle.mepc //保持原样
io.csr_bundle.mcause_write_data := io.csr_bundle.mcause
io.csr_bundle.direct_write_enable := false.B
io.interrupt_assert := false.B
io.interrupt_handler_address :=io.csr_bundle.mtvec //don't care
}

CSR.scala

1
2
3
4
5
6
7
8
//以下对应原理图中CSR和CLINT之间的左四根线
//lab2(CLINTCSR)
//what data should be passed from csr to clint (Note: what should clint see is the next state of the CPU)
io.clint_access_bundle.mstatus := Mux(io.reg_write_enable_id && io.reg_write_address_id===CSRRegister.MSTATUS,io.reg_write_data_ex,mstatus)
io.clint_access_bundle.mtvec := Mux(io.reg_write_enable_id && io.reg_write_address_id===CSRRegister.MTVEC,io.reg_write_data_ex,mtvec)
io.clint_access_bundle.mcause := Mux(io.reg_write_enable_id && io.reg_write_address_id===CSRRegister.MCAUSE,io.reg_write_data_ex,mcause)
io.clint_access_bundle.mepc := Mux(io.reg_write_enable_id && io.reg_write_address_id===CSRRegister.MEPC,io.reg_write_data_ex,mepc)
//lab2 end

测试用例和波形图

  1. interrupt_flag设置为1,检查interrupt_assert是否为true,interrupt_handler_address是否为MTVEC的值(之前设置为0x1144)

    jump_flag设置为false,检查MEPC是否保存了PC+4的值,即0x1904

    检查MCAUSE,因为设置了interrupt_flag=1,MCAUSE应当为0x80000007L

    检查MSTATUS是否变为0x1880L(也就是将MIE为由1改为0,关中断)

    image-20240115233213190

  2. 遇到mret指令,期望interrupt_assert为true

    interrupt_handler_addressMEPC的值,即0x1904

    MSTATUS恢复为0x1888L(也就是将MIE为由0改为1,开中断)

  3. 然后是检验跳转的时候遇到中断,

    interrupt_flag设置为2,检查interrupt_assert是否为true,interrupt_handler_address是否为MTVEC的值(之前设置为0x1144)

    jump_flag设置为true,检查MEPC是否保存了jump_address,即0x1990

    检查MCAUSE,因为设置了interrupt_flag=2,MCAUSE应当为0x8000000BL

    检查MSTATUS是否变为0x1880L(也就是将MIE为由1改为0,关中断)

  4. 遇到mret指令, 期望interrupt_assert为true

    interrupt_handler_addressMEPC的值,即0x1990

    MSTATUS恢复为0x1888L(也就是将MIE为由0改为1,开中断)

  5. 最后检查在关中断(MSTATUS=0x1880)的情况下, 尽管interrupt_flag=1, 因为中断被屏蔽, interrupt_assertfalse

    image-20240115234507869

Timer

测试用例

  1. 首先向内存0x4.U(limit寄存器所对应的内存空间)写入0x990315,然后读limit寄存器的值,检查是不是0x990315
  2. 向内存0x8.U(enabled寄存器所对应的内存空间)写入0,然后读enabled寄存器的值,检查是不是false

波形图

image-20240116080220120

  • 3ps时,io_debug_limit变为0x990315,说明0x4.U(limit寄存器所对应的内存空间)成功写入0x990315
  • 7ps时,io_debug_enabled变为0,说明0x8.U(enabled寄存器所对应的内存空间)成功写入0

代码填空

Timer.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//lab2(CLINTCSR)
//finish the read-write for count,limit,enabled. And produce appropriate signal_interrupt
//读
io.bundle.read_data := limit

when(io.bundle.address === 8.U){
when(enabled){
io.bundle.read_data:=1.U
}
.otherwise{
io.bundle.read_data:=0.U
}
}
//写
when(io.bundle.write_enable){
when(io.bundle.address === 4.U){
limit := io.bundle.write_data
}
.elsewhen(io.bundle.address === 8.U){
enabled := io.bundle.write_data =/= 0.U
}
}
//计时
io.signal_interrupt := false.B
when(count === limit)
{
count := 0.U
when(enabled){
io.signal_interrupt := true.B
}
}
.otherwise{
count := count + 1.U
io.signal_interrupt := false.B
}

CPUTest

测试用例

  1. 运行fibonacci.asmbin,递归计算斐波那契数列第10个元素的值,检验是否为55
  2. 运行quicksort.asmbin,执行快速排序。如果快排运行成功,内存地址从4开始应当存储了一个长度为10的数组,值为0到9
  3. 运行sb.asmbin,检验寄存器的读写功能是否正确
  4. 运行simpletest.asmbin,检验中断功能

波形图

前三个测试的波形图和lab1的CPUTest的三个波形图相同,这里不再重复分析,点击跳转

以下是对第四个测试的分析

jump to trap handler and then return

根据simpletest.c的代码,这个程序首先往内存0x4写入数据0xDEADBEEF,然后使用函数enable_interrupt()允许中断发生,定义中断处理函数trap_handler,会往内存0x4写入数据0x2022

  • 首先让程序执行1000个周期,读取内存0x4,检查数据是否为0xDEADBEEF,如下图,在2003ps处,得到mem_debug_read_data=0xDEADBEEF

image-20240121201844023

  • 随后令interrupt_flag为0x1,进入中断状态,程序会跳转执行函数trap_handler,在1000个周期之后,检查MSTATUS和MCAUSE寄存器的值。(测试代码是有问题的,导致无法在波形图上显示MSTATUS的值,详见改进的建议第3条)。如下图,MCAUSE的值为0x80000007,与预期相符

image-20240121203220033

  • 最后检查内存0x4地址处的值,以检验中断处理函数是否被正确执行,结果是0x2022,与预期相符,如下图

image-20240121203357737

改进的建议

网站实验任务栏Timer的代码位置写错了

Timer 的代码位于 src/main/scala/riscv/peripheral/Timer.scala

改为src/main/scala/peripheral/Timer.scala

ExecuteTest.scala中四条指令的注释标错了

1
2
3
4
c.io.instruction.poke(0x30047073L.U) // csrc mstatus,3  //csrrci
c.io.instruction.poke(0x30046073L.U) //csrs mastatus,3 //csrrsi
c.io.instruction.poke(0x30051073L.U) //csrw mstatus, a0 //csrrw
c.io.instruction.poke(0x30002573L.U) //csrr a0, mstatus //csrrs

image-20231104213955177

  1. 在测试代码jump to trap handler and then return中,以下代码存在问题
1
2
3
4
c.io.csr_regs_debug_read_address.poke(0x300.U) // CSRRegister.MSTATUS
c.io.csr_regs_debug_read_data.expect(0x1888.U)
c.io.csr_regs_debug_read_address.poke(0x342.U) // CSRRegister.MCAUSE
c.io.csr_regs_debug_read_data.expect(0x80000007L.U)

需要在第2行下面加一行代码c.clock.step(),否则在波形图上无法显示MSTATUS的值

实验3-流水线

pipeline Register

填空代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Lab3(PipelineRegister)
val myreg = RegInit(UInt(width.W),defaultValue)
val out =RegInit(UInt(width.W),defaultValue)
when(io.flush){
out := defaultValue
myreg := defaultValue
}
.elsewhen(io.stall){
out := myreg
}
.otherwise{
myreg := io.in
out := io.in
}
io.out := out //在最后一步才给io.out赋值,是为了防止出现组合逻辑环路导致sbt "testOnly riscv.ThreeStageCPUTest"无法通过(sbt "testOnly riscv.PipelineRegisterTest"可以通过)
//踩了很多次坑猜测出来的,可能是因为如果在前面的条件判断中就给io.out赋值,硬件就不会理会后面代码对io.out的再次赋值
// Lab3(PipelineRegister) End

测试用例

进行1000次循环,每次循环随机决定是阻塞还是复位还是正常

  • 阻塞的时候stall=true,期望输出为上次输入的值
  • 复位的时候flush=true,期望输出default value
  • 正常情况下输出in

波形图

image-20240116085436940

该测试的波形图很长,故只截取一段分析

  • 319ps, io_flush=1,所以io_out=4ACD92E8H,为随机生成的default_value
  • 321ps, io_stall=1, 所以io_out保持不变
  • 323ps, io_flush=io_stall=0, io_out=io_in=1BAC194DH

ThreeStageCPU

填空代码

Control.scala

1
2
3
4
5
6
7
// Lab3(Flush)
val io = IO(new Bundle{
val JumpFlag = Input(Bool())
val Flush = Output(Bool())
})
io.Flush := io.JumpFlag
// lab3 end

CPU.scala

1
2
3
4
5
// Lab3(Flush)
ctrl.io.JumpFlag := ex.io.if_jump_flag
if2id.io.flush := ctrl.io.Flush
id2ex.io.flush := ctrl.io.Flush
// Lab3(Flush) End

测试用例和波形图(lab4ThreeStageCPU)

前三个测试的测试用例和波形图和lab1的CPUTest的三个波形图相同,这里不再重复分析,点击跳转

solve control hazards

这个测试用例检验了寄存器和内存存数,取数的能力

image-20240116094157242

2005ps的时候(程序已经运行结束), 1号寄存器的值为1AH即26, 与预期相符

读取内存地址为4的空间, 结果为1H, 符合预期

读取内存地址为8的空间, 结果为3H, 符合预期

FiveStageCPUStall

填空代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Lab3(Stall)
io.if_flush := false.B
io.id_flush := false.B
io.pc_stall := false.B
io.if_stall := false.B
when(io.jump_flag){
io.if_flush := true.B
io.id_flush := true.B
}.elsewhen((io.reg_write_enable_ex && (io.rd_ex === io.rs1_id || io.rd_ex === io.rs2_id) && io.rd_ex =/= 0.U)
|| (io.reg_write_enable_mem && (io.rd_mem === io.rs1_id || io.rd_mem === io.rs2_id) && io.rd_mem =/= 0.U)
){
io.id_flush := true.B
io.pc_stall := true.B
io.if_stall := true.B
}
// Lab3(Stall) End

测试用例和波形图

前三个测试的测试用例和波形图和lab1的CPUTest的三个波形图相同,这里不再重复分析,点击跳转

第四个测试的测试用例和波形图和lab3的ThreeStageCPU的第四个波形图相同,这里不再重复分析,点击跳转

FiveStageCPUForward

填空代码

control.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Lab3(Forward)
//我们用一个控制单元来处理流水线的阻塞和清空,模块接口已经定义在这里
io.if_flush := false.B
io.id_flush := false.B
io.pc_stall := false.B
io.if_stall := false.B
when(io.jump_flag){
io.if_flush := true.B
io.id_flush := true.B
}.elsewhen(io.memory_read_enable_ex && io.rd_ex =/= 0.U && (io.rd_ex === io.rs1_id || io.rd_ex === io.rs2_id)){
io.id_flush := true.B
io.pc_stall := true.B
io.if_stall := true.B
}
// Lab3(Forward) End

Forwarding.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Lab3(Forward)
//用一个旁路单元来检测数据冒险并发出旁路控制信号,模块接口已经定义在这里
when(io.reg_write_enable_mem && io.rs1_ex === io.rd_mem && io.rd_mem =/= 0.U){
io.reg1_forward_ex := ForwardingType.ForwardFromMEM
}.elsewhen(io.reg_write_enable_wb && io.rs1_ex === io.rd_wb && io.rd_wb =/= 0.U){
io.reg1_forward_ex := ForwardingType.ForwardFromWB
}.otherwise{
io.reg1_forward_ex := ForwardingType.NoForward
}
when(io.reg_write_enable_mem && io.rs2_ex === io.rd_mem && io.rd_mem =/= 0.U){
io.reg2_forward_ex := ForwardingType.ForwardFromMEM
}.elsewhen(io.reg_write_enable_wb && io.rs2_ex === io.rd_wb && io.rd_wb =/= 0.U){
io.reg2_forward_ex := ForwardingType.ForwardFromWB
}.otherwise{
io.reg2_forward_ex := ForwardingType.NoForward
}

Execute.scala

1
2
3
4
5
6
7
8
9
// Lab3(Forward)
//在执行单元中根据旁路单元的控制信号使用对应的旁路数据
val reg1_data = Mux(io.reg1_forward === ForwardingType.ForwardFromMEM, io.forward_from_mem,
Mux(io.reg1_forward === ForwardingType.ForwardFromWB,io.forward_from_wb,io.reg1_data)
)
val reg2_data = Mux(io.reg2_forward === ForwardingType.ForwardFromMEM,io.forward_from_mem,
Mux(io.reg2_forward === ForwardingType.ForwardFromWB,io.forward_from_wb,io.reg2_data)
)
// Lab3(Forward) End

测试用例和波形图

前三个测试的测试用例和波形图和lab1的CPUTest的三个波形图相同,这里不再重复分析,点击跳转

第四个测试的测试用例和波形图和lab3的ThreeStageCPU的第四个波形图相同,这里不再重复分析,点击跳转

FiveStageCPUFinal

自制数据冒险的指令序列样例

1
2
3
4
5
0000: add x1, x0, x0
0004: sub x2, x0, x1
0008: and x1, x1, x2
000C: jalr x4, x1, 0
0010: or x5, x3, x4
时钟周期 0 1 2 3 4 5 6 7 8 9 10
IF add sub and jalr or or xor
ID add sub and jalr jalr nop xor
EX add sub and nop jalr nop xor
EX2MEM add:x1 sub:x2 and:x1 jalr:x4 xor:x6
MEM add sub and nop jalr nop xor
MEM2WB add:x1 sub:x2 and:x1 jalr:x4 xor:x6
WB add sub and nop jalr nop xor
1
2
3
0000: lw x1, 4(x1)
0004: jalr x4, x1, 0
0008: add x1, x4, x1
时钟周期 0 1 2 3 4 5 6 7
IF lw jalr add add add xor
ID lw jalr jalr jalr nop xor
EX lw nop nop jalr nop xor
EX2MEM jalr:x4
MEM lw nop nop jalr nop
MEM2WB lw:x1 jalr:x4
WB lw nop nop jalr

填空代码

InstructionDecode.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
  // Lab3(Final)
// io.clint_jump_flag := io.interrupt_assert
// io.clint_jump_address := io.interrupt_handler_address
val reg1_data = MuxLookup(
io.reg1_forward,
0.U,
IndexedSeq(
ForwardingType.NoForward -> (io.reg1_data),
ForwardingType.ForwardFromWB -> (io.forward_from_wb),
ForwardingType.ForwardFromMEM -> (io.forward_from_mem)
)
)
val reg2_data = MuxLookup(
io.reg2_forward,
0.U,
IndexedSeq(
ForwardingType.NoForward -> (io.reg2_data),
ForwardingType.ForwardFromWB -> (io.forward_from_wb),
ForwardingType.ForwardFromMEM -> (io.forward_from_mem)
)
)
io.ctrl_jump_instruction := opcode === Instructions.jal ||
(opcode === Instructions.jalr) ||
(opcode === InstructionTypes.B)

io.if_jump_flag := opcode === Instructions.jal ||
(opcode === Instructions.jalr) ||
(opcode === InstructionTypes.B) && MuxLookup(
funct3,
false.B,
IndexedSeq(
InstructionsTypeB.beq -> (reg1_data === reg2_data),
InstructionsTypeB.bne -> (reg1_data =/= reg2_data),
InstructionsTypeB.blt -> (reg1_data.asSInt < reg2_data.asSInt),
InstructionsTypeB.bge -> (reg1_data.asSInt >= reg2_data.asSInt),
InstructionsTypeB.bltu -> (reg1_data.asUInt < reg2_data.asUInt),
InstructionsTypeB.bgeu -> (reg1_data.asUInt >= reg2_data.asUInt)
)
) || io.interrupt_assert

io.if_jump_address := Mux(io.interrupt_assert, io.interrupt_handler_address, MuxLookup(
opcode,
0.U,
IndexedSeq(
InstructionTypes.B -> (io.instruction_address + io.ex_immediate),
Instructions.jal -> (io.instruction_address + io.ex_immediate),
Instructions.jalr -> (reg1_data + io.ex_immediate)
)
))
io.clint_jump_flag := io.ctrl_jump_instruction
io.clint_jump_address := MuxLookup(
opcode,
0.U,
IndexedSeq(
InstructionTypes.B -> (io.instruction_address + io.ex_immediate),
Instructions.jal -> (io.instruction_address + io.ex_immediate),
Instructions.jalr -> (reg1_data + io.ex_immediate)
)
)
// Lab3(Final) End

control.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Lab3(Final)
io.if_flush := false.B
io.id_flush := false.B
io.pc_stall := false.B
io.if_stall := false.B
when(((io.jump_instruction_id || io.memory_read_enable_ex) && io.rd_ex =/= 0.U && (io.rd_ex === io.rs1_id || io.rd_ex === io.rs2_id))||
(io.jump_instruction_id && io.memory_read_enable_mem && io.rd_mem =/= 0.U && (io.rd_mem === io.rs1_id || io.rd_mem === io.rs2_id))) {
io.id_flush := true.B
io.pc_stall := true.B
io.if_stall := true.B
}.elsewhen(io.jump_flag){
io.if_flush := true.B
}
// Lab3(Final) End

Forwarding.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Lab3(Final)
//io.reg1_forward_ex
when(io.reg_write_enable_mem && io.rs1_ex === io.rd_mem && io.rd_mem =/= 0.U) {
io.reg1_forward_ex := ForwardingType.ForwardFromMEM
}.elsewhen(io.reg_write_enable_wb && io.rs1_ex === io.rd_wb && io.rd_wb =/= 0.U) {
io.reg1_forward_ex := ForwardingType.ForwardFromWB
}.otherwise {
io.reg1_forward_ex := ForwardingType.NoForward
}
//io.reg2_forward_ex
when(io.reg_write_enable_mem && io.rs2_ex === io.rd_mem && io.rd_mem =/= 0.U) {
io.reg2_forward_ex := ForwardingType.ForwardFromMEM
}.elsewhen(io.reg_write_enable_wb && io.rs2_ex === io.rd_wb && io.rd_wb =/= 0.U) {
io.reg2_forward_ex := ForwardingType.ForwardFromWB
}.otherwise {
io.reg2_forward_ex := ForwardingType.NoForward
}
//io.reg1_forward_id
when(io.reg_write_enable_mem && io.rs1_id === io.rd_mem && io.rd_mem =/= 0.U){
io.reg1_forward_id := ForwardingType.ForwardFromMEM
}.elsewhen(io.reg_write_enable_wb && io.rs1_id === io.rd_wb && io.rd_wb =/= 0.U) {
io.reg1_forward_id := ForwardingType.ForwardFromWB
}.otherwise {
io.reg1_forward_id := ForwardingType.NoForward
}
//io.reg2_forward_id
when(io.reg_write_enable_mem && io.rs2_id === io.rd_mem && io.rd_mem =/= 0.U) {
io.reg2_forward_id := ForwardingType.ForwardFromMEM
}.elsewhen(io.reg_write_enable_wb && io.rs2_id === io.rd_wb && io.rd_wb =/= 0.U) {
io.reg2_forward_id := ForwardingType.ForwardFromWB
}.otherwise {
io.reg2_forward_id := ForwardingType.NoForward
}
// Lab3(Final) End

测试用例和波形图

前三个测试的测试用例和波形图和lab1的CPUTest的三个波形图相同,这里不再重复分析,点击跳转

第四个测试的测试用例和波形图和lab3的ThreeStageCPU的第四个波形图相同,这里不再重复分析,点击跳转

改进的建议

“扩展:使用旁路减少阻塞” 的表格有问题

时钟周期 0 1 2 3 4 5 6 7
IF addi sub and lw or
ID addi sub and lw or or
EX addi sub and lw nop or
EX2MEM addi:x1 sub:x2 and:x2
MEM addi sub and lw nop
MEM2WB addi:x1 sub:x2 and:x2 lw:x2
WB addi sub and lw

区别在第6个时钟

如果按照网站上的表格,在control.scala必须使用id_stall,实际上,control.scala中只有if_flush,id_flush,pc_stallif_stall

使用本改进的表格,只需要使用pc_stallif_stallid_flush使or暂停一个周期

实验4-总线

填空代码

Master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//lab4(BUS)
io.channels.read_address_channel.ARADDR := addr
io.channels.write_address_channel.AWADDR := addr
when(state === AXI4LiteStates.Idle) { //空闲
read_valid := false.B
write_valid := false.B
ARVALID := false.B
RREADY := false.B
AWVALID := false.B
WVALID := false.B
BREADY := false.B
when(io.bundle.read) {
state := AXI4LiteStates.ReadAddr
addr := io.bundle.address
io.channels.read_address_channel.ARADDR := io.bundle.address
}.elsewhen(io.bundle.write) {
state := AXI4LiteStates.WriteAddr
write_strobe := io.bundle.write_strobe
addr := io.bundle.address
write_data := io.bundle.write_data
}
}.elsewhen(state === AXI4LiteStates.ReadAddr) { //发送读地址
io.channels.read_address_channel.ARADDR := addr
ARVALID := true.B
when(io.channels.read_address_channel.ARREADY) {
io.channels.read_address_channel.ARADDR := addr
ARVALID := false.B
state := AXI4LiteStates.ReadData
//RREADY := true.B
}
}.elsewhen(state === AXI4LiteStates.ReadData) { //接收读数据
when(io.channels.read_data_channel.RVALID) {
RREADY := true.B
read_data := io.channels.read_data_channel.RDATA
io.bundle.read_data := io.channels.read_data_channel.RDATA
read_valid := true.B
state := AXI4LiteStates.Idle
}
}.elsewhen(state === AXI4LiteStates.WriteAddr) { //发送写地址
io.channels.write_address_channel.AWADDR := addr
AWVALID := true.B
when(io.channels.write_address_channel.AWREADY) {
AWVALID := false.B
state := AXI4LiteStates.WriteData
}
}.elsewhen(state === AXI4LiteStates.WriteData) { //发送写数据
io.channels.write_data_channel.WDATA := io.bundle.write_data
WVALID := true.B
when(io.channels.write_data_channel.WREADY) {
state := AXI4LiteStates.WriteResp
}
}.elsewhen(state === AXI4LiteStates.WriteResp) { //接收写回复
BREADY := true.B
when(io.channels.write_response_channel.BVALID) {
WVALID := false.B
write_valid := true.B
state := AXI4LiteStates.Idle
}
}
//lab4(BUS)end

Slave

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//lab4(BUS)
when(state === AXI4LiteStates.Idle) { //空闲
read := false.B
write := false.B
ARREADY := false.B
RVALID := false.B
AWREADY := false.B
WREADY := false.B
BVALID := false.B
when(io.channels.read_address_channel.ARVALID) { //读地址有效
state := AXI4LiteStates.ReadAddr
addr := io.channels.read_address_channel.ARADDR
//ARREADY := true.B //告诉主机读准备//改
}.elsewhen(io.channels.write_address_channel.AWVALID) { //写地址有效
state := AXI4LiteStates.WriteAddr
// AWREADY := true.B
}
}.elsewhen(state === AXI4LiteStates.ReadAddr) { //接收读地址
read := true.B
when(io.channels.read_address_channel.ARREADY) {
state := AXI4LiteStates.ReadData
}
ARREADY := true.B
when(io.channels.read_data_channel.RREADY) {
ARREADY := false.B
}
}.elsewhen(state === AXI4LiteStates.WriteAddr) { //接收写地址
addr := io.channels.write_address_channel.AWADDR
AWREADY := true.B
state := AXI4LiteStates.WriteData
}.elsewhen(state === AXI4LiteStates.ReadData) { //发送读数据
when(io.bundle.read_valid) {
io.channels.read_data_channel.RDATA := io.bundle.read_data
RVALID := true.B
when(io.channels.read_data_channel.RREADY) {
state := AXI4LiteStates.Idle
ARREADY := false.B
}
}
}.elsewhen(state === AXI4LiteStates.WriteData) { //接收写数据
WREADY := true.B
when(io.channels.write_data_channel.WVALID) {
AWREADY := false.B
write_data := io.channels.write_data_channel.WDATA
write_strobe := io.channels.write_data_channel.WSTRB.asBools
write := true.B
state := AXI4LiteStates.WriteResp
}
}.elsewhen(state === AXI4LiteStates.WriteResp) { //发送写回复
BVALID := true.B
when(io.channels.write_response_channel.BREADY) {
WREADY := false.B
state := AXI4LiteStates.Idle
write := false.B
}
}
//lab4(BUS)

测试用例和波形图

Timer read and write the limit

image-20240121162737649

该测试用例往地址0x4先写后读,检验了读写数据的正确性和总线运行占用时钟周期数的正确性

首先测试写操作的正确性

  • 令写使能为true,address设置为0x4,写数据为0x990315,一个时钟周期之后(4ps),关闭写使能、写数据和地址清零,观察到busy变为true,符合预期
  • 8个时钟周期之后(20ps),观察到busy变为false,write_valid变为true,io_limit变为0x990315,说明数据成功写到从设备中,并且主设备的状态回到idle,符合预期

然后测试读操作的正确性

  • 从20ps开始,设置读使能为true,读地址为0x4,一个时钟周期之后(22ps),观察到busy变为true,符合预期
  • 再6个时钟之后(33ps),观察到busy变为false,read_valid变为true,read_data变为0x990315,说明数据被成功读取,并且主设备的状态回到idle,数据的内容是正确的,和先前往0x4写的内容一致,符合预期

Memory perform read and write

image-20240121172426698

首先测试写操作的正确性

  • 令写使能为true,address设置为0x4,write_strobe设置为0xF,写数据为0xDEADBEEF,一个时钟周期之后(4ps),关闭写使能、写数据和地址清零,观察到busy变为true,符合预期
  • 8个时钟周期之后(20ps),观察到busy变为false,write_valid变为true,说明写数据执行完毕,并且主设备的状态回到idle,符合预期

然后测试读操作的正确性

  • 从20ps开始,设置读使能为true,读地址为0x4,一个时钟周期之后(22ps),观察到busy变为true,符合预期
  • 再6个时钟之后(33ps),观察到busy变为false,read_valid变为true,read_data变为0xDEADBEEF,说明数据被成功读取,并且主设备的状态回到idle,数据的内容是正确的,和先前往0x4写的内容一致,符合预期

ROMLoader load program

image-20240121174710643

总线写一次数据需要9个时钟周期,读一次数据需要7个时钟周期

该测试用例测试总线从ROM中读取数据写到主存的功能

  • 首先设定load_address为0x100,load_start为true,1个时钟后(4ps),令load_start为false,rom_address为0x0,说明现在正在读取ROM地址为0x0的数据,符合预期
  • 8个时钟周期后(20ps),bundle_write变为true,bundle.address变为0x100,说明现在正在往主存0x100写数据
  • 4个时钟周期后(28ps),rom_address为0x1,说明当前正在ROM的下一个地址单元读取数据
  • 7个时钟周期后(42ps),rom_address为0x1,bundle.write为true,bundle.address变为0x104,表明当前正在往主存0x104写数据
  • 1个周期后(44ps),检查rom_adress是否保持为0x1,3个周期后(49ps),读取数据结束,检查load_finished为true

threeStageCPUTest

第1、2、4个测试和波形图和lab1的CPUTest的三个波形图相同,这里不再重复分析,点击跳转

第3个测试用例代码存在问题:

1
2
3
4
c.io.regs_debug_read_address.poke(5.U)
c.io.regs_debug_read_data.expect(100000000.U)
c.io.regs_debug_read_address.poke(6.U)
c.io.regs_debug_read_data.expect(0xBEEF.U)

poke和expect缺少c.clock.step()语句,在波形图中无法从regs_debug_read_data得到期望数据

fiveStageCPUTest

第1、2、4个测试和波形图和lab1的CPUTest的三个波形图相同,这里不再重复分析,点击跳转

第三个测试用例同threeStageCPUTest


yatcpu实验报告
https://blog.algorithmpark.xyz/2024/01/31/yatcpu/index/
作者
CJL
发布于
2024年1月31日
更新于
2024年2月27日
许可协议