// Copyright (c) 2020, Andrew Kay // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. `default_nettype none module control ( input clk, input reset, // SPI input spi_cs_n, input [7:0] spi_rx_data, input spi_rx_strobe, output reg [7:0] spi_tx_data, output reg spi_tx_strobe, output loopback, // TX output reg tx_reset, input tx_active, output reg [9:0] tx_data, output reg tx_load_strobe, output reg tx_start_strobe, input tx_empty, input tx_full, input tx_ready, output tx_parity, // RX output reg rx_reset, input rx_active, input rx_error, input [9:0] rx_data, output reg rx_read_strobe, input rx_empty, output rx_parity ); parameter DEFAULT_CONTROL_REGISTER = 8'b01001000; localparam STATE_IDLE = 0; localparam STATE_READ_REGISTER_1 = 1; localparam STATE_READ_REGISTER_2 = 2; localparam STATE_WRITE_REGISTER_1 = 3; localparam STATE_WRITE_REGISTER_2 = 4; localparam STATE_TX_1 = 5; localparam STATE_TX_2 = 6; localparam STATE_TX_3 = 7; localparam STATE_RX_1 = 8; localparam STATE_RX_2 = 9; localparam STATE_RX_3 = 10; localparam STATE_RX_4 = 11; localparam STATE_RESET = 12; reg [7:0] state = STATE_IDLE; reg [7:0] next_state; reg [7:0] control_register = DEFAULT_CONTROL_REGISTER; reg [7:0] next_control_register; reg [7:0] register_mask; reg [7:0] next_register_mask; reg [7:0] command; reg [7:0] next_command; reg [7:0] next_spi_tx_data; reg next_spi_tx_strobe; reg next_tx_reset; reg previous_tx_active; reg [9:0] next_tx_data; reg tx_data_valid = 0; reg next_tx_data_valid; reg next_tx_load_strobe; reg next_tx_start_strobe; reg tx_complete = 0; reg next_tx_complete; reg next_rx_reset; reg next_rx_read_strobe; reg [15:0] rx_buffer; reg [15:0] next_rx_buffer; reg [1:0] spi_cs_n_d; always @(posedge clk) begin spi_cs_n_d <= { spi_cs_n_d[0], spi_cs_n }; end always @(*) begin next_state = state; next_control_register = control_register; next_register_mask = register_mask; next_command = command; next_spi_tx_data = spi_tx_data; next_spi_tx_strobe = 0; next_tx_reset = 0; next_tx_data = tx_data; next_tx_data_valid = tx_data_valid; next_tx_load_strobe = 0; next_tx_start_strobe = 0; next_tx_complete = tx_complete; next_rx_reset = 0; next_rx_read_strobe = 0; next_rx_buffer = rx_buffer; case (state) STATE_IDLE: begin if (spi_rx_strobe) begin next_command = spi_rx_data; case (spi_rx_data[3:0]) 4'h2: next_state = STATE_READ_REGISTER_1; 4'h3: next_state = STATE_WRITE_REGISTER_1; 4'h4: next_state = STATE_TX_1; 4'h5: next_state = STATE_RX_1; 4'hf: next_state = STATE_RESET; endcase end end STATE_READ_REGISTER_1: begin next_spi_tx_data = 0; case (command[7:4]) 4'h1: next_spi_tx_data = { 1'b0, rx_error, rx_active, 1'b0, tx_complete, tx_active, 2'b0 }; 4'h2: next_spi_tx_data = control_register; 4'hf: next_spi_tx_data = 8'ha5; endcase next_spi_tx_strobe = 1; next_state = STATE_READ_REGISTER_2; end STATE_READ_REGISTER_2: begin if (spi_rx_strobe) next_state = STATE_READ_REGISTER_1; end STATE_WRITE_REGISTER_1: begin if (spi_rx_strobe) begin next_register_mask = spi_rx_data; next_state = STATE_WRITE_REGISTER_2; end end STATE_WRITE_REGISTER_2: begin if (spi_rx_strobe) begin case (command[7:4]) 4'h2: next_control_register = (control_register & ~register_mask) | (spi_rx_data & register_mask); endcase next_state = STATE_IDLE; end end STATE_TX_1: begin next_tx_complete = 0; next_state = STATE_TX_2; end STATE_TX_2: begin if (spi_rx_strobe) begin next_tx_data_valid = 0; if (tx_full) begin next_spi_tx_data = 8'b10000001; // Overflow next_spi_tx_strobe = 1; end else if (!tx_ready) begin next_spi_tx_data = 8'b10000010; // Underflow next_spi_tx_strobe = 1; end else begin next_tx_data = { spi_rx_data[1:0], 8'b00000000 }; next_tx_data_valid = 1; next_spi_tx_data = 8'h00; next_spi_tx_strobe = 1; end next_state = STATE_TX_3; end end STATE_TX_3: begin if (spi_rx_strobe) begin next_tx_data = { tx_data[9:8], spi_rx_data }; next_tx_load_strobe = tx_data_valid; next_state = STATE_TX_2; end end STATE_RX_1: begin next_rx_buffer = { rx_error, rx_empty, 4'b0000, rx_data }; next_state = STATE_RX_2; end STATE_RX_2: begin next_spi_tx_data = rx_buffer[15:8]; next_spi_tx_strobe = 1; next_state = STATE_RX_3; end STATE_RX_3: begin if (spi_rx_strobe) begin next_spi_tx_data = rx_buffer[7:0]; next_spi_tx_strobe = 1; // Reset on error and only dequeue if not empty. if (rx_buffer[15]) next_rx_reset = 1; // TODO: should this be more explicit? else if (!rx_buffer[14]) next_rx_read_strobe = 1; next_state = STATE_RX_4; end end STATE_RX_4: begin if (spi_rx_strobe) next_state = STATE_RX_1; end STATE_RESET: begin // TODO: should this also reset the control register flags // like loopback? next_tx_reset = 1; next_tx_complete = 0; next_rx_reset = 1; next_state = STATE_IDLE; end endcase if (spi_cs_n_d[1]) begin if (!tx_empty && !tx_active) next_tx_start_strobe = 1; // TODO: this can last for multiple clock cycles, but that is okay... next_state = STATE_IDLE; end if (!tx_active && previous_tx_active) next_tx_complete = 1; end always @(posedge clk) begin state <= next_state; control_register <= next_control_register; register_mask <= next_register_mask; command <= next_command; spi_tx_data <= next_spi_tx_data; spi_tx_strobe <= next_spi_tx_strobe; tx_reset <= next_tx_reset; tx_data <= next_tx_data; tx_data_valid <= next_tx_data_valid; tx_load_strobe <= next_tx_load_strobe; tx_start_strobe <= next_tx_start_strobe; tx_complete <= next_tx_complete; rx_reset <= next_rx_reset; rx_read_strobe <= next_rx_read_strobe; rx_buffer <= next_rx_buffer; if (reset) begin state <= STATE_IDLE; control_register <= DEFAULT_CONTROL_REGISTER; command <= 0; spi_tx_data <= 0; spi_tx_strobe <= 0; tx_reset <= 0; tx_data <= 0; tx_load_strobe <= 0; tx_start_strobe <= 0; tx_complete <= 0; rx_reset <= 0; rx_read_strobe <= 0; rx_buffer <= 0; end previous_tx_active <= tx_active; end assign loopback = control_register[0]; assign tx_parity = control_register[3]; assign rx_parity = control_register[6]; endmodule