基于FPGA的BPSK、QPSK以及OQPSK实现_fpga实现bpsk调制-程序员宅基地

技术标签: fpga开发  小白学FPGA  

大家第一次接触PSK是什么时候呢?我第一次是在通信原理里面的数字带通传输系统里面接触到了数字调制原理。然后由于自己现在在学FPGA,所以就想着看能不能用FPGA实现一下书本里面所学的BPSK、QPSK以及OQPSK。

首先介绍一下几种调制原理:

一、二进制相移键控(BPSK)

相移键控是利用载波的相位变化来传递信息,而振幅和频率保持不变。在BPSK中,通常用初始相位0和π分别表示二进制“1”和“0”。因此,BPSK信号的时域表达式为  

    

其中φn表示第n个符号的绝对相位,即    

                    

因此,BPSK信号的表达式也可写为

BPSK信号的调制有两种方法,一种是模拟调制方法,如图1(a)所示,另一种则是键控法,如图1(b)所示,即通过开关电路来输出相应相位的载波。我们在这里采用键控法来产生BPSK信号。

(a)模拟调制方法

(b)键控法

图1BPSK信号调制原理框图

 二、正交相移键控(QPSK) 

QPSK即正交相移键控又被称为四相移键控。它具有4种相位状态对应四组数据,即00,01,10,11。

QPSK信号的产生有两种方法,第一种是正交调相,其原理如图2(a)所示。输入的基带信号(单极性归零码元)经过串并转换电路变成两路二进制不归零双极性码元a和b。并行码元a和b的持续时间均是输入码元的2倍。a路码元与载波cos\left ( wct \right )相乘得到I路信号,b路码元与载波-sin(wct)相乘得到Q路信号,再经过相乘电路将I、Q两路信号进行叠加得到QPSK信号。

第二种是相位选择法,其原理如图2(a)所示。这时输入的基带信号经过串并转换后用于控制一个相位选择电路,按照当时输入的双比特ab,决定选择哪一个相位的载波输出。

(a)正交调相法产生QPSK信号

(b)相位选择法产生QPSK信号

图2QPSK信号调制原理框图

三、偏置正交相移键控(OQPSK)

OQPSK称为偏置正交相移键控,它与QPSK的唯一区别在于两个正交分量的两个比特a和b在时间上错开了半个码元周期。在QPSK体制中,它的相邻码元最大相位差达到了180°,由于这样的相位突变在频带受限的系统中会引起信号包络的很大起伏,这是不希望的,所以为了减少此相位突变,将两个正交的分量比特a和b在时间上错开半个码元,使之不能同时改变。 

 下面就进入程序设计及仿真部分:

整个程序设计主要包括伪随机序列产生模块、载波产生模块、调制模块。

一、随机序列产生模块

module m_data(
    input sys_clk,
    input rst_n,
    output m_data1,
    output reg [1:0] m_data2,
    output reg [1:0] om_data2
    );
parameter N = 32;
parameter counter = 10_000;
reg [8:0] reg1;
reg reg2;
reg [N-1:0] count;
reg count1_flag;
reg count2_flag;
assign m_data1 = reg1[0];
always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
        count <= 32'd0;
    else if(count == counter - 1'b1)
        count <= 32'd0;
    else
        count <= count + 1'b1;
end
always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
        reg1 <= 9'b000111101;
    else if(count == counter - 1'b1)
    begin
        reg1[8] <= reg1[0]^reg1[2];
        reg1[7:0] <= reg1[8:1]; 
    end
end

always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
    begin
        om_data2 <= 2'b00;
        reg2 <= 1'b0;
        count1_flag <= 1'b1;
    end
    else if(count == counter - 1'b1)
    begin
        if(count1_flag == 1'b1)
        begin
            om_data2[1] <= m_data1;
            count1_flag <= 1'b0;
        end
        else
        begin 
            om_data2[0] <= m_data1;
            count1_flag <= 1'b1;
        end
    end
end
always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
    begin
        m_data2 <= 2'b00;
        reg2 <= 1'b0;
        count2_flag <= 1'b1;
    end
    else if(count == counter - 1'b1)
    begin
        if(count2_flag == 1'b1)
        begin
            reg2 <= m_data1;
            count2_flag <= 1'b0;
        end
        else
        begin 
            m_data2[1] <= reg2;
            m_data2[0] <= m_data1;
            count2_flag <= 1'b1;
        end
    end
end
endmodule

二、载波产生模块

module sine_cosine(
    input sys_clk,
    input rst_n,
    output [7:0] sine,
    output [7:0] cosine
    );
parameter N = 32;
/*
Fout = 50khz    Fclk = 50Mhz    Fout = FEORD*(Fclk / 2**N)
FWORD = (Fout * 2**N) / Fclk = 50_000 * 65536 * 65536 / 50_000_000 = 4294967
*/
parameter FWORD = 4294967;
parameter PWORD = 128;
reg [N-1:0] addr;
wire [8:0] sine_addr;
wire [8:0] cosine_addr;
assign sine_addr = addr[N-1:N-9];
assign cosine_addr = addr[N-1:N-9] + PWORD;
always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
        addr <= 32'd0;
    else
        addr <= addr + FWORD;  
end
rom_sine rom_sine_inst1 (
  .clka(sys_clk),    // input wire clka
  .addra(sine_addr),  // input wire [8 : 0] addra
  .douta(sine)  // output wire [7 : 0] douta
);
rom_sine rom_sine_inst2 (
  .clka(sys_clk),    // input wire clka
  .addra(cosine_addr),  // input wire [8 : 0] addra
  .douta(cosine)  // output wire [7 : 0] douta
);
endmodule

三、调制模块

(1)BPSK

module bpsk(
    input sys_clk,
    input rst_n,
    input m_data1,
    output [8:0] bpsk
    );
reg [31:0] bpsk_PWORD;
wire [8:0] rom_addr;
wire [7:0] bpsk_reg;
assign bpsk = {1'b0,bpsk_reg};
always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
        bpsk_PWORD <= 32'd0;
    else if(m_data1)
        bpsk_PWORD <= 32'd0;
    else
        bpsk_PWORD <= 32'd256;
end
rom_addr  rom_addr_inst(
    .sys_clk(sys_clk),
    .rst_n(rst_n),
    .PWORD(bpsk_PWORD),
    .rom_addr(rom_addr)
);
rom_sine rom_sine_inst (
  .clka(sys_clk),    // input wire clka
  .addra(rom_addr),  // input wire [8 : 0] addra
  .douta(bpsk_reg)  // output wire [7 : 0] douta
);
endmodule

(2)QPSK

module qpsk(
    input sys_clk,
    input rst_n,
    input [1:0] m_data2,
    input [7:0] sine,
    input [7:0] cosine,
    output reg  [8:0] qpsk
);
reg [1:0] I_tmp;
reg [1:0] Q_tmp;
reg [7:0] I_cos;
reg [7:0] Q_sin;
always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
        begin
            I_tmp <= 2'b00;
            Q_tmp <= 2'b00;
        end
    else
        begin
             I_tmp <= (m_data2[0]) ? 2'b01 : 2'b11; 
             Q_tmp <= (m_data2[1]) ? 2'b01 : 2'b11;
        end
end
always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
        Q_sin <= 8'd0;
    else
    begin
        case(Q_tmp)
        2'b01:
            Q_sin <= sine;
        2'b11:
            Q_sin <= -sine;
        endcase
    end
end
always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
        I_cos <= 8'd0;
    else
    begin
        case(I_tmp)
        2'b01:
            I_cos <= cosine;
        2'b11:
            I_cos <= -cosine;
        endcase
    end
end
always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
        qpsk <= 9'd0;
    else
        qpsk <= I_cos + Q_sin;
end
endmodule

(3)OQPSK

OQPSK的代码基本上和QPSK保持一致,区别仅在于输入基带信号的不同。

module oqpsk(
    input sys_clk,
    input rst_n,
    input [1:0] m_data2,
    input [7:0] sine,
    input [7:0] cosine,
    output reg [8:0] oqpsk
);
reg [1:0] I_tmp;
reg [1:0] Q_tmp;
reg [7:0] I_cos;
reg [7:0] Q_sin;
always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
        begin
            I_tmp <= 2'b00;
            Q_tmp <= 2'b00;
        end
    else
        begin
             I_tmp <= (m_data2[0]) ? 2'b01 : 2'b11; 
             Q_tmp <= (m_data2[1]) ? 2'b01 : 2'b11;
        end
end
always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
        Q_sin <= 8'd0;
    else
    begin
        case(Q_tmp)
        2'b01:
            Q_sin <= sine;
        2'b11:
            Q_sin <= ~sine +1'b1;
        endcase
    end
end
always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
        I_cos <= 8'd0;
    else
    begin
        case(I_tmp)
        2'b01:
            I_cos <= cosine;
        2'b11:
            I_cos <= ~cosine + 1'b1;
        endcase
    end
end
always@(posedge sys_clk or negedge rst_n)
begin
    if(!rst_n)
        oqpsk <= 9'd0;
    else
        oqpsk <= I_cos + Q_sin;
end
endmodule

以上都是各子模块的代码,最终我们需要在顶层模块中调用以上模块,然后编写仿真文件才能进行仿真。 

 四、仿真结果分析

通过仿真,我们得到如图所示的结果。m_data1是我们生成的数字基带信号,可以看到,当m_data1为1时,我们的BPSK信号相位为0;m_data1为0时,BPSK信号的相位为180°,达到了设计要求。m_data2是m_data1串并转换之后的信号,可以看出它的码元长度是基带信号的2倍,m_data2[1]和m_data2[0]分别代表I、Q两路信号,当I、Q两路信号为10时,相位为7π/4;为00时,相位为5π/4;为11时,相位为1π/4;为01时,相位为3π/4。m_data3是m_data2的I、Q两路信号错开半个码元周期的信号,此时我们可以看到信号从00变到11经历了00到10再到11即相位从5π/4到7π/4再到1π/4这样一个过程,避免了信号包络起伏大和相位发生突变。

到此,本次的设计基本上就结束了。由于水平有限,有些想法和思路还不成熟,存在很大问题。但是我还是想把我的想法分享给大家,希望大家看到之后能有所启发,同时也希望大家在发现错误之后能给我指出来,我会继续对此进行完善。 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_60956202/article/details/132840919

智能推荐

解决win10/win8/8.1 64位操作系统MT65xx preloader线刷驱动无法安装_mt65驱动-程序员宅基地

文章浏览阅读1.3w次。转载自 http://www.miui.com/thread-2003672-1-1.html 当手机在刷错包或者误修改删除系统文件后会出现无法开机或者是移动定制(联通合约机)版想刷标准版,这时就会用到线刷,首先就是安装线刷驱动。 在XP和win7上线刷是比较方便的,用那个驱动自动安装版,直接就可以安装好,完成线刷。不过现在也有好多机友换成了win8/8.1系统,再使用这个_mt65驱动

SonarQube简介及客户端集成_sonar的客户端区别-程序员宅基地

文章浏览阅读1k次。SonarQube是一个代码质量管理平台,可以扫描监测代码并给出质量评价及修改建议,通过插件机制支持25+中开发语言,可以很容易与gradle\maven\jenkins等工具进行集成,是非常流行的代码质量管控平台。通CheckStyle、findbugs等工具定位不同,SonarQube定位于平台,有完善的管理机制及强大的管理页面,并通过插件支持checkstyle及findbugs等既有的流..._sonar的客户端区别

元学习系列(六):神经图灵机详细分析_神经图灵机方法改进-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏27次。神经图灵机是LSTM、GRU的改进版本,本质上依然包含一个外部记忆结构、可对记忆进行读写操作,主要针对读写操作进行了改进,或者说提出了一种新的读写操作思路。神经图灵机之所以叫这个名字是因为它通过深度学习模型模拟了图灵机,但是我觉得如果先去介绍图灵机的概念,就会搞得很混乱,所以这里主要从神经图灵机改进了LSTM的哪些方面入手进行讲解,同时,由于模型的结构比较复杂,为了让思路更清晰,这次也会分开几..._神经图灵机方法改进

【机器学习】机器学习模型迭代方法(Python)-程序员宅基地

文章浏览阅读2.8k次。一、模型迭代方法机器学习模型在实际应用的场景,通常要根据新增的数据下进行模型的迭代,常见的模型迭代方法有以下几种:1、全量数据重新训练一个模型,直接合并历史训练数据与新增的数据,模型直接离线学习全量数据,学习得到一个全新的模型。优缺点:这也是实际最为常见的模型迭代方式,通常模型效果也是最好的,但这样模型迭代比较耗时,资源耗费比较多,实时性较差,特别是在大数据场景更为困难;2、模型融合的方法,将旧模..._模型迭代

base64图片打成Zip包上传,以及服务端解压的简单实现_base64可以装换zip吗-程序员宅基地

文章浏览阅读2.3k次。1、前言上传图片一般采用异步上传的方式,但是异步上传带来不好的地方,就如果图片有改变或者删除,图片服务器端就会造成浪费。所以有时候就会和参数同步提交。笔者喜欢base64图片一起上传,但是图片过多时就会出现数据丢失等异常。因为tomcat的post请求默认是2M的长度限制。2、解决办法有两种:① 修改tomcat的servel.xml的配置文件,设置 maxPostSize=..._base64可以装换zip吗

Opencv自然场景文本识别系统(源码&教程)_opencv自然场景实时识别文字-程序员宅基地

文章浏览阅读1k次,点赞17次,收藏22次。Opencv自然场景文本识别系统(源码&教程)_opencv自然场景实时识别文字

随便推点

ESXi 快速复制虚拟机脚本_exsi6.7快速克隆centos-程序员宅基地

文章浏览阅读1.3k次。拷贝虚拟机文件时间比较长,因为虚拟机 flat 文件很大,所以要等。脚本完成后,以复制虚拟机文件夹。将以下脚本内容写入文件。_exsi6.7快速克隆centos

好友推荐—基于关系的java和spark代码实现_本关任务:使用 spark core 知识完成 " 好友推荐 " 的程序。-程序员宅基地

文章浏览阅读2k次。本文主要实现基于二度好友的推荐。数学公式参考于:http://blog.csdn.net/qq_14950717/article/details/52197565测试数据为自己随手画的关系图把图片整理成文本信息如下:a b c d e f yb c a f gc a b dd c a e h q re f h d af e a b gg h f bh e g i di j m n ..._本关任务:使用 spark core 知识完成 " 好友推荐 " 的程序。

南京大学-高级程序设计复习总结_南京大学高级程序设计-程序员宅基地

文章浏览阅读367次。南京大学高级程序设计期末复习总结,c++面向对象编程_南京大学高级程序设计

4.朴素贝叶斯分类器实现-matlab_朴素贝叶斯 matlab训练和测试输出-程序员宅基地

文章浏览阅读3.1k次,点赞2次,收藏12次。实现朴素贝叶斯分类器,并且根据李航《统计机器学习》第四章提供的数据训练与测试,结果与书中一致分别实现了朴素贝叶斯以及带有laplace平滑的朴素贝叶斯%书中例题实现朴素贝叶斯%特征1的取值集合A1=[1;2;3];%特征2的取值集合A2=[4;5;6];%S M LAValues={A1;A2};%Y的取值集合YValue=[-1;1];%数据集和T=[ 1,4,-1;..._朴素贝叶斯 matlab训练和测试输出

Markdown 文本换行_markdowntext 换行-程序员宅基地

文章浏览阅读1.6k次。Markdown 文本换行_markdowntext 换行

错误:0xC0000022 在运行 Microsoft Windows 非核心版本的计算机上,运行”slui.exe 0x2a 0xC0000022″以显示错误文本_错误: 0xc0000022 在运行 microsoft windows 非核心版本的计算机上,运行-程序员宅基地

文章浏览阅读6.7w次,点赞2次,收藏37次。win10 2016长期服务版激活错误解决方法:打开“注册表编辑器”;(Windows + R然后输入Regedit)修改SkipRearm的值为1:(在HKEY_LOCAL_MACHINE–》SOFTWARE–》Microsoft–》Windows NT–》CurrentVersion–》SoftwareProtectionPlatform里面,将SkipRearm的值修改为1)重..._错误: 0xc0000022 在运行 microsoft windows 非核心版本的计算机上,运行“slui.ex