We choose a 4bit PWM here so the PWM period is 16. The input can go from 0 to 15 so the PWM output ratio goes from 0% to 15/16=93%. If you need to be able to go up to 100%, the input needs to have an extra bit.
The code works fine, although it is a bit naive in its current form because the input must be fixed (or change only when the counter overflows = goes back to 0). Otherwise the output will glitch. So most likely a bit of extra logic is required (usually in the form of a latch capturing the input at the right time).
A first-order sigma-delta modulator resembles a PWM, but with a better frequency response if you need to filter it because of its higher frequency output content.
The simplest way to create a first-order sigma-delta modulator is to use an hardware accumulator… every time the accumulator overflows, output a ‘1’. Otherwise output a ‘0’. That’s very easily done in an FPGA.
Take one pin of an FPGA, connect a speaker and listen to an MP3? Easy. Here, we’ll use a PC to decode an MP3, and then send the decoded data to an FPGA that is configured as a one-bit DAC.
Audio output
We require a DAC (digital-to-analog converter) to connect the FPGA (digital) to a speaker (analog). The conventional approach would be to use a resistor ladder (see here), or use a dedicated DAC IC, like the venerable DAC-08.
Since the frequency at which FPGAs run is so fast compared to the frequencies required in the audio domain (MHZ’s compared to KHz’s), a one-bit DAC is a better choice.
Basically, to create the analog output, we smooth out the PWM or sigma-delta modulator output pulses using a low-pass filter. A sigma-delta modulator is better because of its higher-frequency output content, with which a single-order low-pass RC filter is usually enough.
Playing an MP3
The first step is to decode the MP3. The decoded MP3 data is called “PCM” data. To keep things simple, we send the PCM data through the serial port of the PC to the FPGA. The maximum rate possible through the serial port is 115200 baud (i.e. about 11.5 KBytes per second), so the music has to be down-sampled to 11KHz 8 bits. All that is easily done by a PC. Here’s the software used (with source code). And for the HDL code, we simply modify the sigma-delta modulator so that the PWM data input comes from the serial port.
PWM_driver.v具體內容為:
module PWM_driver(
input clk_i,
input rst_n_i,
input [31:0]pwm_reg0_i,
input [31:0]pwm_reg1_i,
input [31:0]pwm_reg2_i,
input [31:0]pwm_reg3_i,
input [31:0]pwm_reg4_i,
input [31:0]pwm_reg5_i,
input [31:0]pwm_reg6_i,
input [31:0]pwm_reg7_i,
output reg [7:0] pwm_o
);
reg[31:0]pwm_cnt0;
reg [31:0]pwm_cnt1;
reg [31:0]pwm_cnt2;
reg [31:0]pwm_cnt3;
reg [31:0]pwm_cnt4;
reg [31:0]pwm_cnt5;
reg [31:0]pwm_cnt6;
reg [31:0]pwm_cnt7;//pwm0
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt0 <=32'd0;
pwm_o[0]<=1'b0;
end
else begin
if(pwm_cnt0<pwm_reg0_i)begin
pwm_cnt0 <= pwm_cnt0 +1'b1;
end
else begin
pwm_cnt0<=32'D0;
pwm_o[0]<=~pwm_o[0];
end
end
end
//pwm1
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt1 <=32'd0;
pwm_o[1]<=1'b0;
end
else begin
if(pwm_cnt1<pwm_reg1_i)begin
pwm_cnt1 <= pwm_cnt1 +1'b1;
end
else begin
pwm_cnt1<=32'D0;
pwm_o[1]<=~pwm_o[1];
end
end
end
//pwm2
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt2 <=32'd0;
pwm_o[2]<=1'b0;
end
else begin
if(pwm_cnt2<pwm_reg2_i)begin
pwm_cnt2 <= pwm_cnt2 +1'b1;
end
else begin
pwm_cnt2<=32'D0;
pwm_o[2]<=~pwm_o[2];
end
end
end
//pwm3
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt3 <=32'd0;
pwm_o[3]<=1'b0;
end
else begin
if(pwm_cnt3<pwm_reg3_i)begin
pwm_cnt3 <= pwm_cnt3 +1'b1;
end
else begin
pwm_cnt3<=32'D0;
pwm_o[3]<=~pwm_o[3];
end
end
end
//pwm4
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt4 <=32'd0;
pwm_o[4]<=1'b0;
end
else begin
if(pwm_cnt4<pwm_reg4_i)begin
pwm_cnt4 <= pwm_cnt4 +1'b1;
end
else begin
pwm_cnt4<=32'D0;
pwm_o[4]<=~pwm_o[4];
end
end
end
//pwm5
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt5 <=32'd0;
pwm_o[5]<=1'b0;
end
else begin
if(pwm_cnt5<pwm_reg5_i)begin
pwm_cnt5 <= pwm_cnt5 +1'b1;
end
else begin
pwm_cnt5<=32'D0;
pwm_o[5]<=~pwm_o[5];
end
end
end
//pwm5
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt6 <=32'd0;
pwm_o[6]<=1'b0;
end
else begin
if(pwm_cnt6<pwm_reg6_i)begin
pwm_cnt6 <= pwm_cnt6 +1'b1;
end
else begin
pwm_cnt6<=32'D0;
pwm_o[6]<=~pwm_o[6];
end
end
end
//pwm7
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt7 <=32'd0;
pwm_o[7]<=1'b0;
end
else begin
if(pwm_cnt7<pwm_reg7_i)begin
pwm_cnt7 <= pwm_cnt7 +1'b1;
end
else begin
pwm_cnt7<=32'D0;
pwm_o[7]<=~pwm_o[7];
end
end
end
endmodule
6.點擊File–>點擊 Save all files,最終如下
4.修改完成后還要重新打包 1 選擇 tool–>Create and Package New Ip… 2. 選擇 package your current project 選擇 next
3.保持默認設置,不做任何修改,點擊 Next
4.點擊 Next 選擇 Overwrite
5.點擊 Finish,完成。
6.執行以下操作檢查 IP是否封裝完成,展開 IP XACT(1)>雙擊 component.xml,展開 Ports and Interface,可以看到封裝 IP完成。