verilog實現線性插值實現正弦波生成器
最近在項目上遇到一個需要在低資源FPGA上實現FFT邏輯的項目,而且要求實現窗函數。對於窗函數來説,莫非是實現正弦波生成器,正弦波生成器可以利用DDS模塊,CORDIC模塊,或者查找表的方式實現,以下主要講解ROM核線性插值相結合的波形生成器,用於生成正弦波。
1.線性插值
線性插值是一種數據估值算法,由於其擬合線是一條直線,所以叫做線性插值。即通過需要估值點的左右兩個點的權重以及距離,對估值點的權重進行計算的一種算法。
由於估值擬合線是直線那麼,已知(x0,y0)和(x1,y1),以及x到兩點的距離,對y進行計算。
對正弦函數進行估值:
其中(x,y)表示估算值,(x2,y2)表示真實值,誤差為y2-y,即當x1-x0越小,估算值越準確。樣本點越多越精確。
2.樣本生成
以下matlab代碼用於生成正弦函數樣本值,用於進行數據估算。
clc,clear,close all
%% 生成 rom 數據
Width=16;
Depth=256;
phi=linspace(0,2*pi,Depth+1);
phi=phi(1:end-1)';
cos_sig=cos(phi);
cos_sig=floor(cos_sig*(2^(Width-1)-1));
plot(cos_sig)
%% 生成.coe文件
filename='.\cos_rom.coe';
fid = fopen(filename,'w');
radix = 10;
fprintf(fid,"memory_initialization_radix=%d;\n",radix); %使用的進制
fprintf(fid,"memory_initialization_vector=");
for i=1:size(cos_sig,1)
fprintf(fid,"\n%d",cos_sig(i));
end
fprintf(fid,";");
fclose(fid);
3.verilog實現線性插值
以下將使用參數:樣本深度256,相位最大值65536進行講解。
對某一個點進行線性估值的時候,我們需要知道當前點在樣本中對應相應點的鄰近點。樣本鄰近兩點相位差65536/256 = 256,假設插值相位位置為phase,則相鄰點為floor(phase/256)和floor(phase/256)+1,floor表示向下取整,rom表示查找表數據。
那麼
以下為VERILOG代碼實現:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2025/03/29 15:47:50
// Design Name:
// Module Name: cos_gen_pipeline
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module cos_gen_pipeline(
input clk ,
input rst ,
input valid ,
input [15:0] phase , //相位,0~65535對應0~2pi)
output rdy ,
output reg [15:0] cos_out
);
reg [4:0] valid_d;
always @(posedge clk) begin
if(rst)begin
valid_d <= 0;
end else begin
valid_d <= {valid_d[3:0],valid};
end
end
assign rdy = valid_d[4];
wire [7:0] addr1;
wire [7:0] addr2;
wire signed [15:0] cos_dat1;
wire signed [15:0] cos_dat2;
wire [15:0] phase1;
//-----------線性插值-----------------------------
assign addr1 = (phase>>8) ;
assign addr2 = (phase>>8)+1 ;
assign phase1 = addr1<<8 ;
cos_rom cos_rom_inst1(
.clka (clk ),
.addra (addr1 ),
.douta (cos_dat1 )
);
cos_rom cos_rom_inst2(
.clka (clk ),
.addra (addr2 ),
.douta (cos_dat2 )
);
reg [15:0] phase_d0 ;
reg [15:0] phase_d1 ;
reg [15:0] phase1_d0 ;
reg [15:0] phase1_d1 ;
always @(posedge clk) begin
if(rst)begin
phase_d0 <= 0 ;
phase_d1 <= 0 ;
phase1_d0 <= 0 ;
phase1_d1 <= 0 ;
end else begin
phase_d0 <= phase ;
phase_d1 <= phase_d0 ;
phase1_d0 <= phase1 ;
phase1_d1 <= phase1_d0 ;
end
end
reg [31:0] multi;
reg [15:0] delta_cos_data ;
reg [15:0] delta_phase ;
always @(posedge clk) begin
if(rst)begin
multi <= 0;
end else begin
if(cos_dat2 > cos_dat1)begin
delta_cos_data <= (cos_dat2 - cos_dat1) ;
delta_phase <= phase_d1 - phase1_d1 ;
multi <= delta_cos_data*delta_phase ;
end else begin
delta_cos_data <= (cos_dat1 - cos_dat2) ;
delta_phase <= phase_d1 - phase1_d1 ;
multi <= delta_cos_data*delta_phase ;
end
end
end
reg signed [15:0] cos_dat1_d;
reg signed [15:0] cos_dat2_d;
always @(posedge clk) begin
if(rst)begin
cos_dat1_d <= 0;
cos_dat2_d <= 0;
end else begin
cos_dat1_d <= cos_dat1;
cos_dat2_d <= cos_dat2;
end
end
reg signed [15:0] cos_dat1_d1;
reg signed [15:0] cos_dat2_d1;
always @(posedge clk) begin
if(rst)begin
cos_dat1_d1 <= 0;
cos_dat2_d1 <= 0;
end else begin
cos_dat1_d1 <= cos_dat1_d;
cos_dat2_d1 <= cos_dat2_d;
end
end
always @(posedge clk) begin
if(rst)begin
cos_out <= 0;
end else begin
if(cos_dat2_d1 > cos_dat1_d1)begin
cos_out <= cos_dat1_d1 + (multi >> 8);
end else begin
cos_out <= cos_dat1_d1 - (multi >> 8);
end
end
end
endmodule
仿真代碼:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2025/04/05 00:00:38
// Design Name:
// Module Name: tb_cos_gen_pipeline
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module tb_cos_gen_pipeline();
reg clk;
reg rst;
initial begin
clk <=0;
rst <=1;
#300
rst <=0;
end
always #10 clk <= ~clk;
reg valid ;
reg [15:0] phase ;
localparam FREQ_FTW = 6554; // 頻率控制字 生成5Mhz的正弦波,採樣率50M,round((5/50)*(2^16))
always @(posedge clk)begin
if(rst)begin
valid <= 0;
phase <= 0;
end
else begin
valid <= 1;
phase <= phase + FREQ_FTW;
end
end
wire rdy ;
wire [15:0] cos_out ;
cos_gen_pipeline cos_gen_pipeline(
.clk (clk ),
.rst (rst ),
.valid (valid ), //使能信號
.phase (phase ), //相位,0~65535對應[0~2pi)
.rdy (rdy ), //輸出準備好信號
.cos_out (cos_out)
);
integer file = 0;
initial begin
file = $fopen("cos_gen_pipeline.txt", "w");
if (file == 0) begin
$display("Error opening file");
$finish;
end
end
reg [15:0] data_cnt = 0;
always @(posedge clk)begin
if(rdy)begin
data_cnt <= data_cnt + 1;
$fwrite(file, "%d\n", $signed(cos_out));
if(data_cnt == 4096*4-1)begin
$fclose(file);
$finish;
end
end
end
endmodule
仿真結果:


這樣一個正弦波生成器就完成了,SNR=91db,足以滿足大多數的使用情況了,如果需要更高的精度,可以更改樣本的點數,為了提升頻率精度,需要對相位控制字位寬進行擴展。