第一章:绪论

数字技术与EDA技术概述

模拟信号:在时间和数值上都是连续的物理量,如正弦波、矩形波等 。

数字信号:用数码表示,01没有数量意义,仅代表事物的对立两面,并遵循特定的码制原则 。

EDA技术定义:EDA(电子设计自动化)是一种以计算机为工作平台、以EDA软件工具为开发环境、以FPGA或ASIC为目标器件来实现电路系统设计的技术 。

发展阶段:经历了20世纪70年代的CAD初级阶段、80年代的CAE中级阶段,发展到90年代至今以可编程器件为核心的高级阶段 。

EDA设计方法与构成要素

-

设计思想:EDA采用自上而下(Top-down)的设计方法,将整个数字系统分解为各个子系统和模块,便于逐层描述与仿真 。

-

构成要素:包含实现载体(可编程逻辑器件PLD,如FPGA和CPLD)、描述方式(原理图或HDL硬件描述语言等)、软件开发环境(如Quartus II或ModelSim)以及硬件开发环境(如DE2-115开发板) 。

传统方法与EDA方法的区别:

比较维度 传统设计方法 EDA设计方法
设计方向 自下而上(Bottom-up) 自上而下(Top-down)
核心器件 通用的逻辑元器件 可编程逻辑器件
仿真节点 系统硬件设计的后期进行仿真和调试 系统设计的早期进行仿真和修改
主要文件 原理图 多种设计文件,以HDL描述文件为主
实现方式 手工实现 自动实现

EDA设计全流程

设计准备:确定系统功能、复杂程度及器件成本,进行方案论证和器件选择 。

设计输入:将电路以文本、图形或混合的形式送入计算机 。

综合:将HDL文本或原理图转化为便于底层可编程器件实现的电路网表文件 。

适配:将网表文件配置到目标器件中,完成布局布线,生成最终下载文件 。

功能仿真:直接测试逻辑功能是否满足设计要求,不涉及具体器件的硬件时延特性 。

时序仿真:在完成布局布线后进行,包含了由于不同器件和布局方案带来的延迟模拟 。

编程下载:将适配生成的编程文件装入PLD器件中 。

硬件测试:在含有设计程序的硬件系统上进行统一测试,验证实际工作情况并排除错误 。

第二章:“数制”与“码制”

基本概念:数字电路处理的各种数字信号均以数码形式给出 。

数制

用于表示数量的大小。它规定了多位数码中每一位的构成方法以及从低位向高位的进位规则(二进制,十进制,十六进制等等) 。

补码规则

正数的补码和它的原码完全相同 。

负数的补码等于其数值位逐位求反(反码)再加1 。

二进制与十进制转换:

image-20260508114511302

二进制与十六进制转换:

image-20260508114754605

码制

用于表示不同事物或事物的不同状态。不同的事物代号称为代码,而编制代码的规则即为码制(格雷码,ASCLL码) 。

格雷码:其每一位的状态按照一定顺序循环变化,最大的特点是相邻两个代码之间只有一位发生状态改变 。这一特性使得格雷码在实际应用中能有效减少过渡噪声 (000,001,011,010…)。

ASCII码:全称为美国信息交换标准代码,是一组7位二进制代码,共能表示128个字符符号 。它被广泛应用于计算机与通讯领域 。

第三章:逻辑代数基础

逻辑运算基础

逻辑代数是二值逻辑运算的数学基础,其变量取值仅为 0 或 1 。

三种基本运算

与 (AND):只有当所有条件同时具备时,结果才发生。表达式为Y=AB。

或 (OR):只要条件之一具备,结果就会发生。表达式为 Y=A+B 。

非 (NOT):条件不具备时结果才发生。表达式为 Y=A’。

image-20260508145154807

复合逻辑运算

image-20260508150027112

异或 (XOR)

逻辑定义:两个输入变量的取值相异时,结果才为 1 。

image-20260508150203310

同或 (XNOR)

逻辑定义:两个输入变量的取值相同时,结果才为 1 。它在逻辑上与异或互为反运算。

image-20260508150241865

重要公式

A✖ B + A ✖B’ = A

A + BC = (A + B)(A + C)

(AB)′ = A′ + B′

(A + B)′ = A′B′

逻辑函数的表示方法

真值表:
遍历输入变量所有可能的取值组合
输出对应的取值

逻辑式:与或非的运算式

逻辑图:逻辑图形符号+逻辑电路的实现

卡诺图:

image-20260508154344515

化简:

image-20260508161752866

两种标准形式:最小项之和/最大项之积

最小项之和

举例:在 A、B 两个变量的系统中,A’B’、A’B、AB’、AB 就是所有的四个最小项。

真值表挑出所有输出 Y=1的行。把这些行对应的最小项写出来,然后用加号(或操作)连在一起。

最大项之积

举例:在 A、B 两个变量的系统中,(A+B)、(A+B’)、(A’+B)、(A’+B’)就是所有的四个最大项。

同理挑出Y=0的行相乘。

第四章:门电路

“如何用半导体材料做成开关,并用开关来做逻辑运算(0和1)”:

  • \1. 基础材料(半导体):在纯净半导体中加入杂质,可以造出N型和P型半导体 。它们拼在一起形成的“PN结”具有单向导电的特性(只能正向通电) 。

  • \2. 初级开关(二极管):利用PN结构成的二极管,可以作为一个单向开关 。用它可以搭出最简单的“与门”和“或门” 。但这玩意儿有瑕疵(电平不准、带不动负载),所以只能在芯片内部凑合用 。

  • \3. 进阶开关(CMOS):MOS管是一种受输入电压控制的“高级开关” 。用它组成的CMOS电路,不仅能搭建更复杂的“与非门”、“或非门”,还能做成传输门和三态门,应用非常广泛 。

  • \4. 另一种进阶开关(TTL):除了MOS管,还可以用双极型三极管(BJT)来做开关 。只要参数设计合理,它同样能在输入不同信号时,精准地“导通”或“截止”,从而输出稳定的高低电平 。

第五章:可编程逻辑器件(PLD)

PLD分类

集成度分类:PLD可划分为简单PLD(包括PROM、PLA、PAL、GAL)和复杂PLD(包括CPLD和FPGA) 。

image-20260509172100124

结构特点分类:主要分为基于乘积项结构的PLD(涵盖所有低密度PLD和绝大多数CPLD)与基于查找表结构的PLD(涵盖绝大多数FPGA) 。

image-20260509172444975

易失与非易失

非易失性器件 (掉电后数据不丢失)

这类器件在断电后,内部烧写的逻辑配置依然存在,重新上电后无需重新配置即可直接工作 。课件中提到了以下几种具体的工艺类型 :

  • 熔丝 (Fuse)反熔丝 (Antifuse) :早期的器件通常使用这种编程元件。
  • EPROM工艺:采用紫外线擦除,并进行电可编程 。
  • EEPROM 与 Flash Memory (快闪存储器) 工艺:采用电擦除和电可编程方式 。目前的绝大多数 CPLD 器件都采用这种工艺 。正因如此,CPLD 直接烧写程序后,掉电程序不会消失,且一般支持反复擦写(几百次左右) 。

易失性器件 (掉电后数据会丢失)

这类器件的存储单元无法在断电状态下保持数据,因此每次断电后逻辑配置都会被清空 。

  • SRAM (静态存储器) 结构目前的绝大多数 FPGA 器件采用此类编程结构

第六章:Verilog设计初步

Verilog HDL是一种用于数字系统建模的硬件描述语言,支持从算法级、门级到开关级的多种抽象层次设计

核心功能与建模方式

Verilog具有极强的设计表达能力,不受设计规模的限制 :

三种建模方式

  1. 行为描述方式(顺序):使用 always 等过程化结构建模。
  2. 数据流方式:使用 assign 连续赋值语句建模。
  3. 结构化方式:使用基本的逻辑门(如and、or)和模块实例化(调用)进行描述。

两大类数据类型

  • 线网类型(Wire):表示构件间的物理硬件连线。
  • 寄存器类型(Reg):表示抽象的数据存储元件。

Verilog模块(Module)的结构

模块是Verilog中代表硬件电路逻辑实体的基本单元 :

基本语法:每个模块的内容都必须包含在 moduleendmodule 关键字之间,并且需要明确声明输入(input)、输出(output)或双向(inout)端口 。

并行与顺序(核心重点)

  • 并行:在同一个模块中,所有的过程块(如 always)、连续赋值语句(assign)和底层模块的实例化调用都是并行执行的,它们出现的先后顺序不影响功能。

  • 顺序:但在 always 过程块内部的代码语句则是顺序执行的。

  • 层次化设计:支持高层模块通过“模块实例化”的方式调用低层子模块,最终通过一个顶层模块(Top-module)将各部分连接成完整的系统 。

image-20260509174730115

image-20260509174714405

基础语言要素

标识符命名规则:可以由字母、数字、下划线(_)和美元符号($)组成,但首字符必须是字母或下划线 。标识符严格区分大小写 。

关键字:Verilog的保留关键字全部为小写字母,用户不能将其用作变量名 。

注释:与C语言一致,单行注释使用 //,多行注释使用 /* ... */

Verilog与C语言的本质区别

虽然语法相似,但设计思想截然不同 :

  • C等软件语言是完全顺序执行的,而Verilog描述的硬件具有并行性

  • 不能完全套用软件的单步、断点调试思维来调试Verilog 。

  • 代码编写必须符合硬件“规矩”,否则即使代码能通过语法检查,实际的电路仿真或综合也会出现很大问题

第七章:数据类型与表达式

数据类型

Verilog HDL 的数据主要分为两类:常量和变量 。

常量 (Constants)

常量是在程序运行过程中,其值不能被改变的量 。Verilog有4种基本的逻辑状态来构建常量:0(逻辑假)、1(逻辑真)、x(未知状态)、z(高阻状态) 。

  1. 整型常量 (整数)

-

十进制格式:直接写数字,可以带正负号 。

例子32(十进制32)、-15(十进制-15) 。

-

基数格式:遵循 <位宽>'<进制><数值> 的格式,通常是无符号数 。 (位宽是二进制的位数)

-

*例子*:`5'O37`(5位八进制数) 。  

-

*例子*:`4'd2`(4位十进制数) 。  

-

*例子*:`8'h 2 A`(8位十六进制数,位数和字符之间、基数和数值之间允许有空格) 。  

-

*提示*:为了增加较长数字的可读性,可以随意插入下划线 `_`,例如 `4'B1x_01` 。  
  1. 实数型常量 (浮点数)

-

十进制格式:由数字和小数点组成,且必须带有小数点 。

-

*例子*:`2.0`、`5.678` 。注意:`2.` 是非法的,因为小数点右侧必须有数字 。  

-

指数格式:由数字和字符 eE 组成 。

-

*例子*:`3.6E2`(即360.0)、`5E-4`(即0.0005) 。
  1. 字符串型常量

由一对双引号括起来的字符序列 。在底层,每个字符都会被转换成对应的8位二进制ASCII码进行存储 。

-

例子"INTERNAL ERROR" 。因为共有14个字符,所以存储这个字符串需要 8*14 位的存储空间 。

  1. 参数 (Parameter)

使用 parameter 关键字定义的符号常量,经常用来定义时延和变量的宽度 。

-

例子parameter sel=8, code=8'ha3;

变量 (Variables)

变量代表了电路中的实际物理意义 。

  1. 线网型变量 (Net)

相当于硬件电路里的各种物理连接(比如导线) 。它的特点是输出值紧跟输入值的变化而变化 。最常用的类型是 wire

-

例子wire a, b;(定义了两个名为 a 和 b 的1位导线) 。

-

例子wire [7:0] databus;(定义了一个8位宽的总线) 。

-

注意:在写可综合代码时,建议不要对同一个线网变量进行多次赋值(多重驱动),否则逻辑值可能无法确定 。

  1. 寄存器型变量 (Register)

对应具有状态保持作用的硬件元件,比如触发器寄存器 。它只能在 alwaysinitial 过程语句中被赋值,未被赋值时缺省值为 x 。最常用的类型是 reg

-

例子reg a, b;(定义了两个1位的寄存器变量) 。

-

例子reg [7:0] q;(定义了一个8位宽的寄存器向量) 。

  1. 存储器 (Memories)

Verilog中不能直接声明存储器,它是通过寄存器数组的形式来声明的 。

-

例子reg [0:3] MyMem [0:63];(定义了一个名为 MyMem 的存储器,它由64个寄存器组成,每个寄存器宽度为4位) 。

-

赋值方法:不能用一条语句就完成对整个存储器的赋值 。你可以对每个寄存器单独赋值(如 Xrom[1]=4'hA;),或者使用 $readmemb 系统任务从指定的文本文件中读取数据并加载到存储器中 。

image-20260510233942882

image-20260510234016797

表达式

操作数

操作数就是参与运算的实体,它们分布在操作符的两侧 。在 Verilog 中,操作数主要有以下 8 种来源 :

  1. 常数(如 4'd2
  2. 参数(由 parameter 定义的值)
  3. 线网(如 wire 定义的物理连线)
  4. 寄存器(如 reg 定义的存储单元)
  5. 位选择(取变量的某一个特定位,如 a[3]
  6. 部分选择(取变量的连续几位,如 bus[7:4]
  7. 存储器单元(如 MyMem[1]
  8. 函数调用的返回值

操作符

  1. 算术操作符 (+, -, *, /, %)

-

截断特性: 整数除法会直接扔掉小数部分(如 7/4 结果为 1) 。

-

模运算符号: 取模运算 % 的结果符号总是和第一个操作数保持一致(如 -7%4 结果是 -3) 。

-

X 的传染性: 只要算术操作数里混进了一个 xz,整个算术运算的结果就会变成 x(不确定状态) 。

  1. 关系操作符 (>, <, >=, <=)

用于大小比较,结果是 1(真)或 0(假) 。

  • 同样具有“传染性”:如果操作数有 xz,比较结果就是 x

  • 如果两边位宽不同,较短的那一边会在高位自动补 0 再比 。

  1. 相等操作符 (==, !=, ===, !==)

这是 Verilog 非常有特色的地方,分成了“逻辑等”和“全等”:

-

逻辑等 (==, !=): 只比较 01。如果碰到 xz,它无法判断,结果会输出 x

-

全等 (===, !==): 极其严格的“按位像素级比较”。连 xz 都会当作确定的状态来比对。只要两边每一位的状态完全一模一样,结果就是 1,绝不会输出 x

4.逻辑操作符 (&&, ||, !)

&&
(逻辑与)
||
(逻辑或)
!
(逻辑非)

处理整体的真假逻辑,操作数只能被看作逻辑 01,输出结果也是 01

  1. 按位操作符 (~, &, |, ^, ~^)

把两个操作数对齐,一位一位地进行与、或、非、异或逻辑运算 。

假设定义了两个 4 位变量 A = 4'b0110B = 4'b0100

  • 按位或 (A | B):上下对齐后,每一位分别做“或”运算。0110``0100 结果为 0110
  • 按位与 (A & B):上下对齐后,每一位分别做“与”运算。0110``0100 结果为 0100
  1. 归约/缩位操作符 (&, ~&, |, ~|, ^, ~^)

这是硬件描述语言特有的操作符。它和按位操作符长得很像,但它只有一个操作数(放在操作数前面)。

-

作用: 把一个多位的向量,通过内部递推运算,“压缩”成一个单位的标量

-

假设 A = 4'b0110B = 4'b0100

  • 归约或 (|B):相当于 0 | 1 | 0 | 0。因为 B 内部只要有 1,整体结果就为 1

**归约与 (`&B`)**:相当于 `0 & 1 & 0 & 0`。因为 B 内部有 0,整体结果就为 `0` 。  

-

**归约异或 (`^A`)**:相当于 `0 ^ 1 ^ 1 ^ 0`。这通常用来判断 1 的个数,因为 A 内部有偶数个 1,所以结果为 `0` 。  

-

**特殊用法(检查未知态)**:归约异或操作符 `^` 经常被用来检查数据中是否混入了未知的 `x` 状态。例如 `MyReg = 4'b01x0`,执行 `^MyReg` 结果会输出 `x`,直接报警说明数据不纯 。  
  1. 移位操作符 (<<, >>)

把操作数整体向左或向右移动指定的位数 。空出来的位置通常补 0

假设 a = 8'h08(转换成二进制是 0000_1000)。

-

左移 (a << 2):整体向左移 2 位。最左边的两个 00 被挤掉,最右边空出两个位置补 00。结果变成了 0010_0000,也就是十六进制的 8'h20

-

右移 (a >> 2):整体向右移 2 位。最右边的 00 被挤掉,最左边空出两个位置补 00。结果变成了 0000_0010,也就是十六进制的 8'h02

  1. 条件操作符 (?:)

经典的三目运算符:信号 = 条件 ? 表达式1 : 表达式2; 。非常适合用来写硬件中的多路选择器 (Multiplexer)。除了它以外,绝大多数操作符都是从左向右关联的,而它是从右向左关联 。

  1. 拼接与复制操作符 ({})

完全服务于底层排线的操作符。

-

拼接: 可以把几个毫不相干的信号线生硬地“捆”成一根总线。例如 {cout, sum}

-

复制: 通过指定次数来快速复制信号。例如 {3{Ack}} 等同于 {Ack, Ack, Ack}

在实际编写复杂表达式时,为了避免记忆繁琐的优先级规则,课件强烈建议直接使用括号 () 来控制运算的优先级,这能最大程度避免逻辑错误 。

第八章:Verilog语法

过程语句与块语句

Verilog 的行为逻辑主要通过 过程语句 承载。

-

initial 语句:过程块中的语句仅执行一次,常用于仿真中的初始化 。

-

always 语句:块内语句不断重复执行,通常带有基于敏感信号列表(event-expression)的触发条件 。

  • 敏感信号可以包含电平变化(如 a or b)或边沿触发(使用 posedgenegedge 关键字) 。

**注意**:在 `always` 块中被赋值的变量必须定义为 `reg` 类型 。  

-

块标志符begin-end 用于界定串行块(顺序执行),而 fork-join 用于并行块 。

赋值方式:持续 vs. 过程

赋值类型 关键字/符号 适用变量类型 执行特点
持续赋值 assign wire 逻辑随时反映输入的变化 。
阻塞赋值 = reg 立即完成赋值,后面的语句必须等待,类似于串行执行 。
非阻塞赋值 <= reg 块结束时才完成赋值,不阻塞后续语句,常用于实现并发逻辑 。

条件与循环控制

Verilog 提供了类似于 C 语言的控制结构来描述复杂逻辑:

-

条件语句:包括 if-elsecase 及其变体 。

-

`casez`:忽略高阻值 `z` 的位比较 。  

-

`casex`:忽略高阻值 `z` 和未知值 `x` 的位比较 。  

-

**潜在风险**:在组合电路中,如果 `if-else` 或 `case` 条件不完备,会意外综合出 **锁存器(Latch)** 。  

-

循环语句:包括 forrepeat(执行 n 次)、whileforever(持续执行,多用于生成仿真时钟) 。

任务(Task)与函数(Function)

两者用于实现代码复用,但在行为上有显著区别 :

-

任务 (task):可以有多个输入输出参数,支持定时事件(如 #@wait),不返回值,只能在过程语句中调用 。

-

函数 (function):至少有一个输入,不能有输出/双向参数,不支持时间控制,必须通过函数名返回一个值 。函数可以在持续赋值 assign 的右侧调用 。

编译指示语句 (define, include, ifdef)

-

宏定义 (`define):用简单宏名代替复杂字符串 。

-

**示例**:``define sum ina+inb+inc` 。  

-

文件包含 (`include):将外部文件插入当前位置 。

-

**示例**:``include "adder.v"` 。  

-

条件编译 (`ifdef):根据宏是否定义决定是否编译某段代码 。

  • 示例:``ifdef add assign out = a + b; else assign out = a - b; endif` 。

并发与顺序

在一个模块中,多个 always 块、assign 语句和实例调用是 并发执行 的 。但在 always 内部,使用阻塞赋值(=)的语句则是 顺序执行 的 。

第九章:Verilog描述电路的方法

结构描述 (Structural Description)

这种方式通过调用内置门元件(如与门、或门、非门)或已有的模块来构建电路,类似于在面包板上插元器件并连线 。

-

特点:需要明确电路的物理结构 。

-

示例代码

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
module Logic_Gates_Demo(
out_not, out_and, out_or, out_nand, out_nor, out_xor, out_xnor, // 输出端口
a, b // 输入端口
);

// 1. 端口声明 (与你的风格保持一致)
output out_not, out_and, out_or, out_nand, out_nor, out_xor, out_xnor;
input a, b;

// 2. 门级原语调用 (输出永远写在第一个参数)

not (out_not, a); // 非门: out_not = ~a

and (out_and, a, b); // 与门: out_and = a & b

or (out_or, a, b); // 或门: out_or = a | b

nand (out_nand, a, b); // 与非门:out_nand = ~(a & b)

nor (out_nor, a, b); // 或非门:out_nor = ~(a | b)

xor (out_xor, a, b); // 异或门:out_xor = a ^ b

xnor (out_xnor, a, b); // 同或门:out_xnor = a ~^ b

endmodule

行为描述 (Behavioral Description)

这种方式从电路的功能和行为角度进行描述,抽象程度最高,类似于使用高级编程语言 。

-

特点:设计者只需描述输入输出的关系,无需关心门级电路的实现,由 EDA 软件自动挑选方案 。

-

常用结构:使用 always 过程块 。

-

注意:在 always 块中被赋值的变量(如 out)必须定义为 reg 类型 。

-

示例代码

1
2
3
4
5
6
7
8
9
10
11
module mux2(out, a, b, sel);
output out;
input a, b, sel;
reg out; // 行为描述中赋值变量定义为 reg

always @(a or b or sel) begin // 敏感信号列表
if(sel) out = b; // 如果 sel 为 1,输出 b
else out = a; // 否则输出 a
end

endmodule

数据流描述 (Dataflow Description)

数据流描述主要使用持续赋值语句 (assign),非常适合将布尔逻辑方程直接转换为代码 。

-

特点:多用于描述组合逻辑电路 。

-

运行机制:右侧表达式的操作数一旦变化,assign 就会重新计算并更新左侧变量 。

-

示例代码

1
2
3
4
5
6
7
8
9
10
11
module MUX3(out, a, b, sel);
output out;
input a, b, sel;

// 方式 A:使用逻辑运算符
assign out = (a & (~sel)) | (b & sel);

// 方式 B:使用三目运算符 (条件表达式)
// assign out = sel ? b : a;

endmodule

对比

描述方式 抽象级别 核心语法 适用场景
结构描述 最低 and, or, 模块名 关键路径优化、顶层模块连接
数据流描述 中等 assign 简单的组合逻辑电路、逻辑方程实现
行为描述 最高 always, if-else 复杂逻辑、大规模系统设计、降低难度

第十章:组合逻辑电路

组合逻辑电路的特点与描述

-

电路特点:组合逻辑电路不含记忆(存储)元件 。

-

功能特点:在任何时刻,电路的输出仅仅取决于该时刻的输入状态 。

-

逻辑描述:通常使用多输入多输出的逻辑框图以及逻辑函数表达式 Y=F(A) 来描述其功能 。

组合逻辑电路的设计方法

-

逻辑抽象:首先需要分析因果关系以确定输入和输出变量,并对逻辑状态进行赋值,然后列出真值表 。

-

推导与选择:根据真值表写出逻辑函数式,并选定所使用的器件类型(如小规模集成电路SSI、中规模集成电路MSI或可编程逻辑器件PLD) 。

-

实现与验证:对逻辑式进行化简或变换后,画出逻辑电路图(或下载到PLD),最后进行设计验证和工艺设计 。

若干常用组合逻辑电路

-

编码器:将输入的每个高/低电平信号转换成对应的二进制代码 。

  • 普通编码器:任何时刻只允许输入一个编码信号(例如8线-3线编码器) 。

  • 优先编码器:允许同时输入多个编码信号,但电路只对其中优先权最高的一个进行编码(例如74HC148) 。

译码器:将输入的二进制代码翻译成对应的输出高、低电平信号 。

  • 二进制译码器:如3线-8线译码器(实例74HC138) 。

  • 显示译码器:用于驱动七段字符显示器,如BCD七段字符显示译码器7448 。

数据选择器:根据选择控制信号,从多个输入中选择一个作为输出,例如二选一数据选择器(表达式为 Y=SELA+SEL’B)和“双四选一”数据选择器74HC153 。

  • 加法器

    • 半加器:不考虑来自低位的进位,仅将两个1位二进制数相加(逻辑式如 $S=A\oplus B$ 和 $CO=AB$) 。

    • 全加器:将两个1位二进制数以及来自低位的进位一起相加 。

    • 多位加法器:如串行进位加法器,其优点是电路简单,缺点是运算速度较慢 。

数值比较器:用于比较两个二进制数的数值大小 。

  • 1位比较器会输出 A>B、A<B 或 A=B 的逻辑结果 。
  • 多位比较器的原理是从高位比起,只有在高位相等时,才比较下一位 。

竞争-冒险现象

-

成因:当两个输入信号“同时向相反的逻辑电平变化”时,便存在“竞争” 。由于竞争的存在,可能会在输出端产生尖峰脉冲,这种现象被称为“竞争-冒险” 。

  • 消除方法

    • 接入滤波电容:因为尖峰脉冲很窄,用很小的电容就可以将其削弱到阈值电压以下 。
    • 引入选通脉冲:在电路达到稳定状态之后,再利用选通脉冲读取输出信号,从而避开尖峰 。
    • 修改逻辑设计:通过增加冗余项来消除冒险,例如将逻辑式 Y=AB+A’C 修改为 Y=AB+A’C+BC。

第十一章:组合逻辑电路设计

基本门电路的设计建模方式

结构描述 (Structural Description)

核心思想:最底层、最直观的描述方式。就像拼乐高积木一样,直接调用系统内置的基础逻辑门元件(原语),并手动一根一根地把线(连线变量)接起来 。

  • 特点:直接反映硬件的物理连接,非常严谨,但如果电路很复杂,代码会显得极其冗长。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
// 结构描述
module gate1 (F, A, B, C, D);
input A, B, C, D;
output F;

// 内部连线(原代码省略了wire声明,这里作为补充理解)
wire F1, F2;

// 调用内置门级元件,格式:元件名 实例名 (输出, 输入1, 输入2...);
nand (F1, A, B); // 将A和B进行与非,结果输出到线F1
and (F2, B, C, D); // 将B,C,D进行与,结果输出到线F2
or (F, F1, F2); // 将F1和F2进行或,结果输出到端口F
endmodule

image-20260513230450102

数据流描述 (Dataflow Description)

核心思想:抽象级别升高了一点。不再关心底层具体用了几个门,而是关心“数据是如何经过逻辑运算流向输出的”。它主要依赖于 assign 连续赋值语句和按位逻辑运算符(如 & 表示与,| 表示或,~ 表示非)。

1
2
3
4
5
6
7
8
// 数据流描述
module gate2(F, A, B, C, D);
input A, B, C, D;
output F;

// 使用 assign 连续赋值语句,直接写出逻辑表达式
assign F = (~(A & B)) | (B & C & D);
endmodule

行为描述 (Behavioral Description)

核心思想:最高级的抽象方式。不再拘泥于具体的逻辑门或方程式,而是从系统行为和算法的角度出发,描述“当输入发生什么变化时,输出应该变成什么样” 。它通常包裹在 always 过程块中。

-

特点:极大地减轻了设计者的负担,特别适合设计复杂的组合逻辑(如包含多路选择分支、优先级判断等)。在行为描述中,被赋值的输出变量必须声明为 reg(寄存器)类型 ,尽管它在综合后可能仍然只是一根纯粹的导线。

-

代码示例

Verilog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 行为描述
module gate3(F, A, B, C, D);
input A, B, C, D;
output F;

reg F; // 在always块中被赋值的变量必须定义为reg类型

// 敏感列表:当 A,B,C,D 中任意一个发生变化时,触发内部的赋值过程
always @(A or B or C or D)
begin
// 过程赋值
F = (~(A & B)) | (B & C & D);
end
endmodule

常用中规模集成电路 (MSI) 的 Verilog 实现

加法器设计

1
2
3
4
5
6
7
8
9
10
//4位全加器
module adder4(cout,sum,ina,inb,cin);
output[3:0] sum;
output cout;
input[3:0] ina,inb;
input cin;
assign {cout,sum}=ina+inb+cin;
endmodule


4位大小比较器

1
2
3
4
5
6
7
8
9
10
module comp2(A, B, A_gt_B, A_it_B, A_eq_B);
input [3:0] A, B;
output A_gt_B, A_it_B, A_eq_B;


assign A_gt_B = (A > B);
assign A_it_B = (A < B);
assign A_eq_B = (A == B);

endmodule

编码器

普通 8-3 编码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module encoder (in, out);
input [7:0] in; // 定义 8 根输入线,名字叫 in
output [2:0] out; // 定义 3 根输出线,名字叫 out
reg [2:0] out;

always @(in) // 只要输入 in 发生任何变化,就执行下面的代码
case(in) // 开始“查字典”,拿着 in 的当前状态去对号入座
8'b00000001: out = 3'b000; // 如果 in[0] 是 1(其他全是0),输出 000
8'b00000010: out = 3'b001; // 如果 in[1] 是 1(其他全是0),输出 001
8'b00000100: out = 3'b010; // 如果 in[2] 是 1,输出 010
// ... 中间省略 ... [cite: 1057-1077]
8'b10000000: out = 3'b111; // 如果 in[7] 是 1,输出 111
default: out = 3'bxxx; // 如果不符合上面任何一种情况(比如同时按了两个键,或者全没按),输出未知状态 x
endcase

endmodule

8-3 优先编码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module encoder8_3(none_on, out, a, b, c, d, e, f, g, h);
output none_on; // 附加输出:指示是不是一个键都没按
output [2:0] out; // 3 位编码输出
input a, b, c, d, e, f, g, h; // 8 个独立的输入端
reg [3:0] outtemp; // 一个 4 位的临时变量,用来打包存储结果

// 把 4 位的临时结果拆开:最高位给 none_on,低 3 位给 out
assign {none_on, out} = outtemp;

always @(a or b or c or d or e or f or g or h) begin
// 核心逻辑开始:从高到低依次判断
if(h) outtemp = 4'b0111; // h 优先级最高!只要 h 是 1,立刻输出 111 (低3位),不管别人
else if(g) outtemp = 4'b0110; // 只有在 h 是 0 的前提下,才会来看 g 是不是 1
else if(f) outtemp = 4'b0101; // 只有 h 和 g 都是 0,才轮得到 f
else if(e) outtemp = 4'b0100;
else if(d) outtemp = 4'b0011;
else if(c) outtemp = 4'b0010;
else if(b) outtemp = 4'b0001;
else if(a) outtemp = 4'b0000; // a 优先级最低,必须上面所有人都没按,它才有机会
else outtemp = 4'b1000; // 如果全是 0(谁都没按),最高位置 1 ,低位随意
end
endmodule [cite: 1098-1106]

3-8 译码器

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
module decoder (Ain, En, Yout);
input En;
input [2:0] Ain;
output [7:0] Yout;
reg [7:0] Yout;


always @ (En or Ain) begin
if (!En)
Yout = 8'b00000000; // 使能信号无效时会把输出置0
else
case (Ain)
3'b000: Yout = 8'b00000001; // 0
3'b001: Yout = 8'b00000010; // 1
3'b010: Yout = 8'b00000100; // 2
3'b011: Yout = 8'b00001000; // 3
3'b100: Yout = 8'b00010000; // 4
3'b101: Yout = 8'b00100000; // 5
3'b110: Yout = 8'b01000000; // 6
3'b111: Yout = 8'b10000000; // 7
default : Yout = 8'b00000000;
endcase
end

endmodule

多路数据选择器

用 if-else 构造的 4选1 选择器

1
2
3
4
5
6
7
8
9
10
11
12
module IF_MUX (C, D, E, F, S, MUX_OUT);
input C, D, E, F;
input [1:0] S;
output reg MUX_OUT;

always @(*) begin
if (S == 2'b00) MUX_OUT = C;
else if (S == 2'b01) MUX_OUT = D;
else if (S == 2'b10) MUX_OUT = E;
else MUX_OUT = F;
end
endmodule

用 case 构造的 4选1 选择器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module MUX (C, D, E, F, S, MUX_OUT);
input C, D, E, F;
input [1:0] S;
output reg MUX_OUT;

always @(C or D or E or F or S) begin
case (S)
2'b00: MUX_OUT = C;
2'b01: MUX_OUT = D;
2'b10: MUX_OUT = E;
default : MUX_OUT = F;
endcase
end
endmodule

奇偶校验位产生器

利用缩减运算符 ^ (异或) 快速计算输入数据中 1 的个数 。

1
2
3
4
5
6
7
module parity(even_bit, odd_bit, a);
output even_bit, odd_bit;
input [7:0] a;

assign even_bit = ^a; // 产生偶校验位
assign odd_bit = ~even_bit; // 产生奇校验位
endmodule

只读存储器 (ROM)

通过定义 functioncase 语句实现查表逻辑(存储了 0-15 的平方值)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module rom(addrin, dataout);
input [3:0] addrin;
output [7:0] dataout;

function [7:0] romout;
input [3:0] addr;
case (addr)
0: romout = 0; 1: romout = 1; 2: romout = 4; 3: romout = 9;
4: romout = 16; 5: romout = 25; 6: romout = 36; 7: romout = 49;
8: romout = 64; 9: romout = 81; 10: romout = 100; 11: romout = 121;
12: romout = 144; 13: romout = 169; 14: romout = 196; 15: romout = 225;
default: romout = 8'hxx;
endcase
endfunction

assign dataout = romout(addrin);
endmodule

第十二章:存储电路

存储电路概述

-

基本功能:存储电路的核心功能是存储各种数据和信息 。

-

寄存器 (Register):用于存储一组数据的电路,其结构是由一组具有公共时钟信号输入端的触发器构成 。

-

存储器 (Memory):用于存储大量数据的电路,基本结构由存储矩阵和读/写控制电路组成 。

锁存器与触发器

锁存器

SR锁存器:由两个或非门或与非门交叉反馈构成 。在任何时刻,输入都能直接改变输出的状态 。它必须遵循输入约束条件 SDRD=0 。

image-20260514115812028

触发器

为了解决锁存器输入一变输出就变的“透明”问题,触发器引入了时钟信号(CLK)来进行同步控制。根据触发方式的演进,可以分为三种:

电平触发 (Level-triggered)

-

结构:在基本 RS 触发器前增加输入控制门 。

-

特点:只有触发信号 CLK 到达时,S 和 R 的输入才起作用 。

-

缺陷:在 CLK=1 的全部时间里,S 和 R 的变化都会引起输出状态的变化 。这意味着在一个 CLK 高电平期间,输出 Q 和 Q’可能会随着输入的抖动发生多次翻转(空翻现象)

脉冲触发 / 主从结构 (Pulse-triggered / Master-Slave)

为了提高可靠性,要求每个 CLK 周期输出状态只能改变 1 次,于是出现了主从结构 。

-

结构:由前级的“主触发器”和后级的“从触发器”级联构成 。

  • 两步动作机制

    1. 第一步:当 CLK=1 时,“主”接收输入信号并翻转,“从”保持当前状态不变 。
    2. 第二步:当 CLK 下降沿到达后,“从”根据“主”的状态发生翻转 。

优势:这种机制确保了每个时钟周期,输出状态只可能改变一次 。经典的例子是主从 JK 触发器,它还能解除 SR 触发器输入必须互斥的约束 。

边沿触发 (Edge-triggered)

这是现代时序逻辑电路中最常用的触发机制,目的在于极大增强抗干扰能力 。

-

特点:触发器的次态仅取决于 CLK 的下降沿(或上升沿)到达瞬间的输入信号状态,与在此之前或之后的输入状态毫无关系 。

-

常见实现:可以利用两个电平触发的 D 触发器组合而成,或者使用 CMOS 传输门来实现 。以 CMOS 传输门边沿触发器为例,状态的改变精准发生在 CLK 沿跳变的瞬间 。

触发器的逻辑功能分类

SR触发器

image-20260514135010241

JK 触发器

image-20260514135059316

T 触发器

image-20260514135359571

D 触发器

image-20260514135445748

动态特性

  • 在实际的硬件底层调试中,触发器并非理想模型,必须考虑门电路带来的传输延迟 。关键的动态参数包括 :

    • 建立时间 :在时钟有效沿到来前**,数据输入必须保持稳定的最短时间。
    • 保持时间 :在时钟有效沿到来后**,数据输入必须继续保持稳定的最短时间。
    • 传输延迟时间 :从时钟有效沿到来,到输出端 Q 发生改变所需的时间。

存储器

存储器拥有庞大的存储单元,主要分为只读存储器(ROM)和随机存取存储器(RAM)两类 。其一般结构包含地址输入、地址译码器、存储矩阵和输入/输出电路 。

随机存取存储器 (RAM)

-

SRAM (静态 RAM):其存储单元通常采用六管N沟道增强型MOS管构成的基本RS触发器来存储信息 。

-

DRAM (动态 RAM):利用MOS管栅极电容可以存储电荷的原理来保存数据,由于电荷会泄漏,因此需要定期刷新 。

只读存储器 (ROM)

-

掩模 ROM:出厂时数据已固定,不能更改,具有非易失性,适合大批量生产 。

-

PROM (可编程 ROM):出厂时节点有熔丝(易熔合金),用户可进行一次性编程(烧断不需要的熔丝),写入后不能改写 。

-

Flash Memory (闪存):用电信号擦除的可编程ROM 。为了提高集成度,它改用叠栅MOS管,利用隧道效应进行放电,利用雪崩注入方式进行充电 。

存储器容量的扩展

-

位扩展方式:当每片存储器的字数够用但位数(数据线宽)不够时使用。将各片的地址线、读写线、片选线直接并联即可 。

-

字扩展方式:当每片存储器的位数够用但字数(存储深度)不够时使用。需要使用译码器对高位地址线进行译码,以分别控制不同存储芯片的片选信号 。

ROM的应用

-

实现组合逻辑函数:利用存储器的结构特性,若将ROM的地址线作为逻辑函数的输入变量,则数据线输出即为一组关于地址变量的逻辑函数结果 。

第十三章:时序逻辑电路

时序逻辑电路概述

功能特点:任一时刻的输出不仅取决于该时刻的输入,还与电路原来的状态有关 。

电路结构:包含存储电路和组合电路两部分 。

数学描述:可以通过三个方程组来描述,即输出方程、驱动方程和状态方程 。

电路分类:按时钟控制方式分为同步时序电路(统一时钟)和异步时序电路(无统一时钟) ;按输出特性分为 Mealy 型(输出与输入及状态有关)和 Moore 型(输出仅取决于状态)

时序电路的分析方法

同步时序电路分析步骤:写出驱动方程 -> 代入触发器特性方程得到状态方程 -> 写出输出方程 。

功能表达工具:分析结果通常通过状态转换表、状态转换图、状态机流程图或时序图来直观展示 。

剩下内容见:https://www.bilibili.com/video/BV1SW4y177hC?spm_id_from=333.788.videopod.sections&vd_source=499c257f0104fdb6ce035bd58aedbdf9