======Week1====== //Week 1 notes// ---- ====Introduction==== * **How**: Implementation * **What**: Abstraction ==Multiple layers Abstraction== * 抽象层可以分为多层 * 任何的实现层都可以作为下一层的抽象层,通过接口提供功能即可 * 复杂的系统就是这么堆砌起来的 \\ {{ :cs:comp_n_arch:courses:fnti_i:abstraction_layers.jpg |}} ==Design a chip== 使用 hardware simulatior 来设计: // // {{ :cs:comp_n_arch:courses:fnti_i:design_chip.svg?350 |}} ==Folder Structures== {{ :cs:comp_n_arch:courses:fnti_i:class_contentes.jpg?500 |}} ====Boolean Functions and Gate Logic==== Boolean 使用 ''0'' 和 ''1'' 表示状态。 ===Boolean Logic=== ==Operations & Expressions== Boolean 的基础运算有三种://AND / OR / NOT//。其结果可以用真值表(//Truth Table//)表示: \\ \\ {{ :cs:comp_n_arch:courses:fnti_i:truth_table.svg?500 |}} \\ \\ 以上的这些逻辑运算可以组合成复杂的表达式,比如: 1 AND (0 OR (NOT (1))) 我们将其称为逻辑表达式。 ==Boolean Functions== 如果将表达式中的 ''0'' 和 ''1'' 以变量表示,那么得到的函数就被称为逻辑函数(//Boolean Functions//): f(x,y,z) = (x AND (y OR (NOT (z)))) 这样的函数也可以通过真值表来将其所有可能的结果列出,比如当 ''(x,y,z) = (1,0,1)'' 时,其真值表为: \\ \\ {{ :cs:comp_n_arch:courses:fnti_i:boolean_function.svg?200 |}} \\ \\ 可见逻辑函数可以用函数或真值表的方式来表达。 ==Boolean Indentites== # Commutative (x AND y) = (y AND x) (x OR y) = (y OR x) # Associative (x AND (y AND z)) = ((x AND y) AND z) (x OR (y OR z)) = ((x OR y) Or z) # Distributive (x AND(y OR z)) = (x AND y) OR (x AND z) (x OR(y AND z)) = (x OR y) AND (x OR z) # De Morgen (when NOT involved) NOT(x AND y) = NOT(x) OR NOT(y) NOT(x OR y) = NOT(x) AND NOT(y) #Idenpotence x AND x = x #Double Negation NOT(NOT(x)) = x ===Boolean Functions Synthesis=== 真值表是通过逻辑函数使用不同的变量计算出来的。但在硬件设计的过程中,这个过程是相反的。我们已经知道结果(真值表),而我们希望得到实现这个结果的功能(硬件/逻辑函数)。 ==将真值表转换为表达式== 一种方法是构建真值表的**析取范式** (//Disjunctive Normal Form//)。其步骤: - 按行遍历真值表,找出所有函数值为 ''1'' 的行 - 对每符合条件的行,写出一个能够得到函数值的逻辑函数 - 将得到的所有符合条件的逻辑表达式,进行 **//OR//** 运算 比如下面的例子: // // {{ :cs:comp_n_arch:courses:fnti_i:truth_table_to_bool_exp.svg?200 |}} # line 1 NOT(x) AND NOT(y) AND NOT(z) # line 3 NOT(x) AND y AND NOT(z) # line 5 x AND NOT(y) AND NOT(z) ==NP hard problem & Theorem== 上述的三个表达式进行 //OR// 运算以后,还存在着化简的空间。化简的结果可能存在多种,也存在一个最短的表达式。我们将求这个最短表达式的问题称为**NP困难问题** (//NP hard problem//)。\\ \\ 但无论如何,已经被证明的是: >//Any Boolean function can be repersented using an expression contining AND, OR, NOT operations.// 也就是说,无论效率怎样,所有的逻辑函数,都是可以通过有限的逻辑运算来表达的。不过,该定理可以被进一步的简化: >//Any Boolean function can be repersented using an expression contining AND, NOT operations.// 证明的过程很简单:如果可以用 //AND// 和 //NOT// 表示 //OR//,那么上面的推论就是成立的: # proof # using De Morgen (x OR y) = NOT(NOT(x) AND NOT(y)) ==NAND function== 计算机学家们总结出了一种逻辑函数,//**NAND**//,用于作为构建逻辑表达式的基础。其真值表如下: \\ \\ {{ :cs:comp_n_arch:courses:fnti_i:nand.svg?150 |}} \\ \\ 其逻辑表达式为: $$ \texttt{(x NAND y) = NOT (x AND y)} $$ //**NAND**// 的好处在于,只要使用这么一个逻辑运算,就能表达出所有的逻辑表达式。证明的过程也很简单:只要证明 //NAND// 既可以实现 //AND//,又可以实现 //NOT// 即可: ## proof # NOT NOT(x) = (x NAND x) # AND (x AND y) = NOT(x NAND y) -> ((x NAND y) NAND (x NAND y)) ===Logic Gates=== **门**(//Gate//,也称为(//Chip//))是一种实现了逻辑函数的**物理设备**。gate 的描述分为两个部分: * 接口(//Interface//)来诠释其功能。其 obstruction 只能有一种 * 实现:可以有非常多种 gate 根据复杂性也分为两种: * //Elementary(Primitive) gate//: 比如 //AND, OR, NOT, NAND//。 *// Compulicated gate//:通过组合基础逻辑运算的逻辑函数的实现。 gate 的接口与逻辑函数的输入和输出是一致的,不同的是其实现的方法。 ==Elementary logic gates: NAND== //**Diagram:**// \\ \\ {{ :cs:comp_n_arch:courses:fnti_i:nand_gate_1_.svg?200 |}} \\ //**Specification:**// if (a==1 and b==1) out = 0 else out = 1 ==Elementary logic gates: And, Or, Not== {{ :cs:comp_n_arch:courses:fnti_i:and_or_not.svg?450 |}} ==Composite gates== //Composite gate// 由基础门组合而来。比如下面的 Three way And,实际上是通过两个基础 and gate 构建的: \\ \\ {{ :cs:comp_n_arch:courses:fnti_i:3_way_and.svg?400 |}} \\ 可以看到前者是**接口**,而后者是**实现**。 ==Circuit implementations exmaple== //**AND:**// * 使用了灯泡来表示 output,灯亮为 ''1'',灯灭 ''0'' * 使用了//relay// 来作为输入:开着的时候为 ''1'',关着的时候为 ''0''。显然,只有 //relay// ''a'' 和 ''b'' 都开着时,灯才会亮。 {{ :cs:comp_n_arch:courses:fnti_i:circuit_and.svg?200 |}} //**Or:**// * 任意一个 //relay// 连上时,灯都会亮: {{ :cs:comp_n_arch:courses:fnti_i:circuit_or.svg?200 |}} 物理层面的实现是 EE 的内容。CS 只关注功能实现。 ===Design with HDL=== //Hardware Description Languge//(HDL) 是一种允许设计者在电脑上设计并模拟测试硬件结构的语言。设计者可以通过 //HDL// 模拟硬件,并在硬件模拟器中使用,测试。一个典型的 //HDL// 程序应该包括如下的信息: * //Interface// * 硬件的功能描述 * 硬件名 * 硬件的输入输出 * //Implementaion// 比如下面的例子: /** API documentation: what the chip does. */ CHIP ChipName { IN inputPin1, inputPin2, ... ; OUT outputPin1, outputPin2, ... ; PARTS: // Here comes the implementation. chipPart(connection, ... , connection); chipPart(connection, ... , connection); } //HDL// 是一种静态描述语言(//functional & declarative//)。//HDL// 的执行会在之后交给解释器执行,但 //HDL// 编程本身不涉及任何的执行。因此,//HDL// 可以以任意顺序书写。 \\ \\ 即便如此,为了维护可读性,//HDL// 的约定是按照设计图,**从左到右**依次描述 gate. ==Design I : General Idea== 以 //XOR// 为例。在最开始设计时,我们会得到硬件的结果信息,大多数情况下是真值表。根据真值表,我们可以反推出需要实现的逻辑函数是什么,比如 //XOR// 的逻辑函数可以通过其真值表的第二第三行判断: \\ \\ {{ :cs:comp_n_arch:courses:fnti_i:xor_1.jpg?400 |}} ==Design II : Gate diagram== 根据上述的信息,可以将上面的逻辑转化为包含基础逻辑运算的 idea: out = 1 when: a And Not(b) or b And Not(a) 根据该 idea,可以进一步的将输入输出和逻辑运算转换为初步硬件设计图。具体可以分一下的步骤: - 画出该硬件的**边界**:边界外部的内容是接口,内部的内容是实现:\\ \\ {{ :cs:comp_n_arch:courses:fnti_i:xor_ii.jpg?400 |}}\\ - 根据之前 idea 中的逻辑,''a'' 和 ''b'' 都产生了两路信号。以 ''a'' 为例,存在 ''a'' 和 ''Not(a)'' 的信号。我们需要根据这些信号将存在的链接画出来: - 画出每一个链接 - 对每个链接都给与有意义的命名\\ \\ {{ :cs:comp_n_arch:courses:fnti_i:xor_ii_b.jpg?450 |}} 如果设计中使用了别的成品 gate (//off the shelf gate//),其**命名和输入输出**必须严格按照其定义在设计图上标记。 ==Design III: Describing diagram with HDL== 接下来的工作是如何将上面的设计图用 HDL 表达出来。HDL 的描述也分为接口和实现两部分: * 接口(//header//):描述**目标芯片** 的输入输出(//XOR//) * 实现:从左到右描述所有实现中用到的芯片的**输入输出**,以设计图中的命名(//Inner Pins//): /** out = (a And Not(b)) Or (Not(a) And b)) */ CHIP Xor { IN a, b; OUT out; /* implementations */ PARTS: Not(in = a, out = nota); Not(in = b, out = notb); And(a = a, b = notb, out = aAndNotb); And(a = nota, b = b, out = notaAndb); Or(a = aAndNotb, b = notaAndb, out = out); } ===Hardware simulation=== 当完成 //HDL// 的设计后,我们使用硬件模拟器对其进行测试。测试可以通过: * 模拟器本身(//Interactive//) * Scripting Based 测试完毕后都会得到一个结果文件(//compare files//)用于验证测试结果。 ==Interactive simulation== 测试的过程: - 加载 HDL - 赋予输入端不同的数据(0 & 1) - 验证结果: - out pins (''out'') - internal pins ==Scirpt-based simulation== 我们可以通过创建后缀为 ''.tst'' 的文件来手动指定需要运行的测试。下面脚本示例可以用于测试 //XOR//: // loading file // every chips reload before each testing load Xor.hdl; // series of test command set a 0, set b 0, eval; set a 0, set b 1, eval; set a 1, set b 0, eval; set a 1, set b 1, eval; ==Result file== 测试后,模拟器会返回一个后缀为 ''.out'' 的结果文件。如果我们的 //HDL// 正确,那么返回的结果会与真值表一致。比如 //XOR// 正确的返回: | a | b | out | | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 0 | ''.out'' file 的输出可以在 ''.tst'' 文件中设置: //setting output file output-file Xor.out; //output set a 1, set b 1, eval, output; ==compare file== 对于比较复杂的检测,我们不可能去一位一位的检查结果是否正确。通用的做法是使用 ''.cmp'' 文件存储正确的真值表结果,并使用之前生成的 ''.out'' 文件与其进行对比。比较的过程是**按行**比较,如果两行不相同,那么模拟器会报错。\\ \\ ''.cmp'' 文件的指定也是在 ''.tst'' 文件中设置: // compare result with file compare-to Xor.cmp; //**comapare 文件是如何制作的?**// \\ \\ 就像我们生成 out 文件一样,compare 文件是通过正常的测试流程来制作的。唯一不同的是,我们使用**高级语言**(比如 JAVA),而不是 HDL 来制作 compare 文件。该过程被称为 //Behavioral simulation//。 ==硬件开发的流程== - 首先由系统架构师提出需求,并交由团队中的所有人来讨论实现方法 - 系统架构师来决定哪些基础芯片需要设计 - 对于每个基础芯片,系统架构师会指定 - 芯片的 API - 测试脚本(''.tst'') - 校验文件(''.cmp'') - 开发者进行开发(使用 HDL) ===Multi-Bit Buses=== 在硬件的设计过程中,有时候需要对一组 //bit// 进行操作(比如数字,就可以被视作是一组 16 位 的 bit)。这一组 //bit// 往往被我们视作整体,并称之为 **//buses//**(拉丁语,many or multiple 的意思)。为了支持此类操作, //HDL// 中提供了相应的实现方法。 \\ \\ 以两个数字相加为例,门实际上接收的是两个 16 bit 的数据,返回的是一个 16 bit 的数据: \\ \\ {{ :cs:comp_n_arch:courses:fnti_i:add_gate.jpg?350 |}} ==buses in HDL== 上述的 gate 在 HDL 的定义可以写为: /* Adds two 16-bit values */ CHIP Add16 { IN a[16], b[16]; OUT out[16]; PARTS: //... } 其中 //Buses// 写成了类似数组一样的形式。 ==在 buses 中访问 Bit== //buses// 中访问 bit 与数组中访问元素一致,都是使用下标访问。下标也是从** 0** 开始。比如 ''a'' 是一个 4bit 的 bus,访问其第一个 bit 的表达式为: a = a[0]; 下边是一个 4bit 按位 AND 的门的实现: /* ANDs together all 4 bits of the input*/ CHIP And4Way { IN a[4]; OUT out; PARTS: AND (a = a[0], b = a[1], out = t01); AND (a = t01, b = a[2], out = t012); AND (a = t012, b = a[3], out = out); } HDL 中的下标是**从右到左**的。 //a[0]// 是**最右边**一位 bit. ==buses 作为输出值== //buses// 本身也可以作为芯片的输出,这使得按位返回得以实现。比如下面的芯片实现了按位 AND 并按位返回的效果: /* Computers a bit-wise and of its two 4-bits input buses */ CHIP And4 { IN a[4], b[4]; OUT out[4]; PARTS: AND (a = a[0], b = b[0], out = out[0]); AND (a = a[1], b = b[1], out = out[1]); AND (a = a[2], b = b[2], out = out[2]); AND (a = a[2], b = b[3], out = out[3]); } ==sub-buses 的使用== HDL 允许定义子bus。写法如下: //a full bus a[16]; //two sub buses a[0..7]; a[8..15]; 根据上面的写法可以简单的实现一个将两个 8bit 数据合并为一个 16 bit 数据的门: IN lsb[8], msb[8]; OUT a[16]; Add16[a[0..7] = lsb, a[8..15] = msb, b=..., out =...); ==buses 的几个惯例== sub-buses 输出时,允许基于 output buses 的带宽来进行重叠: // 16bits input is truncated in 14bits output Add16(a=Bus1[0..15], b=Bus2[0..15], out[0..14]=out2[0..14]); ''true'' 和 ''false'' 代表对应的(1 & 0)任意长度的输入输出: Add16(a=true, b=false, out=out2); internal pin 的带宽会被自动推断: // single bit width x = lsb[i]; //j-i+1 bit width y = lsb[i..j]; internal pin 不允许使用下标访问,比如 //y[i]// 是非法的。 ===Elementary Gates=== ==Not Gate== {{ :cs:comp_n_arch:courses:fnti_i:not_imp.svg?300 |}} $$\texttt{Not(a) = a Nand a}$$ ==And Gate== {{ :cs:comp_n_arch:courses:fnti_i:and_imp.svg?300 |}} $$\texttt{a And b = Not(a Nand b)}$$ ==Or Gate== {{ :cs:comp_n_arch:courses:fnti_i:or_imp.svg?600 |}} $$\texttt{a Or b = Not(Not(a) And Not(b))}$$ ==Xor Gate== {{ :cs:comp_n_arch:courses:fnti_i:xor_imp.svg?600 |}} $$\texttt{a Xor b = (Not(a) And b) Or (a And Not(b))}$$ ==Mux Gate== * //Multiplexor//(Mux) 充当了一个 switch 的角色,通过 ''sel'' 的开启和关闭可以选择接入 ''a'' 还是 ''b''。 * ''sel'' 可以输入周期性的信号,可以使输出在 ''a'' 和 ''b'' 之前自动切换。 {{ :cs:comp_n_arch:courses:fnti_i:mux.jpg |}} * 实现: {{ :cs:comp_n_arch:courses:fnti_i:mux_imp.svg?600 |}} $$\texttt{Mux(a, b, sel) = (a And Not(sel)) Or (b And sel)}$$ ==DMux Gate== * //Demultiplexor//(Dmux) 是 Mux 的反向操作,可以将单个输入分解为两个输出 {{ :cs:comp_n_arch:courses:fnti_i:dmux.jpg |}} * 实现: {{ :cs:comp_n_arch:courses:fnti_i:dmux_imp.svg?450 |}} \[ \begin{align*} &\texttt{DMux(in, sel)} \\ &\texttt{a = in And Not(sel) }\\ &\texttt{b = in And sel} \end{align*} \] 作业中通常涉及到组多路的基础门。这种牵涉到多路的情况一般都是用一个多 bits 的 sel 来控制输出状态。只要搞清楚哪一位 bit 控制的是哪一个层级的 switch 即可。层级可以通过真值表来判断。比如 4 way 的需要用 00 01 10 11 来控制。如果 00 对应 ''a'', 01 对应 ''b'',发生变化的只有 index 0,因此其控制层级就为 ''sel[0]''。\\ \\ 另外一点需要注意的是区分多个输入和多个输出的情况: * 多个输入的情况,输入的时候需要先通过低层级的选择器筛选,才能将结果交给更高层级的门进行运算 * 多个输出的情况则完全相反;首先需要从最高层级进行划分,再对 sub-group 进行次层级的选择,以此类推。 ====References==== * [[https://drive.google.com/file/d/1dPj4XNby9iuAs-47U9k3xtYy9hJ-ET0T/view|HDL guide]] * [[https://drive.google.com/file/d/1IsDnH0t7q_Im491LQ7_5_ajV0CokRbwR/view|The Hack Chip Set]]