///////////////////////////////////////////////////////////
// mem_uart.v
// Memory emulator. Provides design with large memory
// through a UART connection.
// Script provides memory and allows debug.
// DUT <-Parallel-> mem_uart <-UART-> Script
//
//
// UART
//    Baud = 9600
//    [7 6 5 4 3 2 1 0]
//    [D D D D 0 0 0 0] - Mid transmission
//    [D D D D 0 0 0 1] - Start of data
//    [D D D D 0 0 1 0] - Start of address
//    [D D D D 0 1 0 0] - End of data/address and write 
//    [D D D D 1 0 0 0] - End of data/address and read
//
///////////////////////////////////////////////////////////


`timescale 1ns/1ps

module mem_uart(
	input    wire				         i_clk,
	input    wire				         i_nrst,

   // Memory
   input    wire  [DATA_WIDTH-1:0]  i_data,
   input    wire  [ADDR_WIDTH-1:0]  i_addr,
   output   reg   [DATA_WIDTH-1:0]  o_data,
   input    wire                    i_read_valid,
   output   reg                     o_read_accept,
   input    wire                    i_write_valid,
   output   reg                     o_write_accept,

   // UART
   input    wire                    i_uart_rx,
   output   wire                    o_uart_tx	
);

   parameter   DATA_WIDTH        = 0;
   parameter   ADDR_WIDTH        = 0;
   parameter   DATA_NIBBLE_WIDTH = DATA_WIDTH >> 2;
   parameter   ADDR_NIBBLE_WIDTH = ADDR_WIDTH >> 2;
   parameter   NIBBLE_WIDTH      = DATA_NIBBLE_WIDTH + ADDR_NIBBLE_WIDTH;
   parameter   DATA_BYTE_WIDTH   = DATA_WIDTH >> 3;
   parameter   SAMPLE            = 0;             

   parameter   SM_IDLE           = 3'h0, 
               SM_WRITE_VALID    = 3'h1,
               SM_WRITE_ACCEPT   = 3'h2,
               SM_READ_VALID     = 3'h3,
               SM_READ_ACCEPT    = 3'h4,
               SM_READ_RX        = 3'h5;

   parameter   CMD_TRANS         = 4'b0000,
               CMD_DATA_START    = 4'b0001,
               CMD_ADDR_START    = 4'b0010,
               CMD_END_WRITE     = 4'b0100,
               CMD_END_READ      = 4'b1000;

   reg      [2:0]                               state;
   reg      [$clog2(DATA_WIDTH+ADDR_WIDTH)-1:0] nibble_ptr;
   wire     [DATA_WIDTH+ADDR_WIDTH-1:0]         data_addr;
   wire     [3:0]                               cmd,
                                                write_cmd,
                                                read_cmd;
   wire     [7:0]                               uart_rx_o_data;
   wire     [3:0]                               uart_tx_i_data;

   assign   cmd         =  (  (state == SM_WRITE_VALID   )  ||
                              (state == SM_WRITE_ACCEPT  )  )?        write_cmd         : 
                                                                     read_cmd;
   
   assign   write_cmd   =  (nibble_ptr == 'b0                  )  ?  CMD_ADDR_START    :
                           (nibble_ptr == ADDR_NIBBLE_WIDTH-1  )  ?  CMD_END_WRITE     :
                           (nibble_ptr == NIBBLE_WIDTH-1       )  ?  CMD_END_WRITE     :
                           (nibble_ptr == ADDR_NIBBLE_WIDTH    )  ?  CMD_DATA_START    :
                                                                     CMD_TRANS;

   assign   read_cmd    =  (nibble_ptr == 'b0                  )  ?  CMD_ADDR_START    :
                           (nibble_ptr == ADDR_NIBBLE_WIDTH-1  )  ?  CMD_END_READ      :
                                                                     CMD_TRANS;


   assign   uart_tx_i_valid = (state == SM_WRITE_ACCEPT) | (state == SM_READ_ACCEPT);

   assign   data_addr = {i_data,i_addr};

   assign   uart_tx_i_data[0] = data_addr[      (nibble_ptr << 2)];
   assign   uart_tx_i_data[1] = data_addr[1 +   (nibble_ptr << 2)];
   assign   uart_tx_i_data[2] = data_addr[2 +   (nibble_ptr << 2)];
   assign   uart_tx_i_data[3] = data_addr[3 +   (nibble_ptr << 2)];


   always@(posedge i_clk or negedge i_nrst) begin
		if(!i_nrst) begin
         o_data         <= 'b0;
         o_read_accept  <= 1'b0;
         o_write_accept <= 1'b0;
         state          <= SM_IDLE;
		end else begin
	      case(state)
            SM_IDLE:          begin
                                 o_write_accept <= 1'b0;
                                 o_read_accept <= 1'b0;
                                 nibble_ptr  <= 'b0;
                                 if(i_read_valid)
                                     state   <= SM_READ_VALID;
                                 if(i_write_valid)
                                     state   <= SM_WRITE_VALID; 
                              end

            // Write sequence
            SM_WRITE_VALID:   begin 
                                 state       <= SM_WRITE_ACCEPT; 
                              end                     
            SM_WRITE_ACCEPT:  if(uart_tx_o_accept)
                                 if(nibble_ptr == NIBBLE_WIDTH-1) begin
                                    state    <= SM_IDLE;
                                    o_write_accept  <= 1'b1;
                                 end else begin
                                    state    <= SM_WRITE_VALID;
                                    nibble_ptr  <= nibble_ptr + 'b1; 
                                 end
            // Read sequence
            SM_READ_VALID:    begin
                                 state       <= SM_READ_ACCEPT;
                                 nibble_ptr  <= nibble_ptr;
                              end
            SM_READ_ACCEPT:   if(uart_tx_o_accept) begin
                                 nibble_ptr  <= nibble_ptr + 'b1; 
                                 if(nibble_ptr == ADDR_NIBBLE_WIDTH-1) begin
                                    state       <= SM_READ_RX;
                                    nibble_ptr  <= 'b0;
                                 end else
                                    state       <= SM_READ_VALID;
                              end
            SM_READ_RX:       if(uart_rx_o_valid) begin
                                 o_data <= {o_data[DATA_WIDTH-9:0], uart_rx_o_data};
                                 if(nibble_ptr == (DATA_BYTE_WIDTH-1)) begin
                                    state          <= SM_IDLE;
                                    o_read_accept  <= 1'b1;
                                 end
                                 nibble_ptr <= nibble_ptr + 'b1;
                              end
         endcase
      end
	end

   uart_rx #(
      .SAMPLE     (SAMPLE                 )   
   ) uart_rx (
      .i_clk      (i_clk                  ),
      .i_nrst     (i_nrst                 ),
      .o_data     (uart_rx_o_data         ),
      .i_rx       (i_uart_rx              ),
      .o_valid    (uart_rx_o_valid        ),
      .i_accept   (1'b1                   )
	);

   uart_tx #(
      .SAMPLE     (SAMPLE                 )
   ) uart_tx (
	   .i_clk      (i_clk                  ),
      .i_nrst     (i_nrst                 ),
      .i_data     ({uart_tx_i_data,cmd}   ),
      .o_tx       (o_uart_tx              ),
      .i_valid    (uart_tx_i_valid        ),
      .o_accept   (uart_tx_o_accept       )
   );


endmodule