Files
super6502/hw/fpga/uart.sv
Byron Lathi 59d2d4f601 Add receive logic to UART
The UART has a receive buffer which will fill up when it receives bytes.
Once the buffer is full, it raises the RX flag until the value is read
by the cpu.
2022-03-14 16:41:59 -05:00

229 lines
5.1 KiB
Systemverilog

module uart(
input clk_50,
input clk,
input rst,
input cs,
input rw,
input [7:0] data_in,
input [1:0] addr,
input RXD,
output logic TXD,
output logic irq,
output logic [7:0] data_out
);
//Handle reading and writing registers
logic [7:0] tx_buf;
logic [7:0] rx_buf;
logic [7:0] status;
logic tx_flag;
logic rx_flag;
logic tx_flag_set;
logic tx_flag_clear;
logic rx_flag_set;
logic rx_flag_clear;
assign status[0] = tx_flag | tx_flag_clear;
assign status[1] = rx_flag | rx_flag_set;
assign irq = status[1];
always_ff @(posedge clk) begin
if (rst) begin
tx_flag_set <= '0;
rx_flag_clear <= '0;
tx_buf <= '0;
status[7:2] <= '0;
end
if (cs) begin
if (~rw) begin
if (addr == 0)
tx_buf <= data_in;
end else begin
if (addr == 0)
data_out <= rx_buf;
if (addr == 1)
data_out <= status;
end
end
if (~rw & cs && addr == 0)
tx_flag_set <= '1;
else
tx_flag_set <= '0;
if (rw & cs && addr == 0)
rx_flag_clear <= '1;
else
rx_flag_clear <= '0;
end
// tx state controller
typedef enum bit [2:0] {START, DATA, PARITY, STOP, IDLE} macro_t;
struct packed {
macro_t macro;
logic [3:0] count;
} tx_state, tx_next_state, rx_state, rx_next_state;
localparam logic [3:0] maxcount = 4'h7;
// baud rate: 9600
localparam baud = 9600;
localparam count = (50000000/baud)-1;
logic [14:0] tx_clkdiv;
always_ff @(posedge clk_50) begin
if (rst) begin
tx_clkdiv <= 0;
tx_state.macro <= IDLE;
tx_state.count <= 3'b0;
tx_flag <= '0;
end else begin
if (tx_flag_set)
tx_flag <= '1;
else if (tx_flag_clear)
tx_flag <= '0;
if (tx_clkdiv == count) begin
tx_clkdiv <= 0;
tx_state <= tx_next_state;
end else begin
tx_clkdiv <= tx_clkdiv + 15'b1;
end
end
end
always_comb begin
tx_next_state = tx_state;
unique case (tx_state.macro)
START: begin
tx_next_state.macro = DATA;
tx_next_state.count = 3'b0;
end
DATA: begin
if (tx_state.count == maxcount) begin
tx_next_state.macro = STOP; // or PARITY
tx_next_state.count = 3'b0;
end else begin
tx_next_state.count = tx_state.count + 3'b1;
tx_next_state.macro = DATA;
end
end
PARITY: begin
end
STOP: begin
tx_next_state.macro = IDLE;
tx_next_state.count = '0;
end
IDLE: begin
if (tx_flag)
tx_next_state.macro = START;
else
tx_next_state.macro = IDLE;
end
default:;
endcase
end
always_comb begin
TXD = '1;
tx_flag_clear = '0;
unique case (tx_state.macro)
START: begin
TXD = '0;
end
DATA: begin
TXD = tx_buf[tx_state.count];
end
PARITY: begin
end
STOP: begin
tx_flag_clear = '1;
TXD = '1;
end
IDLE: begin
TXD = '1;
end
default:;
endcase
end
//basically in idle state we need to sample RXD very fast,
//then as soon as we detect that RXD is low, we start clkdiv
//going and then go into the start state.
logic [14:0] rx_clkdiv;
always_ff @(posedge clk_50) begin
if (rst) begin
rx_buf <= '0;
rx_clkdiv <= 0;
rx_state.macro <= IDLE;
rx_state.count <= 3'b0;
end else begin
if (rx_flag_set)
rx_flag <= '1;
else if (rx_flag_clear)
rx_flag <= '0;
if (rx_state.macro == IDLE) begin // Sample constantly in idle state
rx_state <= rx_next_state;
rx_clkdiv <= count/15'h2; // offset rx clock by 1/2 phase
end else begin
if (rx_clkdiv == count) begin // other states are as usual
rx_clkdiv <= 0;
rx_state <= rx_next_state;
if (rx_state.macro == DATA)
rx_buf[rx_state.count] = RXD;
end else begin
rx_clkdiv <= rx_clkdiv + 15'b1;
end
end
end
end
always_comb begin
rx_next_state = rx_state;
rx_flag_set = '0;
unique case (rx_state.macro)
IDLE: begin
if (~RXD)
rx_next_state.macro = START;
end
START: begin
rx_next_state.macro = DATA;
rx_next_state.count = 3'b0;
end
DATA: begin
if (rx_state.count == maxcount) begin
rx_next_state.macro = STOP;
rx_next_state.count = 3'b0;
end else begin
rx_next_state.count = rx_state.count + 3'b1;
rx_next_state.macro = DATA;
end
end
PARITY: begin
end
STOP: begin
rx_flag_set = '1;
rx_next_state.macro = IDLE;
end
endcase
end
endmodule