/*
    NCO module.
    The module implements CORDIC algorithm
*/

module CordicNco 
#(	parameter                   ODatWidth	= 18,
	parameter                   PhIncWidth	= 32,
	parameter                   IterNum		= 10,
	parameter                   EnSinN		= 0,
	parameter                   WinTypeW	= 0
)
(
    input	Clk_i,
    input	Rst_i,
    input	Val_i,
    input	[PhIncWidth-1:0]	PhaseInc_i,
	input	[WinTypeW-1:0]	WinType_i,
	input	WindVal_i,
	output	[ODatWidth-1:0]	Wind_o,
	output	[ODatWidth-1:0]	Sin_o,
	output	[ODatWidth-1:0]	Cos_o,
    output	reg	Val_o
);

//================================================================================
//  FUNCTIONS
//================================================================================
    function integer log2;
        input integer value;
        begin
            log2 = 0;
            while (value > 1) begin
                value   = value >> 1;
                log2    = log2 + 1;
            end
        end
    endfunction
//================================================================================
//  LOCALPARAMS
//================================================================================
	localparam  [PhIncWidth-1:0]	angle270	= 3<<(PhIncWidth-2);
	localparam  [PhIncWidth-1:0]	angle180	= 1<<(PhIncWidth-1);
	localparam  [PhIncWidth-1:0]	angle90		= 1<<(PhIncWidth-2);
	
	localparam [17:0] initValue = 18'd78498;
//================================================================================
//  REG/WIRE DECLARATIONS
//================================================================================
	
    wire	[PhIncWidth-1:0]	precompAngle[ODatWidth-1:0];   
    wire	[ODatWidth-1:0]		xPipe[IterNum:0];
    wire	[ODatWidth-1:0]		yPipe[IterNum:0];
    wire	[IterNum:0]			valPipe;
    reg		[PhIncWidth-1:0]	phaseDiffPipe[IterNum-1:0];
    reg		[2:0]				scwSignPipe[IterNum-1:0];

    reg		[PhIncWidth-1:0]	phaseAcc;
    reg     [PhIncWidth-1:0]	currPhase;
    reg		[2:0]				scwSignPrev;
    reg		[2:0]				scwSign;
    reg		[2:0]				valSr;

	reg		[ODatWidth-1:0]		sin_o;
	reg		[ODatWidth-1:0]		cos_o;
	reg		[ODatWidth-1:0]		wind_o;
    genvar	g;
    integer	i;
	
	reg		valR;
//================================================================================
//  ASSIGNMENTS
//================================================================================
    assign	xPipe[0]	=	(Val_i)	?	initValue:xPipe[0];
    assign	yPipe[0]	=	(Val_i)	?	initValue:yPipe[0];
    assign	valPipe[0]	=	valSr[2];
	assign	Wind_o		=	(WindVal_i&&WinType_i==0)	?	wind_o:14'b0;

	assign precompAngle[0] = 32'd536870912;
	assign precompAngle[1] = 32'd316933406;
	assign precompAngle[2] = 32'd167458907;
	assign precompAngle[3] = 32'd85004756;
	assign precompAngle[4] = 32'd42667331;
	assign precompAngle[5] = 32'd21354465;
	assign precompAngle[6] = 32'd10679838;
	assign precompAngle[7] = 32'd5340245;
	assign precompAngle[8] = 32'd2670163;
	assign precompAngle[9] = 32'd1335087;
	assign precompAngle[10] = 32'd667544;
	assign precompAngle[11] = 32'd333772;
	assign precompAngle[12] = 32'd166886;
	assign precompAngle[13] = 32'd83443;
	// assign precompAngle[14] = 32'd41722;
	// assign precompAngle[15] = 32'd20861;
	// assign precompAngle[16] = 32'd10430;
	// assign precompAngle[17] = 32'd5215;
	//assign precompAngle[18] = 32'd2608;

	assign	Sin_o	=	WindVal_i	?	sin_o	:	14'h0;
	assign	Cos_o	=	WindVal_i	?	cos_o	:	14'h0;
//================================================================================
//  CODING
//================================================================================
always @(posedge Clk_i) begin
    if (Rst_i) begin
        valR	<=	1'b0;
    end else begin
		valR	<=	Val_i;
	end
end

//  Phase handle logic
always @(posedge Clk_i) begin
    if (Rst_i) begin
        phaseAcc   <= {PhIncWidth{1'b0}};
    end else if (Val_i) begin
        phaseAcc   <= phaseAcc + PhaseInc_i;
    end	else	begin
		phaseAcc   <= {PhIncWidth{1'b0}};
	end
end

always @(posedge Clk_i) begin
    if (Rst_i) begin
        currPhase   <= {PhIncWidth{1'b0}};
        scwSign         <= 3'b0;
    end else begin
        if (phaseAcc > angle270) begin
            currPhase   <= {PhIncWidth{1'b0}} - phaseAcc;
            scwSign         <= 3'b010;
        end else if (phaseAcc > angle180) begin
            currPhase   <= phaseAcc - angle180;
            scwSign         <= 3'b011;
        end else if (phaseAcc > angle90) begin
            currPhase   <= angle180 - phaseAcc;
            scwSign         <= 3'b001;
        end else begin
            currPhase   <= phaseAcc;
            scwSign         <= 3'b000;
        end
    end
end

//--------------------------------------------------------------------------------
//  CORDIC pipe

always @(posedge Clk_i) begin
    if (Rst_i) begin
        valSr <= 3'b0;
    end else if	(Val_i)	begin
        valSr <= {valSr[1:0], Val_i};
    end	else	begin
		valSr <= 3'b0;
	end
end

always @(posedge Clk_i) begin
    phaseDiffPipe[0]  <= currPhase - precompAngle[0];
    scwSignPipe[0]     <= scwSign;
    for(i=1; i<IterNum; i=i+1) begin
        scwSignPipe[i] <= scwSignPipe[i-1];
        if (phaseDiffPipe[i-1][PhIncWidth-1]) begin
            phaseDiffPipe[i] <= phaseDiffPipe[i-1] + precompAngle[i];
        end else begin
            phaseDiffPipe[i] <= phaseDiffPipe[i-1] - precompAngle[i];
        end
    end
end

generate
    for (g = 0; g < IterNum; g = g + 1) begin : cordic_pipe
        cordic_rotation #(
            .ODatWidth	(ODatWidth),
            .Shift      (g+1)
        ) cordic_rotation_inst (
            .Clk_i      (Clk_i),
            .Rst_i      (Rst_i),
			.X_i        (xPipe[g]),
			.Y_i        (yPipe[g]),
			.Val_i      (valPipe[g]),
			.Sign_i     (phaseDiffPipe[g][PhIncWidth-1]),
			.X_o        (xPipe[g+1]),
			.Y_o        (yPipe[g+1]),
			.Val_o      (valPipe[g+1])
		);
    end
endgenerate

//--------------------------------------------------------------------------------
//  Output logic

generate 
    if (EnSinN) begin
        always @(posedge Clk_i) begin
            if (Rst_i) begin
                sin_o       <= {ODatWidth{1'b0}};
            end else begin
                if (scwSignPrev[1]) begin
                    sin_o   <=  yPipe[IterNum];
                end else begin
                    sin_o   <= ~yPipe[IterNum] + {{(ODatWidth-1){1'b0}}, 1'b1};
                end
            end
        end
    end else begin
        always @(posedge Clk_i) begin
            if (Rst_i) begin
                sin_o       <= {ODatWidth{1'b0}};
            end else begin
				if (scwSignPrev[1]) begin
					sin_o   <= ~yPipe[IterNum] + {{(ODatWidth-1){1'b0}}, 1'b1};
				end else begin
					sin_o   <=  yPipe[IterNum];
				end
            end
        end
		always @(posedge Clk_i) begin
            if (Rst_i) begin
                wind_o       <= {ODatWidth{1'b0}};
            end else begin
				if (scwSignPrev[2]) begin
					wind_o   <= ~yPipe[IterNum] + {{(ODatWidth-1){1'b0}}, 1'b1};
				end else begin
					wind_o   <=  yPipe[IterNum];
				end
            end
        end
    end
endgenerate

always @(posedge Clk_i) begin
    if (Rst_i) begin
        cos_o		<= {ODatWidth{1'b0}};
        scwSignPrev	<= 3'b0;
        Val_o		<= 1'b0;
    end else begin
        if (scwSignPrev[0]) begin
            cos_o	<= ~xPipe[IterNum] + {{(ODatWidth-1){1'b0}}, 1'b1};
        end else begin
            cos_o	<= xPipe[IterNum];
        end
		scwSignPrev	<= scwSignPipe[IterNum-1];
		Val_o		<= valPipe[0];
    end	
end
endmodule
