Verilog always语句详解:从组合逻辑到时序逻辑

**Verilog ****always**语句详解:从组合逻辑到时序逻辑

always语句是Verilog/SystemVerilog中最核心、最强大的过程块,用于描述硬件的重复行为。它定义了"在什么条件下"以及"如何执行"的硬件行为模型。

一、基本语法与结构

verilog 复制代码
// 基本形式
always @(敏感事件列表) begin
  // 过程语句
  // 可以包含:赋值语句、条件语句、循环语句等
end

关键特性:

  1. 无限循环执行always块一旦被激活,就会不断重复执行
  2. 敏感事件驱动:执行由敏感事件列表控制
  3. 内部顺序执行:块内语句按书写顺序执行(但注意赋值的语义)

二、敏感事件列表的三种主要形式

1. 电平敏感(组合逻辑)

verilog 复制代码
// 形式1:使用 *(推荐,自动推断所有输入信号)
always @(*) begin
  y = a & b | c;
end

// 形式2:显式列出所有输入信号
always @(a, b, c) begin
  y = a & b | c;
end

// 形式3:SystemVerilog简化写法
always_comb begin
  y = a & b | c;
end

特点

  • 任何输入信号变化时立即执行
  • 必须使用阻塞赋值 (=)
  • 描述组合逻辑电路

2. 边沿敏感(时序逻辑)

verilog 复制代码
// 时钟上升沿触发
always @(posedge clk) begin
  q <= d;
end

// 时钟下降沿触发
always @(negedge clk) begin
  q <= d;
end

// 多信号边沿触发(不推荐,易出错)
always @(posedge clk, posedge reset) begin
  // ...
end

特点

  • 只在时钟边沿触发
  • 必须使用非阻塞赋值 (<=)
  • 描述触发器、寄存器等时序元件

3. 混合敏感(异步控制)

verilog 复制代码
// 异步复位,时钟上升沿触发
always @(posedge clk or posedge reset) begin
  if (reset) begin
    q <= 1'b0;  // 异步复位
  end else begin
    q <= d;     // 同步工作
  end
end

// 异步复位,异步置位
always @(posedge clk or posedge reset or posedge set) begin
  if (reset) begin
    q <= 1'b0;
  end else if (set) begin
    q <= 1'b1;
  end else begin
    q <= d;
  end
end

三、always块的四种主要应用模式

1. 纯组合逻辑

verilog 复制代码
// 多路选择器
always @(*) begin
  case (sel)
    2'b00: out = a;
    2'b01: out = b;
    2'b10: out = c;
    2'b11: out = d;
    default: out = 1'b0;
  endcase
end

// 译码器
always @(*) begin
  // 避免锁存器:必须为所有分支赋值
  decode = 4'b0000;
  if (en) begin
    case (addr)
      2'b00: decode = 4'b0001;
      2'b01: decode = 4'b0010;
      2'b10: decode = 4'b0100;
      2'b11: decode = 4'b1000;
    endcase
  end
end

重要 :组合逻辑always块中,必须为所有可能的输入组合 指定输出值,否则会推断出锁存器

2. 同步时序逻辑(最常见)

verilog 复制代码
// 带同步复位的D触发器
always @(posedge clk) begin
  if (reset) begin
    q <= 1'b0;
  end else begin
    q <= d;
  end
end

// 带使能的计数器
always @(posedge clk) begin
  if (reset) begin
    count <= 0;
  end else if (enable) begin
    count <= count + 1;
  end
  // 注意:没有else分支,count保持原值
end

// 状态机
always @(posedge clk) begin
  if (reset) begin
    state <= IDLE;
  end else begin
    case (state)
      IDLE: if (start) state <= WORK;
      WORK: if (done) state <= IDLE;
      default: state <= IDLE;
    endcase
  end
end

3. 异步时序逻辑

verilog 复制代码
// 异步复位D触发器
always @(posedge clk or posedge async_reset) begin
  if (async_reset) begin
    q <= 1'b0;  // 异步动作:不受时钟控制
  end else begin
    q <= d;     // 同步动作:时钟上升沿触发
  end
end

// 异步复位,同步释放(推荐,避免亚稳态)
reg reset_sync;
always @(posedge clk or posedge async_reset) begin
  if (async_reset) begin
    reset_sync <= 1'b1;
    q <= 1'b0;
  end else begin
    reset_sync <= 1'b0;
    q <= d;
  end
end

4. 电平敏感锁存器(通常应避免)

verilog 复制代码
// 不完整的if语句会产生锁存器
always @(*) begin
  if (enable) begin
    q = d;  // 当enable=0时,q没有赋值,保持原值 → 锁存器
  end
end

// 明确的锁存器(不推荐在FPGA中使用)
always @(*) begin
  if (gate) begin
    q = d;  // 电平敏感存储
  end
end

四、高级用法与注意事项

1. 多个always块操作同一变量

verilog 复制代码
// 错误:多个always块对同一变量赋值 → 多驱动错误
reg [7:0] data;
always @(posedge clk) begin
  if (load_a) data <= input_a;
end
always @(posedge clk) begin
  if (load_b) data <= input_b;  // 编译错误
end

// 正确:合并到一个always块
reg [7:0] data;
always @(posedge clk) begin
  if (load_a) begin
    data <= input_a;
  end else if (load_b) begin
    data <= input_b;
  end
end

2. always块中的循环语句

verilog 复制代码
// 用于组合逻辑的循环(会被展开)
parameter WIDTH = 8;
reg [WIDTH-1:0] parity;
integer i;

always @(*) begin
  parity = 0;
  for (i = 0; i < WIDTH; i = i + 1) begin
    parity = parity ^ data[i];  // 计算奇偶校验
  end
end

// 注意:循环边界必须能在编译时确定

3. SystemVerilog增强

verilog 复制代码
// always_comb: 自动推断敏感列表,检查组合逻辑
always_comb begin
  y = a & b;
  // 工具会自动检查是否所有输入都在敏感列表中
  // 检查是否推断出锁存器
end

// always_ff: 明确指定时序逻辑
always_ff @(posedge clk or posedge reset) begin
  if (reset) q <= '0;
  else       q <= d;
end

// always_latch: 明确指定锁存器
always_latch begin
  if (en) q = d;
end

五、常见错误与调试技巧

错误1:组合逻辑中产生锁存器

verilog 复制代码
// 错误:缺少else分支
always @(*) begin
  if (sel) 
    out = a;
  // 当sel=0时,out没有赋值 → 锁存器!
end

// 修复:添加默认值
always @(*) begin
  out = 0;  // 默认赋值
  if (sel) 
    out = a;
end

错误2:时序逻辑中使用阻塞赋值

verilog 复制代码
// 错误:可能导致竞争条件
always @(posedge clk) begin
  a = b;  // 阻塞赋值
  c = a;  // 使用新的a值
  // 仿真与综合可能不一致
end

// 正确:使用非阻塞赋值
always @(posedge clk) begin
  a <= b;  // 计划赋值
  c <= a;  // 使用时钟沿前的a值
end

错误3:不完整的敏感列表

verilog 复制代码
// 错误:缺少信号c
always @(a, b) begin
  y = a & b | c;  // c变化时,y不会更新
end

// 正确:使用@(*)
always @(*) begin
  y = a & b | c;
end

六、性能与优化建议

  1. 尽量使用时序逻辑:组合逻辑容易产生毛刺,时序逻辑更稳定
  2. 一个变量一个always块:保持代码清晰
  3. 优先使用同步设计:异步设计难验证、难综合
  4. 明确复位策略:同步复位还是异步复位,保持一致
  5. 避免在always块中使用#延迟:不可综合,仅用于仿真

七、实例:用always块实现一个FIFO控制器

verilog 复制代码
module fifo_ctrl #(
  parameter ADDR_WIDTH = 4
)(
  input  wire clk, reset,
  input  wire wr_en, rd_en,
  output reg  full, empty,
  output reg  [ADDR_WIDTH-1:0] wptr, rptr
);

  localparam DEPTH = 1 << ADDR_WIDTH;
  reg [ADDR_WIDTH:0] wptr_reg, rptr_reg;  // 额外一位用于判断空满

  // 写指针逻辑
  always @(posedge clk or posedge reset) begin
    if (reset) begin
      wptr_reg <= 0;
    end else if (wr_en && !full) begin
      wptr_reg <= wptr_reg + 1;
    end
  end

  // 读指针逻辑
  always @(posedge clk or posedge reset) begin
    if (reset) begin
      rptr_reg <= 0;
    end else if (rd_en && !empty) begin
      rptr_reg <= rptr_reg + 1;
    end
  end

  // 空满判断(组合逻辑)
  always @(*) begin
    full  = (wptr_reg[ADDR_WIDTH-1:0] == rptr_reg[ADDR_WIDTH-1:0]) &&
    (wptr_reg[ADDR_WIDTH] != rptr_reg[ADDR_WIDTH]);
    empty = (wptr_reg == rptr_reg);
    wptr = wptr_reg[ADDR_WIDTH-1:0];
    rptr = rptr_reg[ADDR_WIDTH-1:0];
  end

endmodule

总结表格

特性 组合逻辑always 时序逻辑always
触发条件 电平变化(@(*)) 时钟边沿(@(posedge clk))
赋值类型 阻塞赋值(=) 非阻塞赋值(<=)
实现电路 组合门电路 触发器/寄存器
输出特性 立即变化 时钟边沿后变化
敏感列表 所有输入信号 时钟、异步控制信号
常见错误 产生锁存器 使用阻塞赋值导致竞争

黄金法则

  1. 描述组合逻辑 → always @(*)+ 阻塞赋值(=)
  2. 描述时序逻辑 → always @(posedge clk)+ 非阻塞赋值(<=)
  3. 避免锁存器 → 为组合逻辑的所有分支赋值
  4. 避免多驱动 → 一个变量只在一个always块中赋值

掌握always语句是Verilog设计的核心,理解其工作原理和适用场景,才能写出可靠、可综合的硬件描述代码。

相关推荐
仲南音2 天前
Xilinx FPGA——时钟IP核MMCM、PLL
fpga开发
I'm a winner2 天前
基于Xilinx FPGA的LVDS高速串行通信系统(三)--数据训练及握手机制【文末源码】
fpga开发
upper20203 天前
coremark移植坑之-nostartfiles -nostdlib参数
学习·fpga开发·coremark移植·riscv设计
Eloudy3 天前
Vivado 纯命令行构建 FPGA 项目教程
fpga开发
Rambo.xia3 天前
AXI跨时钟域与数据一致性——CDC FIFO深度不够、写后读冒险、多Master仲裁、乱序返回,出了Bug根本查不出来
fpga开发·bug
upper20203 天前
FPGA部署卷积神经网络识别MINST图片
fpga开发·卷积神经网络·minst·minst识别·部署神经网络·神经网络识别图片
你是我的解忧王子3 天前
嵌入式 ADC采样 原始数据分析上层(平均值 波动率 正态分布 线性补偿 系统误差)
fpga开发·数据挖掘·数据分析
9527华安4 天前
FPGA纯verilog代码ISP图像处理培训教程,基于IMX214,提供工程源码+视频教程+FPGA开发板
图像处理·fpga开发·isp·imx214·mipi
千寻xun5 天前
二、实战篇-NVME SSD控制之ZYNQ实现(六)-读写NVME SSD硬盘数据
fpga开发·nvme·nvme ssd
千寻xun5 天前
二、实战篇-NVME SSD控制之ZYNQ实现(三)
fpga开发·nvme ssd·nvme协议