别再死记硬背Verilog语法了!用Xilinx Vivado跑通这4个实战小模块(附源码)
2026/5/12 11:10:24 网站建设 项目流程

从实战中掌握Verilog:4个Vivado项目带你突破语法困境

很多初学者在接触Verilog时都会陷入一个怪圈:反复阅读语法手册,却在实际编码时无从下手。这种"纸上谈兵"的学习方式效率极低,往往让人在抽象的概念中迷失方向。本文将彻底改变这一现状——我们不再从语法规则开始,而是直接动手实现4个实用功能模块,通过Xilinx Vivado的完整开发流程,让你在实践中自然而然地掌握Verilog的核心思想。

1. 准备工作:搭建Vivado开发环境

在开始实战之前,我们需要确保开发环境准备就绪。Xilinx Vivado是业界广泛使用的FPGA开发工具套件,它集成了设计、仿真、综合和实现的全套功能。

首先从Xilinx官网下载并安装Vivado Design Suite。对于初学者,建议选择WebPACK版本,这是免费的轻量级版本,包含了我们所需的所有基础功能。安装过程中,注意勾选以下组件:

  • Vivado
  • Vivado Simulator
  • 适用于你开发板的器件支持文件

安装完成后,创建一个新项目:

# 在Vivado Tcl控制台中创建新项目的命令示例 create_project verilog_lab /path/to/project -part xc7a35ticsg324-1L

提示:项目创建时选择的器件型号需要与你的实际开发板匹配。如果不确定,可以查阅开发板手册或选择通用的Artix-7系列器件。

2. 实战项目一:位宽计算器

第一个项目我们将实现一个简单的位宽计算器模块。这个模块看似基础,但包含了Verilog中多个核心概念:模块定义、端口声明、参数化设计以及基本的算术运算。

2.1 模块设计与实现

在Vivado中新建一个Verilog源文件,命名为bit_width_calculator.v。以下是完整的模块代码:

module bit_width_calculator #( parameter INPUT_WIDTH = 8 ) ( input wire [INPUT_WIDTH-1:0] data_in, output wire [$clog2(INPUT_WIDTH):0] width_out ); // 计算输入数据实际使用的位宽 integer i; reg [$clog2(INPUT_WIDTH):0] count; always @(*) begin count = 0; for (i = INPUT_WIDTH-1; i >= 0; i = i-1) begin if (data_in[i]) begin count = i + 1; disable loop; // 找到最高有效位后退出循环 end end end assign width_out = count; endmodule

这个模块的核心功能是计算输入数据实际使用的位宽。例如,对于8位输入8'b0010_1100,实际使用的位宽是6(从第5位到第0位)。

2.2 仿真与波形分析

创建测试平台(testbench)是验证设计的关键步骤。新建一个仿真源文件tb_bit_width.v

`timescale 1ns / 1ps module tb_bit_width; reg [7:0] test_data; wire [3:0] calculated_width; bit_width_calculator #(.INPUT_WIDTH(8)) uut ( .data_in(test_data), .width_out(calculated_width) ); initial begin test_data = 8'b00000000; #10; test_data = 8'b00000001; #10; test_data = 8'b00010000; #10; test_data = 8'b01010101; #10; test_data = 8'b10000000; #10; $finish; end endmodule

运行仿真后,观察波形图可以直观地看到模块的行为。特别注意以下几点:

  1. 当输入全为0时,输出位宽为0
  2. 最低有效位为1时,输出位宽为1
  3. 最高有效位为1时,输出位宽等于输入位宽

3. 实战项目二:多条件状态机

第二个项目我们将实现一个具有多个判断条件的状态机。这个例子将展示Verilog中case语句和状态机设计的典型用法。

3.1 状态机设计

创建一个新模块multi_condition_fsm.v,实现一个简单的交通灯控制器:

module multi_condition_fsm ( input wire clk, input wire reset_n, input wire emergency, input wire pedestrian, output reg [1:0] light_state // 00:红, 01:黄, 10:绿 ); // 状态定义 parameter RED = 2'b00; parameter YELLOW = 2'b01; parameter GREEN = 2'b10; // 状态寄存器 reg [1:0] current_state, next_state; // 状态转移逻辑 always @(posedge clk or negedge reset_n) begin if (!reset_n) begin current_state <= RED; end else begin current_state <= next_state; end end // 下一状态逻辑 always @(*) begin case (current_state) RED: begin if (emergency) next_state = RED; else next_state = GREEN; end GREEN: begin if (pedestrian || emergency) next_state = YELLOW; else next_state = GREEN; end YELLOW: next_state = RED; default: next_state = RED; endcase end // 输出逻辑 always @(*) begin light_state = current_state; end endmodule

3.2 测试与调试技巧

为这个状态机创建测试平台时,我们需要考虑各种条件组合:

`timescale 1ns / 1ps module tb_fsm; reg clk, reset_n, emergency, pedestrian; wire [1:0] light_state; multi_condition_fsm uut ( .clk(clk), .reset_n(reset_n), .emergency(emergency), .pedestrian(pedestrian), .light_state(light_state) ); // 时钟生成 always #5 clk = ~clk; initial begin clk = 0; reset_n = 0; emergency = 0; pedestrian = 0; #10 reset_n = 1; // 测试正常状态转换 #20 pedestrian = 1; #20 pedestrian = 0; // 测试紧急情况 #20 emergency = 1; #30 emergency = 0; #50 $finish; end endmodule

在仿真波形中,重点关注以下几点:

  1. 复位后初始状态是否为RED
  2. 正常情况下RED→GREEN的转换
  3. 行人请求时GREEN→YELLOW→RED的转换
  4. 紧急情况下保持RED状态

4. 实战项目三:循环计数器与时钟分频

第三个项目将展示如何使用Verilog实现循环计数器和时钟分频器,这是FPGA设计中非常常见的功能。

4.1 可配置计数器实现

创建cycle_counter.v文件:

module cycle_counter #( parameter WIDTH = 8, parameter MAX_COUNT = 255 ) ( input wire clk, input wire reset_n, input wire enable, output reg [WIDTH-1:0] count, output reg overflow ); always @(posedge clk or negedge reset_n) begin if (!reset_n) begin count <= 0; overflow <= 0; end else if (enable) begin if (count == MAX_COUNT) begin count <= 0; overflow <= 1; end else begin count <= count + 1; overflow <= 0; end end end endmodule

4.2 时钟分频应用

利用上面的计数器,我们可以实现一个时钟分频器:

module clock_divider #( parameter DIV_RATIO = 10 ) ( input wire clk_in, input wire reset_n, output wire clk_out ); wire [31:0] counter_out; wire overflow; cycle_counter #( .WIDTH(32), .MAX_COUNT(DIV_RATIO-1) ) counter_inst ( .clk(clk_in), .reset_n(reset_n), .enable(1'b1), .count(counter_out), .overflow(overflow) ); reg div_clk; always @(posedge clk_in or negedge reset_n) begin if (!reset_n) begin div_clk <= 0; end else if (overflow) begin div_clk <= ~div_clk; end end assign clk_out = div_clk; endmodule

测试这个分频器时,可以设置不同的分频比并观察输出时钟的频率:

`timescale 1ns / 1ps module tb_divider; reg clk, reset_n; wire divided_clk; clock_divider #(.DIV_RATIO(5)) uut ( .clk_in(clk), .reset_n(reset_n), .clk_out(divided_clk) ); always #5 clk = ~clk; initial begin clk = 0; reset_n = 0; #20 reset_n = 1; #200 $finish; end endmodule

5. 实战项目四:边沿检测电路

最后一个项目将实现一个边沿检测电路,这是数字系统中常见的前级处理模块。

5.1 边沿检测原理

边沿检测电路可以检测输入信号的上升沿、下降沿或双边沿。我们以实现上升沿检测为例:

module edge_detector ( input wire clk, input wire reset_n, input wire signal_in, output wire rising_edge, output wire falling_edge ); reg signal_delay; always @(posedge clk or negedge reset_n) begin if (!reset_n) begin signal_delay <= 0; end else begin signal_delay <= signal_in; end end assign rising_edge = (~signal_delay) & signal_in; assign falling_edge = signal_delay & (~signal_in); endmodule

5.2 实际应用测试

创建一个测试平台来验证边沿检测功能:

`timescale 1ns / 1ps module tb_edge; reg clk, reset_n, test_signal; wire rise, fall; edge_detector uut ( .clk(clk), .reset_n(reset_n), .signal_in(test_signal), .rising_edge(rise), .falling_edge(fall) ); always #5 clk = ~clk; initial begin clk = 0; reset_n = 0; test_signal = 0; #20 reset_n = 1; // 生成测试信号 #15 test_signal = 1; #30 test_signal = 0; #20 test_signal = 1; #10 test_signal = 0; #50 $finish; end endmodule

在仿真波形中,注意观察:

  1. rising_edge脉冲只在信号从0变1时出现
  2. falling_edge脉冲只在信号从1变0时出现
  3. 脉冲宽度为一个时钟周期

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询