2021-11-06 13:32:39 -05:00

352 lines
9.5 KiB
Verilog

// 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_protocol,
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_protocol,
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_protocol = control_register[2];
assign tx_parity = control_register[3];
assign rx_protocol = control_register[5];
assign rx_parity = control_register[6];
endmodule