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设计的核心,理解其工作原理和适用场景,才能写出可靠、可综合的硬件描述代码。

相关推荐
李嘉图Ricado3 小时前
FPGA 时序约束与分析
fpga开发
白又白、6 小时前
时序优化和上板调试小结
fpga开发
Z22ZHaoGGGG8 小时前
verilog实现采样电流有效值的计算
fpga开发
fei_sun8 小时前
牛客Verilog刷题篇
fpga开发
my_daling11 小时前
DSMC通信协议理解,以及如何在FPGA上实现DSMC从设备(1)
学习·fpga开发
fei_sun1 天前
FPGA&数字前端
fpga开发
尤老师FPGA1 天前
HDMI数据的接收发送实验(九)
fpga开发
Flamingˢ1 天前
ZYNQ + OV5640 视频系统开发(四):HDMI 显示链路
嵌入式硬件·fpga开发·硬件架构·音视频
LCMICRO-133108477461 天前
国产长芯微LDC5141完全P2P替代DAC80501,数模转换器 (DAC)
单片机·嵌入式硬件·fpga开发·硬件工程·dsp开发·数模转换器 dac