cs 61c学习总结

代码资料
课程网站
最后采用了su19的资料。最新的课程评分系统不能用。以前的课程评分系统可以访问,但是已经过期锁死了。在网上找到了别人写过的su19.暂时还不知道全不全。可以对照着参考。还能加快速度。时间紧张基本都是参考着来了。学会了就行。

学的太费劲了。英语差到流泪。一天看一节视频,什么都听不懂,字幕也跟不上。教材看不明白,采用了旧版的中文版。
教材答案

第一周

介绍一下二进制,还有C语言的基础,指针。lab0就是简单的设置。
深入介绍了C语言的指针和内存管理。学习用gcc编译程序,用cgdb来调试,用valgrind管理内存。lab1很简单。
gcc
Debugging with GDB
valgrind使用指南

第二周

学习了RISC-V最基础的部分。lab2运用位运算之类的实现了一些功能,以及对汇编语言的理解。
cunit
cunit中文

project1

项目是为了熟悉C语言和内存管理。做一个跟踪一系列机场之间飞行的飞行系统。整理一下集成测试文件的流程。

  1. 检查命令行参数是否符合需求。测试需要一个飞机列表文件,包含每个飞机的时间表的文件,一个路线表文件。把它们的名字存入测试文件名列表里。
  2. 读取文件名列表中的文件。创建一个飞行系统,把飞机场列表里的飞机加入系统里并打印。把每个飞机的时间表加入系统并打印。检查路线表,打印每个路线可以完成的最快时间。全打印完成后,删除系统。
  3. 检测做的代码输出的总体花销。
  4. 关闭测试文件。

第三周

学习了数字逻辑相关的东西。lab3练习了位运算,RISV的函数调用和返回,RISV调用者与被调用者,伪随机数生成。lab4练习调试,RISV与内存的接口,最后一个也是位运算问题。

proj2

把C语言生成汇编代码。

task1 二进制表达式不变

目的:最新的值存在S1,返回两个表达式中第一个的值。
计算第一个表达式,结果存在S1,栈中申请空间,把S1存进去。计算第二个表达式,结果存在S1,把栈中存的取出来放在S2。释放栈。返回S2。

task2 二进制表达式
  1. 按位与或异或:后面的表达式存在S1,前面的存在firstReg。确定二者位数匹配。进行AND,OR,XOR操作。确保我们压扁所有溢出以备将来使用。
  2. 逻辑与:后面的表达式存在S1,前面的存在firstReg。创建两个标签notTrue,quit,如果两个寄存器有为0的,S1存0,结束。如果都不为0,S1存1,结束。确保我们压扁所有溢出以备将来使用。
  3. 逻辑或:后面的表达式存在S1,前面的存在firstReg。创建两个标签isTrue,quit,如果两个寄存器有不为0的,S1存1,结束。如果都为0,S1存0,结束。释放notTrue,quit。
  4. 等于,不等于:beq判断。
  5. 加法:后面的表达式存在S1,前面的存在firstReg。c1PtrTotal,c2PtrTotal确定child是不是指针。匹配类型的尺寸。如果S1或firstReg是指针,则计算数据类型大小,与其相乘,再和另一个寄存器里的值相加存入S1。如果都不是指针,用普通的ADD指令。确保我们压扁所有溢出以备将来使用。
  6. 减法:如果第一个是指针,第二个不是,方法类似加法;如果反过来,则没有意义;如果都是指针,则计算数据类型大小,与S1相乘,与firstReg相加结果存入firstReg,再与S1相加,最后除以数据类型尺寸;如果都是数字,用SUB普通的减法。确保我们压扁所有溢出以备将来使用。
  7. 大于等于:如果两个都是指针而且数据类型相同且不小于0,则比较firstReg是否小于S1,小于则S1置一;如果不是指针类型,分别比较题中提示的五种类型,和上面一样比较大小。最后用异或来重置一下S1
  8. 大于 :和上面类似。多两个标签equal,quit。如果相等则为0,比上面少了最后一步。结尾要释放标签。
  9. < =:和上一个类似。如果相等则为1.别的一样
  10. <:只要中间那堆就行了。判断不同类型的大小。
task3 调用表达式
  1. 函数的ID,参数,函数标签。
  2. 参数不为0的话,申请offsets, sizes用来计算需要多少栈空间和把参数放在哪里,值为数据类型大小*参数数组大小。把算出的值存入decr。
  3. 减少栈decr大小的空间。
  4. 遍历参数列表,dispatch每个参数的孩子,分别存到S1。
  5. 通过size判断它是字还是字节,把S1存入栈中offset位置。
  6. 最后释放offsets, sizes。
  7. 跳转到函数标签,把返回值A0存入S1。还原栈。
task4 序言与结语

通过保存必要的寄存器来完成函数声明。保存所有可能需要的寄存器。

  1. 栈中申请48b空间。把S1~S11存入栈,最后把RA存入栈
  2. 返回48
  3. 还原部分和这个差不多。倒过来,先RA,再S11~S1
task5 if声明
  1. 创建两个标签condEnd, elseStart。表达式三个,条件,if和else的函数体。
  2. 如果只有if,运行条件表达式结果存在S1,判断是否为真。否的话跳转到elseStart。是的话进行condEnd,执行if函数体,最后跳转到elseStart。
  3. 如果有if和else,创建一个标签quit。同样解析条件判断真假。执行完if函数体,调到quit标签。否则调到elseStart标签,运行else函数体,然后到quit标签。
  4. 释放1里的两个标签。
task6 while循环
  1. 创建两个标签startWhile, endWhile。两个表达式,条件,while体。
  2. startWhile标签开始,判断条件,存入S1。假的话跳转到endWhile。真的话运行while体,再次跳转到startWhile。
  3. endWhile标签开始,释放两个标签。结束

第四周

学习处理器和高速缓存。

Lab5 设计逻辑电路

  1. 设计NAND, NOR, XOR, 这个简单;接下来数据选择器:一个有 2n 输入端的数据选择器有 n 个可选择的输入-输出线路,可以通过控制端来选择其中一个信号被选择作为输出。2-to-1 MUX的公式是:Z = (A and ~S) OR (B and S);4-to-1 MUX公式:Z = (A and ~S0 and ~S1) or (B and ~S0 and S1) or (C and S0 and ~S1) or (D and S0 and S1)。大型的数据选择器可以由较小的数据选择器级联来实现。例如,一个8选1数据选择器可以由两个4选1数据选择器组成,一个4选1数据选择器可以由两个2选1数据选择器组成。
  2. 状态存储:就照着提示来,一顿傻瓜操作。结果那个output的pin是朝东的。应该改成朝西的。我还找了半天不知道哪错了。
  3. 有限状态机到数字逻辑:照着真值表连线。
  4. 分割:照着做。变成负的最后融合输出out2的部分,在最下面bit7要加非门。
  5. 循环右移:类似于lab3中的伪随机数生成。B中的四位,从最高位也就是3开始,用多选器,如果x位是1,则A中低2^x的数移位,剩下的移位。例如例子里的5,就是0-3位挪到12-15去,4-15挪到0-11去,然后0挪到15,1-15挪到0-14去。

Proj3-1

1. 设计一个ALU

4-6. 无符号逻辑右移:需要一个splitter,因为32位的话,只能取最低0-4位。用一个shifter元件。有符号算术右移:和上面一样。shifter选项改一下。逻辑左移
7. 小于?:用一个comparator,选择补码。正数和0的补码就是该数字本身。负数的补码则是将其对应正数按位取反再加1。数字a(正负数皆可)的补码即为 -a。补码相加的符号位就是大小的值。然后用0扩展到32位。
8-10. 无符号除法:把0中加法器换成divider。无符号求余数:把结果接到rem。有符号乘法:和8类似。
11. 乘法,输出高位。结果接到下面out。14照着别人写的答案来失败了。因为需要修改alu_harness.circ,但是没看明白咋回事。改成input里3c后面的都没有。ALU_Sel里,只有三行,00, 0e, 1c。
12-13. 略。没通过测试。应该是要修改那个文件。

2. 寄存器文件
  1. 一个多路输出选择器。输入是RegWEn, 决定在下一个时钟上升边沿将数据写入寄存器。选择信号决定数据写入到哪一个寄存器。输出是表格中ra到a0是否可用的信号,位置按照x编号来。
  2. 针对上述每个寄存器编号做一个寄存器,写入的数据D是Write _data,WE是该寄存器是否可用的信号,clk是时钟信号。reset空着,输出接每个寄存器名称的隧道。一共做八个。还要再做一个x0,数入一个constant,为0.
  3. 做两个多选器。输入是从x0到a0九个寄存器的隧道,选择信号是rs1-2,决定输出那个寄存器的值。输出read_data1-2,是驱动寄存器的值。

第五周

学习高速缓存,数据级并行。

Lab7 高速缓存

步长a1,次数a2直接影响多少高速缓存被击中/漏掉。复习:

命中策略
  1. 写回:只写入高速缓存,脏位变为1,当块从写回高速缓存中被赶出且脏位为1,更新内存,硬件实现很难;
  2. 写直达:同时写入告诉缓存和内存中,延迟大,硬件实现容易;
  3. write-around:写转是指在每种情况下,数据都只写到主存;如果我们在缓存中写入了数据块,那么数据块的有效位就会变为无效位。本质上,写转缓存中不存在写命中;写“命中”和写错的效果是一样的。
缺失策略:
  1. 写分配:(write-back/write-allocate)在写失败时,把错过的块拉到缓存中。对于写回,意味着内存永远不会被直接写入;写操作总是对缓存进行,内存在驱逐时更新。
  2. 不写分配:(write-through/no write allocate)在写失败时,不将错过的块拉到缓存中。只更新内存。
替换策略:
  1. LRU:最近最少使用。
  2. Random:决定退出一个缓存块以腾出空间时,随机选择缓存中的一个块来退出。
  3. MRU:最近使用的,决定驱逐一个缓存块以腾出空间时,选择所有其他可用块中最近使用的块。
EX1 两个内存访问场景(自制答案无从验证)

Q: 高速缓存块多大?(32)一个块能容纳多少连续访问(步长)?(block size / a1)一个高速缓存有多少数据?在内存中,映射到同一集合的块之间有多远(可能会产生冲突)?缓存的结合度是多少?一个特定的块映射到缓存中的什么位置?在考虑为什么某个特定访问失败或失败时:您以前访问过此数据吗?如果是,它是否仍然在缓存中?

  1. hit rate=0.int有四个字节,块大小为8字节,块地址为字节地址/每块字节数,no.块为块地址%块数,步长为8(32个字节),块数为4,重复4次,只有读操作,128字节的数组访问16次每次都是一直在只访问第一个块第一个地址。提高rep count不会提高hit rate,因为仍然一直在只访问第一个块第一个地址。步长改为5可以提高hit rate.
  2. hit rate=0.75.块大小为16字节,有16个块,步长为2(8个字节),option=1,有读和写两次访问。相联度为4,256字节的数组访问64次,有四个组,每个组4个块。组地址是字节地址/每块字节数%组数,在组内把最先访问的块覆盖,一共四个块,所以每个组四次覆盖一次。当无限提高reg count时,hit rate无限接近1.数组的字节数和cache的字节数相等,第一次循环后每个块都被填充了数据,所以后面的访问全都命中,所以hit rate会很高。we should try to access index_ of the array at a time and apply all of the funcs_ to that location so we can be completely done with it before moving on, thereby keeping that _block hot in the cache and not having to circle back to it later on!
  3. L1:16/32=0.5,L2全miss了。L2 16次访问。L1miss的话会访问L2。减小Li的块容量可以让L2得到访问。提高L2的块容量和相联度可以提高L2的hit rate不改变L1的。
EX2 循环排序和矩阵乘法

对于最内层循环,B移动一个索引,A移动n个索引。第六个最快,和第四个差不多。第二个和第五个最慢。当内存访问利用空间和时间局部性,利用缓存中已经包含的块时,缓存的性能会更好(缓存命中更多,缓存丢失更少)。

  1. 对C中每个元素,A反复遍历一列直到顶层循环改变,B每次第二层循环改变就遍历一整行。所以每次顶层循环改变就遍历一次B,这样整个过程会反复遍历B,不断刷去缓冲区数据。A,C一共遍历一次。
  2. C随着底层循环遍历一列,顶层循环改变则换一列,A一直遍历一个元素直到第二层循环改变,B随着底层循环遍历一列,第二层循环改变则换一列。所以每次顶层循环改变就遍历一次B。A遍历一次。但是C每次改变顶层循环就遍历一次C的一列,所以最慢。
  3. 对C中每个元素,每次改变顶层循环,就遍历一次A,B,C一共遍历一次。
  4. 底层循环反复遍历C的一行直到顶层循环改变,但是A和B都只遍历一个元素直到第二次循环改变。也就是A,B一共遍历一次,C每次改变顶层循环就遍历一次C的一行。重复利用率最高,所以最快。
  5. 5-6情况和上面的有些情况类似懒得看了。
EX3 缓存阻塞和矩阵变换

缓存阻塞是一种通过进一步改善内存访问的时间和/或空间局部性来尝试减少缓存丢失率的技术。
四层循环,前两层按照分块的大小遍历,后两层类似于给的那个,遍历每个小矩阵。

  1. 当矩阵尺寸小于缓冲区大小的时候,普通的转置更快。
  2. 尺寸不变下,随着分块的变大,性能先变好后变差。变好应该是因为小矩阵都可以装进缓冲区,变坏是因为矩阵太小,耗费了更多的时间来分块。

Proj3

你可能感兴趣的:(linux)