`timescale 1ns / 1ps

`include "src/eth/crc32.v"

module mac_memory_access_controller #(
	parameter DATA_WIDTH = 8,
	parameter ADDR_WIDTH = 12,
	
	// AUTOCALCULATED
	parameter DATA_MSB = DATA_WIDTH - 1,
	parameter ADDR_MSB = ADDR_WIDTH - 1,
	parameter LEN_MSB = 10,
	parameter MIN_FRAME_SIZE = 6'd60
)(
	input clk, rst, clear, // general signals
	
	input [DATA_MSB:0] mem_data, // data fetched from the memory
	output reg [ADDR_MSB:0] mem_addr, // memory address for data fetching
		
	input transmitter_busy, // indicates that the transmitter is busy with previous transmission
	input start, // start a new transmission
	input [ADDR_MSB:0] frame_addr, // starting address of the Ethernet frame
	input [LEN_MSB:0] frame_len, // length of the Ethernet frame
	
	output busy, // a frame is currently being processed and transmitted
	
	output reg [DATA_MSB:0] tx_data_in, // data input for the transmitter block
	input tx_not_full, // transmitter block supports storing new blocks of data
	output tx_wrt // issue a write onto the transmitter block
);

// --------------

wire clear_crc = (state == IDLE);
wire crc_en = ((state == FETCH_FRAME) || (state == INSERT_PADDING)) && tx_not_full;
wire [31:0] crc_out;

// override CRC input when zero padding is applied
reg [DATA_WIDTH-1:0] crc_in;
always @(*)
begin
	case (state)
		INSERT_PADDING: crc_in <= 8'h00;
		default: crc_in <= mem_data;
	endcase
end

crc32 crc32_engine (
	.clk(clk),
	.rst(rst),
	.clear(clear_crc | clear),

	.data_in(crc_in),
	.crc_en(crc_en),
	.crc_out(crc_out)
);

// --------------

wire rstclr = rst | clear;

// state machine states
localparam IDLE = 0;
localparam INSERT_PREAMBLE = 1;
localparam FETCH_FRAME = 2;
localparam INSERT_PADDING = 3;
localparam APPEND_CRC = 4;
localparam WAIT_TX_COMPL = 5;

// main state machine
reg [2:0] state;
wire frame_params_valid = frame_len != 0;

// frame setup and transmission
localparam PREAMBLE_PATTERN = 8'h55; // Ethernet preamble pattern
localparam SOF_PATTERN = 8'hD5; // Ethernet Start-of-Frame pattern
localparam PREAMBLE_LEN = 8; // length of the frame preamble
reg [2:0] preamble_idx; // index of the preamble byte being sent
reg [1:0] crc_idx; // index of the CRC bytes

reg [LEN_MSB:0] len_left; // remaining bytes to be transmitted
reg [5:0] pad_left; // padding left

always @(posedge clk)
begin
	if (rstclr)
	begin
		len_left <= 0;
		//tx_wrt <= 0;
		mem_addr <= 0;
		len_left <= 0;
		preamble_idx <= 0;
		crc_idx <= 0;
		pad_left <= 0;
	end	
	else
	begin
		case (state)
			IDLE:
			begin
				mem_addr <= frame_addr;
				len_left <= frame_len - 1'b1;
				preamble_idx <= 0;
				crc_idx <= 0;
				pad_left <= (frame_len < MIN_FRAME_SIZE) ? (MIN_FRAME_SIZE - frame_len - 1'b1) : 0;
			end

			INSERT_PREAMBLE:
			begin
				if (tx_not_full)
				begin
					preamble_idx <= preamble_idx + 1'b1;
				end
			end
			
			FETCH_FRAME:
			begin
				if (len_left != 0)
				begin					
					if (tx_not_full)
					begin
						mem_addr <= mem_addr + 1'b1;
						len_left <= len_left - 1'b1;
					end
				end
			end
			
			INSERT_PADDING:
			begin
				if (tx_not_full)
				begin
					pad_left <= pad_left - 1'b1;
				end
			end

			APPEND_CRC:
			begin
				if (tx_not_full)
				begin
					crc_idx <= crc_idx + 1'b1;
				end
			end
			
			default: ;
		endcase
	end
	
	// next state logic
	case (state)
		IDLE: if (start && frame_params_valid) state <= INSERT_PREAMBLE;
		INSERT_PREAMBLE: if ((preamble_idx == (PREAMBLE_LEN - 1)) && tx_not_full) state <= FETCH_FRAME;
		FETCH_FRAME: if ((len_left == 0) && tx_not_full) begin if (pad_left != 0) state <= INSERT_PADDING; else state <= APPEND_CRC; end
		INSERT_PADDING: if ((pad_left == 0) && tx_not_full) state <= APPEND_CRC;
		APPEND_CRC: if ((crc_idx == (3)) && tx_not_full) state <= WAIT_TX_COMPL;
		WAIT_TX_COMPL: if (!transmitter_busy) state <= IDLE;
		default: state <= IDLE;
	endcase
end


// combinatorial outputs
assign busy = (state != IDLE);
//assign tx_data_in = mem_data;
assign tx_wrt = (state == INSERT_PREAMBLE) || (state == FETCH_FRAME) || (state == INSERT_PADDING) || (state == APPEND_CRC);

// assign data output
always @(*)
begin
	case (state)
		INSERT_PREAMBLE: tx_data_in <= (preamble_idx == (PREAMBLE_LEN - 1'b1)) ? SOF_PATTERN : PREAMBLE_PATTERN;
		INSERT_PADDING: tx_data_in <= 8'h00;
		APPEND_CRC:
			case (crc_idx)
				2'd3: tx_data_in <= crc_out[31:24];
				2'd2: tx_data_in <= crc_out[23:16];
				2'd1: tx_data_in <= crc_out[15:8];
				default: tx_data_in <= crc_out[7:0];
			endcase
		default: tx_data_in <= mem_data;
	endcase
end

endmodule
