赞
踩
基于XLINX公司生产的AX7035开发板,具有HDMI输出输出,可以满足在没有示波器条件下输入输出回环测试。项目中仅使用了ROM ip核用来存储查找表计算根号、对数、cos、sin,可以移植到其他任意开发中,但HDMI输出波形可能无法观测到,只能通过示波器显示。
设计内容主要分为两部分:高斯分布序列产生和HDMI显示。该项目侧重点是高斯白噪声产生,我主要介绍LFSR序列发生器和Box Muller转换设计思路。
该模块产生32位均匀分布序列,循环周期是2^64 = 1.8*10^19 。
利用64位斐波那契型LFSR,反馈多项式为 x^64 + x^63 + x^61 + x^60 + 1,可通过调用函数改变初始种子seed参数以产生不同的随机序列。
同时修改参数中的MIN和MAX大小设置输出随机序列的范围,理论上可以设置为-2,147,483,648——+2,147,483,648,也就是±2^31。
- module LFSR_fibonacci #(
- parameter seed = 64'd18446744070000000000,
- parameter MIN = 1,
- parameter MAX = 100
- )(
- input wire clk,
- input wire rst_n,
- input wire enable,
- output wire [31:0] rand_out
- );
- wire signed [63:0] rand64;
- wire signed [31:0] rand_out1;
- wire signed [31:0] rand_out2;
- reg [63:0] lfsr_reg;
-
- always @(posedge clk or posedge rst_n) begin
- if (!rst_n) begin
- lfsr_reg <= seed; // 初始值可以根据需要设置为其他值
- end else if (enable) begin
- // LFSR 反馈多项式为 x^64 + x^63 + x^61 + x^60 + 1
- lfsr_reg <= {lfsr_reg[62:0], lfsr_reg[63] ^ lfsr_reg[60] ^ lfsr_reg[61] ^ lfsr_reg[63]};
- end
- end
-
- assign rand64 = lfsr_reg;
- assign rand_out1 = rand64[47:16] + {rand64[15:0],rand64[63:48]};
- assign rand_out2 = rand64[31:0] + rand64[63:32];
- assign rand_out = (rand_out1 + rand_out2) % (MAX - MIN+1'b1) + MIN;
- endmodule
注意,我将LFSR设置为64位,而输出是经过64位随机序列变换得到,因此产生的随机序列周期是64位线性移位寄存器产生的伪随机数周期,但输出位数为32位。如果有对于资源要求比较高可以减小线性移位寄存器位数,通过查找响应位数特征多项式(不同位数有不同的特征多项式,这决定了产生随机序列的周期,使用特征多项式可以达到最大周期2……N-1)。
对于输出变换的解释,由LFSR直接产生的随机序列在一段时间内小数特别多,不符合我们对于均匀随机序列的要求,具体可编写测试文件观测到。因此,我对输出64位随机序列进行变换,这里我用两个rand_out1和rand_out2相加的结果取模,rand_out1和rand_out2分别是rand64取不同位数得到。当然,这里的方法不唯一,感兴趣的可以自行设计。
同时,需要注意的是,在输出级采用MAX和MIN对输出范围做了限制,输出的随机序列最大最小值均是有符号数!想要获得任意范围定点数可以自行另外设计输出逻辑。
更详细的LFSR设计原理和结构可以参照
https://cloud.tencent.com/developer/article/2287083?areaId=106001
Box-Muller transformation算法有两种形式,一种称之为基本形式:
其中U1,U2是均匀分布的随机数,Z1和Z2是满足高斯分布的随机数。可以看出,要得到高斯分布序列需要两个均匀分布序列,同时经过算数运算得到。
另一种表达形式是极坐标形式我没不使用,原因是极坐标形式Box-Muller转换虽然不涉及cos、sin运算,但引入了除法,这在FPGA设计中需要占用较多逻辑资源,我就使用基本形式对其变换。
利用使用ROM查找表,设计ln、根号、正余弦计算器。根据输入变量大小实现地址映射,考虑到设计精度等因素,对输入输出进行一定比例放大。对于cos只需要存储1/4周期的数据,其余数据均可通过地址映射得到这里不展开细讲。
- module cos(
- input clk,
- input [15:0] x_in,
- input enable,
-
- output [15:0] result_out
- );
- wire [9:0] addr_ROM;
- wire [13:0] ROM_out;
- cos_ROM cos_ROM_0(
- .clka (~clk),
- .ena (enable),
- .addra (addr_ROM),
-
- .douta (ROM_out)
- );
- wire [15:0] angle_3600;
-
- assign angle_3600 = (x_in[15] == 1'b1) ? ((~x_in[15:0]+1'b1) % 16'd3600) : (x_in[15:0] % 16'd3600);
- wire sign;
- wire [15:0] addr_angle;
- assign addr_angle = (angle_3600 <= 16'd900) ? (angle_3600 ) :
- (angle_3600 < 16'd1800) ? (16'd1800 - angle_3600) :
- (angle_3600 < 16'd2700) ? (angle_3600 - 16'd1800) :
- (angle_3600 < 16'd3600) ? (16'd3600 - angle_3600) : 16'd901;
- assign sign = (angle_3600 <= 16'd900) ? (1'b0 ) :
- (angle_3600 < 16'd1800) ? (1'b1 ) :
- (angle_3600 < 16'd2700) ? (1'b1 ) :
- (angle_3600 < 16'd3600) ? (1'b0 ) : 1'b0;
- assign addr_ROM = addr_angle[9:0];
- reg sign_delay0;
- always @(posedge clk) begin
- sign_delay0 <= sign;
- end
- assign result_out = sign_delay0 ? (~{2'b0,ROM_out} + 1'b1): {2'b0,ROM_out}; //
- endmodule
输入是角度制,同时需要将角度放大10倍,也就是3600是一个周期。输出16位有符号数,为是结果放大10000倍。
另外,是利用流水线的计算模式,ln和cos是第一级流水线,ln计算的结果乘以-2后再取根号,因此需要把cos计算的结果延迟一个节拍,再相乘输出。
Box Muller 转换流水线代码如下
- module Box_Muller_transform(
- input wire clk_data,
- input wire clk_cal,
- input wire enable,
- input wire signed [15:0] data_in_1,
- input wire signed [15:0] data_in_2,
-
- output reg signed [31:0] result_out_0,
- output reg signed [31:0] result_out_1
- );
- wire signed [15:0] cos_sin_in;
- wire signed [15:0] sqrt_in;
- wire signed [15:0] sqrt_out, ln_out, cos_out, sin_out;
- reg signed [15:0] cos_out_delay, sin_out_delay;
-
- reg signed [15:0] x_in_1, x_in_2;
- always @(posedge clk_data) begin
- if (enable) begin
- x_in_1 <= data_in_1;
- x_in_2 <= data_in_2;
- end
- end
- // instantiate sqrt module
- assign sqrt_in = ln_out * (-16'd2);
- sqrt sqrt_inst (
- .clk(clk_cal),
- .enable(enable),
- .x_in(sqrt_in),
- .result_out(sqrt_out)
- );
- // instantiate ln module
- ln ln_inst (
- .clk(clk_cal),
- .enable(enable),
- .x_in(x_in_1),
- .result_out(ln_out)
- );
- // instantiate cos module
- assign cos_sin_in = x_in_2 * 16'd36 / 16'd10;
- cos cos_inst (
- .clk(clk_cal),
- .enable(enable),
- .x_in(cos_sin_in),
- .result_out(cos_out)
- );
- sin sin_inst (
- .clk(clk_cal),
- .enable(enable),
- .x_in(cos_sin_in),
- .result_out(sin_out)
- );
- // delay cos result
- always @(posedge clk_cal) begin
- if (enable) begin
- cos_out_delay <= cos_out;
- sin_out_delay <= sin_out;
- end
- end
- // calculate final result using your formula
- always @(posedge clk_data) begin
- if (enable) begin
- result_out_0 <= cos_out_delay * sqrt_out;
- result_out_1 <= sin_out_delay * sqrt_out;
- end
- end
- endmodule
该模块例化了两个LFSR随机数生成模块(利用不同的种子)和一个Box Muller转换模块,将两个均匀分布序列转换为高斯分布序列。
同时,可以在模块参数中修改均值和方差,以达到产生任意分布正态分布的作用。默认为标准正态分布。
- /默认产生32位有符号数,结果除以1M为实际值
- module Guassian_generate #(
- parameter average = 32'd0, //均值
- parameter variance = 32'd0 //方差
- )(
- //input sys_clk,
- input clk_data,
- input clk_tra,
- input rst_n,
- input enable,
-
- debug
- //output wire [31:0] rand_out_1,
- //output wire [31:0] rand_out_2,
-
- output wire signed [31:0] rand_out1,
- output wire signed [31:0] rand_out2
- );
-
- wire [31:0] rand_out_1, rand_out_2;
- LFSR_fibonacci #(
- .seed(64'd18446744070000000000),
- .MIN(32'd1),
- .MAX(32'd1000)
- ) Uniform_Distribution_generator_0(
- .clk(clk_data),
- .rst_n(rst_n),
- .enable(1'b1),
- .rand_out(rand_out_1)
- );
- LFSR_fibonacci #(
- .seed(64'd14742147901846518232),
- .MIN(32'd1),
- .MAX(32'd1000)
- ) Uniform_Distribution_generator_1(
- .clk(clk_data),
- .rst_n(rst_n),
- .enable(1'b1),
- .rand_out(rand_out_2)
- );
- //将均匀分布输出数据转化为16位输入
- wire [15:0] tra_data_in_1, tra_data_in_2;
- assign tra_data_in_1 = rand_out_1[15:0];
- assign tra_data_in_2 = rand_out_2[15:0];
-
- wire signed [31:0] Guassian_0;
- wire signed [31:0] Guassian_1;
- Box_Muller_transform Box_Muller_transform(
- .clk_data(clk_data),
- .clk_cal(clk_tra),
- .enable(1'b1),
- .data_in_1(tra_data_in_1),
- .data_in_2(tra_data_in_2),
-
- .result_out_0(Guassian_0),
- .result_out_1(Guassian_1)
- );
- assign rand_out1 = enable ? ((Guassian_0[31] == 1'b1) ? (~((~Guassian_0 + 1'b1) / variance ) + 1'b1) + average : Guassian_0 / variance + average) : 32'd0;
- assign rand_out2 = enable ? ((Guassian_1[31] == 1'b1) ? (~((~Guassian_1 + 1'b1) / variance ) + 1'b1) + average : Guassian_1 / variance + average) : 32'd0;
- endmodule
注意该模块默认产生32位有符号数,结果除以1000000为实际值。这里实际值是标准正态分布的结果,因为根据Box Muller转换,输入均匀分布的范围是0-1,我利用LFSR产生1-1000的随机数,相当与输入放大了1000倍。
使用vivado内置仿真环境
matlab仿真
延长仿真时间生成10M个样本
LFSR仿真测试代码和MATLB验证代码同下正态高斯测试一样,只需稍加修改即可。
高斯序列产生测试Verilog代码
- `timescale 1ns / 1ps
- //
- // Company:
- // Engineer:
- //
- // Create Date: 2024/01/14 12:37:28
- // Design Name:
- // Module Name: Guassian_generate_tb
- // Project Name:
- // Target Devices:
- // Tool Versions:
- // Description:
- //
- // Dependencies:
- //
- // Revision:
- // Revision 0.01 - File Created
- // Additional Comments:
- //
- //
-
-
- module Guassian_generate_tb;
- reg clk_data;
- reg clk_tra;
- reg rst_n;
- reg enable;
-
- wire signed [31:0] rand_out1;
- Guassian_generate#(
- .average(32'd0),
- .variance(32'd0)
- )Guassian_generate_inst (
- .clk_data(clk_data),
- .clk_tra(clk_tra),
- .rst_n(rst_n),
- .enable(enable),
-
- .rand_out1(rand_out1)
- );
- wire [7:0] rand;
- assign rand = rand_out1[7:0];
- // Clock generation
- initial begin
- clk_tra = 0;
- forever #3 clk_tra = ~clk_tra;
- end
-
- initial begin
- clk_data = 0;
- #7;
- forever #10 clk_data = ~clk_data;
- end
- integer Guss0;
- //integer Guss1;
- initial begin
- Guss0=$fopen("randGuss0.txt"); //打开所创建的文件
- // Guss1=$fopen("randGuss1.txt"); //打开所创建的文件
- // rand0=$fopen("rand0.txt"); //打开所创建的文件
- // rand1=$fopen("rand1.txt"); //打开所创建的文件
- end
-
- always @(negedge clk_data) begin
- if(enable) begin
- $fdisplay(Guss0,"%d",$signed(rand_out1)); //$fdisplay(dout_file,"%d",$signed(num)); //保存有符号数据
- // $fdisplay(Guss1,"%d",$signed(Z_standard1)); //$fdisplay(dout_file,"%d",$signed(num)); //保存有符号数据
- // $fdisplay(rand0,"%d",$signed(rand_out_1)); //$fdisplay(dout_file,"%d",$signed(num)); //保存有符号数据
- // $fdisplay(rand1,"%d",$signed(rand_out_2)); //$fdisplay(dout_file,"%d",$signed(num)); //保存有符号数据
- end
- end
- // 在仿真结束时关闭文件
- initial begin
- #10000000; // 确保仿真足够长的时间
- $fclose(Guss0);
- // $fclose(Guss1);
- // $fclose(rand0);
- // $fclose(rand1);
- $stop;
- end
- // Testbench stimulus
- initial begin
- rst_n = 0;
- enable = 0;
-
- #100
- rst_n =1;
-
- #100
- enable = 1;
- // Finish simulation
- #10000000;
- end
- endmodule
将测试文件生成的随机数存储在randGuss0.txt文件中,再通过matlab读取进行分析。
可以看出产生的随机序列为一个标准的正态分布!
matlab测试代码如下。注:这里没有使用绝对路径查看txt文件,'randGuss0.txt'使用相对路径保存的文件再sim目录下,请自行查找。
- %生成rand64向量
- vivado_data_Guss0 = importdata('randGuss0.txt'); %read files
- % 统计数据分布
- minValue = min(vivado_data_Guss0);
- maxValue = max(vivado_data_Guss0);
- meanValue = mean(vivado_data_Guss0);
- medianValue = median(vivado_data_Guss0);
- stdDeviation = std(vivado_data_Guss0);
-
- % 显示统计结果
- fprintf('最小值:%f\n', minValue);
- fprintf('最大值:%f\n', maxValue);
- fprintf('平均值:%f\n', meanValue);
- fprintf('中位数:%f\n', medianValue);
- fprintf('标准差:%f\n', stdDeviation);
-
- % 绘制直方图
- figure(1)
- histogram(vivado_data_Guss0, 100, 'Normalization', 'probability');
- title('guss1数据分布直方图');
- xlabel('数值');
- ylabel('频率');
DC输出高斯分布序列AD采集,HDMI显示
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。