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

运行与验证

  1. 仿真:使用Vivado或ModelSim运行testbench,观察pwm_out波形。
  2. 通过条件:仿真结束后,打印的实测占空比应为30%(允许±1%误差);周期计数器最大值应为2500(50MHz/20kHz-1),对应20kHz输出。
  3. 硬件验证:将pwm_out连接至示波器或逻辑分析仪,通过AXI-Lite或自定义总线写入寄存器,观察输出波形频率和占空比与配置一致。

常见问题

  • 频率字为0:会导致除零错误,需在总线接口层限制写入值≥1000。
  • 占空比更新时机:当前实现中占空比阈值在每个周期开始时更新,若需立即生效可改为组合逻辑计算阈值。
  • 输出毛刺:当占空比设为0%或100%时,比较器输出恒定电平,无毛刺;中间值切换瞬间可能有1个时钟周期的毛刺,可在输出端加D触发器打拍消除。