11verilog过程赋值
Verilog过程赋值详解
📑 目录
- 1. 过程赋值概述
- 2. 阻塞赋值详解
- 3. 非阻塞赋值详解
- 4. 赋值类型对比分析
- 5. 竞争与冒险问题
- 6. 时序建模最佳实践
- 7. 综合性考虑
- 8. 实际应用案例
- 9. 常见错误与调试
- 10. 高级主题
1. 过程赋值概述
1.1 什么是过程赋值
过程赋值(Procedural Assignment)是在过程块(initial或always语句块)中对变量进行赋值的操作,是Verilog HDL中实现时序逻辑和复杂组合逻辑的核心机制。
1.2 过程赋值的特点
- 🎯 执行时机: 只有在语句执行时才起作用
- 📊 目标类型: 只能赋值给reg类型变量
- 💾 状态保持: 赋值后保持该值直到重新赋值
- ⏱️ 时序控制: 可以控制赋值的时序特性
1.3 过程赋值 vs 连续赋值
特性 | 过程赋值 | 连续赋值 |
---|---|---|
使用位置 | initial/always块内 | 模块内部 |
目标类型 | reg类型 | wire类型 |
执行特性 | 语句执行时生效 | 始终激活 |
关键字 | = 或 <= |
assign |
应用场景 | 时序/复杂组合逻辑 | 简单组合逻辑 |
1.4 过程赋值分类
graph TD
A[过程赋值] --> B[阻塞赋值 =]
A --> C[非阻塞赋值 <=]
B --> B1[顺序执行]
B --> B2[立即更新]
B --> B3[组合逻辑]
C --> C1[并行调度]
C --> C2[延迟更新]
C --> C3[时序逻辑]
A --> D[应用场景]
D --> D1[组合逻辑建模]
D --> D2[时序逻辑建模]
D --> D3[状态机设计]
2. 阻塞赋值详解
2.1 基本语法与特性
2.1.1 语法格式
变量名 = 表达式;
2.1.2 执行特性
- ⏱️ 顺序执行: 当前语句完成后才执行下一条语句
- 🔄 立即更新: 赋值立即生效,后续语句可以使用新值
- 📝 仿真特性: 在Delta时间内完成赋值
- 🎯 用途: 主要用于组合逻辑建模
2.2 基本应用示例
2.2.1 简单阻塞赋值
module blocking_basic_example;
reg [3:0] a, b, c, d;
initial begin
// 阻塞赋值序列
a = 4'b0001; // a立即变为1
b = a + 1; // b使用a的新值,b=2
c = b + a; // c使用a和b的新值,c=3
d = c * 2; // d使用c的新值,d=6
$display("Results: a=%d, b=%d, c=%d, d=%d", a, b, c, d);
// 输出: Results: a=1, b=2, c=3, d=6
end
endmodule
2.2.2 时序控制的阻塞赋值
module blocking_with_timing;
reg [7:0] data;
reg clk;
initial begin
data = 8'h00;
clk = 0;
// 带时序控制的阻塞赋值
#10 data = 8'hAA; // 10ns后赋值
#20 data = 8'h55; // 再过20ns后赋值
#30 data = 8'hFF; // 再过30ns后赋值
$display("Final data value: %h at time %0t", data, $time);
end
endmodule
2.3 组合逻辑建模
2.3.1 ALU设计示例
module alu_blocking_design(
input wire [7:0] a, b,
input wire [2:0] op_code,
output reg [7:0] result,
output reg zero_flag, carry_flag
);
always @(*) begin
// 使用阻塞赋值建模组合逻辑
carry_flag = 1'b0; // 默认值
case (op_code)
3'b000: result = a + b; // 加法
3'b001: result = a - b; // 减法
3'b010: result = a & b; // 逻辑与
3'b011: result = a | b; // 逻辑或
3'b100: result = a ^ b; // 逻辑异或
3'b101: result = ~a; // 逻辑非
3'b110: {carry_flag, result} = a + b; // 带进位加法
3'b111: result = a << 1; // 左移
default: result = 8'h00;
endcase
// 零标志检测
zero_flag = (result == 8'h00);
end
endmodule
2.3.2 多路选择器
module mux_blocking_design #(
parameter DATA_WIDTH = 8,
parameter SEL_WIDTH = 2
)(
input wire [DATA_WIDTH-1:0] data_in [0:(1<<SEL_WIDTH)-1],
input wire [SEL_WIDTH-1:0] sel,
output reg [DATA_WIDTH-1:0] data_out
);
integer i;
always @(*) begin
// 默认值
data_out = {DATA_WIDTH{1'b0}};
// 使用阻塞赋值的选择逻辑
for (i = 0; i < (1<<SEL_WIDTH); i = i + 1) begin
if (sel == i) begin
data_out = data_in[i];
end
end
end
endmodule
2.4 阻塞赋值的时序特性
2.4.1 执行顺序演示
module blocking_order_demo;
reg [3:0] x, y, z;
initial begin
$monitor("Time=%0t: x=%d, y=%d, z=%d", $time, x, y, z);
x = 1;
y = x + 1; // y = 2 (使用x的新值)
z = y + x; // z = 3 (使用x和y的新值)
#10;
x = 5;
y = x * 2; // y = 10
z = y - x; // z = 5
end
endmodule
3. 非阻塞赋值详解
3.1 基本语法与特性
3.1.1 语法格式
变量名 <= 表达式;
3.1.2 执行特性
- ⚡ 并行调度: 所有非阻塞赋值同时调度
- ⏰ 延迟更新: 在当前时间片结束时统一更新
- 🔄 使用旧值: 表达式计算使用变量的当前值
- 🎯 用途: 主要用于时序逻辑建模
3.2 基本应用示例
3.2.1 简单非阻塞赋值
module nonblocking_basic_example;
reg [3:0] a, b, c;
initial begin
a = 4'b0001;
b = 4'b0010;
c = 4'b0011;
$monitor("Time=%0t: a=%d, b=%d, c=%d", $time, a, b, c);
// 非阻塞赋值:并行调度
#10;
a <= b; // a将获得b的当前值(2)
b <= c; // b将获得c的当前值(3)
c <= a; // c将获得a的当前值(1)
// 在这个时间点,所有赋值同时生效
#1 $display("After NBA: a=%d, b=%d, c=%d", a, b, c);
// 输出: After NBA: a=2, b=3, c=1
end
endmodule
3.3 时序逻辑建模
3.3.1 D触发器
module d_flipflop(
input wire clk, rst_n, d,
output reg q, q_n
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
q <= 1'b0;
q_n <= 1'b1;
end else begin
q <= d;
q_n <= ~d;
end
end
endmodule
3.3.2 移位寄存器
module shift_register #(
parameter WIDTH = 8
)(
input wire clk, rst_n, serial_in, shift_en,
output reg [WIDTH-1:0] parallel_out,
output wire serial_out
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
parallel_out <= {WIDTH{1'b0}};
end else if (shift_en) begin
// 非阻塞赋值确保正确的移位操作
parallel_out <= {parallel_out[WIDTH-2:0], serial_in};
end
end
assign serial_out = parallel_out[WIDTH-1];
endmodule
3.3.3 计数器链
module counter_chain(
input wire clk, rst_n, enable,
output reg [7:0] count0, count1, count2
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
count0 <= 8'h00;
count1 <= 8'h00;
count2 <= 8'h00;
end else if (enable) begin
// 非阻塞赋值创建正确的计数器链
count0 <= count0 + 1;
count1 <= count0; // 使用count0的旧值
count2 <= count1; // 使用count1的旧值
end
end
endmodule
3.4 非阻塞赋值的调度机制
3.4.1 调度演示
module nba_scheduling_demo;
reg clk;
reg [3:0] a, b, c, d;
initial begin
clk = 0;
forever #5 clk = ~clk;
end
initial begin
a = 1; b = 2; c = 3; d = 4;
$monitor("Time=%0t: a=%d, b=%d, c=%d, d=%d", $time, a, b, c, d);
end
always @(posedge clk) begin
// 所有非阻塞赋值使用当前值计算,然后同时更新
a <= b; // a将得到2
b <= c; // b将得到3
c <= d; // c将得到4
d <= a; // d将得到1
$display(" During evaluation: a=%d, b=%d, c=%d, d=%d", a, b, c, d);
end
endmodule
4. 赋值类型对比分析
4.1 执行行为对比
4.1.1 相同逻辑的不同实现
module assignment_comparison;
reg clk;
reg [3:0] a_block, b_block, c_block; // 阻塞赋值版本
reg [3:0] a_nonblock, b_nonblock, c_nonblock; // 非阻塞赋值版本
initial begin
clk = 0;
forever #10 clk = ~clk;
end
initial begin
// 初始化
a_block = 1; b_block = 2; c_block = 3;
a_nonblock = 1; b_nonblock = 2; c_nonblock = 3;
$monitor("Time=%0t: Block: a=%d,b=%d,c=%d | NonBlock: a=%d,b=%d,c=%d",
$time, a_block, b_block, c_block, a_nonblock, b_nonblock, c_nonblock);
end
// 阻塞赋值版本
always @(posedge clk) begin
a_block = b_block; // a得到b的新值
b_block = c_block; // b得到c的新值
c_block = a_block; // c得到a的新值(已更新)
end
// 非阻塞赋值版本
always @(posedge clk) begin
a_nonblock <= b_nonblock; // a得到b的旧值
b_nonblock <= c_nonblock; // b得到c的旧值
c_nonblock <= a_nonblock; // c得到a的旧值
end
endmodule
4.2 应用场景对比
4.2.1 组合逻辑:阻塞赋值
module combinational_logic_correct;
input wire [7:0] a, b, c;
input wire [1:0] sel;
output reg [7:0] result;
always @(*) begin
// ✅ 组合逻辑应该使用阻塞赋值
case (sel)
2'b00: result = a;
2'b01: result = b;
2'b10: result = c;
2'b11: result = a + b + c;
endcase
end
endmodule
4.2.2 时序逻辑:非阻塞赋值
module sequential_logic_correct;
input wire clk, rst_n;
input wire [7:0] data_in;
output reg [7:0] pipe_stage1, pipe_stage2, pipe_stage3;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// ✅ 时序逻辑应该使用非阻塞赋值
pipe_stage1 <= 8'h00;
pipe_stage2 <= 8'h00;
pipe_stage3 <= 8'h00;
end else begin
pipe_stage1 <= data_in;
pipe_stage2 <= pipe_stage1;
pipe_stage3 <= pipe_stage2;
end
end
endmodule
4.3 混合使用的问题
4.3.1 错误的混合使用
// ❌ 错误示例:混合使用导致意外结果
module mixed_assignment_bad;
reg clk, a, b, c;
always @(posedge clk) begin
a <= 1'b1; // 非阻塞:a在时钟边沿后更新
b = a; // 阻塞:b立即使用a的当前值(旧值)
c <= b; // 非阻塞:c使用b的新值
end
endmodule
// ✅ 正确示例:统一使用非阻塞赋值
module mixed_assignment_good;
reg clk, a, b, c;
always @(posedge clk) begin
a <= 1'b1;
b <= a; // 统一使用非阻塞赋值
c <= b;
end
endmodule
5. 竞争与冒险问题
5.1 竞争条件的产生
5.1.1 阻塞赋值引起的竞争
// ❌ 问题代码:阻塞赋值导致的竞争
module race_condition_blocking_bad;
reg clk, a, b, c;
// 过程1
always @(posedge clk) begin
a = b; // a立即获得b的值
c = a; // c获得a的新值
end
// 过程2(同时执行)
always @(posedge clk) begin
b = c; // b立即获得c的值
end
// 结果取决于过程的执行顺序,产生竞争条件
endmodule
// ✅ 解决方案:使用非阻塞赋值
module race_condition_nonblocking_good;
reg clk, a, b, c;
always @(posedge clk) begin
a <= b; // 使用旧值
c <= a; // 使用旧值
b <= c; // 使用旧值
end
// 所有信号正确轮换,无竞争条件
endmodule
5.2 冒险现象分析
5.2.1 冒险的产生机制
module hazard_demonstration;
reg clk, data, enable;
reg output1, output2;
// 可能产生冒险的设计
always @(posedge clk) begin
if (enable) begin
output1 = data; // 阻塞赋值
output2 <= output1; // 非阻塞赋值使用output1的新值
end
end
// 更安全的设计
always @(posedge clk) begin
if (enable) begin
output1 <= data; // 统一使用非阻塞赋值
output2 <= output1; // 使用output1的旧值
end
end
endmodule
5.3 避免竞争的设计原则
5.3.1 原则1:分离组合和时序逻辑
module separate_logic_design;
input wire clk, rst_n;
input wire [7:0] data_in;
input wire [2:0] operation;
output reg [7:0] result;
// 组合逻辑:计算下一个状态
reg [7:0] next_result;
always @(*) begin
case (operation)
3'b000: next_result = data_in;
3'b001: next_result = data_in + 1;
3'b010: next_result = data_in - 1;
3'b011: next_result = ~data_in;
default: next_result = 8'h00;
endcase
end
// 时序逻辑:寄存器更新
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
result <= 8'h00;
end else begin
result <= next_result;
end
end
endmodule
5.3.2 原则2:单一时钟域设计
module single_clock_domain;
input wire clk, rst_n;
input wire [3:0] input_data;
output reg [3:0] stage1, stage2, stage3, stage4;
// 所有时序逻辑使用同一时钟
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
stage1 <= 4'h0;
stage2 <= 4'h0;
stage3 <= 4'h0;
stage4 <= 4'h0;
end else begin
stage1 <= input_data;
stage2 <= stage1;
stage3 <= stage2;
stage4 <= stage3;
end
end
endmodule
6. 时序建模最佳实践
6.1 同步设计原则
6.1.1 标准同步时序设计
module synchronous_design_template(
input wire clk, rst_n,
input wire [DATA_WIDTH-1:0] data_in,
input wire enable, load,
output reg [DATA_WIDTH-1:0] data_out,
output reg valid_out
);
parameter DATA_WIDTH = 8;
// 内部状态寄存器
reg [DATA_WIDTH-1:0] internal_reg;
reg valid_reg;
// 同步时序逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// 异步复位
internal_reg <= {DATA_WIDTH{1'b0}};
valid_reg <= 1'b0;
data_out <= {DATA_WIDTH{1'b0}};
valid_out <= 1'b0;
end else begin
// 同步操作
if (load) begin
internal_reg <= data_in;
valid_reg <= 1'b1;
end else if (enable) begin
internal_reg <= internal_reg + 1;
end
// 输出寄存器
data_out <= internal_reg;
valid_out <= valid_reg;
end
end
endmodule
6.2 状态机设计
6.2.1 三段式状态机
module three_segment_fsm(
input wire clk, rst_n,
input wire start, done, error,
output reg busy, complete, error_flag
);
// 状态定义
typedef enum reg [2:0] {
IDLE = 3'b000,
INIT = 3'b001,
ACTIVE = 3'b010,
WAIT = 3'b011,
FINISH = 3'b100,
ERROR = 3'b101
} state_t;
state_t current_state, next_state;
// 第一段:状态寄存器(时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 第二段:次态逻辑(组合逻辑)
always @(*) begin
next_state = current_state; // 默认保持当前状态
case (current_state)
IDLE: begin
if (start) next_state = INIT;
end
INIT: begin
if (error) next_state = ERROR;
else next_state = ACTIVE;
end
ACTIVE: begin
if (error) next_state = ERROR;
else if (done) next_state = FINISH;
end
WAIT: begin
next_state = IDLE;
end
FINISH: begin
next_state = WAIT;
end
ERROR: begin
if (!start) next_state = IDLE;
end
default: next_state = IDLE;
endcase
end
// 第三段:输出逻辑(组合逻辑)
always @(*) begin
// 默认输出
busy = 1'b0;
complete = 1'b0;
error_flag = 1'b0;
case (current_state)
IDLE: begin
// 所有输出为默认值
end
INIT, ACTIVE: begin
busy = 1'b1;
end
FINISH: begin
complete = 1'b1;
end
ERROR: begin
error_flag = 1'b1;
end
endcase
end
endmodule
6.3 流水线设计
6.3.1 数据通路流水线
module pipeline_datapath #(
parameter DATA_WIDTH = 32,
parameter STAGES = 4
)(
input wire clk, rst_n, enable,
input wire [DATA_WIDTH-1:0] data_in,
output reg [DATA_WIDTH-1:0] data_out,
output reg valid_out
);
// 流水线寄存器
reg [DATA_WIDTH-1:0] stage_data [0:STAGES-1];
reg stage_valid [0:STAGES-1];
integer i;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// 复位所有流水线级
for (i = 0; i < STAGES; i = i + 1) begin
stage_data[i] <= {DATA_WIDTH{1'b0}};
stage_valid[i] <= 1'b0;
end
data_out <= {DATA_WIDTH{1'b0}};
valid_out <= 1'b0;
end else if (enable) begin
// 流水线数据流动
stage_data[0] <= data_in;
stage_valid[0] <= 1'b1;
for (i = 1; i < STAGES; i = i + 1) begin
stage_data[i] <= stage_data[i-1] + 1; // 简单处理
stage_valid[i] <= stage_valid[i-1];
end
// 输出级
data_out <= stage_data[STAGES-1];
valid_out <= stage_valid[STAGES-1];
end
end
endmodule
7. 综合性考虑
7.1 可综合的赋值模式
7.1.1 推荐的综合模式
module synthesizable_patterns;
input wire clk, rst_n;
input wire [7:0] data_in;
input wire enable, load;
output reg [7:0] data_out;
// ✅ 推荐模式1:同步复位
always @(posedge clk) begin
if (!rst_n) begin
data_out <= 8'h00;
end else if (load) begin
data_out <= data_in;
end else if (enable) begin
data_out <= data_out + 1;
end
end
// ✅ 推荐模式2:异步复位
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_out <= 8'h00;
end else begin
if (load) begin
data_out <= data_in;
end else if (enable) begin
data_out <= data_out + 1;
end
end
end
endmodule
7.1.2 避免的综合模式
// ❌ 不推荐的模式
module non_synthesizable_patterns;
reg clk, data;
// 问题1:延时语句
always @(posedge clk) begin
data <= #5 1'b1; // 延时不可综合
end
// 问题2:多时钟
always @(posedge clk1 or posedge clk2) begin // 多时钟不推荐
// ...
end
// 问题3:不完整的复位
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// 只复位部分信号
end else begin
// 使用了未复位的信号
end
end
endmodule
7.2 综合优化指导
7.2.1 资源共享
module resource_sharing_example;
input wire clk, rst_n, sel;
input wire [15:0] a, b, c, d;
output reg [16:0] result;
// ✅ 资源共享:复用加法器
reg [15:0] operand1, operand2;
always @(*) begin
if (sel) begin
operand1 = a;
operand2 = b;
end else begin
operand1 = c;
operand2 = d;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
result <= 17'h00000;
end else begin
result <= operand1 + operand2; // 共享的加法器
end
end
endmodule
8. 实际应用案例
8.1 UART接收器设计
8.1.1 完整的UART接收器
module uart_receiver #(
parameter CLOCK_FREQ = 50000000,
parameter BAUD_RATE = 115200
)(
input wire clk, rst_n, rx_serial,
output reg [7:0] rx_data,
output reg rx_valid, rx_error
);
// 波特率计算
localparam BAUD_TICKS = CLOCK_FREQ / BAUD_RATE;
localparam HALF_BAUD_TICKS = BAUD_TICKS / 2;
// 状态定义
typedef enum reg [2:0] {
IDLE = 3'b000,
START = 3'b001,
DATA = 3'b010,
STOP = 3'b011,
ERROR = 3'b100
} uart_state_t;
uart_state_t current_state, next_state;
// 内部寄存器
reg [$clog2(BAUD_TICKS)-1:0] baud_counter;
reg [2:0] bit_counter;
reg [7:0] shift_register;
reg rx_sync1, rx_sync2; // 同步器
// 输入同步化
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_sync1 <= 1'b1;
rx_sync2 <= 1'b1;
end else begin
rx_sync1 <= rx_serial;
rx_sync2 <= rx_sync1;
end
end
// 状态机寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 次态逻辑
always @(*) begin
next_state = current_state;
case (current_state)
IDLE: begin
if (!rx_sync2) next_state = START; // 检测起始位
end
START: begin
if (baud_counter == HALF_BAUD_TICKS) begin
if (!rx_sync2) begin
next_state = DATA;
end else begin
next_state = ERROR; // 假起始位
end
end
end
DATA: begin
if (baud_counter == BAUD_TICKS && bit_counter == 7) begin
next_state = STOP;
end
end
STOP: begin
if (baud_counter == BAUD_TICKS) begin
if (rx_sync2) begin
next_state = IDLE;
end else begin
next_state = ERROR; // 停止位错误
end
end
end
ERROR: begin
if (rx_sync2) next_state = IDLE; // 等待回到空闲
end
endcase
end
// 计数器和数据寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
baud_counter <= 0;
bit_counter <= 0;
shift_register <= 8'h00;
end else begin
case (current_state)
IDLE: begin
baud_counter <= 0;
bit_counter <= 0;
end
START: begin
baud_counter <= baud_counter + 1;
end
DATA: begin
if (baud_counter == BAUD_TICKS) begin
baud_counter <= 0;
bit_counter <= bit_counter + 1;
shift_register <= {rx_sync2, shift_register[7:1]};
end else begin
baud_counter <= baud_counter + 1;
end
end
STOP: begin
baud_counter <= baud_counter + 1;
end
ERROR: begin
baud_counter <= 0;
bit_counter <= 0;
end
endcase
end
end
// 输出逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_data <= 8'h00;
rx_valid <= 1'b0;
rx_error <= 1'b0;
end else begin
rx_valid <= 1'b0; // 默认无效
rx_error <= 1'b0; // 默认无错误
if (current_state == STOP && next_state == IDLE) begin
rx_data <= shift_register;
rx_valid <= 1'b1;
end else if (current_state == ERROR) begin
rx_error <= 1'b1;
end
end
end
endmodule
8.2 SPI主控制器
8.2.1 SPI控制器设计
module spi_master #(
parameter DATA_WIDTH = 8,
parameter CLOCK_DIV = 4
)(
input wire clk, rst_n,
input wire [DATA_WIDTH-1:0] tx_data,
input wire start_tx,
output reg [DATA_WIDTH-1:0] rx_data,
output reg tx_done, busy,
// SPI接口
output reg sclk, mosi, cs_n,
input wire miso
);
// 内部信号
reg [$clog2(CLOCK_DIV)-1:0] clk_counter;
reg [$clog2(DATA_WIDTH)-1:0] bit_counter;
reg [DATA_WIDTH-1:0] tx_shift_reg, rx_shift_reg;
reg sclk_enable;
// 状态定义
typedef enum reg [1:0] {
IDLE = 2'b00,
TRANSMIT = 2'b01,
COMPLETE = 2'b10
} spi_state_t;
spi_state_t current_state, next_state;
// 时钟分频器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
clk_counter <= 0;
sclk <= 1'b0;
end else if (sclk_enable) begin
if (clk_counter == CLOCK_DIV - 1) begin
clk_counter <= 0;
sclk <= ~sclk;
end else begin
clk_counter <= clk_counter + 1;
end
end else begin
clk_counter <= 0;
sclk <= 1'b0;
end
end
// 状态机
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// 次态逻辑
always @(*) begin
next_state = current_state;
case (current_state)
IDLE: begin
if (start_tx) next_state = TRANSMIT;
end
TRANSMIT: begin
if (bit_counter == DATA_WIDTH - 1 &&
clk_counter == CLOCK_DIV - 1 && sclk) begin
next_state = COMPLETE;
end
end
COMPLETE: begin
next_state = IDLE;
end
endcase
end
// 数据传输逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx_shift_reg <= 0;
rx_shift_reg <= 0;
bit_counter <= 0;
sclk_enable <= 1'b0;
cs_n <= 1'b1;
mosi <= 1'b0;
end else begin
case (current_state)
IDLE: begin
if (start_tx) begin
tx_shift_reg <= tx_data;
rx_shift_reg <= 0;
bit_counter <= 0;
sclk_enable <= 1'b1;
cs_n <= 1'b0;
mosi <= tx_data[DATA_WIDTH-1];
end else begin
sclk_enable <= 1'b0;
cs_n <= 1'b1;
end
end
TRANSMIT: begin
if (clk_counter == CLOCK_DIV - 1) begin
if (!sclk) begin // 上升沿
rx_shift_reg <= {rx_shift_reg[DATA_WIDTH-2:0], miso};
end else begin // 下降沿
if (bit_counter < DATA_WIDTH - 1) begin
bit_counter <= bit_counter + 1;
tx_shift_reg <= {tx_shift_reg[DATA_WIDTH-2:0], 1'b0};
mosi <= tx_shift_reg[DATA_WIDTH-2];
end
end
end
end
COMPLETE: begin
sclk_enable <= 1'b0;
cs_n <= 1'b1;
end
endcase
end
end
// 输出信号
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_data <= 0;
tx_done <= 1'b0;
busy <= 1'b0;
end else begin
tx_done <= (current_state == COMPLETE);
busy <= (current_state != IDLE);
if (current_state == COMPLETE) begin
rx_data <= rx_shift_reg;
end
end
end
endmodule
9. 常见错误与调试
9.1 赋值相关错误
9.1.1 错误类型总结
module common_assignment_errors;
reg clk, rst_n, data_in;
reg a, b, c, d;
// ❌ 错误1:时序逻辑中使用阻塞赋值
always @(posedge clk) begin
a = data_in; // 应该使用 <=
b = a; // 会导致组合逻辑行为
end
// ❌ 错误2:组合逻辑中使用非阻塞赋值
always @(*) begin
c <= a + b; // 应该使用 =
end
// ❌ 错误3:混合使用
always @(posedge clk) begin
a <= data_in; // 非阻塞
b = a; // 阻塞:使用a的旧值
c <= b; // 非阻塞:使用b的新值
end
// ✅ 正确的做法
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
a <= 1'b0;
b <= 1'b0;
c <= 1'b0;
end else begin
a <= data_in;
b <= a; // 使用a的旧值
c <= b; // 使用b的旧值
end
end
endmodule
9.2 调试技巧
9.2.1 添加调试信号
module assignment_debug;
input wire clk, rst_n, enable;
input wire [7:0] data_in;
output reg [7:0] data_out;
// 调试信号
`ifdef DEBUG
reg [31:0] assignment_count;
reg last_enable;
wire enable_edge;
`endif
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_out <= 8'h00;
`ifdef DEBUG
assignment_count <= 0;
last_enable <= 1'b0;
`endif
end else begin
`ifdef DEBUG
last_enable <= enable;
`endif
if (enable) begin
data_out <= data_in;
`ifdef DEBUG
assignment_count <= assignment_count + 1;
$display("Assignment #%0d at time %0t: data_out <= %h",
assignment_count + 1, $time, data_in);
`endif
end
end
end
`ifdef DEBUG
assign enable_edge = enable && !last_enable;
always @(posedge enable_edge) begin
$display("Enable edge detected at time %0t", $time);
end
`endif
endmodule
9.2.2 波形分析工具
module waveform_analysis_testbench;
reg clk, rst_n;
reg [3:0] data_in;
wire [3:0] data_out;
// 被测模块
assignment_debug dut(
.clk(clk),
.rst_n(rst_n),
.enable(1'b1),
.data_in(data_in[7:0]),
.data_out(data_out[7:0])
);
// 时钟生成
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 测试序列
initial begin
// 波形文件生成
$dumpfile("assignment_test.vcd");
$dumpvars(0, waveform_analysis_testbench);
rst_n = 0;
data_in = 4'h0;
#20 rst_n = 1;
// 测试数据序列
repeat(16) begin
#10 data_in = data_in + 1;
end
#50 $finish;
end
// 信号监控
always @(posedge clk) begin
$strobe("Time=%0t: data_in=%h, data_out=%h", $time, data_in, data_out);
end
endmodule
10. 高级主题
10.1 跨时钟域处理
10.1.1 异步FIFO设计
module async_fifo #(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 4
)(
// 写端口
input wire wr_clk, wr_rst_n,
input wire wr_en,
input wire [DATA_WIDTH-1:0] wr_data,
output reg wr_full,
// 读端口
input wire rd_clk, rd_rst_n,
input wire rd_en,
output reg [DATA_WIDTH-1:0] rd_data,
output reg rd_empty
);
localparam DEPTH = 1 << ADDR_WIDTH;
// 存储器
reg [DATA_WIDTH-1:0] memory [0:DEPTH-1];
// 格雷码指针
reg [ADDR_WIDTH:0] wr_ptr_gray, wr_ptr_gray_next;
reg [ADDR_WIDTH:0] rd_ptr_gray, rd_ptr_gray_next;
// 二进制指针
reg [ADDR_WIDTH:0] wr_ptr_bin, wr_ptr_bin_next;
reg [ADDR_WIDTH:0] rd_ptr_bin, rd_ptr_bin_next;
// 同步化的指针
reg [ADDR_WIDTH:0] wr_ptr_gray_sync, rd_ptr_gray_sync;
reg [ADDR_WIDTH:0] wr_ptr_gray_sync_r, rd_ptr_gray_sync_r;
// 写指针逻辑
always @(*) begin
wr_ptr_bin_next = wr_ptr_bin + (wr_en & ~wr_full);
wr_ptr_gray_next = (wr_ptr_bin_next >> 1) ^ wr_ptr_bin_next;
end
always @(posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n) begin
wr_ptr_bin <= 0;
wr_ptr_gray <= 0;
end else begin
wr_ptr_bin <= wr_ptr_bin_next;
wr_ptr_gray <= wr_ptr_gray_next;
end
end
// 读指针逻辑
always @(*) begin
rd_ptr_bin_next = rd_ptr_bin + (rd_en & ~rd_empty);
rd_ptr_gray_next = (rd_ptr_bin_next >> 1) ^ rd_ptr_bin_next;
end
always @(posedge rd_clk or negedge rd_rst_n) begin
if (!rd_rst_n) begin
rd_ptr_bin <= 0;
rd_ptr_gray <= 0;
end else begin
rd_ptr_bin <= rd_ptr_bin_next;
rd_ptr_gray <= rd_ptr_gray_next;
end
end
// 指针同步化
always @(posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n) begin
rd_ptr_gray_sync_r <= 0;
rd_ptr_gray_sync <= 0;
end else begin
rd_ptr_gray_sync_r <= rd_ptr_gray;
rd_ptr_gray_sync <= rd_ptr_gray_sync_r;
end
end
always @(posedge rd_clk or negedge rd_rst_n) begin
if (!rd_rst_n) begin
wr_ptr_gray_sync_r <= 0;
wr_ptr_gray_sync <= 0;
end else begin
wr_ptr_gray_sync_r <= wr_ptr_gray;
wr_ptr_gray_sync <= wr_ptr_gray_sync_r;
end
end
// 状态标志
always @(*) begin
wr_full = (wr_ptr_gray_next == {~rd_ptr_gray_sync[ADDR_WIDTH:ADDR_WIDTH-1],
rd_ptr_gray_sync[ADDR_WIDTH-2:0]});
rd_empty = (rd_ptr_gray == wr_ptr_gray_sync);
end
// 存储器读写
always @(posedge wr_clk) begin
if (wr_en && !wr_full) begin
memory[wr_ptr_bin[ADDR_WIDTH-1:0]] <= wr_data;
end
end
always @(posedge rd_clk or negedge rd_rst_n) begin
if (!rd_rst_n) begin
rd_data <= 0;
end else if (rd_en && !rd_empty) begin
rd_data <= memory[rd_ptr_bin[ADDR_WIDTH-1:0]];
end
end
endmodule
10.2 复杂控制逻辑
10.2.1 多级流水线控制器
module pipeline_controller #(
parameter STAGES = 5,
parameter DATA_WIDTH = 32
)(
input wire clk, rst_n,
input wire pipeline_enable, flush,
input wire [DATA_WIDTH-1:0] data_in,
input wire valid_in,
output reg [DATA_WIDTH-1:0] data_out,
output reg valid_out,
output wire pipeline_ready
);
// 流水线寄存器
reg [DATA_WIDTH-1:0] stage_data [0:STAGES-1];
reg stage_valid [0:STAGES-1];
reg stage_enable [0:STAGES-1];
// 流水线控制信号
wire stall;
reg [STAGES-1:0] stage_stall;
integer i;
// 停顿检测逻辑
assign stall = |stage_stall; // 任一级停顿则整个流水线停顿
assign pipeline_ready = pipeline_enable && !stall;
// 流水线主控制逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// 复位所有流水线级
for (i = 0; i < STAGES; i = i + 1) begin
stage_data[i] <= 0;
stage_valid[i] <= 1'b0;
stage_enable[i] <= 1'b0;
end
data_out <= 0;
valid_out <= 1'b0;
end else if (flush) begin
// 流水线冲刷
for (i = 0; i < STAGES; i = i + 1) begin
stage_valid[i] <= 1'b0;
end
valid_out <= 1'b0;
end else if (pipeline_enable && !stall) begin
// 正常流水线操作
// 第一级:输入
stage_data[0] <= data_in;
stage_valid[0] <= valid_in;
stage_enable[0] <= 1'b1;
// 中间级:数据传递和处理
for (i = 1; i < STAGES; i = i + 1) begin
stage_data[i] <= stage_data[i-1] + 1; // 简单处理
stage_valid[i] <= stage_valid[i-1];
stage_enable[i] <= stage_enable[i-1];
end
// 输出级
data_out <= stage_data[STAGES-1];
valid_out <= stage_valid[STAGES-1];
end
end
// 各级停顿条件(示例)
always @(*) begin
for (i = 0; i < STAGES; i = i + 1) begin
stage_stall[i] = 1'b0; // 默认不停顿
// 可以根据具体需求添加停顿条件
// 例如:资源冲突、数据依赖等
end
end
endmodule
🎯 总结
核心要点回顾
- 过程赋值基础: 只能在过程块中使用,目标必须是reg类型
- 阻塞赋值 (=): 顺序执行,立即更新,适用于组合逻辑
- 非阻塞赋值 (<=): 并行调度,延迟更新,适用于时序逻辑
- 竞争避免: 正确选择赋值类型是避免竞争的关键
- 设计原则: 分离组合和时序逻辑,使用统一的时钟域
设计指导原则
- 🎯 组合逻辑: 使用阻塞赋值 (=) 和 always @(*)
- ⏰ 时序逻辑: 使用非阻塞赋值 (<=) 和时钟边沿
- 🔄 避免混合: 不要在同一个always块中混用两种赋值
- 📊 状态机: 推荐三段式设计模式
- 🛡️ 复位策略: 异步复位、同步释放
学习建议
- 深入理解两种赋值的执行机制
- 练习组合逻辑和时序逻辑的标准写法
- 学习状态机和流水线设计模式
- 掌握跨时钟域处理技术
- 培养良好的调试和验证习惯
💡 重要提醒: 正确使用阻塞和非阻塞赋值是Verilog设计的基础技能。记住:阻塞赋值用于组合逻辑,非阻塞赋值用于时序逻辑,这是避免设计错误的黄金法则!