赞
踩
交织多么好听的名字,第一次听见这个名字是在移动通信的课程中,当时就对这个名字一亮。交织其实就是把原有的码元顺序打乱进行发送,说白了就是矩阵变换。至于为什么要把原有的码元顺序打乱,是因为现实中传送的码元每一段都有固定的校验位,通过该校验位可以在码元错误较少的情况下进行纠错。而实际中受环境影响的码元是突发性错误,那么我们先经过交织之后传送的码元产生的突发性错误,经过解交织之后就会把原有的突发性错误转变成离散性错误,那么就可以有效的利用校验位进行纠错。这也是交织在通信系统中起到的主要作用,即变突发性错误为随机性错误。
从上面我们可以知道交织就是把原有的码元顺序打乱。一般的码组分为信息位和校验位,在交织的过程中,我们一般分为两步:
1、进行校验位的交织操作
2、对整个码组进行交织操作
上面两步的交织的原理并不一样,接下来分别介绍常见的交织操作。
校验位交织的数学公式如下:
d
i
=
u
i
f
o
r
0
<
=
i
<
=
K
1
d_i=u_i\ \ \ \ for\ \ \ 0<=i<=K1
di=ui for 0<=i<=K1
d
K
1
+
360
t
+
s
=
u
K
1
+
Q
l
d
p
c
s
+
t
f
o
r
0
<
=
s
<
360
,
0
<
=
t
<
=
Q
l
d
p
c
d_{K1+360t+s}=u_{K1+Q_{ldpc}s+t}\ \ \ \ \ for \ \ \ \ 0<=s<360,0<=t<=Q_{ldpc}
dK1+360t+s=uK1+Qldpcs+t for 0<=s<360,0<=t<=Qldpc
其中
i
i
i是码组中第几个码元,
K
1
K1
K1是信息位的个数,
u
i
u_i
ui是交织前的码元,
d
i
d_i
di是交织后的输出码元,
Q
l
d
p
c
=
校
验
位
个
数
/
360
Q_{ldpc}=校验位个数/360
Qldpc=校验位个数/360。
依据校验位交织地址生成公式可知,校验位交织本质是行进列出,若校验位数据向量重新构成矩阵形式,交织前矩阵为Qldpc*360,即依次将向量数据每行写入 360 个数,第 361 个数为第 2 行第 1 列数,依次类推。存入 Rom 地址如图所示,
我们矩阵的时候都是一行一行的看,这里虽然本质上是列进行出,但是我们读地址是一行一行读的,所以这个就相当于矩阵的转置操作。即取数据依次以如下地址取数据,重新生成新读出矩阵。
这里隐含一个非常重要的FPGA技巧,FPGA中矩阵的转置如何使用硬件语言来描述。 等进行讲解Verilog代码的时候,可以着重注意矩阵转置对应的Verilog代码。
上面我们已经进行了检验位交织,将校验位交织与原来的信息位组合成的输入
d
i
d_{i}
di 按列顺序依次写入列旋转交织器,然后按行依次读出完成列旋转交织,每列写入的起始位置由
t
c
tc
tc 决定 ,整个列旋转交织器:
这里解释一下与校验位交织的不同点:
1、校验位交织只有码组的校验位参与交织操作,而列旋转交织是整个码组进行交织操作
2、校验位交织的行是固定的360个元素,而列旋转交织则不是固定值
3、列旋转交织在转置的基础上,每一行均有一个tc的偏移,这也使得在书写FPGA代码的时候要比校验位交织难一点
以16QAM为例,上面的列旋交织主要有2025行8列,其中每一列的tc的值如下:
tc | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
16QAM | 0 | 0 | 0 | 1 | 7 | 20 | 20 | 21 |
列旋转交织器的输入为
d
i
,
0
<
=
i
<
=
N
d_i,0<=i<=N
di,0<=i<=N(其中N是整个码组的长度,在16QAM中N的长度是16200),写入列旋转交织器的
c
m
c_m
cm 列,
r
n
r_n
rn 行,则行和列需要满足下面的情况(以16QAM为例):
c
m
=
i
/
2025
c_m=i/2025
cm=i/2025
r
n
=
(
i
−
t
c
)
%
2025
r_n=(i-tc)\%2025
rn=(i−tc)%2025
由上面的的公式,我们可以写出列旋交织器的输入矩阵与输出矩阵之间的关系,这里同样是按照按照行的顺序写入读出。交织前的地址是:
进行列旋交织之后的地址如下:
这里注意,FPGA的语言叫做硬件描述语言,那么要想才能成功描述上面矩阵的变化,就必须找到上面矩阵的规律,上面的公式规律已经标红,大家可以最终结合相应的FPGA代码进行学习。大家阅读下面的FPGA代码的时候,可以发现一共2025行数据,只有前21行数据是乱的,后面的2000多行数据的计算都是有规律的,这也是FPGA代码中有一个很笨重状态机的原因。大家阅读FPGA代码的时候,一定要联系上面的矩阵,可以便于理解。
经过上面的原理介绍,相信大家对交织的操作有了一定的了解,那么接下来我们给出相应的MATLAB代码供大家更深入的了解。这里说明一下,代码不是博主编写,博主只是在原有的代码上进行了一些更改,可以更方便的理解代码,代码的来源是发烧友学院,在文章的最后,会附上参考网址,需要的同学可以关注一下。
sim_options = struct(... 'CRATE_DATA', '3/5', ... %业务编码码率1/2 3/5 2/3 3/4 4/5 5/6 'CONSTELLATION', '16-QAM' ... ); tx_nFrame =1; fid1 = fopen('bint_data_in.txt','r'); DataIn = fscanf(fid1,'%d'); switch sim_options.CRATE_DATA case '1/2', Q = 25; CR=4/9; case '3/5', Q = 18; CR=3/5; case '2/3', Q = 15; CR=2/3; case '3/4', Q = 12; CR=11/15; case '4/5', Q = 10; CR=7/9; case '5/6', Q = 8; CR=37/45; otherwise error('sim_options UNKNOWN CODING RATE'); end %------------------------------------------------------------------------------ % PLP-specific Parameters Definition %------------------------------------------------------------------------------ CONSTEL = sim_options.CONSTELLATION; % modulation constellation COD_RATE = CR; S = 360; % The S of the code CRATE = sim_options.CRATE_DATA; % The code rate %------------------------------------------------------------------------------ % Procedure %--------------------------------------- data = DataIn'; nbint_len = 16200 ; % block size, need to pass this in int_type = 2; parity_only = false; Nc = 8; switch CONSTEL case 'QPSK' int_type = 0; case '16-QAM' Tc = [0 0 0 1 7 20 20 21]; end Nr = nbint_len / Nc; numIntlvBlocks = floor(length(data)/nbint_len); % Number of interleaving blocks data = data(1:numIntlvBlocks*nbint_len); %clip the length of the input data bitIntlvOut = zeros(1,length(data),'single'); %mem preallocation if int_type == 0 %do no interleaving bitIntlvOut = data; elseif int_type == 2 %the type B interleaver plen = round(nbint_len * (1 - COD_RATE)); %make the parity interleaving table parity_table = zeros(1,plen); count = 1; for scount = 0:S-1 for t = 0:Q-1 parity_table(count) = S * t + scount + 1; count = count + 1; end end %compare data fid1 = fopen('mb_parity_addr.txt','w'); fprintf(fid1,'%d\n',parity_table); %make the Sony interleaving table if ~parity_only; code_table = zeros(Nc, Nr); for cols = 1:Nc tc = Tc(cols); rw = (0:Nr-1) - tc; rwm = mod(rw, Nr) + 1; rwm = rwm + (cols - 1) * Nr; code_table(cols, :) = rwm; end code_table = reshape(code_table, Nr, []);% + 1; code_table = reshape(code_table, nbint_len, []);% + 1; end intered_parity = zeros(1, plen); for it=1:numIntlvBlocks %pick off the parity bits parity = data( (it * nbint_len) - plen + 1:it * nbint_len); %interleave the parity bits intered_parity(parity_table) = parity; %tack on the parity bits and interleave the whole lot bitIntlvWr = [data((it-1)*nbint_len+1:it* nbint_len - plen) intered_parity]; %interleave the code word if ~parity_only; bitIntlvWr = bitIntlvWr(code_table); end % LDPC block append bitIntlvOut((it-1)*nbint_len+1:it*nbint_len) = bitIntlvWr; end end %if int_type == 1 DataOut.data = bitIntlvOut; save bitIntlvOut.mat bitIntlvOut
接下来对上面的代码进行简单的描述。
上面是是码组的编码率,下面是码元的调制方式,这里定义了一个结构体,进行一些全局定义。
1、引入输入码元
2、根据全局变量的定义选择不同的参数列表
1、输入码组的个数
2、根据全局变量的设置选择相应tc的值
3、其实这个程序不光可以对一个码组进行交织操作,同时可以对多个码组进行相应的交织操作。看起来程序处理的那么复杂,其实有绝大多数程序是对这方面进行相应的处理,本来博主想更改成一个码组进行处理,这样方便理解一点,但是一想,这是对源代码的不尊重,因为源代码明明功能多,自己非得降低他的功能。
1、计算校验位的个数
2、对校验位进行交织
1、计算列旋交织的输出地址
根据校验位交织、列旋交织的地址进行数据码元的变化,并取出输出码元。
上面只是简要的介绍了上面程序的大体功能,具体的需要大家联合数学原理与MATLAB代码一一相互验证,便可以真正学会该交织操作。
上面讲解完了交织的MATLAB实现,接下来讲解交织的FPGA实现。首先结合前面的理论知识,我先给大家画出程序框图,供大家理解整个设计。
下面整个代码是按照上面的流程进行书写的,当然上面是自己的理解,代码也不是自己书写,也只是改了一些地方。
这里直接给出相应的代码,
顶层模块:
`timescale 1ns/1ps // //Company: MYMINIEYE //Engineer: rp lv // //Create Date: 2016/03/14 09:41:00 //Design name: //Module name: tx_Bit_interleaver //Project name: tx_dvb_t2 //Target Devices: zc706 //Tool Versions: vivado 2015.1 //Description: // //Dependencies: // //Revision: v_01 //Revision 0.01 -File Created //Additional Comments // // `define UD #1 module tx_Bit_interleaver ( input sclk , input rst_n , input s_config_tvalid , input [3:0] 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 m_data_tdata , input m_data_tready , output reg m_data_tlast ); //========================================================================================\ //**************Define Parameter and Internal Signals********************************** //========================================================================================/ reg mode_type ; reg [2:0] code_rate ; reg [5:0] Qldpc ; reg [13:0] Nbch ; reg [13:0] Pldpc ; reg bitInter_enb ; reg [13:0] s_data_cnt ; reg start_parity ; reg [13:0] bitInter_ram_addra ; reg bitInter_ram_wren ; reg bitInter_ram_dina ; reg bitInter_ram_wren_reg ; reg store_over ; reg start_colrot_reg ; reg start_colrot_reg2 ; wire start_colrot ; reg [13:0] bitInter_ram_addrb ; wire bitInter_ram_doutb ; reg colrot_valid_reg ; reg colrot_valid_reg2 ; wire [13:0] parity_addr ; wire parity_valid ; wire [13:0] colrot_addr ; wire colrot_valid ; reg s_data_tvalid_reg ; wire start_data_invld ; //========================================================================================\ //************** Main Code ********************************** //========================================================================================/ //==================================================================================== // configure parameter //==================================================================================== always@(posedge sclk) begin if(~rst_n) begin mode_type <=`UD 0; code_rate <= `UD 0; end else if (s_config_tvalid) begin mode_type <= `UD s_config_tdata[0]; code_rate <= `UD s_config_tdata[3:1]; end end always@(posedge sclk) begin if(~rst_n) Qldpc <= `UD 0; else begin case (code_rate) 3'b000 : Qldpc <= `UD 6'd36; // Code Rate = 1/4; 3'b001 : Qldpc <= `UD 6'd25; // Code Rate = 1/2; 3'b010 : Qldpc <= `UD 6'd18; // Code Rate = 3/5; 3'b011 : Qldpc <= `UD 6'd15; // Code Rate = 2/3; 3'b100 : Qldpc <= `UD 6'd12; // Code Rate = 3/4; 3'b101 : Qldpc <= `UD 6'd10; // Code Rate = 4/5; 3'b110 : Qldpc <= `UD 6'd8; // Code Rate = 5/6; default: Qldpc <= `UD 6'd25; // Code Rate = 1/2; endcase end end //==================================================================================== // Processing Parity_Interleaver //==================================================================================== always@(posedge sclk) begin if(~rst_n) Nbch <= `UD 0; else begin case(code_rate) 3'd0 : Nbch <= `UD 14'd3240; 3'd1 : Nbch <= `UD 14'd7200; 3'd2 : Nbch <= `UD 14'd9720; 3'd3 : Nbch <= `UD 14'd10800; 3'd4 : Nbch <= `UD 14'd11880; 3'd5 : Nbch <= `UD 14'd12600; 3'd6 : Nbch <= `UD 14'd13320; default:Nbch <= `UD 14'd7200; endcase end end always@(posedge sclk) begin if(~rst_n) Pldpc <= `UD 0; else begin case(code_rate) 3'd0 : Pldpc <= `UD 14'd12960; 3'd1 : Pldpc <= `UD 14'd9000; 3'd2 : Pldpc <= `UD 14'd6480; 3'd3 : Pldpc <= `UD 14'd5400; 3'd4 : Pldpc <= `UD 14'd4320; 3'd5 : Pldpc <= `UD 14'd3600; 3'd6 : Pldpc <= `UD 14'd2880; default:Pldpc <= `UD 14'd9000; endcase end end always@(posedge sclk) begin bitInter_enb <=`UD s_data_tvalid; end always@(posedge sclk) begin if(bitInter_enb) s_data_cnt <= `UD s_data_cnt + 1'b1; else s_data_cnt <= `UD 0; end always@(posedge sclk) begin if(s_data_cnt == Nbch-5'd4) // consider latency start_parity <= `UD 1'b1; else start_parity <= `UD 1'b0; end always@(posedge sclk) begin if(~rst_n) bitInter_ram_addra <= `UD 0; else if(bitInter_enb) begin if(parity_valid && mode_type == 1) bitInter_ram_addra <= `UD parity_addr + Nbch; else if(mode_type == 1) bitInter_ram_addra <= `UD bitInter_ram_addra + 1'b1; else if(mode_type == 0) bitInter_ram_addra <= `UD bitInter_ram_addra + 1'b1; end else begin bitInter_ram_addra <= `UD 0; end end always@(posedge sclk) begin bitInter_ram_wren <=`UD s_data_tvalid; bitInter_ram_dina <=`UD s_data_tdata; end always@(posedge sclk) begin bitInter_ram_wren_reg <=`UD bitInter_ram_wren; end always@(posedge sclk) begin if(~rst_n || start_colrot) store_over <= `UD 1'b0; else if(~bitInter_ram_wren && bitInter_ram_wren_reg) store_over <= `UD 1'b1; end always@(posedge sclk) begin if(~rst_n) start_colrot_reg <= `UD 0; else if(m_data_tready && store_over) start_colrot_reg <= `UD 1; else start_colrot_reg <= `UD 0; end always@(posedge sclk) begin start_colrot_reg2 <=`UD start_colrot_reg; end assign start_colrot = ~start_colrot_reg2 && start_colrot_reg; always@(posedge sclk) begin if(~rst_n) bitInter_ram_addrb <=`UD 0; else if(mode_type == 0 && colrot_valid_reg && bitInter_ram_addrb < 14'd16199) // QPSK bitInter_ram_addrb <=`UD bitInter_ram_addrb + 1'b1; else if(mode_type == 1) //16 QAM bitInter_ram_addrb <=`UD colrot_addr; else bitInter_ram_addrb <=`UD 0; end always@(posedge sclk) begin colrot_valid_reg <= `UD colrot_valid; colrot_valid_reg2 <= `UD colrot_valid_reg; m_data_tvalid <= `UD colrot_valid_reg2; end always@(posedge sclk) begin m_data_tdata <= `UD bitInter_ram_doutb; end always@(posedge sclk) begin if(~colrot_valid_reg && colrot_valid_reg2) m_data_tlast <= `UD 1'b1; else m_data_tlast <= `UD 1'b0; end always@(posedge sclk) begin s_data_tvalid_reg <= `UD s_data_tvalid; end assign start_data_invld = ~s_data_tvalid_reg && s_data_tvalid; always@(posedge sclk) begin if(~rst_n) s_data_tready <= `UD 0; else if(s_config_tvalid) s_data_tready <= `UD 1; else if(start_data_invld) s_data_tready <= `UD 0; else if(m_data_tlast) s_data_tready <= `UD 1; end parity_bom_addr_gen parity_bom_addr_gen ( .sclk(sclk), //input .rst_n(rst_n), //input .Qldpc(Qldpc), //input [5:0] .Pldpc(Pldpc), //input [13:0] .start_parity(start_parity), //input .parity_addr(parity_addr), //output reg [13:0] .parity_valid(parity_valid) //output reg ); ColRot_bom_addr_gen ColRot_bom_addr_gen ( .sclk(sclk), // input .rst_n(rst_n), //input .start_colrot(start_colrot), // input .colrot_addr(colrot_addr), // output reg [13:0] .colrot_valid(colrot_valid) // output reg ); ldpc_ram ldpc_ram ( .clka(sclk), // input clka .wea(bitInter_ram_wren), // input [0 : 0] wea .addra(bitInter_ram_addra), // input [13 : 0] addra .dina(bitInter_ram_dina), // input [0 : 0] dina .clkb(sclk), // input clkb .addrb(bitInter_ram_addrb), // input [13 : 0] addrb .doutb(bitInter_ram_doutb) // output [0 : 0] doutb ); endmodule
校验位交织模块:
`timescale 1ns/1ps // //Company: MYMINIEYE //Engineer: rp lv // //Create Date: 2016/03/14 09:41:00 //Design name: //Module name: parity_bom_addr_gen //Project name: tx_dvb_t2 //Target Devices: zc706 //Tool Versions: vivado 2015.1 //Description: // //Dependencies: // //Revision: v_01 //Revision 0.01 -File Created //Additional Comments // // `define UD #1 module parity_bom_addr_gen ( input sclk , input rst_n , input [5:0] Qldpc , input [13:0] Pldpc , input start_parity , output reg [13:0] parity_addr , output reg parity_valid ); //========================================================================================\ //**************Define Parameter and Internal Signals********************************** //========================================================================================/ reg parity_enb ; reg [13:0] parity_cnt ; reg [4:0] s_cnt ; reg [13:0] S ; reg [13:0] T ; //========================================================================================\ //************** Main Code ********************************** //========================================================================================/ always@(posedge sclk) begin if(~rst_n) parity_enb <= `UD 1'b0; else if(start_parity) parity_enb <= `UD 1'b1; else if(parity_cnt == Pldpc - 1) parity_enb <= `UD 1'b0; end always@(posedge sclk) begin if(~rst_n) parity_cnt <= `UD 0; else if(parity_enb) parity_cnt <= `UD parity_cnt + 1'b1; else parity_cnt <= `UD 0; end always@(posedge sclk) begin if(~rst_n) begin s_cnt <= `UD 0; S <= `UD 0; end else if(parity_enb) begin if(s_cnt < Qldpc - 1) begin s_cnt <= `UD s_cnt + 1'b1; S <= `UD S + 14'd360; end else begin s_cnt <= `UD 0; S <= `UD 0; end end else begin s_cnt <= `UD 0; S <= `UD 0; end end always@(posedge sclk) begin if(~rst_n) T <= `UD 0; else if(parity_enb) begin if(s_cnt == Qldpc -1) T <= `UD T + 1; end else T <= `UD 0; end always@(posedge sclk) begin if(parity_enb) parity_addr <= `UD S + T; else parity_addr <= `UD 0; end always@(posedge sclk) begin parity_valid <= `UD parity_enb; end endmodule
列旋交织模块:
`timescale 1ns/1ps // //Company: MYMINIEYE //Engineer: rp lv // //Create Date: 2016/03/14 13:54:00 //Design name: //Module name: ColRot_bom_addr_gen //Project name: tx_dvb_t2 //Target Devices: zc706 //Tool Versions: vivado 2015.1 //Description: //************************************** // bom_addr_gen // 0 2050 4050 8099 10118 12130 14155 16179 // 1 2026 4051 6075 10119 12131 14156 16180 // 2 2027 4052 6076 10120 12132 14157 16181 // ...... //************************************** //Dependencies: // //Revision: v_01 //Revision 0.01 -File Created //Additional Comments // // `define UD #1 module ColRot_bom_addr_gen ( input sclk , input rst_n , input start_colrot , output reg [13:0] colrot_addr , output reg colrot_valid ); //========================================================================================\ //**************Define Parameter and Internal Signals********************************** //========================================================================================/ parameter Nc = 3'd7; parameter Lcr = 14'd16033;//lcr = ldpc - Nc*21=16200 - 8*21 = 16032; parameter Nr = 11'd2025; parameter Idle = 24'b0000_0000_0000_0000_0000_0000; parameter Row_1 = 24'b0000_0000_0000_0000_0000_0001; parameter Row_2 = 24'b0000_0000_0000_0000_0000_0010; parameter Row_3 = 24'b0000_0000_0000_0000_0000_0100; parameter Row_4 = 24'b0000_0000_0000_0000_0000_1000; parameter Row_5 = 24'b0000_0000_0000_0000_0001_0000; parameter Row_6 = 24'b0000_0000_0000_0000_0010_0000; parameter Row_7 = 24'b0000_0000_0000_0000_0100_0000; parameter Row_8 = 24'b0000_0000_0000_0000_1000_0000; parameter Row_9 = 24'b0000_0000_0000_0001_0000_0000; parameter Row_10 = 24'b0000_0000_0000_0100_0000_0000; parameter Row_11 = 24'b0000_0000_0000_1000_0000_0000; parameter Row_12 = 24'b0000_0000_0001_0000_0000_0000; parameter Row_13 = 24'b0000_0000_0010_0000_0000_0000; parameter Row_14 = 24'b0000_0000_0100_0000_0000_0000; parameter Row_15 = 24'b0000_0000_1000_0000_0000_0000; parameter Row_16 = 24'b0000_0001_0000_0000_0000_0000; parameter Row_17 = 24'b0000_0010_0000_0000_0000_0000; parameter Row_18 = 24'b0000_0100_0000_0000_0000_0000; parameter Row_19 = 24'b0000_1000_0000_0000_0000_0000; parameter Row_20 = 24'b0001_0000_0000_0000_0000_0000; parameter Row_21 = 24'b0010_0000_0000_0000_0000_0000; parameter Row_last = 24'b0100_0000_0000_0000_0000_0000; parameter Gen_end = 24'b1000_0000_0000_0000_0000_0000; parameter Idle1 = 2'b00; parameter gen1 = 2'b01; parameter gen2 = 2'b10; reg [23:0] state=0; reg [23:0] state_n=0; reg [2:0] Col_cal; reg [13:0] Row_cal; reg start_colrot_reg; reg start_colrot_cal; reg colrot_enb; reg [13:0]colrot_cnt; reg [2:0] col_cnt; reg [13:0]col_addr; reg [13:0]row_addr; reg [13:0]gen_taddr; //==================================================================================== // Generate addr for ColRot bit and valid signal //==================================================================================== always@(posedge sclk) begin if(~rst_n) state <= `UD 0; else state <= `UD state_n; end always@(posedge sclk) begin start_colrot_reg <= `UD start_colrot; start_colrot_cal <= `UD start_colrot_reg; end always@(*) begin state_n = state; case(state) Idle : if(start_colrot_cal) state_n = Row_1; else state_n = Idle; Row_1: if(Col_cal < Nc) state_n = Row_1; else state_n = Row_2; Row_2: if(Col_cal < Nc) state_n = Row_2; else state_n = Row_3; Row_3: if(Col_cal < Nc) state_n = Row_3; else state_n = Row_4; Row_4: if(Col_cal < Nc) state_n = Row_4; else state_n = Row_5; Row_5: if(Col_cal < Nc) state_n = Row_5; else state_n = Row_6; Row_6: if(Col_cal < Nc) state_n = Row_6; else state_n = Row_7; Row_7: if(Col_cal < Nc) state_n = Row_7; else state_n = Row_8; Row_8: if(Col_cal < Nc) state_n = Row_8; else state_n = Row_9; Row_9: if(Col_cal < Nc) state_n = Row_9; else state_n = Row_10; Row_10: if(Col_cal < Nc) state_n = Row_10; else state_n = Row_11; Row_11: if(Col_cal < Nc) state_n = Row_11; else state_n = Row_12; Row_12: if(Col_cal < Nc) state_n = Row_12; else state_n = Row_13; Row_13: if(Col_cal < Nc) state_n = Row_13; else state_n = Row_14; Row_14: if(Col_cal < Nc) state_n = Row_14; else state_n = Row_15; Row_15: if(Col_cal < Nc) state_n = Row_15; else state_n = Row_16; Row_16: if(Col_cal < Nc) state_n = Row_16; else state_n = Row_17; Row_17: if(Col_cal < Nc) state_n = Row_17; else state_n = Row_18; Row_18: if(Col_cal < Nc) state_n = Row_18; else state_n = Row_19; Row_19: if(Col_cal < Nc) state_n = Row_19; else state_n = Row_20; Row_20: if(Col_cal < Nc) state_n = Row_20; else state_n = Row_21; Row_21: if(Col_cal < Nc) state_n = Row_21; else state_n = Row_last; Row_last: if(Row_cal < Lcr) state_n = Row_last; else state_n = Gen_end; Gen_end : state_n = Idle; default : state_n = Idle; endcase end always@(posedge sclk) begin if(~rst_n) Row_cal <= `UD 0; else if (state_n == Row_last) Row_cal <= `UD Row_cal + 1'b1; else Row_cal <=`UD 0; end always @(posedge sclk) begin case(state_n) Idle : Col_cal <= `UD 0; Row_1: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_2: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_3: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_4: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_5: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_6: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_7: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_8: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_9: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_10: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_11: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_12: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_13: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_14: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_15: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_16: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_17: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_18: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_19: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_20: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_21: if(Col_cal == Nc) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; Row_last:if(Row_cal == Lcr) Col_cal <=`UD 0; else Col_cal <=`UD Col_cal + 1'b1; default : Col_cal <=`UD 0; endcase end //==================================================================================== // Generate Rotate addr for Ldpc Block //==================================================================================== always@(posedge sclk) begin case(state) Idle: colrot_addr <=`UD 0; Row_1: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_2: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_3: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_4: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_5: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_6: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_7: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_8: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_9: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_10: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_11: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_12: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_13: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_14: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_15: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_16: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_17: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_18: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_19: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_20: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_21: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr + 12'd2025 - 5'd21; Row_last: if (Col_cal < 3'd3) colrot_addr <=`UD gen_taddr; else if (Col_cal == 3'd3) colrot_addr <=`UD gen_taddr - 5'd1; else if (Col_cal == 3'd4) colrot_addr <=`UD gen_taddr - 5'd7; else if (Col_cal == 3'd5) colrot_addr <=`UD gen_taddr - 5'd20; else if (Col_cal == 3'd6) colrot_addr <=`UD gen_taddr - 5'd20; else if (Col_cal == 3'd7) colrot_addr <=`UD gen_taddr - 5'd21; Gen_end : colrot_addr <=`UD 0; default : colrot_addr <=`UD 0; endcase end //==================================================================================== // Generate normal addr , so that easy to get rotate addr //==================================================================================== always@(posedge sclk) begin if(~rst_n) colrot_enb <= `UD 1'b0; else if(start_colrot) colrot_enb <= `UD 1'b1; else if(colrot_cnt == 14'd16199) colrot_enb <= `UD 1'b0; end always@(posedge sclk) begin if(~rst_n) colrot_cnt <= `UD 0; else if(colrot_enb) colrot_cnt <= `UD colrot_cnt + 1'b1; else colrot_cnt <= `UD 0; end always@(posedge sclk) begin if(~rst_n) begin col_cnt <= `UD 0; col_addr <= `UD 0; end else if(colrot_enb) begin if(col_cnt < Nc) begin col_cnt <= `UD col_cnt + 1'b1; col_addr <= `UD col_addr + 14'd2025; end else begin col_cnt <= `UD 0; col_addr <= `UD 0; end end else begin col_cnt <= `UD 0; col_addr <= `UD 0; end end always@(posedge sclk) begin if(~rst_n) row_addr <= `UD 0; else if(colrot_enb ) begin if(col_cnt == Nc) row_addr <= `UD row_addr + 1; end else begin row_addr <= `UD 0; end end always@(posedge sclk) begin if(colrot_enb) gen_taddr <= `UD col_addr + row_addr; else gen_taddr <= `UD 0; end //==================================================================================== // Generate Rotate addr valid signal //==================================================================================== always @(posedge sclk) begin if(~rst_n) colrot_valid <= `UD 0; else if(state_n == Idle || state_n ==Gen_end) colrot_valid <=`UD 0; else colrot_valid <=`UD 1; end endmodule
上面着重说一下列旋交织模块中的状态机,我认为状态机完全可以用行计数器来代替,可以很好的减少代码的复杂度。上面的代码,博主学习了一天的时间才看懂上面的逻辑流程,但是相信经过前面的框图与原理性的介绍,大家学习可以快点。
测试代码如下:
`timescale 1ns / 1ps // Company: MYMINEYE // Engineer:rplv // // Create Date: 11:32:55 03/15/2016 // Design Name: tx_Bit_interleaver // Module Name: tb_tx_Bit_interleaver.v // Project Name: tx_dvb_t2 // Target Device: zc706 // Tool versions: vivado.2015.1 // Description: // // // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // `define UD #1 module tb_tx_Bit_interleaver; // Inputs reg clk; reg rstn; reg s_config_tvalid; wire [3:0] 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 m_data_tdata; wire m_data_tlast; reg s_data_tvalid1; reg s_data_tlast1; // Instantiate the Unit Under Test (UUT) tx_Bit_interleaver uut ( .clk(clk), .rstn(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) ); //========================================================================= // config parameter //========================================================================= reg mode_type = 1'b1; reg [2:0]code_rate = 3'd2; assign s_config_tdata [0] = mode_type; assign s_config_tdata [3:1] = code_rate; //========================================================================= // initial //========================================================================= initial begin s_config_tvalid = 0; s_data_tvalid = 0; rstn = 0; clk = 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; repeat(1) @(posedge clk);#1; s_data_tvalid = 1; s_data_tlast = 0; repeat(16200*1-1) @(posedge clk);#1;//QPSK = 8100, //repeat(4049) @(posedge clk);#1;//QAM = 4050; s_data_tvalid = 1; s_data_tlast = 1; repeat(1) @(posedge clk);#1;// s_data_tvalid = 0; s_data_tlast = 0; end always@(posedge clk) begin s_data_tvalid1 <= `UD s_data_tvalid; s_data_tlast1 <= `UD s_data_tlast; end //========================================================================= // input //========================================================================= integer fid1; initial begin fid1 = $fopen("bint_data_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_bitInter_sim.txt","w"); end always@(posedge clk) begin if(m_data_tvalid) $fwrite(fid2,"%b\n",m_data_tdata); end always #5 clk = ~clk; //========================================================================= // compare addr //========================================================================= wire parity_valid = tb_tx_Bit_interleaver.uut.parity_valid; wire signed [13:0] parity_addr = tb_tx_Bit_interleaver.uut.parity_addr; integer fid3; initial begin fid3 = $fopen("ms_parity_addr.txt","w"); end always@(posedge clk) begin if(parity_valid) $fwrite(fid3,"%d\n",parity_addr+1'b1); end endmodule
从上面我们可以看出测试代码将交织后的码元输出到一个txt文件中,然后我们就可以在MATLBA中与MATLAB生成的交织后的码元相互验证,证明FPGA侧交织的正确性。
MATLAB验证代码如下:
clc ; clear all; load bitIntlvOut.mat data_lab = bitIntlvOut; tx_nFrame =1; bitInter_data_lab = [data_lab']; fid1 = fopen('ms_bitInter_sim.txt','r'); bitInter_data_sim = fscanf(fid1,'%d'); start_Idx = length(bitInter_data_lab)*(tx_nFrame - 1); if(isempty(bitInter_data_sim)) bitInter_data_result = 0; else bitInter_data_result = sum(abs(bitInter_data_lab - bitInter_data_sim(start_Idx+1:start_Idx+length(bitInter_data_lab)))); end a = bitInter_data_result
最终测试的结果FPGA与MATLAB交织后的码元完全相同。如下图:
相信大家在学习算法的FPGA实现的时候都掌握了上面的流程,就是先在MATLAB中实现,然后再在FPGA中实现,交互验证实现的正确性。
[1]、电子发烧友学院
创作不易,认为文章有帮助的同学们可以关注、点赞、转发支持。为行业贡献及其微小的一部分。或者对文章有什么看法或者需要更近一步交流的同学,可以加入下面的群:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。