从零开始学Verilog:数据类型与运算符详解

二牛学FPGA
文章2026-04-28
43

Quick Start

  • 步骤一:安装EDA工具(如Vivado或ModelSim),并创建一个空工程。
  • 步骤二:新建一个Verilog源文件(.v),输入以下代码作为第一个模块:

    module hello_world;

    initial begin

    $display("Hello, Verilog!");

    #10 $finish;

    end

    endmodule

  • 步骤三:在仿真器中编译该文件,无语法错误即通过。
  • 步骤四:运行仿真(run -all),观察控制台输出“Hello, Verilog!”。
  • 步骤五:修改代码,声明一个reg类型变量并赋值:

    reg [3:0] counter;

    initial begin

    counter = 4'b1010;

    $display("counter = %b", counter);

    end

  • 步骤六:运行仿真,确认输出为“counter = 1010”。
  • 步骤七:尝试使用运算符,如加法:

    reg [3:0] a, b, sum;

    initial begin

    a = 4'b0011;

    b = 4'b0101;

    sum = a + b;

    $display("sum = %b", sum);

    end

  • 步骤八:运行仿真,验证sum输出为“1000”(即3+5=8)。

前置条件与环境

项目/推荐值说明替代方案
EDA工具Vivado 2020.1+ 或 ModelSim SE-64 10.5+Quartus Prime、Icarus Verilog(开源)
仿真器Vivado Simulator 或 ModelSimVCS、NC-Verilog
器件/板卡无(仅仿真学习)任意FPGA开发板(如Xilinx Artix-7)
时钟/复位仿真中手动提供时钟周期(如#10 clk = ~clk)PLL或外部晶振(上板时)
接口依赖无(纯仿真)UART/SPI等(上板时)
约束文件仅仿真不需要;上板时需要XDC/SDCQSF(Quartus)
操作系统Windows 10/11 或 Linux CentOS 7+macOS(需虚拟机)

目标与验收标准

  • 功能点:掌握Verilog中wire、reg、integer、parameter等数据类型的声明与使用。
  • 功能点:熟练使用算术、逻辑、位、关系、移位、拼接等运算符。
  • 性能指标:无(纯语法学习,不涉及时序约束)。
  • 验收方式:能独立编写一个包含多种数据类型和运算符的仿真测试模块,并正确输出预期结果。
  • 关键日志:仿真控制台显示所有$display输出,无编译错误或警告。

实施步骤

工程结构

创建一个新目录,包含以下文件:

top.v:主模块(用于测试)。

tb.v:测试激励模块(可选,但推荐)。

对于纯语法学习,直接在top.v中写initial块即可。

关键模块:数据类型

Verilog有四大基本数据类型:wire(线网)、reg(寄存器)、integer(整数)、parameter(参数)。

// wire:用于组合逻辑连接,不能存储值

wire [7:0] data_bus;

assign data_bus = 8'hA5;

// reg:用于过程赋值(always/initial),可存储值

reg [3:0] count;

initial count = 4'd0;

// integer:32位有符号整数,常用于循环

integer i;

initial for(i=0; i<10; i=i+1) $display("%d", i);

// parameter:编译时常量

parameter WIDTH = 8;

reg [WIDTH-1:0] data;

注意wire只能在assign或模块端口赋值,reg只能在alwaysinitial中赋值。混用会导致编译错误。

关键模块:运算符

运算符分为以下几类,每类给出示例:

// 算术运算符:+ - * / %

reg [3:0] a = 4'd7, b = 4'd3;

reg [3:0] sum = a + b; // 10 -> 4'b1010

// 逻辑运算符:&& || !

wire logic_result = (a > 4'd5) && (b < 4'd5);

// 位运算符:& | ~ ^

wire [3:0] bitwise_and = a & b; // 4'b0011

// 关系运算符:> = <= == !=

wire eq = (a == b); // 0

// 移位运算符:<>

wire [3:0] shifted = a << 1; // 4'b1110

// 拼接运算符:{ }

wire [7:0] concat = {a, b}; // 8'b01110011

// 条件运算符:? :

wire [3:0] max = (a > b) ? a : b;

注意:算术运算中,结果宽度由操作数最大宽度决定,可能溢出。使用$signed()$unsigned()控制符号。

时序/CDC/约束

本教程为语法入门,不涉及时序约束或跨时钟域。但需注意:

– 在always @(posedge clk)中,赋值使用非阻塞赋值<=,避免竞争。

– 组合逻辑使用阻塞赋值=

验证

编写测试模块,覆盖所有数据类型和运算符:

module tb;

reg [3:0] a, b;

wire [7:0] concat;

assign concat = {a, b};

initial begin

a = 4'b1010; b = 4'b0101;

#10;

$display("a=%b, b=%b, concat=%b", a, b, concat);

$display("a+b=%b", a + b);

$display("a&b=%b", a & b);

$display("a>>1=%b", a >> 1);

$display("max=%b", (a > b) ? a : b);

#10 $finish;

end

endmodule

验收点:仿真输出所有$display结果,且符合预期。

常见坑与排查

  • 坑1:把wire放在always块中赋值 → 编译错误。需改为reg或使用assign
  • 坑2:算术运算结果溢出但未察觉 → 使用足够宽度的变量,或检查高位。
  • 坑3:逻辑运算符与位运算符混淆(如&& vs &) → 逻辑运算符返回1位布尔值,位运算符按位操作。
  • 坑4:拼接运算符中位宽不匹配 → 确保每个元素位宽明确,如{4'b1010, 4'b0101}
  • 坑5:移位运算符对reg类型操作时,结果可能为x → 确保操作数已初始化。

原理与设计说明

Verilog的数据类型和运算符设计遵循硬件描述的核心需求:wire对应物理连线,reg对应存储单元(触发器或锁存器)。parameter允许设计可配置,避免硬编码。运算符直接映射到硬件门电路:加法器、比较器、多路选择器等。理解这一点有助于写出高效的RTL代码。

关键trade-off

– 使用integer类型在仿真中方便,但综合时会被视为32位寄存器,浪费资源。推荐使用reg指定位宽。

– 拼接运算符{}可以简化数据打包,但过多嵌套会降低可读性,建议用assign分步完成。

– 条件运算符?:综合为多路选择器,嵌套深度过大时影响时序。可改用case语句。

验证与结果

测试用例输入预期输出实际输出(仿真)
加法a=4’b1010 (10), b=4’b0101 (5)4’b1111 (15)4’b1111
位与a=4’b1010, b=4’b01014’b00004’b0000
移位右移1位a=4’b10104’b01014’b0101
拼接a=4’b1010, b=4’b01018’b101001018’b10100101
条件运算符a=10, b=51010

测量条件:Vivado 2020.1 Simulator,默认设置,无时序约束。

故障排查(Troubleshooting)

  • 现象:编译错误“Illegal assignment to wire” → 原因:在always块中给wire赋值。检查点:确认变量类型。修复:改为reg或使用assign。
  • 现象:仿真输出为x或z → 原因:变量未初始化或未驱动。检查点:查看initial块或assign语句。修复:在initial中赋初值。
  • 现象:算术结果截断 → 原因:结果位宽不足。检查点:比较操作数和结果位宽。修复:扩展结果位宽或使用$signed。
  • 现象:逻辑运算结果始终为0或1 → 原因:误用位运算符代替逻辑运算符。检查点:检查运算符类型。修复:使用&&、||、!。
  • 现象:拼接结果顺序错误 → 原因:拼接顺序与期望相反。检查点:确认{}内元素顺序。修复:调整顺序。
  • 现象:移位后高位丢失 → 原因:移位操作未考虑位宽。检查点:移位位数是否超过位宽。修复:使用中间变量或限制移位位数。
  • 现象:条件运算符嵌套导致综合警告 → 原因:嵌套过深。检查点:检查代码复杂度。修复:改用case语句。
  • 现象:仿真时间过长 → 原因:无限循环。检查点:检查for或while循环条件。修复:添加$finish或限制循环次数。

扩展与下一步

  • 扩展1:学习wireregalways @(*)中的使用,实现组合逻辑。
  • 扩展2:掌握for循环和generate语句,实现参数化设计。
  • 扩展3:学习系统任务如$monitor$dumpvars用于调试。
  • 扩展4:尝试编写一个简单的计数器模块,综合并上板验证。
  • 扩展5:研究运算符的优先级,避免因优先级错误导致的逻辑错误。
  • 扩展6:学习使用typedefstruct(SystemVerilog)进行更高级的数据组织。

参考与信息来源

  • IEEE Std 1364-2001 (Verilog HDL) 官方标准
  • “Verilog HDL: A Guide to Digital Design and Synthesis” by Samir Palnitkar
  • Xilinx Vivado Design Suite User Guide (UG901)
  • EDA Playground 在线仿真平台(https://www.edaplayground.com/)

技术附录

术语表

  • wire:线网类型,用于连接模块端口或组合逻辑输出。
  • reg:寄存器类型,用于存储值,在always或initial中赋值。
  • integer:32位有符号整数,常用于仿真循环。
  • parameter:编译时常量,用于参数化设计。
  • 阻塞赋值(=):立即执行,用于组合逻辑。
  • 非阻塞赋值(<=):在always块结束时更新,用于时序逻辑。

检查清单

  • [ ] 所有变量类型正确(wire/reg)。
  • [ ] 所有运算符使用正确,优先级明确。
  • [ ] 拼接运算符中位宽匹配。
  • [ ] 算术运算结果位宽足够。
  • [ ] 仿真无编译错误。
  • [ ] 所有$display输出与预期一致。

关键约束速查

约束说明示例
位宽声明使用[MSB:LSB]格式reg [7:0] data;
基数表示二进制’b,十进制’d,十六进制’h8'b1010_1010
符号扩展使用$signed()$signed(a) + $signed(b)
阻塞赋值组合逻辑中使用=always @(*) c = a & b;
非阻塞赋值时序逻辑中使用<=always @(posedge clk) q <= d;
分类
技术分享
标签
Verilog数据类型运算符
浏览 43
分享:

相关推荐

同频道 · 相近分类

暂无相关推荐

作者

二牛学FPGA查看主页

同分类阅读

文章

延伸阅读与实操

  • 文章 + 课程联动深度文章常对应体系课章节,可一键选课。
  • 学习产出可参考笔记与作业案例在学习产出广场持续更新。

探索全站