PLL 的 verilog 实现

  锁相环(PLL)是一种常用的频率、相位追踪算法,在信号解调、交流并网等领域有着广泛的应用。本文对全数字锁相环的原理进行介绍,随后给出 verilog 实现及仿真。

PLL 锁相原理

  锁相环结构如下图所示,主要由鉴相器、环路滤波器、压控振荡器等构成

在这里插入图片描述

其中鉴相器是一个乘法器,设参考信号 u i u_i ui 、本地信号 u o u_o uo 均为正弦信号
u i ( t ) = c o s ( ω 1 t + φ 1 ) u_i(t)=cos(\omega_1 t+\varphi_1) ui(t)=cos(ω1t+φ1)

u o ( t ) = c o s ( ω 2 t + φ 2 ) u_o(t)=cos(\omega_2 t+\varphi_2) uo(t)=cos(ω2t+φ2)

  根据积化和差公式, u i u_i ui u o u_o uo 的乘积将包含 ω 1 + ω 2 \omega_1+\omega_2 ω1+ω2 ω 1 − ω 2 \omega_1-\omega_2 ω1ω2 两个频率分量,经过 LF 低通滤波后,将仅剩两者的差频信号
u c = c o s [ ( ω 1 − ω 2 ) t + ( φ 1 − φ 2 ) ] = c o s [ 2 π ( f 1 − f 2 ) t + ( φ 1 − φ 2 ) ] \begin{aligned} u_c&=cos[(\omega_1-\omega_2)t+(\varphi_1-\varphi_2)]\\ &=cos[2\pi(f_1-f_2)t+(\varphi_1-\varphi_2)] \end{aligned} uc=cos[(ω1ω2)t+(φ1φ2)]=cos[2π(f1f2)t+(φ1φ2)]
使用 f 2 = f 0 + K 0 u c f_2=f_0+K_0 u_c f2=f0+K0uc 控制压控振荡器(数字式的一般用 DDS 技术生成)的频率,即可完成锁相。

  假设输入信号相对于基准频率 f 0 f_0 f0 存在 Δ f \Delta f Δf 的频率偏差,则完成锁相后两信号将具有固定的相位偏差 Δ φ \Delta \varphi Δφ,关系如下
Δ f = K 0 c o s ( Δ φ ) \Delta f=K_0cos(\Delta \varphi) Δf=K0cos(Δφ)
当然也应当注意到这里的 Δ φ \Delta \varphi Δφ 符号无法被确定。

verilog 实现

  PLL 模块主程序如下

/* 
 * file			: ADPLL.v
 * author		: 今朝无言
 * lab		    : WHU-EIS-LMSWE
 * date			: 2023-08-03
 * version		: v1.0
 * description	: 锁相环
 * Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.
 */
module ADPLL(
input						clk,
input						rst_n,

input		signed	[15:0]	A,		//参考信号
input		signed	[15:0]	B,		//本地信号

output		signed	[15:0]	df		//频偏
);

parameter	CLK_FREQ	= 1_000_000;	//采样频率

reg signed	[15:0]	df	= 16'd0;

//-----------------------multi---------------------------------
reg	signed	[31:0]	multi	= 32'd0;

always @(posedge clk) begin
	if(~rst_n) begin
		multi	<= 32'd0;
	end
	else begin
		multi	<= A*B;
	end
end

//------------------------FIR---------------------------------
wire	signed	[15:0]	multi_filt  [1:3];

localparam	FIR_N = 20;	//FIR阶数

wire	[16*(FIR_N+1)-1:0]	FIR_params;

FIR_params_0d1 FIR_params_inst(
	.params		(FIR_params)
);

wire    clk_div10;
wire    clk_div100;

clkdiv #(.N(10)) clkdiv10(
	.clk_in     (clk),
	.clk_out    (clk_div10)
);

clkdiv #(.N(100)) clkdiv100(
	.clk_in     (clk),
	.clk_out    (clk_div100)
);

//低通滤波						多级低通滤波,中间穿插下采样
FIR_filter #(.N(FIR_N + 1))
FIR_filter_inst1(
	.clk			(clk),
	.rst_n			(rst_n),

	.filter_params	(FIR_params),

	.data_in		(multi[31:16]),
	.data_out		(multi_filt[1])
);

//低通滤波
FIR_filter #(.N(FIR_N + 1))
FIR_filter_inst2(
	.clk			(clk_div10),
	.rst_n			(rst_n),

	.filter_params	(FIR_params),

	.data_in		(multi_filt[1]),
	.data_out		(multi_filt[2])
);

//低通滤波
FIR_filter #(.N(FIR_N + 1))
FIR_filter_inst3(
	.clk			(clk_div100),
	.rst_n			(rst_n),

	.filter_params	(FIR_params),

	.data_in		(multi_filt[2]),
	.data_out		(multi_filt[3])
);

//---------------------control---------------------------------
always @(posedge clk_div100) begin
	df	<= multi_filt[3];		//  df=K*multi_filt,此处省略鉴相灵敏度K,外部请自行设置合理的K值s
end

endmodule

  低通滤波器及其参数代码如下

/* 
 * file         : FIR_filter.v
 * author       : 今朝无言
 * lab		    : WHU-EIS-LMSWE
 * date		    : 2023-07-03
 * version      : v1.0
 * description  : FIR 滤波器
 */
module FIR_filter(
input							clk,
input							rst_n,

input				[16*N-1:0]	filter_params,

input		signed	[15:0]		data_in,
output	reg	signed	[15:0]		data_out
);

parameter	N		= 32;	//滤波器参数个数
parameter	div_N	= 16;	//sum结果除 2^div_N,作为 filter 的输出

//FIR 滤波器参数
reg	signed	[15:0] b[0:N-1];

integer	m;
always @(*) begin
	for(m=0; m<N; m=m+1) begin
		b[m]	<= filter_params[(m << 4) +: 16];
	end
end

reg	signed	[15:0]	shift_reg[0:N-1];

integer	i;
always @(posedge clk) begin
	if(~rst_n) begin
		for(i=N-1; i>=0; i=i-1) begin
			shift_reg[i]	<= 16'd0;
		end
	end
	else begin
		for(i=N-1; i>0; i=i-1) begin
			shift_reg[i]	<= shift_reg[i-1];
		end
		shift_reg[0]		<= data_in;
	end
end

reg		signed	[31:0]	multi[0:N-1];

integer	j;
always @(*) begin
	for(j=0; j<N; j=j+1) begin
		multi[j]	<= shift_reg[j] * b[j];
		//这里可以考虑使用multiplier IP核,使用LUT搭建(而这里直接乘使用的是DSP资源,一般的FPGA芯片只有几百个)
	end
end

reg		signed	[47:0]	sum;

integer	k;
always @(*) begin
	sum		= 0;
	for(k=0; k<N; k=k+1) begin
		sum	= sum + multi[k];
	end
end

always @(posedge clk) begin
	data_out	<= sum[47-div_N : 32-div_N];
end

endmodule
/* 
 * file			: FIR_params.v
 * author		: 今朝无言
 * lab			: WHU-EIS-LMSWE
 * date			: 2023-08-04
 * version		: v1.0
 * description	: FIR 滤波器    lowpass   N=20   fc=0.1 fs
 */
module FIR_params_0d1(
output	[335:0]	params
);

assign	params[15:0]	= 16'h0000;
assign	params[31:16]	= 16'h0057;
assign	params[47:32]	= 16'h0131;
assign	params[63:48]	= 16'h0302;
assign	params[79:64]	= 16'h0616;
assign	params[95:80]	= 16'h0A6D;
assign	params[111:96]	= 16'h0FA8;
assign	params[127:112]	= 16'h1518;
assign	params[143:128]	= 16'h19E1;
assign	params[159:144]	= 16'h1D28;
assign	params[175:160]	= 16'h1E53;
assign	params[191:176]	= 16'h1D28;
assign	params[207:192]	= 16'h19E1;
assign	params[223:208]	= 16'h1518;
assign	params[239:224]	= 16'h0FA8;
assign	params[255:240]	= 16'h0A6D;
assign	params[271:256]	= 16'h0616;
assign	params[287:272]	= 16'h0302;
assign	params[303:288]	= 16'h0131;
assign	params[319:304]	= 16'h0057;
assign	params[335:320]	= 16'h0000;

endmodule

关于 FIR 滤波器这部分可以参考我之前的博文。

仿真

  仿真测试代码如下

`timescale 100ns/1ns

module PLL_tb();

reg		clk_1M	= 1'b1;
always #5 begin
	clk_1M	<= ~clk_1M;
end

reg		rst_n	= 1'b1;

//---------------------参考信号A-------------------------------
wire			[15:0]	A_out_tmp;
wire	signed	[15:0]	A_out;		//参考信号

localparam	f0	= 24'd10_000;
localparam	df	= -24'd9;		//频率偏差

DDS #(
	.Freq(1_000_000)
)
DDS_inst1(
	.clk		(clk_1M),
	.rst_n		(rst_n),

	.fout		(f0+df),
	.phase0		(16'd0),

	.sin_out	(A_out_tmp)
);

assign	A_out	= A_out_tmp - 16'd32768;

//---------------------本地信号B-------------------------------
wire			[15:0]	B_out_tmp;
wire	signed	[15:0]	B_out;

wire	signed	[23:0]	df2;		//控制本地信号的频偏

DDS #(
	.Freq		(1_000_000)
)
DDS_inst2(
	.clk		(clk_1M),
	.rst_n		(rst_n),

	.fout		(f0+df2),
	.phase0		(16'd0),

	.sin_out	(B_out_tmp)
);

assign	B_out	= B_out_tmp - 16'd32768;

//-----------------------PLL---------------------------------
wire	signed	[15:0]	df_PLL;

ADPLL #(
	.Freq		(1_000_000)
)
PLL_inst(
	.clk		(clk_1M),
	.rst_n		(rst_n),

	.A			(A_out),		//参考信号
	.B			(B_out),		//本地信号

	.df			(df_PLL)		//频偏
);

assign	df2	= df_PLL/64;

//-----------------------tb---------------------------------
initial begin
	rst_n	<= 1'b0;
	#5000;
	rst_n	<= 1'b1;
	#100;

	#1000000;
	$stop;
end

endmodule

  DDS 代码如下

/* 
 * file			: DDS.v
 * author		: 今朝无言
 * Lab			: WHU-EIS-LMSWE
 * date			: 2023-05-17
 * version		: v1.0
 * description	: 根据给定频率输出正弦信号
 * Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.
 */
module DDS(
input			clk,
input			rst_n,

input	[23:0]	fout,		//输出正弦波的频率  1k-10M 要24位
input	[15:0]	phase0,		//初相

output	[15:0]	sin_out
);

parameter	Freq	= 100_000_000;		//clk频率,Hz

//-----------------相位累加器-----------------------
reg		[47:0]	int_f_16	= 48'd0;	//相位累加器,x-16定点数
wire	[55:0]	dphi_16;				//相位步进

//dphi*Freq=fout*T, T=65536
assign	dphi_16	= (fout << 32)/Freq;

always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		int_f_16	<= 48'd0;
	end
	else begin
		int_f_16	<= int_f_16 + dphi_16;
	end
end

//-----------------正弦查找表-----------------------
wire	[15:0]	phase;

sin_gen sin_gen_inst(
	.clk		(clk),

	.phase		(phase),		//相位
	.sin_out	(sin_out)
);

assign phase	= phase0 + (int_f_16 >> 16);

endmodule

相应的正弦查找表如下(该模块使用线性插值的方法,在仅少量增加资源消耗的情况下,将量化误差缩小了两个数量级;这部分也可详见我之前的博文)

/* 
 * file			: sin_gen.v
 * author		: 今朝无言
 * Lab			: WHU-EIS-LMSWE
 * date			: 2023-05-17
 * version		: v1.0
 * description	: 根据给定相位输出正弦信号
 * Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.
 */
module sin_gen(
input			clk,

input	[15:0]	phase,		//相位,0~65535对应[0~2pi)
output	[15:0]	sin_out
);

//---------------------正弦查找表-------------------------
wire	[7:0]	addr1;
wire	[7:0]	addr2;
wire	[15:0]	sin_dat1;
wire	[15:0]	sin_dat2;

//sin rom, 16bit, 256 depth
sin_rom sin_rom_inst1(
	.clka	(clk),
	.addra	(addr1),
	.douta	(sin_dat1)
);

sin_rom sin_rom_inst2(
	.clka	(clk),
	.addra	(addr2),
	.douta	(sin_dat2)
);

//-----------线性插值获取更精确的相位分辨率-------------------
assign	addr1	= (phase>>8);
assign	addr2	= (phase>>8)+1;

wire	[15:0]	phase1;
wire	[15:0]	phase2;

assign	phase1	= addr1<<8;
assign	phase2	= addr2<<8;

reg		[15:0]	phase_d0;
reg		[15:0]	phase_d1;	//由于rom数据2拍后才给出,因此phase需要与之同步
reg		[15:0]	phase1_d0;
reg		[15:0]	phase1_d1;

always @(posedge clk) begin
	phase_d0	<= phase;
	phase_d1	<= phase_d0;

	phase1_d0	<= phase1;
	phase1_d1	<= phase1_d0;
end

wire	[31:0]	multi;
assign	multi	= (sin_dat2 > sin_dat1)? 
				(sin_dat2 - sin_dat1)*(phase_d1 - phase1_d1) : 
				(sin_dat1 - sin_dat2)*(phase_d1 - phase1_d1);

assign	sin_out	= (sin_dat2 > sin_dat1)? 
				sin_dat1 + (multi >> 8) : sin_dat1 - (multi >> 8);

endmodule

  仿真结果如下

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

4.DNS和负载均衡

文章目录 coreDNS概念部署croeDNS测试 kubernetes多master集群结构master节点部署 负载均衡配置部署nginx做四层反向代理安装高可用 keepalivednginx监控脚本修改k8s中组件的配置文件 coreDNS 概念 coreDNS是kubernetes的默认DNS实现。可以为集群中的service资源创建一个资源名…

【Unity3D】消融特效

1 前言 选中物体消融特效中基于 Shader 实现了消融特效&#xff0c;本文将基于 Shader Graph 实现消融特效&#xff0c;两者原理一样&#xff0c;只是表达方式不同&#xff0c;另外&#xff0c;选中物体消融特效中通过 discard 丢弃片元&#xff0c;本文通过 alpha 测试丢弃片元…

idea 2023 新版ui中git的相关操作

前两个月换了新电脑&#xff0c;下了最新版的idea发现可以切换一套新的ui了 切换新ui肯定不太习惯&#xff0c;很多操作都得重新摸索一下 在这里记录几个git相关的操作 忽略我下面截图中当前项目是js的后端项目…… 切换ui 首先说一下怎么切换新旧版ui&#xff0c;我这里就…

Spring很常用的@Conditional注解的使用场景和源码解析

介绍 今天要分享的是Spring的注解Conditional&#xff0c;Conditional是一个条件注解&#xff0c;它的作用是判断Bean是否满足条件&#xff0c;如果满足条件&#xff0c;则将Bean注册进IOC中&#xff0c;如果不满足条件&#xff0c;则不进行注册&#xff0c;这个注解在SpringB…

Three.js给场景添加背景颜色,背景图,全景图

1.相关API的使用&#xff1a; 1 THREE.Color &#xff08;用于创建和表示颜色&#xff09; 2. THREE.TextureLoader&#xff08;用于加载和处理图片纹理&#xff09; 3. THREE.SphereGeometry&#xff08;用于创建一个球体的几何体&#xff09; 4. THREE.Mesh&#xff08;用…

chapter13:springboot与任务

Spring Boot与任务视频 1. 异步任务 使用注解 Async 开启一个异步线程任务&#xff0c; 需要在主启动类上添加注解EnableAsync开启异步配置&#xff1b; Service public class AsyncService {Asyncpublic void hello() {try {Thread.sleep(3000);} catch (InterruptedExcept…

Tuxera NTFS2023Mac强大的Mac读写工具

Mac用户在使用NTFS格式移动硬盘时&#xff0c;会遇到无法写入硬盘的情况。要想解决无法写入的问题&#xff0c;很多人选择使用Mac读写软件。面对市面上“众多”的读写硬盘软件&#xff0c;用户应该怎么选择呢&#xff1f;初次接触移动硬盘的伙伴可能不知道移动硬盘怎么和电脑连…

git【潦草学习】

初始配置git 查询版本号 初次使用git前配置用户名与邮箱地址 git config --global user.name "your name" git config --global user.name "your email" git config -l 发现最后两行多出了用户名和邮箱&#xff0c;说明配置成功

【SpringCloud】Feign远程调用

先来看我们以前利用RestTemplate发起远程调用的代码&#xff1a; String url "http://userservice/user/" order.getUserId(); User user restTemplate.getForObject(url, User.class);存在下面的问题&#xff1a; • 代码可读性差&#xff0c;编程体验不统一 • …

外国机构在中国境内提供金融信息服务23家许可名单

6月30日&#xff0c;国家互联网信息办公室公布23家外国&#xff08;境外&#xff09;机构在中国境内提供金融信息服务许可名单&#xff0c;如下&#xff1a;

CCL 2023 电信网络诈骗案件分类评测-第一名方案

1 任务内容 1.1 任务背景 2022年12月1日起&#xff0c;新出台的《反电信网络诈骗犯罪法》正式施行&#xff0c;表明了我国治理当前电信网络诈骗乱象的决心。诈骗案件分类问题是打击电信网路诈骗犯罪过程中的关键一环&#xff0c;根据不同的诈骗方式、手法等将其分类&#xff…

PyTorch深度学习实战(9)——学习率优化

PyTorch深度学习实战&#xff08;9&#xff09;——学习率优化 0. 前言1. 学习率简介2. 梯度值、学习率和权重之间的相互作用3. 学习率优化实战3.1 学习率对缩放后的数据集的影响3.2 学习率对未缩放数据集的影响 小结系列链接 0. 前言 学习率( learning rate )是神经网络训练中…

Spring Data JPA源码

导读: 什么是Spring Data JPA? 要解释这个问题,我们先将Spring Data JPA拆成两个部分&#xff0c;即Sping Data和JPA。 从这两个部分来解释。 Spring Data是什么? 摘自: https://spring.io/projects/spring-data Spring Data’s mission is to provide a familiar and cons…

压力测试与测试工具jmeter的介绍

目录 一、性能指标 二、jmeter &#xff08;一&#xff09;JMeter 安装 &#xff08;二&#xff09;JMeter 压测示例 1、添加线程组 2、添加 HTTP 请求 3、添加监听器 4、启动压测&查看分析结果 &#xff08;三&#xff09;JMeter Address Already in use 错误解决 压力测…

【ChatGPT 指令大全】怎么使用ChatGPT写履历和通过面试

目录 怎么使用ChatGPT写履历 寻求履历的反馈 为履历加上量化数据 把经历修精简 为不同公司客制化撰写履历 怎么使用ChatGPT通过面试 汇整面试题目 给予回馈 提供追问的问题 用 STAR 原则回答面试问题 感谢面试官的 email 总结 在职场竞争激烈的今天&#xff0c;写一…

【逗老师的PMP学习笔记】5、项目范围管理

目录 一、规划范围管理二、收集需求1、【关键工具】头脑风暴2、【关键工具】访谈3、【关键工具】问卷调查4、【关键工具】标杆对照&#xff08;对标&#xff09;5、【关键工具】亲和图和思维导图6、【关键工具】质量功能展开7、【关键工具】用户故事8、【关键工具】原型法9、【…

软件测试缺陷报告

缺陷报告是描述软件缺陷现象和重现步骤地集合。软件缺陷报告Software Bug Report&#xff08;SBR&#xff09;或软件问题报告Software Problem Report&#xff08;SPR&#xff09; 作用&#xff1a;缺陷报告是软件测试人员的工作成果之一&#xff0c;体现软件测试的价值缺陷报…

传染病学模型 | Python实现基于SIR模型分析Covid19爆发

效果一览 文章概述 传染病学模型 | Python实现基于SIR 模型分析Covid19爆发 源码设计 import jax.numpy as npimport matplotlib.pyplot

数据结构刷题训练——链表篇(一)

目录 前言 题目一&#xff1a;链表的中间节点 思路 分析 题解 题目二&#xff1a;链表中倒数第k个结点 思路 分析 题解 题目三&#xff1a;合并两个有序链表 思路 分析 题解 方法二 题解 题目四&#xff1a;链表的回文结构 思路 分析 题解 总结 前言 今天我将开…

3.netty和protobuf

1.ChannelGroup可以免遍历由netty提供,覆盖remove方法即可触发删除channel\ 2.群聊私聊 13.群聊私聊简单原理图 3.netty心跳检测机制,客户端对服务器有没有读写(读,写空闲) //IdleStateHandler(3,5,7,TimeUnite.SECONDS)是netty提供的检测状态的处理器,也加到pipeline,读,写,…