|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
Verilog作为硬件描述语言(HDL)的一种,在现代数字电路设计中扮演着至关重要的角色。在Verilog中,赋值操作是最基本也是最重要的操作之一,它直接影响着电路的行为和性能。正确理解和使用Verilog中的赋值操作,特别是阻塞赋值(Blocking Assignment)和非阻塞赋值(Non-blocking Assignment),对于设计可靠、高效的数字电路至关重要。
本文将全面介绍Verilog中的赋值操作,深入探讨阻塞赋值和非阻塞赋值的区别,提供在硬件设计中的正确使用方法,分析实际项目中可能遇到的问题及其解决方案,并分享提高数字电路可靠性的必备知识。无论您是Verilog初学者还是有经验的设计工程师,本文都能帮助您更好地理解和应用Verilog赋值操作。
Verilog赋值基础
在Verilog中,赋值操作用于将值赋给变量或网络(net)。根据赋值发生的时间和方式,Verilog中的赋值主要可以分为两大类:连续赋值(Continuous Assignment)和过程赋值(Procedural Assignment)。
连续赋值
连续赋值使用assign关键字,主要用于组合逻辑电路的描述。连续赋值在右侧表达式发生变化时立即执行,并且没有时序控制。
- // 连续赋值示例
- assign y = a & b; // 当a或b发生变化时,y立即更新
复制代码
过程赋值
过程赋值发生在always块或initial块中,可以分为阻塞赋值和非阻塞赋值两种。
- // 过程赋值示例
- always @(posedge clk) begin
- // 阻塞赋值
- b = a;
-
- // 非阻塞赋值
- c <= a;
- end
复制代码
在接下来的章节中,我们将重点讨论过程赋值中的阻塞赋值和非阻塞赋值,它们是Verilog设计中最关键也最容易混淆的概念。
阻塞赋值(Blocking Assignment)
阻塞赋值使用=符号表示,它在执行时会阻塞后续语句的执行,直到当前赋值操作完成。阻塞赋值的行为类似于顺序编程语言中的赋值操作,执行顺序严格按照代码的书写顺序。
基本语法
执行特点
1. 顺序执行:阻塞赋值按照代码中的顺序依次执行。
2. 立即更新:赋值操作完成后,左侧变量的值立即更新。
3. 阻塞后续语句:在当前赋值操作完成之前,不会执行下一条语句。
使用场景
阻塞赋值主要用于描述组合逻辑,因为在组合逻辑中,我们希望信号的变化能够立即传播。
示例代码
- module blocking_example(
- input clk,
- input a,
- input b,
- output reg c,
- output reg d
- );
-
- always @(posedge clk) begin
- // 阻塞赋值示例
- c = a & b; // 先执行
- d = c; // 后执行,使用的是更新后的c值
- end
-
- 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. 并行计算:所有非阻塞赋值语句在同一时间步内并行计算右侧表达式。
2. 延迟更新:左侧变量的值在时间步结束时才更新。
3. 不阻塞后续语句:当前非阻塞赋值语句不会阻塞后续语句的执行。
使用场景
非阻塞赋值主要用于描述时序逻辑,特别是在时钟边沿触发的寄存器赋值中。
示例代码
- module non_blocking_example(
- input clk,
- input a,
- input b,
- output reg c,
- output reg d
- );
-
- always @(posedge clk) begin
- // 非阻塞赋值示例
- c <= a & b; // 并行计算
- d <= c; // 并行计算,使用的是c的旧值
- end
-
- 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中有着本质的区别,理解这些区别对于正确设计数字电路至关重要。
执行方式
• 阻塞赋值:顺序执行,一条语句执行完后才执行下一条语句。
• 非阻塞赋值:并行执行,所有语句在同一时间步内计算,但在时间步结束时才更新值。
值的更新时间
• 阻塞赋值:赋值语句执行后立即更新值。
• 非阻塞赋值:在时间步结束时才更新值。
对后续语句的影响
• 阻塞赋值:会阻塞后续语句的执行,直到当前赋值完成。
• 非阻塞赋值:不会阻塞后续语句的执行。
对应的硬件实现
• 阻塞赋值:通常对应组合逻辑,没有寄存器。
• 非阻塞赋值:通常对应时序逻辑,有寄存器。
示例对比
让我们通过一个具体的例子来对比阻塞赋值和非阻塞赋值的区别:
- module assignment_comparison(
- input clk,
- input a,
- input b,
- output reg c1, d1, // 阻塞赋值输出
- output reg c2, d2 // 非阻塞赋值输出
- );
-
- // 阻塞赋值示例
- always @(posedge clk) begin
- c1 = a & b; // 先执行
- d1 = c1; // 后执行,使用更新后的c1值
- end
-
- // 非阻塞赋值示例
- always @(posedge clk) begin
- c2 <= a & b; // 并行计算
- d2 <= c2; // 并行计算,使用c2的旧值
- end
-
- 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中,组合逻辑通常使用阻塞赋值来描述。
- module combinational_logic(
- input a,
- input b,
- input c,
- output reg y
- );
-
- // 使用阻塞赋值描述组合逻辑
- always @(*) begin
- y = (a & b) | c;
- end
-
- endmodule
复制代码
时序逻辑设计
时序逻辑是有记忆功能的逻辑,输出不仅取决于当前的输入,还取决于之前的状态。在Verilog中,时序逻辑通常使用非阻塞赋值来描述。
- module sequential_logic(
- input clk,
- input reset,
- input d,
- output reg q
- );
-
- // 使用非阻塞赋值描述时序逻辑
- always @(posedge clk or posedge reset) begin
- if (reset) begin
- q <= 1'b0; // 复位
- end else begin
- q <= d; // 在时钟上升沿将d的值赋给q
- end
- end
-
- endmodule
复制代码
复杂状态机设计
在设计复杂的状态机时,通常需要使用两个always块:一个用于状态寄存器的更新(时序逻辑),另一个用于组合逻辑的计算。
- module state_machine(
- input clk,
- input reset,
- input x,
- output reg z
- );
-
- // 定义状态
- parameter S0 = 2'b00;
- parameter S1 = 2'b01;
- parameter S2 = 2'b10;
- parameter S3 = 2'b11;
-
- // 状态寄存器和下一状态
- reg [1:0] current_state, next_state;
-
- // 状态寄存器更新(时序逻辑)
- always @(posedge clk or posedge reset) begin
- if (reset) begin
- current_state <= S0; // 使用非阻塞赋值
- end else begin
- current_state <= next_state; // 使用非阻塞赋值
- end
- end
-
- // 组合逻辑计算下一状态和输出
- always @(*) begin
- // 默认值
- next_state = current_state; // 使用阻塞赋值
- z = 1'b0; // 使用阻塞赋值
-
- case (current_state)
- S0: begin
- if (x) begin
- next_state = S1;
- end else begin
- next_state = S2;
- end
- end
-
- S1: begin
- z = 1'b1;
- if (x) begin
- next_state = S3;
- end else begin
- next_state = S0;
- end
- end
-
- S2: begin
- if (x) begin
- next_state = S0;
- end else begin
- next_state = S3;
- end
- end
-
- S3: begin
- z = 1'b1;
- if (x) begin
- next_state = S2;
- end else begin
- next_state = S1;
- end
- end
- endcase
- end
-
- endmodule
复制代码
时钟域交叉设计
在涉及多个时钟域的设计中,正确使用非阻塞赋值尤为重要,以避免亚稳态问题。
- module clock_domain_crossing(
- input clk_a,
- input clk_b,
- input signal_a,
- output reg signal_b
- );
-
- reg signal_sync1, signal_sync2;
-
- // 在时钟域A中捕获信号
- always @(posedge clk_a) begin
- signal_sync1 <= signal_a; // 使用非阻塞赋值
- end
-
- // 在时钟域B中同步信号
- always @(posedge clk_b) begin
- signal_sync2 <= signal_sync1; // 使用非阻塞赋值
- signal_b <= signal_sync2; // 使用非阻塞赋值
- end
-
- endmodule
复制代码
最佳实践总结
1. 遵循”组合逻辑用阻塞赋值,时序逻辑用非阻塞赋值”的原则:这是最基本的规则,遵循它可以避免大多数问题。
2. 在同一个always块中不要混合使用阻塞赋值和非阻塞赋值:这会导致仿真和综合结果不一致。
3. 在敏感列表中使用@(*)来描述组合逻辑:这样可以确保所有输入都被包含在敏感列表中。
4. 为所有输出和寄存器提供默认值:这可以避免生成不必要的锁存器。
5. 使用参数和局部参数来定义常量:这可以提高代码的可读性和可维护性。
6. 使用有意义的命名约定:这可以提高代码的可读性。
7. 添加适当的注释:这可以帮助他人理解你的设计意图。
实际项目中的问题排查与解决方案
在实际项目中,由于对阻塞赋值和非阻塞赋值的理解不足或使用不当,可能会导致各种问题。本节将讨论一些常见问题及其解决方案。
问题1:仿真与综合结果不一致
问题描述:在仿真中行为正确的代码,在综合后或实际硬件中表现异常。
可能原因:在同一个always块中混合使用阻塞赋值和非阻塞赋值,或者在不适当的地方使用了阻塞赋值。
解决方案:
1. 检查代码,确保在同一个always块中不混合使用阻塞赋值和非阻塞赋值。
2. 确保在描述时序逻辑时使用非阻塞赋值,在描述组合逻辑时使用阻塞赋值。
3. 使用综合工具进行综合,并检查综合报告,确保没有意外的锁存器或逻辑。
示例:
- // 问题代码:在同一个always块中混合使用阻塞赋值和非阻塞赋值
- always @(posedge clk) begin
- a <= b; // 非阻塞赋值
- c = d; // 阻塞赋值
- end
- // 解决方案:将阻塞赋值和非阻塞赋值分开到不同的always块
- // 时序逻辑部分
- always @(posedge clk) begin
- a <= b; // 非阻塞赋值
- end
- // 组合逻辑部分
- always @(*) begin
- c = d; // 阻塞赋值
- end
复制代码
问题2:意外的锁存器生成
问题描述:在组合逻辑中,某些情况下会生成意外的锁存器,导致设计行为异常。
可能原因:在组合逻辑的always块中,没有为所有可能的输入组合提供输出值,或者在条件语句中缺少else分支。
解决方案:
1. 在组合逻辑的always块中,为所有输出提供默认值。
2. 确保所有条件语句都有完整的else分支。
3. 使用@(*)作为敏感列表,确保所有输入都被包含。
示例:
- // 问题代码:可能生成意外的锁存器
- always @(a or b or sel) begin
- if (sel) begin
- y = a;
- end else begin
- y = b;
- end
- // 缺少对z的赋值,可能导致生成锁存器
- end
- // 解决方案:为所有输出提供默认值
- always @(*) begin
- // 默认值
- y = 1'b0;
- z = 1'b0;
-
- if (sel) begin
- y = a;
- end else begin
- y = b;
- end
-
- // 其他条件
- if (enable) begin
- z = a & b;
- end
- end
复制代码
问题3:时序逻辑中的竞争条件
问题描述:在时序逻辑中,由于赋值顺序不当,导致信号之间的依赖关系不正确,产生竞争条件。
可能原因:在时序逻辑中使用了阻塞赋值,或者非阻塞赋值的使用不当。
解决方案:
1. 在时序逻辑中始终使用非阻塞赋值。
2. 确保信号之间的依赖关系正确,避免在同一时钟周期内产生循环依赖。
示例:
- // 问题代码:在时序逻辑中使用阻塞赋值,可能导致竞争条件
- always @(posedge clk) begin
- a = b; // 阻塞赋值
- b = a; // 阻塞赋值,使用的是更新后的a值
- end
- // 解决方案:在时序逻辑中使用非阻塞赋值
- always @(posedge clk) begin
- a <= b; // 非阻塞赋值
- b <= a; // 非阻塞赋值,使用的是a的旧值
- end
复制代码
问题4:时钟域交叉问题
问题描述:在多个时钟域之间传递信号时,由于时序不当,导致亚稳态问题。
可能原因:没有正确同步跨时钟域的信号,或者同步器的实现不当。
解决方案:
1. 使用两级或更多级的触发器来同步跨时钟域的信号。
2. 在同步器中使用非阻塞赋值。
3. 考虑使用握手协议或FIFO来安全地传递数据。
示例:
- // 问题代码:没有正确同步跨时钟域的信号
- module cdc_problem(
- input clk_a,
- input clk_b,
- input signal_a,
- output reg signal_b
- );
-
- always @(posedge clk_b) begin
- signal_b <= signal_a; // 直接使用跨时钟域的信号,可能导致亚稳态
- end
-
- endmodule
- // 解决方案:使用两级触发器同步跨时钟域的信号
- module cdc_solution(
- input clk_a,
- input clk_b,
- input signal_a,
- output reg signal_b
- );
-
- reg signal_sync1, signal_sync2;
-
- // 在时钟域A中捕获信号
- always @(posedge clk_a) begin
- signal_sync1 <= signal_a; // 使用非阻塞赋值
- end
-
- // 在时钟域B中同步信号
- always @(posedge clk_b) begin
- signal_sync2 <= signal_sync1; // 使用非阻塞赋值
- signal_b <= signal_sync2; // 使用非阻塞赋值
- end
-
- endmodule
复制代码
问题5:异步复位问题
问题描述:在使用异步复位时,由于赋值方式不当,导致复位行为不正确。
可能原因:在异步复位逻辑中使用了阻塞赋值,或者复位条件不完整。
解决方案:
1. 在异步复位逻辑中使用非阻塞赋值。
2. 确保复位条件完整,包括所有可能的复位信号。
示例:
- // 问题代码:在异步复位逻辑中使用阻塞赋值
- 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) begin
- if (reset) begin
- q <= 1'b0; // 非阻塞赋值
- end else begin
- q <= d; // 非阻塞赋值
- end
- end
复制代码
问题排查工具和方法
在实际项目中,使用适当的工具和方法可以帮助快速定位和解决问题:
1. 仿真工具:使用ModelSim、VCS、Xcelium等仿真工具进行功能仿真,验证设计行为。
2. 波形查看器:使用波形查看器分析信号变化,找出异常行为。
3. 代码检查工具:使用SpyGlass、Lint工具等进行静态代码检查,发现潜在问题。
4. 综合工具:使用Synplify、DC等综合工具进行综合,检查综合结果。
5. 时序分析工具:使用PrimeTime、Tempus等时序分析工具进行时序分析,确保设计满足时序要求。
提高数字电路可靠性的必备知识
在数字电路设计中,可靠性是一个至关重要的考虑因素。正确使用Verilog赋值操作可以显著提高设计的可靠性。本节将介绍一些与赋值相关的可靠性设计技术。
时序约束和时序分析
时序约束是确保设计在目标频率下正常工作的关键。通过正确设置时序约束,可以帮助综合工具和布局布线工具优化设计,满足时序要求。
- // 时序约束示例(SDC格式)
- create_clock -name clk -period 10 [get_ports clk]
- set_input_delay -max 2 -clock clk [get_ports data_in]
- set_output_delay -max 2 -clock clk [get_ports data_out]
复制代码
时序收敛技术
时序收敛是确保设计满足时序要求的过程。以下是一些常用的时序收敛技术:
1. 流水线技术:通过在组合逻辑中插入寄存器,减少关键路径的延迟。
2. 重定时技术:通过移动寄存器的位置,平衡各路径的延迟。
3. 逻辑复制:通过复制高扇出网络,减少负载电容,提高速度。
4. 逻辑优化:通过优化逻辑结构,减少逻辑级数。
- // 流水线技术示例
- module pipelined_multiplier(
- input clk,
- input reset,
- input [7:0] a,
- input [7:0] b,
- output reg [15:0] result
- );
-
- reg [7:0] a_reg, b_reg;
- reg [15:0] partial_result;
-
- // 第一级流水线:输入寄存
- always @(posedge clk or posedge reset) begin
- if (reset) begin
- a_reg <= 8'b0;
- b_reg <= 8'b0;
- end else begin
- a_reg <= a;
- b_reg <= b;
- end
- end
-
- // 第二级流水线:部分乘积
- always @(posedge clk or posedge reset) begin
- if (reset) begin
- partial_result <= 16'b0;
- end else begin
- partial_result <= a_reg * b_reg;
- end
- end
-
- // 第三级流水线:结果寄存
- always @(posedge clk or posedge reset) begin
- if (reset) begin
- result <= 16'b0;
- end else begin
- result <= partial_result;
- end
- end
-
- endmodule
复制代码
异步设计技术
在某些情况下,异步设计可以提高性能和功耗效率。然而,异步设计也带来了新的挑战,如竞争条件和冒险。
- // 异步FIFO设计示例
- module async_fifo(
- input wclk,
- input rclk,
- input wrst_n,
- input rrst_n,
- input [7:0] wdata,
- input winc,
- input rinc,
- output [7:0] rdata,
- output wfull,
- output rempty
- );
-
- // FIFO存储器
- reg [7:0] memory [0:15];
-
- // 写指针和读指针
- reg [3:0] wptr, rptr;
- reg [3:0] wptr_sync1, wptr_sync2;
- reg [3:0] rptr_sync1, rptr_sync2;
-
- // 写操作
- always @(posedge wclk or negedge wrst_n) begin
- if (!wrst_n) begin
- wptr <= 4'b0;
- end else if (winc && !wfull) begin
- memory[wptr] <= wdata;
- wptr <= wptr + 1;
- end
- end
-
- // 读操作
- always @(posedge rclk or negedge rrst_n) begin
- if (!rrst_n) begin
- rptr <= 4'b0;
- end else if (rinc && !rempty) begin
- rdata <= memory[rptr];
- rptr <= rptr + 1;
- end
- end
-
- // 同步写指针到读时钟域
- always @(posedge rclk or negedge rrst_n) begin
- if (!rrst_n) begin
- wptr_sync1 <= 4'b0;
- wptr_sync2 <= 4'b0;
- end else begin
- wptr_sync1 <= wptr;
- wptr_sync2 <= wptr_sync1;
- end
- end
-
- // 同步读指针到写时钟域
- always @(posedge wclk or negedge wrst_n) begin
- if (!wrst_n) begin
- rptr_sync1 <= 4'b0;
- rptr_sync2 <= 4'b0;
- end else begin
- rptr_sync1 <= rptr;
- rptr_sync2 <= rptr_sync1;
- end
- end
-
- // 生成满标志和空标志
- assign wfull = (wptr == {~rptr_sync2[3:3], rptr_sync2[2:0]});
- assign rempty = (rptr == wptr_sync2);
-
- endmodule
复制代码
低功耗设计技术
低功耗设计是现代数字电路设计的重要考虑因素。以下是一些常用的低功耗设计技术:
1. 时钟门控:在不使用模块时关闭其时钟。
2. 电源门控:在不使用模块时关闭其电源。
3. 多电压域:使用不同的电压供应给不同的模块。
4. 动态电压和频率调整:根据工作负载动态调整电压和频率。
- // 时钟门控示例
- module clock_gating(
- input clk,
- input enable,
- output reg gated_clk
- );
-
- reg enable_reg;
-
- always @(posedge clk) begin
- enable_reg <= enable;
- end
-
- assign gated_clk = clk & enable_reg;
-
- endmodule
- // 使用时钟门控的模块
- module gated_module(
- input clk,
- input reset,
- input enable,
- input [7:0] data_in,
- output reg [7:0] data_out
- );
-
- wire gated_clk;
-
- // 实例化时钟门控
- clock_gating cg (
- .clk(clk),
- .enable(enable),
- .gated_clk(gated_clk)
- );
-
- // 使用门控时钟
- always @(posedge gated_clk or posedge reset) begin
- if (reset) begin
- data_out <= 8'b0;
- end else begin
- data_out <= data_in;
- end
- end
-
- endmodule
复制代码
容错设计技术
容错设计是确保系统在出现故障时仍能正常工作的技术。以下是一些常用的容错设计技术:
1. 冗余设计:使用多个相同的模块并行工作,通过投票机制确定输出。
2. 错误检测和纠正码:使用ECC码检测和纠正数据错误。
3. 自测试和自修复:设计能够检测和修复自身错误的系统。
- // 三模冗余示例
- module tmr_voter(
- input a,
- input b,
- input c,
- output reg y
- );
-
- always @(*) begin
- // 多数投票
- y = (a & b) | (b & c) | (a & c);
- end
-
- endmodule
- module tmr_system(
- input clk,
- input reset,
- input x,
- output y
- );
-
- // 三个相同的模块
- wire y1, y2, y3;
-
- module1 m1 (.clk(clk), .reset(reset), .x(x), .y(y1));
- module1 m2 (.clk(clk), .reset(reset), .x(x), .y(y2));
- module1 m3 (.clk(clk), .reset(reset), .x(x), .y(y3));
-
- // 投票器
- tmr_voter voter (.a(y1), .b(y2), .c(y3), .y(y));
-
- endmodule
- // 假设的module1
- module module1(
- input clk,
- input reset,
- input x,
- output reg y
- );
-
- always @(posedge clk or posedge reset) begin
- if (reset) begin
- y <= 1'b0;
- end else begin
- y <= x;
- end
- end
-
- endmodule
复制代码
可测试性设计技术
可测试性设计是确保系统易于测试的技术。以下是一些常用的可测试性设计技术:
1. 扫描链:将所有触发器连接成一个长链,便于测试。
2. 内建自测试:在芯片内部集成测试逻辑。
3. 边界扫描:提供对芯片引脚的控制和观测能力。
- // 扫描触发器示例
- module scan_flip_flop(
- input clk,
- input reset,
- input se, // 扫描使能
- input si, // 扫描输入
- input d, // 数据输入
- output reg q, // 输出
- output reg so // 扫描输出
- );
-
- always @(posedge clk or posedge reset) begin
- if (reset) begin
- q <= 1'b0;
- so <= 1'b0;
- end else if (se) begin
- q <= si;
- so <= si;
- end else begin
- q <= d;
- so <= d;
- end
- end
-
- endmodule
- // 使用扫描触发器的模块
- module scannable_module(
- input clk,
- input reset,
- input se,
- input si,
- input [7:0] data_in,
- output [7:0] data_out,
- output so
- );
-
- wire [7:0] scan_chain;
-
- genvar i;
- generate
- for (i = 0; i < 8; i = i + 1) begin : scan_ffs
- if (i == 0) begin
- scan_flip_flop ff (
- .clk(clk),
- .reset(reset),
- .se(se),
- .si(si),
- .d(data_in[i]),
- .q(data_out[i]),
- .so(scan_chain[i])
- );
- end else begin
- scan_flip_flop ff (
- .clk(clk),
- .reset(reset),
- .se(se),
- .si(scan_chain[i-1]),
- .d(data_in[i]),
- .q(data_out[i]),
- .so(scan_chain[i])
- );
- end
- end
- endgenerate
-
- assign so = scan_chain[7];
-
- endmodule
复制代码
结论
Verilog中的赋值操作是数字电路设计的基础,正确理解和使用阻塞赋值和非阻塞赋值对于设计可靠、高效的数字电路至关重要。本文详细介绍了Verilog中的赋值操作,包括阻塞赋值和非阻塞赋值的概念、语法、使用场景和区别,提供了在硬件设计中的正确使用方法,分析了实际项目中可能遇到的问题及其解决方案,并分享了提高数字电路可靠性的必备知识。
通过本文的学习,您应该能够:
1. 理解阻塞赋值和非阻塞赋值的区别和适用场景。
2. 在硬件设计中正确使用阻塞赋值和非阻塞赋值。
3. 识别和解决与赋值相关的常见问题。
4. 应用可靠性设计技术提高数字电路的可靠性。
在实际项目中,遵循”组合逻辑用阻塞赋值,时序逻辑用非阻塞赋值”的原则,避免在同一个always块中混合使用阻塞赋值和非阻塞赋值,可以避免大多数与赋值相关的问题。同时,应用时序约束、时序收敛技术、异步设计技术、低功耗设计技术、容错设计技术和可测试性设计技术,可以显著提高数字电路的可靠性。
希望本文能够帮助您更好地理解和应用Verilog中的赋值操作,设计出更加可靠、高效的数字电路。
版权声明
1、转载或引用本网站内容(Verilog输出赋值完整学习指南 覆盖所有关键概念包括阻塞赋值和非阻塞赋值的区别 在硬件设计中的正确使用方法 实际项目中的问题排查与解决方案 提高数字电路可靠性的必备知识)须注明原网址及作者(威震华夏关云长),并标明本网站网址(https://pixtech.org/)。
2、对于不当转载或引用本网站内容而引起的民事纷争、行政处理或其他损失,本网站不承担责任。
3、对不遵守本声明或其他违法、恶意使用本网站内容者,本网站保留追究其法律责任的权利。
本文地址: https://pixtech.org/thread-41233-1-1.html
|
|