—— 因项目需要,苦学AD9361,制作不易,记得三连哦
——工程文件: 基于FPGA控制的AD9361载波信号发生器 提取码:tscb
硬件设备:Neptune SDR P210开发板
软件环境:vivado2018.3
频谱仪:AV4036A频谱分析仪
实验目的
针对传统载波信号发生器可配置性较差,难以更改信号参数,维护和升级困难等问题,本文对软件无线电(Software Defined Radio, SDR)和锁相环(phase-locked loop,PLL)的基础理论和电路展开研究,设计一种基于片上系统ZYNQ和射频收发捷变芯片AD9361的载波信号发生器。通过ZYNQ片上系统的PS端配置AD9361的锁相环参数,进行增益控制,数字滤波,数据接口设置等操作。通过PL端实现基带信号的数字化处理。
以2.4GHz的载波信号为例,基于FPGA控制的AD9361载波信号发生器设计通过成功发送2.4GHz频率的载波信号的情况,验证了载波信号发生器的功能。
一.理论分析
1. SDR无线收发系统结构
1.1 SDR无线收发机整体结构
SDR收发机主要由天线、射频前端、宽带A/D-D/A转换器、通用和专用数字信号处理器等各种模块组成,典型SDR收发机结构如图1.1.1所示。SDR的天线一般要覆盖比较宽的频段,例如1MHz~2GHz,要求每个频段的特性均匀。
图1.1.1 SDR收发机框图
射频前端由带通滤波器,放大器、混频器,本振、AGC控制电路等组成,在发射时主要完成上变频、滤波、功率放大等任务,接收时实现滤波、放大、下变频等功能。射频前端在中频进行数字化的结构框图1.1.2所示。由于SDR在中高频甚至是在射频进行数字化,这样可以减少模拟环节,使得前端引入的噪声更少,信号失真更小,电路更简洁。SDR和普通的窄带接收机相比,瞬时处理带宽更宽,动态范围更大、可扩展性更好。
图1.1.2 中频数字化框图
模拟信号进行数字化后的处理任务全由数字信号处理(Digital Signal Processing,DSP)软件承担。为了减轻通用DSP的处理压力,通常把A/D转换器传来的数字信号,经过专用数字信号处理器件(如数字下变频器,数字上变频器),降低数据流速率,并把信号变至基带后,再把数据送给通用DSP进行处理。通用DSP主要完成各种数据率相对较低的基带信号的处理,例如信号的调制解调,各种抗干扰,抗衰落。DSP的结构如图1.1.3所示。
图1.1.3 DSP框图
1.2 锁相环
锁相环(PLL)是一种闭环反馈系统,能够跟踪输出相位和参考时钟之间的固定相位关系。它广泛用于时钟恢复,作为 GPS 接收器中的频率合成器、抖动衰减器和芯片同步 [5,6]。PLL由鉴相器、环路滤波器和压控振荡器模块组成。这些模块相互连接,因此,输出信号的相位和频率被跟踪并反馈到鉴相器的输入端,在那里它与输入信号进行比较。这种互连的主要目的是将输出信号的相位和频率锁定到输入信号的相位和频率[7],锁相环的基本框图如图1.2.1所示。
鉴频鉴相器(PD-Phase Detector)通过比较输入信号和压控振荡器的输出信号的相位,输出一定的电压信号。输出的电压信号是关于这个信号相位差的函数。环路滤波器(LF-Loop Filter)可作为低通滤波器,滤除掉鉴相器输出电压中的高频分量和噪声,只保留低频分量。压控振荡器(Voltage-Controlled Oscillator,VCO)受环路滤波器输出电压的控制,压控振荡器的振荡频率向输出信号的频率靠拢,直到它们的频率相同。同时使VCO的输出信号和输入信号的相位保持着特定的关系,用来锁定相位。从VCO输出的信号通过反馈路径输入到相位比较器,与输入信号进行比较,形成反馈信号。反馈路径的目的是让系统试图保持输入信号和VCO输出信号的相位差稳定。
图1.2.1 锁相环的基本框图
AD9361含有两个完全相同的频率合成器,用于生成本振频率,一个用于接收器,一个用于发射器。锁相环(PLL)频率合成器采用分数分频,融入了完全集成式电压控制振荡器(VCO)和环路滤波器。在FDD模式下, TX PLL和RX PLL可以同时激活,发送和接收的频率可以相同或不同。AD9361的PLL合成频率框图如图1.2.2所示。
图1.2.2 AD9361的PLL频率合成框图
1.3 AD9361发送链路
AD9361发送部分由两个相同且独立控制的通道Tx Channel 1 和 Tx Channel 2 组成,同时共用一个通用型频率合成器。TX信号路径从数字接口接收IQ格式的12位补码数据,并且每个通道(I和Q)通过具有内插选项的完全可编程的128抽头FIR滤波器。之后进入到一系列附加的插值滤波器,这些滤波器在到达12位DAC之前提供附加的滤波和数据速率插值。转换为基带模拟信号时, I和Q信号将进行滤波,以移除采样伪像,然后进入上变频混频器使 I和Q信号将重新组合起来,并在载波频率下进行调制。组合信号还会通过模拟滤波器,进行额外的频带整形处理,然后再将信号传输至输出放大器。
发送器模块还为每个通道提供一个TX监控模块。该模块监视发送器输出,并通过一个未使用的接收器通道将其送回基带处理器(Baseband Processor,BBP),以实现信号监控。 TX监控器模块仅在接收器空闲的时分双工(Time Division Duplexing,TDD)模式下可用。当AD9361处于频分双工(Frequency Division Duplexing,FDD)模式时,FDD独立控制模式可以允许接收链和发射链独立使能。本次作品采用FDD模式,并未启动TX监控器模块。AD9361发送链框图如图1.3.1所示。
图1.3.1 AD9361发送链
1.4 LVDS数据接口
1.4.1低压差分信号模式
AD9361的数据传输路径采用低压差分信号(Low-Voltage Differential Signaling,LVDS)模式,与标准CMOS接口相比,在噪声环境中更具优势,能提供优越的开关性能和更高的数据速率。AD9361数据路径接口使用并行数据总线(P0和P1)在AD9361和BBP之间传输数据样本。总线传输使用简单的硬件握手信令来控制。在LVDS模式下,两条总线(P0和P1)都使用差分LVDS信号。AD9361的 LVDS接口便于连接具有LVDS功能的ASIC和FPGA。
AD9361在LVDS模式下的接口如图1.4.1所示。DATA_CLK是一个差分LVDS信号,作为接收数据路径的主时钟提供给BBP,BBP使用该主时钟作为接口数据传输和采样数据基带处理的时序参考。Rx_FRAME和Tx_FRAME高转换表示帧的开始,可以在突发开始时进行单个高电平转换,并在整个突发期间保持高电平。Rx_D[5:0]和Tx_D[5:0]是差分LVDS数据总线,由六个差分对组成。数据以成对数据字的形式在12位数据总线上传输。ENABLE 和TXNRX由BBP向AD9361驱动,以在TDD模式下提供数据传输突发控制。在正常FDD模式下,TXNRX信号被忽略,但必须保持在有效的逻辑电平。
图1.4.1 AD9361在LVDS模式下的接口框图
1.4.2 双端口全双工模式
双端口全双工LVDS模式通过SPI写寄存器来使能。在这种模式下,P0(Tx_D[5:0])和P1(Rx_D[5:0])均为LVDS信号,数据总线(D[11:0])被分成独立的子总线(Rx_D[5:0]和Tx_D[5:0])。每个子总线同时工作,允许BBP和AD9361之间全双工发送和接收数据。LVDS模式下的收发数据时序图如图1.4.2所示。
发送数据(Tx_D[5:0])、FB_CLK和Tx_FRAME由BBP驱动,FB_CLK和Tx_D[5:0]、Tx_FRAME之间的建立和保持时间足够以允许AD9361使用FB_CLK捕获Tx_D[5:0]和Tx_FRAME。Tx_D[5:0]采用二进制补码格式,每个数据包中的第一个6位字包含MSB,第二个6位字包含LSB。
接收数据(Rx_D[5:0])、DATA_CLK和Rx_FRAME由AD9361驱动,DATA_CLK和Rx_D[5:0]、Rx_FRAME之间的建立和保持时间足够以允许BBP使用DATA_CLK捕获Rx_D[5:0]和Rx_FRAME。接收采样数据采用二进制补码格式,每个数据包中的第一个6位字包含MSB,第二个6位字包含LSB。
图1.4.2 LVDS模式下的收发数据时序图
1.5 AD9361接收链路结构
AD9361接收部分有两个独立控制的通道Rx Channel 1 和 Rx Channel 2 可以接收来自不同来源的信号,从而允许该设备在共享通用频率合成器的同时在多输入多输出(Multiple Input Multiple Output,MIMO)系统中使用。每个通道具有三个输入,这些输入可以多路复用到信号链,从而使AD9361适用于具有多个天线输入的分集系统。接收链数据首先经过低噪声放大器(Low Noise Amplifier,LNA),然后是同相(I)和正交(Q)放大器,混频器和频带整形滤波器,可将接收到的信号下变频为基带以进行数字化。
AD9361 RX 信号路径将下变频信号(I 和 Q)传递到基带接收器部分,基带RX信号路径由两个可编程模拟低通滤波器,一个12位ADC和四级数字抽取滤波器组成。四个抽取滤波器中的每一个都可以被旁路,每个低通滤波器的转折频率是可编程的。当工作频率等于或低于3 GHz时,任何LNA输入端口都将提供最佳性能, 当工作频率高于3 GHz时,利用Rx1A和Rx2A LNA输入端口获得最佳性能,B和C端口在3 GHz以上时性能会下降,本作品要求工作频率为2.4GHz,因此我们采用工作在全频段性能较好的A端口。AD9361接收链结构如图所示。
二.发生器系统实现
2.1 发生器系统总体结构
2.1.1硬件设计
本次作品采用Neptune SDR P210开发板,开发板主要由XC7Z020+1个DDR3+AD9361+1个QSPIFLASH的最小系统构成。开发系统的总体结构如图所示。
2.1.2系统总体通信框图
系统总体通信框图如图2.1.2所示。
当前SoC芯片的复杂度越来越高,芯片内部集成的模块也越来越多,而总线 是这些模块之间互相通信必不可少的组件之一[8]。随着SoC性能的不断提高,对总线性能的要求也越来越高,传统的APB(Advanced PeripheraI Bus)、AHB(Advanced High Performance Bus)等总线无法满足总线传输中高速和低延时的要求[9]。AXI总线有五条独立的传输通道,具有读与写通道独立、地址与数据通道独立的优点,支持burst传输,支持非对齐传输,支持outstanding 传输和out of order传输。AXI总线具有高速、可支持突发传输、低延时等特征,架构和性能都有着很大的优势,非常适合用于 SoC 芯片总线设计[10]。
AD9361芯片的数字接口有3种,分别是串行外设接口(serial peripheral interface,SPI)、通用输入输出( general purpose inputloutput, GPIO)接口和高速数字接口,其中 SPI接口用于传输控制信息,GPIO接口用于读取AD9361 的工作状态并对 AD9361进行状态控制,高速数字接口用于和FPGA之间传输基带数据。使用 FPGA 的逻辑资源将GPIO,SPI等各个功能模块组装成带有AXI总线的知识产权( intellectual property,IP)核,并通过 FPGA 的I/O 接口完成与 AD9361射频芯片之间信息数据的交互[11]。
图2.1.2 系统总体通信框图
2.2 PL端实现原理
2.2.1AD9361数据流图
AD9361数据流图如图2.2.1所示。
将PS端生成2.4GHZ的载波信号,通过AXI总线交互传到PL端,然后利用DAC模块处理后生成frame(识别数据有效的标志位)和I/Q两路信号,通过ODDR模块转换成AD9361可识别的数据,最后利用FPGA将单端数据转换为以差分对传输的数据。
图2.2.1 AD9361数据流图
2.2.2FPGA顶层设计
2.2.2.1FPGA顶层文件的代码
FPGA顶层文件的代码如下:
`timescale 1ns / 1ps
module system_top(
//zynq子系统
inout [14:0]DDR_addr ,
inout [2:0]DDR_ba ,
inout DDR_cas_n ,
inout DDR_ck_n ,
inout DDR_ck_p ,
inout DDR_cke ,
inout DDR_cs_n ,
inout [3:0]DDR_dm ,
inout [31:0]DDR_dq ,
inout [3:0]DDR_dqs_n ,
inout [3:0]DDR_dqs_p ,
inout DDR_odt ,
inout DDR_ras_n ,
inout DDR_reset_n ,
inout DDR_we_n ,
inout FIXED_IO_ddr_vrn ,
inout FIXED_IO_ddr_vrp ,
inout [53:0]FIXED_IO_mio ,
inout FIXED_IO_ps_clk ,
inout FIXED_IO_ps_porb ,
inout FIXED_IO_ps_srstb ,
//spi 接口
output spi_csn ,
output spi_clk ,
output spi_mosi ,
input spi_miso ,
//ad936x ctrl signals
output en_agc ,// 用于自动增益控制(AGC)的手动控制输入 G5
output resetb ,// 异步复位。逻辑低电平复位器件 K5
output sync_in ,// 用于同步多个AD9361器件之间数字时钟的输入。若未使用此引脚,则将其接地。 H5
output enable ,// 控制输入。该引脚使器件在各种运行状态之间移动 G6
output txnrx ,//使能状态机控制信号。该引脚控制数据端口总线方向。逻辑低电平选择RX方向,逻辑高电平选择TX方向。H4
output [3:0] ctrl_in ,// 控制输入。用于手动RX增益和TX衰减控制。 C5, C6, D5, D6
//tx channel
output tx_clk_out_n ,
output tx_clk_out_p ,
output [5:0]tx_data_out_n ,
output [5:0]tx_data_out_p ,
output tx_frame_out_n ,
output tx_frame_out_p ,
//rx channel
input rx_clk_in_n ,
input rx_clk_in_p ,
input [5:0]rx_data_in_n ,
input [5:0]rx_data_in_p ,
input rx_frame_in_n ,
input rx_frame_in_p
);
wire data_clk;
wire fclk_clk1_200m;
//zynq 的 gpio
wire [63:0] gpio_o;
// gpio 控制信号, 引出了64个emio,, gpio_o[i]在ps端对应54+i
assign txnrx = gpio_o[0];
assign enable = gpio_o[1];
assign resetb = gpio_o[2];
assign sync_in = gpio_o[3];
assign en_agc = gpio_o[4];
assign ctrl_in = gpio_o[8:5];
// zynq 输出的一些控制信号
wire adc_mode_r1; // 0:2r 1:1r
wire dac_mode_r1; // 0:2t 1:1t
wire cfg_done; //配置完成信号
wire dds_en; //dds使能信号
wire [11:0] dds_phase; //相位增量
//dds 的输出
wire [31:0] data_tdata ;
wire data_tvalid ;
// 解析后的数据
wire adc_valid ;//接收数据valid信号, 1R1T时,2个data_clk有效一次,2R2T,4个data_clk有效一次
wire [11:0] adc_data_i1 ;//解析后的接收数据:I1路
wire [11:0] adc_data_q1 ;//解析后的接收数据:Q1路
wire [11:0] adc_data_i2 ;//解析后的接收数据:I2路
wire [11:0] adc_data_q2 ;//解析后的接收数据:Q2路
wire adc_status ;//对rx frame信号的正确性检测
//需要发送的信号
reg dac_valid ;//发送数据valid信号, 1R1T时,2个data_clk有效一次,2R2T,4个data_clk有效一次
reg [11:0] dac_data_i1 ;//DDS输入的I路数据
reg [11:0] dac_data_q1 ;//DDS输入的Q路数据
reg [11:0] dac_data_i2 ;//DDS输入的I路数据(目前输入的两路数据一样)
reg [11:0] dac_data_q2 ;//DDS输入的Q路数据
.
.
.
.
.
.
.
代码太多
部分省略
.
.
.
.
.
.
.
wire mmcm_locked;
wire mmcm_data_clk_160m;
wire mmcm_data_clk_320m;
wire mmcm_clk_out_160m;
wire mmcm_clk_out_320m;
mmcm_clk_160m instance_name
(
// Clock out ports
.mmcm_clk_out_160m(mmcm_clk_out_160m), // output mmcm_clk_out_160m
.mmcm_clk_out_320m(mmcm_clk_out_320m), // output mmcm_clk_out_320m
// Status and control signals
.resetn(cfg_done), // input resetn
.locked(mmcm_locked), // output locked
// Clock in ports
.clk_in_160m(data_clk) // input clk_in_160m
);
assign mmcm_data_clk_160m = mmcm_clk_out_160m & mmcm_locked;
assign mmcm_data_clk_320m = mmcm_clk_out_320m & mmcm_locked;
endmodule
2.2.2.2AD9361接口文件代码
AD9361接口文件代码如下:
`timescale 1ns / 1ps
module ad9361_lvds(
// system signals
input wire cfg_done ,
output wire data_clk ,//(输出时钟信号)接收和发送数据的时钟
// ad936x rx path signals
input wire rx_clk_in_p ,//接收9361数据的时钟信号(差分)
input wire rx_clk_in_n ,
input wire [5:0] rx_data_in_p ,//接收来自9361的数据(每个data_clk边沿接收6bit数据)(差分)
input wire [5:0] rx_data_in_n ,
input wire rx_frame_in_p ,//rx frame接收控制信号(差分)
input wire rx_frame_in_n ,
// ad936x tx path signals
output wire tx_clk_out_p ,//发送数据时钟信号(差分)
output wire tx_clk_out_n ,
output wire tx_frame_out_p ,//tx frame 发送控制信号(差分)
output wire tx_frame_out_n ,
output wire [5:0] tx_data_out_p ,//发送的数据(差分)
output wire [5:0] tx_data_out_n ,
// adc sample iq - 解析后的IQ数据
output reg adc_valid ,//接收数据valid信号, 详见时序图
output reg adc_status ,//对rx frame信号的正确性检测 1:error 0:right
output reg [11:0] adc_data_i1 ,//解析后的接收数据:I1路
output reg [11:0] adc_data_q1 ,//解析后的接收数据:Q1路
output reg [11:0] adc_data_i2 ,//解析后的接收数据:I2路
output reg [11:0] adc_data_q2 ,//解析后的接收数据:Q2路
// dac sample iq - 需要发送的IQ数据
input wire dac_valid ,//发送数据valid信号, 详见时序图
input wire [11:0] dac_data_i1 ,//DDS输入的I路数据
input wire [11:0] dac_data_q1 ,//DDS输入的Q路数据
input wire [11:0] dac_data_i2 ,//DDS输入的I路数据(目前输入的两路数据一样)
input wire [11:0] dac_data_q2 ,//DDS输入的Q路数据
//external ctrl signals
input wire adc_mode_r1 ,//==1 is r1 mode; == 0 is r2 mode
input wire dac_mode_r1 //==1 is t1 mode; == 0 is t2 mode
);
wire data_clk_ibuf;
//-------------------------------------------------------------------
// 将输入的差分时钟转换为单端时钟
//-------------------------------------------------------------------
//1.将 rx_clk_in_p/n 差分信号转化为单端信号 data_clk_ibuf 1r1t:80m 2r2t: 160m
IBUFDS #(
.DIFF_TERM("TRUE"), // Differential Termination 使能终端电阻100欧姆
.IBUF_LOW_PWR("TRUE"), // Low power="TRUE", Highest performance="FALSE"
.IOSTANDARD("DEFAULT") // Specify the input I/O standard
) IBUFDS_data_clk_inst (
.O(data_clk_ibuf), // Buffer output
.I(rx_clk_in_p), // Diff_p buffer input (connect directly to top-level port)
.IB(rx_clk_in_n) // Diff_n buffer input (connect directly to top-level port)
);
//2.时钟输出
BUFG data_clk1_bufg(.O(data_clk),.I(data_clk_ibuf));
wire rx_frame_ibuf;
wire rx_frame_idelay;
wire rx_frame_iddr_p;
wire rx_frame_iddr_n;
//-------------------------------------------------------------------
// 将输入的差分帧信号转换为单端帧信号
//-------------------------------------------------------------------
//1.将输入的差分信号 rx_frame_in_p/n 转换为单端信号 rx_frame_ibuf
IBUFDS #(
.DIFF_TERM("TRUE"), // Differential Termination
.IBUF_LOW_PWR("TRUE"), // Low power="TRUE", Highest performance="FALSE"
.IOSTANDARD("DEFAULT") // Specify the input I/O standard
) IBUFDS_rx_frame_inst (
.O(rx_frame_ibuf), // Buffer output
.I(rx_frame_in_p), // Diff_p buffer input (connect directly to top-level port)
.IB(rx_frame_in_n) // Diff_n buffer input (connect directly to top-level port)
);
//3.将输入的串行信号:rx_frame_idelay 转换为并行信号:rx_frame_iddr_p/n
IDDR #(
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED"), // "OPPOSITE_EDGE", "SAME_EDGE" or "SAME_EDGE_PIPELINED"
.INIT_Q1(1'b0), // Initial value of Q1: 1'b0 or 1'b1 默认输出0
.INIT_Q2(1'b0), // Initial value of Q2: 1'b0 or 1'b1 默认输出0
.SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
) IDDR_rx_frame_inst (
.Q1(rx_frame_iddr_p), // 1-bit output for positive edge of clock
.Q2(rx_frame_iddr_n), // 1-bit output for negative edge of clock
.C(data_clk), // 1-bit clock input
.CE(1'b1), // 1-bit clock enable input
.D(rx_frame_ibuf), // 1-bit DDR data input
.R(1'b0), // 1-bit reset
.S(1'b0) // 1-bit set
);
genvar i;
wire [5:0] rx_data_ibuf;
wire [5:0] rx_data_idelay;
wire [5:0] rx_data_iddr_p;
wire [5:0] rx_data_iddr_n;
//-------------------------------------------------------------------
// 将输入的差分数据信号转换为单端数据信号
//-------------------------------------------------------------------
generate
for(i=0;i<6;i=i+1)begin
//1.将输入的差分信号 rx_data_in_p/n 转换为单端信号 rx_data_ibuf
IBUFDS #(
.DIFF_TERM("TRUE"), // Differential Termination
.IBUF_LOW_PWR("TRUE"), // Low power="TRUE", Highest performance="FALSE"
.IOSTANDARD("DEFAULT") // Specify the input I/O standard
) IBUFDS_rx_data_inst (
.O( rx_data_ibuf[i]), // Buffer output
.I( rx_data_in_p[i]), // Diff_p buffer input (connect directly to top-level port)
.IB(rx_data_in_n[i]) // Diff_n buffer input (connect directly to top-level port)
);
//3.将输入的串行信号:rx_data_idelay 转换为并行信号:rx_data_iddr_p/n
IDDR #(
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED"), // "OPPOSITE_EDGE", "SAME_EDGE" or "SAME_EDGE_PIPELINED"
.INIT_Q1(1'b0), // Initial value of Q1: 1'b0 or 1'b1
.INIT_Q2(1'b0), // Initial value of Q2: 1'b0 or 1'b1
.SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
) IDDR_rx_data_inst (
.Q1(rx_data_iddr_p[i]), // 1-bit output for positive edge of clock
.Q2(rx_data_iddr_n[i]), // 1-bit output for negative edge of clock
.C(data_clk), // 1-bit clock input
.CE(1'b1), // 1-bit clock enable input
.D(rx_data_ibuf[i]), // 1-bit DDR data input
.R(1'b0), // 1-bit reset
.S(1'b0) // 1-bit set
);
end
endgenerate
reg [3:0] rx_frame_shift;
reg [23:0] rx_data_shift; //接收数据解析
//-------------------------------------------------------------------
// 对 frame 和 data 信号进行拼接
//-------------------------------------------------------------------
// 1.rx frame 信号的拼接为: rx_frame_shift
// 1r1t: xx11->1100->0011->1100->0011->1100(有效)->0011->1100(有效)->0011->1100(有效)
// 2r2t: xx11->1111->1100->0000(有效)->0011->1111->1100->0000(有效)->0011->1111->1100->0000(有效)
always @(posedge data_clk) begin
rx_frame_shift <= {rx_frame_shift[1:0],rx_frame_iddr_p,rx_frame_iddr_n};
end
// 2.rx data 信号的拼接为 rx_data_shift
// 1r1t:{x,x,IM,QM}->{IM,QM,IL,QL}(有效)->{IL,QL,IM,QM}->{IM,QM,IL,QL}(有效)->{IL,QL,IM,QM}->{IM,QM,IL,QL}(有效)
// 2r2t: {x,x,I1M,QIM}->{I1M,Q1M,I1L,Q1L}(可解析通道1)->{I1L,Q1L,I2M,Q2M}->{I2M,Q2M,I2L,Q2L}(可解析通道2)->{I2L,Q2L,I1M,QIM}->{I1M,Q1M,I1L,Q1L}(可解析通道1)
always @(posedge data_clk) begin
rx_data_shift <= {rx_data_shift[11:0],rx_data_iddr_p,rx_data_iddr_n};
end
reg rx_valid_r1=0; // 1r1t mode data valid
reg rx_error_r1=0; // frame error
reg [11:0] rx_data_i_r1=0; // 1r1t mode data i
reg [11:0] rx_data_q_r1=0; // 1r1t mode data q
//-------------------------------------------------------------------
// 单通道接收数据解析
//-------------------------------------------------------------------
// 1.rx valid 1r1t 每两个 data_clk 内有效一次
always @(posedge data_clk) begin
if(rx_frame_shift == 4'b1100) begin
rx_valid_r1 <= 1'b1;
end
else begin
rx_valid_r1 <= 1'b0;
end
end
// 2.判断:rx_frame_shift 拼接结果是否出错(1r1t mode: 1100 or 0011)
always @(posedge data_clk) begin
rx_error_r1 <= (rx_frame_shift==4'b1100 || rx_frame_shift == 4'b0011)?1'b0:1'b1;
end
// 3.rx data 数据解析,rx_frame_shift==1100时, rx_data_shift == {IM[23:18],QM[17:12],IL[11:6],QL[5:0]}
// I路数据:{IM,IL} Q路数据:{QM,QL}
always @(posedge data_clk) begin
if(rx_frame_shift==4'b1100) begin
rx_data_i_r1 <= {rx_data_shift[23:18],rx_data_shift[11:6]}; //rx_data_i_r1 = {IM,IL}
rx_data_q_r1 <= {rx_data_shift[17:12],rx_data_shift[5:0]}; //rx_data_q_r1 = {QM,QL}
end
end
reg rx_valid_r2=0; // 2r2t mode data valid
reg rx_error_r2=0; // frame error
reg [11:0] rx_data_i1_r2=0;// 2r2t mode data i1
reg [11:0] rx_data_q1_r2=0;// 2r2t mode data q1
reg [11:0] rx_data_i2_r2=0;// 2r2t mode data i2
reg [11:0] rx_data_q2_r2=0;// 2r2t mode data q2
//-------------------------------------------------------------------
// 双通道接收数据解析
//-------------------------------------------------------------------
// 1.rx valid 2r2t 每4个 data_clk 内有效一次
always @(posedge data_clk) begin
if(rx_frame_shift==4'b0000) begin
rx_valid_r2 <= 1'b1;
end
else begin
rx_valid_r2 <= 1'b0;
end
end
// 2.判断:rx_frame_shift 拼接结果是否出错(2r2t mode: 0011 or 1111 or 1100 or 0000)
always @(posedge data_clk ) begin
rx_error_r2 <= (rx_frame_shift == 4'b0011 || rx_frame_shift ==4'b1111 || rx_frame_shift == 4'b1100 || rx_frame_shift == 4'b0000)?1'b0:1'b1;
end
// 3.rx_frame_shift == 1111,通道1,rx_data_shift=={I1M,Q1M,I1L,Q1L}
always @(posedge data_clk) begin
if(rx_frame_shift == 4'b1111) begin
rx_data_i1_r2 <= {rx_data_shift[23:18],rx_data_shift[11:6]};
rx_data_q1_r2 <= {rx_data_shift[17:12],rx_data_shift[5:0]};
end
end
// 4.rx_frame_shift == 0000,通道2,rx_data_shift=={I2M,Q2M,I2L,Q2L}
always @(posedge data_clk) begin
if(rx_frame_shift == 4'b0000) begin
rx_data_i2_r2 <= {rx_data_shift[23:18],rx_data_shift[11:6]};
rx_data_q2_r2 <= {rx_data_shift[17:12],rx_data_shift[5:0]};
end
end
//-------------------------------------------------------------------
// 保存对接收到的数据进行解析后的结果
//-------------------------------------------------------------------
//r1 r2 mux, 通道选择,当 adc_mode_r1==1时, 为1r1t
always @(posedge data_clk) begin
if(adc_mode_r1 == 1'b1) begin//1R1T
adc_valid <= rx_valid_r1;
adc_status <= rx_error_r1;
adc_data_i1 <= rx_data_i_r1;
adc_data_q1 <= rx_data_q_r1;
adc_data_i2 <= 'd0;
adc_data_q2 <= 'd0;
end
else begin//2R2T
adc_valid <= rx_valid_r2;
adc_status <= rx_error_r2;
adc_data_i1 <= rx_data_i1_r2;
adc_data_q1 <= rx_data_q1_r2;
adc_data_i2 <= rx_data_i2_r2;
adc_data_q2 <= rx_data_q2_r2;
end
end
//tx
reg tx_data_cnt_flag=0;
reg [1:0] tx_data_cnt=0;
reg [11:0] tx_data_i1_d=0;
reg [11:0] tx_data_q1_d=0;
reg [11:0] tx_data_i2_d=0;
reg [11:0] tx_data_q2_d=0;
reg [5:0] tx_data_p;
reg [5:0] tx_data_n;
reg tx_frame;
wire [3:0] tx_data_sel;
wire tx_clk;
wire tx_frame_out;
wire [5:0] tx_data_out;
.
.
.
.
.
代码太多
部分省略
.
.
.
.
.
//tx frame signal --- begin
//1.对输入的 tx_frame 信号转换为 tx_frame_out 与tx_frame反向
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT(1'b0), // Initial value of Q: 1'b0 or 1'b1
.SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_frame_inst (
.Q(tx_frame_out), // 1-bit DDR output
.C(data_clk), // 1-bit clock input
.CE(1'b1), // 1-bit clock enable input
.D1(tx_frame), // 1-bit data input (positive edge)
.D2(tx_frame), // 1-bit data input (negative edge)
.R(1'b0), // 1-bit reset
.S(1'b0) // 1-bit set
);
//2. 对输入的单端 tx_frame_out 转换为差分信号:tx_frame_out_p/n
OBUFDS #(
.IOSTANDARD("DEFAULT"), // Specify the output I/O standard
.SLEW("SLOW") // Specify the output slew rate
) OBUFDS_frame_inst (
.O(tx_frame_out_p), // Diff_p output (connect directly to top-level port)
.OB(tx_frame_out_n), // Diff_n output (connect directly to top-level port)
.I(tx_frame_out) // Buffer input
);
//tx frame signal --- end
//tx data signal --- begin
generate
for(i=0;i<6;i=i+1) begin
//1.把输入的并行数据 tx_data_p/n 转换为串行输出: tx_data_out
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT(1'b0), // Initial value of Q: 1'b0 or 1'b1
.SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_tx_data_inst (
.Q(tx_data_out[i]), // 1-bit DDR output
.C(data_clk), // 1-bit clock input
.CE(1'b1), // 1-bit clock enable input
.D1(tx_data_p[i]), // 1-bit data input (positive edge)
.D2(tx_data_n[i]), // 1-bit data input (negative edge)
.R(1'b0), // 1-bit reset
.S(1'b0) // 1-bit set
);
//2.对输入的单端 tx_data_out 转换为差分信号:tx_data_out_p/n
OBUFDS #(
.IOSTANDARD("DEFAULT"), // Specify the output I/O standard
.SLEW("SLOW") // Specify the output slew rate
) OBUFDS_tx_data_inst (
.O(tx_data_out_p[i]), // Diff_p output (connect directly to top-level port)
.OB(tx_data_out_n[i]), // Diff_n output (connect directly to top-level port)
.I(tx_data_out[i]) // Buffer input
);
end
endgenerate
//tx data signal --- end
//tx_clk signal --- begin
//1.输出的时钟:tx_clk 与 data_clk 反相(取决于D1,D2) D1:0 D2:1
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT(1'b0), // Initial value of Q: 1'b0 or 1'b1
.SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_data_clk_inst (
.Q(tx_clk), // 1-bit DDR output
.C(data_clk), // 1-bit clock input
.CE(1'b1), // 1-bit clock enable input
.D1(1'b0), // 1-bit data input (positive edge)
.D2(1'b1), // 1-bit data input (negative edge)
.R(1'b0), // 1-bit reset
.S(1'b0) // 1-bit set
);
//2.将时钟 tx_clk 转化为差分时钟输出:tx_clk_out_p/n
OBUFDS #(
.IOSTANDARD("DEFAULT"), // Specify the output I/O standard
.SLEW("SLOW") // Specify the output slew rate
) OBUFDS_data_clk_inst (
.O(tx_clk_out_p), // Diff_p output (connect directly to top-level port)
.OB(tx_clk_out_n), // Diff_n output (connect directly to top-level port)
.I(tx_clk) // Buffer input
);
//tx_clk signal --- end
endmodule
2.3 PS端实现原理
2.3.1 PS端实现原理
Zynq-7020基于 Xilinx SoC架构,在单个设备上集成了功能丰富的双核 ARM CortexTM-A9 MPCore 处理系统(PS)和 Xilinx可编程逻辑(PL),实现了灵活性,可配置性和性能的完美结合。双核ARM Cortex-A9 MPCore 处理器是PS的核心,它还包括片内存储器、外部存储器接口和一组丰富的I/O外设。VIVADO内集成了ZYNQ7 Processing System的IP核,通过PS端配置寄存器组,进行GPIO,SPI,AD9361初始化,配置AD9361基本参数等操作,生成2.4GHZ的载波信号。
ZYNQ7 Processing System的IP核的配置如图2.3所示。
图2.3 ZYNQ7 Processing System的IP核配置
2.3.1 PS端/SDK代码
SDK代码如下:
void invalid();
void flush();
struct ad9361_rf_phy *ad9361_phy;
uint32_t sample_rate = 15.36e6; //******
uint64_t tx_lo_freq = 2400e6;//******
uint64_t rx_lo_freq = 2400e6;//*****
uint32_t bandwidth = 12e6; //*****
int32_t gain = 10; //RX gain dB ***********
uint32_t txatt = 20000; //TX attenuation mdB ********
void ad9361_Base_Parameters_Cfg(); //
void Get_Reg_Value(struct spi_device spi);
void Get_init_info();
void ad9361_gpio_cfg();
int main()
{
#ifdef XILINX_PLATFORM
Xil_ICacheEnable();
Xil_DCacheEnable();
#endif
struct spi_device spi;
spi.id_no = SPI_DEVICE_ID;
// *************
Xil_Out32(cfg_done , 0x0);
Xil_Out32(dds_en , 0x0);
Xil_Out32(adc_mode_r1, 0x1); //0:2r2t 1:1r1t
Xil_Out32(dac_mode_r1, 0x1); //0:2r2t 1:1r1t
// NOTE: The user has to choose the GPIO numbers according to desired
// carrier board.
default_init_param.gpio_resetb = GPIO_RESET_PIN;
default_init_param.gpio_sync = -1;
default_init_param.gpio_cal_sw1 = -1;
default_init_param.gpio_cal_sw2 = -1;
//gpio initial
gpio_init(GPIO_DEVICE_ID);
gpio_direction(default_init_param.gpio_resetb, 1);
// spi initial
spi_init(SPI_DEVICE_ID, 1, 0);
// ad9361 initial
ad9361_init(&ad9361_phy, &default_init_param);
// config ad9361
ad9361_Base_Parameters_Cfg();
// ad9361 引脚设置
ad9361_gpio_cfg();
// ad9361_cfg 模块 配置
Xil_Out32(cfg_done, 0x1);
Xil_Out32(dds_en , 0x1);
// 1r1t:2^12/15.36
Xil_Out32(phase , 266); // ******************
//查看相关信息
//获取配置信息
Get_init_info();
//读取寄存器值
Get_Reg_Value(spi);
while(1)
{
sleep(2);
printf("dida~\n");
}
#ifdef XILINX_PLATFORM
Xil_DCacheDisable();
Xil_ICacheDisable();
#endif
return 0;
}
.
.
.
.
.
.
代码太多
部分省略
.
.
.
.
.
.
void ad9361_gpio_cfg()
{
// 参数1:引脚 参数2:方向 0:输入 1:输出
gpio_direction(GPIO_TXNRX_PIN, 1);
gpio_direction(GPIO_ENABLE_PIN, 1);
gpio_direction(GPIO_RESET_PIN, 1);
gpio_direction(GPIO_SYNC_PIN, 1);
gpio_direction(GPIO_EN_AGC, 1);
gpio_direction(GPIO_CTL0_PIN, 1);
gpio_direction(GPIO_CTL1_PIN, 1);
gpio_direction(GPIO_CTL2_PIN, 1);
gpio_direction(GPIO_CTL3_PIN, 1);
// 参数1:引脚 参数2:值
// 如果0x014[D4]: 1:由ENABLE和TXNRX控制状态切换 0:SPI通过写入不同值来切换状态 default:0
gpio_set_value(GPIO_TXNRX_PIN, 1);
gpio_set_value(GPIO_ENABLE_PIN, 1);
gpio_set_value(GPIO_RESET_PIN, 1); // 低电平复位
gpio_set_value(GPIO_SYNC_PIN, 0); // 用于同步多个AD9361器件之间数字时钟的输入。若未使用此引脚,则将其接地
gpio_set_value(GPIO_EN_AGC, 1); // 用于自动增益控制(AGC)的手动控制输入 G5
gpio_set_value(GPIO_CTL0_PIN, 0);
gpio_set_value(GPIO_CTL1_PIN, 0);
gpio_set_value(GPIO_CTL2_PIN, 0);
gpio_set_value(GPIO_CTL3_PIN, 0);
}
void invalid()
{
Xil_DCacheInvalidateRange(POSITION_INFO_BASEADDR, 18*4);
}
void flush()
{
Xil_DCacheFlushRange(POSITION_INFO_BASEADDR, 18*4);
}
三 仿真验证
本次作品采用的是Vivado的在线逻辑分析仪(Integrated Logic Analyzer,ILA),其借用了传统逻辑分析仪的理念以及大部分的功能,并利用FPGA中的逻辑资源,将这些功能植入到FPGA的设计当中。通过观察发送的I,Q两路信号,进而确认波形是否有明显失真。设置参数为dac_data_i1,dac_data_q1表示发送的I,Q两路信号。由图3.1可知,波形无明显失真。
图3.1 发送数据波形图
四. 实物验证
由图4.1可知,发送的频率为2.4GHz,该作品满足产生载波信号频率为2.4GHz的要求。
图4.1 发送频率的频谱图
参考文献
[1] 雷鹏;罗斐翔;张博诚等基于软件无线电的数字信号处理综合实验平台[J].工业和信息化教育,2016(07).
[2] 楼才义;;徐建良;;杨小牛.软件无线电原理与应用[M].电子工业出版社,2014.
[3] 王奇.基于GNURadio的软件无线电平台研究[D].哈尔滨工业大学,2011(05).
[4] 吕海清,颜凯,赵保磊,等.基于软件无线电的多载波信号发生器设计与实现[C]//天津市 电子学会.第三十七届中国(天津)2023’IT、网络、信息技术、电子、仪器仪表创新学术会议论文集.天津光电通信技术有限公司
,2023:4.DOI:10.26914/c.cnkihy.2023.022913.
[5] Razavi, B.: "Design of Analog CMOS Integrated Circuits," McGraw-HILL, 2001.
[6] A. Singhal, C. Madhu and V. Kumar, "Designs of All Digital Phase Locked Loop," 2014 Recent Advances in Engineering and Computational Sciences (RAECS), Chandigarh, India, 2014, pp. 1-5, doi: 10.1109/RAECS.2014.6799523.
[7] A. Godave, P. Choudhari and A. Jadhav, "Comparison and Simulation of Analog and Digital Phase Locked Loop," 2018 9th International Conference on Computing, Communication and Networking Technologies (ICCCNT), Bengaluru, India, 2018, pp. 1-4, doi: 10.1109/ICCCNT.2018.8494198.
[8] 马鹏;刘佩;张伟;.基于UVM的AMBA总线接口通用验证平台[J].计算机系统应 用,2021(07).
[9] 刘达;倪伟;徐春琳;.基于UVM的AXI总线验证IP设计[J].微电子学,2019(05).
[10] 李晨阳;宋澍申;王涛;黄坤超;.一种基于UVM的高层次化验证平台设计[J].微电子学 与计算机,2019(06).
[11] 禹永植,夏泽宇,刘宇.一种搭载FPGA和AD9361的软件无线电平台实现方法[J].应用科 技,2023,50(04):90-95.