北航计算机组成P4
北航计算机组成P4
前言
- 咳咳,最近太忙了作业好多啊,搭完CPU每周还要迭代自己的测评机,原来CO也可以训练我们面向对象的能力
课下
- 其实P4就是把P3的电路图翻译成代码,整体上没有难度,如果你P3有好好设计你的CPU的话,
没有好好设计的话,就照着学长设计好的CPU翻译,总之也是从一个一个的模块开始搭建,这里不赘述了
组合逻辑建模
- 我啰嗦一两句,最近几年课程组不知道在干什么(
明年等我当助教我也要搞得抽象),考试喜欢考一些奇奇怪怪的组合逻辑,所以或许你需要根据你使用的建模逻辑稍微熟练一些Verilog的语法,这样课上的时候或许不会那么慌 - 两种组合逻辑建模方式对于写CPU的思路还是有略微不同的,而且各有各的好处,见仁见智了
assign
- 最大的优点就是简洁、代码量很少,而且就是电路连线,从P3转过来会比较方便
assign ALU_out = (ALUOp == `ALU_add) ? A + B :
(ALUOp == `ALU_sub) ? A - B :
(ALUOp == `ALU_or) ? A | B :
(ALUOp == `ALU_and) ? A & B : 32'b0;- 一行就解决了ALU,反正就是挺爽的
- 缺点是对于复杂逻辑建模比较困难,所以强烈建议掌握
function
和task
(这个或许不用嘛)的用法,反正考场上会用到的,比如我随便编一个指令,要求实现gxadd rd, rs, rt
gray(00): 00
gray(01): 01
gray(10): 11
gray(11): 10
temp1 <- GPR[rs] + GPR[rt]
for i in range(0,16):
temp2[2*i+1: 2*i] <- gray(temp1[2*i+1: 2*i])
temp2 <- temp2 ^ temp1
if temp2 > temp1:
GPR[rd] = temp2
else:
GPR[rd] = temp1- 好,你现在用assign语句把它建模到ALU里面去(),是吧,这个时候
function
就要发力了,管他三七二十一全部丢进函数里面function [1: 0] cal_gray;
input[1: 0] origin;
if (origin == 2'b0) cal_gray = 2'b0;
else if (origin == 2'b1) cal_gray = 2'b1;
else if (origin == 2'b10) cal_gray = 2'b11;
else cal_gray = 2'b10;
endfunction - 其实也可以如果你熟悉掩码和格雷码的定义的话,例如
gray(x) = x ^ (x >> 1)
,又因为两位的二进制右移一位第一位一定是0,所以我们考虑将32位的数字的奇数位覆盖再整体右移,达到一次实现16次2位的异或temp1 = A + B;
temp2 = temp1 & 0xaaaaaaaa;
//0xaaaaaaaa就是....10101010
//反正奇数位就这样被覆盖了
temp2 = temp2 >> 1
//相当于进行了上面的 x >> 1,反正奇数位都是0了
temp2 = temp2 ^ temp1
//最终的temp2就是经历两位gray转化后的数字了assign ALU_out = (ALUOp == `ALU_gxadd) ?
(( ((A+B) ^ (((A+B) & 32'haaaaaaaa) >> 1)) > (A+B) ) ? ((A+B) ^ (((A+B) & 32'haaaaaaaa) >> 1)) : (A+B))
- 好,你现在用assign语句把它建模到ALU里面去(),是吧,这个时候
always@(*)
- 最大的缺点是代码量可能会比上面大一些,也可能大很多,全面准备吧
- 最大的优点就是适合复杂逻辑的建模,比如上面那个瞎编的指令
- 不过还是建议掌握
function
,多好啊,变成纯纯面向过程的语言了 - 讲一个经常会用的切片,好玩爱玩
- 假设你现在想要学lb、lh指令取出一个字中的某个字节或者半字,但是不想要一直if-else(考场上不会的话枚举是一个很好的选择),你想到了verilog可以切片
input wire h_adder;
input wire[1: 0] b_adder;
reg[31: 0] pos;
reg[0: 31] neg;
//pos[h_adder * 16 + 15: h_adder * 16]
//pos[b_adder * 8 + 7: b_adder * 8]
//错误用法,Verilog的切片两边要是常数
//正确写法
//定义高位到低位
pos[h_adder * 16 + 15 -: 16]
//从h_adder * 16 + 15 取低16位,按照高位到低位排列
//pos[h_adder * 16 + 15: h_adder * 16]
pos[h_adder * 16 +: 16]
//从h_adder * 16 取高16位,但是还是按照从高到低排列
//pos[h_adder * 16 + 15: h_adder * 16]
//定义从低到高
neg[h_adder * 16 + 15 -: 16]
//同上,但是从低到高排列
//neg[h_adder * 16: h_adder * 16 + 15]
neg[h_adder * 16 +: 16]
//同理
//neg[h_adder * 16: h_adder * 16 + 15]
- 假设你现在想要学lb、lh指令取出一个字中的某个字节或者半字,但是不想要一直if-else(考场上不会的话枚举是一个很好的选择),你想到了verilog可以切片
课上
T1 eam
eam $rd, $rs, $rt
- R型指令
ext_imm <- signed(GPR[rs][15: 0])
res <- ext_imm % 17
if signed(GPR[rt]) < 0:
GPR[rd] = one_ext(res)
else:
GPR[rd] = zero_ext(res)- 将rs寄存器低16位截断为有符号数,这个数对17取模得到的正余数,然后根据另一个操作数的正负进行1或者0拓展
T2 cptl
cptl $rs, $rt, label
- I型指令
temp <- PC + 4 + sign_ext(offset || 00)
PC <- temp
if GPR[rs][17: 12] == 6'b111111 || ......GPR[rs][5: 0] == 6'b111111:
GPR[rt] <- PC + 4
else:
GPR[31] <- PC + 4- 跳转类指令,然后链接内容和rs寄存器低18位是否有6个连续的1有关系
T3 olw
olw $rt, offset($base)
- I型指令
vadder <- GPR[base] + sign_ext(offset || 00)
temp <- Mem[vadder[31: 2]]
if func(temp):
GPR[rt] <- temp
else:
None- 如果读出来的数据是单调不减的,那么才写入
总结
- 课上没什么好玩的,第一个指令注意要使用有符号,不然你就会像我一样疑惑了半个小时为什么这么取模都是0然后暴力while循环求余数。。。
- 过两天会把P5好好更新的,顺便预告一下测评机,其实写好了,只是想等P6直接做一个流水线全的,over
- 先不写题解了,亲爱的小羊找我看视频,我先溜了
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Trash Bin for Chi!
评论