北航计算机组成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,反正就是挺爽的
  • 缺点是对于复杂逻辑建模比较困难,所以强烈建议掌握functiontask(这个或许不用嘛)的用法,反正考场上会用到的,比如我随便编一个指令,要求实现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))

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]

课上

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
  • 先不写题解了,亲爱的小羊找我看视频,我先溜了