前言

这是 bluebeanP6 回忆录。

我认为 P6 的难度较 P5 降低了许多,因为在 P6 只需要在 P5 的流水线基础上进行增量开发即可。当然,若 P5 的架构不完善的话,在 P6 可能将要坐大牢。

按照课程组(2021 版,以后可能会有变化)的要求,在 P6 中主要需要完成一下任务:

  • IMDM 的外置。
  • P5 的基础上对指令集进行增量拓展。
  • 添加乘除法模块,实现和乘除法相关的指令。

外置 IMDM 模块

P6 中,我们的 IMDM 模块需要外置,外置的模块在课程组提供的 test bench 中。在 CPU 中需要提供相应的与 IMDM 通信的端口。

IM 的通信没什么好说的,直接把原来 IM 的接口接出去就行。

课程组的 DM 模块采用了字节使能方法来支持按字节读写的功能。这部分可以阅读课程组的代码后自行思考相应的实现方式。

我在实现过程中,产生了一个新的信号 MemWrite 用于指示读写哪些字节。

1
2
3
wire [3:0]MemWriteD = Sw == 1'b1 ? 4'b1111 :
Sh == 1'b1 ? 4'b0011 :
Sb == 1'b1 ? 4'b0001 : 4'b0000;

在连接 DM 的信号时,采用了如下方式:

1
2
assign m_data_wdata  = AD1M << {ALUAnsM[1:0], 3'b000};
assign m_data_byteen = MemWriteM << ALUAnsM[1:0];

其中 ALUAnsMALU 的计算结果(M 级),存储着要写入的地址信息。

另外在 DM 读出的数据要进行相应的数据扩展。这里写一个 DM_EXT 模块判断一下对应的指令再进行相应的符号扩展就可以了。

指令集拓展

P6 中一项重要的工作就是进行指令集拓展。在一个良好的 P5 基础上进行拓展并不是难事。

建议每拓展一个指令就进行相应的功能测试。不求数据点多强,至少基本的功能测试要做好。

例如在拓展了 bne 指令后,进行简单的功能测试:

1
2
3
4
5
6
7
8
9
10
ori $1 0xf0
ori $2 0xf0
bne $1 $2 end
nop
lui $1 0xff # run this
bne $1 $2 end
sw $1 0($0)
lui $1 0xee # not run this
end:
lui $5 0xcc

建议同学们在课下针对每一个指令都做好测试,减少后期 dbug 难度。

乘除模块

关于乘除指令,我们要拓展的有 mult, multu, div, divu, mfhi, mflo, mthi, mtlo

我们要模拟出 hi, lo 这两个寄存器。

在写乘法时可以采用写法:{HiTem, LoTem} = $signed(A) * $signed(B);。这里 HiTemLoTem 是用来暂时存储计算的值,当模拟乘除延迟结束时将结果存入 hi, lo 寄存器。

此外要注意和乘除法指令相关的阻塞。当乘除模块正在运行时,遇到新的乘除指令是要阻塞流水线的。这里存在一定的优化空间,比如两条连续的乘法指令,我们就可以忽略掉前一条乘法指令,因为后一条指令会重新写 hi, lo 寄存器。

上机

上机一般是两道简单题 + 一道难题。可能会涉及改动乘除模块。建议也是提前把课上可能用到的接口留出来。此外上机前可以提前想好添加一个功能时可能要改动哪些相应的信号。

这里给出一个我在上机时遇到的一个困难指令:shl

shl 的功能是交换乘除法模块中 hi 寄存器和 lo 寄存器中的值。乍看上去很简单,但是这里存在坑点。比如在乘除法之后紧跟一串 shl 指令,最优情况是不进行阻塞。

标准思路:交换两个寄存器的值,等价于交换两个寄存器的名字。使用一个 tag 标记,当 tag1 时,hi 作为 lo 寄存器,lo 作为 hi 寄存器;tag0 ,情况照常。

这个题实际上是可难可易……如果不卡时间的话(允许乘除法后遇到 shl 时阻塞),本题毫无难度,否则难度暴增。(主要是没有考虑到这样阴间的阻塞情况。我在考前一直以为在乘除法后紧跟的是 mflomfhi,直到舍友告诉我那是“在乘除法结束后紧跟 mfhimflo”)

结语

P6 的工作基本上只有这些内容了。说实话我感觉 P5 做的更艰难一点(毕竟是从头开始搭流水线)。

有能力的同学也可以在课下空余时间拓展更多指令。