// To include other Verilog files within a specified directory:
// cpp_verilator_include_dir:

// To specify Verilator command line options:
// cpp_verilator_options:

// To run a specified shell command before Verilator is run on this module:
// cpp_verilator_pre_command: --top-module ds_dt_ord2_candy_mbit_ver

// -> Note that the above statements must be preceeded with '//' as shown

`timescale 1ns/10ps

module ds_dt_ord2_candy_mbit_ver(in, rstb, clk, valid_in, out, valid_out);

parameter wid_in = 24;
parameter wid_out = 5;
parameter signed_out = 0;

input [31:0] in;
input rstb;
input clk;
input valid_in;
output [31:0] out;
output valid_out;

wire [wid_in-1:0] in_w;
wire [wid_out-1:0] out_w;

sat_limits_ver #(.p_iwid(32),.p_owid(wid_in)) inst_in_sat(.in(in),.out(in_w));
   
assign out = (signed_out == 0 || wid_out == 1) ? {{(32-wid_out){1'b0}},out_w} : {{(32-wid_out){out_w[wid_out-1]}},out_w};
   
ds_ord2_candy_mbit_ver_core #(wid_in,wid_out,signed_out) inst_ds(in_w, valid_in, clk, rstb, out_w, valid_out);
   
endmodule

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

module ds_ord2_candy_mbit_ver_core #(parameter p_iwid=16,p_owid=5,p_osigned=0) (
 input signed [p_iwid-1:0]  in,
 input 			    i_valid,
 input 			    clk,
 input 			    rstb,
 output signed [p_owid-1:0] out,
 output reg		    o_valid
);

reg signed [p_iwid-1:0] quant_reg;      
   
wire signed [p_iwid+1-1:0] error1;
wire signed [p_iwid+2-1:0] error2;   
wire signed [p_iwid+1-1:0] accum1;
wire signed [p_iwid+2-1:0] accum2;
wire signed [p_iwid-1:0] accum2_sat;
wire signed [p_owid-1:0] out_raw;
wire assert_quant_lsb_bits;
   

assign error1 = in - quant_reg;
assign error2 = accum1 - 2*quant_reg;
assign out_raw = quant_reg[p_iwid-1:p_iwid-p_owid];
assign out = (p_osigned == 0 || p_owid == 1) ? (out_raw + {1'b1,{(p_owid-1){1'b0}}}) : out_raw;
assign assert_quant_lsb_bits = (p_owid == 1 && accum2_sat[p_iwid-1] == 1'b0) || (p_owid > 1 && accum2[p_iwid+2-1] == 1'b0 && (accum2[p_iwid+2-2] == 1'b1 || accum2[p_iwid+2-3] == 1'b1));

always@(posedge clk or negedge rstb) begin
  if (!rstb) begin
     quant_reg <= {(p_iwid){1'b0}};
     o_valid <= 1'b0;
  end
  else if (i_valid) begin
     quant_reg <= {accum2_sat[p_iwid-1:p_iwid-p_owid],{(p_iwid-p_owid){assert_quant_lsb_bits}}};
     o_valid <= 1'b1;
  end
  else begin
     o_valid <= 1'b0;
  end
end

// accumulators   
accumulator_ver #(.p_iwid(p_iwid+1),.gain_shift(0),.no_delay(0)) inst_accum1(.clk(clk),.in_valid(i_valid),.rstb(rstb),.in(error1),.out(accum1));
accumulator_ver #(.p_iwid(p_iwid+2),.gain_shift(0),.no_delay(1)) inst_accum2(.clk(clk),.in_valid(i_valid),.rstb(rstb),.in(error2),.out(accum2));
   
// apply saturation limits to accum2
sat_limits_ver #(.p_iwid(p_iwid+2), .p_owid(p_iwid)) inst_error2_sat (.in(accum2),.out(accum2_sat));   
   
endmodule

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

module accumulator_ver #(parameter p_iwid=24,gain_shift=3,no_delay=0) (
 input signed [p_iwid-1:0] in,
 input in_valid,
 input clk,
 input rstb,
 output signed [p_iwid-1:0] out
);

wire signed [p_iwid+gain_shift-1:0 ] in_with_gain;
wire signed [p_iwid+gain_shift:0  ] in_out_sum;
wire signed [p_iwid+gain_shift-1:0] sum_sat;
reg  signed [p_iwid+gain_shift-1:0] sum_sat_reg;

assign in_with_gain = {{gain_shift{in[p_iwid-1]}},in};
assign in_out_sum   =  in_with_gain + sum_sat_reg;

// apply saturation limits
sat_limits_ver #(.p_iwid(p_iwid+1+gain_shift), .p_owid(p_iwid+gain_shift)) u_accum_w_dealy_offset_sat (.in(in_out_sum),.out(sum_sat));

//---------
always@(posedge clk or negedge rstb) begin
  if(!rstb)
    sum_sat_reg<={(p_iwid+gain_shift){1'b0}};
  else if (in_valid)
    sum_sat_reg<=sum_sat;
end 

//output
assign out = (no_delay == 0) ? sum_sat_reg[p_iwid+gain_shift-1:gain_shift] : sum_sat[p_iwid+gain_shift-1:gain_shift];

endmodule

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

module sat_limits_ver #(parameter p_iwid=1,p_owid=0) (
 input [p_iwid-1:0] in,
 output [p_owid-1:0] out
);
   
//create full scale positive and negative saturation limits        
wire [p_owid-1:0] sat_pos_limit = ~(1'b1 << (p_owid-1)); // sat_pos_limit:  0111111...
wire [p_owid-1:0] sat_neg_limit = (1'b1 << (p_owid-1));  // sat_neg_limit:  1000000...

//selects the bits that are to be checked for saturation limits
wire [(p_iwid-2):(p_owid-1)] sat_check_bits = in[(p_iwid-2):(p_owid-1)];

//determines the sign bit
wire sign_bit = in[p_iwid-1];

//determines if positive or a negative saturation limit should apply 
wire limitPos = ((|(sat_check_bits)) && !sign_bit);
wire limitNeg = (!(&(sat_check_bits)) && sign_bit);

// output with saturation limits applied
assign out = limitPos ? sat_pos_limit : limitNeg ? sat_neg_limit : in[p_owid-1:0];

endmodule

