項目簡述
在通信過程中,經常能碰見調製解調,這裏將講解一種映射調製的方法,並且給出相應的代碼供大家學習。一般調製位於無線通信中比較靠後的未知,在交織之後。這裏的映射調製並沒有介入載波,只是進行了相應的星座圖映射,至於最後載波的加入,將在後面的博客中學習。
數學建模
首先我們常見的QPSK與16-QAM的星座圖如下:
由星座圖可以得到編碼之後的如下信息:
QPSK編碼對應座標軸大小
16-QAM編碼對應座標軸大小
星座圖映射的對象爲位交織輸出 , 主要是因爲LDPC編碼之後每包的長度就是16200,星座圖映射分爲兩步:
(1)解複用,將 分解爲 個子流
(2)將 個子流根據調製方式映射到對應星座圖。
那麼對於不同的調製方式, $N_{substreams}的值如下:
Modulation | Number of sub-streams |
---|---|
QPSK | 2 |
16-QAM | 8 |
首先我們要完成解複用,其實說白了解複用就是一個串並變換。
解複用模塊將輸入 映射到輸出,其中爲第個子流,。其中對於QPSK調製,i與的值一樣,對於16-QAM則進行了相應的變換,如下表:
Modulation format | QPSK | |
---|---|---|
0 | 1 | |
e | 0 | 1 |
Modulation format | 16-QAM | |||||||
---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
e | 7 | 1 | 4 | 2 | 5 | 3 | 6 | 0 |
進行完解複用之後就應該進行相應的映射操作,將會將上面產生的個並行通道映射到相應的個碼元,映射的關係如下表:
Modulation | ||
---|---|---|
QPSK | 2 | 2 |
16-QAM | 8 | 4 |
對於 QPSK 調製, Nsubstreams 個子流每次組成一個調製符號,而對於 16-QAM, 個子流每次組成兩個調製符號,前半個子流組成一個調製符號,後半個子流組成一個調製符號。具體公式如下:
定義調製符號:
對於QPSK調製:
對於16-QAM調製:
然後就完成了相應的映射操作。也就將原來的碼映射到了星座表中的座標上。
MATLAB仿真
上面我們已經講解了映射調製的星座圖對應關係,但是相信大家還都不是特別明白,接下來給出相應的MATLAB代碼供大家學習,學習的時候需要結合數學模型與MATLAB仿真相互學習。
sim_options = struct(...
'CONSTELLATION', '16-QAM' ...
);
fid1 = fopen('mapper_in.txt','r');
DataIn = fscanf(fid1,'%d');
%------------------------------------------------------------------------------
%------------------------------------------------------------------------------
switch sim_options.CONSTELLATION
case 'QPSK'
V = 2; % Bits per cell
case '16-QAM'
V = 4;
otherwise, error('sim_options UNKNOWN CONSTELLATION');
end
%------------------------------------------------------------------------------
% Parameters Definition
%------------------------------------------------------------------------------
CONSTEL = sim_options.CONSTELLATION;
%------------------------------------------------------------------------------
% Procedure
%------------------------------------------------------------------------------
%bits to cells
data = DataIn';
streams = V * 2;
switch CONSTEL
case 'QPSK'
mapping = [0 1 2 3] + 1;
case '16-QAM'
mapping = [7 1 4 2 5 3 6 0] + 1;
end
numBits = length(data);
numCells = floor(numBits/streams);
numBits = numCells*streams;
data = data(1:numBits);
%make streams
cells = reshape(data', streams, size(data,2)/streams)';
%swap this to agree with spec, above line maps the wrong way
cells(:, mapping) = cells;
cells = reshape(cells', [] , 1);
cells = reshape(cells, V , [])';
if ~isempty(cells)
DataOut = bi2de(cells,'left-msb');
else
DataOut = [];
end
save DataOut.mat DataOut
上面的代碼主要是源於電子發燒友學院,只是博主爲了方便理解,進行了一小部分的更改。需要的同學可以關注上面的課程信息。閱讀完相應的MATLAB代碼,再觀察前面的數學模型,會發現映射調製非常簡單。
設置調製方式:
找到輸入數據:
設置映射方式:
解複用:
映射到星座圖:
FPGA代碼
上面我們已經詳細介紹了映射調製的原理與MATLAB代碼,這裏將給出相應的MATLAB代碼。代碼非常簡單,這裏也就不對說了。
FPGA映射的代碼
tx_Mapper模塊:
`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author : zhangningning
// Email : [email protected]
// Website :
// Module Name : tx_Mapper.v
// Create Time : 2020-05-31 11:23:32
// Editor : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date By Version Change Description
// -----------------------------------------------------------------------
// XXXX zhangningning 1.0 Original
//
// *********************************************************************************
module tx_Mapper(
input clk ,
input rst_n ,
input s_config_tvalid ,
input s_config_tdata ,
input s_data_tvalid ,
input s_data_tdata ,
output reg s_data_tready ,
input s_data_tlast ,
output reg m_data_tvalid ,
output reg [ 3:0] m_data_tdata ,
input m_data_tready ,
output reg m_data_tlast
);
//========================================================================================\
//**************Define Parameter and Internal Signals**********************************
//========================================================================================/
reg mode_type ;
reg [12:0] Ncell ;
reg [ 1:0] QPSK_cnt ;
reg [ 3:0] QPSK_data ;
reg map_ena ;
reg [ 7:0] QPSK_mod_data ;
reg QPSK_mod_valid ;
reg [ 2:0] QAM_cnt ;
reg [ 7:0] QAM_data ;
reg map_enb ;
reg [ 7:0] QAM_mod_data ;
reg QAM_mod_valid ;
reg map_wea ;
reg [11:0] map_addra ;
reg [ 7:0] map_dina ;
reg [12:0] map_addrb ;
wire [ 3:0] map_doutb ;
reg data_ordy ;
reg [12:0] m_cnt ;
reg s_data_tvalid_reg ;
wire start_data_invld ;
//========================================================================================\
//************** Main Code **********************************
//========================================================================================/
assign start_data_invld = ~s_data_tvalid_reg && s_data_tvalid;
//====================================================================================
// configure parameter
//====================================================================================
always@(posedge clk)
if(rst_n == 1'b0)
mode_type <= 0;
else if(s_config_tvalid == 1'b1)
mode_type <= s_config_tdata; ///mode_type = 0,QPSK ; mode_type = 1 ,16QAM;
always@(*)
case(mode_type)
1'b0 : Ncell = 13'd8100;
1'b1 : Ncell = 13'd4050;
default : Ncell = 13'd0;
endcase
always@(posedge clk)
if(mode_type == 1'b0 && s_data_tvalid == 1'b1)
QPSK_cnt <= QPSK_cnt + 1'b1;
else
QPSK_cnt <= 0;
always@(posedge clk)
if(rst_n == 1'b0)
QPSK_data <= 0;
else case(QPSK_cnt)
2'b00 : QPSK_data[0] <= s_data_tdata;
2'b01 : QPSK_data[1] <= s_data_tdata;
2'b10 : QPSK_data[2] <= s_data_tdata;
2'b11 : QPSK_data[3] <= s_data_tdata;
default : QPSK_data <= QPSK_data;
endcase
always@(posedge clk)
if(QPSK_cnt == 2'b11)
map_ena <= 1'b1;
else
map_ena <= 1'b0;
always@(posedge clk)
if(map_ena)begin
QPSK_mod_data <= {2'b00,QPSK_data[2],QPSK_data[3],2'b00,QPSK_data[0],QPSK_data[1]};
QPSK_mod_valid <= 1'b1;
end else begin
QPSK_mod_data <= 0;
QPSK_mod_valid <= 0;
end
//16QAM
always@(posedge clk)
if(mode_type == 1'b1 && s_data_tvalid)
QAM_cnt <= QAM_cnt + 1'b1;
else
QAM_cnt <= 0;
always@(posedge clk)
if(rst_n == 1'b0)
QAM_data <= 0;
else case(QAM_cnt)
3'b000 : QAM_data[0] <= s_data_tdata;
3'b001 : QAM_data[1] <= s_data_tdata;
3'b010 : QAM_data[2] <= s_data_tdata;
3'b011 : QAM_data[3] <= s_data_tdata;
3'b100 : QAM_data[4] <= s_data_tdata;
3'b101 : QAM_data[5] <= s_data_tdata;
3'b110 : QAM_data[6] <= s_data_tdata;
3'b111 : QAM_data[7] <= s_data_tdata;
default : QAM_data <= QAM_data;
endcase
always@(posedge clk)
if(QAM_cnt == 3'b111)
map_enb <= 1'b1;
else
map_enb <= 1'b0;
always@(posedge clk)
if(map_enb)begin
QAM_mod_data <= {QAM_data[0],QAM_data[6],QAM_data[4],QAM_data[2],QAM_data[5],QAM_data[3],QAM_data[1],QAM_data[7]};
QAM_mod_valid <= 1'b1;
end else begin
QAM_mod_data <= 0;
QAM_mod_valid <= 0;
end
always@(posedge clk)
map_wea <= QAM_mod_valid || QPSK_mod_valid;
always@(posedge clk)
if(mode_type == 0)
map_dina <= QPSK_mod_data ;
else
map_dina <= QAM_mod_data;
always@(posedge clk)
if(rst_n == 1'b0)
map_addra <= 0;
else if(mode_type == 0 && map_addra == 12'd4050)
map_addra <= 0;
else if(mode_type == 1 && map_addra == 12'd2025)
map_addra <= 0;
else if(map_wea == 1'b1)
map_addra <= map_addra + 1'b1;
always@(posedge clk)
if(map_addra > 12'd3100 && mode_type == 0)
map_addrb <= map_addrb + 1'b1;
else if(map_addra > 12'd1800 && mode_type == 1)
map_addrb <= map_addrb + 1'b1;
else if(map_addrb < (Ncell-1) && map_addrb > 0)
map_addrb <= map_addrb + 1'b1;
else
map_addrb <= 0;
//====================================================================================
// Output
//====================================================================================
always@(posedge clk)
if(mode_type)
m_data_tdata <= {map_doutb[0],map_doutb[1],map_doutb[2],map_doutb[3]};
else
m_data_tdata <= map_doutb;
always@(posedge clk)
if(~rst_n)
m_data_tvalid <= 0;
else if(map_addrb > 0)
m_data_tvalid <= 1;
else if(m_cnt == (Ncell -1))
m_data_tvalid <= 0;
always@(posedge clk)
if(m_data_tvalid)
m_cnt <= m_cnt + 1'b1;
else
m_cnt <= 0;
always@(posedge clk)
if(m_cnt == (Ncell-2))
m_data_tlast <= 1'b1;
else
m_data_tlast <= 1'b0;
//====================================================================================
// m_data_tready && s_data_tready
//====================================================================================
always@(posedge clk)
s_data_tvalid_reg <= s_data_tvalid;
always@(posedge clk)
if(rst_n == 1'b0)
data_ordy <= 1'b0;
else if(s_config_tvalid == 1'b1)
data_ordy <= 1'b1;
else if(start_data_invld == 1'b1)
data_ordy <= 1'b0;
else if(m_data_tlast == 1'b1)
data_ordy <= 1'b1;
always@(posedge clk)
s_data_tready <= data_ordy && m_data_tready;
map_ram map_ram(
.clka (clk ), // input wire clka
.wea (map_wea ), // input wire [0 : 0] wea
.addra (map_addra ), // input wire [11 : 0] addra
.dina (map_dina ), // input wire [7 : 0] dina
.clkb (clk ), // input wire clkb
.addrb (map_addrb ), // input wire [12 : 0] addrb
.doutb (map_doutb ) // output wire [3 : 0] doutb
);
endmodule
這部分代碼也是主要參考了電子發燒友學院中的課程。代碼非常簡單,大家自己閱讀學習即可,這裏不再詳細說明。
FPGA的映射測試代碼
測試代碼
`timescale 1ns / 1ps
module tb_tx_Mapper;
// Inputs
reg clk ;
reg rstn ;
reg s_config_tvalid ;
wire s_config_tdata ;
reg s_data_tvalid ;
reg s_data_tdata ;
reg s_data_tlast ;
reg m_data_tready ;
// Outputs
wire s_data_tready ;
wire m_data_tvalid ;
wire [3:0] m_data_tdata ;
wire m_data_tlast ;
reg s_data_tvalid1 ;
reg s_data_tlast1 ;
reg mode_type = 1 ;
assign s_config_tdata = mode_type;
integer i;
initial begin
clk = 0;
rstn = 0;
s_config_tvalid = 0;
s_data_tvalid = 0;
m_data_tready = 1;
repeat(10) @(posedge clk);#1;
rstn = 1;
repeat(5) @(posedge clk);#1;
s_config_tvalid = 1;
repeat(1) @(posedge clk);#1;
s_config_tvalid = 0;
for(i=1;i<=1;i=i+1)
begin
repeat(1)@(posedge clk);#1; //begin
s_data_tvalid = 1;
s_data_tlast = 0;
repeat(16200*1-1)@(posedge clk);#1;
s_data_tvalid = 1;
s_data_tlast =1;
repeat(1)@(posedge clk);#1; //end
s_data_tvalid = 0;
s_data_tlast = 0;
repeat(5000*1)@(posedge clk);#1; //IDLE
end
end
always #5 clk = ~clk;
//====================================================================================
// input
//====================================================================================
always@(posedge clk)
begin
s_data_tvalid1 <=`UD s_data_tvalid;
s_data_tlast1 <= `UD s_data_tlast;
end
integer fid1;
initial
begin
fid1 = $fopen("mapper_in.txt","r");
end
always@(posedge clk)
begin
if(s_data_tvalid)
$fscanf(fid1,"%d",s_data_tdata);
end
//====================================================================================
// output
//====================================================================================
integer fid2;
initial
begin
fid2 = $fopen("ms_mapper_out.txt","w");
end
always@(posedge clk)
begin
if(m_data_tvalid)
$fwrite(fid2,"%d\n",m_data_tdata);
end
tx_Mapper uut (
.clk (clk ),
.rst_n (rstn ),
.s_config_tvalid (s_config_tvalid ),
.s_config_tdata (s_config_tdata ),
.s_data_tvalid (s_data_tvalid1 ),
.s_data_tdata (s_data_tdata ),
.s_data_tready (s_data_tready ),
.s_data_tlast (s_data_tlast1 ),
.m_data_tvalid (m_data_tvalid ),
.m_data_tdata (m_data_tdata ),
.m_data_tready (m_data_tready ),
.m_data_tlast (m_data_tlast )
);
endmodule
這部分測試代碼的編寫沒有難度,這裏不再介紹。
FPGA與MATLAB的交叉驗證
通過閱讀MATLAB代碼,可以知道我們最後會把映射之後的信息保存到相應的mat文件,然後再Modelsim仿真的時候會將映射之後的結果保存到txt文件,那麼我們通過MATLAB代碼很容易驗證實現兩塊內容的交叉驗證。MATLAB代碼如下:
clc;
clear all;
load DataOut.mat
data_lab = DataOut;
map_data_lab = [data_lab];
fid1 = fopen('ms_mapper_out.txt','r');
map_data_sim = fscanf(fid1,'%d');
if(isempty(map_data_sim))
map_data_result = 0;
else
map_data_result = sum(abs(map_data_lab - map_data_sim(1:length(map_data_lab))));
end
a = map_data_result;
運行之後,發現如下結果:
從而驗證了我們實驗的正確性。
小結
相信大家在學習算法的FPGA實現的時候都掌握了上面的流程,就是先在MATLAB中實現,然後再在FPGA中實現,交互驗證實現的正確性。
參考文獻
[1]、電子發燒友學院
總結
創作不易,認爲文章有幫助的同學們可以關注、點贊、轉發支持。爲行業貢獻及其微小的一部分。或者對文章有什麼看法或者需要更近一步交流的同學,可以加入下面的羣: