简体中文 繁體中文 English Deutsch 한국 사람 بالعربية TÜRKÇE português คนไทย Français Japanese

站内搜索

搜索

活动公告

通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,将及时处理!
10-23 09:31

Verilog输出赋值完整学习指南 覆盖所有关键概念包括阻塞赋值和非阻塞赋值的区别 在硬件设计中的正确使用方法 实际项目中的问题排查与解决方案 提高数字电路可靠性的必备知识

SunJu_FaceMall

3万

主题

238

科技点

3万

积分

大区版主

碾压王

积分
32126

立华奏

发表于 2025-10-4 22:20:10 | 显示全部楼层 |阅读模式 [标记阅至此楼]

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

Verilog作为硬件描述语言(HDL)的一种,在现代数字电路设计中扮演着至关重要的角色。在Verilog中,赋值操作是最基本也是最重要的操作之一,它直接影响着电路的行为和性能。正确理解和使用Verilog中的赋值操作,特别是阻塞赋值(Blocking Assignment)和非阻塞赋值(Non-blocking Assignment),对于设计可靠、高效的数字电路至关重要。

本文将全面介绍Verilog中的赋值操作,深入探讨阻塞赋值和非阻塞赋值的区别,提供在硬件设计中的正确使用方法,分析实际项目中可能遇到的问题及其解决方案,并分享提高数字电路可靠性的必备知识。无论您是Verilog初学者还是有经验的设计工程师,本文都能帮助您更好地理解和应用Verilog赋值操作。

Verilog赋值基础

在Verilog中,赋值操作用于将值赋给变量或网络(net)。根据赋值发生的时间和方式,Verilog中的赋值主要可以分为两大类:连续赋值(Continuous Assignment)和过程赋值(Procedural Assignment)。

连续赋值

连续赋值使用assign关键字,主要用于组合逻辑电路的描述。连续赋值在右侧表达式发生变化时立即执行,并且没有时序控制。
  1. // 连续赋值示例
  2. assign y = a & b; // 当a或b发生变化时,y立即更新
复制代码

过程赋值

过程赋值发生在always块或initial块中,可以分为阻塞赋值和非阻塞赋值两种。
  1. // 过程赋值示例
  2. always @(posedge clk) begin
  3.   // 阻塞赋值
  4.   b = a;
  5.   
  6.   // 非阻塞赋值
  7.   c <= a;
  8. end
复制代码

在接下来的章节中,我们将重点讨论过程赋值中的阻塞赋值和非阻塞赋值,它们是Verilog设计中最关键也最容易混淆的概念。

阻塞赋值(Blocking Assignment)

阻塞赋值使用=符号表示,它在执行时会阻塞后续语句的执行,直到当前赋值操作完成。阻塞赋值的行为类似于顺序编程语言中的赋值操作,执行顺序严格按照代码的书写顺序。

基本语法
  1. variable = expression;
复制代码

执行特点

1. 顺序执行:阻塞赋值按照代码中的顺序依次执行。
2. 立即更新:赋值操作完成后,左侧变量的值立即更新。
3. 阻塞后续语句:在当前赋值操作完成之前,不会执行下一条语句。

使用场景

阻塞赋值主要用于描述组合逻辑,因为在组合逻辑中,我们希望信号的变化能够立即传播。

示例代码
  1. module blocking_example(
  2.   input clk,
  3.   input a,
  4.   input b,
  5.   output reg c,
  6.   output reg d
  7. );
  8.   
  9.   always @(posedge clk) begin
  10.     // 阻塞赋值示例
  11.     c = a & b;  // 先执行
  12.     d = c;      // 后执行,使用的是更新后的c值
  13.   end
  14.   
  15. endmodule
复制代码

在上面的例子中,当时钟上升沿到来时:

1. 首先执行c = a & b,c的值立即更新为a和b的逻辑与结果。
2. 然后执行d = c,d的值被设置为更新后的c值。

仿真波形分析

假设a和b的值在某个时钟上升沿分别为1和1,那么:

1. 在时钟上升沿,c = a & b执行,c的值变为1。
2. 然后立即执行d = c,d的值也变为1。

如果a和b的值在下一个时钟上升沿变为1和0,那么:

1. 在时钟上升沿,c = a & b执行,c的值变为0。
2. 然后立即执行d = c,d的值也变为0。

非阻塞赋值(Non-blocking Assignment)

非阻塞赋值使用<=符号表示,它在执行时不会阻塞后续语句的执行。所有非阻塞赋值语句在同一个时间步内并行计算,但在时间步结束时才更新左侧变量的值。

基本语法
  1. variable <= expression;
复制代码

执行特点

1. 并行计算:所有非阻塞赋值语句在同一时间步内并行计算右侧表达式。
2. 延迟更新:左侧变量的值在时间步结束时才更新。
3. 不阻塞后续语句:当前非阻塞赋值语句不会阻塞后续语句的执行。

使用场景

非阻塞赋值主要用于描述时序逻辑,特别是在时钟边沿触发的寄存器赋值中。

示例代码
  1. module non_blocking_example(
  2.   input clk,
  3.   input a,
  4.   input b,
  5.   output reg c,
  6.   output reg d
  7. );
  8.   
  9.   always @(posedge clk) begin
  10.     // 非阻塞赋值示例
  11.     c <= a & b;  // 并行计算
  12.     d <= c;      // 并行计算,使用的是c的旧值
  13.   end
  14.   
  15. endmodule
复制代码

在上面的例子中,当时钟上升沿到来时:

1. c <= a & b和d <= c同时计算右侧表达式。
2. c <= a & b计算a和b的逻辑与结果,但c的值不会立即更新。
3. d <= c计算时使用的是c的旧值(上一个时钟周期的值)。
4. 在时间步结束时,c和d的值同时更新。

仿真波形分析

假设在第一个时钟上升沿,a和b的值分别为1和1,c和d的初始值为0:

1. 在时钟上升沿,c <= a & b计算结果为1,d <= c计算结果为0(使用c的旧值)。
2. 在时间步结束时,c的值更新为1,d的值更新为0。

在下一个时钟上升沿,a和b的值仍为1和1:

1. 在时钟上升沿,c <= a & b计算结果为1,d <= c计算结果为1(使用c的旧值,即上一个时钟周期更新后的值1)。
2. 在时间步结束时,c的值保持为1,d的值更新为1。

阻塞赋值与非阻塞赋值的区别

阻塞赋值和非阻塞赋值在Verilog中有着本质的区别,理解这些区别对于正确设计数字电路至关重要。

执行方式

• 阻塞赋值:顺序执行,一条语句执行完后才执行下一条语句。
• 非阻塞赋值:并行执行,所有语句在同一时间步内计算,但在时间步结束时才更新值。

值的更新时间

• 阻塞赋值:赋值语句执行后立即更新值。
• 非阻塞赋值:在时间步结束时才更新值。

对后续语句的影响

• 阻塞赋值:会阻塞后续语句的执行,直到当前赋值完成。
• 非阻塞赋值:不会阻塞后续语句的执行。

对应的硬件实现

• 阻塞赋值:通常对应组合逻辑,没有寄存器。
• 非阻塞赋值:通常对应时序逻辑,有寄存器。

示例对比

让我们通过一个具体的例子来对比阻塞赋值和非阻塞赋值的区别:
  1. module assignment_comparison(
  2.   input clk,
  3.   input a,
  4.   input b,
  5.   output reg c1, d1,  // 阻塞赋值输出
  6.   output reg c2, d2   // 非阻塞赋值输出
  7. );
  8.   
  9.   // 阻塞赋值示例
  10.   always @(posedge clk) begin
  11.     c1 = a & b;  // 先执行
  12.     d1 = c1;     // 后执行,使用更新后的c1值
  13.   end
  14.   
  15.   // 非阻塞赋值示例
  16.   always @(posedge clk) begin
  17.     c2 <= a & b;  // 并行计算
  18.     d2 <= c2;     // 并行计算,使用c2的旧值
  19.   end
  20.   
  21. endmodule
复制代码

假设在第一个时钟上升沿,a和b的值分别为1和1,c1、d1、c2、d2的初始值都为0:

阻塞赋值部分:

1. 执行c1 = a & b,c1的值立即变为1。
2. 执行d1 = c1,d1的值变为1(使用更新后的c1值)。
3. 结果:c1 = 1,d1 = 1。

非阻塞赋值部分:

1. 计算c2 <= a & b,结果为1,但c2的值不立即更新。
2. 计算d2 <= c2,结果为0(使用c2的旧值0)。
3. 在时间步结束时,c2的值更新为1,d2的值更新为0。
4. 结果:c2 = 1,d2 = 0。

在下一个时钟上升沿,a和b的值仍为1和1:

阻塞赋值部分:

1. 执行c1 = a & b,c1的值保持为1。
2. 执行d1 = c1,d1的值保持为1。
3. 结果:c1 = 1,d1 = 1。

非阻塞赋值部分:

1. 计算c2 <= a & b,结果为1,但c2的值不立即更新。
2. 计算d2 <= c2,结果为1(使用c2的旧值1)。
3. 在时间步结束时,c2的值保持为1,d2的值更新为1。
4. 结果:c2 = 1,d2 = 1。

通过这个例子,我们可以清楚地看到阻塞赋值和非阻塞赋值在行为上的差异。

在硬件设计中的正确使用方法

在Verilog设计中,正确使用阻塞赋值和非阻塞赋值是确保设计正确性和可靠性的关键。以下是一些使用指南和最佳实践。

基本原则

1. 组合逻辑使用阻塞赋值:在描述组合逻辑时,应使用阻塞赋值(=)。
2. 时序逻辑使用非阻塞赋值:在描述时序逻辑,特别是在时钟边沿触发的寄存器赋值时,应使用非阻塞赋值(<=)。
3. 避免在同一个always块中混合使用阻塞赋值和非阻塞赋值:这会导致仿真和综合结果不一致。

组合逻辑设计

组合逻辑是没有记忆功能的逻辑,输出只取决于当前的输入。在Verilog中,组合逻辑通常使用阻塞赋值来描述。
  1. module combinational_logic(
  2.   input a,
  3.   input b,
  4.   input c,
  5.   output reg y
  6. );
  7.   
  8.   // 使用阻塞赋值描述组合逻辑
  9.   always @(*) begin
  10.     y = (a & b) | c;
  11.   end
  12.   
  13. endmodule
复制代码

时序逻辑设计

时序逻辑是有记忆功能的逻辑,输出不仅取决于当前的输入,还取决于之前的状态。在Verilog中,时序逻辑通常使用非阻塞赋值来描述。
  1. module sequential_logic(
  2.   input clk,
  3.   input reset,
  4.   input d,
  5.   output reg q
  6. );
  7.   
  8.   // 使用非阻塞赋值描述时序逻辑
  9.   always @(posedge clk or posedge reset) begin
  10.     if (reset) begin
  11.       q <= 1'b0;  // 复位
  12.     end else begin
  13.       q <= d;     // 在时钟上升沿将d的值赋给q
  14.     end
  15.   end
  16.   
  17. endmodule
复制代码

复杂状态机设计

在设计复杂的状态机时,通常需要使用两个always块:一个用于状态寄存器的更新(时序逻辑),另一个用于组合逻辑的计算。
  1. module state_machine(
  2.   input clk,
  3.   input reset,
  4.   input x,
  5.   output reg z
  6. );
  7.   
  8.   // 定义状态
  9.   parameter S0 = 2'b00;
  10.   parameter S1 = 2'b01;
  11.   parameter S2 = 2'b10;
  12.   parameter S3 = 2'b11;
  13.   
  14.   // 状态寄存器和下一状态
  15.   reg [1:0] current_state, next_state;
  16.   
  17.   // 状态寄存器更新(时序逻辑)
  18.   always @(posedge clk or posedge reset) begin
  19.     if (reset) begin
  20.       current_state <= S0;  // 使用非阻塞赋值
  21.     end else begin
  22.       current_state <= next_state;  // 使用非阻塞赋值
  23.     end
  24.   end
  25.   
  26.   // 组合逻辑计算下一状态和输出
  27.   always @(*) begin
  28.     // 默认值
  29.     next_state = current_state;  // 使用阻塞赋值
  30.     z = 1'b0;  // 使用阻塞赋值
  31.    
  32.     case (current_state)
  33.       S0: begin
  34.         if (x) begin
  35.           next_state = S1;
  36.         end else begin
  37.           next_state = S2;
  38.         end
  39.       end
  40.       
  41.       S1: begin
  42.         z = 1'b1;
  43.         if (x) begin
  44.           next_state = S3;
  45.         end else begin
  46.           next_state = S0;
  47.         end
  48.       end
  49.       
  50.       S2: begin
  51.         if (x) begin
  52.           next_state = S0;
  53.         end else begin
  54.           next_state = S3;
  55.         end
  56.       end
  57.       
  58.       S3: begin
  59.         z = 1'b1;
  60.         if (x) begin
  61.           next_state = S2;
  62.         end else begin
  63.           next_state = S1;
  64.         end
  65.       end
  66.     endcase
  67.   end
  68.   
  69. endmodule
复制代码

时钟域交叉设计

在涉及多个时钟域的设计中,正确使用非阻塞赋值尤为重要,以避免亚稳态问题。
  1. module clock_domain_crossing(
  2.   input clk_a,
  3.   input clk_b,
  4.   input signal_a,
  5.   output reg signal_b
  6. );
  7.   
  8.   reg signal_sync1, signal_sync2;
  9.   
  10.   // 在时钟域A中捕获信号
  11.   always @(posedge clk_a) begin
  12.     signal_sync1 <= signal_a;  // 使用非阻塞赋值
  13.   end
  14.   
  15.   // 在时钟域B中同步信号
  16.   always @(posedge clk_b) begin
  17.     signal_sync2 <= signal_sync1;  // 使用非阻塞赋值
  18.     signal_b <= signal_sync2;      // 使用非阻塞赋值
  19.   end
  20.   
  21. endmodule
复制代码

最佳实践总结

1. 遵循”组合逻辑用阻塞赋值,时序逻辑用非阻塞赋值”的原则:这是最基本的规则,遵循它可以避免大多数问题。
2. 在同一个always块中不要混合使用阻塞赋值和非阻塞赋值:这会导致仿真和综合结果不一致。
3. 在敏感列表中使用@(*)来描述组合逻辑:这样可以确保所有输入都被包含在敏感列表中。
4. 为所有输出和寄存器提供默认值:这可以避免生成不必要的锁存器。
5. 使用参数和局部参数来定义常量:这可以提高代码的可读性和可维护性。
6. 使用有意义的命名约定:这可以提高代码的可读性。
7. 添加适当的注释:这可以帮助他人理解你的设计意图。

实际项目中的问题排查与解决方案

在实际项目中,由于对阻塞赋值和非阻塞赋值的理解不足或使用不当,可能会导致各种问题。本节将讨论一些常见问题及其解决方案。

问题1:仿真与综合结果不一致

问题描述:在仿真中行为正确的代码,在综合后或实际硬件中表现异常。

可能原因:在同一个always块中混合使用阻塞赋值和非阻塞赋值,或者在不适当的地方使用了阻塞赋值。

解决方案:

1. 检查代码,确保在同一个always块中不混合使用阻塞赋值和非阻塞赋值。
2. 确保在描述时序逻辑时使用非阻塞赋值,在描述组合逻辑时使用阻塞赋值。
3. 使用综合工具进行综合,并检查综合报告,确保没有意外的锁存器或逻辑。

示例:
  1. // 问题代码:在同一个always块中混合使用阻塞赋值和非阻塞赋值
  2. always @(posedge clk) begin
  3.   a <= b;  // 非阻塞赋值
  4.   c = d;   // 阻塞赋值
  5. end
  6. // 解决方案:将阻塞赋值和非阻塞赋值分开到不同的always块
  7. // 时序逻辑部分
  8. always @(posedge clk) begin
  9.   a <= b;  // 非阻塞赋值
  10. end
  11. // 组合逻辑部分
  12. always @(*) begin
  13.   c = d;   // 阻塞赋值
  14. end
复制代码

问题2:意外的锁存器生成

问题描述:在组合逻辑中,某些情况下会生成意外的锁存器,导致设计行为异常。

可能原因:在组合逻辑的always块中,没有为所有可能的输入组合提供输出值,或者在条件语句中缺少else分支。

解决方案:

1. 在组合逻辑的always块中,为所有输出提供默认值。
2. 确保所有条件语句都有完整的else分支。
3. 使用@(*)作为敏感列表,确保所有输入都被包含。

示例:
  1. // 问题代码:可能生成意外的锁存器
  2. always @(a or b or sel) begin
  3.   if (sel) begin
  4.     y = a;
  5.   end else begin
  6.     y = b;
  7.   end
  8.   // 缺少对z的赋值,可能导致生成锁存器
  9. end
  10. // 解决方案:为所有输出提供默认值
  11. always @(*) begin
  12.   // 默认值
  13.   y = 1'b0;
  14.   z = 1'b0;
  15.   
  16.   if (sel) begin
  17.     y = a;
  18.   end else begin
  19.     y = b;
  20.   end
  21.   
  22.   // 其他条件
  23.   if (enable) begin
  24.     z = a & b;
  25.   end
  26. end
复制代码

问题3:时序逻辑中的竞争条件

问题描述:在时序逻辑中,由于赋值顺序不当,导致信号之间的依赖关系不正确,产生竞争条件。

可能原因:在时序逻辑中使用了阻塞赋值,或者非阻塞赋值的使用不当。

解决方案:

1. 在时序逻辑中始终使用非阻塞赋值。
2. 确保信号之间的依赖关系正确,避免在同一时钟周期内产生循环依赖。

示例:
  1. // 问题代码:在时序逻辑中使用阻塞赋值,可能导致竞争条件
  2. always @(posedge clk) begin
  3.   a = b;  // 阻塞赋值
  4.   b = a;  // 阻塞赋值,使用的是更新后的a值
  5. end
  6. // 解决方案:在时序逻辑中使用非阻塞赋值
  7. always @(posedge clk) begin
  8.   a <= b;  // 非阻塞赋值
  9.   b <= a;  // 非阻塞赋值,使用的是a的旧值
  10. end
复制代码

问题4:时钟域交叉问题

问题描述:在多个时钟域之间传递信号时,由于时序不当,导致亚稳态问题。

可能原因:没有正确同步跨时钟域的信号,或者同步器的实现不当。

解决方案:

1. 使用两级或更多级的触发器来同步跨时钟域的信号。
2. 在同步器中使用非阻塞赋值。
3. 考虑使用握手协议或FIFO来安全地传递数据。

示例:
  1. // 问题代码:没有正确同步跨时钟域的信号
  2. module cdc_problem(
  3.   input clk_a,
  4.   input clk_b,
  5.   input signal_a,
  6.   output reg signal_b
  7. );
  8.   
  9.   always @(posedge clk_b) begin
  10.     signal_b <= signal_a;  // 直接使用跨时钟域的信号,可能导致亚稳态
  11.   end
  12.   
  13. endmodule
  14. // 解决方案:使用两级触发器同步跨时钟域的信号
  15. module cdc_solution(
  16.   input clk_a,
  17.   input clk_b,
  18.   input signal_a,
  19.   output reg signal_b
  20. );
  21.   
  22.   reg signal_sync1, signal_sync2;
  23.   
  24.   // 在时钟域A中捕获信号
  25.   always @(posedge clk_a) begin
  26.     signal_sync1 <= signal_a;  // 使用非阻塞赋值
  27.   end
  28.   
  29.   // 在时钟域B中同步信号
  30.   always @(posedge clk_b) begin
  31.     signal_sync2 <= signal_sync1;  // 使用非阻塞赋值
  32.     signal_b <= signal_sync2;      // 使用非阻塞赋值
  33.   end
  34.   
  35. endmodule
复制代码

问题5:异步复位问题

问题描述:在使用异步复位时,由于赋值方式不当,导致复位行为不正确。

可能原因:在异步复位逻辑中使用了阻塞赋值,或者复位条件不完整。

解决方案:

1. 在异步复位逻辑中使用非阻塞赋值。
2. 确保复位条件完整,包括所有可能的复位信号。

示例:
  1. // 问题代码:在异步复位逻辑中使用阻塞赋值
  2. always @(posedge clk or posedge reset) begin
  3.   if (reset) begin
  4.     q = 1'b0;  // 阻塞赋值
  5.   end else begin
  6.     q <= d;    // 非阻塞赋值
  7.   end
  8. end
  9. // 解决方案:在异步复位逻辑中使用非阻塞赋值
  10. always @(posedge clk or posedge reset) begin
  11.   if (reset) begin
  12.     q <= 1'b0;  // 非阻塞赋值
  13.   end else begin
  14.     q <= d;     // 非阻塞赋值
  15.   end
  16. end
复制代码

问题排查工具和方法

在实际项目中,使用适当的工具和方法可以帮助快速定位和解决问题:

1. 仿真工具:使用ModelSim、VCS、Xcelium等仿真工具进行功能仿真,验证设计行为。
2. 波形查看器:使用波形查看器分析信号变化,找出异常行为。
3. 代码检查工具:使用SpyGlass、Lint工具等进行静态代码检查,发现潜在问题。
4. 综合工具:使用Synplify、DC等综合工具进行综合,检查综合结果。
5. 时序分析工具:使用PrimeTime、Tempus等时序分析工具进行时序分析,确保设计满足时序要求。

提高数字电路可靠性的必备知识

在数字电路设计中,可靠性是一个至关重要的考虑因素。正确使用Verilog赋值操作可以显著提高设计的可靠性。本节将介绍一些与赋值相关的可靠性设计技术。

时序约束和时序分析

时序约束是确保设计在目标频率下正常工作的关键。通过正确设置时序约束,可以帮助综合工具和布局布线工具优化设计,满足时序要求。
  1. // 时序约束示例(SDC格式)
  2. create_clock -name clk -period 10 [get_ports clk]
  3. set_input_delay -max 2 -clock clk [get_ports data_in]
  4. set_output_delay -max 2 -clock clk [get_ports data_out]
复制代码

时序收敛技术

时序收敛是确保设计满足时序要求的过程。以下是一些常用的时序收敛技术:

1. 流水线技术:通过在组合逻辑中插入寄存器,减少关键路径的延迟。
2. 重定时技术:通过移动寄存器的位置,平衡各路径的延迟。
3. 逻辑复制:通过复制高扇出网络,减少负载电容,提高速度。
4. 逻辑优化:通过优化逻辑结构,减少逻辑级数。
  1. // 流水线技术示例
  2. module pipelined_multiplier(
  3.   input clk,
  4.   input reset,
  5.   input [7:0] a,
  6.   input [7:0] b,
  7.   output reg [15:0] result
  8. );
  9.   
  10.   reg [7:0] a_reg, b_reg;
  11.   reg [15:0] partial_result;
  12.   
  13.   // 第一级流水线:输入寄存
  14.   always @(posedge clk or posedge reset) begin
  15.     if (reset) begin
  16.       a_reg <= 8'b0;
  17.       b_reg <= 8'b0;
  18.     end else begin
  19.       a_reg <= a;
  20.       b_reg <= b;
  21.     end
  22.   end
  23.   
  24.   // 第二级流水线:部分乘积
  25.   always @(posedge clk or posedge reset) begin
  26.     if (reset) begin
  27.       partial_result <= 16'b0;
  28.     end else begin
  29.       partial_result <= a_reg * b_reg;
  30.     end
  31.   end
  32.   
  33.   // 第三级流水线:结果寄存
  34.   always @(posedge clk or posedge reset) begin
  35.     if (reset) begin
  36.       result <= 16'b0;
  37.     end else begin
  38.       result <= partial_result;
  39.     end
  40.   end
  41.   
  42. endmodule
复制代码

异步设计技术

在某些情况下,异步设计可以提高性能和功耗效率。然而,异步设计也带来了新的挑战,如竞争条件和冒险。
  1. // 异步FIFO设计示例
  2. module async_fifo(
  3.   input wclk,
  4.   input rclk,
  5.   input wrst_n,
  6.   input rrst_n,
  7.   input [7:0] wdata,
  8.   input winc,
  9.   input rinc,
  10.   output [7:0] rdata,
  11.   output wfull,
  12.   output rempty
  13. );
  14.   
  15.   // FIFO存储器
  16.   reg [7:0] memory [0:15];
  17.   
  18.   // 写指针和读指针
  19.   reg [3:0] wptr, rptr;
  20.   reg [3:0] wptr_sync1, wptr_sync2;
  21.   reg [3:0] rptr_sync1, rptr_sync2;
  22.   
  23.   // 写操作
  24.   always @(posedge wclk or negedge wrst_n) begin
  25.     if (!wrst_n) begin
  26.       wptr <= 4'b0;
  27.     end else if (winc && !wfull) begin
  28.       memory[wptr] <= wdata;
  29.       wptr <= wptr + 1;
  30.     end
  31.   end
  32.   
  33.   // 读操作
  34.   always @(posedge rclk or negedge rrst_n) begin
  35.     if (!rrst_n) begin
  36.       rptr <= 4'b0;
  37.     end else if (rinc && !rempty) begin
  38.       rdata <= memory[rptr];
  39.       rptr <= rptr + 1;
  40.     end
  41.   end
  42.   
  43.   // 同步写指针到读时钟域
  44.   always @(posedge rclk or negedge rrst_n) begin
  45.     if (!rrst_n) begin
  46.       wptr_sync1 <= 4'b0;
  47.       wptr_sync2 <= 4'b0;
  48.     end else begin
  49.       wptr_sync1 <= wptr;
  50.       wptr_sync2 <= wptr_sync1;
  51.     end
  52.   end
  53.   
  54.   // 同步读指针到写时钟域
  55.   always @(posedge wclk or negedge wrst_n) begin
  56.     if (!wrst_n) begin
  57.       rptr_sync1 <= 4'b0;
  58.       rptr_sync2 <= 4'b0;
  59.     end else begin
  60.       rptr_sync1 <= rptr;
  61.       rptr_sync2 <= rptr_sync1;
  62.     end
  63.   end
  64.   
  65.   // 生成满标志和空标志
  66.   assign wfull = (wptr == {~rptr_sync2[3:3], rptr_sync2[2:0]});
  67.   assign rempty = (rptr == wptr_sync2);
  68.   
  69. endmodule
复制代码

低功耗设计技术

低功耗设计是现代数字电路设计的重要考虑因素。以下是一些常用的低功耗设计技术:

1. 时钟门控:在不使用模块时关闭其时钟。
2. 电源门控:在不使用模块时关闭其电源。
3. 多电压域:使用不同的电压供应给不同的模块。
4. 动态电压和频率调整:根据工作负载动态调整电压和频率。
  1. // 时钟门控示例
  2. module clock_gating(
  3.   input clk,
  4.   input enable,
  5.   output reg gated_clk
  6. );
  7.   
  8.   reg enable_reg;
  9.   
  10.   always @(posedge clk) begin
  11.     enable_reg <= enable;
  12.   end
  13.   
  14.   assign gated_clk = clk & enable_reg;
  15.   
  16. endmodule
  17. // 使用时钟门控的模块
  18. module gated_module(
  19.   input clk,
  20.   input reset,
  21.   input enable,
  22.   input [7:0] data_in,
  23.   output reg [7:0] data_out
  24. );
  25.   
  26.   wire gated_clk;
  27.   
  28.   // 实例化时钟门控
  29.   clock_gating cg (
  30.     .clk(clk),
  31.     .enable(enable),
  32.     .gated_clk(gated_clk)
  33.   );
  34.   
  35.   // 使用门控时钟
  36.   always @(posedge gated_clk or posedge reset) begin
  37.     if (reset) begin
  38.       data_out <= 8'b0;
  39.     end else begin
  40.       data_out <= data_in;
  41.     end
  42.   end
  43.   
  44. endmodule
复制代码

容错设计技术

容错设计是确保系统在出现故障时仍能正常工作的技术。以下是一些常用的容错设计技术:

1. 冗余设计:使用多个相同的模块并行工作,通过投票机制确定输出。
2. 错误检测和纠正码:使用ECC码检测和纠正数据错误。
3. 自测试和自修复:设计能够检测和修复自身错误的系统。
  1. // 三模冗余示例
  2. module tmr_voter(
  3.   input a,
  4.   input b,
  5.   input c,
  6.   output reg y
  7. );
  8.   
  9.   always @(*) begin
  10.     // 多数投票
  11.     y = (a & b) | (b & c) | (a & c);
  12.   end
  13.   
  14. endmodule
  15. module tmr_system(
  16.   input clk,
  17.   input reset,
  18.   input x,
  19.   output y
  20. );
  21.   
  22.   // 三个相同的模块
  23.   wire y1, y2, y3;
  24.   
  25.   module1 m1 (.clk(clk), .reset(reset), .x(x), .y(y1));
  26.   module1 m2 (.clk(clk), .reset(reset), .x(x), .y(y2));
  27.   module1 m3 (.clk(clk), .reset(reset), .x(x), .y(y3));
  28.   
  29.   // 投票器
  30.   tmr_voter voter (.a(y1), .b(y2), .c(y3), .y(y));
  31.   
  32. endmodule
  33. // 假设的module1
  34. module module1(
  35.   input clk,
  36.   input reset,
  37.   input x,
  38.   output reg y
  39. );
  40.   
  41.   always @(posedge clk or posedge reset) begin
  42.     if (reset) begin
  43.       y <= 1'b0;
  44.     end else begin
  45.       y <= x;
  46.     end
  47.   end
  48.   
  49. endmodule
复制代码

可测试性设计技术

可测试性设计是确保系统易于测试的技术。以下是一些常用的可测试性设计技术:

1. 扫描链:将所有触发器连接成一个长链,便于测试。
2. 内建自测试:在芯片内部集成测试逻辑。
3. 边界扫描:提供对芯片引脚的控制和观测能力。
  1. // 扫描触发器示例
  2. module scan_flip_flop(
  3.   input clk,
  4.   input reset,
  5.   input se,  // 扫描使能
  6.   input si,  // 扫描输入
  7.   input d,   // 数据输入
  8.   output reg q,   // 输出
  9.   output reg so   // 扫描输出
  10. );
  11.   
  12.   always @(posedge clk or posedge reset) begin
  13.     if (reset) begin
  14.       q <= 1'b0;
  15.       so <= 1'b0;
  16.     end else if (se) begin
  17.       q <= si;
  18.       so <= si;
  19.     end else begin
  20.       q <= d;
  21.       so <= d;
  22.     end
  23.   end
  24.   
  25. endmodule
  26. // 使用扫描触发器的模块
  27. module scannable_module(
  28.   input clk,
  29.   input reset,
  30.   input se,
  31.   input si,
  32.   input [7:0] data_in,
  33.   output [7:0] data_out,
  34.   output so
  35. );
  36.   
  37.   wire [7:0] scan_chain;
  38.   
  39.   genvar i;
  40.   generate
  41.     for (i = 0; i < 8; i = i + 1) begin : scan_ffs
  42.       if (i == 0) begin
  43.         scan_flip_flop ff (
  44.           .clk(clk),
  45.           .reset(reset),
  46.           .se(se),
  47.           .si(si),
  48.           .d(data_in[i]),
  49.           .q(data_out[i]),
  50.           .so(scan_chain[i])
  51.         );
  52.       end else begin
  53.         scan_flip_flop ff (
  54.           .clk(clk),
  55.           .reset(reset),
  56.           .se(se),
  57.           .si(scan_chain[i-1]),
  58.           .d(data_in[i]),
  59.           .q(data_out[i]),
  60.           .so(scan_chain[i])
  61.         );
  62.       end
  63.     end
  64.   endgenerate
  65.   
  66.   assign so = scan_chain[7];
  67.   
  68. endmodule
复制代码

结论

Verilog中的赋值操作是数字电路设计的基础,正确理解和使用阻塞赋值和非阻塞赋值对于设计可靠、高效的数字电路至关重要。本文详细介绍了Verilog中的赋值操作,包括阻塞赋值和非阻塞赋值的概念、语法、使用场景和区别,提供了在硬件设计中的正确使用方法,分析了实际项目中可能遇到的问题及其解决方案,并分享了提高数字电路可靠性的必备知识。

通过本文的学习,您应该能够:

1. 理解阻塞赋值和非阻塞赋值的区别和适用场景。
2. 在硬件设计中正确使用阻塞赋值和非阻塞赋值。
3. 识别和解决与赋值相关的常见问题。
4. 应用可靠性设计技术提高数字电路的可靠性。

在实际项目中,遵循”组合逻辑用阻塞赋值,时序逻辑用非阻塞赋值”的原则,避免在同一个always块中混合使用阻塞赋值和非阻塞赋值,可以避免大多数与赋值相关的问题。同时,应用时序约束、时序收敛技术、异步设计技术、低功耗设计技术、容错设计技术和可测试性设计技术,可以显著提高数字电路的可靠性。

希望本文能够帮助您更好地理解和应用Verilog中的赋值操作,设计出更加可靠、高效的数字电路。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

联系我们|小黑屋|TG频道|RSS |网站地图

Powered by Pixtech

© 2025-2026 Pixtech Team.

>