下冰雹 · 7月17日

基于FPGA的直接扩频通信系统设计(附代码)

大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。

今天给大侠带来直接扩频通信,由于篇幅较长,话不多说,上货。

导读 

本篇适用于有一定通信基础的大侠,本篇使用的理论不仅仅是扩频通信。为了便于学习,本章将会以实战的方式,对整个工程的仿真。并对一些关键的仿真结果进行说明。各位大侠可依据自己的需要进行阅读,参考学习。

理论基础

一、扩频通信

香农(E.Shannon)在 1945 年、1948 年和 1949 年连续发表了有关信息论和通信加密以及系统安全性等 3 篇论文。最后给出信道容量的数学计算公式:

image.png
( C:信道容量;B:带宽大小;S:信号能量;N:噪声能量 )

根据香农最后给出的信道容量公式(C=B·log2(1+S/N))可知,信道容量与带宽大小正好成正比,不论信噪比(S/N)有多小(但不会为零),只要带宽足够大,信道容量就足够大。根据这个结论,引出了扩频通信技术。

扩频通信,即扩展频谱通信技术(Spread Spectrum Communication),通过扩频调制用一个更高频率的伪随机码将基带信号扩展到一个更宽的频带内,使发射信号的能量被扩展到一个更宽的频带内,从而看来如同噪声一样,使该系统更具隐藏性和抗干扰性。接收端则采用相同的伪随机码进行解扩,从而恢复出原始信息数据。按照频谱扩展的方式的不同,现有的扩频通信系统可以分为直接序列扩频(Direct Sequence Spectrum)工作方式(简称直接扩频方式)、跳变频率(Frequency Hopping)方式(简称跳频方式)和混合方式四种[1]。本文所设计的使用直接序列扩频方式。

直接序列扩频通信是将带传输的二进制信息数据用高速的伪随机码(PN 码)直接调制,实现频谱扩展后传输,在接收端使用相逆方式进行解扩,从而可以恢复信源的信息。最能体现扩频通信的特点就是它具有优异的抗干扰能力。所以它常常被运用于一些干扰性很强的通信领域中。比如无线通信。

二、M序列

2.1 伪随机码概述

伪随机码也称为伪随机序列。是模仿随机序列的随机特性而产生的一种码字,也称为伪噪声序列或者伪噪声吗。直接扩频通信的性能取决于其伪随机序列的性能,伪随机码序列是一种规律难以发现、具有类似白噪声统计特性的编码信号。所以,伪随机序列通常有以下要求:

  • a. ‘0’和’1’的个数基本相等,具有良好的随机性(由于数字通信通常以二进制位多,所以要‘0’的概率和’1’的概率基本相等);
  • b. 具有尖锐的自相关特性,以保证通过同步伪随机序列完成扩频信号的解扩;
  • c. 不同的 PN 序列具有很小的互相关特性,以防止通过不同的 PN 序列扩频后的信号被此干扰;
  • d. 不同的 PN 序列具有很小的互相关特性,以防止通过不同的 PN 序列扩频后的信号被此干扰;
  • e. PN 序列总量大,以满足多用户需求

2.2 伪随机码选型

根据上述要求,常用的序列有包括:m 序列、gold 序列和 Walsh 序列等,m 序列通常容易硬件直接硬件实现;gold 序列自相关性差;Walsh 序列一般使用写入双口 RAM 中,然后启动读取逻辑序列产生,但耗费大量的硬件逻辑单元。故本设计选用了 m 序列作为系统的伪随机码。

2.3 m 序列产生

m 序列是最长线性反馈移位寄存器序列的简称,它是最常用的一种伪随机序列。由 n 级串联寄存器组成,通过反馈逻辑的移位寄存器设定初始状态后,在时钟的触发下,每次移位后各级寄存器状态会发生变化。从任何一个寄存器输出得到的一串序列,该序列称为移位寄存器。其框图如图 1 所示为一个时钟触发下的时序电路。

image.png

图中使用 n 个寄存器,通常将 a0 作为输出信号产生 m 序列。从上图也可以看出,一个完成的 n 级 m 序列是由一个相应的线性反馈逻辑表达式,即为:

image.png

(其中, ‘⊕’代表异或运算或叫模 2 加运算,Cn ∈{0,1} )

由上式可知,只有当 Cn=1 时,对应的多项式才有效。为了便于表示,通常将上式与本原多项式对应。本原多项式的数学表达式如下:

image.png

仅当该多项式为本原多项式时才能产生 m 序列,以下列出部分本原多项式表 1。 

image.png

其中,n 阶 m 序列具有如下特点:

a. 序列长度为 2n-1; 

b.‘0’和‘1’个数相当,即‘1’的个数比‘0’的个数多且仅多 1 个。

本原多项式是由多位科学家及其科学工作者最终得来,关于它们的具体得来,这里不作多解释。

本文设计采用的是 5 阶 m 序列作为系统的伪随机码发生器,其对应的硬件框图如图 2。由于级联的寄存器初始状态不能全为 0。

本设计中规定初始状态为:a4 a3 a2 a1 a0 = 5’b10000。

a0 输出的得到的 m 序列为:{0000101011101100011111001101001}。从左到右顺序输出。

image.png

根据以上 m 序列的拓扑结构图,我们就很容易使用 FPGA 的资源来设计5 阶的 m 序列,只要 5 个触发器和 1 个异或门就可以完成该设计。而 Verilog HDL 语言更容易完成设计。具体内容,参考 coder 模块。

三、汉明码

数字信号在传输过程中常常因干扰而发生损坏。接收端接收到数据后可能错误的判决。乘性干扰引起的码间串扰可以采用均衡的办法纠正。而加性干扰的影响则需要其他办法解决。对于加性干扰,本文考虑使用差错控制措施。

差错控制措施,即在数据中间添加必要的监督位,达到可以对错误数据的监督和纠错能力。对于差错控制措施,前辈科学家和科学工作者也设计出多种方法,各有各的优劣。本设计使用的是汉明码(7,4),其中 7 为码组的总长度,4 为原始信息位数,则监督位为 3 位。故每发送 4 比特信息需要添加 3 比特的监督位,监督位是根据信息位既定约束关系得到。汉明码是一种能纠错 1 比特错误的特殊的线性分组码。由于它的编译码简单,在数据通信和计算机存储系统中广泛应用,如蓝牙通信技术和硬盘阵列等。

本设计所使用的汉明码的最小码距为 3,可以纠正 1 为错误,检测 2 位错误。但对 2 位错误码并不能正确的纠错。尽管发生 1 位错的概率相对最高,但在一些比较高的应用中汉明码不能满足要求。码距是指两个不同码组间对应位不同的个数,例如 1000111 和 10001100 的码距为 3。

对于以上介绍比较乏味,以下使用另一种角度来对(7,4)码进行介绍汉明码的原理与设计过程。

我们可以把添加纠错码作为一个系统,即输入 4 比特原始信息位(a6,a5,a4,a3)而输出带有 3 比特监督位(a2,a1,a0)的码组。

对于 3 个监督位,有以下规则:

S1. 监督位 a2 作为 a6、a5 和 a4 的偶校验码,即 a2^a6^a5^a4=0;

S2. 监督位 a1 作为 a6、a5 和 a3 的偶校验码,即 a2^a6^a5^a3=0;

S3. 监督位 a0 作为 a6、a4 和 a3 的偶校验码,即 a2^a6^a4^a3=0;(‘^’表示异或或者表示模 2 加)对应以上 3 个监督位的规则,可以列出其对应的全部码组,如表 2。

image.png

从上表中,不难看出,纠错码产生系统输出由 a6a5a4a3a2a1a0 构成,而每发送一个码组,只发送 4 比特的原始信息。从上表中,也不能直观的说明该编码方式可以纠错 1 位码元,而不能纠错 2位,而图 3 正好可以解释。

image.png

图中 3 个大圆圈对应 3 个监督位的三个规则,可以这么理解,如下:

  1. 如果接收到的信息只不符合规则“S1”,则对应图中 a2;
  2. 如果接收到的信息只不符合规则“S2”,则对应图中 a1;
  3. 如果接收到的信息只不符合规则“S3”,则对应图中 a0;
  4. 如果接收到的信息不符合规则“S1”和“S2”,则对应图中 a2a1;
  5. 如果接收到的信息不符合规则“S1”和“S3”,则对应图中 a2a0;
  6. 如果接收到的信息不符合规则“S2”和“S3”,则对应图中 a1a0;
  7. 如果接收到的信息不符合规则“S1”、“S2”和“S3”,则对应图中的a2a1a0。

从图 3 中,可以给出一个结论,只要错误码只有 1 位,系统就可以纠正错误;而如果错误码达到 2 位,就无法纠正错误。

根据以上两表对应关系可以推出以下错误规则和误码位置关系的结论,列出如表 3 所示。

image.png

(注明: 对应的 1 表示错误,例如 S1 S2 S3 等于 001,表示接收到的数据违反规则 S3)

从以上对汉明码的原理,到设计使用(7,4)码的设计,设计中的 3 个监督位都可以使用异或操作完成。具体内容将在后面介绍。

四、系统结构

对于该系统,我们最注重的是原始码元汉明码编码、扩频、信道编码、频解码和纠错码系统。当然,由于设计仿真需要模拟一些关于加性干扰,不得不在模拟发送过程中添加干扰源。加上测试平台的模块构成了整个系统的通信方式,便于读者理解。整个系统的拓扑结构图如图 4 所示。

image.png

图中包括整个设计的构架,也是数字信号传输的基本模型。包括信源、汉明码编码、m 序列发生器、解扩器、m 序列同步器、汉明码解码器和信宿等。Testbench 平台会把信源和信宿进行比对,输出传输的结果,并且打印到屏幕上供查看。其中,发送端和接收端才可综合,其它模块均用于测试,不可综合。噪声发送器为模拟信道传输过程中的干扰。加法器表示加性干扰。各个模块的代码对应如表 4 所示。 

image.png

还有一些相关的文件,将在中篇详细说明。

作为一个底层模块设计人员,以数据流作为主线是必须的。关于本人这个结论,并没有真正的得到老一辈工程师的验证。但在本篇中,就以数据流的方式作为设计主线。

系统的 verilog 实现 

一、数据传输过程

从上一章中的拓扑结构图中可知数据流的过程,如图 5 所示。

image.png

输出给 coder 模块原始信息,每 4 比特作为一组,所以,在 mcu 中,每一个字节拆分成 2 个 4 比特发送到 coder 模块中。

在coder中对原始信号进行扩频、信道编码、最终输出2比特的数据01和11(即 -1 和+1)给 add_noise,经过 add_noise 加性干扰噪声后,输出 3 比特的数据给decoder 模块,decoder 模块经过解扩后输出给 correct 模块纠错,最终发送给 slaver模块。

最终 top 模块根据发送的原始数据和接收后的数据进行比对,输出结果(打印到屏幕上)。这里只是大概的介绍了设计中数据流的过程。在以下各个模块设计中还会具体提到。

二、MCU模块

模块 mcu 负责通信的信源部分,除了给 coder 发送数据,也为 coder 模块和add_noise 模块提供时钟、复位信号。该模块对整个仿真有着相当重要。因为它的设计关系到系统仿真的完整性。

mcu 模块包含随机数据的产生、存储、发送。随机数的产生采用系统函数 random产生。而数据存储有两个位置,一个是输出存储到文件中,另一个是存储到 memory中。存储到文件中是为了提供仿真后数据的查看,而存放 memory 中为了数据的发送和之后数据的比对。

该模块与下游模块 coder 有一定的时序逻辑,它控制 coder 模块的开始,由 mcu发送 send_ena 到 coder,随后等待 coder 模块反馈信号 insourse_ena。Insourse_ena信号有效,则发送数据,否则停止发送数据。数据发送结束只要撤销 send_ena 信号的有效性即可。

具体代码如下:


//***************************************************************/
//模块名: mcu
//作 者: The last one
//用 途: 包含发送部分全部内容
//版本说明:
//***************************************************************/
`timescale 1us/1us
module mcu(
noised_data //输出带有噪声信号
)

parameter TestNumber = 400;
parameter Period = 100;

/**********************发送数据端口信号定义*********************/
  wire [1:0] un_noised_data;
  output [2:0] noised_data;
  reg clk1,clk31,rst_n;
  reg send_ena;
  
  wire insourse_ena;
  integer indataFILE; //指向一个文件,用于存储
  integer i,j,k;
  
  reg [7:0] indata_mem[TestNumber:1];
  reg [7:0] indatabyte;
  wire in_data;
  assign in_data=indatabyte[7];
  
// 初始值
  initial 
    begin
      i = 0;
      j = 1;
      k = 1;
    end
    
  initial
    begin
      rst_n = 0;
      send_ena = 0;
      @(posedge clk31)
      #(Period * 150)
      rst_n = 1;
      #(Period * 33)
      send_ena = 1;
    end
    
    initial 
      begin
        clk1 = 0;
        #(Period*3)
        forever #(Period * 31) clk1 = ~clk1;
      end
      
    initial 
      begin
        clk31 = 0;
        #(Period*20)
        forever #(Period) clk31 = ~clk31;
      end
      
    initial
    /********************************************
    打开或者创建一个文件(名为 indataRandom.dat)
    生成测试用的随机字节写入该文件中
    把第一个数据赋给 indatabyte
    最后关闭该文件,释放 indataFILE
    ********************************************/
      begin
        indataFILE = $fopen("./indataRandom.dat");
        $display (" indataFILE=%0d ", indataFILE);
        for(k = 1; k <= TestNumber; k = k+1) 
          begin
            indata_mem[k]={$random}%256; 
            $fdisplay(indataFILE," %0h ",indata_mem[k]); 
          end
        indatabyte <= indata_mem[1];
        $fclose(indataFILE ); 
      end
    
  always@(posedge clk1)
  /************************************************
  当 coder 使能信号(insourse_ena)到来,每 1 个 clk1 时钟把一个数据传出
  传出的数据与 indataRandom.dat 文件的数据一样
  ************************************************/
    begin 
      if(insourse_ena)
      if ( j<=TestNumber )
        begin
         if(i<7)
              begin 
                indatabyte={indatabyte[6:0],1'b0};
                i=i+1;
              end
          else if (i==7)
              begin 
                indatabyte=indata_mem[j+1];
                j=j+1;
                i=0;
              end
        end 
      else
      j = 1;
      else
      ;
    end 
  
  coder coder(
              .clk1(clk1),
              .clk31(clk31),
              .rst_n(rst_n),
              .send_ena(send_ena),
              .in_data(in_data),
              .out_data(un_noised_data),
              .insourse_ena(insourse_ena)
              );
              
  add_noise noise(
                .clk31(clk31),
                .rst_n(rst_n),
                .un_noised_data(un_noised_data),
                .noised_data(noised_data)
                );
                
endmodule

三、coder 模块

模块 coder 为原始数据的接收、对数据的汉明码编码、扩频和信道编码等操作。

模块的通信时序如下:

1. 接收到模块 mcu 原始数据到来之前,先发送一个同步头,起止由 mcu 控制;

2. 每发送 128 个字节原始数据前,发送数据 0000 作为数据帧同步,用于检测发送和接收两端数据发送是否同步;

3. 对原始数据进行汉明码编码,监督位为 3 位,全部放到数据位后;

4. 对编好的信息进行扩频,1 比特扩频到 31 比特;

5. 对扩频后的信号进行信道编码,即 1 用 01(+1)、0 用 11(-1)。

扩频通信,原始数据的频率必然比扩频后的频率小得多,本设计的 m 序列码是 31 比特位为一个周期。所以,原始信息的频率假设为 f 1,则扩频频率 f2 = 31x f1。因此,该模块有两个时钟。

该模块采用输入使能信号(send_ena)和输出反馈信号(insourse_ena)作为与上游模块(mcu)的握手信号。当 send_ena 有效,同时 insourse_ena 有效时,mcu 才会发送真实的数据到输入端口。而当 send_ena 信号有效的一段时间内,先发送同步头和数据帧同步后,才使 insourse_ena 有效,发送原始数据。

发送端固定对应的 m 序列为{0000101011101100011111001101001}。则每一个数据的发送都是按该序列发送,在接收端更容易同步(解调时更详细解释)。因此 m 序列的寄存器需一个标示位(flag),使数据和随机码同步送。关

于该模块的工作过程,可以参照下篇的仿真。

该模块的参考具体代码如下:


//************************************************************
//模块名: coder
//作 者: The last one
//工 程:
//用 途: 汉明码编码、扩频、信道编码
//版本说明:
//************************************************************
module coder(
              input wire clk1,
              input wire clk31,
              input wire rst_n,
              input wire send_ena, //发送信号使能
              input wire in_data,
              output reg insourse_ena, // 获取数据,用于与 mcu 握手
              output wire [1:0] out_data // 输入数据。
              );
              
    parameter idle = 4'b0001,
    body = 4'b0010;
    
    reg in_data_buf;
    reg out_data_flag;
    reg check1,check2, check3; // 3 位监督位
    reg [4:0] m_coder; //m 系列码组,最低位输出作为 m 序列
    reg flag;
    reg [3:0] state1;
    reg [7:0] data_number;
    reg [3:0] state;
    reg [3:0] state_m;
    
/***************************************************
作为输出使能模块,并进行信道编码
***************************************************/
    assign out_data = (send_ena && out_data_flag)?
    (((in_data_buf ^ m_coder[0]) == 1'b1)? 2'b01 : 2'b11) : 2'b10;
    // 该部分的 ll 信号只用于调制代码时使用
    reg ll;
    always @(posedge clk1)
      if(!rst_n)
        ll <= 0;
      else
        ll <= in_data_buf;
    
/***********************************************
 主状态机,发送头同步->数据帧同步->数据
 每发送 128 个数据又跳转到发送数据帧同步 Start
***********************************************/
    always @(posedge clk1) 
      begin
        if(!rst_n)
          sys_reset;
        else if(send_ena)
                case(state) // synthesis full_case
                    4'h0 : head; //产生头同步信号 11111111110
                    4'h1 : data_frames; //数据帧同步信号 0000+000
                    4'h2 : ready_data; //数据发送
                endcase
            else
          sys_reset; //复位
      end
      
/***************
 复位 Start
****************/
    task sys_reset;
      begin
          in_data_buf <= 1'b0;
          insourse_ena <= 1'b0;
          data_number <= 8'd0;
          out_data_flag <= 1'b0;
          flag <= 1'b0;
          state <= 4'h0;
          state1 <= 4'h0;
          check1 <= 1'b0;
          check2 <= 1'b0;
          check3 <= 1'b0;
      end
    endtask
    
/*****************************************
 发送数据帧同步信号 0000+000 Start
*****************************************/
    task head;
        begin
            case(state1) // synthesis full_case
                0,1,2,3,4,5,6,7,8,9:
              begin
                  out_data_flag <= 1'b1;
                  flag <= 1'b1;
                  in_data_buf <= 1'b1;
                  state1 <= state1 + 1'b1;
              end
                  10 : begin
                          in_data_buf <= 1'b0;
                          state <= 4'h1;
                          state1 <= 4'h0;
                        end
            endcase
        end
    endtask
    
/*****************************************
 发送数据帧同步信号 0000+000 Start
*****************************************/
    task data_frames;
        begin
            case(state1) // synthesis full_case
                0,1,2,3,4,5:begin
                                in_data_buf <= 1'b0;
                                state1 <= state1 + 1'b1;
                            end
                            
                6 : begin
                        in_data_buf <= 1'b0;
                        state <= 4'h2;
                        state1 <= 4'h0;
                        data_number <= 8'd0;
                        insourse_ena <= 1'b1;
                    end
            endcase
        end
    endtask
    
/********************************************************
 发送真实数据模块,每发送 4 位信息位和 3 位监督位 Start
********************************************************/
    task ready_data;
        begin
            case(state1) // synthesis full_case
            0 :begin
                  insourse_ena <= 1'b1;
                  in_data_buf <= in_data;
                  check1 <= in_data;
                  check2 <= in_data;
                  check3 <= in_data;
                  state1 <= state1 + 1'b1;
               end
               
            1 :begin
                    insourse_ena <= 1'b1;
                    in_data_buf <= in_data;
                    check1 <= check1 ^ in_data;
                    check2 <= check2 ^ in_data;
                    state1 <= state1 + 1'b1;
               end
            
            2 :begin
                    insourse_ena <= 1'b1;
                    in_data_buf <= in_data;
                    check1 <= check1 ^ in_data;
                    check3 <= check3 ^ in_data;
                    state1 <= state1 + 1'b1;
               end
            
            3 :begin
                  in_data_buf <= in_data;
                  check2 <= check2 ^ in_data;
                  check3 <= check3 ^ in_data;
                  state1 <= state1 + 1'b1;
                  insourse_ena <= 1'b0; //暂停主机送来数据,接下来发送监督位
               end
            
            4 :begin
                  in_data_buf <= check1;
                  state1 <= state1 + 1'b1;
                end
            
            5 :begin
            in_data_buf <= check2;
            state1 <= state1 + 1'b1;
            end
            
            6 :begin
            in_data_buf <= check3;
            state1 <= 4'h0;
            if(data_number == 8'd127)      
              begin
              insourse_ena <= 1'b0;
              data_number <= 8'd0;
              state <= 4'h1;
              end
              else
              begin
              insourse_ena <= 1'b1;
              data_number <= data_number + 1'b1;
              end
              end
              endcase
              end
              endtask
    
/*********************************************
 m 系列产生 由主机发出使能信号 Start
*********************************************/
    always @(posedge clk31) 
        begin
            if(!rst_n)
                begin
                state_m <= idle;
                m_coder <= 5'b01000;
                end
            else
                case(state_m) // synthesis full_case
                    idle : if(flag)
                              state_m <= body;
                           else
                              state_m <= idle;
                              body: begin
                                        m_coder[4] <= m_coder[0] ^ m_coder[3];
                                        m_coder[3:0] <= m_coder[4:1];
                                    end
                endcase
        end
endmodule

四、add_noise 模块

该模块代码的作用是产生干扰,这里所说的干扰都为加性干扰,只要把无干扰数据 01(+1)和 11(-1)分别加上范围在[-2,+2]的随机数。

加干扰后,+1 将会变成 01±[-2,+2] = [-1,+3],-1 将会变成 11±[-2,+2] = [-3,+1]。并且两个范围都是均匀分布。

由于输入数据为 2 个比特,必须扩展后加减法才是我们需要的。具体代码如下:


/************************************************************/
//模块名: mcu
//作 者: The last one
//用 途: 添加加性干扰
//版本说明:
//************************************************************/
module add_noise(
                  clk31,
                  rst_n,
                  un_noised_data, //干扰数据输入
                  noised_data //添加干扰后输出
              );
              
    input clk31,
    rst_n;
    input [1:0] un_noised_data;
    output [2:0] noised_data;
    reg [2:0] noise;
    
/*****************************************************
+1 = [-1,3]
-1 = [-3,1]
都是等概率的出现
*****************************************************/
    assign noised_data = {un_noised_data[1],un_noised_data} + noise;
    
    always @(posedge clk31)
      if(!rst_n)
        noise <= 3'd0;
      else
        noise <= $random % 3; // noise = [-2,+2]
    
endmodule

模块 mcu、模块 coder、模块 add_noise,使用 mcu 作为顶层模块,激励由 mcu产生、发送输出为加加性噪声后的信号。

五、decoder模块

decoder 是解扩模块,包括查找同步头、数据同步、解扩。

同步头{1111_1111_110},数据帧同步{0000_000},必须接收到同步头,且同步同步头后和接收数据帧同步,之后才对数据解扩。

由于发送模块和接收模块有时间差,但可以确认的是,必须先接收,后再发送。发送端采用的是固定的 m 序列码作为扩频伪随机码,这样做的利处就是接收端只要采用一样的 m 序列作为解扩码。

由于伪随机序列具有很强的相关性。只要有 1 个时钟错误,解扩结果相差会相当的大。依靠它的这个特性,可以把发送数据一一解扩。(具体解扩过程在仿真部分将更详细说明)。

由于在模块 add_noise 中添加了干扰,发送数据会有一定的误差,所以,解扩过程需使用累加的方法进行。而累加的阀值这里固定在 28,由于累加过程会有减法运算,所以计算初值均为 100。

具体代码如下:


//*******************************************************/
//模块名: mcu
//作 者: The last one
//用 途: 解扩
//版本说明:
//******************************************************/
module decoder(
                rst_n,
                ena,
                clk31x,
                in_data,
                out_data,
                decode_data_flag
              );
              
    input rst_n;
    input ena;
    input clk31x;
    input [2:0] in_data;
    output out_data;
    output decode_data_flag;
    
    reg out_data;
    reg decode_data_flag;
    reg [39:0] m_coder_buf;
    reg [7:0] mm;
    reg temp;
    reg [7:0] temp_syn;
    reg [3:0] state;
    wire [2:0] psumi;
    
//已知的解调系列
    wire [30:0] m =31'b1001011001111100011011101010000;
    
/************************************
取绝对值
正数是本身,负数则取反加 1
************************************/
    assign psumi =(in_data[2]==0)?{in_data[1],in_data[0]}:(~in_data+1);
    
    parameter find_head = 4'b0001, 
              synchronize = 4'b0010,
              //找到头信号之后的同步解码过程以找到 0 时为结束
              find_head_end = 4'b0100, 
              //用于解调除 11111111110 以外的所有传输数据
              main_body = 4'b1000; 
              
    reg [7:0] sum1,
              sum2,
              sum3,
              sum4,
              sum5,
              sum6,
              sum7,
              sum8,
              sum9,
              sum10,
              sum;   
    reg [7:0] i,j;
    
/******************************************************
产生一个循环的随机码,用于解扩
******************************************************/
    always @(posedge clk31x) 
        begin
            if(!rst_n || (!ena))
                m_coder_buf <= {m[8:0],m};
            else
                m_coder_buf<={m_coder_buf[9:1],m_coder_buf[0],m_coder_buf[30:1
            ]};
        end
        
    always @(posedge clk31x)
        if(!rst_n || (!ena))
            begin
                state <= find_head;
                i <= 8'd0;
                j <= 8'd0;
                sum1 <= 8'd100;
                sum2 <= 8'd100;
                sum3 <= 8'd100;
                sum4 <= 8'd100;
                sum5 <= 8'd100;
                sum6 <= 8'd100;
                sum7 <= 8'd100;
                sum8 <= 8'd100;
                sum9 <= 8'd100;
                sum10 <= 8'd100;
                sum <= 8'd100;
                mm <= 8'd0;
                decode_data_flag <= 1'b0;
                temp <= 1'bz;
                out_data <= 1'bz;
                temp_syn <= 8'b0000_0000;
            end
        else
            case(state)
            find_head:    
/********************************************************
寻找同步头。
*********************************************************/
              begin
                if(j != 8'd30)
                  begin
                  j <= j + 1'b1;
                  if(in_data[2] == m_coder_buf[i])
                      sum1 <= sum1 + psumi;
                  else
                      sum1 <= sum1 - psumi;
                  if(in_data[2] == m_coder_buf[i+1])
                      sum2 <= sum2 + psumi;
                  else
                      sum2 <= sum2 - psumi;
                  if(in_data[2] == m_coder_buf[i+2])
                      sum3 <= sum3 + psumi;
                  else
                      sum3 <= sum3 - psumi;
                  if(in_data[2] == m_coder_buf[i+3])
                      sum4 <= sum4 + psumi;
                  else
                      sum4 <= sum4 - psumi;
                  if(in_data[2] == m_coder_buf[i+4])
                      sum5 <= sum5 + psumi;
                  else
                      sum5 <= sum5 - psumi;
                  if(in_data[2] == m_coder_buf[i+5])
                      sum6 <= sum6 + psumi;
                  else
                      sum6 <= sum6 - psumi;
                  if(in_data[2] == m_coder_buf[i+6])
                      sum7 <= sum7 + psumi;
                  else
                      sum7 <= sum7 - psumi;
                  if(in_data[2] == m_coder_buf[i+7])
                      sum8 <= sum8 + psumi;
                  else
                      sum8 <= sum8 - psumi;
                  if(in_data[2] == m_coder_buf[i+8])
                      sum9 <= sum9 + psumi;
                  else
                      sum9 <= sum9 - psumi;
                  if(in_data[2] == m_coder_buf[i+9])
                      sum10 <= sum10 + psumi;
                  else
                      sum10 <= sum10 - psumi;
                  if(sum1 >= 8'd128 || sum2 >= 8'd128 || sum3 >= 8'd128 || 
                      sum4 >= 8'd128 || sum5 >= 8'd128 || 
                      sum6 >= 8'd128 || sum7 >= 8'd128 || sum8 >= 8'd128 || 
                      sum9 >= 8'd128 || sum10 >= 8'd128)
                  
                    begin
                      if(sum1 >= 8'd128) mm <= i;
                      if(sum2 >= 8'd128) mm <= i+1;
                      if(sum3 >= 8'd128) mm <= i+2;
                      if(sum4 >= 8'd128) mm <= i+3;
                      if(sum5 >= 8'd128) mm <= i+4;
                      if(sum6 >= 8'd128) mm <= i+5;
                      if(sum7 >= 8'd128) mm <= i+6;
                      if(sum8 >= 8'd128) mm <= i+7;
                      if(sum9 >= 8'd128) mm <= i+8;
                      if(sum10 >= 8'd128) mm <= i+9;
                      state <= synchronize;
                    end
                  end
                else
                  begin
                    if(i < 30)
                        i <= i + 8'd10;
                    else
                        i <= 8'd0;
                        j <= 8'd0;
                    if(in_data[2] == m_coder_buf[i])
                        sum1 <= 8'd100 + psumi;
                    else
                        sum1 <= 8'd100 - psumi;
                    if(in_data[2] == m_coder_buf[i+1])
                        sum2 <= 8'd100 + psumi;
                    else
                        sum2 <= 8'd100 - psumi;
                    if(in_data[2] == m_coder_buf[i+2])
                        sum3 <= 8'd100 + psumi;
                    else
                        sum3 <= 8'd100 - psumi;
                    if(in_data[2] == m_coder_buf[i+3])
                        sum4 <= 8'd100 + psumi;
                    else
                        sum4 <= 8'd100 - psumi;
                    if(in_data[2] == m_coder_buf[i+4])
                        sum5 <= 8'd100 + psumi;
                    else
                        sum5 <= 8'd100 - psumi;
                    if(in_data[2] == m_coder_buf[i+5])
                        sum6 <= 8'd100 + psumi;
                    else
                        sum6 <= 8'd100 - psumi;
                    if(in_data[2] == m_coder_buf[i+6])
                        sum7 <= 8'd100 + psumi;
                    else
                        sum7 <= 8'd100 - psumi;
                    if(in_data[2] == m_coder_buf[i+7])
                        sum8 <= 8'd100 + psumi;
                    else
                        sum8 <= 8'd100 - psumi;
                    if(in_data[2] == m_coder_buf[i+8])
                        sum9 <= 8'd100 + psumi;
                    else
                        sum9 <= 8'd100 - psumi;
                    if(in_data[2] == m_coder_buf[i+9])
                        sum10 <= 8'd100 + psumi;
                    else
                        sum10 <= 8'd100 - psumi;
                  end
              end
    
                synchronize :
/*********************************************************
 同步同步头
************************************************/
                begin
                    if(mm < 8'd22)
                       temp_syn<={m_coder_buf[mm+7],temp_syn[7:1]};
                    else
                       temp_syn<= {m_coder_buf[mm-22],temp_syn[7:1]};
                    if(temp_syn == m[7:0]) 
                        begin
                              state <= find_head_end;
                              j <= 8'd0;
                            if(in_data[2] == m_coder_buf[mm])
                              sum <= 8'd100 + psumi;
                            else
                              sum <= 8'd100 - psumi;
                        end
                end
            
                find_head_end :
/************************************************
找数据帧同步
************************************************/
                begin
                    if(j != 8'd30) 
                    begin
                        if(in_data[2] == m_coder_buf[mm])
                            sum <= sum + psumi;
                        else
                            sum <= sum - psumi;
                            j <= j + 1'b1;
                   end
                        else
                            begin
                            j <= 8'd0;
                            if(in_data[2] == m_coder_buf[mm])
                                sum <= 8'd100 + psumi;
                            else
                                sum <= 8'd100 - psumi;
                            if(sum >= 8'd100)
                                begin
                                temp <= 1'b1;
                                end
                            else
                                begin
                                    temp <= 1'b0;
                                    decode_data_flag <= 1'b1;
                                    state <= main_body;
                                end
                            end
                end
    
                main_body :
/**************************************************
 解调数据
****************************************************/
                begin 
                if(j != 8'd30) 
                    begin
                        if(in_data[2] == m_coder_buf[mm])
                        sum <= sum + psumi;
                    else
                        sum <= sum - psumi;
                        j <= j + 1'b1;
                    end
                else
                    begin
                        j <= 8'd0;
                        if(in_data[2] == m_coder_buf[mm])
                            sum <= 8'd100 + psumi;
                        else
                            sum <= 8'd100 - psumi;
                        if(sum >= 8'd100)
                            out_data <= 1'b1;
                        else
                            out_data <= 1'b0;
                    end
                end
            endcase
    
endmodule

该模块只是对应的解扩,并未涉及信息的检错和纠错,检错将在 correct 模块中进行。

六、correct 模块

模块 correct 将对解扩后的信息就行检错和纠错。检错过程就相当于汉明码编码的逆过程。但(7,4)汉明码仅在 1 位错误的情况下可以检出错误,如果多于 1 位错误,将无法纠错过来(依据 d>e+1;码距 d=3)。

具体代码如下:


//***********************************************************/
//模块名: mcu
//作 者: The last one
//用 途: 检错、纠错
//版本说明:
//***********************************************************/
module correct (
                  clk1,
                  rst_n,
                  in_data, //输入解调后的数据
                  out_data, //输出纠错后的结果
                  decode_data_flag,//来自 decode 模块的信号,用以标
                  识信号已解调完毕(不包括同步判断信号 11111111110)
                  correct_data_flag,//输出信号,表明已经完成查错和
                  纠错功能 
                  asyn_flag //输出信号,高电平表示不同步
              );
              
    parameter IDLE = 4'b0001,
    PROCESS = 4'b0010,
    FLAG_OUT = 4'b0011;
    input clk1,rst_n;
    input in_data;
    input decode_data_flag;
    output [3:0] out_data;
    output correct_data_flag;
    output asyn_flag;
    reg [3:0] out_data;
    reg correct_data_flag;
    reg asyn_flag;
    reg [6:0] in_data_buf,//输入数据移位寄存器
    data_buf; //输入数据缓冲寄存器
    reg flag; //读满标示位
    reg s1,s2,s3; //纠错码运算结果
    reg [11:0] data_number;
    reg [3:0] state1,state2;
    
    always @(posedge clk1) //接收外来的数据
        if(!rst_n || !decode_data_flag)
          begin
              state1 <= 4'h0;
              flag <= 1'b0;
          end
        else
          case(state1)
/**********************************************
接收解调出来的 7 位数据
接收完 7 个数据后,给 flag 信号,进行数据处理.
 **********************************************/
          0 : state1 <= 1;
            1,2,3,4,5,6 :
            begin
                flag <= 1'b0;
                in_data_buf <= {in_data_buf[5:0],in_data};
                state1 <= state1 + 1'b1;
            end
            
            7 : begin
                  in_data_buf <= {in_data_buf[5:0],in_data};
                  flag <= 1'b1;
                  state1 <= 4'h1;
                end
                
          endcase
          
          always @(posedge clk1) //把接收到的数据进行处理后,送出端口
            if(!rst_n || !decode_data_flag)
              begin
                  s1 <= 1'b0;
                  s2 <= 1'b0;
                  s3 <= 1'b0;
                  data_buf <= 7'hxx;
                  state2 <= IDLE;
                  data_number <= 12'd0;
                  correct_data_flag <= 1'b0;
                  asyn_flag <= 1'b0;
              end
            else
              case(state2)
                IDLE : begin // 等待 flag 到来
                        correct_data_flag <= 1'b0;
                        if(flag)
                          begin
                            state2 <= PROCESS;
                            preprocessing; //预加工数据
                          end
                        else 
                          state2 <= IDLE;
                      end
                  
                  PROCESS: begin //纠错处理
                              correct_task;
                              state2 <= FLAG_OUT;
                           end
                           
                  FLAG_OUT: begin
                              state2 <= IDLE;
                              if(data_number != 12'd1)
                                correct_data_flag <= 1'b1;
                            end
                            
                  default : state2 <= 4'h0;
              endcase
              
          task preprocessing;
          begin
/*******************************************************
计算错码情况,但如果有两个码组错误将无法判定
将 in_data_buf 赋给 data_buf 保存起来
数据计满 903 位(512 信息位,384 监督位,4 位数据帧,3 位数据帧监督位)
data_number 赋 0 开始计数(0 -> 902)
*******************************************************/
              s1<=(in_data_buf[6]^in_data_buf[5]^in_data_buf[4]^in_data_buf[2]);
              s2<=(in_data_buf[6]^in_data_buf[5]^in_data_buf[3]^in_data_buf[1]);
              s3<=(in_data_buf[6]^in_data_buf[4]^in_data_buf[3]^in_data_buf[0]);
              data_buf <= in_data_buf;
          if(data_number < 902)
            data_number <=data_number + 1'b1;
          else
            data_number <= 12'd0;
         end
         endtask
         
          task correct_task;
            begin
            case({s3,s2,s1})
/***********************************************************
数据位 监督位
-------------------------------- -------------------
d6 d5 d4 d3 s1 s2 s3
x x x s1
x x x s2
x x x s3
 -----------------------------------------------------------
如果有一位错,必定是监督位错。
如果是第一位数据,判断是否为数据帧(0000)
若不是数据帧,则认为系统没有同步数据帧,
发送 syn_flag 高电平,以下类似.
s1,s2,s3 如果错误有两个以上,可以找到他们的相交区间
s1,s2 错误,s3 正确 可以找到 不属于 s3,而同时属于 s1,s2 的数据为 d5
s1,s3 错误,s2 正确 可以找到 不属于 s2,而同时属于 s1,s3 的数据为 d4
s2,s3 错误,s1 正确 可以找到 不属于 s1,而同时属于 s2,s3 的数据为 d3
s1,s2,s3 错误, 而同时属于 s1,s2,s3 的数据为 d6
*************************************************************/
            3'b000,3'b001,3'b010,3'b100 :
              begin
                if(data_number == 12'd1)
                if(data_buf[6:3] == 4'h0)
                  asyn_flag <= 1'b0;
                else
                  asyn_flag <= 1'b1;
                else if(data_number <= 902)
                  out_data <= data_buf[6:3];
                else
                ;
              end
              
            3'b011 :begin
                    if(data_number == 12'd1)
                    if(data_buf[6:3] == 4'b0100)
                      asyn_flag <= 1'b0;
                    else
                      asyn_flag <= 1'b1;
                    else if(data_number <= 902) 
                      out_data<={data_buf[6],~data_buf[5],data_buf[4:3]};
                    else
                    ;
                    end
                    
              3'b110 :begin
                      if(data_number == 12'd1)
                      if(data_buf[6:3] == 4'b0001)
                        asyn_flag <= 1'b0;
                      else
                        asyn_flag <= 1'b1;
                      else if(data_number <= 902)
                        out_data <= {data_buf[6:4],~data_buf[3]};
                      else
                      ;
                      end
            
            3'b101 :begin
                    if(data_number == 12'd1)
                    if(data_buf[6:3] == 4'b0010)
                      asyn_flag <= 1'b0;
                    else
                      asyn_flag <= 1'b1;
                    else if(data_number <= 902)
                      out_data<={data_buf[6:5],~data_buf[4],data_buf[3]};
                    else
                    ;
                    end
                    
            3'b111 :begin
                    if(data_number == 12'd1)
                    if(data_buf[6:3] == 4'b1000)
                      asyn_flag <= 1'b0;
                    else
                      asyn_flag <= 1'b1;
                    else if(data_number <= 902)
                      out_data <= {~data_buf[6],data_buf[5:3]};
                    else
                    ;
                    end
            default : ;
           endcase
      end
            endtask
            
endmodule

七、Correct_Decoder 模块

模块 Correct_Decoder 是模块 decoder 和模块 correct 的顶层模块。

代码如下:


//*******************************************************/
//模块名: Correct_Decoder
//作 者: The last one
//用 途: 解扩和纠错模块的顶层模块
//版本说明:
//*******************************************************/
module Correct_Decoder(
                        rst_n,
                        clk1,
                        clk31x,
                        ena_decoder,
                        noised_data,
                        pro_correct_data,
                        correct_data_flag,
                        asyn_flag
                  );
                  
    input rst_n,
          clk1,
          clk31x;
          
    input ena_decoder; //使能 decoder 信号
    input [2:0] noised_data;//从 add_noise 输出的噪声信号,等待解调
    output [3:0] pro_correct_data; //处理后输出的数据
    output correct_data_flag; //错误码标示位
    output asyn_flag; //系统数据帧同步信号
    wire pro_decode_data;
    wire decode_data_flag;
    
    decoder decoder(
                    .rst_n(rst_n),
                    .ena(ena_decoder),
                    .clk31x(clk31x),
                    .in_data(noised_data),
                    .out_data(pro_decode_data),
                    .decode_data_flag(decode_data_flag)
                  );
                  
    correct correct(
                      .clk1(clk1),
                      .rst_n(rst_n),
                      .in_data(pro_decode_data),
                      .out_data(pro_correct_data), 
                      .decode_data_flag(decode_data_flag), 
                      .correct_data_flag(correct_data_flag), 
                      .asyn_flag(asyn_flag)
                    );
                    
Endmodule

八、slaver模块

模块 slaver 充当信宿。它接收来自于模块 correct 纠错后的数据,对数据进行保存。以便查看结果。

模块 slaver 作为接收端,它将给解扩和纠错模块提供时钟信号,但其的起始必须必发送的起始快。并且它所产生的时钟可以是随机的开始,以 mcu 模块产生的时钟没有相位上的关系。

其代码如下:

//**********************************************************/
//模块名: slaver
//作 者: The last one
//用 途: 包含发送部分全部内容
//版本说明:
//************************************************************/
`timescale 1us/1us
module slaver(
              input [2:0] noised_data //接收带有噪声干扰信号
             );
             
  parameter TestNumber = 400;
  parameter Period = 100;
  reg rst_nx,clk1x,clk31x,ena_decoder;
  wire [3:0] pro_correct_data;
  wire correct_data_flag;
  wire asyn_flag;
  integer i,j,h,k,l,zz;
  reg flag;
  reg [7:0] decoderout_mem[TestNumber:1]; //用于存储发送的数据
  reg [7:0] decoderout_buf;
  integer outdataFILE;
  
  initial 
    begin
      i = 1;
      j = 0;
      h = 1;
      k = 0;
      l = 0;
      zz = 0;
      flag = 0;
      ena_decoder <= 1'b0;
      #(Period*3)
      ena_decoder <= 1'b1;
    end
  
  initial 
  /***********************************
  产生 clk1x 信号,延迟是随机的.
  ***********************************/
    begin
      clk1x = 0;
      rst_nx = 0;
      #(Period*({$random}%10)) //产生一个随机的延迟开始
      rst_nx = 1;
      forever #(Period * 31) clk1x = ~clk1x;
    end
  
    initial 
    /***********************************
    产生 clk31x 信号,延迟是随机的.
    ***********************************/
      begin
        clk31x = 0;
        forever #(Period) clk31x = ~clk31x;
      end
      
    always @(posedge correct_data_flag)
      begin
        if(k == 902)
            begin
              k = 1;
              h = 1;
              j = 0;
            end
        else
            k = k + 1;
            if(zz == 0)
                begin
                  decoderout_buf[7:4] = pro_correct_data;
                  if((h+j)%65 != 0 || flag == 1)
                    begin
                      zz = 1;
                      flag = 0;
                    end
                  else if(flag == 0)
                    begin
                      zz = 0;
                      flag = 1;
                      j = j + 1;
                    end
                  end
                  else
                    begin
                      decoderout_buf[3:0] = pro_correct_data;
                      decoderout_mem[i] = decoderout_buf;
                      i = i + 1;
                      h = h + 1;
                      zz = 0;
              end
    end
  
  initial
    begin
      wait(i == TestNumber+1)
       outdataFILE = $fopen("./decoderOut.dat");
      $display (" outdataFILE=%0d ", outdataFILE);
        for(l = 1; l <= TestNumber; l = l+1) 
         begin 
            $fdisplay(outdataFILE," %0h ",decoderout_mem[l]); 
         end
      $fclose(outdataFILE ); 
    end
      
    always @(posedge clk1x) 
    if(asyn_flag)
      begin
        $display("Error The system doesn't synchronize any more"); 
        $stop;
      end
  
  Correct_Decoder Correct_Decoder(
                                  .rst_n(rst_nx),
                                  .clk1(clk1x),
                                  .clk31x(clk31x),
                                  .ena_decoder(ena_decoder),
                                  .noised_data(noised_data),
                                  .pro_correct_data(pro_correct_data),
                                  .correct_data_flag(correct_data_flag),
                                  .asyn_flag(asyn_flag)
                               );
  
enndmodule

九、Top模块

模块 top 作为仿真平台的顶层模块,它包含 mcu 和 slaver 两个模块。并且对发送数据和接收数据进行对比。统计结果,并输出(打印到屏幕)。

模块 top 将给两个模块提供周期,仿真个数等参数,以传参的形式传送。

它的代码如下:


//**********************************************************/
//模块名: slaver
//作 者: The last one
//用 途: 包含发送部分全部内容
//版本说明:
//************************************************************/
`define PERIOD 100
`define testnumber 500 //测试数据个数
`timescale 1us/1us
module top;
  integer m,n;
  wire [2:0] noised_data;

// 模块整体工作流程,都是以任务形式
//******** START *****************
  initial
  begin
    sys_reset;
    delay_system_end;
    compare_data;
    stop;
  end

//******* END *****************
//--------------------------------------------------------------------------------------------------------
  task sys_reset; //复位
    begin
      m = 0; //记录错误个数
      n = 1;
    end
  endtask
  
//--------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------
  task delay_system_end; // 等待系统仿真结束
    begin
      wait(slaver.i == `testnumber+1)
      $display("The system transmission end\n");
      $display("\n\n***********************************************");
      $display(" Start to compare the data");
    end
  endtask

//--------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------
  task compare_data; // 比较发送数据和接收数据,并统计结果
  begin
    $display(" NO. Result org rep");
    $display(" ------------------------------------");
  for(n=1;n <= `testnumber; n = n + 1)
  begin
    if(mcu.indata_mem[n] == slaver.decoderout_mem[n])
      $display("%d Right %0h=
      %0h",n,mcu.indata_mem[n],slaver.decoderout_mem[n]);
    else
      begin
        $display("%d Wrong %0h!=
        %0h",n,mcu.indata_mem[n],slaver.decoderout_mem[n]);
        m = m + 1;
      end
  end
  $display(" ------------------------------------");
    if(m != 0)
      begin
        $display(" Wrong data number is %5d",m);
        $display(" Right data number is %5d",`testnumber-m);
      end
    else
      $display(" No wrong data!");
      $display(" ------------------------------------");
  end
  endtask

//--------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------
  task stop; // 仿真停止
    begin
      $display(" Sim time is over");
      $display(" ------------------------------------\n");
      $stop;
    end
  endtask

//--------------------------------------------------------------------------------------------------------
mcu mcu(
        .noised_data(noised_data)
        );
        slaver slaver(
        .noised_data(noised_data)
       );
       
  defparam slaver.Period = `PERIOD;
  defparam mcu.Period = `PERIOD;
  defparam mcu.TestNumber = `testnumber;
  defparam slaver.TestNumber = `testnumber;

endmodule

仿真 

一、模块的建立及其仿真环境的生成

1.1、在计算机上,找一个没有中文字符的目录,新建以下几个文件,如图 6:

image.png

上图为可以建立的文件,sim_wave.do 是仿真波形保存文件.tt.do。

其代码如下:

#建立 library 名为”work”
vlib work
vmap work work
#编译当前目录(./)中的 top.v、mcu.v …. 
vlog -work work -L mtiAvm -L mtiOvm -L mtiUPF ./top.v
vlog -work work -L mtiAvm -L mtiOvm -L mtiUPF ./mcu.v
vlog -work work -L mtiAvm -L mtiOvm -L mtiUPF ./slaver.v
vlog -work work -L mtiAvm -L mtiOvm -L mtiUPF ./coder.v
vlog -work work -L mtiAvm -L mtiOvm -L mtiUPF ./add_noise.v
vlog -work work -L mtiAvm -L mtiOvm -L mtiUPF ./decoder.v
vlog -work work -L mtiAvm -L mtiOvm -L mtiUPF ./correct.v
vlog -work work -L mtiAvm -L mtiOvm -L mtiUPF ./Correct_Decoder.v
#仿真 work 中的 top 模型
vsim -novopt work.top

以上是输入方式进行仿真,也可以直接使用图形化的方式进行仿真。但没有开始仿真,因为我们以下还要添加一条语句。但没有响应的文件。

tt.bat 的代码如下:

echo
pause
vsim -do .\tt.do
pause

tt.bat 文件为批处理文件,仅为打开 modelsim、运行 tt.do 文件使用。也可以不使用该文件(以下不会详细介绍)。

1.2、将对应的代码写到相应的文件中(sim_wave.do、tt.bat 文件可以不管)。

1.3、用 modelsim 的打开方式打开 top.v 文件(或者你先打开 modelsim,然后把目录修改成以上所述的目录也可)。运行的界面如图 7(modelsim6.5d):

image.png

图中的乱码均为modelsim不兼容我所使用的notepad软件编写的中文字符,大侠均可不以理睬。

1.4、在 Transcript 中输入”do tt.do”,运行当前目录下的 tt.do 文件。

运行过程中,最后跳出如图 8 的窗口。如果有错误,会在 Transcript 中用红色字体说明(当然,这里都是英文)。

image.png

在框图 1 中为整个仿真平台上的模型,可以点击模型+展开。框图 2 显示当前模型所含的项目。

1.5、添加波形,如图 9、10、11,对模块 coder 添加波形,并对波形进行分组。

image.png
image.png

对所有仿真模型添加波形,并且分组,如图 12。

image.png

1.6、仿真开始 

在 Transcript 中输入”run -all” 等待结果。以上将生成仿真环境的全过程。下面会将对各个模块进行说明。

二、模块仿真

2.1、模块 mcu 仿真

mcu 扮演一个信源产生模块,其波形如图 14。

image.png

在 send_ena 使能的情况下,当 insourse_ena 为高时,数据从 indatabyte 第 7 位端口输出到 coder 模块,图中发送十六进制 24 的过程,仅在 insourse_ena 为高时发送。该模块还产生两个时钟,两个时钟分别是 31 倍的频率。clk1 和 clk31。

2.2、模块 coder仿真

模块 coder 将对 mcu 传送的数据进行编码、扩频。仿真波形如图 15。

image.png

图中的 in_data_buf 为发送码,当接收到 send_ena 后,先发送头和数据帧,然后才发送数据如图中从 133600us 开始发送数据”0010”(十六进制 2)后发送监督码的”101”,在 177000us 开始发送数据”0100”(十六进制 4)后发送监督码”110”。所有数据经过信道编码后,out_data 发送出去。

2.3、模块 noise仿真

添加干扰,经 coder 发送的 2bit 数据扩展到 3bit 数据,并与噪声进行加性。

仿真波形如图 16。

image.png

图中是对 1bit 数据进行扩频后,其中 un_noised_data 为输入数据(无噪声)、经过与 noise 数据相加,得到数据 noised_data。

这模块就是充当信道中的加性干扰源。

2.4、模块 decoder仿真

解扩是本系统的设计重点。它包含同步头的同步和数据的接收等。

本设计采用一个循环伪随机作为解扩码。采用一个 31bit 的寄存器,初始化为级数为 5 的 m 序列,首尾循环。那么,在寄存器每一位上采数,都可以得到一个伪随机序列。分别得出 31 个 m 序列。而且靠近的寄存器位,采集的 m 序列只有一位的移位。因此,可以采用该方法,在发送端发送的数据,不管为何时发送,在 31bit个寄存器中的 1 个寄存器中与之对应。更通俗的说法,不管发送设备何时开始发送。都可以在 31bit 的寄存器中找到一个寄存器采到的 m 序列与之对应。

由于在 31 比特的寄存器同时采数是比较耗费 FPGA 内部资源,所以本设计采用寄存器的每 10 个 bit 位进行一一处理。如果前 10 个没能找到对应的 m 序列,则累加到后 10 个,以此类推,在 3 次的累加中,总能完全扫描完 31bit 位的寄存器。此时可以找到对应的比特位。

由于发送设备的数据头为 10 个”1”和 1 个”0”,而在 10 个”1”中的 1 是延伸的,没法直接得到相邻”1”的交界,而在得到合适的 m 序列位后,必须进行同步,同步的方法为采集最后一个”0”作为同步。

在接收完成数据头后,进行数据帧同步。数据帧是 4bit 数据”0000”和 3bit 监督位”000”。

接收完成数据帧之后才是数据的开始。由于数据比较大,累加基数这里是 100,阀值为 30,那么,当接收到 130,说明接收到一个”1”。

仿真结果如下:

image.png

图17 为接收的整体工作状态,sum1~sum10 分别采集 10 个寄存器比特位,当有1 个接收超过 130,说明寄存器该为上的 m 序列可以接收到 1 个”1”,sum 是对数据帧和数据的解扩统计。

image.png

图18 是一个完整数据解扩的过程,clk31 是采集时钟,数据为 in_data_buf,从输入到输出,延迟一段时间后传送到解扩模块。psumi 为解扩的值,通过累加得到sum(in_data[2]判断。为 1,则加;为 0,则减)。如果 sum 超过 130,说明发送数据为”1”,否则为”0”。(以上为数据”1”的例子)

通过解扩的数据,送到 correct 模块进行纠错。

2.5、模块 correct仿真

模块 correct 为纠错模块。它将解扩后的数据进行分析,即对汉明码的反运算。该模块的仿真过程省略。

2.6、模块 Slaver仿真

Slaver 是接收模块端,它将解扩、纠错后的数据进行存储。仿真过程省略。

2.7、模块 Top 仿真

Top 模块应该放第一块讲解,因为它是一个仿真平台,它的子模块包括 mcu 和slaver。它将两个模块的发送接收进行统计、并且进行计算、输出,并对模块参数设置。以下设置发送数据比特位为 500 的输出结果(图 19、图 20):

image.png
image.png

以上是整个设计的仿真过程。

到此结束,直接扩频通信也到此结束,各位大侠,有缘再见!

作者:The last one
原文:FPGA技术江湖

相关文章推荐

更多FPGA干货请关注FPGA的逻辑技术专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
10604
内容数
561
FPGA Logic 二三事
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息