OFDM802.11a的FPGA实现(七)一级交织:分组交织器(含verilog和matlab代码)

1.前言

  在前面的文章中讲解了卷积编码和删余,实现了1/2、2/3、3/4编码速率的输出。数据域在编码之后,下一个部分就是交织。今天对交织进行具体实现。

  交织是为了在时域或频域或者同时在时域、频域上分布传输的信息比特,使信道的突发错误在时间上得以扩散,从而使得译码器可以将它们当作随机错误处理。交织器在几个分组长度或几个约束长度的范围内对码元进行混洗,这个范围是由突发持续时间决定的。通信系统的交织模式取决于信道特性。如果系统在一个纯粹的AWGN环境下运行,即准平稳信道,那么在一个数据包的持续时间上基本没有什么变化,就不需要交织。因为这时,通过重新分配的方法是无法改变误码分布的。

  交织必然在系统中引人延时,这是因为接收到的比特顺序与信息源发送时的顺序是不相同的。通信系统通常规定了系统所能容忍的最大延时,因此也限制了所能使用的交织器的交织深度。

2.原理

2.1分组交织器

  分组交织器是针对一组bit进行的,该分组当中bit的数量称为交织深度,交织深度越大,离散度越大,抗突发差错的能力也就越大,相应的引起的交织编码处理时间也越长。
我们可以用一个矩阵来描述分组交织器,将数据按照行进行写入数据,按照列的方式进行读出,或者按照相反的方式也是可以的。

4x5分组交织器的写和读的结构

  4x5的分组交织器,由此可见交织深度为20,以行的方式进行写入,以列的方式进行读出。

0 1 2 3 4
5 6 7 8 9
10 11 12 13 14
15 16 17 18 19

  反交织的作用与交织的作用正好相反,将上图当中的矩阵进行转秩,然后同样按照以行的方式进行写入,以列的方式进行读出,即可将原始的数据恢复。

0 5 10 15
1 6 11 16
2 7 12 17
3 8 13 18
4 9 14 19

2.2 802.11a当中的交织

  802.11a规定了包括调制方案、编码效率以及数据速率之间的关系,如下面三个表所示:
表1

表2

表3
  802.11a当中交织的深度为一个OFDM符号,因此这是一个分组交织器,交织深度与所采用的调制方式有关:BPSK,QPSK,16QAM,64QAM的交织深度分别为48,96,192,288个bit。每种调制当时的交织深度是通过数据子载波的数量与每个符号当中bit的个数相乘得到的。确定了调制方案、编码效率以及数据速率之后,就可以计算出每个OFDM符号在调制前的码长,公式如下:
N C B P S = 48 × M ÷ R N_{CBPS} = 48 \times M \div R NCBPS=48×M÷R

  其中,$ N_{CBPS} 表示每个 O F D M 符号在调制前的码长, 表示每个OFDM符号在调制前的码长, 表示每个OFDM符号在调制前的码长,M 表示调制阶数( 1 − B P S K , 2 − Q P S K , 3 − 8 Q A M , 4 − 16 Q A M , 5 − 32 Q A M , 6 − 64 Q A M ) , 表示调制阶数(1-BPSK,2-QPSK,3-8QAM,4-16QAM,5-32QAM,6-64QAM), 表示调制阶数(1BPSK2QPSK,38QAM,416QAM532QAM,664QAM,R$表示编码效率。

  802.11a规定数据域的交织分为两个步骤,今天主要讲第一次交织,其目的是保证相邻的比特在经过ofdm调制之后数据会落在不相邻的子载波上。
以QAM64调制,速率为54Mb/s为例,能够确定 s = 3 , N C B P S = 288 s=3,N_{CBPS = 288} s=3NCBPS=288有了这些参数之后,我们就可以将这个交织的变换关系给计算出来,我们只需要根据这个关系进行将数据写入到一个RAM对应的地址当中,再依次将数据从RAM中读取出来即可以完成交织的工作。802.11a中最大的交织深度即为288,搞定了这个之后其他的也是一样的操作,其关系网如下所示:

以QAM64调制,速率为54Mb/s为例的交织关系

3.verilog代码

  在设计上,为保证流水线设计结构,能够对数据进行连续处理,需要将RAM的存储空间设置为最大交织深度288的一倍。相邻的OFDM符元就能实现乒乓缓存。0-287地址空间为乒乓缓存A区数据空间,288-575为乒乓缓存B区数据空间,实现A区写入数据,B区读取数据。 当输入数据有效时,对输入数据按一级交织方式进行错位写入RAM,并驱动写入计数器以交织深度为长度进行计数,计满一个交织深度后,拉高当前缓存区标志信号,并驱动读取计数器以当前缓存区长度计数。模块接口如下所示:

**《基于XILINX FPGA的OFDM通信系统基带设计》**书中对于这个模块是这样设计的:

  他又用到了跨时钟域,其实很没有必要,还是采用之前的valid-ready握手机制就可以解决这个问题。还有这里虽说是用到了RAM,但是个人认为并没有必要去调用FPGA的Block RAM资源,因为一块Block RAM一般都是18k或者36k,用在这里很浪费。具体可以去查看芯片资料。这里两个缓存区加起来不过也才576位,直接用两个288位的寄存器即可。

  接下来需要解决写地址的问题:定义第一次交织前的编码比特序列角标顺序号用 k 表示,交织之后的数据序列角标顺序号用 i 表示,第二次交织后数据序列角标顺序号用 j 来表示,我们可以得到如下公式:

  由于FPGA中直接用除法和取余非常浪费资源,此处处被除数是固定的,我们只需要采用移位的方式就可以实现除法和取余。代码如下:

//---------valid-ready握手---------//
assign	wr_en	 = intv1_dout_rdy & intv1_din_vld ;//与上游握手成功,开始接收数据
assign	rd_en	 = intv1_din_rdy & intv1_dout_vld ;//与下游握手成功,开始输出
assign	intv1_dout_rdy = (~bufferA_full | ~bufferB_full) ? 1'b1 : 1'b0;
assign	intv1_dout_vld = ( bufferA_full |  bufferB_full) ? 1'b1 : 1'b0;
//------------buffer读写控制----------------//	
always@(posedge clk or negedge rst_n ) begin
	if(!rst_n)
		buffer_flag <= 1'b0;//0为A区,1为B区
	else if(w_cnt_last & wr_en)
		buffer_flag <= ~buffer_flag;//0为A区,1为B区
end
//-----------buffer写满和读空控制------------//	
always @(posedge clk or negedge rst_n)begin
	if(!rst_n)
		bufferA_full <= 1'b0;
	else if(w_cnt_last & wr_en & ~buffer_flag)
		bufferA_full <= 1'b1;
	else if(r_cnt_last & rd_en & bufferA_full)
		bufferA_full <= 1'b0;
end

always @(posedge clk or negedge rst_n)begin
	if(!rst_n)
		bufferB_full <= 1'b0;
	else if(w_cnt_last & wr_en & buffer_flag)
		bufferB_full <= 1'b1;
	else if(r_cnt_last & rd_en & bufferB_full)
		bufferB_full <= 1'b0;
end
//-----------根据符号长度选择写计数值------------//	
always@( * ) begin  
    case ( intv1_Con )
		N_48 	:  	cnt_Max = 48 -1	; 
		N_96 	:  	cnt_Max = 96 -1	;  
		N_192	:  	cnt_Max = 192 -1;  
		N_288	:  	cnt_Max = 288 -1;  
		default	:	cnt_Max = 48 -1	;
    endcase 
end
//------buffer写入操作,以行写入,但是地址换算为列输出时的地址--//
counter_in #(.CNT_NUM('d288),
		.ADD(1'b1))
u_counter_w(
.clk		(clk				),	
.rst_n		(rst_n				),
.En_cnt		(wr_en				), 
.cnt_din	(cnt_Max			),     
.cnt		(w_cnt				),	
.cnt_last	(w_cnt_last			)
);

always@(*) begin
	if(!rst_n) 
		w_addr = 0; 
	else begin
		case(intv1_Con)
			N_48	: w_addr = w_cnt[3:0] + (w_cnt[3:0]<<1) + w_cnt[8:4] ; //N = 48//w_cnt *3	  
			N_96	: w_addr = (w_cnt[3:0]<<1)  + (w_cnt[3:0]<<2) + w_cnt[8:4] ;//N = 96 //w_cnt *6
			N_192	: w_addr = (w_cnt[3:0]<<3)  + (w_cnt[3:0]<<2) + w_cnt[8:4]; //N = 192//w_cnt *12
			N_288	: w_addr = (w_cnt[3:0]<<4)  + (w_cnt[3:0]<<1) + w_cnt[8:4]; //N = 288//w_cnt *18
			default	: w_addr = w_cnt[3:0] + (w_cnt[3:0]<<1) + w_cnt[8:4] ; //N = 48//w_cnt *3
		endcase
	end	
end

always@(posedge clk or negedge rst_n ) begin
	if(!rst_n)begin
		bufferA <= 1'b0;
		bufferB <= 1'b0;	
	end
	else if(wr_en)begin
		if(buffer_flag)
			bufferB[w_addr] <= intv1_din;
		else
			bufferA[w_addr] <= intv1_din;
	end	
end

//------buffer读出操作,按照顺序地址读出---------//
counter_in #(.CNT_NUM('d288),
		.ADD(1'b1))
u_counter_r(
.clk		(clk				),	
.rst_n		(rst_n				),
.En_cnt		(rd_en				), 
.cnt_din	(cnt_Max			),     
.cnt		(r_cnt				),	
.cnt_last	(r_cnt_last			)
);

assign	intv1_dout = (rd_en & buffer_flag) ? bufferA[r_cnt] : bufferB[r_cnt];

//输出Map_Type,作为后面调制映射的输入,确定调制方式
always@(posedge clk or negedge rst_n ) begin
    if(!rst_n) begin 
        Map_Type <= 0;        
    end    
    else if(intv1_dout_vld) begin 
        Map_Type <= intv1_Con ; 
    end   
end 

4.Matlab仿真

  Matlab代码如下:

%% 一级交织
%一级交织器产生:16*list 列写行读
conv_out_Len = length(conv_out);    %编码后的长度
symbol_Len = conv_out_Len / k;      %单个符号码长度
list = conv_out_Len / k / 16;       %一级交织的列数
row = length(conv_out)/list;        %将数据转为list列的矩阵 
ram = zeros(row,list);              %将输入数据存储在list列,row行的矩阵中
for n = 1:k                         %将ram矩阵拆为n个16*list的矩阵,每次写入list列
    for m = 1:symbol_Len            %以列写入
        row_index = mod(m-1,16)+1;      %写入到矩阵中行的位置
        list_index = ceil(m/16);           %写入到矩阵中列的位置
        ram((n-1)*16+row_index,list_index) = conv_out((n-1)*symbol_Len+m); %将数据写入ram矩阵
    end
end
int_lea_1_out = zeros(1,conv_out_Len);
for n = 1:k
    for row_index = 1:16
        for list_index = 1:list    %按行读出ram中的数据,每次读list列
            int_lea_1_out((n-1)*symbol_Len+(row_index-1)*list+list_index) = ...
            ram(((n-1)*16+row_index),list_index);
        end
    end
end

5.ModelSim仿真

  按照如下连接仿真:

  仿真截图如下,可以看到读写可以同时进行,实现了乒乓操作。


为了验证模块的正确性,仿真使用4个OFDM符号输入,调制方式位BPSK(其他调制方式数据太多,不方便展示),3/4编码效率。计算得知,每个符号码长为48,即16行3列,列写入的数据如下:

1 1 0
1 0 0
1 0 0
1 0 0
1 1 0
0 0 1
1 0 0
1 1 0
0 1 0
1 0 0
0 0 0
0 1 1
0 0 1
1 0 1
1 0 1
0 0 0
0 0 1
1 0 1
0 1 1
0 1 1
0 1 0
0 0 0
0 1 0
0 1 0
0 1 0
0 0 1
1 0 0
1 1 1
0 1 0
1 1 0
0 1 1
0 1 1
0 0 1
0 0 1
0 1 1
0 1 1
1 0 0
0 1 1
1 0 0
0 0 1
1 0 0
1 0 0
0 1 1
0 1 0
1 1 1
0 0 0
1 0 0
1 0 1
1 1 0
0 0 1
0 0 0
1 1 1
1 1 1
1 0 0
1 0 1
1 0 1
1 1 0
0 0 1
1 0 0
1 0 1
1 0 0
0 1 0
0 1 1
1 1 0

  将matlab仿真的数据和ModelSim中的数据进行对比。

clc;
%% 串并转换
test_data = load('D:/FPGA/OFDM_802.11a_my/TX/matlab/test_data.txt')';
display(test_data);
FPGA_S2P2S = load('D:/FPGA/OFDM_802.11a_my/TX/matlab/u2_data_out.txt')';
display(FPGA_S2P2S);
check_S2P = test_data == FPGA_S2P2S; 
display(check_S2P);
%% 扰码
FPGA_scram_dout = load('D:/FPGA/OFDM_802.11a_my/TX/matlab/scram_data_out.txt')';
display(scram_out0);
display(FPGA_scram_dout);
check_scram = FPGA_scram_dout == scram_out0;
display(check_scram);
%% 卷积编码
FPGA_conv_dout = load('D:/FPGA/OFDM_802.11a_my/TX/matlab/conv_data_out.txt')';
display(conv_out0);
display(FPGA_conv_dout);
check_conv = FPGA_conv_dout == conv_out0;
display(check_conv);
%% 删余
FPGA_punt_dout = load('D:/FPGA/OFDM_802.11a_my/TX/matlab/punt_data_out.txt')';
display(conv_out);
display(FPGA_punt_dout);
check_punt = FPGA_punt_dout == conv_out;
display(check_punt);
%% 交织
FPGA_intv1_dout = load('D:/FPGA/OFDM_802.11a_my/TX/matlab/intv1_data_out.txt')';
display(int_lea_1_out);
display(FPGA_intv1_dout);
check_intv1 = FPGA_intv1_dout == int_lea_1_out;
display(check_intv1);

  matlab交织输出如下:

int_lea_1_out =

列 1 至 25

 1     1     0     1     0     0     1     0     0     1     0     0     1     1     0     0     0     1     1     0     0     1     1     0     0

列 26 至 50

 1     0     1     0     0     0     0     0     0     1     1     0     0     1     1     0     1     1     0     1     0     0     0     0     0

列 51 至 75

 1     1     0     1     0     1     1     0     1     1     0     1     0     0     0     0     0     1     0     0     1     0     0     1     0

列 76 至 100

 0     0     1     1     0     0     1     1     1     0     1     0     1     1     0     0     1     1     0     1     1     0     0     1     0

列 101 至 125

 0     1     0     1     1     0     1     1     1     0     0     0     1     1     1     0     0     0     0     1     1     0     0     1     0

列 126 至 150

 0     0     1     1     0     1     0     1     1     1     0     0     0     1     0     0     1     0     1     1     1     0     0     0     1

列 151 至 175

 0     0     0     1     1     1     1     1     1     1     0     0     1     0     1     1     0     1     1     1     0     0     0     1     1

列 176 至 192

 0     0     1     0     1     1     0     0     0     1     0     0     1     1     1     1     0

  FPGA交织输出如下:
FPGA_intv1_dout =

列 1 至 25

 1     1     0     1     0     0     1     0     0     1     0     0     1     1     0     0     0     1     1     0     0     1     1     0     0

列 26 至 50

 1     0     1     0     0     0     0     0     0     1     1     0     0     1     1     0     1     1     0     1     0     0     0     0     0

列 51 至 75

 1     1     0     1     0     1     1     0     1     1     0     1     0     0     0     0     0     1     0     0     1     0     0     1     0

列 76 至 100

 0     0     1     1     0     0     1     1     1     0     1     0     1     1     0     0     1     1     0     1     1     0     0     1     0

列 101 至 125

 0     1     0     1     1     0     1     1     1     0     0     0     1     1     1     0     0     0     0     1     1     0     0     1     0

列 126 至 150

 0     0     1     1     0     1     0     1     1     1     0     0     0     1     0     0     1     0     1     1     1     0     0     0     1

列 151 至 175

 0     0     0     1     1     1     1     1     1     1     0     0     1     0     1     1     0     1     1     1     0     0     0     1     1

列 176 至 192

 0     0     1     0     1     1     0     0     0     1     0     0     1     1     1     1     0

  对比结果如下:

check_intv1 =

1×192 logical 数组

列 1 至 38

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

列 39 至 76

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

列 77 至 114

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

列 115 至 152

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

列 153 至 190

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

列 191 至 192

1 1

  结果全为1,说明完全正确。作者也对其他调制方式和编码方案进行了测试,都是正确的,这里就不再赘述。

4总结

  需要回顾前面知识请点击链接:OFDM 802.11a的xilinx FPGA实现

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-05-03 11:42:06       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-05-03 11:42:06       106 阅读
  3. 在Django里面运行非项目文件

    2024-05-03 11:42:06       87 阅读
  4. Python语言-面向对象

    2024-05-03 11:42:06       96 阅读

热门阅读

  1. 软件开发标准流程与软件工程基本理论

    2024-05-03 11:42:06       28 阅读
  2. OneFlow 概念清单

    2024-05-03 11:42:06       40 阅读
  3. Leetcode之python使用记录

    2024-05-03 11:42:06       42 阅读
  4. Layui中change事件不生效

    2024-05-03 11:42:06       37 阅读
  5. 基于Spring EL表达式处理业务表达式

    2024-05-03 11:42:06       29 阅读
  6. 【无标题】

    2024-05-03 11:42:06       35 阅读
  7. 云计算服务模型比较:IaaS、PaaS与SaaS

    2024-05-03 11:42:06       38 阅读
  8. 力扣67 二进制求和 C语言

    2024-05-03 11:42:06       35 阅读
  9. Vue入门到关门之第三方框架elementui

    2024-05-03 11:42:06       30 阅读
  10. 什么是oneflow

    2024-05-03 11:42:06       41 阅读
  11. 70.爬楼梯

    2024-05-03 11:42:06       36 阅读
  12. Bug优先级定义

    2024-05-03 11:42:06       34 阅读