module cal_det_par_32bit
#(
	parameter WIDTH = 32,
	parameter ENDMEMBER = 21
)
(
	// Input Ports
	input clk,rst,
	input req,
	input [(WIDTH*ENDMEMBER*ENDMEMBER-1):0] matrix_nueva,
	// Output Ports
	output [12:0] determinante,
	output reg ack
);

reg [(WIDTH*ENDMEMBER*ENDMEMBER-1):0] matrix_reg;
wire [(WIDTH*ENDMEMBER*ENDMEMBER-1):0] matrix_next;
reg [ENDMEMBER-1:0]en_matrix;
wire [WIDTH*ENDMEMBER-1:0]fila_switch;
reg sel_primero;
wire [WIDTH*ENDMEMBER-1:0]fila_ref;
wire [WIDTH*ENDMEMBER*(ENDMEMBER-1)-1:0] fila_procesada;
reg [(ENDMEMBER-1)*2-1:0]sel_fila;
wire [ENDMEMBER-2:0]req_procesar;
wire [ENDMEMBER-2:0]ack_procesada;
wire [4:0] index_i,i_menos_1;
reg en_i,sync_i;
reg en_fila_ref,en_fila_switch;
wire [4:0] index_switch;
wire [WIDTH*ENDMEMBER-1:0]diagonal;
reg req_log_sum;
wire ack_log_sum;
wire [WIDTH*ENDMEMBER-1:0]fila_analizada;
wire [ENDMEMBER-1:0] igual_cero;
reg req_inter;
wire ack_inter;
wire cero_det;
wire cambiar;
reg en_todo,en_procesar,en_cambiar;
wire [ENDMEMBER-1:0] en_procesar_total,en_cambiar_total,en_procesar_ref,en_procesar_switch,en_procesar_total_temp,en_procesar_switch_temp;
reg req_processing;
wire ack_processing;
reg [6:0] state_reg,state_next;
wire [4:0]index_switch_temp;

genvar i;

localparam 	state_idle 				= 	7'h01,
				state_intercambiador	=	7'h02,
				state_cambio			=	7'h04,
				state_registrar_ref	=	7'h08,
				state_procesar			=	7'h10,
				state_log_sum			=	7'h20,
				state_fin				=	7'h40;

always@(posedge clk,posedge rst)
begin
	if (rst)
		matrix_reg <= 5'h00;
	else
		matrix_reg <= matrix_next;
end

//Primera fila
mux2_en #(.WIDTH(WIDTH*ENDMEMBER)) stage_mux2_en
(
	.data_in_default(matrix_reg[(WIDTH*ENDMEMBER*ENDMEMBER-1):(WIDTH*ENDMEMBER*(ENDMEMBER-1))]),
	.data_in0(fila_switch),
	.data_in1(matrix_nueva[(WIDTH*ENDMEMBER*ENDMEMBER-1):(WIDTH*ENDMEMBER*(ENDMEMBER-1))]),
	.sel(sel_primero),
	.en(en_matrix[ENDMEMBER-1]),
	.data_out(matrix_next[(WIDTH*ENDMEMBER*ENDMEMBER-1):(WIDTH*ENDMEMBER*(ENDMEMBER-1))])
);

//El resto de filas
generate
for (i=0;i<=ENDMEMBER-2;i=i+1) begin :generate_filas

	mux4_en #(.WIDTH(WIDTH*ENDMEMBER)) stage_mux_matrix_next
	(
		.data_in_default(matrix_reg[WIDTH*ENDMEMBER*(i+1)-1:WIDTH*ENDMEMBER*i]),
		.data_in0(matrix_nueva[WIDTH*ENDMEMBER*(i+1)-1:WIDTH*ENDMEMBER*i]),
		.data_in1(fila_switch),
		.data_in2(fila_ref),
		.data_in3(fila_procesada[WIDTH*ENDMEMBER*(i+1)-1:WIDTH*ENDMEMBER*i]),
		.sel(sel_fila[(i+1)*2-1:i*2]),
		.en(en_matrix[i]),
		.data_out(matrix_next[WIDTH*ENDMEMBER*(i+1)-1:WIDTH*ENDMEMBER*i])
	);
	
	//Processing
	anulador1_32bit #(.WIDTH(WIDTH),.ENDMEMBER(ENDMEMBER)) stage_processing
	//anulador2 #(.WIDTH(WIDTH),.ENDMEMBER(ENDMEMBER)) stage_processing
	//anulador_de_columnas #(.WIDTH(WIDTH),.ENDMEMBER(ENDMEMBER)) stage_processing
	(
	.clk(clk),
	.rst(rst),
	.fila_nueva(matrix_reg[WIDTH*ENDMEMBER*(i+1)-1:WIDTH*ENDMEMBER*i]),
	.fila_ref(fila_ref),
	.req(req_procesar[i]),
	.i(index_i),
	.fila_sig(fila_procesada[WIDTH*ENDMEMBER*(i+1)-1:WIDTH*ENDMEMBER*i]),
	.ack(ack_procesada[i])
	);
end
endgenerate

//Index_i
i_register #(.ENDMEMBER(ENDMEMBER)) stage_i
(
	.clk(clk),
	.rst(rst),
	.en(en_i),
	.sync(sync_i),
	.i(index_i),
	.i_menos(i_menos_1)
);

////Seleccionador de filas
selecionador_fila #(.WIDTH(WIDTH),.ENDMEMBER(ENDMEMBER)) stage_fila_ref
(
	.clk(clk),
	.rst(rst),
	.en(en_fila_ref),
	.matrix(matrix_reg),
	.sel(index_i),
	.fila(fila_ref)
);
selecionador_fila #(.WIDTH(WIDTH),.ENDMEMBER(ENDMEMBER)) stage_fila_switch
(
	.clk(clk),
	.rst(rst),
	.en(en_fila_switch),
	.matrix(matrix_reg),
	.sel(index_switch),
	.fila(fila_switch)
);

generate
for (i=0;i<=ENDMEMBER-1;i=i+1) begin :generate_diagonal
	assign diagonal[WIDTH*(i+1)-1:WIDTH*i] = matrix_reg[(WIDTH*ENDMEMBER*i+WIDTH*i+WIDTH)-1:(WIDTH*ENDMEMBER*i+WIDTH*i)];
end
endgenerate

log_sum2_32bit #(.WIDTH(WIDTH),.ENDMEMBER(ENDMEMBER)) stage_log_sum
(
	.clk(clk),
	.rst(rst),
	.fila(diagonal),
	.req(req_log_sum),
	.result(determinante),
	.ack(ack_log_sum)
);

//intercambiador
generate
for (i=0;i<=ENDMEMBER-1;i=i+1) begin :generate_fila_a_analizar
	mux5bits #(.WIDTH(WIDTH)) stage_fila_i (.data_in(matrix_reg[WIDTH*ENDMEMBER*(i+1)-1:WIDTH*ENDMEMBER*i]),.sel(index_i),.data_out(fila_analizada[WIDTH*(i+1)-1:WIDTH*i]));
	assign igual_cero[i]=(fila_analizada[WIDTH*(i+1)-1:WIDTH*i]==16'h0000)?1'b1:1'b0;
end
endgenerate

intercambiador_filas2 #(.ENDMEMBER(ENDMEMBER)) stage_intercambiador_filas
(
	.clk(clk),
	.rst(rst),
	.req(req_inter),
	.fila(igual_cero),
	.index_i(index_i),
	.ack(ack_inter),
	.cero(cero_det),
	.cambiar(cambiar),
	.indice_cambiar(index_switch)
);

//assign index_switch_temp=index_switch+1;
assign index_switch_temp=index_switch;

decodificador5a32 stage_en_procesar_total (.data_in(i_menos_1),.data_out(en_procesar_total));
assign en_procesar_total_temp = en_procesar_total&{32{en_procesar}};
decodificador5a32_real stage_en_procesar_ref (.data_in(index_i),.data_out(en_procesar_ref));
decodificador5a32_real stage_en_procesar_switch (.data_in(index_switch_temp),.data_out(en_procesar_switch_temp));
assign en_procesar_switch= en_procesar_ref&{32{en_cambiar}};
assign en_cambiar_total= (en_procesar_ref|en_procesar_switch_temp)&{32{en_cambiar}};

always@(*)
begin
en_matrix=5'h00;
if (en_todo)
	en_matrix=32'hffffffffffffffff;
else if (en_procesar)
	en_matrix= en_procesar_total;
else if (en_cambiar)
	en_matrix=en_cambiar_total;
else
	en_matrix=5'h00;
end

//Selector del primero
always@(*)
begin
sel_primero=1'b0;
if (en_todo)
	sel_primero=1'b1;
else
	sel_primero=1'b0;
end

//selector de las fitas
generate
for (i=0;i<=ENDMEMBER-2;i=i+1) begin :generate_sel_filas
	always@(*)
	begin
		sel_fila[(i+1)*2-1:i*2]=2'h0;
		if (en_procesar_total_temp[i])
			sel_fila[(i+1)*2-1:i*2]=2'h3;
		else if (en_todo)
			sel_fila[(i+1)*2-1:i*2]=2'h0;
		else if (en_procesar_switch[i])
			sel_fila[(i+1)*2-1:i*2]=2'h1;
		else
			sel_fila[(i+1)*2-1:i*2]=2'h2;
	end
end
endgenerate

assign req_procesar=en_procesar_total&{32{req_processing}};
assign ack_processing= &((~req_procesar)|ack_procesada);
 
 //Registro
always@(posedge clk,posedge rst)
begin
	if (rst)
		state_reg <= 3'b000;
	else
		state_reg <= state_next;
end

always@(*)
begin
en_todo=1'b0;
en_procesar=1'b0;
en_cambiar=1'b0;
req_processing=1'b0;
en_i=1'b0;
sync_i=1'b0;
en_fila_ref=1'b0;
en_fila_switch=1'b0;
req_log_sum=1'b0;
req_inter=1'b0;
ack=1'b0;
state_next=state_reg;
case(state_reg)
state_idle:
begin
	if (req)
	begin
		en_todo=1'b1;
		en_i=1'b1;
		sync_i=1'b1;
		state_next=state_intercambiador;
	end
end
state_intercambiador:
begin
	req_inter=1'b1;
	en_fila_ref=1'b1;
	if (ack_inter)
	begin
		if (cero_det)
		begin
			state_next=state_fin;
		end
		else
		begin
			if (cambiar)
			begin
				en_fila_ref=1'b1;
				en_fila_switch=1'b1;
				state_next=state_cambio;
			end
			else
			begin
				en_fila_ref=1'b1;
				state_next=state_procesar;
			end
		end
	end
end
state_cambio:
begin
	en_cambiar=1'b1;
	state_next=state_registrar_ref;
end
state_registrar_ref:
begin
	en_fila_ref=1'b1;
	state_next=state_procesar;
end
state_procesar:
begin
	req_processing=1'b1;
	if (ack_processing)
	begin
		en_procesar=1'b1;
		en_i=1'b1;
		if (index_i==4'b0001)
		begin
			state_next=state_log_sum;
		end
		else
		begin
			state_next=state_intercambiador;
		end
	end
end
state_log_sum:
begin
	req_log_sum=1'b1;
	if (ack_log_sum)
	begin
		state_next=state_fin;
	end
end
state_fin:
begin
	ack=1'b1;
	if (~req)
	begin
		state_next=state_idle;
	end
end
default:
begin
	en_todo=1'b0;
	en_procesar=1'b0;
	en_cambiar=1'b0;
	req_processing=1'b0;
	en_i=1'b0;
	sync_i=1'b0;
	en_fila_ref=1'b0;
	en_fila_switch=1'b0;
	req_log_sum=1'b0;
	req_inter=1'b0;
	state_next=state_idle;
end
endcase
end

endmodule
