module usb_fifo
	(
		input	wire			usb_clock,
		inout	wire	[7:0]	usb_data,
		input	wire			usb_full, usb_empty,
		output	wire			usb_wrreq, usb_rdreq, usb_rden, usb_pktend,
		output	wire	[1:0]	usb_addr,
		
		input	wire			clock,
		input	wire			tx_wrreq, rx_rdreq,
		input	wire	[7:0]	tx_data,
		output	wire			tx_full, rx_empty,
		output	wire	[7:0]	rx_q
	);

	wire			int_rx_full, int_tx_empty;
	wire			rx_ready, tx_ready;
	wire			int_rdreq, int_wrreq, int_pktend;
	reg				is_rx_addr_ok;
	reg		[8:0]	byte_counter;
	reg		[4:0]	idle_counter;

	wire	[7:0]	int_rx_data = usb_data;
	wire	[7:0]	int_tx_q;

	dcfifo #(
		.intended_device_family("Cyclone III"),
		.lpm_numwords(16),
		.lpm_showahead("ON"),
		.lpm_type("dcfifo"),
		.lpm_width(8),
		.lpm_widthu(4),
		.rdsync_delaypipe(4),
		.wrsync_delaypipe(4),
		.overflow_checking("ON"),
		.underflow_checking("ON"),
		.use_eab("OFF")) fifo_tx (
		.data(tx_data),
		.rdclk(usb_clock),
		.rdreq(int_wrreq),
		.wrclk(clock),
		.wrreq(tx_wrreq),
		.q(int_tx_q),
		.rdempty(int_tx_empty),
		.wrfull(tx_full),
		.aclr(),
		.rdfull(),
		.rdusedw(),
		.wrempty(),
		.wrusedw());

	dcfifo #(
		.intended_device_family("Cyclone III"),
		.lpm_numwords(16),
		.lpm_showahead("ON"),
		.lpm_type("dcfifo"),
		.lpm_width(8),
		.lpm_widthu(4),
		.rdsync_delaypipe(4),
		.wrsync_delaypipe(4),
		.overflow_checking("ON"),
		.underflow_checking("ON"),
		.use_eab("OFF")) fifo_rx (
		.data(int_rx_data),
		.rdclk(clock),
		.rdreq(rx_rdreq),
		.wrclk(usb_clock),
		.wrreq(int_rdreq),
		.q(rx_q),
		.rdempty(rx_empty),
		.wrfull(int_rx_full),
		.aclr(),
		.rdfull(),
		.rdusedw(),
		.wrempty(),
		.wrusedw());
	
	assign	rx_ready = (~usb_empty) & (~int_rx_full) & (~int_pktend);
	assign	tx_ready = (~rx_ready) & (~usb_full) & (~int_tx_empty) & (~int_pktend);

	assign	int_rdreq = (rx_ready) & (is_rx_addr_ok);
	assign	int_wrreq = (tx_ready) & (~is_rx_addr_ok);
	
	assign	int_pktend = (&idle_counter);

	always @ (posedge usb_clock)
	begin
		// respect 1 clock delay between fifo selection
		// and data transfer operations
		is_rx_addr_ok <= rx_ready;

		// assert pktend if buffer contains unsent data
		// and fifo_tx_unit stays empty for more than 30 clocks
		if (int_pktend)
		begin
			byte_counter <= 9'd0;
			idle_counter <= 5'd0;
		end
		else if (int_wrreq)
		begin
			byte_counter <= byte_counter + 9'd1;
			idle_counter <= 5'd0;
		end
		else if ((|byte_counter) & (int_tx_empty) & (~rx_ready))
		begin
			byte_counter <= byte_counter;
			idle_counter <= idle_counter + 5'd1;
		end

	end

	assign	usb_pktend = int_pktend;
	assign	usb_rdreq = int_rdreq;
	assign	usb_wrreq = int_wrreq;
	assign	usb_rden = int_rdreq;
	assign	usb_addr = {1'b1, ~rx_ready};
	assign	usb_data = int_wrreq ? int_tx_q : 8'bz;

endmodule
