module SdfUnit #(
    parameter   N = 64,     //  Number of FFT Point
    parameter   M = 64,     //  Twiddle Resolution
    parameter   WIDTH = 16, //  Data Bit Length
    parameter   T4_EN = 0,  //  Use TwiddleConvert4
    parameter   T8_EN = 1,  //  Use TwiddleConvert8
    parameter   TW_FF = 1,  //  Use Twiddle Output Register
    parameter   TC_FF = 1,  //  Use TwiddleConvert Output Register
    parameter   B1_RH = 0,  //  1st Butterfly Round Half Up
    parameter   B2_RH = 1,  //  2nd Butterfly Round Half Up
    parameter   LP = 0      //  Power Saving
)(
    input               clock,  //  Master Clock
    input               reset,  //  Active High Asynchronous Reset
    input               di_en,  //  Input Data Enable
    input   [WIDTH-1:0] di_re,  //  Input Data (Real)
    input   [WIDTH-1:0] di_im,  //  Input Data (Imag)
    output              do_en,  //  Output Data Enable
    output  [WIDTH-1:0] do_re,  //  Output Data (Real)
    output  [WIDTH-1:0] do_im   //  Output Data (Imag)
);

//  log2 constant function
function integer log2;
    input integer x;
    integer value;
    begin
        value = x-1;
        for (log2=0; value>0; log2=log2+1)
            value = value>>1;
    end
endfunction

localparam  LOG_N = log2(N);    //  Bit Length of N
localparam  LOG_M = log2(M);    //  Bit Length of M

//  When using Twiddle Conversion, start counting 1T earlier
localparam  TC_EN = T4_EN | T8_EN;
localparam  EARLY = TC_EN & TW_FF & TC_FF;

//----------------------------------------------------------------------
//  Internal Regs and Nets
//----------------------------------------------------------------------
//  1st Butterfly
reg [LOG_N-1:0] di_count;   //  Input Data Count
wire            bf1_bf;     //  Butterfly Add/Sub Enable
wire[WIDTH-1:0] bf1_x0_re;  //  Data #0 to Butterfly (Real)
wire[WIDTH-1:0] bf1_x0_im;  //  Data #0 to Butterfly (Imag)
wire[WIDTH-1:0] bf1_x1_re;  //  Data #1 to Butterfly (Real)
wire[WIDTH-1:0] bf1_x1_im;  //  Data #1 to Butterfly (Imag)
wire[WIDTH-1:0] bf1_y0_re;  //  Data #0 from Butterfly (Real)
wire[WIDTH-1:0] bf1_y0_im;  //  Data #0 from Butterfly (Imag)
wire[WIDTH-1:0] bf1_y1_re;  //  Data #1 from Butterfly (Real)
wire[WIDTH-1:0] bf1_y1_im;  //  Data #1 from Butterfly (Imag)
wire[WIDTH-1:0] db1_di_re;  //  Data to DelayBuffer (Real)
wire[WIDTH-1:0] db1_di_im;  //  Data to DelayBuffer (Imag)
wire[WIDTH-1:0] db1_do_re;  //  Data from DelayBuffer (Real)
wire[WIDTH-1:0] db1_do_im;  //  Data from DelayBuffer (Imag)
wire[WIDTH-1:0] bf1_sp_re;  //  Single-Path Data Output (Real)
wire[WIDTH-1:0] bf1_sp_im;  //  Single-Path Data Output (Imag)
reg             bf1_sp_en;  //  Single-Path Data Enable
reg [LOG_N-1:0] bf1_count;  //  Single-Path Data Count
wire            bf1_start;  //  Single-Path Output Trigger
wire            bf1_end;    //  End of Single-Path Data
wire            bf1_mj;     //  Twiddle (-j) Enable
reg [WIDTH-1:0] bf1_do_re;  //  1st Butterfly Output Data (Real)
reg [WIDTH-1:0] bf1_do_im;  //  1st Butterfly Output Data (Imag)

//  2nd Butterfly
reg             bf2_bf;     //  Butterfly Add/Sub Enable
wire[WIDTH-1:0] bf2_x0_re;  //  Data #0 to Butterfly (Real)
wire[WIDTH-1:0] bf2_x0_im;  //  Data #0 to Butterfly (Imag)
wire[WIDTH-1:0] bf2_x1_re;  //  Data #1 to Butterfly (Real)
wire[WIDTH-1:0] bf2_x1_im;  //  Data #1 to Butterfly (Imag)
wire[WIDTH-1:0] bf2_y0_re;  //  Data #0 from Butterfly (Real)
wire[WIDTH-1:0] bf2_y0_im;  //  Data #0 from Butterfly (Imag)
wire[WIDTH-1:0] bf2_y1_re;  //  Data #1 from Butterfly (Real)
wire[WIDTH-1:0] bf2_y1_im;  //  Data #1 from Butterfly (Imag)
wire[WIDTH-1:0] db2_di_re;  //  Data to DelayBuffer (Real)
wire[WIDTH-1:0] db2_di_im;  //  Data to DelayBuffer (Imag)
wire[WIDTH-1:0] db2_do_re;  //  Data from DelayBuffer (Real)
wire[WIDTH-1:0] db2_do_im;  //  Data from DelayBuffer (Imag)
wire[WIDTH-1:0] bf2_sp_re;  //  Single-Path Data Output (Real)
wire[WIDTH-1:0] bf2_sp_im;  //  Single-Path Data Output (Imag)
reg             bf2_ct_en;  //  Single-Path Data Count Enable
reg [LOG_N-1:0] bf2_count;  //  Single-Path Data Count
wire            bf2_start_0t;// Single-Path Output Trigger
reg             bf2_start_1t;// Single-Path Output Trigger
wire            bf2_start;  //  Single-Path Output Trigger
wire            bf2_end;    //  End of Single-Path Data
reg             bf2_ct_en_1d;// Single-Path Data Enable When Using TC
wire            bf2_sp_en;  //  Single-Path Data Enable
reg [WIDTH-1:0] bf2_do_re;  //  2nd Butterfly Output Data (Real)
reg [WIDTH-1:0] bf2_do_im;  //  2nd Butterfly Output Data (Imag)
reg             bf2_do_en;  //  2nd Butterfly Output Data Enable

//  Multiplication
wire[1:0]       tw_sel;     //  Twiddle Select (2n/n/3n)
wire[LOG_N-3:0] tw_num;     //  Twiddle Number (n)
wire[LOG_N-1:0] tw_addr;    //  Twiddle Table Address
wire[LOG_N-1:0] tc_addr;    //  Twiddle Address from TwiddleConvert
wire[LOG_N-1:0] tw_addr_tc; //  Twiddle Address after TC_EN Switch
wire[WIDTH-1:0] tw_re;      //  Twiddle Data from Table (Real)
wire[WIDTH-1:0] tw_im;      //  Twiddle Data from Table (Imag)
wire[WIDTH-1:0] tc_re;      //  Twiddle Data from TwiddleConvert (Real)
wire[WIDTH-1:0] tc_im;      //  Twiddle Data from TwiddleConvert (Imag)
reg             tw_nz;      //  Multiplication Enable
reg             tw_nz_1d;   //  Multiplication Enable
wire            mu_en;      //  Multiplication Enable
wire[WIDTH-1:0] mu_a_re;    //  Multiplier Input (Real)
wire[WIDTH-1:0] mu_a_im;    //  Multiplier Input (Imag)
wire[WIDTH-1:0] mu_b_re;    //  Twiddle Data to Multiplier (Real)
wire[WIDTH-1:0] mu_b_im;    //  Twiddle Data to Multiplier (Imag)
wire[WIDTH-1:0] mu_m_re;    //  Multiplier Output (Real)
wire[WIDTH-1:0] mu_m_im;    //  Multiplier Output (Imag)
reg [WIDTH-1:0] mu_do_re;   //  Multiplication Output Data (Real)
reg [WIDTH-1:0] mu_do_im;   //  Multiplication Output Data (Imag)
reg             mu_do_en;   //  Multiplication Output Data Enable

//----------------------------------------------------------------------
//  1st Butterfly
//----------------------------------------------------------------------
always @(posedge clock or posedge reset) begin
    if (reset) begin
        di_count <= {LOG_N{1'b0}};
    end else begin
        di_count <= di_en ? (di_count + 1'b1) : {LOG_N{1'b0}};
    end
end
assign  bf1_bf = di_count[LOG_M-1];

//  Reducing signal changes may reduce power consumption
//  Set unknown value x for verification
assign  bf1_x0_re = bf1_bf ? db1_do_re : LP ? {WIDTH{1'b0}} : {WIDTH{1'bx}};
assign  bf1_x0_im = bf1_bf ? db1_do_im : LP ? {WIDTH{1'b0}} : {WIDTH{1'bx}};
assign  bf1_x1_re = bf1_bf ? di_re : LP ? {WIDTH{1'b0}} : {WIDTH{1'bx}};
assign  bf1_x1_im = bf1_bf ? di_im : LP ? {WIDTH{1'b0}} : {WIDTH{1'bx}};

Butterfly #(.WIDTH(WIDTH),.RH(B1_RH)) BF1 (
    .x0_re  (bf1_x0_re  ),  //  i
    .x0_im  (bf1_x0_im  ),  //  i
    .x1_re  (bf1_x1_re  ),  //  i
    .x1_im  (bf1_x1_im  ),  //  i
    .y0_re  (bf1_y0_re  ),  //  o
    .y0_im  (bf1_y0_im  ),  //  o
    .y1_re  (bf1_y1_re  ),  //  o
    .y1_im  (bf1_y1_im  )   //  o
);

DelayBuffer #(.DEPTH(2**(LOG_M-1)),.WIDTH(WIDTH)) DB1 (
    .clock  (clock      ),  //  i
    .di_re  (db1_di_re  ),  //  i
    .di_im  (db1_di_im  ),  //  i
    .do_re  (db1_do_re  ),  //  o
    .do_im  (db1_do_im  )   //  o
);

assign  db1_di_re = bf1_bf ? bf1_y1_re : di_re;
assign  db1_di_im = bf1_bf ? bf1_y1_im : di_im;
assign  bf1_sp_re = bf1_bf ? bf1_y0_re : bf1_mj ?  db1_do_im : db1_do_re;
assign  bf1_sp_im = bf1_bf ? bf1_y0_im : bf1_mj ? -db1_do_re : db1_do_im;

always @(posedge clock or posedge reset) begin
    if (reset) begin
        bf1_sp_en <= 1'b0;
        bf1_count <= {LOG_N{1'b0}};
    end else begin
        bf1_sp_en <= bf1_start ? 1'b1 : bf1_end ? 1'b0 : bf1_sp_en;
        bf1_count <= bf1_sp_en ? (bf1_count + 1'b1) : {LOG_N{1'b0}};
    end
end
assign  bf1_start = (di_count == (2**(LOG_M-1)-1));
assign  bf1_end = (bf1_count == (2**LOG_N-1));
assign  bf1_mj = (bf1_count[LOG_M-1:LOG_M-2] == 2'd3);

always @(posedge clock) begin
    bf1_do_re <= bf1_sp_re;
    bf1_do_im <= bf1_sp_im;
end

//----------------------------------------------------------------------
//  2nd Butterfly
//----------------------------------------------------------------------
always @(posedge clock) begin
    bf2_bf <= bf1_count[LOG_M-2];
end

//  Reducing signal changes may reduce power consumption
//  Set unknown value x for verification
assign  bf2_x0_re = bf2_bf ? db2_do_re : LP ? {WIDTH{1'b0}} : {WIDTH{1'bx}};
assign  bf2_x0_im = bf2_bf ? db2_do_im : LP ? {WIDTH{1'b0}} : {WIDTH{1'bx}};
assign  bf2_x1_re = bf2_bf ? bf1_do_re : LP ? {WIDTH{1'b0}} : {WIDTH{1'bx}};
assign  bf2_x1_im = bf2_bf ? bf1_do_im : LP ? {WIDTH{1'b0}} : {WIDTH{1'bx}};

//  Negative bias occurs when RH=0 and positive bias occurs when RH=1.
//  Using both alternately reduces the overall rounding error.
Butterfly #(.WIDTH(WIDTH),.RH(B2_RH)) BF2 (
    .x0_re  (bf2_x0_re  ),  //  i
    .x0_im  (bf2_x0_im  ),  //  i
    .x1_re  (bf2_x1_re  ),  //  i
    .x1_im  (bf2_x1_im  ),  //  i
    .y0_re  (bf2_y0_re  ),  //  o
    .y0_im  (bf2_y0_im  ),  //  o
    .y1_re  (bf2_y1_re  ),  //  o
    .y1_im  (bf2_y1_im  )   //  o
);

DelayBuffer #(.DEPTH(2**(LOG_M-2)),.WIDTH(WIDTH)) DB2 (
    .clock  (clock      ),  //  i
    .di_re  (db2_di_re  ),  //  i
    .di_im  (db2_di_im  ),  //  i
    .do_re  (db2_do_re  ),  //  o
    .do_im  (db2_do_im  )   //  o
);

assign  db2_di_re = bf2_bf ? bf2_y1_re : bf1_do_re;
assign  db2_di_im = bf2_bf ? bf2_y1_im : bf1_do_im;
assign  bf2_sp_re = bf2_bf ? bf2_y0_re : db2_do_re;
assign  bf2_sp_im = bf2_bf ? bf2_y0_im : db2_do_im;

always @(posedge clock or posedge reset) begin
    if (reset) begin
        bf2_ct_en <= 1'b0;
        bf2_count <= {LOG_N{1'b0}};
    end else begin
        bf2_ct_en <= bf2_start ? 1'b1 : bf2_end ? 1'b0 : bf2_ct_en;
        bf2_count <= bf2_ct_en ? (bf2_count + 1'b1) : {LOG_N{1'b0}};
    end
end

//  When using Twiddle Conversion, start counting 1T earlier
assign  bf2_start_0t = (bf1_count == (2**(LOG_M-2)-1)) & bf1_sp_en;
always @(posedge clock) begin
    bf2_start_1t <= bf2_start_0t;
end
assign  bf2_start = EARLY ? bf2_start_0t : bf2_start_1t;
assign  bf2_end = (bf2_count == (2**LOG_N-1));

always @(posedge clock) begin
    bf2_ct_en_1d <= bf2_ct_en;
end
assign  bf2_sp_en = EARLY ? bf2_ct_en_1d : bf2_ct_en;

always @(posedge clock) begin
    bf2_do_re <= bf2_sp_re;
    bf2_do_im <= bf2_sp_im;
end

always @(posedge clock or posedge reset) begin
    if (reset) begin
        bf2_do_en <= 1'b0;
    end else begin
        bf2_do_en <= bf2_sp_en;
    end
end

//----------------------------------------------------------------------
//  Multiplication
//----------------------------------------------------------------------
assign  tw_sel[1] = bf2_count[LOG_M-2];
assign  tw_sel[0] = bf2_count[LOG_M-1];
assign  tw_num = bf2_count << (LOG_N-LOG_M);
assign  tw_addr = tw_num * tw_sel;

Twiddle #(.TW_FF(TW_FF)) TW (
    .clock  (clock      ),  //  i
    .addr   (tw_addr_tc ),  //  i
    .tw_re  (tw_re      ),  //  o
    .tw_im  (tw_im      )   //  o
);

generate if (T4_EN) begin
    TwiddleConvert4 #(.LOG_N(LOG_N),.WIDTH(WIDTH),.TW_FF(TW_FF),.TC_FF(TC_FF)) TC (
        .clock      (clock  ),  //  i
        .tw_addr    (tw_addr),  //  i
        .tw_re      (tw_re  ),  //  i
        .tw_im      (tw_im  ),  //  i
        .tc_addr    (tc_addr),  //  o
        .tc_re      (tc_re  ),  //  o
        .tc_im      (tc_im  )   //  o
    );
end else if (T8_EN) begin
    TwiddleConvert8 #(.LOG_N(LOG_N),.WIDTH(WIDTH),.TW_FF(TW_FF),.TC_FF(TC_FF)) TC (
        .clock      (clock  ),  //  i
        .tw_addr    (tw_addr),  //  i
        .tw_re      (tw_re  ),  //  i
        .tw_im      (tw_im  ),  //  i
        .tc_addr    (tc_addr),  //  o
        .tc_re      (tc_re  ),  //  o
        .tc_im      (tc_im  )   //  o
    );
end else begin
    assign  tc_addr = {LOG_N{1'bx}};
    assign  tc_re = {WIDTH{1'bx}};
    assign  tc_im = {WIDTH{1'bx}};
end endgenerate

assign  tw_addr_tc = TC_EN ? tc_addr : tw_addr;
assign  mu_b_re = TC_EN ? tc_re : tw_re;
assign  mu_b_im = TC_EN ? tc_im : tw_im;

//  Multiplication is bypassed when twiddle address is 0.
always @(posedge clock) begin
    tw_nz <= (tw_addr != {LOG_N{1'b0}});
    tw_nz_1d <= tw_nz;
end
//  When using Twiddle Conversion, address generated 1T Earlier
assign  mu_en = EARLY ? tw_nz_1d : tw_nz;

//  Set unknown value x for verification
assign  mu_a_re = mu_en ? bf2_do_re : {WIDTH{1'bx}};
assign  mu_a_im = mu_en ? bf2_do_im : {WIDTH{1'bx}};

Multiply #(.WIDTH(WIDTH)) MU (
    .a_re   (mu_a_re),  //  i
    .a_im   (mu_a_im),  //  i
    .b_re   (mu_b_re),  //  i
    .b_im   (mu_b_im),  //  i
    .m_re   (mu_m_re),  //  o
    .m_im   (mu_m_im)   //  o
);

always @(posedge clock) begin
    mu_do_re <= mu_en ? mu_m_re : bf2_do_re;
    mu_do_im <= mu_en ? mu_m_im : bf2_do_im;
end

always @(posedge clock or posedge reset) begin
    if (reset) begin
        mu_do_en <= 1'b0;
    end else begin
        mu_do_en <= bf2_do_en;
    end
end

//  No multiplication required at final stage
assign  do_en = (LOG_M == 2) ? bf2_do_en : mu_do_en;
assign  do_re = (LOG_M == 2) ? bf2_do_re : mu_do_re;
assign  do_im = (LOG_M == 2) ? bf2_do_im : mu_do_im;

endmodule