本文最后更新于 2025-01-20T12:00:09+00:00
                  
                  
                
              
            
            
              
                
                计算机组成原理课程实验报告 
yatcpu源仓库 https://github.com/hrpccs/2022-fall-yatcpu-repo 
完整的答案代码 https://github.com/CJL196/2022-fall-yatcpu-repo 
            免责声明:本文仅供学习和技术交流,如有侵权请联系删除
           
实验1-单周期CPU 
InstructionFetch取址 
填空代码 
1 2 3 4 5 6 7 4. U   
测试用例 
该测试用例测试取址操作的正确性,指定程序入口地址为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 Mux (InstructionTypes .L  || opcode === InstructionTypes .I  || opcode === Instructions .jalrInstructions .jal || opcode === InstructionTypes .S  || opcode === InstructionTypes .B  ||Instructions .lui || opcode === Instructions .auipc,ALUOp2Source .Immediate ,ALUOp2Source .Register Mux (InstructionTypes .L  , 1. U (1. W ) , 0. U (1. W )Mux (InstructionTypes .S  , 1. U (1. W ) , 0. U (1. W )RegWriteSource .ALUResult  InstructionTypes .RM  || opcode === InstructionTypes .I Instructions .lui || opcode === Instructions .auipc){RegWriteSource .ALUResult InstructionTypes .L ){RegWriteSource .Memory Instructions .jal || opcode === Instructions .jalr){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 Mux (1. U , io.instruction_address, io.reg1_dataMux (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
测试用例 
运行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 )0. U Instructions .csr){InstructionsTypeCSR .csrrw){InstructionsTypeCSR .csrrs){Mux (rs1 =/= 0. U , io.csr_reg_read_data .| (io.reg1_data) ,io.csr_reg_read_data)InstructionsTypeCSR .csrrc){Mux (rs1 =/= 0. U , io.csr_reg_read_data .& (~io.reg1_data) ,io.csr_reg_read_data)InstructionsTypeCSR .csrrwi){InstructionsTypeCSR .csrrsi){Mux (rs1 =/= 0. U , io.csr_reg_read_data .| ((0. U (27. W ))##rs1) ,io.csr_reg_read_data)InstructionsTypeCSR .csrrci){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 InterruptStatus .None  && interrupt_enable) {   31 ,13 )### 3. U (2. W ) ### io.csr_bundle.mstatus(10 ,4 ) ### 0. U (1. W ) ### io.csr_bundle.mstatus(2 ,0 )Mux (io.interrupt_flag(0 ),0x80000007 L.U ,0x8000000B L.U )true .B true .B InstructionsRet .mret) {31 ,13 )### 3. U (2. W ) ### io.csr_bundle.mstatus(10 ,4 ) ### 1. U (1. W ) ### io.csr_bundle.mstatus(2 ,0 )true .B true .B false .B false .B 
CSR.scala
1 2 3 4 5 6 7 8 Mux (io.reg_write_enable_id && io.reg_write_address_id===CSRRegister .MSTATUS ,io.reg_write_data_ex,mstatus)Mux (io.reg_write_enable_id && io.reg_write_address_id===CSRRegister .MTVEC ,io.reg_write_data_ex,mtvec)Mux (io.reg_write_enable_id && io.reg_write_address_id===CSRRegister .MCAUSE ,io.reg_write_data_ex,mcause)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 8. U ){1. U 0. U 4. U ){8. U ){0. U false .B 0. U true .B 1. U 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 ) 0x30046073 L.U ) 0x30051073 L.U ) 0x30002573 L.U ) 
在测试代码jump to trap handler and then return中,以下代码存在问题 
 
1 2 3 4 c.io.csr_regs_debug_read_address.poke(0x300 .U ) 0x1888 .U )0x342 .U ) 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)
测试用例 
进行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 ())Flush  := io.JumpFlag 
CPU.scala
1 2 3 4 5 JumpFlag  := ex.io.if_jump_flagFlush 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 false .B false .B false .B false .B true .B true .B 0. U )0. U )true .B true .B true .B 
测试用例和波形图 
前三个测试的测试用例和波形图和lab1的CPUTest的三个波形图相同,这里不再重复分析,点击跳转 
第四个测试的测试用例和波形图和lab3的ThreeStageCPU的第四个波形图相同,这里不再重复分析,点击跳转 
FiveStageCPUForward 
填空代码 
control.scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 false .B false .B false .B false .B true .B true .B 0. U  && (io.rd_ex === io.rs1_id || io.rd_ex === io.rs2_id)){true .B true .B true .B 
Forwarding.scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 0. U ){ForwardingType .ForwardFromMEM 0. U ){ForwardingType .ForwardFromWB ForwardingType .NoForward 0. U ){ForwardingType .ForwardFromMEM 0. U ){ForwardingType .ForwardFromWB 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
时钟周期 
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)
时钟周期 
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 (0. U ,IndexedSeq (ForwardingType .NoForward  -> (io.reg1_data),ForwardingType .ForwardFromWB  -> (io.forward_from_wb),ForwardingType .ForwardFromMEM  -> (io.forward_from_mem)val  reg2_data = MuxLookup (0. U ,IndexedSeq (ForwardingType .NoForward  -> (io.reg2_data),ForwardingType .ForwardFromWB  -> (io.forward_from_wb),ForwardingType .ForwardFromMEM  -> (io.forward_from_mem)Instructions .jal ||Instructions .jalr) ||InstructionTypes .B )Instructions .jal ||Instructions .jalr) ||InstructionTypes .B ) && MuxLookup (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)Mux (io.interrupt_assert, io.interrupt_handler_address, MuxLookup (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)MuxLookup (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 false .B false .B false .B false .B 0. U  && (io.rd_ex === io.rs1_id || io.rd_ex === io.rs2_id))||0. U  && (io.rd_mem === io.rs1_id || io.rd_mem === io.rs2_id))) {true .B true .B true .B 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 0. U ) {ForwardingType .ForwardFromMEM 0. U ) {ForwardingType .ForwardFromWB ForwardingType .NoForward 0. U ) {ForwardingType .ForwardFromMEM 0. U ) {ForwardingType .ForwardFromWB ForwardingType .NoForward 0. U ){ForwardingType .ForwardFromMEM 0. U ) {ForwardingType .ForwardFromWB ForwardingType .NoForward 0. U ) {ForwardingType .ForwardFromMEM 0. U ) {ForwardingType .ForwardFromWB 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 ARADDR  := addrAWADDR  := addrAXI4LiteStates .Idle ) { false .B false .B ARVALID  := false .B RREADY  := false .B AWVALID  := false .B WVALID  := false .B BREADY  := false .B AXI4LiteStates .ReadAddr ARADDR  := io.bundle.addressAXI4LiteStates .WriteAddr AXI4LiteStates .ReadAddr ) { ARADDR  := addrARVALID  := true .B ARREADY ) {ARADDR  := addrARVALID  := false .B AXI4LiteStates .ReadData AXI4LiteStates .ReadData ) { RVALID ) {RREADY  := true .B RDATA RDATA true .B AXI4LiteStates .Idle AXI4LiteStates .WriteAddr ) { AWADDR  := addrAWVALID  := true .B AWREADY ) {AWVALID  := false .B AXI4LiteStates .WriteData AXI4LiteStates .WriteData ) { WDATA  := io.bundle.write_dataWVALID  := true .B WREADY ) {AXI4LiteStates .WriteResp AXI4LiteStates .WriteResp ) { BREADY  := true .B BVALID ) {WVALID  := false .B true .B 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 AXI4LiteStates .Idle ) { false .B false .B ARREADY  := false .B RVALID  := false .B AWREADY  := false .B WREADY  := false .B BVALID  := false .B ARVALID ) { AXI4LiteStates .ReadAddr ARADDR AWVALID ) { AXI4LiteStates .WriteAddr AXI4LiteStates .ReadAddr ) { true .B ARREADY ) {AXI4LiteStates .ReadData ARREADY  := true .B RREADY ) {ARREADY  := false .B AXI4LiteStates .WriteAddr ) { AWADDR AWREADY  := true .B AXI4LiteStates .WriteData AXI4LiteStates .ReadData ) { RDATA  := io.bundle.read_dataRVALID  := true .B RREADY ) {AXI4LiteStates .Idle ARREADY  := false .B AXI4LiteStates .WriteData ) { WREADY  := true .B WVALID ) {AWREADY  := false .B WDATA WSTRB .asBoolstrue .B AXI4LiteStates .WriteResp AXI4LiteStates .WriteResp ) { BVALID  := true .B BREADY ) {WREADY  := false .B AXI4LiteStates .Idle 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 )100000000. U )6. U )0xBEEF .U )
poke和expect缺少c.clock.step()语句,在波形图中无法从regs_debug_read_data得到期望数据
fiveStageCPUTest 
第1、2、4个测试和波形图和lab1的CPUTest的三个波形图相同,这里不再重复分析,点击跳转 
第三个测试用例同threeStageCPUTest