本文最后更新于 2024-02-27T01:30:10+00:00
计算机组成原理课程实验报告
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 pc := pc + 4. U when(io.jump_flag_id) { pc := io.jump_address_id }
测试用例
该测试用例测试取址操作的正确性,指定程序入口地址为entry=0x1000
,随后进行100次循环,每次循环都随机确定当前指令是否为跳转指令jump
。
如果不是跳转指令,则检查指令地址是否正常+4
如果是跳转指令,则检查指令地址是否跳转到entry
波形图
可以观察到图中信号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 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 := Mux ( opcode === InstructionTypes .L , 1. U (1. W ) , 0. U (1. W ) ) io.memory_write_enable := Mux ( opcode === InstructionTypes .S , 1. U (1. W ) , 0. U (1. W ) ) io.wb_reg_write_source:=RegWriteSource .ALUResult 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 }
测试用例
测试用例总共测试了三条不同种类的指令,分别是S型、lui
、add
指令
对于S型指令
测试的指令为0x00a02223L
,对应二进制编码00000000101000000010001000100011
期望结果为aluop1_source
选Register
,aluop2_source
选Immediate
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_source
和ex_aluop2_source
都选取Register
波形图
入图,2ps,4ps,6ps时分别执行了上述三条指令
填空部分是为io.ex_aluop2_source
、io.memory_read_enable
、io.memory_write_enable
、io.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 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 )
测试用例
测试用例分别执行add
和beq
两种操作
对于add
,测试程序循环了101次,每次循环都生成两个随机数让ALU执行加法操作,检验运算是否正确
对于beq
,测试程序分别检验了两个寄存器操作数相等和不等时,跳转使能if_jump_flag
和跳转地址if_jump_address
的值是否正确
波形图
如图,因为alu.io.func
和alu_ctrl.io.alu_funct
是直接相连的,在波形图中它们的值始终相同
io_op1
和io_op2
是传递给ALU运算的操作数,图中红色游标处正在执行加法运算,经检验,0EBC9B5C+12856ECB=21420A27,说明ALU的计算是正确的
RegisterFile寄存器组
测试用例
read the written content
该测试用例先向一号寄存器写入0xDEADBEEFL
,然后再从一号寄存器读出数据,检验寄存器组的读写功能
x0 always be zero
该测试用例向0号寄存器写入数据0xDEADBEEFL
,然后检验0号寄存器的数据是否保持为0
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
测试用例
运行fibonacci.asmbin
,递归计算斐波那契数列第10个元素的值,检验是否为55
运行quicksort.asmbin
,执行快速排序。如果快排运行成功,内存地址从4开始应当存储了一个长度为10的数组,值为0到9
运行sb.asmbin
,检验寄存器的读写功能是否正确
波形图(lab1CPUTest)
calculate recursively fibonacci(10)
这个测试用例使用递归算法计算了斐波那契数列第10项
100003ps时io.mem_debug_read_address
设置为4, 读得io.mem_debug_read_data==37H
37H=3*16+7=55, 与期望相符
quicksort 10 numbers
这个测试用例使用快速排序将0~9这10个打乱的数从小到大排列
从100004ps开始, mem_debug_read_data
依次输出了按照从小到大排好的0~9, 说明快速排序程序的结果是正确的
store and load single byte
这个测试用例检验了寄存器存数,取数的能力
在2ns时(这时候程序已经运行完毕)读取1,5,6三个寄存器的值,
1号寄存器为15EFH
5号寄存器为DEADBEEFH
6号寄存器为EFH
和预期相符
对实验指导的改进建议
网站实验一的电路图错误
图中红圈圈出的Mux
有连线错误,0应当接Reg1RD
,1应当接InsAddr
实验2-中断
Execute
指令说明
以下翻译自非特权级手册第九章
符号说明:以下用[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 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 .& ((0x7ffffff L.U (27. W ))##(~rs1)) ,io.csr_reg_read_data) } }
测试用例和波形图
测试用例检验了其中四条指令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寄存器
代码填空
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 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 ) io.csr_bundle.mepc_write_data := instruction_address io.csr_bundle.mcause_write_data := Mux (io.interrupt_flag(0 ),0x80000007 L.U ,0x8000000B L.U ) 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 ) 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 }
CSR.scala
1 2 3 4 5 6 7 8 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)
测试用例和波形图
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,关中断)
遇到mret
指令,期望interrupt_assert
为true
interrupt_handler_address
为MEPC
的值,即0x1904
MSTATUS
恢复为0x1888L
(也就是将MIE为由0改为1,开中断)
然后是检验跳转的时候遇到中断,
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,关中断)
遇到mret
指令, 期望interrupt_assert
为true
interrupt_handler_address
为MEPC
的值,即0x1990
MSTATUS
恢复为0x1888L
(也就是将MIE为由0改为1,开中断)
最后检查在关中断(MSTATUS=0x1880
)的情况下, 尽管interrupt_flag=1
, 因为中断被屏蔽, interrupt_assert
为false
Timer
测试用例
首先向内存0x4.U
(limit寄存器所对应的内存空间)写入0x990315
,然后读limit
寄存器的值,检查是不是0x990315
向内存0x8.U
(enabled寄存器所对应的内存空间)写入0
,然后读enabled
寄存器的值,检查是不是false
波形图
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 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
测试用例
运行fibonacci.asmbin
,递归计算斐波那契数列第10个元素的值,检验是否为55
运行quicksort.asmbin
,执行快速排序。如果快排运行成功,内存地址从4开始应当存储了一个长度为10的数组,值为0到9
运行sb.asmbin
,检验寄存器的读写功能是否正确
运行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
随后令interrupt_flag为0x1,进入中断状态,程序会跳转执行函数trap_handler
,在1000个周期之后,检查MSTATUS和MCAUSE寄存器的值。(测试代码是有问题的,导致无法在波形图上显示MSTATUS的值,详见改进的建议第3条)。如下图,MCAUSE的值为0x80000007
,与预期相符
最后检查内存0x4地址处的值,以检验中断处理函数是否被正确执行,结果是0x2022,与预期相符,如下图
改进的建议
网站实验任务栏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(0x30047073 L.U ) c.io.instruction.poke(0x30046073 L.U ) c.io.instruction.poke(0x30051073 L.U ) c.io.instruction.poke(0x30002573 L.U )
在测试代码jump to trap handler and then return
中,以下代码存在问题
1 2 3 4 c.io.csr_regs_debug_read_address.poke(0x300 .U ) c.io.csr_regs_debug_read_data.expect(0x1888 .U ) c.io.csr_regs_debug_read_address.poke(0x342 .U ) c.io.csr_regs_debug_read_data.expect(0x80000007 L.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 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
测试用例
进行1000次循环,每次循环随机决定是阻塞还是复位还是正常
阻塞的时候stall=true
,期望输出为上次输入的值
复位的时候flush=true
,期望输出default value
正常情况下输出in
波形图
该测试的波形图很长,故只截取一段分析
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 val io = IO (new Bundle { val JumpFlag = Input (Bool ()) val Flush = Output (Bool ()) }) io.Flush := io.JumpFlag
CPU.scala
1 2 3 4 5 ctrl.io.JumpFlag := ex.io.if_jump_flag if2id.io.flush := ctrl.io.Flush id2ex.io.flush := ctrl.io.Flush
测试用例和波形图(lab4ThreeStageCPU)
前三个测试的测试用例和波形图和lab1的CPUTest的三个波形图相同,这里不再重复分析,点击跳转
solve control hazards
这个测试用例检验了寄存器和内存存数,取数的能力
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 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 }
测试用例和波形图
前三个测试的测试用例和波形图和lab1的CPUTest的三个波形图相同,这里不再重复分析,点击跳转
第四个测试的测试用例和波形图和lab3的ThreeStageCPU的第四个波形图相同,这里不再重复分析,点击跳转
FiveStageCPUForward
填空代码
control.scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 }
Forwarding.scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 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) )
测试用例和波形图
前三个测试的测试用例和波形图和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 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) ) )
control.scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 }
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 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 } 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 } 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 }
测试用例和波形图
前三个测试的测试用例和波形图和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_stall
和if_stall
使用本改进的表格,只需要使用pc_stall
,if_stall
和id_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 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 } }.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 } }
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 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 }.elsewhen(io.channels.write_address_channel.AWVALID ) { state := AXI4LiteStates .WriteAddr } }.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 } }
测试用例和波形图
Timer read and write the limit
该测试用例往地址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写的内容一致,符合预期
首先测试写操作的正确性
令写使能为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
总线写一次数据需要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