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.
229 lines
5.1 KiB
Systemverilog
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
|