上次课内容:
本次课内容:
0
和1
数字芯片 = 处理数字信号的芯片 =
处理0
和1
的芯片
0
和1
的基本元件金属-氧化物-半导体场效应晶体管
(Metal-Oxide-Semiconductor
Field-Effect Transistor, MOSFET)
0
和1
CMOS = Complementary MOS = nMOS + pMOS
最简单的CMOS电路:
CMOS将nMOS和pMOS的开关特性转换成输出电压的高低
1
(高电平),
低电压定义为逻辑0
(低电平)我们得到了数字电路中信号的两种基本状态!
光有0
和1
还不够, 还要进行运算
0
和1
进行各种有意义的转换
1
时, n管导通, p管截止, Y点为0
0
时, n管截止, p管导通, Y点为1
这正好是逻辑上的非运算!
1
0
A | B | P1 | P2 | N1 | N2 | Y |
---|---|---|---|---|---|---|
0 | 0 | 导通 | 导通 | 截止 | 截止 | 1 |
0 | 1 | 导通 | 截止 | 截止 | 导通 | 1 |
1 | 0 | 截止 | 导通 | 导通 | 截止 | 1 |
1 | 1 | 截止 | 截止 | 导通 | 导通 | 0 |
这正好是逻辑上的与非运算!
A | B | P1 | P2 | N1 | N2 | Y0 | P3 | N3 | Y |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 导通 | 导通 | 截止 | 截止 | 1 | 截止 | 导通 | 0 |
0 | 1 | 导通 | 截止 | 截止 | 导通 | 1 | 截止 | 导通 | 0 |
1 | 0 | 截止 | 导通 | 导通 | 截止 | 1 | 截止 | 导通 | 0 |
1 | 1 | 截止 | 截止 | 导通 | 导通 | 0 | 导通 | 截止 | 1 |
1
0
A | B | P1 | P2 | N1 | N2 | Y |
---|---|---|---|---|---|---|
0 | 0 | 导通 | 导通 | 截止 | 截止 | 1 |
0 | 1 | 导通 | 截止 | 截止 | 导通 | 0 |
1 | 0 | 截止 | 导通 | 导通 | 截止 | 0 |
1 | 1 | 截止 | 截止 | 导通 | 导通 | 0 |
这正好是逻辑上的或非运算!
或非门的输出连一个非门, 可组成或门
在门电路层面搭建
记#T(x)
为门电路x
所需的晶体管数量, 则
#T(nand3) = #T(and) + #T(nand) = 6 + 4 = 10
可通过#T(x)
粗略评估电路的面积
在晶体管层面搭建
#T(nand3) = 6
#T(nandN) = 2N
#T(andN) = #T(nandN) + #T(not) = 2(N + 1)
体现了ASIC设计中全定制电路的优势
在门电路层面
Y=A^B=A*~B+~A*B
#T(xor) = 2#T(not) + 2#T(and) + #T(or) = 2 * 2 + 2 * 6 + 6 = 22
在晶体管层面搭建, 效果更优
A=1
时, P2和N2相当于非门; P3和N3均截止;
故此时Y=~B
A=0
时, P2和N2均截止; B=0
时N3导通;
B=1
时P3导通; 故此时Y=B
#T(xor) = 6
检测\(n\)位输入的值, 使\(2^n\)位输出中的相应位为1
1
,
其他位为0
例: 2-4译码器
\(A_1\) | \(A_0\) | \(|\) | \(Y_3\) | \(Y_2\) | \(Y_1\) | \(Y_0\) |
---|---|---|---|---|---|---|
0 | 0 | \(|\) | 0 | 0 | 0 | 1 |
0 | 1 | \(|\) | 0 | 0 | 1 | 0 |
1 | 0 | \(|\) | 0 | 1 | 0 | 0 |
1 | 1 | \(|\) | 1 | 0 | 0 | 0 |
#T(2-4 dec) = 2#T(not) + 4#T(and) = 2 * 2 + 4 * 6 = 28
#T(5-32 dec) = 5#T(not) + 32#T(and5) = 5 * 2 + 32 * 12 = 394
#T(10-1024 dec) = 10#T(not) + 1024#T(and10) = 10 * 2 + 1024 * 22 = 22548
\(2^n\)位输入, \(n\)位输出, 与译码器功能相反
1
, 则输出\(X\)1
的位置
例: 4-2编码器
\(A_3\) | \(A_2\) | \(A_1\) | \(A_0\) | \(|\) | \(Y_1\) | \(Y_0\) |
---|---|---|---|---|---|---|
0 | 0 | 0 | 1 | \(|\) | 0 | 0 |
0 | 0 | 1 | 0 | \(|\) | 0 | 1 |
0 | 1 | 0 | 0 | \(|\) | 1 | 0 |
1 | 0 | 0 | 0 | \(|\) | 1 | 1 |
其 | 他 | 情 | 况 | \(|\) | 0 | 0 |
#T(4-2 enc) = 4#T(not) + 4#T(and4) + 2#(or) = 4 * 2 + 4 * 10 + 2 * 6 = 60
可支持独热码以外的输入, 但只编码优先级最高的位
1
的位置
例: 4-2优先编码器
\(A_3\) | \(A_2\) | \(A_1\) | \(A_0\) | \(|\) | \(Y_1\) | \(Y_0\) |
---|---|---|---|---|---|---|
0 | 0 | 0 | 1 | \(|\) | 0 | 0 |
0 | 0 | 1 | X | \(|\) | 0 | 1 |
0 | 1 | X | X | \(|\) | 1 | 0 |
1 | X | X | X | \(|\) | 1 | 1 |
其 | 他 | 情 | 况 | \(|\) | 0 | 0 |
#T(4-2 prioenc) = 2#T(not) + #T(and3) + #T(and) + 2#(or) = 2 * 2 + 8 + 6 + 2 * 6 = 30
根据选择端选择一路输入
例: 2选1多路选择器
\(S\) | \(|\) | \(Y\) |
---|---|---|
0 | \(|\) | \(D_0\) |
1 | \(|\) | \(D_1\) |
#T(2-1 mux) = #T(1-2 dec) + 2#T(and) + #(or) = #T(not) + 2 * 6 + 6 = 20
#T(2-1 mux32) = #T(1-2 dec) + 32(2#T(and) + #(or)) = 2 + 32 * (2 * 6 + 6) = 578
对于数据位宽为\(M\)位的\(N\)选1多路选择器, \(\log_{2}{N}\)-\(N\)译码器的输出只有1位为1
,
且可被\(M\)个\(N\)选1多路选择器复用
检查两个输入的每一位是否完全一致
例: 4位比较器
#T(cmp4) = 4#T(xnor) + #T(and4) = 4 * 6 + (4 + 4 + 2) = 34
#T(cmp32) = 32#T(xnor) + #T(and32) = 32 * 6 + (32 + 32 + 2) = 258
#T(HA) = #T(xor) + #T(and) = 6 + 6 = 12
\(A\) | \(B\) | \(|\) | \(S\) | \(C\) |
---|---|---|---|---|
0 | 0 | \(|\) | 0 | 0 |
0 | 1 | \(|\) | 1 | 0 |
1 | 0 | \(|\) | 1 | 0 |
1 | 1 | \(|\) | 0 | 1 |
可以用两个半加器组成一个全加器
#T(FA) = 2#T(HA) + #T(or) = 2 * 12 + 6 = 30
将低位FA的进位输出作为高位FA的进位输入
行波进位加法器(Ripple-Carry Adder, RCA)
#T(RCA4) = #(HA) + 3#T(FA) = 12 + 3 * 30 = 102
#T(RCA32) = #(HA) + 31#T(FA) = 12 + 31 * 30 = 942
PC = PC + 4
?
正确理解PC = PC + 4
:
次态的PC = 当前的PC + 4
需要通过时序逻辑电路实现 - 可以存储状态的电路
\(Q = A_{out} = \overline{A_{in}} = \overline{B_{out}} = \overline{\overline{B_{in}}} = \overline{\overline{Q}} = Q\) \(\overline{Q} = B_{out} = \overline{B_{in}} = \overline{A_{out}} = \overline{\overline{A_{in}}} = \overline{\overline{\overline{Q}}} = \overline{Q}\)
0
; \(Q=1,
\overline{Q}=0\), 则认为存储1
设线延迟为\(T_w\), 反相器延迟为\(T_g\), 如果一开始\(Q=\overline{Q}=0\), 会怎样?
不过, 即使上述电路位于稳定状态, 也使无法更新\(Q\)和\(\overline{Q}\)
S | R | \(|\) | Q |
---|---|---|---|
0 | 0 | \(|\) | 保持 |
0 | 1 | \(|\) | 0 |
1 | 0 | \(|\) | 1 |
1 | 1 | \(|\) | 禁止 |
S(et)R(eset)锁存器, 其中S和R用于控制锁存器的状态
#T(SR latch) = 2#T(nor) = 2 * 4 = 8
思想: 额外添加两个与门, 将SR锁存器的4种输入限制成3种合法输入
#T(D latch) = #T(SR latch) + 2#T(and) + #T(not) = 8 + 2 * 6 + 2 = 22
WE | D | \(|\) | S | R | \(|\) | Q |
---|---|---|---|---|---|---|
0 | 0 | \(|\) | 0 | 0 | \(|\) | 保持 |
0 | 1 | \(|\) | 0 | 0 | \(|\) | 保持 |
1 | 0 | \(|\) | 0 | 1 | \(|\) | 0 |
1 | 1 | \(|\) | 1 | 0 | \(|\) | 1 |
#T(D latch) = 4#T(nand) = 4 * 4 = 16
面积更小
WE | D | \(|\) | \(\overline{S}\) | \(\overline{R}\) | \(|\) | Q |
---|---|---|---|---|---|---|
0 | 0 | \(|\) | 1 | 1 | \(|\) | 保持 |
0 | 1 | \(|\) | 1 | 1 | \(|\) | 保持 |
1 | 0 | \(|\) | 1 | 0 | \(|\) | 0 |
1 | 1 | \(|\) | 0 | 1 | \(|\) | 1 |
需要额外的机制来实现同步关系
同步电路: 存储单元仅在时钟边沿到达时写入数据, 且在该时钟周期中稳定读出该数据
D锁存器的性质: WE有效时, 输入的变化马上传播到输出
将时钟连到D锁存器的WE端仍然无法实现
需要一种新的电路结构
若将左上图的三输入与非门G(nand3)看成二输入与门(and2)和二输入与非门(nand2)的级联, 其行为等价于右上图
#T(DFF) = 5#T(nand) + #T(nand3) = 5 * 4 + 6 = 26
\(|\) | \(\mathrm{sr_0}\) | \(|\) | \(\mathrm{sr_1}\) | \(|\) | \(\mathrm{sr_2}\) | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
clk | D | \(|\) | \(\mathrm{\overline{S}}\) | \(\mathrm{\overline{R}}\) | \(\mathrm{\overline{Q}}\) | \(|\) | \(\mathrm{\overline{S}}\) | \(\mathrm{\overline{R}}\) | \(\mathrm{Q}\) | \(\mathrm{\overline{Q}}\) | \(|\) | \(\mathrm{\overline{S}}\) | \(\mathrm{\overline{R}}\) | \(\mathrm{Q}\) |
0 | 0 | \(|\) | 1 | 0 | 1 | \(|\) | 0 | 0 | 1 | 1 | \(|\) | 1 | 1 | 保持 |
0 | 1 | \(|\) | 0 | 0 | 1 | \(|\) | 0 | 1 | 1 | 0 | \(|\) | 1 | 1 | 保持 |
\(\uparrow\) | 0 | \(|\) | 1 | 1 | 1(保持) | \(|\) | 1 | 0 | 0 | 1 | \(|\) | 1 | 0 | 0 |
1 | 1 | \(|\) | 1 | 1 | 1(保持) | \(|\) | 1 | 1 | 0(保持) | 1(保持) | \(|\) | 1 | 0 | 0 |
\(\uparrow\) | 1 | \(|\) | 0 | 1 | 0 | \(|\) | 0 | 1 | 1 | 0 | \(|\) | 0 | 1 | 1 |
1 | 0 | \(|\) | 1 | 1 | 0(保持) | \(|\) | 0 | 0 | 1 | 1 | \(|\) | 0 | 1 | 1 |
D触发器的核心思想:
#T(DFFE) = #T(DFF) + #T(2-1 mux) = 26 + 20 = 46
多加一个使能端, D触发器的晶体管数量增加77%!
由多个D触发器组成
在RV32中, PC寄存器所需的晶体管数量约为:
#T(RV32.PC) = 32#T(DFFE) = 32 * 46 = 1472
可看成一个由比特构成的矩阵
例: 深度为2, 宽度为3的存储器
地址 | 存储字 |
---|---|
0 | \(b_{(0, 2)}b_{(0, 1)}b_{(0, 0)}\) |
1 | \(b_{(1, 2)}b_{(1, 1)}b_{(1, 0)}\) |
存储器又分只读存储器(ROM)和随机访问存储器(RAM), 后者可读可写
给定地址addr, 读出存储器中的相应内容
地址译码器
给定地址addr和数据D, 将数据D写入存储器中的相应位置
#T(3x2 RAM) = #T(1-2 dec) + 6#T(DFFE) + 8#T(and) + 3#(or) = 2+6*46+8*6+3*6 = 344
#T(RV32.Reg) = #T(32x32 RAM) =
#T(5-32 dec)
+ (32*32)#T(DFFE)
+ 32#T(and) // 生成一行的使能信号, 共32行
+ (32*32)#T(and) // 用于读出一行数据的与门, 其数量与D触发器相同
+ 32#T(or32) // 用于选出一位数据的或门, 输入端口数量=存储器深度, 或门数量=存储器宽度
= 394 + 1024 * 46 + 32 * 6 + 1024 * 6 + 32 * 66 = 55946
#T(32x32 RAM) = #T(5-32 dec) + 1024#T(DFFE) + 32#T(and) + 1024#T(and) + 32#T(or32)
= 394 + 1024 * 46 + 32 * 6 + 1024 * 6 + 32 * 66
随着RAM的规模增加, 带使能端的D触发器的晶体管数量占主要部分
想法: 用面积更小的存储单元构成RAM
SRAM单元 - 在晶体管层面全定制!
可将SRAM单元的行为抽象成一个锁存器(假设高电平有效)
#T(3x2 RAM) = 344
#T(3x2 SRAM) = #T(1-2 dec) + 6#T(SRAM latch) + #T(and) = 2 + 6 * 6 + 6 = 44
#T(32x32 RAM) = 55946
#T(32x32 SRAM) = #T(5-32 dec) + (32*32)#T(SRAM latch) + #T(and)
= 394 + 1024 * 6 + 6
= 6544
如何解决锁存器不满足同步电路特性的问题?
提前用D触发器存放输入端
存储器单元 | #T | 读延迟 | 处理器中的使用场景 | 典型容量 |
---|---|---|---|---|
带使能端的D触发器 | 46 | 当前周期 | 通用寄存器堆 | < 1KB |
SRAM单元 | 6 | 下一周期 | 高速缓存 | 32KB~32MB |
Q: AMD某型号处理器配备一个大小为384MB的高速缓存, 假设该高速缓存通过SRAM存储阵列实现, 问存储阵列的晶体管数量有多少?
A: 384*1024*1024*8*6=19,327,352,832
, 约200亿!
上次课我们用C程序实现了这个状态机并执行指令
现在让我们来用电路实现它!
程序 | 抽象计算机 | CPU | |
---|---|---|---|
状态 | \(\{<V, PC>\}\) | \(\{<R, M>\}\) | \(\{时序逻辑电路\}\) |
状态转移规则 | C语言语句的语义 | 指令的语义 | 组合逻辑电路 |
FM | C语言标准手册 | 指令集手册 | 架构设计文档 |
我们用Chisel代码来展示
// instruction structure and helper functions
val Ibundle = new Bundle {
val imm11_0 = UInt(12.W)
val rs1 = UInt( 5.W)
val funct3 = UInt( 3.W)
val rd = UInt( 5.W)
val opcode = UInt( 7.W)
}
// fetch
val inst = M(PC(31, 2)).asTypeOf(Ibundle)
// opcode decode
val isAddi = (inst.opcode === "b0010011".U) && (inst.funct3 === "b000".U)
val isEbreak = inst.asUInt === "x00100073".U
assert(isAddi || isEbreak, "Invalid instruction 0x%x", inst.asUInt) // (*)
// operand decode
val rs1 = Mux(isEbreak, 10.U(5.W), inst.rs1)
val rs2 = Mux(isEbreak, 11.U(5.W), 0.U(5.W))
val rs1Val = Mux(rs1 === 0.U , 0.U(32.W), R(rs1))
val rs2Val = Mux(rs2 === 0.U , 0.U(32.W), R(rs2))
val immVal = Cat(Fill(20, inst.imm11_0(11)), inst.imm11_0)
// execute
when (isAddi) { R(inst.rd) := rs1Val + immVal }
when (isEbreak && (rs1Val === 0.U)) { printf("%c", rs2Val(7,0)) } // (*)
io.halt := isEbreak && (rs1Val === 1.U)
// update PC
PC := PC + 4.U
(*)
的代码并非电路, 需要仿真环境的辅助来实现功能
import chisel3._
import chisel3.util._
import chisel3.util.experimental.loadMemoryFromFileInline
class YPC extends Module {
val io = IO(new Bundle{ val halt = Output(Bool()) })
val R = Mem(32, UInt(32.W))
val PC = RegInit(0.U(32.W))
val M = Mem(1024 / 4, UInt(32.W))
def Rread(idx: UInt) = Mux(idx === 0.U, 0.U(32.W), R(idx))
loadMemoryFromFileInline(M, "prog.hex")
val Ibundle = new Bundle {
val imm11_0 = UInt(12.W)
val rs1 = UInt( 5.W)
val funct3 = UInt( 3.W)
val rd = UInt( 5.W)
val opcode = UInt( 7.W)
}
def SignEXT(imm11_0: UInt) = Cat(Fill(20, imm11_0(11)), imm11_0)
val inst = M(PC(31, 2)).asTypeOf(Ibundle)
val isAddi = (inst.opcode === "b0010011".U) && (inst.funct3 === "b000".U)
val isEbreak = inst.asUInt === "x00100073".U
assert(isAddi || isEbreak, "Invalid instruction 0x%x", inst.asUInt)
val rs1Val = Rread(Mux(isEbreak, 10.U(5.W), inst.rs1))
val rs2Val = Rread(Mux(isEbreak, 11.U(5.W), 0.U(5.W)))
when (isAddi) { R(inst.rd) := rs1Val + SignEXT(inst.imm11_0) }
when (isEbreak && (rs1Val === 0.U)) { printf("%c", rs2Val(7,0)) }
io.halt := isEbreak && (rs1Val === 1.U)
PC := PC + 4.U
}
软件设计 | 硬件设计 | |
---|---|---|
图 | 算法流程图 | 电路结构图 |
代码 | 描述算法流程 | 描述电路结构 |
多行代码间的关系 | 反映流程的先后顺序 | 反映部件之间的连接关系 |
代码的作用 | 生成程序来执行 | 生成电路版图来制造 |
模块的作用 | 调用子程序 | 实例化子部件 |
我们用晶体管展示各个电路的内部结构, 就是希望大家理解上述本质
给初学者的建议: 如果写电路时大脑蹦出 “执行”这个词, 你就输了
我们给出各个电路#T(x)
的意义:
希望大家对常见电路的面积有大致的认识
课后小作业:
module YPC( // <stdin>:3:10
input clock, // <stdin>:4:11
reset, // <stdin>:5:11
output io_halt // playground/src/YPC.scala:6:14
);
wire [31:0] _M_ext_R0_data; // playground/src/YPC.scala:9:15
wire [31:0] _R_ext_R0_data; // playground/src/YPC.scala:7:15
wire [31:0] _R_ext_R1_data; // playground/src/YPC.scala:7:15
reg [31:0] PC; // playground/src/YPC.scala:8:19
wire isAddi = _M_ext_R0_data[6:0] == 7'h13 & _M_ext_R0_data[14:12] == 3'h0; // playground/src/YPC.scala:9:15, :22:35, :23:{29,47,63}
wire isEbreak = _M_ext_R0_data == 32'h100073; // playground/src/YPC.scala:9:15, :24:30
wire [4:0] _rs1Val_T = isEbreak ? 5'hA : _M_ext_R0_data[19:15]; // playground/src/YPC.scala:9:15, :22:35, :24:30, :27:25
wire [31:0] rs1Val = _rs1Val_T == 5'h0 ? 32'h0 : _R_ext_R0_data; // playground/src/YPC.scala:7:15, :8:19, :10:{29,34}, :27:25, :28:25
wire [4:0] _rs2Val_T = isEbreak ? 5'hB : 5'h0; // playground/src/YPC.scala:24:30, :28:25
`ifndef SYNTHESIS // playground/src/YPC.scala:25:9
always @(posedge clock) begin // playground/src/YPC.scala:25:9
if (~reset & ~(isAddi | isEbreak)) begin // playground/src/YPC.scala:23:47, :24:30, :25:{9,17}
if (`ASSERT_VERBOSE_COND_) // playground/src/YPC.scala:25:9
$error("Assertion failed: Invalid instruction 0x%x\n at YPC.scala:25 assert(isAddi || isEbreak, \"Invalid instruction 0x%%%%x\", inst.asUInt)\n",
_M_ext_R0_data); // playground/src/YPC.scala:9:15, :25:9
if (`STOP_COND_) // playground/src/YPC.scala:25:9
$fatal; // playground/src/YPC.scala:25:9
end
if ((`PRINTF_COND_) & isEbreak & rs1Val == 32'h0 & ~reset) // playground/src/YPC.scala:8:19, :10:29, :24:30, :25:9, :30:{29,47}
$fwrite(32'h80000002, "%c",
{_rs2Val_T[3], _rs2Val_T[1:0]} == 3'h0 ? 8'h0 : _R_ext_R1_data[7:0]); // playground/src/YPC.scala:7:15, :8:19, :10:{29,34}, :23:63, :28:25, :30:47
end // always @(posedge)
`endif // not def SYNTHESIS
always @(posedge clock) begin // <stdin>:4:11
if (reset) // <stdin>:4:11
PC <= 32'h0; // playground/src/YPC.scala:8:19
else // <stdin>:4:11
PC <= PC + 32'h4; // playground/src/YPC.scala:8:19, :32:12
end // always @(posedge)
R_combMem R_ext ( // playground/src/YPC.scala:7:15
// ...
);
M_combMem M_ext ( // playground/src/YPC.scala:9:15
.R0_addr (PC[9:2]), // playground/src/YPC.scala:8:19, :22:{15,18}
.R0_en (1'h1), // <stdin>:3:10
.R0_clk (clock),
.R0_data (_M_ext_R0_data)
);
assign io_halt = isEbreak & rs1Val == 32'h1; // <stdin>:3:10, playground/src/YPC.scala:10:29, :24:30, :31:{23,34}
endmodule
有一些电路无法实现的功能, 需要在仿真环境(是个软件)中实现
assert()
实现RTL代码中的assert()
功能stdio.h
实现RTL代码中的printf()
功能#include <stdio.h>
#include "VYPC.h"
#include "VYPC___024root.h"
static VYPC *top = NULL;
void step() { top->clock = 0; top->eval(); top->clock = 1; top->eval(); }
void reset(int n) { top->reset = 1; while (n --) { step(); } top->reset = 0; }
void load_prog(const char *bin) {
FILE *fp = fopen(bin, "r");
fread(&top->rootp->YPC__DOT__M_ext__DOT__Memory, 1, 1024, fp);
fclose(fp);
}
int main(int argc, char *argv[]) {
top = new VYPC;
load_prog(argv[1]);
reset(10);
while (!top->io_halt) { step(); }
return 0;
}
状态: 包含时序逻辑电路(部分组合逻辑信号和端口也用C变量来表示)
// obj_dir/VYPC___024root.h
// DESIGN SPECIFIC STATE
VL_IN8(clock,0,0);
VL_IN8(reset,0,0);
VL_OUT8(io_halt,0,0);
CData/*0:0*/ __Vtrigrprev__TOP__clock;
CData/*0:0*/ __VactContinue;
IData/*31:0*/ YPC__DOT___M_ext_R0_data;
IData/*31:0*/ YPC__DOT__PC;
IData/*31:0*/ YPC__DOT__rs1Val;
IData/*31:0*/ __VstlIterCount;
IData/*31:0*/ __VactIterCount;
VlUnpacked<IData/*31:0*/, 32> YPC__DOT__R_ext__DOT__Memory;
VlUnpacked<IData/*31:0*/, 256> YPC__DOT__M_ext__DOT__Memory;
// ...
状态转移: 翻译Verilog中的组合逻辑电路
举例 | 效率 | 精确度 | |
---|---|---|---|
指令集模拟器 | YEMU, NEMU, QEMU | +++++ | 指令集(行为正确) |
体系结构模拟器 | GEM5 | +++ | 性能(大致运行时间) |
RTL仿真器 | VCS, Verilator | ++ | 微结构(IPC) |
晶体管仿真器 | Spice | + | 晶体管(物理特性) |
在处理器芯片设计企业中, 前三类都会使用:
后面的课程会进一步讨论
0
和1
0
和1
的简单计算
主从式D触发器