数字逻辑期末复习(2)

易错点:

隐式网

  • 在 Verilog 语言中,如果你在一个 assign语句中使用了一个没有提前声明的信号名称,或者将未声明的信号连接到模块端口,Verilog 编译器并不会报错,而是会“自作聪明”地帮你自动创建一个信号。

  • 默认行为:这个自动创建的信号(隐式网)默认类型是 1-bit 的 wire

1
2
3
4
5
6
7
8
9
10
11
wire [2:0] a, c; // 声明了两个 3 位的向量 a 和 c
assign a = 3'b101; // a 被赋值为二进制 101

// 这里的 b 从未被声明!
// Verilog 隐式地创建了 b,并且它只有 1 bit。
assign b = a;
// 结果:b 只能容纳 a 的最低位(即 1),a 的高两位被丢弃了。

assign c = b;
// 结果:c 得到了 b 的值。因为 b 是 1,c 就变成了 001。
// BUG:原本预期的可能是 c = a (101),但实际上 c 变成了 001。
  • 后果:导致数据丢失,而编译器可能不会报错,导致逻辑错误很难被发现

打包数组 非打包数组

  1. Packed (打包) -> 写在名字左边
    • 形式reg [7:0] data;
    • 含义:这些位是紧挨在一起的,被视为一个整体
    • 理解:把它想象成一个“数值”或一个“字”。比如 [7:0] 就是一个 8 位的整数。你可以直接对它进行数学运算(加减乘除)。
    • 硬件视角:这是一组连续的导线(总线)。
  2. Unpacked (非打包) -> 写在名字右边
    • 形式reg data [7:0];
    • 含义:这些是独立的元素集合。
    • 理解:把它想象成 C 语言或 Java 中的“数组”或“列表”。这里有 8 个独立的寄存器,每一个都互不相干。你通常不能直接拿整个数组去做加法,只能一个一个取出来用。
    • 主要用途:用来做内存(RAM/ROM)

访问向量元素

  • 简单来说,如果你把一个多位的信号(向量)看作是一排并列的电线,这张图就是在教你如何只接通其中的某一根或某几根电线

1.整体赋值 (Accessing Entire Vector)

​ 图片上半部分讲的是最简单的情况:

  • 语法assign w = a;
  • 规则:直接把向量 a 的值赋给向量 w
  • 自动调整:如果 wa位宽(长度)不一致,Verilog 会自动处理:
    • 如果右边短,左边长:零扩展(高位补0)。
    • 如果右边长,左边短:截断(丢弃高位,只保留低位)。
  1. 部分选择 (Part-Select) —— 核心内容

​ 图片下半部分展示了如何只操作向量的一部分。

  • 选取一段 (切片)
    • w[3:0]:选中 w 的低 4 位(第3位到第0位)。
  • 选取一位
    • x[1]:选中 x 的第 1 位。
    • x[1:1]:效果同上,也是选中第 1 位。
  • 特殊索引
    • z[-1:-2]:如果定义时用了负数索引,取值时也可以用负数。
  1. ⚠️ 重要规则:方向必须匹配 (Direction Matching)

图片中特意举了一个**非法(Illegal)**的例子,这是初学者最容易犯的错:

  • 错误代码b[3:0] // Illegal
  • 正确代码b[0:3]
  • 原因:这取决于变量 b 是如何声明的。
    • 如果声明时是 wire [0:7] b;(升序,0是高位/起始位),那么你在切片时也必须遵守这个方向,写成 [0:3]
    • 你不能反着来(用 [3:0] 去访问一个升序声明的变量),这在 Verilog 中是非法的。
  1. 复杂的跨位赋值

​ 最后一行代码展示了如何把两个不同方向或位置的切片连在一起: assign w[3:0] = b[0:3];

​ 这意味着一一对应的连接:

  • w 的第 3 位 <— 连接到 —> b 的第 0 位
  • w 的第 2 位 <— 连接到 —> b 的第 1 位
  • …以此类推。

位操作符 逻辑操作符

  • 位操作符:是对总线上的每一根电线分别进行操作,输入几位,输出通常也是几位。
  • 逻辑操作符:是把整个信号看作**“真”或“假”**,输出结果永远只有 1 位 (1 或 0)。

位操作符

这类操作符会对两个操作数的对应位(第0位对第0位,第1位对第1位……)独立进行逻辑运算。

符号:

  • & :按位与 (AND)
  • | :按位或 (OR)
  • ^ :按位异或 (XOR)
  • ~ :按位取反 (NOT,这是一元操作符)
  • ~^^~ :按位同或 (XNOR)

逻辑操作符

这类操作符用于判断条件的真 (True) 或 假 (False)

  • 规则:如果操作数是 0,则视为“假”;如果操作数是任何非零值(不管有多少位,只要有一位是1),则视为“真”。
  • 结果:结果永远只有 1 bit1 代表真,0 代表假)。

符号:

  • && :逻辑与 (AND)
  • || :逻辑或 (OR)
  • ! :逻辑非 (NOT)

例子

  • **&&&**的差别
  • 假设: A = 4'b0100 (十进制 4) B = 4'b0010 (十进制 2)
1
2
3
4
5
6
7
8
9
10
11
12
13
//位操作符
wire [3:0] res_bitwise = A & B;
// 0 1 0 0
// & 0 0 1 0
// ---------
// 0 0 0 0 -> 结果是 4'b0000 (0)

//逻辑操作符
wire res_logical = A && B;
// A 是非零值 -> 真
// B 是非零值 -> 真
// 真 && 真 -> 真
// 结果是 1'b1

Verilog简介

Verilog建模方式

  • 结构化描述方式 (对应逻辑门)
  • 连线,调用元件
  • 数据流描述方式 (对应逻辑表达式)
  • assign,连续赋值
  • 行为描述方式 (对应真值表)
  • always块,逻辑判断

仿真与测试激励

测试模块(tb.v)的编写

  • 输入信号产生:initial语句,always语句
  • 输出结果检查:$monitor,$display,波形图
  • top模块为什么没有端口:top已经是最顶层模块,不会被其他模块实例化,因此不需要有端口

被测模块(DUT)

  • 测试模块不需要知道被测模块具体的实现细节
  • 测试模块可以通过模块名及端口说明使用被测模块
  • 自上而下设计方法

时间尺度

  • timescale 时间尺度
1
2
3
`timescale 10ns/1ns
//10ns -> 单位时间 time_unit 代码中延时数值的基准
//1ns -> 精度 time_precision 仿真器能分辨的最小时间刻度
  • 时间精度不能大于时间单位

    理解:尺子的最小刻度不能比测量单位大

  • 数字只能是1,10,100,不要出现5,20这样的数字

  • 尽可能的使时间精度与时间单位接近

  • 精度和单位差距越大,仿真越慢

测试模块 — 激励+输出

  • 只有always/initial过程块中赋值的变量才需要定义为reg
  • initial过程块只执行1次,且不可综合
  • #:延迟,不可综合

测试模块 — 过程块

  • initial:赋初值,产生激励信号,检查输出结果(只执行一次)
  • always:产生周期性激励信号(重复执行)

产生激励信号

1. 左侧:产生周期信号(如时钟 clk)

在仿真中,我们需要人为造一个不停跳变的时钟信号。

  • 写法 1:利用 always

    1
    always #10 clk = ~clk;
    • 含义:每过 10 个时间单位,clk 翻转一次。
    • 结果:产生一个周期为 20(10高电平+10低电平)的方波。
    • 注意:这种写法通常需要在 initial 块里先把 clk 初始化为 0,否则 x(未知)取反还是 x
  • 写法 2:利用 forever 循环

    1
    2
    3
    4
    5
    initial begin
    clk = 0; // 先初始化
    forever // 无限循环
    #10 clk = ~clk;
    end
    • 含义:效果和上面完全一样。forever 是死循环语句,专门用于仿真产生时钟。
    • 循环控制语句(蓝框): 列出了 Verilog 中用于控制循环的关键词:for, while, forever, repeat。这些多用于 Testbench,在实际硬件设计(RTL)中较少使用(除了 for 有时用于批量生成电路)。
2. 右侧:产生非周期信号(如复位 rst)
  • 这里展示了三种不同的写法来实现同一个时序波形,重点在于理解 “串行执行”“并行执行” 以及 “时间累加” 的区别。假设我们需要这样一个波形:0时刻为1 -> 15时刻变为0 -> 25时刻变回1 -> 80时刻结束。

  • A. 串行执行 (begin ... end) - 左上角

1
2
3
4
5
6
initial begin
rst = 1;
#15 rst = 0; // 等待15个单位,执行置0
#10 rst = 1; // 再等待10个单位(绝对时间15+10=25),执行置1
#55 $finish; // 再等待55个单位(绝对时间25+55=80),结束仿真
end
  • 核心逻辑begin...end 块内的语句是顺序执行的。

  • 延迟叠加:下一句的延时是在上一句执行完的基础上叠加的。

  • 计算:第二个动作发生在 15 + 10 = 25 时刻。

  • B. 并行执行 (fork ... join) - 右下角

1
2
3
4
5
6
initial fork
rst = 1;
#15 rst = 0; // 绝对时间 15时刻执行
#25 rst = 1; // 绝对时间 25时刻执行
#80 $finish; // 绝对时间 80时刻执行
join
  • 核心逻辑fork...join 块内的语句是并行执行的(同时启动)。

  • 绝对时间:所有的延时都是相对于0时刻(块开始时刻)算的,互不影响。

  • 计算:你看这里的数字变成了 #25,因为它直接指定了在第 25ns 发生动作,而不是像上面那样写 #10(增量)。

  • 红框提醒:这种 fork...join 写法完全不可综合,只能在仿真里用。

  • C. 非阻塞赋值调度 - 右上角(比较少见但在仿真中有效)

1
2
3
4
5
6
initial begin
rst <= 1;
rst <= #15 0; // 调度:在15时刻变成0
rst <= #25 1; // 调度:在25时刻变成1
#80 $finish;
end
  • 核心逻辑:这里使用了非阻塞赋值 + 内部延迟。
  • 非阻塞特性:这几行代码几乎是瞬间执行完的(没有阻塞),但是它们预约了未来的赋值。
  • 结果:效果和 fork...join 类似,指定的是绝对时间点(相对于当前时刻)。

Verilog可综合基本语法

  • 连续赋值语句assign(实现组合逻辑电路)

  • 过程赋值语句always过程块

常用数据类型

  • wire(线网):表示元件中的物理连接
  • reg(寄存器):always、initial过程的输出
  • 除了应该定义为reg的都定义为wire
  • wire缺省值为z,reg缺省值为x

Verilog中的四值逻辑

  • 0,1,x,z
  • 赋值的时候,x代表不在意这个逻辑状态,可以减少bug
  • 仿真结果中x表示状态未知或状态冲突
  • z:悬空、相当于短路的状态,高阻态

数据表示方式

  • 解题:把题面四个十六进制数字写成二进制,前面的6表示只有六位数据,留下后六位,相当于001111,十进制的15,四个选项代表的都是十进制下的15,答案选ABCD
  • “`”表示一个编译预处理
  • 宏定义:`define
  • 文本包含:`include…… 当前文件中插入一个文件
  • 时间尺度:`timescale