2. FPGA PWM波形生成
任务目标
在FPGA中实现一个PWM波形生成器,支持通过寄存器配置频率和占空比,输出用于驱动功率级的PWM信号。本案例以Xilinx Artix-7系列FPGA为平台,使用Verilog HDL完成设计,并通过仿真验证波形参数的正确性。
设计规格
- 输入时钟:50 MHz系统时钟
- 频率范围:1 kHz ~ 100 kHz,步进1 kHz
- 占空比范围:0% ~ 100%,步进1%
- 寄存器接口:2个32位控制寄存器(频率字、占空比字),1个32位状态寄存器(当前计数值)
核心实现
采用周期计数比较法:一个周期计数器在0~N-1之间循环,N = fclk / fpwm;输出信号在计数值小于占空比阈值时拉高,否则拉低。频率字和占空比字由外部总线写入,内部实时更新。
module pwm_generator #(
parameter CLK_FREQ = 50_000_000 // 50 MHz
)(
input wire clk,
input wire rst_n,
// 寄存器写接口
input wire reg_wr_en,
input wire [4:0] reg_addr,
input wire [31:0] reg_wr_data,
// 寄存器读接口
input wire [4:0] reg_rd_addr,
output reg [31:0] reg_rd_data,
// PWM输出
output reg pwm_out
);
// 寄存器地址定义
localparam REG_FREQ = 5'd0; // 频率字:目标频率(Hz),范围1000~100000
localparam REG_DUTY = 5'd1; // 占空比字:0~100,表示百分比
localparam REG_COUNT = 5'd2; // 状态:当前计数值
reg [31:0] freq_reg;
reg [31:0] duty_reg;
reg [31:0] period_cnt;
// 周期计数值 = CLK_FREQ / fpwm - 1
wire [31:0] period_limit = (CLK_FREQ / freq_reg) - 1;
// 占空比比较阈值 = period_limit * duty / 100
wire [31:0] duty_threshold = (period_limit * duty_reg) / 100;
// 寄存器写
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
freq_reg <= 32'd10000; // 默认10 kHz
duty_reg <= 32'd50; // 默认50%
end else if (reg_wr_en) begin
case (reg_addr)
REG_FREQ: freq_reg <= reg_wr_data;
REG_DUTY: duty_reg <= reg_wr_data;
default: ;
endcase
end
end
// 周期计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
period_cnt <= 32'd0;
else if (period_cnt >= period_limit)
period_cnt <= 32'd0;
else
period_cnt <= period_cnt + 1'b1;
end
// PWM输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
pwm_out <= 1'b0;
else
pwm_out <= (period_cnt < duty_threshold) ? 1'b1 : 1'b0;
end
// 寄存器读
always @(*) begin
case (reg_rd_addr)
REG_FREQ: reg_rd_data = freq_reg;
REG_DUTY: reg_rd_data = duty_reg;
REG_COUNT: reg_rd_data = period_cnt;
default: reg_rd_data = 32'd0;
endcase
end
endmodule
仿真验证
编写testbench验证频率和占空比配置是否正确。验证方法:写入频率字和占空比字,运行足够长的仿真时间,测量PWM输出高电平时间与周期之比。
`timescale 1ns/1ps
module tb_pwm_generator;
reg clk;
reg rst_n;
reg reg_wr_en;
reg [4:0] reg_addr;
reg [31:0] reg_wr_data;
wire [31:0] reg_rd_data;
reg [4:0] reg_rd_addr;
wire pwm_out;
pwm_generator dut (
.clk(clk), .rst_n(rst_n),
.reg_wr_en(reg_wr_en), .reg_addr(reg_addr), .reg_wr_data(reg_wr_data),
.reg_rd_addr(reg_rd_addr), .reg_rd_data(reg_rd_data),
.pwm_out(pwm_out)
);
initial clk = 0;
always #10 clk = ~clk; // 50 MHz
// 统计PWM高电平时间
integer high_count, total_count;
reg pwm_prev;
always @(posedge clk) begin
pwm_prev <= pwm_out;
if (pwm_out) high_count = high_count + 1;
total_count = total_count + 1;
end
initial begin
rst_n = 0; reg_wr_en = 0; high_count = 0; total_count = 0;
#100 rst_n = 1;
// 配置:20 kHz,占空比30%
reg_wr_en = 1;
reg_addr = 5'd0; reg_wr_data = 32'd20000; #20;
reg_addr = 5'd1; reg_wr_data = 32'd30; #20;
reg_wr_en = 0;
// 运行2ms统计
#2_000_000;
// 计算实际占空比
$display("Measured duty cycle: %0d%%", (high_count * 100) / total_count);
$display("Expected duty cycle: 30%%");
// 验证频率:统计上升沿间隔
// 此处简化:通过周期计数寄存器观察
reg_rd_addr = 5'd2;
#100;
$display("Period counter max value: %0d", reg_rd_data);
$finish;
end
endmodule
运行与验证
- 仿真:使用Vivado或ModelSim运行testbench,观察
pwm_out波形。 - 通过条件:仿真结束后,打印的实测占空比应为30%(允许±1%误差);周期计数器最大值应为2500(50MHz/20kHz-1),对应20kHz输出。
- 硬件验证:将
pwm_out连接至示波器或逻辑分析仪,通过AXI-Lite或自定义总线写入寄存器,观察输出波形频率和占空比与配置一致。
常见问题
- 频率字为0:会导致除零错误,需在总线接口层限制写入值≥1000。
- 占空比更新时机:当前实现中占空比阈值在每个周期开始时更新,若需立即生效可改为组合逻辑计算阈值。
- 输出毛刺:当占空比设为0%或100%时,比较器输出恒定电平,无毛刺;中间值切换瞬间可能有1个时钟周期的毛刺,可在输出端加D触发器打拍消除。