科斯塔斯环的FPGA的实现


前言

最近在做毕业设计的东西,基于FPGA的扩频通信系统,题目来自某军工类院校。
我的思路是模仿软件无线电的东西,使得AD/DA部分尽可能的靠近射频端。这就使得滤波器、锁相环等部分要在FPGA中实现,滤波器部分可以直接调用IP核,没有什么难点。最终花费了七天时间完成了costas环的verilog代码编写,并最终在FPGA中得以实现。
网上关于costas环的matlab代码有很多,但是有很多应用场景单一,难以经得起调试。关于costas的verilog代码网上资源有限,并且有些在仿真过程中结果正确,但是烧入到FPGA中却不能完成相应功能。因此,最后打算自己手撕costas的verilog代码。

一、costas环的原理

关于costas环的原理,在《通信原理》课程上已经学习了,网上也有很多博客。在此放一个我认为写的很好的博客,讲述costas的基础知识。科斯塔斯环原理以及matlab仿真

二、costas环的FPGA实现

1.costas环的结构

数字锁相环的模型如下:
在这里插入图片描述
我们实现的costas环是经典型的costas的环,此环是最基础的costas环。将此环掌握,再进行改进就不在话下了。
我们实现的costas环的模型如下:
在这里插入图片描述

2.被调制信号的产生

采取最普通的DSB调制形式。
在这里插入图片描述
m(t)我们使用两种不同频率的正弦波产生。
正弦波的产生,我们使用DDS的方法。在altera中,DDS的IP核叫NCO,这也是我们锁相环所需要的——压控振荡器。说白了他还是一种DDS,通过控制输入的相位累加字来改变累加的相位,以此来改变输出正弦波的频率。我没有使用Altera的NCO的IP核(垃圾东西仿真不出波形,难以调试,但烧进FPGA能用),我用的自己写的NCO。
NCO的部分代码如下,我尽可能把注释写的通俗易懂。

module nco (    input clk, rst,//时钟,复位信号
                input signed [31:0] phase,//频率调整字
			    input [31:0] freword,//相位累加字
                output [31:0] out //输出
);
//本模块输入为phase和freword,为32位的相位累加字,分为两种情况
//1.当freword有数值,phase=0时,会产生固定频率的正弦波,频率与freword的数值有关。
//2.当freword和phase都有数值的时候,产生变频信号,频率与freword和phase有关。
//模块输出为32位的out,它的前16位是sin信号,后16位是cos信号,正好产生I,Q两路信号。
reg[31:0] waveform;
wire [9:0] angle;
reg [31:0] scTable;
always @(posedge clk or negedge rst) begin
    if(!rst) begin
        waveform <= 0;
    end
    else if(phase!=0) begin
	 waveform <= waveform + freword + (phase<<6);end//该模块的功能仅仅看着一句话就够了
	 else begin 
	 waveform <= waveform + freword;end
	 
end
assign angle=waveform[31:22];
assign out=scTable;
always@(*)
begin
case(angle)
   0: scTable=32'h7fbc;
   1: scTable=32'hff387fbb;
   2: scTable=32'hfe6f7fb9;
   3: scTable=32'hfda77fb6;
   4: scTable=32'hfcde7fb2;
   5: scTable=32'hfc157fac;
   ........
   1022: scTable=32'h1917fb9;
   1023: scTable=32'hc87fbb;
  endcase
  end
endmodule

下面是产生m(t)的代码:

wire signed [15:0] sNCOss;
wire signed [15:0] cNCOss;
wire signed [13:0] sinss;
wire signed [13:0] cosss;
nco n1ss (    
.clk(clock),
.rst(resest),
.phase(0),
.freword(32'd2576980),   
.out({sNCOss, cNCOss})
);//产生30K左右的正弦波
assign sinss=sNCOss[15:2];
assign cosss=cNCOss[15:2];

wire signed [15:0] sNCOss2;
wire signed [15:0] cNCOss2;
wire signed [13:0] sinss2;
wire signed [13:0] cosss2;
nco n2ss (    
.clk(clock),
.rst(resest),
.phase(0),
.freword(32'd1576981), 
.out({sNCOss2, cNCOss2})
);//产生17K左右的正弦波
assign sinss2=sNCOss2[15:2];
assign cosss2=cNCOss2[15:2];

wire signed[27:0] cossss;
assign cossss=cosss2*cosss;//两个不同频率正弦波相乘

m(t)的仿真波形:
在这里插入图片描述

4.调制载波

/调制信号的产生
wire signed [15:0] sNCO;
wire signed [15:0] cNCO;
wire signed [13:0] sin;
wire signed [13:0] cos;
nco n1 (    
.clk(clock),
.rst(resest),
.freword(32'd257698001),
.phase(0),    //.phase(NCO_in),
.out({sNCO, cNCO})
);//载波的产生
assign sin=sNCO[15:2];
assign cos=cNCO[15:2];

在这里插入图片描述

4.DSB调制

DSB调制就是将信号与载波直接相乘。

//调制信号
wire signed [27:0] code;
wire signed [13:0] ADC;
assign code=$signed(cossss[26:13])*cos;
assign ADC=code[26:13];

在这里插入图片描述
在这里插入图片描述

5.接收端本振

本部分很重要,注意看。
在发射端,我们使用的载波信号相位累加字为32’d257698001
在接收端,我们使用的本振信号相位累加字为32’d257698037
这一块的做法可以直接看出我们锁相环的设计的好坏。
可能有些初学者会有些疑惑,我在此解释一下。

1.为什么要产生不同频率的本振?发射端接收端不应该使用相同频率的信号吗?
其实这一块我当初也有疑惑。但是设想,谁能保证发射端和接收端频率完全一模一样,没有任何误差。因此有一些误差也是合理的。
2.我在此解释一下这样做的合理性和为什么一定要这样做。
锁相环是产生一个与载波同频同相的本振信号。
在此抛开数学表达式,在DDS中,每来一个时钟,累加一次相位,说白了相位和频率是一个东西。因此我们可以认为锁相环是产生一个与调制载波时时刻刻同相的信号。
我们使得两者的相位累加字不同,在没有锁相环的情况下,每时每刻他们的相位应该是不同的(时时刻刻不同相)。在使用锁相环的情况下,通过动态调整,使得两个本应不同相的信号时时刻刻同频同相,这就是锁相环的用处。

//本振信号
wire signed [31:0] phase_error;
wire signed [15:0] sNCOQ;
wire signed [15:0] cNCOI;
wire signed [13:0] sinQ;
wire signed [13:0] cosI;
wire [31:0] NCO_in;
nco n (    
.clk(clock),
.rst(resest2),
.phase(NCO_in),
.freword(32'd257698037),    //.phase(NCO_in),
.out({sNCOQ, cNCOI})
);//本振
assign sinQ=sNCOQ[15:2];
assign cosI=cNCOI[15:2];

在这里插入图片描述

6.I路和Q路的乘法器和滤波器


本模块没啥可说的,注意滤波器的设计和输入输出的位截取就行。

wire signed [26:0] firI;
wire signed [26:0] firQ;
wire signed [13:0] VLO_I;
wire signed [27:0] VI;
wire signed [13:0] VLO_Q;
wire signed [27:0] VQ;
assign firI=cosI*ADC;
assign VLO_I=firI[26:13];
assign firQ=sinQ*ADC;
assign VLO_Q=firQ[26:13];
fir1 IFIRT(
.clk(clock),              //                     clk.clk
.ast_sink_data(VLO_I),
.reset_n(1'b1),
.ast_sink_valid(1'b1),
//.ast_sink_error(2'b00),    //   avalon_streaming_sink.data
.ast_source_data(VI)  // avalon_streaming_source.
);

wire signed [13:0] VII;
wire signed [13:0] VQQ;
fir1 QFIRT(
.clk(clock),              //                     clk.clk
.ast_sink_data(VLO_Q),
.reset_n(1'b1),
.ast_sink_valid(1'b1),
//.ast_sink_error(2'b00),    //   avalon_streaming_sink.data
.ast_source_data(VQ)  // avalon_streaming_source.
);

assign VII=VI[27:14];
assign VQQ=VQ[27:14];

最终得到I路和Q路的滤波器的输出信号VII和VQQ,就是下图的V5和V6。按照数学推导来说,当锁相环达到稳定之后,V5(I路的输出信号)就应该为解调后的信号,V6(Q路输出的信号)为0。
在这里插入图片描述
在这里插入图片描述

6.使用NCO进行本振相位的调节

VDP对应上图的V7,我这个锁相环省去了环路滤波的部分。
NCO_in是压控振荡器的输入,这个值设置的大小直接关系到锁相环的性能。我看了几篇论文,这个NCO_in有很多的形式,我就直接使用了VII*VQQ的形式,然后进行幅度的调整。

wire signed [27:0] VDP;
assign VDP=VII*VQQ;
assign NCO_in = VDP[27]?{4'b1,VDP}:{4'b0,VDP}; // 24.0

回看本振的产生
NCO_in反作用于本振信号的产生,对本振信号的相位起到调整的作用。这样一来就构成了一种闭合的环路。

nco n (    
.clk(clock),
.rst(resest2),
.phase(NCO_in),
.freword(32'd257698037),    //.phase(NCO_in),
.out({sNCOQ, cNCOI})
);//本振

压控振荡器输出如下。未完成锁相之前有较大的值,之后在0左右动态调节。
在这里插入图片描述

三、costas环的效果

1.硬件环境

使用开发板:DE1-SoC
芯片:cyclone 五代 5CSEMA5F31C6
DA:ACM9767
示波器:DS1102E
在这里插入图片描述
在这里插入图片描述

2.测试效果

信号的恢复
黄色的为发射端的波形,蓝色的为接收端的波形。
在这里插入图片描述
载波的恢复
黄色为调制载波,蓝色为接收端恢复的本振。
在这里插入图片描述

总结

1.做的时候产生了时序问题,以后做FPGA看来单单考虑逻辑是不够的了,也要考虑时序问题了,不要太相信quartus。
2.matlab仿真成功不代表modelsim能跑出来,modelsim能跑出来不代表用quartus烧进去就能用。多多调试吧。
3.保研之后不要懈怠,该学还得学。
4.网上代码不一定能用,还是要靠自己写。

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值