作为一名硬件工程师,我经常被问到如何开始FPGA设计。今天我想分享两个基础但极其重要的数字电路设计案例:计时器电路和串并转换电路。这两个案例涵盖了FPGA开发中最核心的时序逻辑和组合逻辑设计思想。
FPGA(现场可编程门阵列)因其可重构性和并行处理能力,在通信、图像处理、工业控制等领域广泛应用。但无论应用场景多么复杂,扎实的基础电路设计能力都是FPGA开发的基石。下面我将结合具体代码和仿真结果,详细解析这两个电路的设计要点。
计时器是数字系统中最常见的功能模块之一。我们需要设计一个可预置的N位二进制计数器,具有以下功能:
在实际项目中,这种计时器常用于产生精确的时间延迟、分频时钟信号,或者作为状态机的定时控制单元。
以下是基于Verilog HDL的计时器实现代码:
verilog复制module timer #(
parameter WIDTH = 8,
parameter MAX_VAL = 255
)(
input clk,
input rst_n,
input en,
output reg [WIDTH-1:0] count,
output reg done
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
count <= 0;
done <= 0;
end
else if (en) begin
if (count == MAX_VAL) begin
count <= 0;
done <= 1;
end
else begin
count <= count + 1;
done <= 0;
end
end
end
endmodule
参数化设计:通过WIDTH和MAX_VAL参数,可以灵活配置计数器的位宽和最大值,提高代码复用性。
异步复位设计:rst_n低电平有效,确保电路在任何时候都能被可靠复位。这是数字电路设计的黄金法则之一。
同步使能控制:en信号仅在时钟上升沿被采样,避免了使能信号毛刺导致的计数错误。
done标志生成:当计数器达到MAX_VAL时,done信号会在下一个时钟周期变为高电平,持续一个时钟周期。
重要提示:在FPGA设计中,所有控制信号(如en)都必须同步处理,否则可能导致亚稳态问题。这是新手最容易犯的错误之一。
使用ModelSim进行仿真的测试激励文件:
verilog复制`timescale 1ns/1ps
module tb_timer;
reg clk;
reg rst_n;
reg en;
wire [7:0] count;
wire done;
timer #(.WIDTH(8), .MAX_VAL(10)) uut (
.clk(clk),
.rst_n(rst_n),
.en(en),
.count(count),
.done(done)
);
initial begin
clk = 0;
forever #5 clk = ~clk;
end
initial begin
rst_n = 0;
en = 0;
#20 rst_n = 1;
#10 en = 1;
#200 en = 0;
#50 $finish;
end
endmodule
仿真波形分析要点:
串并转换电路在通信系统中极为常见,例如:
我们将设计一个通用的N位串并转换器,具有以下特性:
verilog复制module serial_to_parallel #(
parameter WIDTH = 8
)(
input clk,
input rst_n,
input serial_in,
input en,
output reg [WIDTH-1:0] parallel_out,
output reg valid
);
reg [WIDTH-1:0] shift_reg;
reg [3:0] bit_count; // 足够计数WIDTH位
always @(posedge clk) begin
if (!rst_n) begin
shift_reg <= 0;
parallel_out <= 0;
bit_count <= 0;
valid <= 0;
end
else if (en) begin
shift_reg <= {shift_reg[WIDTH-2:0], serial_in};
if (bit_count == WIDTH-1) begin
parallel_out <= {shift_reg[WIDTH-2:0], serial_in};
valid <= 1;
bit_count <= 0;
end
else begin
bit_count <= bit_count + 1;
valid <= 0;
end
end
else begin
valid <= 0;
end
end
endmodule
移位寄存器实现:使用Verilog的拼接操作符{}实现移位寄存器,这是串并转换的核心。
位计数器设计:独立计数器记录已接收的位数,当计数达到WIDTH-1时,表示一个完整数据帧接收完成。
valid信号生成:仅在完整接收一个数据帧时置位valid信号,持续一个时钟周期。
时序约束:必须确保serial_in信号在时钟上升沿前后满足建立时间和保持时间要求。
实际项目经验:在高速应用中(>100MHz),建议对输入信号进行双寄存器同步处理,避免亚稳态问题。这是很多工程师容易忽略的关键点。
测试激励文件示例:
verilog复制module tb_s2p;
reg clk;
reg rst_n;
reg serial_in;
reg en;
wire [7:0] parallel_out;
wire valid;
serial_to_parallel #(.WIDTH(8)) uut (
.clk(clk),
.rst_n(rst_n),
.serial_in(serial_in),
.en(en),
.parallel_out(parallel_out),
.valid(valid)
);
initial begin
clk = 0;
forever #5 clk = ~clk;
end
task send_byte;
input [7:0] data;
integer i;
begin
for (i=0; i<8; i=i+1) begin
serial_in = data[i];
@(posedge clk);
end
end
endtask
initial begin
rst_n = 0;
en = 0;
serial_in = 0;
#20 rst_n = 1;
#10 en = 1;
// 发送测试数据 8'b10101010
send_byte(8'b10101010);
// 发送测试数据 8'b11001100
#10 send_byte(8'b11001100);
#50 $finish;
end
endmodule
仿真结果分析:
计数不准确
done信号毛刺
参数化设计问题
数据错位
valid信号不稳定
高速应用问题
在实际项目中,我通常会先构建这样的基础模块库,然后在更复杂的系统中调用它们。这种自底向上的设计方法可以显著提高开发效率和系统可靠性。