Featured image of post CS61c_5 — Intro to Synchronous Digital Systems

CS61c_5 — Intro to Synchronous Digital Systems

Logic Design

  • And , Or

Transistors

MOS Transistor

  • n-type MOSFET (NMOS)
  • p-type MOSFET (PMOS)
  • 有三个端口:gate, source, drain,当gate电压高于source电压时,NMOS导通;当gate电压低于source电压时,PMOS导通。记住:DGS。
MOSFET
1
2
how to make not gate using MOSFETs?
如下图, 可以知道当X输入0时,PMOS导通,输出为1;当X输入1时,NMOS导通,输出为0。X与Y的关系就是NOT关系。
NOT Gate
1
2
3
其实也可以做一个NAND gate.
如下图:上方是两个PMOS并联,下方是两个NMOS串联。当A和B都为1时,下方的串联N通过,上方的并联P断开,输出为0;否则输出为1。
C = A NAND B = NOT (A AND B) 
NAND Gate
  • 突然发现把1V和0V反过来不就是and了吗,但是ai又说工业上是用NAND + NOT 来实现and的,不懂(

Signals and Waveforms

  • notice delay!
  • Combinational logic(CL) circuits: output only depends on current input ; similar to a pure function in mathematics, y = f(x). (No way to store information from one invocation to the next. NO side effects.)
  • state element: a device that can store information. (e.g., flip-flop, latch, register)

accumulator 累加器

  • feedback loop: output feeds back into input
  • 一个寄存器是n个flip-flop并行组成的,可以存储n位二进制数。
  • 其中涉及到DQ flip-flop,D是data input,Q是output。当时钟上升沿到来时,D的值被锁存到Q中。建立时间、保持时间、Clock-to-Q时间等都是设计时需要考虑的因素。往往我们都希望D的值在建立时间和保持时间保持稳定,希望Q时间越短说明电路越快。
  • 这个D触发器在我之前的fpga的博客里有介绍过。
  • 超频可能会导致时序问题,即数据在建立时间和保持时间内的一些错误值被锁存到寄存器中,导致电路行动异常。

Pipelining to improve performance

  • 对于一个简单的CL循环电路,一次迭代的时间是Clock-to-Q + CL delay + setup time(必须有一段稳定的建立时间)。
  • 可以通过流水线来提升性能,比如一个电路中有一个add 和一个shift,直接通过的话会有一个巨大的CL delay; 但是如果我们在中间添加一个寄存器,那么add和shift就可以同时进行,CL delay被分成两部分,性能提升了。(bottleneck被打破了)
  • 但是流水线也有一些缺点,比如增加了寄存器的数量,增加了时钟周期的数量,增加了时序问题的风险等。

logic gates

  • 我们都知道基本的逻辑门有AND, OR, NOT, NAND, NOR, XOR, XNOR等。
  • 对于n个输入的xor门,如何判断输出是0还是1呢?其实很简单,输出为1当且仅当输入中有奇数个1;输出为0当且仅当输入中有偶数个1。
  • 例如对于4输入的xor门,输入为0,0,0,0时输出为0;输入为1,0,0,0时输出为1;输入为1,1,0,0时输出为0;输入为1,1,1,0时输出为1;输入为1,1,1,1时输出为0。

bool algebra

  • or : A + B = A OR B
  • and : A * B = A AND
  • not : A’ = NOT A (其实是A头上一个小横线)
  • 一些算术定律:
bool algebra
1
2
3
4
5
6
其中可以推导一下distribution law:
x + yz = (x + y)(x + z)
证明:
1. 右式 = x * x + x * z + y * x + y * z
2. 由于 xy + x = x, 所以 x * x + x * z + x * y + y * z = 
x + x * z + x * y + y * z = x + y * z 得证
  • 布尔代数有什么用?对于一个复杂的逻辑电路,我们可以通过布尔代数来简化它,从而得到更加简答的等效电路,减少门的数量。
  • 注意真值表、电路图和布尔表达式之间的关系,一般来说后面二者可以随意转化,真值表和布尔算式也可以相互转化,但是一般我们不会从真值表直接到电路图,一般都是先要搞成布尔表达式然后简化一下得到电路图。

project 2: CS61Classify

  • 提前声明:这个项目的venus.jar也不太行,可以换一个更新版本的。

part A: Mathematical Functions

  • 这个部分主要是完成一些基础的函数,比如abs,relu,dot,argmax等,个人感觉venus在这里用处不大,不怎么需要逐步调试。
  • 一个小问题:在relu中要求如果array长度小于1,就要以78的错误码退出程序(用法参考QA,t.execute(code=78)),但是提供的测试类中的checkarray函数里面有一个assert语句要求array长度大于0,所以要么就把这个assert注释了,要么就不用checkarray了,或者不考虑78了,反正要求是测试覆盖率是100%(
  • abs,argmax,dot都是简单的,但是matmul就很麻烦了,看起来原理很简单,但是涉及到很多寄存器的时候一些鸡毛蒜皮的小事情让代码变得屎山.
 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
在matmul中:
思路很简单,对于每个输出元素C[i][j],我们需要计算A的第i行和B的第j列的点积。(直接调用之前写好的dot函数就行)
1. 错误判断:如果行列数小于1,或者A的列数不匹配B的行数,按照给定的错误码退出。
2. 我们将用到大量的寄存器用来保存一些重要的变量,用s还是t呢,
   这里考虑到后面我们将会大量的使用dot函数,而这个函数会肆无忌惮的使用t寄存器,如果我们使用t寄存器,那么需要在调用前保存,调用后恢复,这样需要重复做很多次,
   所以使用S寄存器,一是dot保证了自己负责s系列寄存器,所以我们不用担心被dot修改,而是这样的话我们只需要在开始的时候保存一次,结束的时候恢复一次就行了(从内存移动次数来说也是更好的)。
   所以我们在prologue阶段开32字节栈空间来保存ra,s0-6.
3. 由于后面会调用dot函数,所以一开始需要把a系列保存到s系列。(这里我们不需要存a4,因为和a2一样)
4. 后面我们会用到A矩阵一行的地址偏移量(字节),所以可以提前使用slli s6, a2,2来计算出这个偏移量。
5. 外层循环条件:i < a1 (bge t0, s3, outer_loop_end), li t1,0 (j = 0)
6. 内存循环开始: bge t1, s4, inner_loop_end (j < b1)
   我们需要确定dot的几个参数:
   - a0,A的第i行地址
   - a1,B的第j列地址
   - a2,A的列数(也是B的行数)
   - a3,stride of A
   - a4, stride of B
   对于a0,鉴于前面我们已经计算了一行的字节偏移量,所以a0 = s0 + i * s6
   对于a1,先用slli t3, t1, 2计算出列的字节偏移量,然后a1 = s1 + t3
   a2 直接就是s2, a3 就是1, a4 就是s4(B的一行)

   注意在call dot之前, 我们可能需要保存一些变量,比如之前有一些临时计算出来的t寄存器的值,可能后面可以接着用,但是recompute一下也行。

   然后call dot
   之后dot的结果会保存在a0中,我们需要把它存到C
   比如如果之前如果行偏移量保存在了t2,列偏移量保存在了t3,那么C的地址就是a6(s5) + t2 + t3, sw一下。

   最后列t1+1,内循环跳回去。
7. 内循环结束: i+1,外循环跳回去。
8. 外循环结束: 恢复寄存器,返回。
  • 总结一下就是对于基准指针,步长一类的变量我们通常使用s寄存器稳定存储,临时变量比如位置、临时偏移量等都用t寄存器。

part B: File Operations and Main

  • ReadMatrix函数:需要将一个bin格式的矩阵读到内存中
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
txt格式矩阵:
3 3 
1 2 3 
4 5 6 
7 8 9

bin格式矩阵:
前8个字节用来存储行数和列数。没有空格直接衔接矩阵内容。
例如: 03 00 00 00 03 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 ...
bin文件默认采用小端序存储,刚好RISC-V也是小端序。
  • 有了上次matmul的经验,这次我所有的重要的变量都用s寄存器存放,因为我们将会大量使用各种file函数,以防万一,只有一些临时的计算结果使用t寄存器。

  • 唯一需要注意的点是malloc的返回值,这个在util.s中并没有明确指出,在文档中也没写?反正经过实测当malloc失败的时候返回0就是了,beq a0, x0, malloc_failed就行了。

  • WriteMatrix函数,写过了read这个就很简单了,直接把a1里的东西原装写入(二进制),唯一要加的就是两个4字节的行列数。我是用malloc了一个8字节的空间来lw了行列数的值,然后分两次fwrite到文件中的。

  • 对了,这里有点奇怪的是在outpus的test_write_matrix文件夹中只有用来测试正确性的reference.bin,但是我们的函数中需要一个a1是内存中的数组地址,这玩意没给啊🤣,所以我们需要面向答案编程,先用utils的工具convert把bin转换成txt,得到正确矩阵后作为参数传入a1.

  • 还有就是unit测试中只有当code是0的时候我们需要check_file_output,不然就算代码对了这里会报错assert file_output != expected_output, 加一个if就好了。

  • Put all components together in classify.s , 将之前写好的函数按照神经网络的结构依次调用就行了,唯一一个坑(我不知道是不是TA故意的)是在matmul函数中最终的结果是保存在a6里返回的,而我是用s6来存储中间矩阵运算的结果的,所以必须先free掉第一层的矩阵结果,但是free这个函数在util中可以发现它竟然使用了a6,修改了这个值,所以我们必须保存一下a6,等free完了再恢复。

  • 一些小细节: fopen函数确实如同材料所说可以用w模式打开,如果不存在这个文件会自动创建,问题是比如我们的output路径如果指定的是一个不存在的文件夹,不好意思会报错,所以得提前手动创建一下。

  • 自己手写数字测试:emmm,感觉这个提供的分类器准确率还有提升空间🤣

Licensed under CC BY-NC-SA 4.0
啊啊啊啊啊啊啊
使用 Hugo 构建
主题 StackJimmy 设计