module tx_port_writer (
	input CLK,
	input RST,
	input [2:0] CONFIG_MAX_PAYLOAD_SIZE,	// Maximum write payload: 000=128B, 001=256B, 010=512B, 011=1024B

	output TXN,							// Write transaction notification
	input TXN_ACK,						// Write transaction acknowledged
	output [31:0] TXN_LEN,				// Write transaction length
	output [31:0] TXN_OFF_LAST,			// Write transaction offset/last
	output [31:0] TXN_DONE_LEN,			// Write transaction actual transfer length
	output TXN_DONE,					// Write transaction done
	output TXN_ERR,						// Write transaction encountered an error
	input TXN_DONE_ACK,					// Write transaction actual transfer length read

	input NEW_TXN,						// Transaction parameters are valid
	output NEW_TXN_ACK,					// Transaction parameter read, continue
	input NEW_TXN_LAST,					// Channel last write
	input [31:0] NEW_TXN_LEN,			// Channel write length (in 32 bit words)
	input [30:0] NEW_TXN_OFF,			// Channel write offset
	input [31:0] NEW_TXN_WORDS_RECVD,	// Count of data words received in transaction
	input NEW_TXN_DONE,					// Transaction is closed

	input [63:0] SG_ELEM_ADDR,			// Scatter gather element address
	input [31:0] SG_ELEM_LEN,			// Scatter gather element length (in words)
	input SG_ELEM_RDY,					// Scatter gather element ready
	input SG_ELEM_EMPTY,				// Scatter gather elements empty
	output SG_ELEM_REN,					// Scatter gather element read enable
	output SG_RST,						// Scatter gather data reset
	input SG_ERR,						// Scatter gather read encountered an error
	
	output TX_REQ,						// Outgoing write request
	input TX_REQ_ACK,					// Outgoing write request acknowledged
	output [63:0] TX_ADDR,				// Outgoing write high address
	output [9:0] TX_LEN,				// Outgoing write length (in 32 bit words)
	output TX_LAST,						// Outgoing write is last request for transaction
	input TX_SENT						// Outgoing write complete
);

`include "common_functions.v"

(* syn_encoding = "user" *)
(* fsm_encoding = "user" *)
reg		[7:0]						rMainState=`S_TXPORTWR_MAIN_IDLE, _rMainState=`S_TXPORTWR_MAIN_IDLE;
reg		[31:0]						rOffLast=0, _rOffLast=0;
reg									rWordsEQ0=0, _rWordsEQ0=0;
reg									rStarted=0, _rStarted=0;
reg		[31:0]						rDoneLen=0, _rDoneLen=0;
reg									rSgErr=0, _rSgErr=0;
reg									rTxErrd=0, _rTxErrd=0;
reg									rTxnAck=0, _rTxnAck=0;

(* syn_encoding = "user" *)
(* fsm_encoding = "user" *)
reg		[7:0]						rTxState=`S_TXPORTWR_TX_IDLE, _rTxState=`S_TXPORTWR_TX_IDLE;
reg		[31:0]						rSentWords=0, _rSentWords=0;
reg		[31:0]						rWords=0, _rWords=0;
reg		[31:0]						rBufWords=0, _rBufWords=0;
reg		[31:0]						rBufWordsInit=0, _rBufWordsInit=0;
reg									rLargeBuf=0, _rLargeBuf=0;
reg		[63:0]						rAddr=64'd0, _rAddr=64'd0;
reg		[2:0]						rCarry=0, _rCarry=0;
reg									rValsPropagated=0, _rValsPropagated=0;
reg		[5:0]						rValsProp=0, _rValsProp=0;
reg									rCopyBufWords=0, _rCopyBufWords=0;
reg									rUseInit=0, _rUseInit=0;
reg		[10:0]						rPageRem=0, _rPageRem=0;
reg									rPageSpill=0, _rPageSpill=0;
reg									rPageSpillInit=0, _rPageSpillInit=0;
reg		[10:0]						rPreLen=0, _rPreLen=0;
reg		[2:0]						rMaxPayloadSize=0, _rMaxPayloadSize=0;
reg		[2:0]						rMaxPayloadShift=0, _rMaxPayloadShift=0;
reg		[9:0]						rMaxPayload=0, _rMaxPayload=0;
reg									rPayloadSpill=0, _rPayloadSpill=0;
reg									rMaxLen=1, _rMaxLen=1;
reg		[9:0]						rLen=0, _rLen=0;
reg		[31:0]						rSendingWords=0, _rSendingWords=0;
reg									rAvail=0, _rAvail=0;
reg		[1:0]						rTxnDone=0, _rTxnDone=0;
reg		[9:0]						rLastLen=0, _rLastLen=0;
reg									rLastLenEQ0=0, _rLastLenEQ0=0;
reg									rLenEQWords=0, _rLenEQWords=0;
reg									rLenEQBufWords=0, _rLenEQBufWords=0;

reg									rNotRequesting=1, _rNotRequesting=1;
reg		[63:0]						rReqAddr=64'd0, _rReqAddr=64'd0;
reg		[9:0]						rReqLen=0, _rReqLen=0;
reg									rReqLast=0, _rReqLast=0;
reg									rTxReqAck=0, _rTxReqAck=0;

reg									rDone=0, _rDone=0;
reg		[9:0]						rAckCount=0, _rAckCount=0;
reg									rTxSent=0, _rTxSent=0;
reg									rLastDoneRead=1, _rLastDoneRead=1;
reg									rTxnDoneAck=0, _rTxnDoneAck=0;

reg									rReqPartialDone=0, _rReqPartialDone=0;
reg									rPartialDone=0, _rPartialDone=0;


assign NEW_TXN_ACK = rMainState[1]; // S_TXPORTWR_MAIN_CHECK

assign TXN = rMainState[2]; // S_TXPORTWR_MAIN_SIG_NEW
assign TXN_DONE = (rMainState[6] | rPartialDone); // S_TXPORTWR_MAIN_SIG_DONE
assign TXN_LEN = rWords;
assign TXN_OFF_LAST = rOffLast;
assign TXN_DONE_LEN = rDoneLen;
assign TXN_ERR = rTxErrd;

assign SG_ELEM_REN = rTxState[2]; // S_TXPORTWR_TX_ADJ_0
assign SG_RST = rMainState[3]; // S_TXPORTWR_MAIN_NEW_ACK

assign TX_REQ = !rNotRequesting;
assign TX_ADDR = rReqAddr; 
assign TX_LEN = rReqLen;
assign TX_LAST = rReqLast;


// Buffer the input signals that come from outside the tx_port.
always @ (posedge CLK) begin
	rTxnAck <= #1 (RST ? 1'd0 : _rTxnAck);
	rTxnDoneAck <= #1 (RST ? 1'd0 : _rTxnDoneAck);
	rSgErr <= #1 (RST ? 1'd0 : _rSgErr);
	rTxReqAck <= #1 (RST ? 1'd0 : _rTxReqAck);
	rTxSent <= #1 (RST ? 1'd0 : _rTxSent);
end

always @ (*) begin
	_rTxnAck = TXN_ACK;
	_rTxnDoneAck = TXN_DONE_ACK;
	_rSgErr = SG_ERR;
	_rTxReqAck = TX_REQ_ACK;
	_rTxSent = TX_SENT;
end
	

// Wait for a NEW_TXN request. Then request transfers until all the data is sent
// or until the specified length is reached. Then signal TXN_DONE.
always @ (posedge CLK) begin
	rMainState <= #1 (RST ? `S_TXPORTWR_MAIN_IDLE : _rMainState);
	rOffLast <= #1 _rOffLast;
	rWordsEQ0 <= #1 _rWordsEQ0;
	rStarted <= #1 _rStarted;
	rDoneLen <= #1 (RST ? 0 : _rDoneLen);
	rTxErrd <= #1 (RST ? 1'd0 : _rTxErrd);
end

always @ (*) begin
	_rMainState = rMainState;
	_rOffLast = rOffLast;
	_rWordsEQ0 = rWordsEQ0;
	_rStarted = rStarted;
	_rDoneLen = rDoneLen;
	_rTxErrd = rTxErrd;
	case (rMainState)

	`S_TXPORTWR_MAIN_IDLE: begin // Wait for channel write request
		_rStarted = 0;
		_rWordsEQ0 = (NEW_TXN_LEN == 0);
		_rOffLast = {NEW_TXN_OFF, NEW_TXN_LAST};
		if (NEW_TXN)
			_rMainState = `S_TXPORTWR_MAIN_CHECK;
	end

	`S_TXPORTWR_MAIN_CHECK: begin // Continue with transaction?
		if (rOffLast[0] | !rWordsEQ0)
			_rMainState = `S_TXPORTWR_MAIN_SIG_NEW;
		else
			_rMainState = `S_TXPORTWR_MAIN_RESET;
	end

	`S_TXPORTWR_MAIN_SIG_NEW: begin // Signal new write
		_rMainState = `S_TXPORTWR_MAIN_NEW_ACK;
	end

	`S_TXPORTWR_MAIN_NEW_ACK: begin // Wait for acknowledgement
		if (rTxnAck) // ACK'd on PC read of TXN length
			_rMainState = (rWordsEQ0 ? `S_TXPORTWR_MAIN_SIG_DONE : `S_TXPORTWR_MAIN_WRITE);
	end

	`S_TXPORTWR_MAIN_WRITE: begin // Start writing and wait for all writes to complete
		_rStarted = (rStarted | rTxState[1]); // S_TXPORTWR_TX_BUF
		_rTxErrd = (rTxErrd | rSgErr);
		if (rTxState[0] & rStarted) // S_TXPORTWR_TX_IDLE
			_rMainState = `S_TXPORTWR_MAIN_DONE;
	end

	`S_TXPORTWR_MAIN_DONE: begin // Wait for the last transaction to complete
		if (rDone & rLastDoneRead) begin
			_rDoneLen = rSentWords;
			_rMainState = `S_TXPORTWR_MAIN_SIG_DONE;
		end
	end

	`S_TXPORTWR_MAIN_SIG_DONE: begin // Signal the done port
		_rTxErrd = 0;
		_rMainState = `S_TXPORTWR_MAIN_RESET;
	end

	`S_TXPORTWR_MAIN_RESET: begin // Wait for the channel tx to drop
		if (NEW_TXN_DONE)
			_rMainState = `S_TXPORTWR_MAIN_IDLE;
	end
	
	default: begin
		_rMainState = `S_TXPORTWR_MAIN_IDLE;
	end
	
	endcase
end


// Manage sending TX requests to the TX engine. Transfers will be limited
// by each scatter gather buffer's size, max payload size, and must not
// cross a (4KB) page boundary. The request is only made if there is sufficient
// data already written to the buffer.
wire [9:0] wLastLen = (NEW_TXN_WORDS_RECVD - rSentWords);
wire [9:0] wAddrLoInv = ~rAddr[11:2];
wire [10:0] wPageRem = (wAddrLoInv + 1'd1);	
always @ (posedge CLK) begin
	rTxState <= #1 (RST | rSgErr ? `S_TXPORTWR_TX_IDLE : _rTxState);
	rSentWords <= #1 (rMainState[0] ? 0 : _rSentWords);
	rWords <= #1 _rWords;
	rBufWords <= #1 _rBufWords;
	rBufWordsInit <= #1 _rBufWordsInit;
	rAddr <= #1 _rAddr;
	rCarry <= #1 _rCarry;
	rValsPropagated <= #1 _rValsPropagated;
	rValsProp <= #1 _rValsProp;
	rLargeBuf <= #1 _rLargeBuf;
	rPageRem <= #1 _rPageRem;
	rPageSpill <= #1 _rPageSpill;
	rPageSpillInit <= #1 _rPageSpillInit;
	rCopyBufWords <= #1 _rCopyBufWords;
	rUseInit <= #1 _rUseInit;
	rPreLen <= #1 _rPreLen;
	rMaxPayloadSize <= #1 _rMaxPayloadSize;
	rMaxPayloadShift <= #1 _rMaxPayloadShift;
	rMaxPayload <= #1 _rMaxPayload;
	rPayloadSpill <= #1 _rPayloadSpill;
	rMaxLen <= #1 (RST ? 1'd1 : _rMaxLen);
	rLen <= #1 _rLen;
	rSendingWords <= #1 _rSendingWords;
	rAvail <= #1 _rAvail;
	rTxnDone <= #1 _rTxnDone;
	rLastLen <= #1 _rLastLen;
	rLastLenEQ0 <= #1 _rLastLenEQ0;
	rLenEQWords <= #1 _rLenEQWords;
	rLenEQBufWords <= #1 _rLenEQBufWords;
end

always @ (*) begin
	_rTxState = rTxState;
	_rCopyBufWords = rCopyBufWords;
	_rUseInit = rUseInit;
	
	_rValsProp = ((rValsProp<<1) | rTxState[3]); // S_TXPORTWR_TX_ADJ_1
	_rValsPropagated = (rValsProp == 6'd0);
	_rLargeBuf = (SG_ELEM_LEN > rWords);
	{_rCarry[0], _rAddr[15:0]} = (rTxState[1] ? SG_ELEM_ADDR[15:0] : (rAddr[15:0] + ({12{rTxState[6]}} & {rLen, 2'd0}))); // S_TXPORTWR_TX_WRITE
	{_rCarry[1], _rAddr[31:16]} = (rTxState[1] ? SG_ELEM_ADDR[31:16] : (rAddr[31:16] + rCarry[0]));
	{_rCarry[2], _rAddr[47:32]} = (rTxState[1] ? SG_ELEM_ADDR[47:32] : (rAddr[47:32] + rCarry[1]));
				 _rAddr[63:48] = (rTxState[1] ? SG_ELEM_ADDR[63:48] : (rAddr[63:48] + rCarry[2]));
	_rSentWords = (rTxState[7] ? NEW_TXN_WORDS_RECVD : rSentWords) + ({10{rTxState[6]}} & rLen); // S_TXPORTWR_TX_WRITE
	_rWords = (NEW_TXN_ACK ? NEW_TXN_LEN : (rWords - ({10{rTxState[6]}} & rLen))); // S_TXPORTWR_TX_WRITE
	_rBufWordsInit = (rLargeBuf ? rWords : SG_ELEM_LEN); 
	_rBufWords = (rCopyBufWords ? rBufWordsInit : rBufWords) - ({10{rTxState[6]}} & rLen); // S_TXPORTWR_TX_WRITE
	_rPageRem = wPageRem;	
	_rPageSpillInit = (rBufWordsInit > wPageRem);	
	_rPageSpill = (rBufWords > wPageRem);	
	_rPreLen = ((rPageSpillInit & rUseInit) | (rPageSpill & !rUseInit) ? rPageRem : rBufWords[10:0]);
	_rMaxPayloadSize = CONFIG_MAX_PAYLOAD_SIZE;
	_rMaxPayloadShift = (rMaxPayloadSize > 3'd4 ? 3'd4 : rMaxPayloadSize);
	_rMaxPayload = (6'd32<<rMaxPayloadShift);
	_rPayloadSpill = (rPreLen > rMaxPayload);
	_rMaxLen = ((rMaxLen & !rValsProp[1]) | rTxState[6]); // S_TXPORTWR_TX_WRITE
	_rLen = (rPayloadSpill | rMaxLen ? rMaxPayload : rPreLen[9:0]);
	_rSendingWords = rSentWords + rLen;
	_rAvail = (NEW_TXN_WORDS_RECVD >= rSendingWords);
	_rTxnDone = ((rTxnDone<<1) | NEW_TXN_DONE);
	_rLastLen = wLastLen;
	_rLastLenEQ0 = (rLastLen == 10'd0);
	_rLenEQWords = (rLen == rWords);
	_rLenEQBufWords = (rLen == rBufWords);
	
	case (rTxState)

	`S_TXPORTWR_TX_IDLE: begin // Wait for channel write request
		if (rMainState[4] & !rStarted) // S_TXPORTWR_MAIN_WRITE
			_rTxState = `S_TXPORTWR_TX_BUF;
	end

	`S_TXPORTWR_TX_BUF: begin // Wait for buffer length and address
		if (SG_ELEM_RDY)
			_rTxState = `S_TXPORTWR_TX_ADJ_0;
	end

	`S_TXPORTWR_TX_ADJ_0: begin // Fix for large buffer
		_rCopyBufWords = 1;
		_rTxState = `S_TXPORTWR_TX_ADJ_1;
	end

	`S_TXPORTWR_TX_ADJ_1: begin // Check for page boundary crossing
		_rCopyBufWords = 0;
		_rUseInit = rCopyBufWords;
		_rTxState = `S_TXPORTWR_TX_ADJ_2;
	end

	`S_TXPORTWR_TX_ADJ_2: begin // Wait for values to propagate
		// Fix for page boundary crossing
		// Check for max payload
		// Fix for max payload
		_rUseInit = 0;
		if (rValsProp[2])
			_rTxState = `S_TXPORTWR_TX_CHECK_DATA;
	end

	`S_TXPORTWR_TX_CHECK_DATA: begin // Wait for available data
		if (rNotRequesting) begin
			if (rAvail)
				_rTxState = `S_TXPORTWR_TX_WRITE;
			else if (rValsPropagated & rTxnDone[1])
				_rTxState = (rLastLenEQ0 ? `S_TXPORTWR_TX_IDLE : `S_TXPORTWR_TX_WRITE_REM);
		end
	end

	`S_TXPORTWR_TX_WRITE: begin // Send len and repeat or finish?
		if (rLenEQWords)
			_rTxState = `S_TXPORTWR_TX_IDLE;
		else if (rLenEQBufWords)
			_rTxState = `S_TXPORTWR_TX_BUF;
		else
			_rTxState = `S_TXPORTWR_TX_ADJ_1;
	end

	`S_TXPORTWR_TX_WRITE_REM: begin // Send remaining data and finish
		_rTxState = `S_TXPORTWR_TX_IDLE;
	end
	
	default: begin
		_rTxState = `S_TXPORTWR_TX_IDLE;
	end
	
	endcase
end

// Request TX transfers separately so that the TX FSM can continue calculating
// the next set of request parameters without having to wait for the TX_REQ_ACK.
always @ (posedge CLK) begin
	rAckCount <= #1 (RST ? 10'd0 : _rAckCount);
	rNotRequesting <= #1 (RST ? 1'd1 : _rNotRequesting);
	rReqAddr <= #1 _rReqAddr;
	rReqLen <= #1 _rReqLen;
	rReqLast <= #1 _rReqLast;
	rDone <= #1 _rDone;
	rLastDoneRead <= #1 (RST ? 1'd1 : _rLastDoneRead);
end

always @ (*) begin
	// Start signaling when the TX FSM is ready.
	if (rTxState[6] | rTxState[7]) // S_TXPORTWR_TX_WRITE
		_rNotRequesting = 0;
	else
		_rNotRequesting = (rNotRequesting | rTxReqAck);

	// Pass off the rAddr & rLen when ready and wait for TX_REQ_ACK.
	if (rTxState[6]) begin // S_TXPORTWR_TX_WRITE
		_rReqAddr = rAddr;
		_rReqLen = rLen;
		_rReqLast = rLenEQWords;
	end
	else if (rTxState[7]) begin // S_TXPORTWR_TX_WRITE_REM
		_rReqAddr = rAddr;
		_rReqLen = rLastLen;
		_rReqLast = 1;
	end
	else begin
		_rReqAddr = rReqAddr;
		_rReqLen = rReqLen;
		_rReqLast = rReqLast;
	end

	// Track TX_REQ_ACK and TX_SENT to determine when the transaction is over.
	_rDone = (rAckCount == 10'd0);
	if (rMainState[0]) // S_TXPORTWR_MAIN_IDLE
		_rAckCount = 0;
	else
		_rAckCount = rAckCount + rTxState[6] + rTxState[7] - rTxSent; // S_TXPORTWR_TX_WRITE, S_TXPORTWR_TX_WRITE_REM
		
	// Track when the user reads the actual transfer amount.
	_rLastDoneRead = (rMainState[6] ? 1'd0 : (rLastDoneRead | rTxnDoneAck)); // S_TXPORTWR_MAIN_SIG_DONE
end


// Facilitate sending a TXN_DONE when we receive a TXN_ACK after the transaction
// has begun sending. This will happen when the workstation detects that it has 
// sent/used all its currently mapped scatter gather elements, but it's not enough 
// to complete the transaction. The TXN_DONE will let the workstation know it can
// release the current scatter gather mappings and allocate new ones.
always @ (posedge CLK) begin
	rPartialDone <= #1 _rPartialDone;
	rReqPartialDone <= #1 (RST ? 1'd0 : _rReqPartialDone);
end

always @ (*) begin
	// Signal TXN_DONE after we've recieved the (seemingly superfluous) TXN_ACK, 
	// we have no outstanding transfer requests, we're not currently requesting a
	// transfer, and there are no more scatter gather elements.
	_rPartialDone = (rReqPartialDone & rDone & rNotRequesting & SG_ELEM_EMPTY & rTxState[1]); // S_TXPORTWR_TX_BUF
	
	// Keep track of (seemingly superfluous) TXN_ACK requests.
	if ((rReqPartialDone & rDone & rNotRequesting & SG_ELEM_EMPTY & rTxState[1]) | rMainState[0]) // S_TXPORTWR_MAIN_IDLE
		_rReqPartialDone = 0;
	else
		_rReqPartialDone = (rReqPartialDone | (rTxnAck & !rMainState[3])); // !S_TXPORTWR_MAIN_NEW_ACK
end



endmodule