数字逻辑实验0:verilog语法和代码初步学习

1 Verilog语法注意点

过程赋值语句块------initial语句块 不可综合,,常用于测试文件中生成激励波形(如复位信号)作为电路的仿真信号

(1)功能描述

Verilog HDL 的三种主要描述方式,三种"思维层次"。

描述方式 英文 抽象层次 关注重点 类比
结构描述 Structural 最低 用了哪些门,怎么连接 搭积木
数据流描述 Data Flow 中等 信号如何变化 写数学公式
行为描述 Behavioral 最高 电路实现什么功能 写程序

结构描述(Structural)

不用告诉你它干什么,我直接告诉你:这里有几个门,它们怎么接。

比如你要做:Y = A \land B

结构描述的写法:

verilog 复制代码
and u1(Y, A, B);
  • 使用一个 and
  • 实例名叫 u1
  • 输出接到 Y
  • 输入接 AB

特点

  • 最接近真实硬件
  • 需要明确写出所有元件和连接
  • 可读性差
  • 适合底层设计

示例:半加器

verilog 复制代码
module half_adder(
    input a, b,
    output sum, carry
);
    xor u1(sum, a, b);
    and u2(carry, a, b);
endmodule

电路结构

  • sum = a XOR b
  • carry = a AND b

数据流描述(Data Flow)

不管你内部用什么门,只告诉你输出和输入的逻辑关系。

主要语句assign

数学公式:y = x + 1

Verilog:

verilog 复制代码
assign y = x + 1;

示例

verilog 复制代码
assign Y = A & B;

意思:

  • 只要 AB 变化
  • Y 自动更新

半加器写法

verilog 复制代码
module half_adder(
    input a, b,
    output sum, carry
);
    assign sum = a ^ b;
    assign carry = a & b;
endmodule

特点

  • 比结构描述简单
  • 接近逻辑表达式
  • 常用于组合逻辑

行为描述(Behavioral)

我只描述电路应该如何工作,不关心内部结构。

主要语句 always initial
if case for while

示例:与门

verilog 复制代码
always @(*) begin
    Y = A & B;
end

特点

  • 最像编程语言
  • 可以写复杂逻辑
  • 最常用

三种方式对比示例

实现:

Y = A \land B

  1. 结构描述
verilog 复制代码
and u1(Y, A, B);
  1. 数据流描述
verilog 复制代码
assign Y = A & B;
  1. 行为描述
verilog 复制代码
always @(*) begin
    Y = A & B;
end

task(任务)

类似"子程序",可以完成复杂操作,并且有多个输入和多个输出。

示例

verilog 复制代码
task add;
    input [3:0] a, b;
    output [4:0] sum;
    begin
        sum = a + b;
    end
endtask

调用:

verilog 复制代码
add(x, y, z);

特点

  • 可以有多个输出
  • 可以包含时间控制(#10
  • 适合复杂操作

function(函数)

接收输入,返回一个值。

示例

verilog 复制代码
function [4:0] add;
    input [3:0] a, b;
    begin
        add = a + b;
    end
endfunction

调用:

verilog 复制代码
sum = add(x, y);

特点

  • 只能返回一个值
  • 不能包含延时
  • 常用于表达式中

小总结

场景 推荐方式
门级设计 结构描述
简单组合逻辑 数据流描述
复杂组合逻辑 行为描述
时序电路 行为描述
可复用计算 function
多步骤操作 task
项目 task function
返回值 多个 只有一个
是否可有输出参数 可以 不需要
是否可带延时 可以 不可以
调用方式 独立语句 表达式中
类比 子程序 数学函数

(2)系统函数

display会在字符串末尾追加一个换行符,write则不会

连续监视器$monitor 函数,每当其参数列表中的变量或表达式发生更改时,会自动打印出变量或表达式的值

②打开和关闭文件向

文件中输入信息

读取文件

读取文件数据到存储器:

$readmemb ("<数据文件名>",<存储器名>);

$readmemh ("<数据文件名>",<存储器名>);

(3)代码示例

01 四位全加器

全加器完成这个计算:

text 复制代码
a + b + cin

其中:

  • a:当前位第一个数
  • b:当前位第二个数
  • cin:来自低位的进位
  • sum:当前位结果
  • cout:向高位输出的进位

第1行

verilog 复制代码
`timescale 1ns / 1ps

设置仿真时间单位:

  • 1ns = 时间单位是纳秒
  • 1ps = 精度是皮秒

这行只影响仿真,不影响硬件。

第6行

verilog 复制代码
);

端口列表结束。

第9行 全加器进位公式

verilog 复制代码
assign cout = (a & b) | ((a ^ b) & cin);

计算进位。
含义:只要以下情况之一发生,就产生进位:

  1. a=1 且 b=1
  2. cin=1a,b 中有一个是 1

第24行

verilog 复制代码
assign cout = carry[3];

最高位的进位就是整个 4 位加法器的进位输出。

text 复制代码
bit0 → bit1 → bit2 → bit3
  │      │      │      │
 c0     c1     c2     c3
                       │
                      cout

02 读写存储器


这张图详细展示了一个32×32位单端口同步写、异步读寄存器文件(Register File,简称regfile)的Verilog HDL设计实现,包含源码、RTL电路原理图、关键语法说明和初始化文件示例,是CPU通用寄存器堆的基础设计模板。


①整体设计概述

该模块实现了一个容量为 32个寄存器 × 每个寄存器32位 的可读写存储器,总容量1024位(128字节)。核心特性:

  • 写操作:同步时钟上升沿触发 ,由写使能wen控制
  • 读操作:组合逻辑异步触发 ,由读使能ren控制
  • 地址空间:5位地址总线(2⁵=32),正好寻址全部32个寄存器
  • 仅用于仿真的初始化功能:通过文件预加载寄存器初始值
②Verilog源码逐行解析
1. 时间尺度与模块接口(第1-11行)
verilog 复制代码
`timescale 1ns / 1ps  // 仿真时间尺度:1单位=1ns,精度1ps
module regfile(
    input wire         clk,     // 系统时钟,写操作同步触发
    input wire         ren,     // 读使能,高电平有效
    input wire         wen,     // 写使能,高电平有效
    input wire[31:0]   wdata,   // 32位写数据总线
    input wire[4:0]    addr,    // 5位地址总线(读/写共用)
    output wire[31:0]  rdata    // 32位读数据总线
);
2. 内部存储单元定义(第12行)
verilog 复制代码
reg[31:0] regfile[31:0];

定义了一个二维寄存器数组

  • 第一个[31:0]:每个寄存器的位宽为32位
  • 第二个[31:0]:总共有32个这样的寄存器
  • 这是寄存器堆的核心存储体,综合后会映射为FPGA中的Block RAM或分布式RAM
3. 仿真初始化块(第13-16行)
verilog 复制代码
initial begin
    $readmemh("C:\\ram_data.txt", regfile);
end

⚠️ 关键注意事项

  • initial语句仅在仿真开始时执行一次,不可综合(无法烧写到FPGA/ASIC硬件中)
  • $readmemh是Verilog系统函数,用于将十六进制格式的文本文件数据按顺序加载到寄存器数组中
  • 配套的ram_data.txt文件格式:每行一个32位十六进制数,第一行对应regfile[0],第二行对应regfile[1],以此类推
4. 异步读逻辑(第17-18行)
verilog 复制代码
assign rdata = (ren == 1'b1) ? regfile[addr] : 32'b0;
  • 纯组合逻辑实现,无时钟延迟 :只要renaddr变化,rdata立刻更新
  • 三目运算符逻辑:
    • 读使能ren=1:输出地址addr对应的寄存器值
    • 读使能ren=0:输出32位全0,避免总线冲突
5. 同步写逻辑(第19-23行)
verilog 复制代码
always @(posedge clk) begin
    if (wen)
        regfile[addr] = wdata;
end
  • 时序逻辑实现,仅在时钟上升沿触发
  • 只有当写使能wen=1时,才会将wdata写入地址addr指定的寄存器
  • 同步写保证了硬件的时序稳定性,是数字电路的标准设计规范
③RTL原理图与代码的对应关系

右边的电路图是上述Verilog代码综合后的RTL级结构,一一对应代码逻辑:

原理图模块 对应代码部分 功能说明
regfile_reg(RTL_RAM) reg[31:0] regfile[31:0]; + 写逻辑 核心存储体,实现同步写和异步读功能
WCLK clk 写操作时钟输入
WE1 wen 写使能输入
WA1[4:0] addr 写地址输入
WD1[31:0] wdata 写数据输入
RA1[4:0] addr 读地址输入(与写地址共用)
RO1[31:0] regfile[addr] 寄存器读数据输出
RTL_MUX assign rdata = ... 读使能控制的2选1多路选择器
选择端S ren 读使能作为多路选择器的控制信号
I0[31:0] RO1 有效读数据输入
I1[31:0] 接地(32'b0) 无效读时的默认输出
O[31:0] rdata 最终读数据输出

④设计特点与扩展方向
现有设计的特点
  1. 单端口结构:读和写共用同一个地址总线,同一时刻只能执行读或写操作
  2. 异步读+同步写:读操作无延迟,写操作时序稳定
  3. 带读使能控制,支持总线共享
常见扩展方向
  1. 双端口寄存器堆:分离读地址和写地址,支持同时读写(CPU标准实现)
  2. 多端口寄存器堆:支持多个读端口(如MIPS架构的2个读端口+1个写端口)
  3. 复位功能:增加复位信号,实现硬件上电复位(替代不可综合的initial语句)
  4. 字节写使能:支持按字节/半字写入,兼容不同位宽的数据操作

这张图是上一张单端口32×32位寄存器文件(regfile)的完整仿真验证方案 ,包含两部分:Verilog Testbench(测试平台)源码对应的仿真波形图,用于验证regfile设计的功能正确性。


⑤Testbench 仿真源码逐行解析

Testbench是数字IC设计中用于验证被测模块(DUT,Device Under Test)的专用代码,它不被综合,仅在仿真环境中运行,负责产生激励信号并观察输出。

通过这个Testbench,我们成功验证了regfile设计的4个核心功能:

  1. 上电初始化功能$readmemh正确将文本文件中的十六进制数据加载到寄存器数组中
  2. 同步写功能 :只有在时钟上升沿且写使能wen=1时,才会将数据写入指定地址
  3. 异步读功能 :读使能ren=1时,立刻输出对应地址的寄存器值,无时钟延迟
  4. 使能控制功能wen=0时禁止写操作,ren=0时输出全0,避免总线冲突
1. 基础设置与信号定义
verilog 复制代码
`timescale 1ns / 1ps  // 与被测模块保持一致的时间尺度
module testbench3();  // 测试平台无输入输出端口

// 激励信号:用reg类型,可在initial块中赋值
reg         clk;      // 系统时钟
reg         ren;      // 读使能
reg         wen;      // 写使能
reg[4:0]    addr;     // 5位地址总线
reg[31:0]   wdata;    // 32位写数据

// 观测信号:用wire类型,连接被测模块的输出
wire[31:0]  rdata;    // 32位读数据
2. 实例化被测模块
verilog 复制代码
// 实例化上一张图的regfile模块,命名为regfile0
// 端口顺序严格对应regfile模块的定义:clk, ren, wen, wdata, addr, rdata
regfile regfile0(clk, ren, wen, wdata, addr, rdata);
3. 激励信号产生(initial块)

initial块在仿真开始时执行一次,按时间顺序产生所有测试激励:

verilog 复制代码
initial begin
    // 0ns时刻:初始化所有信号,避免出现不定态X
    clk   = 1'b0;
    ren   = 1'b0;    // 初始关闭读使能
    wen   = 1'b0;    // 初始关闭写使能
    addr  = 5'b00011;// 初始地址设为3(二进制00011)
    wdata = 32'h0;   // 初始写数据为0

    #12;             // 延迟12ns,等待时钟稳定
    wen   = 1'b1;    // 打开写使能,准备写操作
    wdata = 32'h0a0a0a0a; // 要写入的数据:0a0a0a0a

    #5;              // 再延迟5ns
    wen   = 1'b0;    // 关闭写使能,结束写操作

    #2;              // 再延迟2ns
    ren   = 1'b1;    // 打开读使能,开始读操作
end
4. 时钟信号产生
verilog 复制代码
// 产生周期为10ns(频率100MHz)的方波时钟
// 每5ns翻转一次,0ns:0 → 5ns:1 → 10ns:0 → 15ns:1 → ...
always #5 clk = ~clk;

endmodule

⑥仿真波形图时序分析(核心重点)

右侧波形图是上述Testbench运行后的结果,横坐标是时间(单位ns),纵坐标是各个信号的值。黄色光标标记在15ns时刻,我们按时间轴逐段分析:

1. 0ns ~ 12ns:初始化与等待阶段
  • 0ns时刻

    • 所有信号按initial块初始化:clk=0,ren=0,wen=0,addr=03,wdata=00000000
    • 被测模块的内部寄存器regfile已通过$readmemh完成初始化:
      • regfile[0] = a0000000
      • regfile[1] = a0000001
      • regfile[2] = a0000002
      • regfile[3] = a0000003(初始值)
      • regfile[4] = a0000004
    • 读使能ren=0,所以输出rdata=00000000(符合设计中"ren=0时输出全0"的逻辑)
  • 5ns时刻 :clk第一次上升沿。此时wen=0,无写操作,寄存器值不变。

  • 10ns时刻:clk第二次下降沿。

2. 12ns ~ 17ns:同步写操作阶段
  • 12ns时刻

    • 激励变化:wen=1wdata=0a0a0a0a
    • ⚠️ 关键:此时clk是低电平,虽然wen变高,但写操作是同步时钟上升沿触发,所以此时不会写入数据。
  • 15ns时刻(黄色光标处)

    • clk第三次上升沿到来
    • 此时wen=1addr=03,满足写操作条件
    • 执行写操作:将0a0a0a0a写入regfile[3]
    • 波形验证:regfile[3]的值在15ns处从a0000003跳变为0a0a0a0a,完全符合同步写的设计。
  • 17ns时刻

    • 激励变化:wen=0,关闭写使能
    • 写操作已完成,后续时钟上升沿不会再写入数据。
3. 19ns之后:异步读操作阶段
  • 19ns时刻
    • 激励变化:ren=1,打开读使能
    • ⚠️ 关键:读操作是异步组合逻辑,不需要等待时钟
    • 执行读操作:输出regfile[addr]regfile[3]的值0a0a0a0a
    • 波形验证:rdata在19ns处立刻从00000000跳变为0a0a0a0a,完全符合异步读的设计。
相关推荐
Cat_Rocky3 小时前
Linux学习-Zabbix 7
学习·zabbix
炽烈小老头3 小时前
【每天学习一点算法 2026/05/20】省份数量
学习·算法
清平乐的技术专栏3 小时前
【Flink学习】(七)Flink 状态编程入门,有状态实时计算
大数据·学习·flink
Python大数据分析@4 小时前
现在怎么去学习AI,在哪里去学习?
人工智能·学习
星幻元宇VR4 小时前
VR地震模拟平台|打造沉浸式防震减灾科普新模式
科技·学习·安全·vr·虚拟现实
咸甜适中4 小时前
rust语言学习笔记Trait(六) FromIterator(由迭代器创建集合)
笔记·学习·rust
星幻元宇VR4 小时前
VR安全带防坠落体验平台助力高空作业安全培训
科技·学习·安全·vr·虚拟现实
周淳APP4 小时前
微前端核心沙箱机制深度解析:从iframe到乾坤沙箱
前端·学习·iframe·微前端·qiankun·前端架构
@杰克成4 小时前
Java学习31
java·学习·adb