mirror of
https://github.com/lowobservable/coax.git
synced 2026-01-21 10:32:22 +00:00
352 lines
9.5 KiB
Verilog
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
|