基于FPGA的映射调制实现

项目简述

在通信过程中,经常能碰见调制解调,这里将讲解一种映射调制的方法,并且给出相应的代码供大家学习。一般调制位于无线通信中比较靠后的未知,在交织之后。这里的映射调制并没有介入载波,只是进行了相应的星座图映射,至于最后载波的加入,将在后面的博客中学习。

数学建模

首先我们常见的QPSK与16-QAM的星座图如下:
在这里插入图片描述
由星座图可以得到编码之后的如下信息:
QPSK编码对应座标轴大小
在这里插入图片描述
在这里插入图片描述
16-QAM编码对应座标轴大小
在这里插入图片描述
在这里插入图片描述

星座图映射的对象为位交织输出 u=(u0,u1,...,uNldpc2,uNldpc1)u=(u_0,u_1,...,u_{N_{ldpc}-2},u_{N_{ldpc}-1}) , Nldpc=16200N_{ldpc}=16200主要是因为LDPC编码之后每包的长度就是16200,星座图映射分为两步:
(1)解复用,将uu 分解为 NsubstreamsN_{substreams} 个子流
(2)将 NsubstreamsN_{substreams} 个子流根据调制方式映射到对应星座图。

那么对于不同的调制方式, $N_{substreams}的值如下:

Modulation Number of sub-streams NsubstreamsN_{substreams}
QPSK 2
16-QAM 8

首先我们要完成解复用,其实说白了解复用就是一个串并变换。

在这里插入图片描述
解复用模块将输入vdiv_{di} 映射到输出bi,jb_{i,j},其中ii为第ii个子流,j=di div Nsubstreamsj=di \ div\ N_{substreams}。其中对于QPSK调制,i与di mod Nsubstreamsdi \ mod\ N_{substreams}的值一样,对于16-QAM则进行了相应的变换,如下表:

Modulation format QPSK
di mod Nsubstreamsdi \ mod\ N_{substreams} 0 1
e 0 1
Modulation format 16-QAM
di mod Nsubstreamsdi \ mod\ N_{substreams} 0 1 2 3 4 5 6 7
e 7 1 4 2 5 3 6 0

进行完解复用之后就应该进行相应的映射操作,将会将上面产生的NsubstreamsN_{substreams}个并行通道映射到相应的σ\sigma个码元,映射的关系如下表:

Modulation NsubstreamsN_{substreams} σ\sigma
QPSK 2 2
16-QAM 8 4

对于 QPSK 调制, Nsubstreams 个子流每次组成一个调制符号,而对于 16-QAM,NsubstreamsN_{substreams} 个子流每次组成两个调制符号,前半个子流组成一个调制符号,后半个子流组成一个调制符号。具体公式如下:
定义调制符号:ydo=[y0,do,...,yσ1,do]y_{do}=[y_{0,do},...,y_{\sigma-1,do}]
对于QPSK调制:[y0,do,...,yσ1,do]=[b0,do,...,bNsubstreams1,do][y_{0,do},...,y_{\sigma-1,do}]=[b_{0,do},...,b_{N_{substreams}-1,do}]
对于16-QAM调制:[y0,2do,...,yσ1,2do]=[b0,do,...,bNsubstreams/21,do][y_{0,2do},...,y_{\sigma-1,2do}]=[b_{0,do},...,b_{N_{substreams}/2-1,do}]
[y0,2do+1,...,yσ1,2do+1]=[bNsubstreams/2,do,...,bNsubstreams1,do][y_{0,2do+1},...,y_{\sigma-1,2do+1}]=[b_{N_{substreams}/2,do},...,b_{N_{substreams}-1,do}]
然后就完成了相应的映射操作。也就将原来的码映射到了星座表中的座标上。

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]、电子发烧友学院

总结

创作不易,认为文章有帮助的同学们可以关注、点赞、转发支持。为行业贡献及其微小的一部分。或者对文章有什么看法或者需要更近一步交流的同学,可以加入下面的群:
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章