基于FPGA生成OFDM802.11.a训练序列

2025博客之星年度评选已开启 10w+人浏览 1.6k人参与

由于是基于FPGA生成训练序列,因此需要先生成.coe文件,用于后续添加到RAM IP核中。

先打开matlab,新建脚本文件。

添加短训练序列生成代码,以及生成.coe文件的代码。

%% 短训练序列
%% Parameter
N_FFT = 64;
%% Short_preamble
S_k = sqrt(13/6)*[0,0,1+j,0,0,0,-1-j,0,0,0,1+j,0,0,0,-1-j,0,0,0,-1-j,0,0,0,1+j,0,0,0,0,0,0,0,-1-j,0,0,0,-1-j,0,0,0,1+j,0,0,0,1+j,0,0,0,1+j,0,0,0,1+j,0,0]; % [1x53]
virtual_subcarrier = zeros(1,N_FFT-length(S_k)); % [1x11] 虚拟子载波
Short_preamble_slot_Frequency = [virtual_subcarrier(1:6),S_k,virtual_subcarrier(7:11)]; % [1x64]
Short_preamble_slot_Time = ifft(ifftshift(Short_preamble_slot_Frequency)); % [1x64]
Short_preamble = repmat(Short_preamble_slot_Time(1:16),1,10); % [1x160]

% 实部和虚部分开
Short_preamble_real = real(Short_preamble_slot_Time);
Short_preamble_image = imag(Short_preamble_slot_Time);

% 定点化:生成16位二进制补码字符串(列向量)
Short_preamble_real_bin = bin(fi(Short_preamble_real,1,16,13).');
Short_preamble_image_bin = bin(fi(Short_preamble_image,1,16,13).');

% 你的核心拼接逻辑:虚部16位 + 虚部16位 → 32位二进制串
Short = [(Short_preamble_image_bin), (Short_preamble_real_bin)]; % 64行×32列的字符数组

%%截取Short的前16行,传入函数
gen_coe_binary('short_preamble.coe', Short(1:16, :)); % 只取前16行
%% COE生成函数
function gen_coe_binary(fn, bin_char_array)
    fid = fopen(fn, 'w');
    if fid == -1
        error('无法打开文件!请检查路径是否含中文/特殊字符');
    end
    
    % COE文件头:二进制基数(radix=2)
    fprintf(fid, 'memory_initialization_radix=2;\n');
    fprintf(fid, 'memory_initialization_vector=\n');
    
    % 遍历截取后的行
    [n_rows, ~] = size(bin_char_array); % 此时n_rows=16
    for i = 1:n_rows
        bin_str = bin_char_array(i, :); % 提取第i行32位二进制串
        if i == n_rows
            fprintf(fid, '%s;\n', bin_str);
        else
            fprintf(fid, '%s,\n', bin_str); 
        end
    end
    
    fclose(fid);
    disp(['COE文件已生成:', fullfile(pwd, fn)]);
end

运行后,生成short_preamble.coe文件。如下所示。

添加长训练序列生成代码,以及生成.coe文件的代码。

%% Parameter
N_FFT = 64;
%% Long_preamble   premble:前导码用来同步发射机和接受机,以维系两者之间的定时关系 
L_k = [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,0,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]; % [1x53]
virtual_subcarrier = zeros(1,N_FFT-length(L_k)); % [1x11]
Long_preamble_slot_Frequency = [virtual_subcarrier(1:6),L_k,virtual_subcarrier(7:11)]; % [1x64]
Long_preamble_slot_Time = ifft(ifftshift(Long_preamble_slot_Frequency)); % [1x64]
Long_preamble = [Long_preamble_slot_Time(33:64),Long_preamble_slot_Time,Long_preamble_slot_Time]; % [1x160]
%实部和虚部分开
Long_preamble_real = real(Long_preamble_slot_Time);
Long_preamble_image = imag(Long_preamble_slot_Time);
%定点
Long_preamble_real_bin =bin(fi(Long_preamble_real,1,16,13).'); 
Long_preamble_image_bin =bin(fi(Long_preamble_image,1,16,13).'); 

long=[(Long_preamble_image_bin),(Long_preamble_real_bin)];

%% 调用函数生成COE(默认生成完整64行)
gen_coe_binary('long_preamble.coe', long);
%% COE生成函数(与短训练序列共用同一逻辑,确保格式统一)
function gen_coe_binary(fn, bin_char_array)
    % 打开文件(写入模式,不存在则创建)
    fid = fopen(fn, 'w');
    if fid == -1
        error('无法打开文件!请检查路径是否含中文/特殊字符');
    end
    
    % COE文件头:二进制基数(radix=2),与数据格式匹配
    fprintf(fid, 'memory_initialization_radix=2;\n');
    fprintf(fid, 'memory_initialization_vector=\n');
    
    % 遍历所有数据行(默认64行,如需前16行可修改下面的索引)
    [n_rows, ~] = size(bin_char_array);
    for i = 1:n_rows
        % 提取第i行的32位二进制字符串(不修改任何字符)
        bin_str = bin_char_array(i, :);
        % 遵循COE语法:最后一行用分号,其余用逗号
        if i == n_rows
            fprintf(fid, '%s;\n', bin_str);
        else
            fprintf(fid, '%s,\n', bin_str);
        end
    end
    
    fclose(fid);
    disp(['长训练序列COE文件已生成:', fullfile(pwd, fn)]);
end

生成的长训练序列如下所示。

memory_initialization_radix=2;
memory_initialization_vector=
00000000000000000000010100000000,
11111100001001101111111111010110,
11111100011100010000000101000110,
00000010101001100000001100011001,
00000000111001000000000010101101,
11111101001100100000000111101010,
11111110001111001111110001010001,
11111100100110101111111011000110,
11111111001011000000001100011111,
00000000001000010000000110110101,
11111100010100100000000000001000,
11111110011111001111101110011111,
11111110001000010000000011001001,
11111111100001100000000111100001,
00000101001001001111111101001000,
11111111110111100000001111010001,
11111110000000000000001000000000,
00000011001001100000000100101110,
00000001010000101111111000101011,
00000010000101101111101111001101,
00000010111101010000001010100010,
00000000011101000000001000111010,
00000010100110101111111000010010,
11111111010011011111111000110010,
11111011001011001111111011100001,
11111111011110001111110000011010,
11111111010110001111101111101101,
11111101101000010000001001100111,
00000001101110011111111111101001,
00000011101011111111110100001111,
00000011011000110000001011101111,
00000011001000000000000001100101,
00000000000000001111101100000000,
11111100111000000000000001100101,
11111100100111010000001011101111,
11111100010100011111110100001111,
11111110010001111111111111101001,
00000010010111110000001001100111,
00000000101010001111101111101101,
00000000100010001111110000011010,
00000100110101001111111011100001,
00000000101100111111111000110010,
11111101011001101111111000010010,
11111111100011000000001000111010,
11111101000010110000001010100010,
11111101111010101111101111001101,
11111110101111101111111000101011,
11111100110110100000000100101110,
00000010000000000000001000000000,
00000000001000100000001111010001,
11111010110111001111111101001000,
00000000011110100000000111100001,
00000001110111110000000011001001,
00000001100001001111101110011111,
00000011101011100000000000001000,
11111111110111110000000110110101,
00000000110101000000001100011111,
00000011011001101111111011000110,
00000001110001001111110001010001,
00000010110011100000000111101010,
11111111000111000000000010101101,
11111101010110100000001100011001,
00000011100011110000000101000110,
00000011110110101111111111010110;

注意代码里的memory_initialization_radix=2参数不要修改,表示2进制。用于RAM 模块编译时识别。

建好vivado工程后,把生成好的两个.coe文件,添加到vivado工程中的 .srcs/sources_1目录下,便于软件编译时扫描。

打开vivado工程。点击IP Catalog

搜索bram。

配置短训练序列的RAM ip核。

点击Browse添加.coe文件。

配置完后,点击OK,等待generate完成后,再添加一个ram,配置长训练序列的ip核。下面再对长训练序列的ip核进行配置。

一样等待generate完成后,后面开始编写读取ram的代码。

先读取短训练序列,新建.v文件,对short_preamble.v进行编写

因为需要用例化的RAM模块,读取里面的训练序列,打开生成的.veo文件

添加到short_preamble.v中

`timescale 1ns / 1ps

module preamble_short(
    input   clk,
    input   rst_n,
    
    output  [31:0] short_data,
    output  reg short_predone_flag,
    output  reg short_en
    );

reg    [3:0]  short_addr;
reg    [3:0]  short_cnt;

reg    [17:0] delay_cnt;  
reg           start_flag;

reg    short_en_d;
always@(posedge clk or negedge rst_n) begin 
    if(!rst_n) begin
        delay_cnt  <= 18'd0;  
        start_flag <= 1'b0;
    end else if(delay_cnt == 18'd250_000) begin  
        delay_cnt  <= 18'd0;
        start_flag <= 1'b0;       
    end else if(delay_cnt == 18'd250_000 - 1) begin 
        delay_cnt  <= delay_cnt + 1'b1;
        start_flag <= 1'b1;      
    end else begin
        delay_cnt  <= delay_cnt + 1'b1;
        start_flag <= 1'b0;
    end
end
always@(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        short_en_d <= 1'b0;
    end else if(start_flag) begin
        short_en_d <= 1'b1;
    end else if(short_predone_flag) begin  
        short_en_d <= 1'b0;
    end else begin
        short_en_d <= short_en_d;
        short_en <= short_en_d;
    end
end

always@(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        short_addr         <= 4'd0;
        short_cnt          <= 4'd0;
        short_predone_flag <= 1'b0;
    end else if(short_en) begin  
        short_predone_flag <= 1'b0;  
        if(short_addr == 4'd15) begin  
            short_addr <= 4'd0;       
            short_cnt  <= short_cnt + 1'b1;
        end else begin  
            short_addr <= short_addr + 1'b1;
        end
        if((short_cnt == 4'd9) && (short_addr == 4'd15)) begin
            short_predone_flag <= 1'b1;
        end
    end
    else begin
        short_addr         <= 4'd0;
        short_cnt          <= 4'd0;
        short_predone_flag <= 1'b0;
    end
end

short_preamble  short_preamble_u(
  .clka(clk),    
  .ena(short_en),      
  .addra(short_addr),  
  .douta(short_data)  
);

endmodule

再新建一个.v文件,读取长训练序列,对long_preamble.v进行编写

`timescale 1ns / 1ps

module preamble_long(
    input   clk,
    input   rst_n,
    input   short_predone_flag,
    
    output  [31:0] long_data,
    output  reg    long_en,
    output  reg    long_done
    );
    
reg    [5:0]  long_addr;
reg    [1:0]  long_done_flag;
reg    [1:0]  long_cnt;

reg short_predone_flag_d1;
reg [5:0] long_addr_d1;
always@(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        short_predone_flag_d1 <= 1'b0;
    end else begin
        short_predone_flag_d1 <= short_predone_flag;
    end
end
wire long_start = short_predone_flag & ~short_predone_flag_d1;
wire long_rom_en = long_en || short_predone_flag; 
always@(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        long_en <= 1'b0;
    end else if(long_start) begin
        long_en <= 1'b1;
    end else if(long_done) begin
        long_en <= 1'b0;
    end else begin
        long_en <= long_en;
    end
end

always@(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        long_addr <= 6'd0;
        long_cnt  <= 2'd0;
    end
    else if(long_start) begin
        long_addr <= 6'd33;
        long_cnt  <= 2'd0;
    end
    else if(long_en == 1'b1)begin
        case(long_cnt)
            2'd0:
                if(long_addr == 6'd63)begin
                    long_addr <= 6'd0;
                    long_cnt  <= long_cnt+1'b1;
                end
                else begin
                    long_addr <= long_addr+1'd1;
                    long_cnt  <= long_cnt;
                end
            2'd1:
                if(long_addr == 6'd63)begin
                    long_addr <= 6'd0;
                    long_cnt  <= long_cnt+1'b1;
                end
                else begin
                    long_addr <= long_addr+1'd1;
                    long_cnt  <= long_cnt;
                end
            2'd2:
                if(long_addr == 6'd63)begin
                    long_addr <= 6'd63;
                end
                else begin
                    long_addr <= long_addr+1'd1;
                    long_cnt  <= 2'd2;
                end
            default:begin
                long_addr <= 6'd0;
                long_cnt  <= 2'd0;
            end
        endcase
    end
    else begin
        long_addr <= 6'd32;
        long_cnt  <= 2'd0;
    end
end

always@(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        long_done_flag<=2'b00;
    end
    else if(long_addr == 6'd61 && long_cnt==2'd2 && long_done_flag == 2'b00 && long_en == 1'b1)begin
        long_done_flag<=2'b10;
    end
    else if(long_done_flag==2'b10)begin
        long_done_flag<=2'b11;
    end
    else begin
        long_done_flag<=2'b00;
    end
end

always@(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        long_done<=1'b0;
        //long_done_pulse_d1 <= 1'b0;
    end
    else begin
        long_done<= (long_done_flag==2'b11);
        //long_done_pulse_d1 <= (long_done_flag==2'b11);
        //long_done <= long_done_pulse_d1;
    end
end

always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        long_addr_d1 <= 6'd0;
    else
        long_addr_d1 <= long_addr;
end

long_preamble   u_long_preamble(
  .clka(clk),
  .ena(long_rom_en),
  .addra(long_addr),
  .douta(long_data)
);

endmodule

新建一个preamble.v文件,用于对短训练序列和长训练序列进行衔接。

`timescale 1ns / 1ps

module preamble(
    input   clk,
    input   rst_n,
    
    output  reg [31:0] train_data, 
    output  reg        train_data_valid,
    output  reg        train_data_done
    );

wire [31:0] short_data;
wire [31:0] long_data;
wire        short_predone_flag;
wire        short_en;
wire        long_en;
wire        long_done;

reg         short_en_d1;
reg         short_en_d2;
reg         long_en_d1;
reg         long_done_d1;

always@(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        short_en_d1  <= 1'b0;
        short_en_d2  <= 1'b0;
        long_en_d1   <= 1'b0;
        long_done_d1 <= 1'b0;
    end else begin
        short_en_d1  <= short_en;
        short_en_d2  <= short_en_d1;
        long_en_d1   <= long_en;
        long_done_d1 <= long_done;
    end
end

always@(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        train_data <= 32'b0;
    end else if(long_en_d1) begin
        train_data <= long_data;
    end else if(short_en_d2) begin
        train_data <= short_data;
    end else begin
        train_data <= 32'b0;
    end
end

always@(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        train_data_valid <= 1'b0;
    end
    else if(short_en_d2 || long_en_d1) begin
        train_data_valid <= 1'b1;  
    end
    else begin
        train_data_valid <= 1'b0;
    end
end

always@(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        train_data_done <= 1'b0;
    end
    else begin
        train_data_done <= long_done_d1;
    end
end

preamble_short u_preamble_short(
    .clk               (clk               ),
    .rst_n             (rst_n             ),
    .short_data        (short_data        ),
    .short_predone_flag(short_predone_flag),
    .short_en          (short_en          )
);

preamble_long u_preamble_long(
    .clk               (clk               ),
    .rst_n             (rst_n             ),
    .short_predone_flag(short_predone_flag),
    .long_data         (long_data         ),
    .long_en           (long_en           ),
    .long_done         (long_done         )
);
ila_0  ila_preamble(
	.clk(clk), // input wire clk

	.probe0(train_data), // input wire [31:0]  probe0  
	.probe1(train_data_valid), // input wire [0:0]  probe1 
	.probe2(train_data_done), // input wire [0:0]  probe2 
	.probe3(short_en), // input wire [0:0]  probe3 
	.probe4(long_en), // input wire [0:0]  probe4 
	.probe5(short_predone_flag), // input wire [0:0]  probe5 
	.probe6(short_data), // input wire [31:0]  probe6 
	.probe7(long_data), // input wire [31:0]  probe7 
	.probe8(long_done) // input wire [3:0]  probe8
);
endmodule

在preamble.v中添加一个ila用于查看波形是否正确。

文件都进行保存后,点击生成比特流

然后在vivado中进行烧录。

设置触发条件后,点击开始进行捕捉。可以查看到波形触发

下面是长短训练衔接部分。

训练序列的末尾。

最终训练序列生成成功,短训练序列连续读取10次,共160个。长训练序列先读取后32个,再读取两次64个,共160个。总共320个训练序列。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值