Pseudo-random number generator (LFSR), uniform to normal distribution using central-limit theorem not working correctly?

124 Views Asked by At

I'm having trouble generating normal random variables as part of a bigger project and need some help. First of all, yes, I am aware of AWGN methods, Box-Muller etc and other better random number generators and yes I have chosen a pretty ineffective RNG solution but this is my first big verilog project and I'm fine doing it the slow way because I'm trying to learn so please don't just respond by telling me all the different ways I should be going about this... Unless I'm doing something very wrong! Now, the problem.

The idea is by using a LFSR we can generate uniform pseudo-random variables and then via the central limit theorem if we sample the mean of many independent uniform variables we'll get a normal distribution.

I've read a similar VHDL solution that seemed relatively successful so I believe the method should work (not the most accurate though) - VHDL method

I believe my error is in failing to collect the mean of all 32 LFSR outputs each clock cycle as I've checked the waveforms of my LFSR module and that alone seems to be working fine but when I check the outputs in python I'm still getting a uniform distribution... not a normal one.Mean value distribution

Below is my module to accumulate the uniform pseudo-random variables from 32 LFSRs each clock cycle (with different seeds - "randomly" generated in python haha), and then take the average them (divide by 32).

`timescale 1ns / 1ps

module CLTuniformToNormal(output [31:0] o_Mean, input i_Clock, input i_Reset);
    reg [36:0] r_Accumulator = 0;
    wire [31:0] o_LFSR_Out[31:0];
    parameter c_SEED0 = 32'h2b5f712d; parameter c_SEED1 = 32'h1834a06b; parameter c_SEED2 = 32'h9c82b0a3; parameter c_SEED3 = 32'hf33e3930;
    parameter c_SEED4 = 32'hf5cc2528; parameter c_SEED5 = 32'h6b21a119; parameter c_SEED6 = 32'hf874c4ea; parameter c_SEED7 = 32'h8b5d0748;
    parameter c_SEED8 = 32'hdf4c754a; parameter c_SEED9 = 32'hc1cfb4b8; parameter c_SEED10 = 32'h2097bf95; parameter c_SEED11 = 32'h9bc45a55;
    parameter c_SEED12 = 32'hb49c15c4; parameter c_SEED13 = 32'h0a7b9340; parameter c_SEED14 = 32'h7f658627; parameter c_SEED15 = 32'hee57af5a;
    parameter c_SEED16 = 32'hd8b427bb; parameter c_SEED17 = 32'he793836a; parameter c_SEED18 = 32'hf4e3e39f; parameter c_SEED19 = 32'h2987b38b;
    parameter c_SEED20 = 32'h173ac4ce; parameter c_SEED21 = 32'hdb53d09e; parameter c_SEED22 = 32'h5af5fd7f; parameter c_SEED23 = 32'h1352a672;
    parameter c_SEED24 = 32'h2ab03e34; parameter c_SEED25 = 32'hdd03bb9b; parameter c_SEED26 = 32'h6b72eaca; parameter c_SEED27 = 32'hb5e22b2e;
    parameter c_SEED28 = 32'hfa232aea; parameter c_SEED29 = 32'h067db0f8; parameter c_SEED30 = 32'hed09559a; parameter c_SEED31 = 32'hfacf5eaa;
      
    // Instantiate 32 LFSR modules
    genvar i;
    generate
        for (i = 0; i < 32; i = i + 1) begin : INST_LFSR
            LFSR #(.c_SEED((i==0) ? c_SEED0 : (i==1) ? c_SEED1 : (i==2) ? c_SEED2 : (i==3) ? c_SEED3 : (i==4) ? c_SEED4 :
            (i==5) ? c_SEED5 : (i==6) ? c_SEED6 : (i==7) ? c_SEED7 : (i==8) ? c_SEED8 :
            (i==9) ? c_SEED9 : (i==10) ? c_SEED10 : (i==11) ? c_SEED11 : (i==12) ? c_SEED12 :
            (i==13) ? c_SEED13 : (i==14) ? c_SEED14 : (i==15) ? c_SEED15 : (i==16) ? c_SEED16 :
            (i==17) ? c_SEED17 : (i==18) ? c_SEED18 : (i==19) ? c_SEED19 : (i==20) ? c_SEED20 :
            (i==21) ? c_SEED21 : (i==22) ? c_SEED22 : (i==23) ? c_SEED23 : (i==24) ? c_SEED24 : 
            (i==25) ? c_SEED25 : (i==26) ? c_SEED26 : (i==27) ? c_SEED27 : (i==28) ? c_SEED28 :
            (i==29) ? c_SEED29 : (i==30) ? c_SEED30 : c_SEED31))
            LFSR_inst (
                .o_LFSR_Out(o_LFSR_Out[i]),
                .i_Clock(i_Clock),
                .i_Reset(r_Reset)
            );
        end
    endgenerate

    always @(posedge i_Clock or posedge i_Reset) begin
        if (i_Reset) begin
            r_Accumulator <= 0;
        end else begin
            // Accumulate outputs
            r_Accumulator <= r_Accumulator + o_LFSR_Out[0] + o_LFSR_Out[1] + o_LFSR_Out[2] + o_LFSR_Out[3] + o_LFSR_Out[4] + o_LFSR_Out[5] +
            o_LFSR_Out[6] + o_LFSR_Out[7] + o_LFSR_Out[8] + o_LFSR_Out[9] + o_LFSR_Out[10] +
            o_LFSR_Out[11] + o_LFSR_Out[12] + o_LFSR_Out[13] + o_LFSR_Out[14] + o_LFSR_Out[15] +
            o_LFSR_Out[16] + o_LFSR_Out[17] + o_LFSR_Out[18] + o_LFSR_Out[19] + o_LFSR_Out[20] +
            o_LFSR_Out[21] + o_LFSR_Out[22] + o_LFSR_Out[23] + o_LFSR_Out[24] + o_LFSR_Out[25] +
            o_LFSR_Out[26] + o_LFSR_Out[27] + o_LFSR_Out[28] + o_LFSR_Out[29] + o_LFSR_Out[30] +             
            o_LFSR_Out[31];
        end
    end

    // Calculate mean
    assign o_Mean = (r_Accumulator >> 5);// - 2**31; // Divide by 32 and shift mean to 0
    
endmodule

My testbench writes the o_Mean each clock cycle to a text file which I then plot in a histogram in python. Hope someone can see where I'm going wrong. Thanks.

1

There are 1 best solutions below

1
Sishmasquash On

Ah, I've realised I need to not add r_Accumulator to itself each clock cycle. Normal distribution!

Sorry, simple solution but have been scratching my head on this for a few hours.