CORDIC算法:三角函数的硬件加速革命——从数学原理到FPGA实现的超高效计算方案

计算机该如何求解三角函数?或许你的第一印象是采用泰勒展开,或者采用多项式进行逼近。对于前者,来回的迭代计算开销成本很大;对于后者,多项式式逼近在较窄的范围內比较接近,超过一定范围后,就变得十分不理想了。例如x–>0时,x~sin(x)

今天,我们将要介绍三角函数的另一种求法:CORDIC算法

原理

CORDIC的算法的核心就是通过迭代,利用三角函数的物理性质,不断累积旋转角度,从而得到所求角度的精确近似。

我们假设圆为单位圆,范围且在第一象限,如下图:

在这里插入图片描述

我们假设点(x1,y1)与X轴正半轴夹角为α,那么
{ y 2 = s i n ( α + θ ) x 2 = c o s ( α + θ ) \begin{cases} y_2=sin(α+θ)\\ x_2=cos(α+θ) \end{cases} {y2=sin(α+θ)x2=cos(α+θ)
三角函数展开有
{ y 2 = s i n ( α ) c o s ( θ ) + c o s ( α ) s i n ( θ ) x 2 = c o s ( α ) c o s ( θ ) − s i n ( α ) s i n ( θ ) \begin{cases} y_2=sin(α)cos(θ)+cos(α)sin(θ)\\ x_2=cos(α)cos(θ)-sin(α)sin(θ) \end{cases} {y2=sin(α)cos(θ)+cos(α)sin(θ)x2=cos(α)cos(θ)sin(α)sin(θ)


{ y 1 = s i n ( α ) x 1 = c o s ( α ) \begin{cases} y_1=sin(α)\\ x_1=cos(α) \end{cases} {y1=sin(α)x1=cos(α)
带入上式,有
{ y 2 = s i n ( α ) c o s ( θ ) + c o s ( α ) s i n ( θ ) = y 1 c o s ( θ ) + x 1 s i n ( θ ) = c o s ( θ ) ( y 1 + x 1 t a n ( θ ) ) x 2 = c o s ( α ) c o s ( θ ) − s i n ( α ) s i n ( θ ) = x 1 c o s ( θ ) − y 1 s i n ( θ ) = c o s ( θ ) ( x 1 − y 1 t a n ( θ ) ) \begin{cases} y_2=sin(α)cos(θ)+cos(α)sin(θ)=y_1cos(θ)+x_1sin(θ)=cos(θ)(y_1+x_1tan(θ))\\ x_2=cos(α)cos(θ)-sin(α)sin(θ)=x_1cos(θ)-y_1sin(θ)=cos(θ)(x_1-y_1tan(θ)) \end{cases} {y2=sin(α)cos(θ)+cos(α)sin(θ)=y1cos(θ)+x1sin(θ)=cos(θ)(y1+x1tan(θ))x2=cos(α)cos(θ)sin(α)sin(θ)=x1cos(θ)y1sin(θ)=cos(θ)(x1y1tan(θ))

默认初始值为(1,0),记为
v 0 = [ 1 0 ] v_0=\begin{bmatrix} 1\\ 0 \end{bmatrix} v0=[10]
以上的等式可以表示为旋转矩阵的形式
[ x 2 y 2 ] = c o s ( α ) [ 1 − t a n ( α ) t a n ( α ) 1 ] [ x 1 y 1 ] \begin{bmatrix} x_2\\ y_2 \end{bmatrix} =cos(α) \begin{bmatrix} 1 & -tan(α)\\ tan(α)&1 \end{bmatrix} \begin{bmatrix} x_1\\ y_1 \end{bmatrix} [x2y2]=cos(α)[1tan(α)tan(α)1][x1y1]
如果将令角度α,满足tan(α)=2-i, 那么就将tan和乘法运算就变成乘2的负次幂。对应在计算机中,就是移位计算。因而复杂的计算,就变成了简单的加减和移位运算。

所以我们有
[ x n y n ] = c o s ( α n ) [ 1 − 2 − n 2 − n 1 ] [ x n − 1 y n − 1 ] = c o s ( α n ) c o s ( α n − 1 ) . . c o s ( α 0 ) [ 1 − 2 − n 2 − n 1 ] [ 1 − 2 − n + 1 2 − n + 1 1 ] . . [ 1 − 2 − 0 2 − 0 1 ] [ 1 0 ] \begin{bmatrix} x_n\\ y_n \end{bmatrix} =cos(α_n) \begin{bmatrix} 1 & -2^{-n}\\ 2^{-n}&1 \end{bmatrix} \begin{bmatrix} x_{n-1}\\ y_{n-1} \end{bmatrix}= cos(α_n)cos(α_{n-1})..cos(α_0) \begin{bmatrix} 1 & -2^{-n}\\ 2^{-n}&1 \end{bmatrix} \begin{bmatrix} 1 & -2^{-n+1}\\ 2^{-n+1}&1 \end{bmatrix} .. \begin{bmatrix} 1 & -2^{-0}\\ 2^{-0}&1 \end{bmatrix} \begin{bmatrix} 1\\ 0 \end{bmatrix} [xnyn]=cos(αn)[12n2n1][xn1yn1]=cos(αn)cos(αn1)..cos(α0)[12n2n1][12n+12n+11]..[120201][10]

处理细节

1. 缩放因子

由前面推导,我们可以得到:
[ x n y n ] = c o s ( α n ) [ 1 − 2 − n 2 − n 1 ] [ x n − 1 y n − 1 ] = c o s ( α n ) c o s ( α n − 1 ) . . c o s ( α 0 ) [ 1 − 2 − n 2 − n 1 ] [ 1 − 2 − n + 1 2 − n + 1 1 ] . . [ 1 − 2 − 0 2 − 0 1 ] [ 1 0 ] \begin{bmatrix} x_n\\ y_n \end{bmatrix} =cos(α_n) \begin{bmatrix} 1 & -2^{-n}\\ 2^{-n}&1 \end{bmatrix} \begin{bmatrix} x_{n-1}\\ y_{n-1} \end{bmatrix}= cos(α_n)cos(α_{n-1})..cos(α_0) \begin{bmatrix} 1 & -2^{-n}\\ 2^{-n}&1 \end{bmatrix} \begin{bmatrix} 1 & -2^{-n+1}\\ 2^{-n+1}&1 \end{bmatrix} .. \begin{bmatrix} 1 & -2^{-0}\\ 2^{-0}&1 \end{bmatrix} \begin{bmatrix} 1\\ 0 \end{bmatrix} [xnyn]=cos(αn)[12n2n1][xn1yn1]=cos(αn)cos(αn1)..cos(α0)[12n2n1][12n+12n+11]..[120201][10]

我们可以提前将所有的cos(α)的乘积计算出来,做为一个常量,省去乘法运算,记为K
K = c o s ( α n ) c o s ( α n − 1 ) c o s ( α n − 2 ) . . . c o s ( α 0 ) = 0.60725 K=cos(α_n)cos(α_{n-1})cos(α_{n-2})...cos(α_0)=0.60725 K=cos(αn)cos(αn1)cos(αn2)...cos(α0)=0.60725
2. 旋转方向

通常来说CORDIC算法会引入dn ,来判断旋转方向。当前角度大于该次迭代的角度,dn为正,逆时钟旋转,反之为负,顺时针旋转。之所以会采用双旋转,是因为其通常比单向旋转的收敛性更好,结果更精确。

因而我们迭代可以写为
{ y n + 1 = y n + d n ∗ x n ∗ 2 − n x n + 1 = x n − d ∗ y n ∗ 2 − n a n g l e n + 1 = a n g l e n − d ∗ t a b l e o f a n g l e s [ n ] \begin{cases} y_{n+1}=y_n+d_n*x_n*2^{-n}\\ x_{n+1}=x_n-d*y_n*2^{-n}\\ angle_{n+1}=angle_{n}-d*tableofangles[n] \end{cases} yn+1=yn+dnxn2nxn+1=xndyn2nanglen+1=anglendtableofangles[n]
table_of_angles 存储的是θ值, θn=arctan(2-n);

对应的表格如下:

n2^(-n)arctan(2^(-n))
010.785398163
10.50.463647609
20.250.244978663
30.1250.124354995
40.06250.06241881
50.03150.031239833
60.0156250.015623729
70.00781250.007812341
80.003906250.00390623
90.0019531250.001953123
100.0009765630.000976562
110.0004882810.000488281
120.0002441410.000244141
130.000122070.00012207
146.10352E-056.10352E-05
153.05176E-053.05176E-05

下面我会手把手带领大家从软件建模到硬件实现CORDIC算法,规定输入和输出都是无符号17位数,1位整数位,16位小数位。

Python 代码

测试代码

#初始化部分,定义参数
import math
from math import floorNUM_ITER = 16
Frac_Bits=16
Data_Scale=2**Frac_Bits
Angles_Table=[]#创建对应的对应的角度表
def create_angel_table():   for i in range(NUM_ITER):angles=math.atan(2**(-i))angles=floor(angles*Data_Scale+0.5)/Data_Scale#print(angles)#print(angles)#angles=angles*(1<<Frac_Bits)+0.5#angles=floor(angles)#print(angles)#print(hex(angles))Angles_Table.append(angles)#计算出缩放因子
def compute_k():k=1.0for i in range(NUM_ITER):angles=math.atan(2**(-i))k=k*math.cos(angles)#print(K)#print(hex(floor(K*(1<<Frac_Bits)+0.5)))return  floor(k*Data_Scale+0.5)/Data_Scale  # cordic 算法迭代
def cordic(theta,k):x=ky=0angle_temp= floor(math.radians(theta)*Data_Scale+0.5)/Data_Scale  for i in range(NUM_ITER):if(angle_temp>=0):x_next=x-y*2**(-i)y_next=y+x*2**(-i)angle_temp-=Angles_Table[i]else:   x_next=x+y*2**(-i)y_next=y-x*2**(-i)angle_temp+=Angles_Table[i]x=x_nexty=y_nextreturn x,y#cordic 算法算出的结果,与真实结果进行比较
def compare(ground_truth, test):for i in range(len(ground_truth)): # 如果误差超过 3*2^(-16)次,那么退出比较if( abs(ground_truth[i]-test[i])>3):print("Error! Loss of accuracy! ground_truth: %f, test: %f", ground_truth[i], test[i])return Falsereturn True
#得到cordic算法结果,经行比较
def main():create_angel_table()k=compute_k()cos_truth=[]sin_truth=[]cos_test=[]sin_test=[]for i in range(90):cos_truth.append(floor(math.cos(i*math.pi/180)*Data_Scale+0.5))sin_truth.append(floor(math.sin(i*math.pi/180)*Data_Scale+0.5))cos_temp,sin_temp=cordic(i,k)cos_test.append(floor(cos_temp*Data_Scale+0.5))sin_test.append(floor(sin_temp*Data_Scale+0.5))if (compare(cos_truth,cos_test) and compare(sin_truth,sin_test)):print("Test Pass")else:print("Test Fail")if __name__ == "__main__":main()

比较结果

在这里插入图片描述

由此可知,CORDIC算法精度很高

Verilog 代码

模块代码


module Cordic_Sin(input wire [16:0] theta,   // 输入角度(Q1.16格式,范围0 ~ π/2)output wire [16:0] sin_out, // 输出sin值(Q1.16格式)output wire [16:0] cos_out
);// 预计算参数(Q1.16格式)
localparam signed [16:0] K =17'sh09B75;  // 1/1.64676补偿因子;  17'h1A592;         //Q1.15
reg signed [16:0] angles [0:16];    //arctan(2^-i)
integer iter;
initial beginangles[0]  = 17'h0C910;angles[1]  = 17'h076B2;angles[2]  = 17'h03EB7;angles[3]  = 17'h01FD6;// i=0~3angles[4]  = 17'h00FFB;angles[5]  = 17'h007FF;angles[6]  = 17'h00400;angles[7]  = 17'h00200;// i=4~7angles[8]  = 17'h00100;angles[9]  = 17'h00080;angles[10] = 17'h00040;angles[11] = 17'h00020;// i=8~11angles[12] = 17'h00010;angles[13] = 17'h00008;angles[14] = 17'h00004;angles[15] = 17'h00002;// i=12~15angles[16] = 17'h00001;
endreg signed [32:0]x,y; //初始化 x=K; y=0
reg signed [32:0]x_next,y_next;
reg signed [17:0] angle; // 初始化角度等于输出角度
integer i;always@(*)begin//初始化x={K,16'b0};y=33'h0;angle={1'b0,theta};//迭代计算for(i=0;i<16;i=i+1)beginif(!angle[17])begin   ////正向旋转x_next=x-(y>>>i);  //算术移位y_next=y+(x>>>i);angle=angle-{1'b0,angles[i]};end else begin//负向旋转x_next=x+(y>>>i);y_next=y-(x>>>i);angle=angle+{1'b0,angles[i]};endx=x_next;y=y_next;end
endassign sin_out = y[32:16];
assign cos_out = x[32:16];endmodule

Test Bench

`timescale 1ns / 1psmodule Cordic_Sin_Test();reg  [16:0] theta; wire [16:0] sin_out;wire [16:0] cos_out;initial begintheta=17'h0;#10;//15 theta=17'h4305;#10;//30theta=17'h860A;#10;//45theta=17'hC90F;#10;//60theta=17'h10C15;#10;//75theta=17'h14F1A;#10;//90theta=17'h1921F;#10;endCordic_Sin uut(.theta(theta),   // 输入角度(Q1.16格式,范围0 ~ π/2).sin_out(sin_out),// 输出sin值(Q1.16格式).cos_out(cos_out)
);endmodule

仿真结果

在这里插入图片描述

以上的数据,输入数据需要将其转换成弧度值,然后转换成S0I1F16定点格式,sin,cos准确值也是一样

输入角度sin准确值cos准确值sin计算值cos计算值
06553615465536
15°16962633031696263302
30°32768567563276856755
45°46341463414634046341
60°56756327685675532768
75°63303169626330216962
90°65536065536154

除了个别点外的绝对误差比较大外,其余的计算精度相当高,误差很小

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/73743.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【剪辑_BGM 整合】

【优质BGM➽以剪映为基础】 自定义 一、舒缓惬意 二、轻快 1&#xff0c;快乐骑行 2&#xff0c;医疗科普 3&#xff0c;宣传片励志摇滚热血 Going back to Business 4&#xff0c;电子宠物&#xff08;memories&#xff09; 5&#xff0c;诗与远方&#xff08;热播&…

linux 常见命令使用介绍

Linux 常见命令使用介绍 Linux 是一个功能强大的操作系统&#xff0c;其核心是命令行工具。掌握一些常用的 Linux 命令可以极大地提高工作效率。本文将详细介绍一些常见的 Linux 命令及其用法。 1. 文件与目录操作 ls - 列出文件和目录 # 查看当前目录下的所有文件和子目录&…

Rust从入门到精通之精通篇:24.高级异步编程

高级异步编程 在 Rust 精通篇中&#xff0c;我们将深入探索 Rust 的高级异步编程技术。Rust 的异步编程模型基于 Future 特征和异步运行时&#xff0c;提供了高效的非阻塞 I/O 和并发处理能力。在本章中&#xff0c;我们将超越基础知识&#xff0c;探索如何构建高性能异步系统…

(C语言)学生信息表(基于通讯录改版)(测试版)(C语言项目)

1.首先是头文件&#xff1a; //student.h //头文件//防止头文件被重复包含#pragma once//宏定义符号常量&#xff0c;方便维护和修改 #define ID_MAX 20 #define NAME_MAX 20 #define AGE_MAX 5 #define SEX_MAX 5 #define CLA_MAX 20 //定义初始最大容量 #define MAX 1//定义结…

Problem D: 抽象类

1.题目问题 2.输入 3.输出 4.代码实现 补充&#xff1a; 没错&#xff0c;你没看错&#xff0c;没有 abstract class Vehicle &#xff0c;才能过。 恶心人 答案&#xff1a; {abstract void NoOfWheels(); }class Car extends Vehicle {Overridepublic void NoOfWheels()…

UniApp开发多端应用——流式语音交互场景优化

一、问题背景&#xff1a;UniApp默认方案的局限性 在流式语音交互场景&#xff08;如AI语音助手、实时字幕生成&#xff09;中&#xff0c;UniApp默认的uni.getRecorderManager 和uni.createInnerAudioContext 存在以下瓶颈&#xff1a; 录音端&#xff1a; 延迟高&#xff1…

docker构建并启动前端

docker文件示例代码&#xff1a; # Use a minimal image for development FROM node:18-alpine# Set working directory inside the container WORKDIR /app# Copy package.json and package-lock.json (or yarn.lock) into the container COPY package.json package-lock.jso…

25大唐杯赛道一本科B组大纲总结(上)

25大唐杯省赛马上要开始&#xff0c;还没开始准备的要抓紧了 可看我之前发的备赛攻略&#xff0c;理论的准备要先将大纲整理成思维导图框架 然后根据重点&#xff0c;在资料中寻找&#xff0c;记忆 这里帮大家整理好了&#xff0c;后续其他组别会相继更新 基于竞赛大纲做的思…

【Python3教程】Python3基础篇之Lambda(匿名函数)

博主介绍:✌全网粉丝22W+,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物联网、机器学习等设计与开发。 感兴趣的可…

重试机制之指针退避策略算法

一、目的&#xff1a;随着重试次数增加&#xff0c;逐步延长重连等待时间&#xff0c;避免加重服务器负担。 二、计算公式&#xff1a; 每次重试的延迟时间 初始间隔 (退避基数 ^ 重试次数) 通常设置上限防止等待时间过长。 const delay Math.min(initialDelay * Math.pow…

SSE SseEmitter.completeWithError(e) 触发的处理逻辑

在 Java 客户端使用 OkHttp 监听 SSE&#xff08;Server-Sent Events&#xff09; 的情况下&#xff0c;当服务端调用 SseEmitter.completeWithError(e)&#xff0c;客户端会触发 EventSourceListener 的 onFailure() 方法&#xff08;而不是 onError&#xff09;。 1. 服务端&…

4月手机新品前瞻,影像,性能与设计卷得起飞

在智能手机市场中,4月向来是新品频发的黄金时段。各大手机厂商纷纷摩拳擦掌,准备推出自家的重磅机型,在影像、性能与设计等核心领域展开激烈角逐,一场没有硝烟的“科技大战”即将拉开帷幕。接下来,让我们一同深入了解那些备受瞩目的新品,提前感受科技进步带来的魅力。 一…

设计审查效率革命|CAD原生数据直通自动公差验证

“为何 90% 的 GD&T 问题在设计评审时未被发现&#xff1f;怎样避免因 GD&T 考虑不周导致的批量返工&#xff1f;” 这正是 CETOL 自动辅助审查设计系统要解决的核心问题&#xff1a;通过200结构化审查规则拦截潜在设计疏漏。 功能一&#xff1a;装配约束健康诊断&…

k8s scheduler几种扩展方式的关系及区别

网上关于scheduler扩展介绍的文章很多&#xff0c;但都是东说一句西说一嘴&#xff0c;完全没有逻辑性&#xff0c;对于逻辑建构者看着很痛苦&#xff0c;这篇文章不会深入教你怎么扩展&#xff0c;而是教你几种扩展方式的关系和逻辑结构&#xff1a; 目前Kubernetes支持五种方…

近场探头的选型

近场探头包括磁场探头和电场探头。 下图中画圈的是电场探头&#xff1a; 左侧3只是磁场探头&#xff0c;最右侧一只是电场探头。不同孔径的磁场探头的有效测量距离和分辨率不同 电场探头和磁场探头分别在什么情况下使用&#xff1a; 一般近场测试&#xff0c;使用的都是磁场探…

Pycharm运行时报“Empty suite”,可能是忽略了这个问题

问题&#xff1a;使用Pycharm运行testcases目录下的.py文件&#xff0c;报“Empty suite”&#xff0c;没有找到测试项。 排查过python解释器、pytest框架安装等等&#xff0c;依然报这个错&#xff0c;依然没找到&#xff0c;最后终端运行&#xff1a; pytest test_demo.py&a…

鸿蒙北向应用开发:deveco 5.0 kit化文件相关2

鸿蒙北向应用开发:deveco 5.0 kit化文件相关 在kit化时,有时候会出现这样一种场景即你想把已有的d.ts导出换个名字,这样从名字上更贴合你的kit聚合 什么意思呢?比如现在有 ohos.hilog.d.ts 导出了hilog,现在你想kit化hilog,使得hilog导出名字为usrhilog,这样用户在使用你的k…

《Python实战进阶》No37: 强化学习入门:Q-Learning 与 DQN-加餐版1 Q-Learning算法可视化

在《Python实战进阶》No37: 强化学习入门&#xff1a;Q-Learning 与 DQN 这篇文章中&#xff0c;我们介绍了Q-Learning算法走出迷宫的代码实践&#xff0c;本文加餐&#xff0c;把Q-Learning算法通过代码可视化呈现。我尝试了使用Matplotlib实现&#xff0c;但局限于Matplotli…

Linux 搭建dns主域解析,和反向解析

#!/bin/bash # DNS主域名服务 # user li 20250325# 检查当前用户是否为root用户 # 因为配置DNS服务通常需要较高的权限&#xff0c;只有root用户才能进行一些关键操作 if [ "$USER" ! "root" ]; then# 如果不是root用户&#xff0c;输出错误信息echo "…

GenBI 中如何引入 LLM 做意图路由,区分查数据还是闲聊

写在前面 生成式商业智能(Generative BI, GenBI)的魅力在于其能够理解用户的自然语言,并将复杂的数据查询和分析过程自动化。用户不再需要学习 SQL 或操作复杂的界面,只需像与同事交谈一样提出问题,就能获得数据洞察。然而,一个现实的挑战是:用户的输入并非总是明确的数…