poch路障 2026-01-01 17:05 采纳率: 0%
浏览 4

【fpga】CORDIC算法双曲模式

[FPGA] QAQ 今天写cordic算法,双曲模式中出现一个问题,在进行迭代时,第一次的数据迭代不发生变化,但是第二次迭代是正确的。
我起初以为是开始数据处理出现了问题,但是在cnt = 0 处理后发现最后cnt = 0 和cnt = 1 的数据刚好抵消了

module cordic_sqrt(
    input                           clk,
    input                           rst_n,
    input           signed  [31:0]  theta, //输入为弧度
    input           signed  [31:0]  x, //X0
    input           signed  [31:0]  y, //Y0
    output reg      signed  [31:0]  sinh,
    output reg      signed  [31:0]  cosh,
    output reg      signed  [31:0]  exp,
    output reg      signed  [31:0]  ln,
    output reg                      cs_dv
);
//双曲模式最少进行24次迭代
parameter ITERATION_NUM = 19;       //迭代次数
parameter K = 32'sh0D402;//0.828159860*2^16 
parameter F_K =   32'sd79_134;   
//parameter K = 32'H9B74; //0.6072529350088812
//parameter F_K = 32'H1A592;//1.646760258121066
//状态机
localparam IDLE = 3'b001; //空闲状态
localparam WORK = 3'b010; //迭代计算
localparam DONE = 3'b100; //输出


reg signed [31:0] x_shift;
reg signed [31:0] y_shift;
reg signed [31:0] x_prev;
reg signed [31:0] y_prev;
reg signed [31:0] z_rot;
reg D_sign;
wire D_sign_t;

//查找表
wire [31:0] theta_angle [18:0];
//弧度
//迭代 16次 + 重复2次(4) = 18
//双曲模式查找表(修正重复迭代点)
wire signed [31:0] theta_angle [18:0];
assign theta_angle[0]  = 32'd0;
assign theta_angle[1]  = 32'd35999;  //arctanh(1/2)×65536
assign theta_angle[2]  = 32'd16739;  //arctanh(1/4)×65536
assign theta_angle[3]  = 32'd8235;   //arctanh(1/8)×65536
assign theta_angle[4]  = 32'd4101;   //arctanh(1/16)×65536(重复起点)
assign theta_angle[5]  = 32'd4101;   //arctanh(1/16)×65536(重复迭代)
assign theta_angle[6]  = 32'd2049;   //arctanh(1/32)×65536
assign theta_angle[7]  = 32'd1024;   //arctanh(1/64)×65536
assign theta_angle[8]  = 32'd512;    //arctanh(1/128)×65536
assign theta_angle[9]  = 32'd256;    //arctanh(1/256)×65536
assign theta_angle[10] = 32'd128;    //arctanh(1/512)×65536
assign theta_angle[11] = 32'd64;     //arctanh(1/1024)×65536
assign theta_angle[12] = 32'd32;     //arctanh(1/2048)×65536
assign theta_angle[13] = 32'd16;     //arctanh(1/4096)×65536(重复起点)
assign theta_angle[14] = 32'd16;     //arctanh(1/4096)×65536(重复迭代)
assign theta_angle[15] = 32'd8;      //arctanh(1/8192)×65536
assign theta_angle[16] = 32'd4;      //arctanh(1/16384)×65536
assign theta_angle[17] = 32'd2;      //arctanh(1/32768)×65536
assign theta_angle[18] = 32'd1;      //arctanh(1/65536)×65536

//状态机三段式 

reg [2:0] state_c;
reg [2:0] state_n;

//状态转移
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)
        state_c <= IDLE;
    else
        state_c <= state_n;
end

//组合逻辑判断状态转移
reg skip_en;

always@(*)begin
    state_n = IDLE;
    case(state_c)
        IDLE : begin
            if(skip_en)
                state_n = WORK;
            else
                state_n = IDLE;
        end
        WORK : begin
            if(skip_en)
                state_n = DONE;
            else
                state_n = WORK;
        end
        DONE : begin
            if(skip_en)
                state_n = IDLE;
            else
                state_n = DONE;
        end
        default : state_n = IDLE;
    endcase
end

//时序逻辑电路判断状态输出
//wire [31:0] theta_t;

//Di  第i时钟的z的正负影响第i+1时钟
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        D_sign <= 1'b0;
    end
    else begin
        D_sign <= D_sign_t;
    end
end

assign D_sign_t = z_rot == theta ? 1'b0 : ~z_rot[31]; 

reg [5:0] cnt; //当前迭代次数

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        x_shift <= 32'sd0;
        y_shift <= 32'sd0;
        x_prev  <= 32'sd0;
        y_prev  <= 32'sd0;
        skip_en <= 1'b0;
        cnt     <= 6'd0 ;
        sinh    <= 32'd0;
        cosh    <= 32'd0;
        exp     <= 32'd0;
        ln      <= 32'd0;
        cs_dv <= 1'b0;
        z_rot <= 32'd0;
    end
    else begin
        //为x,y赋初值
        skip_en <= 1'b0;
        x_prev  <= x_shift;
        y_prev  <= y_shift;
        case(state_n)
            IDLE : begin
                x_shift <= x;
                y_shift <= y;
                z_rot <= theta;
                if(cnt == 0) //如果迭代次数小于16,此时进行迭代
                    skip_en <= 1'b1;
                else
                    skip_en <= 1'b0;
            end
            WORK : begin //WORK状态进行迭代 共迭代16次
                //第i次迭代,右移i位
                skip_en <= 1'b0;
                if(cnt == ITERATION_NUM - 1)
                    skip_en <= 1'b1;
                //双曲计算    
                else if(cnt != 0)begin
                    if(D_sign_t) begin //di 为1
                        x_shift       <= $signed(x_prev) + $signed( (y_prev>>>cnt) );
                        y_shift       <= $signed(y_prev) + $signed( (x_prev>>>cnt) );
                        z_rot         <= z_rot   - theta_angle[cnt];
                    end
                    else begin //di 为0
                        x_shift       <= $signed(x_prev) - $signed( (y_prev>>>cnt) );
                        y_shift       <= $signed(y_prev) - $signed( (x_prev>>>cnt) );
                        z_rot         <= z_rot + theta_angle[cnt];
                    end
                    cnt <= cnt + 1;
                end
                else 
                    cnt <= cnt + 1; 
            end
            DONE : begin //计算赋值
                if(cnt == ITERATION_NUM - 1)begin // 迭代完成,输出
                    sinh <= y_shift * F_K;
                    cosh <= x_shift * F_K;
                    ln <= z_rot;
                    exp <= sinh + cosh;
                    skip_en <= 1'b1;
                end
                else ;
            end
        endcase
    end
end

endmodule

TESTBENCH

`timescale 1ns/1ns

module sqrt_tb;
    localparam CLK_PERIOD  = 20;
    parameter ITERATION_NUM = 16; //迭代次数
    //parameter K = 32'H9B74; //0.6072529350088812
    //parameter F_K = 32'H1A592; //1.646760258121066
    parameter K = 32'sh0D402;//0.828159860*2^16 
    parameter F_K =   32'sd79_134; //1.207496340138968
    reg         clk   ;
    reg         rst_n ;
    reg [31:0]  theta ;
    reg [31:0]  x     ;
    reg [31:0]  y     ;
    wire [31:0]  sinh  ;
    wire [31:0]  cosh  ;
    wire [31:0]  exp   ;
    wire [31:0]  ln    ;
    wire         cs_dv ;

    // 1. 定义文件句柄
    integer data_file; // 用于写入数据的文件句柄

    always #(CLK_PERIOD/2) clk = ~clk;

    initial begin
        // 2. 打开文件(仿真开始前)
        data_file = $fopen("simulation_data.txt", "w"); // "w"表示写入模式,会覆盖已有文件
        if (!data_file) begin
            $display("错误:无法打开文件!");
            $stop;
        end
        // 可选:写入文件头
        $fdisplay(data_file, "时间\tx_shift\ty_shift\tz_rot\tcnt\tD_sign\tD_sign_t"); // 使用制表符分隔

        clk = 1'b0;
        rst_n = 1'b0;
        x = 32'sd0;
        y = 32'sd0;
        #5 rst_n = 1'b1;
        //计算sinh,cosh
        x = 32'sd54274; 
        y = 32'sd0; 
        theta =  32'd32_768; //0.5 * 65536
    end

    cordic_sqrt u_cordic_sqrt(
        .clk  (clk  ),
        .rst_n(rst_n),
        .theta(theta),
        .x    (x    ),
        .y    (y    ),
        .sinh (sinh ),
        .cosh (cosh ),
        .exp  (exp  ),
        .ln   (ln   ),
        .cs_dv(cs_dv)
    );

    // 3. 捕获和写入数据
    always @(posedge clk) begin
        if (rst_n) begin // 确保在复位完成后才记录数据
            // 使用层次化路径访问DUT内部信号
            // 关键:使用 $fstrobe 确保采样到非阻塞赋值后的稳定值
            $fstrobe(data_file, "%0t\t%d\t%d\t%d\t%b\t%b\t%b", 
                     $time, 
                     u_cordic_sqrt.x_shift, // 访问内部reg
                     u_cordic_sqrt.y_shift, 
                     u_cordic_sqrt.z_rot, 
                     u_cordic_sqrt.cnt,
                     u_cordic_sqrt.D_sign_t,
                     u_cordic_sqrt.D_sign); // 访问内部wire
        end
    end

    // 4. 控制仿真时长并关闭文件
    initial begin
        #5000; // 根据实际需要调整仿真时间
        $fclose(data_file);
        $display("仿真完成。数据已写入 simulation_data.txt");
        $finish;
    end

endmodule

  • 写回答

1条回答 默认 最新

  • 紫雾凌寒 新星创作者: 人工智能技术领域 2026-01-01 17:07
    关注

    让 “紫雾凌寒” 来帮你回答,本回答结合 DeepSeek

    如果还有问题,请评论或点击头像私信,如果有帮助到您, 请关注、采纳、点赞

    1. 关键点分析:
      - 双曲模式下CORDIC算法迭代时第一次数据迭代不变,第二次才正确。
      - 已排查开始数据处理,且在cnt = 0时有处理动作。
    2. 解决方案:
      - 首先检查在cnt = 0时对输入数据的处理是否正确。比如是否进行了初始化或者特殊的配置,若有,查看这些操作是否完整或正确影响到了后续迭代。
      - 检查第一步迭代的计算逻辑。看在第一次迭代时,各个参数是否按照双曲CORDIC算法的公式正确计算。例如,双曲CORDIC算法的迭代公式可能类似于:
      x_{n + 1}=x_n - y_n * 2^{-n} * sinh(\theta_n) y_{n + 1}=y_n + x_n * 2^{-n} * sinh(\theta_n) \theta_{n + 1}=\theta_n-\atanh(2^{-n})
      检查在第一次迭代时,这些公式中的各项计算是否准确,特别是系数2^{-n}的计算是否正确,以及sinh(\theta_n)atanh(2^{-n})的计算是否无误。
      - 查看是否存在数据存储或传输的问题。比如在第一次迭代时,数据是否正确写入到后续计算需要使用的寄存器中,是否存在数据溢出或者截断等情况影响了第一次迭代的结果。
      - 示例代码(以Verilog为例,假设简单的双曲CORDIC迭代模块):
      ```verilog
      module cordic_hyperbolic (
      input wire clk,
      input wire rst_n,
      input wire [31:0] x_in,
      input wire [31:0] y_in,
      output reg [31:0] x_out,
      output reg [31:0] y_out
      );
      reg [31:0] x_reg, y_reg;
      reg [31:0] angle_reg;
      reg [4:0] cnt;

      initial begin
      x_reg = x_in;
      y_reg = y_in;
      angle_reg = 0;
      cnt = 0;
      end

      always @(posedge clk or negedge rst_n) begin
      if (!rst_n) begin
      x_reg = x_in;
      y_reg = y_in;
      angle_reg = 0;
      cnt = 0;
      x_out = 0;
      y_out = 0;
      end else begin
      case (cnt)
      0: begin
      // 第一次迭代计算
      reg [31:0] sinh_theta;
      reg [31:0] atanh_2n;
      // 计算sinh(angle_reg)
      sinh_theta =…; // 这里需要按照双曲函数计算逻辑实现
      // 计算atanh(2^-cnt)
      atanh_2n =…; // 这里需要按照反双曲函数计算逻辑实现
      x_reg = x_reg - y_reg * (1 << -cnt) * sinh_theta;
      y_reg = y_reg + x_reg * (1 << -cnt) * sinh_theta;
      angle_reg = angle_reg - atanh_2n;
      cnt = cnt + 1;
      end
      default: begin
      // 后续迭代计算,逻辑同第一次迭代类似
      reg [31:0] sinh_theta;
      reg [31:0] atanh_2n;
      // 计算sinh(angle_reg)
      sinh_theta =…;
      // 计算atanh(2^-cnt)
      atanh_2n =…;
      x_reg = x_reg - y_reg * (1 << -cnt) * sinh_theta;
      y_reg = y_reg + x_reg * (1 << -cnt) * sinh_theta;
      angle_reg = angle_reg - atanh_2n;
      if (cnt == ITERATION_COUNT) begin
      x_out = x_reg;
      y_out = y_reg;
      end
      cnt = cnt + 1;
      end
      endcase
      end
      end
      endmodule
      ```
      3. 多种解决方案优缺点:
      - 优点:
      - 全面检查输入处理、计算逻辑和数据存储传输等方面,能较为系统地找出问题所在。
      - 示例代码可以作为一个参考框架,帮助理解和排查问题。
      - 缺点:
      - 排查过程较为繁琐,需要对算法原理和硬件实现细节都有深入了解。
      - 对于复杂的FPGA系统,可能存在多个模块交互影响,定位问题可能需要花费较多时间。
      4. 总结:
      - 针对双曲模式下CORDIC算法第一次迭代数据不变的问题,要从输入数据处理、迭代计算逻辑以及数据存储传输等多方面进行检查。通过详细分析和调试,利用示例代码中的思路和方法,逐步定位并解决问题,确保算法在FPGA上的正确运行。

    希望以上解答对您有所帮助。如果您有任何疑问,欢迎在评论区提出。

    评论

报告相同问题?

问题事件

  • 创建了问题 1月1日