CS61c_8 — Control logic and lab05

Lab05: 熟悉logisim

setup

  • 本次lab我们将使用一个叫做logisim的工具来设计和模拟我们的电路,这个工具十分好用,只需要java -jar logisim-evolution.jar即可使用。
  • 鄙人在基于archlinux的niri界面上使用时遇到了一些问题,特此记录一下。
  • 故障现象:启动后界面可以正常显示1s,之后会变成白屏,无法操作。
  • 起初我以为和之前的一些jar一样是年久失修的问题,于是下载了最新的logisim-evolution,但是问题依旧,于是我开始怀疑是不是平台问题,毕竟niri存在一些小毛病很正常,于是我在windows上跑了一下,正常,说明就是niri的问题。
  • 根本原因:java程序默认用一种老窗口管理器(reparenting)(源于古老的x11),但是新的niri使用wayland合成器,要求程序各扫门前雪,所以就导致了这个问题。
  • 解决方法:export _JAVA_AWT_WM_NONREPARENTING=1,这个指令要求java的awt窗口使用non-reparenting的方式来管理窗口,这个就符合niri了。
  • 说多了都是泪啊😰

电路设计

  • 这次lab让我们通过一个模拟软件来体验电路设计的乐趣,大大减轻了实际操作的困难,具体内容不加以赘述,just try it,lab设计的很有意思,并且最后提到在第三个project中会继续用到,这让我对cs61c的课程设计更加膜拜,在上完cs61c课程后我将出一篇比较cs61c和csapp的文章,锐评一下😈

Control Logic

Control and Status Registers (CSRs)

  • CSRs are separate from the register file (x0-x31).
  • Used for monitoring the status and performance, and they can up to 4096 CSRs.
  • Not in the base ISA , but almost mandatory in every implementation, which is necessary for counters and timers, and communication with peripherals. 本来CSRs是基础ISA的一部分,但是后来由于ISA的模块化,就把它删了,现在属于是extension部分。
1
2
3
大家经常遇到一个叫flag的东西,通常指的是一位的信号,比如carry flag,zero flag,为什么一位信号我们用flag呢?
这个是由于人们在使用邮箱的时候,如果有新的邮件就会在邮箱上放一个小旗子,这样邮递员来取邮件的时候就知道有新的邮件,
然后他们会降下(清除)flag🤣
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
CSRs的常见指令:
csrrw rd, csr, rs1  # read the value of csr into rd, and write the value of rs1 into csr
csrrwi rd, csr, imm # just like csrrw, but change the value of rs1 to an immediate value 
csrrs rd, csr, rs1  # read the value of csr into rd, and set the bits in csr that are the result of bitwise OR between 
                      rs1 and csr
csrrsi rd, csr, imm # just like csrrs, but change the value of rs1 to an immediate value
csrrc rd, csr, rs1  # just like csrrs, but change the bitwise OR to bitwise AND NOT , rs1按位取反然后按位与
csrrci rd, csr, imm # 。。。

指令格式:
31-20 | 19-15 | 14-12 | 11-7 | 6-0
imm[11:0] | rs1 | funct3 | rd | opcode(1110011)
这里的12位imm用来选择哪一个csr,故而我们说有4096个csr。
当这个rs1的5位作为imm的时候,我们需要将其扩展成32位的,(无符号扩展即可),然后再写入csr中。

pseudoinstructions:
csrw csr, rs1 = csrrw x0, csr, rs1 # just write rs1 into csr, 可以把一些不常用的信号放到csr中。
csrrwi x0, csr, uimm = csrrw x0, csr, uimm # just write the immediate value into csr

System Instructions

System Instructions

Instruction Timing

通过在脑子中假想一条指令执行过程中control logic所做的事情,我们发现时序是非常重要的,对此我们以add指令为例来分析一下时序:

Instruction Timing
  • 之前我们提到过前往内存是很慢的,但是这里的Instruction Fetch是从指令cache中取的,所以和加法器差不多。

  • Critical path = $t_{clk-q} + \max{t_{Add}+t_{mux},t_{Imem}+t_{Reg}+t_{mux}+t_{ALU}+t_{mux}} + t_{setup}$,由于Control logic的delay很小(在这个公式中直接没标注),所以那一条路线直接忽略,而上式进一步简化为 Critical path = $t_{clk-q} + t_{Imem} + t_{Reg} + t_{mux} + t_{ALU} + t_{mux} + t_{setup}$。

  • 换一个例子,比如lw指令(5个阶段),Critical path = $t_{clk-q} + \max{t_{Add}+t_{mux},t_{Imem} + t_{imm} + t_{mux} + t_{ALU} + t_{mux} + t_{Dmem} + t_{mux}} + t_{setup}$ = $t_{clk-q}+t_{Imem}+t_{imm}+t_{mux}+t_{ALU}+t_{mux}+t_{Dmem}+t_{mux}+t_{setup}$,可以看到lw指令的critical path比add指令更长,多了一个Dmm的delay。

  • 下图是更抽象意义上的各个阶段的时序图。

Instruction Timing
  • 可以注意到的是不同类型的指令的时序是不同的,有的会经历5阶段,有的只有3或4阶段,这些会影响cpu的频率。

Control Logic Truth Table

Control Logic Truth Table
  • 这个图详细的展示了每条指令的control signal如何控制,非常全面,现在的问题是,我们该如何实现这个呢?
  1. ROM
  • Read-Only memory,我们只需要提前填充好每一个指令对应的信号word,在使用时我们判断出指令的类型然后选择对应的即可。
  • 这种方式很方便,如果有扩展指令或者压缩指令需要添加,我们只需要在ROM中添加对应的信号word即可。
  1. Combinatorial
  • 在现代芯片工艺中,通常直接设计一堆gates,这样更快也更紧凑。
1
2
针对第一种ROM,我们需要判断一个32位指令是哪一种,这个如何做到呢?
注意到我们的32位指令的某些部分的名字:func3,func7,opcode,这些都是指令的特征位,我们可以通过这些特征位来判断指令的类型。
Decoding
  • 对于opcode的最低两位默认都是11,如果有扩展指令集或者压缩指令集,可能需要考虑,但是对于RV32I来说,可以不考虑,总共需要考虑9位(1+3+5)。
  • 除了这个9位指令特征外,我们还有Compare bit,即BrEq和BrLt,一共是11位。
ROM
  • 这个ROM的输入是11位的判断信息,输出是15位的control singal word,可以计算就算11位全都用的到,那么最多存储量是2048*15=30720位,约等于3.75KB,这个存储量是完全可以接受的。

  • 在真正读取ROM的内容之前,肯定需要有一个decoder来根据指令的11位特征然后选择ROM的地址(one-hot)。

ROM
1
2
3
对于这个decoder的实现,他可以是一个很宽的and门,就是11位输入后面连着指令种类数量的一条and路线,这样连续的and保证了one-hot
至于ROM,我们可以先将one-hot与word按位与,然后所有的结果进行或运算,这样就得到了我们需要的control signal word
当然这种简单的实现方式必然效率不高。
  • 上述的方法会有很多冗余,我们可以有很大的优化空间,比如下图是针对无符号的一个优化,对于branch类,想要判断是BrUn其实很简单,像Inst[14]就不需要判断,BrUn = Inst[13] * Branch(Inst[6] * Inst[5] * …),如果想要判断具体是Lt还是Eq,在看inst[12]即可,节省了一位。
  • 通过这个我们也可以理解直接用combinatorial logic的方式来实现control logic了,针对每一个输出信号位,写出所有成立的输入组合即可,和ROM相比是每一个每一个输出信号复杂了,但是不不需要先判断指令类型了,而ROM是输出都搞好了,但是得先复杂地判断指令类型,互有利弊吧!
example
  • 一般来说对于某一个指令想要解码,首先是type opcode的5位,然后是func3的3位,最后是func7的1位,对于RV32I , 只需Inst[0]*Inst[1]即可。

总结

  • 泪目了,我们从高级语言c出发,逐步搞清楚了代码是如何一步步变成机器码在机器上执行的,然后我们从布尔单元出发,逐步设计了一个处理器,硬件和软件相遇了!
  • 用伟大的Bora教授的话来说,我们应该和父母朋友打个电话,庆祝这一伟大的时刻,WE MADE CONTACT !
  • 当然这肯定不是性能好的处理器,毕竟它有着长长的关键路径,但我们会持续优化的!
Licensed under CC BY-NC-SA 4.0
啊啊啊啊啊啊啊
使用 Hugo 构建
主题 StackJimmy 设计