在FPGA上实现DDS

        DDS(Direct Digital Synthesis),直接数字合成信号。核心思想就是使用FPGA的时钟,根据需要的频率来创造波形。

        首先这是一个生成简易方波的代码

module SimpleDDS(DAC_clk, DAC_data);
input DAC_clk;
output [9:0] DAC_data;

// let's create a 16-bit free-running binary counter
reg [15:0] cnt;
always @(posedge DAC_clk) cnt <= cnt + 16'h1;

// and use it to generate the DAC signal output
wire cnt_tap = cnt[7];     // we take one bit out of the counter (here bit 7 = the 8th bit)
assign DAC_data = {10{cnt_tap}};   // and we duplicate it 10 times to create the 10-bit DAC value 
                                     // with the maximum possible amplitude
endmodule

    

        这段代码通过取一个16位数据的某一位作为方波输出,通过cnt的不断自增来控制方波产生的频率。这样所产生的方波的频率应该F(晶振的频率)/(n+1)(n代表第几位作为输出),因为选择的是cnt[7]也就是在0-127范围内cnt[7] = 0,在128-255时cnt[7] = 1,然后再到256-383又为0了。如此往复。可见这样做的方波的占空比为50%,也就是每256个晶振次数为1个周期。所以,我们可以计算出这个方波的频率 f = 50M/256

        若将最后一行替换成assign DAC_data = cnt[9:0];就会生成锯齿波,每自增到1023就归零。若替换成assign DAC_data = cnt[10] ? ~cnt[9:0] : cnt[9:0];就生成对称的三角波,归零节点反转一下就行。

        目前挣扎在有关波形表的选择上(因为是是写入四分之一个波形的数据,所以又两个镜像算法,有可能又出问题),之前傻傻的拿线性+1的表,应该要用公式来模拟正弦函数上的点才对。

        首先我们是使用量化512个正弦波的四分之一个波形,然后在通过算法镜像对称过来。下面的是一段查表代码。首先我们需要知道sine_lut.hex包含的是512个9位数据(由python根据正弦波波形生成)——刚好对应九位的RA覆盖0-511个数据。而RD是10位是因为它必须是一个有符号数才能呢保证这个函数正常,否则只有一般的波形。

        下面是verilog读取外部文件的代码

module LUT(
  CLK    ,           // clock
  RA     ,           // read address
  RD     );          // read data
input          CLK;
input  [8 :0] RA;
output [9 :0] RD;
reg    [9 :0] RD;

 // 声明一个 512×10 的存储
  reg [9:0] mem [0:512];

  // 仿真时和综合时都读取外部文件初始化
  initial begin
    $readmemb("sine_lut.hex", mem);
  end

  // 每个时钟读出对应地址数据
  always @(posedge CLK) begin  
    RD <= #1 mem[RA];
  end

endmodule 

        10位的输出波形,使用相位自增量PHASE_INC 来控制波形的频率,使用phase相位累加器来判断什么时候进行波形的对称算法。当包含的地址已被寻址完毕,即phase[9]即将等于1的时候(此时phase = 00111111111),代表四分之一个波形表示完毕。要表示下一个四分之一,则将波形数据从高相抵递减即可,即 LUT 地址取反(相当于 511-addr)。而当走完二分之一个波形时,必须要来到负半轴了,这时通过phase[10]这个标志位来进行判断(也就是又采集完512个数据了即2的9次方),将波形数据有符号化,并且通过相应算法进行反转。

        使用四分之一个波形数据合成完整波。

module DDS(
    input clk,
    output reg signed [9:0] value  // 最终输出为10位有符号数
);

    // 相位累加器:全周期为2048个采样点(11位)
    reg [10:0] phase;
    // 这里的相位增量可以根据需要调整(例如控制输出频率)
    parameter PHASE_INC = 11'd20;  // 示例值

    always @(posedge clk) begin
        phase <= phase + PHASE_INC;
    end

    // 利用第一重对称性:
    // 当 phase[9]=0 时,表地址直接用 phase[8:0];当 phase[9]=1 时,需要镜像:即 LUT 地址取反(相当于 511-addr)
    wire [8:0] lut_addr = phase[9] ? ~phase[8:0] : phase[8:0];

    // 实例化四分之一正弦波 LUT,数据宽度10位,共512个数据点
    // 请确保你已有如下端口定义的 LUT 模块:
    // module quarter_sine_LUT(input clk, input [8:0] addr, output reg [9:0] data);
    wire [9:0] sine_quarter;
 LUT u_quarter_sine (
        .CLK(clk),
        .RA(lut_addr),
        .RD(sine_quarter)
		);

    // 由于使用 Block RAM 作 LUT 可能会有2个时钟周期的延迟,
    // 因此我们需要对相位最高位(phase[10],用于实现全周期2次对称)进行两级流水寄存
    reg delay1, delay2;
    always @(posedge clk) begin
        delay1 <= phase[10];
        delay2 <= delay1;
    end

    // 应用第二重对称性:如果 delay2 为高,则输出取反,即获得负半周期
    // 将 LUT 输出(本来代表 0 ~ 正极值)转换为有符号数后,根据 delay2 选择符号
    wire signed [9:0] sine_sym = delay2 ? - $signed(sine_quarter) : $signed(sine_quarter);

    // 最终输出在第三个时钟沿寄存,实现整体3个时钟周期延迟
    always @(posedge clk) begin
        value <= sine_sym;
    end

endmodule

        在完成简单的波形的生成之后,我们需要对产生的波形进行操作,使其频率按我们想要的方式变化。

变化频率:

        90/512 = 0.17578125°,也就是每此增加正弦波都会移动这个度数,借此我们也可以推测出一个用于计算频率的公式,即

\dfrac{fsys*PhazeINC}{512}=f1

        其中512是分辨率,即我们又512个采样点,fsys是系统时钟。若要提高频率,那么我们只要加大PhazeINC的值即可,但是如果我们想让频率减少怎么办,直觉上来看使得Phaze_INC = 0.5即可,但是verilog不支持这样的语法,因此我们就要引入定点数的概念来提高采样点的分辨率了。

        我们将Phaze_INC改造成Q11.5的定点数,也就是高11位的数据代表整数,低5位代表小数。则可确定定标因子Scale2^5 = 32,那么浮点数0.5转换为定点数就是 32 * 0.5 = 16

注意这样改了之后都是定点数,需要自己进行一下浮点数到定点数的转换,后面可以写个程序自动转换一下。

改进后并且封装成sine模块

        

module sine(
    input clk,
    output reg signed [9:0] value  // 最终输出为10位有符号数
);

    // 相位累加器:全周期为2048个采样点(11位)
//改成Q11.5的定点数
    reg [15:0] phase;
    // 这里的相位增量可以根据需要调整(例如控制输出频率)
    parameter PHASE_INC = 16'd360;  // 示例值

    always @(posedge clk) begin
        phase <= phase + PHASE_INC;
    end
		
	 wire [10:0] phase_int = phase[15:5];
    // 利用第一重对称性:
    // 当 phase[9]=0 时,表地址直接用 phase[8:0];当 phase[9]=1 时,需要镜像:即 LUT 地址取反(相当于 511-addr)
    wire [8:0] lut_addr = phase_int[9] ? ~phase_int[8:0] : phase_int[8:0];

    // 实例化四分之一正弦波 LUT,数据宽度10位,共512个数据点
    // 请确保你已有如下端口定义的 LUT 模块:
    // module quarter_sine_LUT(input clk, input [8:0] addr, output reg [9:0] data);
    wire [9:0] sine_quarter;
 LUT u_quarter_sine (
        .CLK(clk),
        .RA(lut_addr),
        .RD(sine_quarter)
		);

    // 由于使用 Block RAM 作 LUT 可能会有2个时钟周期的延迟,
    // 因此我们需要对相位最高位(phase[10],用于实现全周期2次对称)进行两级流水寄存
    reg delay1, delay2;
    always @(posedge clk) begin
        delay1 <= phase_int[10];
        delay2 <= delay1;
    end

    // 应用第二重对称性:如果 delay2 为高,则输出取反,即获得负半周期
    // 将 LUT 输出(本来代表 0 ~ 正极值)转换为有符号数后,根据 delay2 选择符号
    wire signed [9:0] sine_sym = delay2 ? - $signed(sine_quarter) : $signed(sine_quarter);

    // 最终输出在第三个时钟沿寄存,实现整体3个时钟周期延迟
    always @(posedge clk) begin
        value <= sine_sym;
    end

endmodule

        有关线性插值,当到跨越节点进行采样波形的时候,会出现失真,我们通过线性插值来缓解这一个问题。(注意我们现在使用的都是Q11.5的定点数)1->32

        产生波形的模块

module sine(
    input clk,
	 input [15:0] phase,
    output reg signed [9:0] value  // 最终输出为10位有符号数
);

    // 相位累加器:全周期为2048个采样点(11位)
	 //改成Q11.5的定点数
    //reg [15:0] phase;
    // 这里的相位增量可以根据需要调整(例如控制输出频率)
    //parameter PHASE_INC = 16'd360;  // 示例值

	 
	 wire [10:0] phase_int = phase[15:5];
    // 利用第一重对称性:
    // 当 phase[9]=0 时,表地址直接用 phase[8:0];当 phase[9]=1 时,需要镜像:即 LUT 地址取反(相当于 511-addr)
    wire [8:0] lut_addr = phase_int[9] ? ~phase_int[8:0] : phase_int[8:0];

    // 实例化四分之一正弦波 LUT,数据宽度10位,共512个数据点
    // 请确保你已有如下端口定义的 LUT 模块:
    // module quarter_sine_LUT(input clk, input [8:0] addr, output reg [9:0] data);
    wire [9:0] sine_quarter;
 LUT u_quarter_sine (
        .CLK(clk),
        .RA(lut_addr),
        .RD(sine_quarter)
		);

    // 由于使用 Block RAM 作 LUT 可能会有2个时钟周期的延迟,
    // 因此我们需要对相位最高位(phase[10],用于实现全周期2次对称)进行两级流水寄存
    reg delay1, delay2;
    always @(posedge clk) begin
        delay1 <= phase_int[10];
        delay2 <= delay1;
    end

    // 应用第二重对称性:如果 delay2 为高,则输出取反,即获得负半周期
    // 将 LUT 输出(本来代表 0 ~ 正极值)转换为有符号数后,根据 delay2 选择符号
    wire signed [9:0] sine_sym = delay2 ? - $signed(sine_quarter) : $signed(sine_quarter);

    // 最终输出在第三个时钟沿寄存,实现整体3个时钟周期延迟
    always @(posedge clk) begin
        value <= sine_sym;
    end

endmodule

        首先我们采样两个波形,一个正常的,另一个是超前一点的。最后通过线性插值公式

Wave = (1-x)*A+x*B得到最后的波形。注意定点数相乘需要右移n位(n又定点数格式决定)来磨除小数点的影响。

        注意:我原本写的地址偏移,不管用,我怀疑是这会儿phase还没更新呢,加了也是原来的值。

always @(posedge clk) begin
    phase_shifted <= phase + 16'd3560;
end

后面改成这样,地址就有明显的偏移了

always @(posedge clk) begin
    phase <= phase + PHASE_INC;
end

// 2) 用最新 phase 生成偏移相位
always @(posedge clk) begin
    phase_shifted <= phase_shifted + 16'd3560;
end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值