`timescale 1ns / 1ps
module data_mover_bram
// Param
#(
	parameter CNT_BIT = 31,
// BRAM
	parameter DWIDTH = 32,
	parameter AWIDTH = 12,
	parameter MEM_SIZE = 4096,
	parameter IN_DATA_WITDH = 8
)

(
    input 				clk,
    input 				reset_n,
	input 				i_run,
	input  [CNT_BIT-1:0]	i_num_cnt,
	output   			o_idle,
	output   			o_read,
	output   			o_write,
	output  			o_done,

// Memory I/F (Read from bram0) // (lab21)  read from input node
	output[AWIDTH-1:0] 	addr_b0,
	output 				ce_b0,
	output 				we_b0,
	input [DWIDTH-1:0]  q_b0,
	output[DWIDTH-1:0] 	d_b0,

// Memory I/F (Read to bram1)   // (lab21)  read from weight
	output[AWIDTH-1:0] 	addr_b1,
	output 				ce_b1,
	output 				we_b1,
	input [DWIDTH-1:0]  q_b1,
	output[DWIDTH-1:0] 	d_b1,
	
	// Memory I/F (Read to bram2)   // Bias
	output[AWIDTH-1:0] 	addr_b2,
	output 				ce_b2,
	output 				we_b2,
	input [DWIDTH-1:0]  q_b2,
	output[DWIDTH-1:0] 	d_b2,

// (lab21) result for 4 core
	output[DWIDTH-1:0]  result_0,
	output[DWIDTH-1:0]  result_1,
	output[DWIDTH-1:0]  result_2,
	output[DWIDTH-1:0]  result_3
    );

/////// Local Param. to define state ////////
localparam S_IDLE	= 2'b00;
localparam S_RUN	= 2'b01;
localparam S_DONE  	= 2'b10;

/////// Type ////////
reg [1:0] c_state_read; // Current state  (F/F)
reg [1:0] n_state_read; // Next state (Variable in Combinational Logic)
reg [1:0] c_state_write; // Current state  (F/F)
reg [1:0] n_state_write; // Next state (Variable in Combinational Logic)
wire	  is_write_done;
wire	  is_read_done;

/////// Main ////////

// Step 1. always block to update state 
always @(posedge clk or negedge reset_n) begin
    if(!reset_n) begin
		c_state_read <= S_IDLE;
    end else begin
		c_state_read <= n_state_read;
    end
end

always @(posedge clk or negedge reset_n) begin
    if(!reset_n) begin
		c_state_write <= S_IDLE;
    end else begin
		c_state_write <= n_state_write;
    end
end

// Step 2. always block to compute n_state_read
//always @(c_state_read or i_run or is_done) 
always @(*) 
begin
	n_state_read = c_state_read; // To prevent Latch.
	case(c_state_read)
	S_IDLE	: if(i_run)
				n_state_read = S_RUN;
	S_RUN   : if(is_read_done)
				n_state_read = S_DONE;
	S_DONE	: n_state_read 	 = S_IDLE;
	endcase
end 

always @(*) 
begin
	n_state_write = c_state_write; // To prevent Latch.
	case(c_state_write)
	S_IDLE	: if(i_run)
				n_state_write = S_RUN;
	S_RUN   : if(is_write_done)
				n_state_write = S_DONE;
	S_DONE	: n_state_write   = S_IDLE;
	endcase
end 

// Step 3.  always block to compute output
// Added to communicate with control signals.
assign o_idle 		= (c_state_read == S_IDLE) && (c_state_write == S_IDLE);
assign o_read 		= (c_state_read == S_RUN);
assign o_write 		= (c_state_write == S_RUN);
assign o_done 		= (c_state_write == S_DONE); // The write state is slower than the read state.

// Step 4. Registering (Capture) number of Count
reg [CNT_BIT-1:0] num_cnt;  
always @(posedge clk or negedge reset_n) begin
    if(!reset_n) begin
        num_cnt <= 0;  
    end else if (i_run) begin
        num_cnt <= i_num_cnt;
	end else if (o_done) begin
		num_cnt <= 0;
	end
end

// Step 5. increased addr_cnt
reg [CNT_BIT-1:0] addr_cnt_read;  
reg [CNT_BIT-1:0] addr_cnt_write;
assign is_read_done  = o_read  && (addr_cnt_read == num_cnt-1);
assign is_write_done = o_write && (addr_cnt_write == num_cnt-1);

always @(posedge clk or negedge reset_n) begin
    if(!reset_n) begin
        addr_cnt_read <= 0;  
    end else if (is_read_done) begin
        addr_cnt_read <= 0; 
    end else if (o_read) begin
        addr_cnt_read <= addr_cnt_read + 1;
	end
end

wire result_valid;

always @(posedge clk or negedge reset_n) begin
    if(!reset_n) begin
        addr_cnt_write <= 0;  
    end else if (is_write_done) begin
        addr_cnt_write <= 0; 
    //end else if (o_write && we_b1) begin  // core delay
    end else if (o_write && result_valid) begin  // (lab21) core delay
        addr_cnt_write <= addr_cnt_write + 1;
	end
end

// Step 6. Read Data from BRAM0
// Assign Memory I/F. Read from BRAM0
assign addr_b0 	= addr_cnt_read;
assign ce_b0 	= o_read;
assign we_b0 	= 1'b0; // read only
assign d_b0		= {DWIDTH{1'b0}}; // no use

reg 				r_valid;
wire [DWIDTH-1:0] 	mem_data_0;

// 1 cycle latency to sync mem output
always @(posedge clk or negedge reset_n) begin
    if(!reset_n) begin
        r_valid <= {DWIDTH{1'b0}};  
    end else begin
		r_valid <= o_read; // read data
	end
end
assign mem_data_0 = q_b0;  

// (lab21) Step 6.1  Read Data from BRAM1 weight
// Assign Memory I/F. Read from BRAM0
assign addr_b1 	= addr_cnt_read;
assign ce_b1 	= o_read;
assign we_b1 	= 1'b0; // read only
assign d_b1		= {DWIDTH{1'b0}}; // no use

wire [DWIDTH-1:0] 	mem_data_1;

assign mem_data_1 = q_b1;

// Assign Memory I/F. Read Data from BRAM1 weight
assign addr_b2 	= addr_cnt_read;
assign ce_b2 	= o_read;
assign we_b2 	= 1'b0; // read only
assign d_b2		= {DWIDTH{1'b0}}; // no use

wire [DWIDTH-1:0] 	mem_data_2;

assign mem_data_2 = q_b2;

wire	[IN_DATA_WITDH-1:0]	    w_a_0	 	= mem_data_0[(4*IN_DATA_WITDH)-1:(3*IN_DATA_WITDH)];
wire	[IN_DATA_WITDH-1:0]	    w_b_0	 	= mem_data_1[(4*IN_DATA_WITDH)-1:(3*IN_DATA_WITDH)];
wire	[IN_DATA_WITDH-1:0]	    w_c_0	 	= mem_data_2[(4*IN_DATA_WITDH)-1:(3*IN_DATA_WITDH)];
wire	[(4*IN_DATA_WITDH)-1:0] w_result_0	;
wire 							w_valid_0 	;

wire	[IN_DATA_WITDH-1:0]	    w_a_1	 	= mem_data_0[(3*IN_DATA_WITDH)-1:(2*IN_DATA_WITDH)];
wire	[IN_DATA_WITDH-1:0]	    w_b_1	 	= mem_data_1[(3*IN_DATA_WITDH)-1:(2*IN_DATA_WITDH)];
wire	[IN_DATA_WITDH-1:0]	    w_c_1	 	= mem_data_2[(3*IN_DATA_WITDH)-1:(2*IN_DATA_WITDH)];
wire	[(4*IN_DATA_WITDH)-1:0] w_result_1	;
wire 							w_valid_1 	;

wire	[IN_DATA_WITDH-1:0]	    w_a_2	 	= mem_data_0[(2*IN_DATA_WITDH)-1:(1*IN_DATA_WITDH)];
wire	[IN_DATA_WITDH-1:0]	    w_b_2	 	= mem_data_1[(2*IN_DATA_WITDH)-1:(1*IN_DATA_WITDH)];
wire	[IN_DATA_WITDH-1:0]	    w_c_2	 	= mem_data_2[(2*IN_DATA_WITDH)-1:(1*IN_DATA_WITDH)];
wire	[(4*IN_DATA_WITDH)-1:0] w_result_2	;
wire 							w_valid_2 	;

wire	[IN_DATA_WITDH-1:0]	    w_a_3	 	= mem_data_0[(1*IN_DATA_WITDH)-1:(0*IN_DATA_WITDH)];
wire	[IN_DATA_WITDH-1:0]	    w_b_3	 	= mem_data_1[(1*IN_DATA_WITDH)-1:(0*IN_DATA_WITDH)];
wire	[IN_DATA_WITDH-1:0]	    w_c_3	 	= mem_data_2[(1*IN_DATA_WITDH)-1:(0*IN_DATA_WITDH)];
wire	[(4*IN_DATA_WITDH)-1:0] w_result_3	;
wire 							w_valid_3 	;

fully_connected_core
// Param
#(
	.IN_DATA_WITDH (IN_DATA_WITDH)
)
u_fully_connected_core_8b_0(
    .clk		(clk	 	),
    .reset_n	(reset_n 	),
	.i_run		(i_run		),
	.i_valid	(r_valid 	),
	.i_node		(w_a_0	 	),
	.i_wegt		(w_b_0	 	),
	.i_bias   (w_c_0    ),
	.o_result	(w_result_0	),
	.o_valid	(w_valid_0	)
);

fully_connected_core
// Param
#(
	.IN_DATA_WITDH (IN_DATA_WITDH)
)
u_fully_connected_core_8b_1(
    .clk		(clk	 	),
    .reset_n	(reset_n 	),
	.i_run		(i_run		),
	.i_valid	(r_valid 	),
	.i_node		(w_a_1	 	),
	.i_wegt		(w_b_1	 	),
	.i_bias     (w_c_1    ),
	.o_result	(w_result_1	),
	.o_valid	(w_valid_1	)
);

fully_connected_core
// Param
#(
	.IN_DATA_WITDH (IN_DATA_WITDH)
)
u_fully_connected_core_8b_2(
    .clk		(clk	 	),
    .reset_n	(reset_n 	),
	.i_run		(i_run		),
	.i_valid	(r_valid 	),
	.i_node		(w_a_2	 	),
	.i_wegt		(w_b_2	 	),
	.i_bias     (w_c_2    ),
	.o_result	(w_result_2	),
	.o_valid	(w_valid_2	)
);

fully_connected_core
// Param
#(
	.IN_DATA_WITDH (IN_DATA_WITDH)
)
u_fully_connected_core_8b_3(
    .clk		(clk	 	),
    .reset_n	(reset_n 	),
	.i_run		(i_run		),
	.i_valid	(r_valid 	),
	.i_node		(w_a_3	 	),
	.i_wegt		(w_b_3	 	),
	.i_bias     (w_c_3    ),
	.o_result	(w_result_3	),
	.o_valid	(w_valid_3	)
);

assign result_valid = w_valid_0 & w_valid_1 & w_valid_2 & w_valid_3;
assign result_0 	= w_result_0;
assign result_1 	= w_result_1;
assign result_2 	= w_result_2;
assign result_3 	= w_result_3;

//// (lab21) no use write.  Step 8. Write Data to BRAM1
//assign addr_b1 	= addr_cnt_write;
//assign ce_b1 	= result_valid;
//assign we_b1 	= result_valid;
//assign d_b1		= result_value;

//assign q_b1; // no use

endmodule