1.前言
在前面的文章中讲解了卷积编码和删余,实现了1/2、2/3、3/4编码速率的输出。数据域在编码之后,下一个部分就是交织。今天对交织进行具体实现。
交织是为了在时域或频域或者同时在时域、频域上分布传输的信息比特,使信道的突发错误在时间上得以扩散,从而使得译码器可以将它们当作随机错误处理。交织器在几个分组长度或几个约束长度的范围内对码元进行混洗,这个范围是由突发持续时间决定的。通信系统的交织模式取决于信道特性。如果系统在一个纯粹的AWGN环境下运行,即准平稳信道,那么在一个数据包的持续时间上基本没有什么变化,就不需要交织。因为这时,通过重新分配的方法是无法改变误码分布的。
交织必然在系统中引人延时,这是因为接收到的比特顺序与信息源发送时的顺序是不相同的。通信系统通常规定了系统所能容忍的最大延时,因此也限制了所能使用的交织器的交织深度。
2.原理
2.1分组交织器
分组交织器是针对一组bit进行的,该分组当中bit的数量称为交织深度,交织深度越大,离散度越大,抗突发差错的能力也就越大,相应的引起的交织编码处理时间也越长。
我们可以用一个矩阵来描述分组交织器,将数据按照行进行写入数据,按照列的方式进行读出,或者按照相反的方式也是可以的。
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规定了包括调制方案、编码效率以及数据速率之间的关系,如下面三个表所示:
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), 表示调制阶数(1−BPSK,2−QPSK,3−8QAM,4−16QAM,5−32QAM,6−64QAM),R$表示编码效率。
802.11a规定数据域的交织分为两个步骤,今天主要讲第一次交织,其目的是保证相邻的比特在经过ofdm调制之后数据会落在不相邻的子载波上。
以QAM64调制,速率为54Mb/s为例,能够确定
s
=
3
,
N
C
B
P
S
=
288
s=3,N_{CBPS = 288}
s=3,NCBPS=288有了这些参数之后,我们就可以将这个交织的变换关系给计算出来,我们只需要根据这个关系进行将数据写入到一个RAM对应的地址当中,再依次将数据从RAM中读取出来即可以完成交织的工作。802.11a中最大的交织深度即为288,搞定了这个之后其他的也是一样的操作,其关系网如下所示:
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实现