module parser_top #(
	parameter C_S_AXIS_DATA_WIDTH = 512,
	parameter C_S_AXIS_TUSER_WIDTH = 128,
	parameter PKT_HDR_LEN = (6+4+2)*8*8+256, // check with the doc
	parameter PARSER_MOD_ID = 3'b0,
	parameter C_NUM_SEGS = 2,
	parameter C_VLANID_WIDTH = 12,
	parameter C_PARSER_RAM_WIDTH = 160
)
(
	input									axis_clk,
	input									aresetn,

	// input slvae axi stream
	input [C_S_AXIS_DATA_WIDTH-1:0]			s_axis_tdata,
	input [C_S_AXIS_TUSER_WIDTH-1:0]		s_axis_tuser,
	input [C_S_AXIS_DATA_WIDTH/8-1:0]		s_axis_tkeep,
	input									s_axis_tvalid,
	input									s_axis_tlast,
	// input vlan info
	input [C_VLANID_WIDTH-1:0]				s_vlan_id,
	input									s_vlan_id_valid,

	// output
	output reg								parser_valid,
	output reg [PKT_HDR_LEN-1:0]			pkt_hdr_vec,

	// back-pressure signals
	output									s_axis_tready,
	input									stg_ready_in,

	// output vlan
	output reg[C_VLANID_WIDTH-1:0]				out_vlan,
	output reg									out_vlan_valid,
	input									out_vlan_ready,

	// output to different pkt fifo queues (i.e., data cache)
	output [C_S_AXIS_DATA_WIDTH-1:0]		m_axis_tdata_0,
	output [C_S_AXIS_TUSER_WIDTH-1:0]		m_axis_tuser_0,
	output [C_S_AXIS_DATA_WIDTH/8-1:0]		m_axis_tkeep_0,
	output									m_axis_tlast_0,
	output									m_axis_tvalid_0,
	input									m_axis_tready_0,

	output [C_S_AXIS_DATA_WIDTH-1:0]		m_axis_tdata_1,
	output [C_S_AXIS_TUSER_WIDTH-1:0]		m_axis_tuser_1,
	output [C_S_AXIS_DATA_WIDTH/8-1:0]		m_axis_tkeep_1,
	output									m_axis_tlast_1,
	output									m_axis_tvalid_1,
	input									m_axis_tready_1,

	output [C_S_AXIS_DATA_WIDTH-1:0]		m_axis_tdata_2,
	output [C_S_AXIS_TUSER_WIDTH-1:0]		m_axis_tuser_2,
	output [C_S_AXIS_DATA_WIDTH/8-1:0]		m_axis_tkeep_2,
	output									m_axis_tlast_2,
	output									m_axis_tvalid_2,
	input									m_axis_tready_2,

	output [C_S_AXIS_DATA_WIDTH-1:0]		m_axis_tdata_3,
	output [C_S_AXIS_TUSER_WIDTH-1:0]		m_axis_tuser_3,
	output [C_S_AXIS_DATA_WIDTH/8-1:0]		m_axis_tkeep_3,
	output									m_axis_tlast_3,
	output									m_axis_tvalid_3,
	input									m_axis_tready_3,

	// ctrl path
	input [C_S_AXIS_DATA_WIDTH-1:0]			ctrl_s_axis_tdata,
	input [C_S_AXIS_TUSER_WIDTH-1:0]		ctrl_s_axis_tuser,
	input [C_S_AXIS_DATA_WIDTH/8-1:0]		ctrl_s_axis_tkeep,
	input									ctrl_s_axis_tvalid,
	input									ctrl_s_axis_tlast,

	output reg [C_S_AXIS_DATA_WIDTH-1:0]		ctrl_m_axis_tdata,
	output reg [C_S_AXIS_TUSER_WIDTH-1:0]		ctrl_m_axis_tuser,
	output reg [C_S_AXIS_DATA_WIDTH/8-1:0]		ctrl_m_axis_tkeep,
	output reg									ctrl_m_axis_tvalid,
	output reg									ctrl_m_axis_tlast
);

wire [C_NUM_SEGS*C_S_AXIS_DATA_WIDTH-1:0]	segs_in;
reg [C_NUM_SEGS*C_S_AXIS_DATA_WIDTH-1:0]	segs_in_r;
wire [C_S_AXIS_TUSER_WIDTH-1:0]				tuser_1st_in;
reg [C_S_AXIS_TUSER_WIDTH-1:0]				tuser_1st_in_r;
wire										segs_in_valid;
reg											segs_in_valid_r;



// paser bram out
wire [C_PARSER_RAM_WIDTH-1:0]	bram_out_0;
reg [C_PARSER_RAM_WIDTH-1:0]	bram_out_0_d1;
reg								out_bram_valid, out_bram_valid_d1;
reg bram_ready, bram_ready_next;


assign s_axis_tready = bram_ready
						&& m_axis_tready_0
						&& m_axis_tready_1
						&& m_axis_tready_2
						&& m_axis_tready_3;
//

wire [3:0] m_axis_tready_queue;

assign m_axis_tready_queue[0] = m_axis_tready_0;
assign m_axis_tready_queue[1] = m_axis_tready_1;
assign m_axis_tready_queue[2] = m_axis_tready_2;
assign m_axis_tready_queue[3] = m_axis_tready_3;


localparam	IDLE=0,
			FLUSH_REST_PKTS=1;
reg [1:0] state, state_next;
reg [1:0] cur_queue, cur_queue_next;
wire [1:0] cur_queue_plus1;

assign cur_queue_plus1 = (cur_queue==3)?0:cur_queue+1;

// ==================================================
assign m_axis_tdata_0 = s_axis_tdata;
assign m_axis_tuser_0 = s_axis_tuser;
assign m_axis_tkeep_0 = s_axis_tkeep;
assign m_axis_tlast_0 = s_axis_tlast;
assign m_axis_tvalid_0 = (cur_queue==0?1:0) & s_axis_tvalid & m_axis_tready_0;

assign m_axis_tdata_1 = s_axis_tdata;
assign m_axis_tuser_1 = s_axis_tuser;
assign m_axis_tkeep_1 = s_axis_tkeep;
assign m_axis_tlast_1 = s_axis_tlast;
assign m_axis_tvalid_1 = (cur_queue==1?1:0) & s_axis_tvalid & m_axis_tready_1;

assign m_axis_tdata_2 = s_axis_tdata;
assign m_axis_tuser_2 = s_axis_tuser;
assign m_axis_tkeep_2 = s_axis_tkeep;
assign m_axis_tlast_2 = s_axis_tlast;
assign m_axis_tvalid_2 = (cur_queue==2?1:0) & s_axis_tvalid & m_axis_tready_2;

assign m_axis_tdata_3 = s_axis_tdata;
assign m_axis_tuser_3 = s_axis_tuser;
assign m_axis_tkeep_3 = s_axis_tkeep;
assign m_axis_tlast_3 = s_axis_tlast;
assign m_axis_tvalid_3 = (cur_queue==3?1:0) & s_axis_tvalid & m_axis_tready_3;
// ==================================================


always @(*) begin
	state_next = state;
	cur_queue_next = cur_queue;

	case (state)
		IDLE: begin
			if (s_axis_tvalid) begin
				if (m_axis_tready_queue[cur_queue]) begin

					if (!s_axis_tlast) begin
						state_next = FLUSH_REST_PKTS;
					end
					else begin
						cur_queue_next = cur_queue_plus1;
					end
				end
			end
		end
		FLUSH_REST_PKTS: begin
			if (s_axis_tvalid) begin
				if (m_axis_tready_queue[cur_queue]) begin

					if (s_axis_tlast) begin
						cur_queue_next = cur_queue_plus1;
						state_next = IDLE;
					end
				end
			end
		end
	endcase
end

always @(posedge axis_clk) begin
	if (~aresetn) begin
		state <= IDLE;
		cur_queue <= 0;
	end
	else begin
		state <= state_next;
		cur_queue <= cur_queue_next;
	end
end

// ==================================================

wire [C_VLANID_WIDTH-1:0]				parser_out_vlan, vlan_fifo_out;
wire									parser_out_vlan_valid;
// wire	vlan_fifo_empty, vlan_fifo_nearly_full;
reg [C_VLANID_WIDTH-1:0] out_vlan_next;
reg out_vlan_valid_next;
// reg		vlan_fifo_rd_en;

// wire [PKT_HDR_LEN-1:0]		phv_fifo_out;
// wire	phv_fifo_empty, phv_fifo_nearly_full;
// reg		phv_fifo_rd_en;



localparam P_IDLE=0;

reg [1:0] p_state, p_state_next;
reg [1:0] p_cur_queue, p_cur_queue_next;
wire [1:0] p_cur_queue_plus1;

assign p_cur_queue_plus1 = (p_cur_queue==3)?0:p_cur_queue+1;

wire [3:0] p_cur_queue_val;
assign p_cur_queue_val[0] = (p_cur_queue==0)?1:0;
assign p_cur_queue_val[1] = (p_cur_queue==1)?1:0;
assign p_cur_queue_val[2] = (p_cur_queue==2)?1:0;
assign p_cur_queue_val[3] = (p_cur_queue==3)?1:0;

wire parser_valid_w;
wire [PKT_HDR_LEN-1:0] pkt_hdr_vec_w;
reg [PKT_HDR_LEN-1:0] pkt_hdr_vec_next, pkt_hdr_vec_r;
reg parser_valid_next, parser_valid_r;

/*
always @(*) begin
	p_state_next = p_state;
	p_cur_queue_next = p_cur_queue;
	
	pkt_hdr_vec_next = pkt_hdr_vec;
	parser_valid_next = 0;
	case (p_state)
		P_IDLE: begin
			if (parser_valid_w) begin
				pkt_hdr_vec_next = {pkt_hdr_vec_w[PKT_HDR_LEN-1:145], p_cur_queue_val, pkt_hdr_vec_w[0+:141]};
				parser_valid_next = 1;

				p_cur_queue_next = p_cur_queue_plus1;
			end
		end
	endcase
end*/

always @(*) begin
	p_cur_queue_next = p_cur_queue;
	pkt_hdr_vec_next = pkt_hdr_vec_r;
	parser_valid_next = 0;

	if (parser_valid_w) begin
		pkt_hdr_vec_next = {pkt_hdr_vec_w[PKT_HDR_LEN-1:145], p_cur_queue_val, pkt_hdr_vec_w[0+:141]};
		parser_valid_next = 1;

		p_cur_queue_next = p_cur_queue_plus1;
	end
end

always @(*) begin
	out_vlan_valid_next = 0;
	out_vlan_next = out_vlan;

	if (parser_out_vlan_valid) begin
		out_vlan_valid_next = 1;
		out_vlan_next = parser_out_vlan;
	end
end

always @(posedge axis_clk) begin
	if (~aresetn) begin
		p_state <= P_IDLE;
		p_cur_queue <= 0;
		pkt_hdr_vec <= 0;
		parser_valid <= 0;

		out_vlan <= 0;
		out_vlan_valid <= 0;

		pkt_hdr_vec_r <= 0;
		parser_valid_r <= 0;
	end
	else begin
		p_state <= p_state_next;
		p_cur_queue <= p_cur_queue_next;
		pkt_hdr_vec_r <= pkt_hdr_vec_next;
		parser_valid_r <= parser_valid_next;
		pkt_hdr_vec <= pkt_hdr_vec_r;
		parser_valid <= parser_valid_r;

		out_vlan <= out_vlan_next;
		out_vlan_valid <= out_vlan_valid_next;
	end
end

//
always @(posedge axis_clk) begin
	if (~aresetn) begin
		segs_in_r <= 0;
		tuser_1st_in_r <= 0;
		segs_in_valid_r <= 0;
	end
	else begin
		segs_in_r <= segs_in;
		tuser_1st_in_r <= tuser_1st_in;
		segs_in_valid_r <= segs_in_valid;
	end
end

//
parser_wait_segs #(
)
get_segs
(
	.axis_clk				(axis_clk),
	.aresetn				(aresetn),

	.s_axis_tdata			(s_axis_tdata),
	.s_axis_tuser			(s_axis_tuser),
	.s_axis_tkeep			(s_axis_tkeep),
	.s_axis_tvalid			(s_axis_tvalid),
	.s_axis_tlast			(s_axis_tlast),

	// output
	.tdata_segs				(segs_in),
	.tuser_1st				(tuser_1st_in),
	.segs_valid				(segs_in_valid)
);

parser_do_parsing_top #(
)
do_parsing
(
	.clk					(axis_clk),
	.aresetn				(aresetn),

	.segs_in				(segs_in_r),
	.segs_in_valid			(segs_in_valid_r),
	.tuser_1st_in			(tuser_1st_in_r),
	.bram_in				(bram_out_0_d1),
	.bram_in_valid			(out_bram_valid_d1),

	.stg_ready				(stg_ready_in),
	.stg_vlan_ready			(out_vlan_ready),

	// output
	.pkt_hdr_vec			(pkt_hdr_vec_w),
	.parser_valid			(parser_valid_w),
	.vlan_out				(parser_out_vlan),
	.vlan_out_valid			(parser_out_vlan_valid)
);

//
localparam	BRAM_IDLE=0,
			BRAM_CYCLE_1=1,
			BRAM_CYCLE_2=2,
			BRAM_CYCLE_3=3;

reg [2:0] bram_state, bram_state_next;
reg out_bram_valid_next;

always @(*) begin
	bram_state_next = bram_state;

	out_bram_valid_next = 0;

	bram_ready_next = bram_ready;
	case (bram_state) 
		BRAM_IDLE: begin
			if (s_vlan_id_valid) begin
				bram_state_next = BRAM_CYCLE_1;
				if (s_axis_tvalid && s_axis_tlast) begin
					bram_ready_next = 0;
				end
			end
		end
		BRAM_CYCLE_1: begin
			bram_state_next = BRAM_IDLE;
			out_bram_valid_next = 1;
			bram_ready_next = 1;
		end
	endcase
end

always @(posedge axis_clk) begin
	if (~aresetn) begin
		bram_state <= BRAM_IDLE;

		out_bram_valid <= 0;
		bram_ready <= 1;
		out_bram_valid_d1 <= 0;
	end
	else begin

		bram_state <= bram_state_next;
		out_bram_valid <= out_bram_valid_next;
		out_bram_valid_d1 <= out_bram_valid;
		bram_out_0_d1 <= bram_out_0;
		bram_ready <= bram_ready_next;
	end
end

// fifo
/*
fallthrough_small_fifo #(
	.WIDTH(PKT_HDR_LEN),
	.MAX_DEPTH_BITS(4)
)
pkt_hdr_fifo (
	.din				(pkt_hdr_vec_w),
	.wr_en				(parser_valid_w),
	
	.dout				(phv_fifo_out),
	.rd_en				(phv_fifo_rd_en),
	.full				(),
	.nearly_full		(phv_fifo_nearly_full),
	.empty				(phv_fifo_empty),
	.reset				(~aresetn),
	.clk				(axis_clk)
);

fallthrough_small_fifo #(
	.WIDTH(C_VLANID_WIDTH),
	.MAX_DEPTH_BITS(4)
)
vlan_fifo (
	.din				(parser_out_vlan),
	.wr_en				(parser_out_vlan_valid),
	
	.dout				(vlan_fifo_out),
	.rd_en				(vlan_fifo_rd_en),
	.full				(),
	.nearly_full		(vlan_fifo_nearly_full),
	.empty				(vlan_fifo_empty),
	.reset				(~aresetn),
	.clk				(axis_clk)
);*/

/*================Control Path====================*/
wire [7:0]          mod_id; //module ID
wire [15:0]         control_flag; //dst udp port num
reg  [7:0]          c_index; //table index(addr)
reg                 c_wr_en; //enable table write(wen)
reg  [159:0]        entry_reg;

reg  [2:0]          c_state;

localparam IDLE_C = 1,
           WRITE_C  =2,
           SU_WRITE_C = 3;

assign mod_id = ctrl_s_axis_tdata[368+:8];
assign control_flag = ctrl_s_axis_tdata[335:320];

//LE to BE switching
wire[C_S_AXIS_DATA_WIDTH-1:0] ctrl_s_axis_tdata_swapped;
assign ctrl_s_axis_tdata_swapped = {	ctrl_s_axis_tdata[0+:8],
									ctrl_s_axis_tdata[8+:8],
									ctrl_s_axis_tdata[16+:8],
									ctrl_s_axis_tdata[24+:8],
									ctrl_s_axis_tdata[32+:8],
									ctrl_s_axis_tdata[40+:8],
									ctrl_s_axis_tdata[48+:8],
									ctrl_s_axis_tdata[56+:8],
									ctrl_s_axis_tdata[64+:8],
									ctrl_s_axis_tdata[72+:8],
									ctrl_s_axis_tdata[80+:8],
									ctrl_s_axis_tdata[88+:8],
									ctrl_s_axis_tdata[96+:8],
									ctrl_s_axis_tdata[104+:8],
									ctrl_s_axis_tdata[112+:8],
									ctrl_s_axis_tdata[120+:8],
									ctrl_s_axis_tdata[128+:8],
									ctrl_s_axis_tdata[136+:8],
									ctrl_s_axis_tdata[144+:8],
									ctrl_s_axis_tdata[152+:8],
									ctrl_s_axis_tdata[160+:8],
									ctrl_s_axis_tdata[168+:8],
									ctrl_s_axis_tdata[176+:8],
									ctrl_s_axis_tdata[184+:8],
									ctrl_s_axis_tdata[192+:8],
									ctrl_s_axis_tdata[200+:8],
									ctrl_s_axis_tdata[208+:8],
									ctrl_s_axis_tdata[216+:8],
									ctrl_s_axis_tdata[224+:8],
									ctrl_s_axis_tdata[232+:8],
									ctrl_s_axis_tdata[240+:8],
									ctrl_s_axis_tdata[248+:8],
									ctrl_s_axis_tdata[256+:8], 
									ctrl_s_axis_tdata[264+:8], 
									ctrl_s_axis_tdata[272+:8], 
									ctrl_s_axis_tdata[280+:8],
									ctrl_s_axis_tdata[288+:8],
									ctrl_s_axis_tdata[296+:8],
									ctrl_s_axis_tdata[304+:8],
									ctrl_s_axis_tdata[312+:8],
									ctrl_s_axis_tdata[320+:8],
									ctrl_s_axis_tdata[328+:8],
									ctrl_s_axis_tdata[336+:8],
									ctrl_s_axis_tdata[344+:8],
									ctrl_s_axis_tdata[352+:8],
									ctrl_s_axis_tdata[360+:8],
									ctrl_s_axis_tdata[368+:8],
									ctrl_s_axis_tdata[376+:8],
									ctrl_s_axis_tdata[384+:8],
									ctrl_s_axis_tdata[392+:8],
									ctrl_s_axis_tdata[400+:8],
									ctrl_s_axis_tdata[408+:8],
									ctrl_s_axis_tdata[416+:8],
									ctrl_s_axis_tdata[424+:8],
									ctrl_s_axis_tdata[432+:8],
									ctrl_s_axis_tdata[440+:8],
									ctrl_s_axis_tdata[448+:8],
									ctrl_s_axis_tdata[456+:8],
									ctrl_s_axis_tdata[464+:8],
									ctrl_s_axis_tdata[472+:8],
									ctrl_s_axis_tdata[480+:8],
									ctrl_s_axis_tdata[488+:8],
									ctrl_s_axis_tdata[496+:8],
									ctrl_s_axis_tdata[504+:8]
									};

always @(posedge axis_clk or negedge aresetn) begin
    if(~aresetn) begin
        c_wr_en <= 1'b0;
        c_index <= 4'b0;

        ctrl_m_axis_tdata <= 0;
        ctrl_m_axis_tuser <= 0;
        ctrl_m_axis_tkeep <= 0;
        ctrl_m_axis_tvalid <= 0;
        ctrl_m_axis_tlast <= 0;
        entry_reg <= 0;

        c_state <= IDLE_C;
    end
    else begin
        case(c_state)
            IDLE_C: begin
                if(ctrl_s_axis_tvalid && mod_id[2:0] == PARSER_MOD_ID && control_flag == 16'hf2f1)begin
                    c_wr_en <= 1'b0;
                    c_index <= ctrl_s_axis_tdata[384+:8];

                    ctrl_m_axis_tdata <= 0;
                    ctrl_m_axis_tuser <= 0;
                    ctrl_m_axis_tkeep <= 0;
                    ctrl_m_axis_tvalid <= 0;
                    ctrl_m_axis_tlast <= 0;

                    c_state <= WRITE_C;

                end
                else begin
                    c_wr_en <= 1'b0;
                    c_index <= 4'b0; 
                    entry_reg <= 0;

                    ctrl_m_axis_tdata <= ctrl_s_axis_tdata;
                    ctrl_m_axis_tuser <= ctrl_s_axis_tuser;
                    ctrl_m_axis_tkeep <= ctrl_s_axis_tkeep;
                    ctrl_m_axis_tvalid <= ctrl_s_axis_tvalid;
                    ctrl_m_axis_tlast <= ctrl_s_axis_tlast;

                    c_state <= IDLE_C;
                end
            end
            //support full table flush
            WRITE_C: begin
                if(ctrl_s_axis_tvalid) begin
                    c_wr_en <= 1'b1;
                    entry_reg <= ctrl_s_axis_tdata_swapped[511 -: 160];
                    if(ctrl_s_axis_tlast) begin
                        c_state <= IDLE_C;
                    end
                    else begin
                        c_state <= SU_WRITE_C;
                    end
                end
                else begin
                    c_wr_en <= 1'b0;
                end
            end

            SU_WRITE_C: begin
                if(ctrl_s_axis_tvalid) begin
                    entry_reg <= ctrl_s_axis_tdata_swapped[511 -: 160];
                    c_wr_en <= 1'b1;
                    c_index <= c_index + 1'b1;
                    if(ctrl_s_axis_tlast) begin
                        c_state <= IDLE_C;
                    end
                    else begin
                        c_state <= SU_WRITE_C;
                    end
                end
                else begin
                    c_wr_en <= 1'b0;
                end
            end
        endcase

    end
end


// =============================================================== //
// parse_act_ram_ip #(
// 	.C_INIT_FILE_NAME	("./parse_act_ram_init_file.mif"),
// 	.C_LOAD_INIT_FILE	(1)
// )
parse_act_ram_ip
parse_act_ram_0
(
	// write port
	.clka		(axis_clk),
	.addra		(c_index[4:0]),
	.dina		(entry_reg),
	.ena		(1'b1),
	.wea		(c_wr_en),

	//
	.clkb		(axis_clk),
	.addrb		(s_vlan_id[8:4]), // TODO: note that we may change due to little or big endian
	.doutb		(bram_out_0),
	.enb		(1'b1) // always set to 1
);

endmodule