blob: c0f775fd2f82487d2e69674d35d6167af2e95d8c [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Description: UART core module
//
module uart_core (
input clk_i,
input rst_ni,
input uart_reg_pkg::uart_reg2hw_t reg2hw,
output uart_reg_pkg::uart_hw2reg_t hw2reg,
input rx,
output logic tx,
output logic intr_tx_watermark_o,
output logic intr_rx_watermark_o,
output logic intr_tx_empty_o,
output logic intr_rx_overflow_o,
output logic intr_rx_frame_err_o,
output logic intr_rx_break_err_o,
output logic intr_rx_timeout_o,
output logic intr_rx_parity_err_o
);
import uart_reg_pkg::*;
localparam int NcoWidth = $bits(reg2hw.ctrl.nco.q);
logic [15:0] rx_val_q;
logic [7:0] uart_rdata;
logic tick_baud_x16, rx_tick_baud;
logic [5:0] tx_fifo_depth, rx_fifo_depth;
logic [5:0] rx_fifo_depth_prev_q;
logic [23:0] rx_timeout_count_d, rx_timeout_count_q, uart_rxto_val;
logic rx_fifo_depth_changed, uart_rxto_en;
logic tx_enable, rx_enable;
logic sys_loopback, line_loopback, rxnf_enable;
logic uart_fifo_rxrst, uart_fifo_txrst;
logic [2:0] uart_fifo_rxilvl;
logic [1:0] uart_fifo_txilvl;
logic ovrd_tx_en, ovrd_tx_val;
logic [7:0] tx_fifo_data;
logic tx_fifo_rready, tx_fifo_rvalid;
logic tx_fifo_wready, tx_uart_idle;
logic tx_out;
logic tx_out_q;
logic [7:0] rx_fifo_data;
logic rx_valid, rx_fifo_wvalid, rx_fifo_rvalid;
logic rx_fifo_wready, rx_uart_idle;
logic rx_sync;
logic rx_in;
logic break_err;
logic [4:0] allzero_cnt_d, allzero_cnt_q;
logic allzero_err, not_allzero_char;
logic event_tx_watermark, event_rx_watermark, event_tx_empty, event_rx_overflow;
logic event_rx_frame_err, event_rx_break_err, event_rx_timeout, event_rx_parity_err;
logic tx_watermark_d, tx_watermark_prev_q;
logic rx_watermark_d, rx_watermark_prev_q;
logic tx_uart_idle_q;
assign tx_enable = reg2hw.ctrl.tx.q;
assign rx_enable = reg2hw.ctrl.rx.q;
assign rxnf_enable = reg2hw.ctrl.nf.q;
assign sys_loopback = reg2hw.ctrl.slpbk.q;
assign line_loopback = reg2hw.ctrl.llpbk.q;
assign uart_fifo_rxrst = reg2hw.fifo_ctrl.rxrst.q & reg2hw.fifo_ctrl.rxrst.qe;
assign uart_fifo_txrst = reg2hw.fifo_ctrl.txrst.q & reg2hw.fifo_ctrl.txrst.qe;
assign uart_fifo_rxilvl = reg2hw.fifo_ctrl.rxilvl.q;
assign uart_fifo_txilvl = reg2hw.fifo_ctrl.txilvl.q;
assign ovrd_tx_en = reg2hw.ovrd.txen.q;
assign ovrd_tx_val = reg2hw.ovrd.txval.q;
typedef enum logic {
BRK_CHK,
BRK_WAIT
} break_st_e ;
break_st_e break_st_q;
assign not_allzero_char = rx_valid & (~event_rx_frame_err | (rx_fifo_data != 8'h0));
assign allzero_err = event_rx_frame_err & (rx_fifo_data == 8'h0);
assign allzero_cnt_d = (break_st_q == BRK_WAIT || not_allzero_char) ? 5'h0 :
//allzero_cnt_q[4] never be 1b without break_st_q as BRK_WAIT
//allzero_cnt_q[4] ? allzero_cnt_q :
allzero_err ? allzero_cnt_q + 5'd1 :
allzero_cnt_q;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) allzero_cnt_q <= '0;
else if (rx_enable) allzero_cnt_q <= allzero_cnt_d;
end
// break_err edges in same cycle as event_rx_frame_err edges ; that way the
// reset-on-read works the same way for break and frame error interrupts.
always_comb begin
unique case (reg2hw.ctrl.rxblvl.q)
2'h0: break_err = allzero_cnt_d >= 5'd2;
2'h1: break_err = allzero_cnt_d >= 5'd4;
2'h2: break_err = allzero_cnt_d >= 5'd8;
default: break_err = allzero_cnt_d >= 5'd16;
endcase
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) break_st_q <= BRK_CHK;
else begin
unique case (break_st_q)
BRK_CHK: begin
if (event_rx_break_err) break_st_q <= BRK_WAIT;
end
BRK_WAIT: begin
if (rx_in) break_st_q <= BRK_CHK;
end
default: begin
break_st_q <= BRK_CHK;
end
endcase
end
end
assign hw2reg.val.d = rx_val_q;
assign hw2reg.rdata.d = uart_rdata;
assign hw2reg.status.rxempty.d = ~rx_fifo_rvalid;
assign hw2reg.status.rxidle.d = rx_uart_idle;
assign hw2reg.status.txidle.d = tx_uart_idle & ~tx_fifo_rvalid;
assign hw2reg.status.txempty.d = ~tx_fifo_rvalid;
assign hw2reg.status.rxfull.d = ~rx_fifo_wready;
assign hw2reg.status.txfull.d = ~tx_fifo_wready;
assign hw2reg.fifo_status.txlvl.d = tx_fifo_depth;
assign hw2reg.fifo_status.rxlvl.d = rx_fifo_depth;
// resets are self-clearing, so need to update FIFO_CTRL
assign hw2reg.fifo_ctrl.rxilvl.de = 1'b0;
assign hw2reg.fifo_ctrl.rxilvl.d = 3'h0;
assign hw2reg.fifo_ctrl.txilvl.de = 1'b0;
assign hw2reg.fifo_ctrl.txilvl.d = 2'h0;
// NCO 16x Baud Generator
// output clock rate is:
// Fin * (NCO/2**NcoWidth)
logic [NcoWidth:0] nco_sum_q; // extra bit to get the carry
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
nco_sum_q <= 17'h0;
end else if (tx_enable || rx_enable) begin
nco_sum_q <= {1'b0,nco_sum_q[NcoWidth-1:0]} + {1'b0,reg2hw.ctrl.nco.q[NcoWidth-1:0]};
end
end
assign tick_baud_x16 = nco_sum_q[16];
//////////////
// TX Logic //
//////////////
assign tx_fifo_rready = tx_uart_idle & tx_fifo_rvalid & tx_enable;
prim_fifo_sync #(
.Width (8),
.Pass (1'b0),
.Depth (32)
) u_uart_txfifo (
.clk_i,
.rst_ni,
.clr_i (uart_fifo_txrst),
.wvalid_i(reg2hw.wdata.qe),
.wready_o(tx_fifo_wready),
.wdata_i (reg2hw.wdata.q),
.depth_o (tx_fifo_depth),
.full_o (),
.rvalid_o(tx_fifo_rvalid),
.rready_i(tx_fifo_rready),
.rdata_o (tx_fifo_data),
.err_o ()
);
uart_tx uart_tx (
.clk_i,
.rst_ni,
.tx_enable,
.tick_baud_x16,
.parity_enable (reg2hw.ctrl.parity_en.q),
.wr (tx_fifo_rready),
.wr_parity ((^tx_fifo_data) ^ reg2hw.ctrl.parity_odd.q),
.wr_data (tx_fifo_data),
.idle (tx_uart_idle),
.tx (tx_out)
);
assign tx = line_loopback ? rx : tx_out_q ;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
tx_out_q <= 1'b1;
end else if (ovrd_tx_en) begin
tx_out_q <= ovrd_tx_val ;
end else if (sys_loopback) begin
tx_out_q <= 1'b1;
end else begin
tx_out_q <= tx_out;
end
end
//////////////
// RX Logic //
//////////////
// sync the incoming data
prim_flop_2sync #(
.Width(1),
.ResetValue(1'b1)
) sync_rx (
.clk_i,
.rst_ni,
.d_i(rx),
.q_o(rx_sync)
);
// Based on: en.wikipedia.org/wiki/Repetition_code mentions the use of a majority filter
// in UART to ignore brief noise spikes
logic rx_sync_q1, rx_sync_q2, rx_in_mx, rx_in_maj;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
rx_sync_q1 <= 1'b1;
rx_sync_q2 <= 1'b1;
end else begin
rx_sync_q1 <= rx_sync;
rx_sync_q2 <= rx_sync_q1;
end
end
assign rx_in_maj = (rx_sync & rx_sync_q1) |
(rx_sync & rx_sync_q2) |
(rx_sync_q1 & rx_sync_q2);
assign rx_in_mx = rxnf_enable ? rx_in_maj : rx_sync;
assign rx_in = sys_loopback ? tx_out :
line_loopback ? 1'b1 :
rx_in_mx;
uart_rx uart_rx (
.clk_i,
.rst_ni,
.rx_enable,
.tick_baud_x16,
.parity_enable (reg2hw.ctrl.parity_en.q),
.parity_odd (reg2hw.ctrl.parity_odd.q),
.tick_baud (rx_tick_baud),
.rx_valid,
.rx_data (rx_fifo_data),
.idle (rx_uart_idle),
.frame_err (event_rx_frame_err),
.rx (rx_in),
.rx_parity_err (event_rx_parity_err)
);
assign rx_fifo_wvalid = rx_valid & ~event_rx_frame_err & ~event_rx_parity_err;
prim_fifo_sync #(
.Width (8),
.Pass (1'b0),
.Depth (32)
) u_uart_rxfifo (
.clk_i,
.rst_ni,
.clr_i (uart_fifo_rxrst),
.wvalid_i(rx_fifo_wvalid),
.wready_o(rx_fifo_wready),
.wdata_i (rx_fifo_data),
.depth_o (rx_fifo_depth),
.full_o (),
.rvalid_o(rx_fifo_rvalid),
.rready_i(reg2hw.rdata.re),
.rdata_o (uart_rdata),
.err_o ()
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) rx_val_q <= 16'h0;
else if (tick_baud_x16) rx_val_q <= {rx_val_q[14:0], rx_in};
end
////////////////////////
// Interrupt & Status //
////////////////////////
always_comb begin
unique case(uart_fifo_txilvl)
2'h0: tx_watermark_d = (tx_fifo_depth < 6'd2);
2'h1: tx_watermark_d = (tx_fifo_depth < 6'd4);
2'h2: tx_watermark_d = (tx_fifo_depth < 6'd8);
default: tx_watermark_d = (tx_fifo_depth < 6'd16);
endcase
end
assign event_tx_watermark = tx_watermark_d & ~tx_watermark_prev_q;
// The empty condition handling is a bit different.
// If empty rising conditions were detected directly, then every first write of a burst
// would trigger an empty. This is due to the fact that the uart_tx fsm immediately
// withdraws the content and asserts "empty".
// To guard against this false trigger, empty is qualified with idle to extend the window
// in which software has an opportunity to deposit new data.
// However, if software deposit speed is TOO slow, this would still be an issue.
//
// The alternative software fix is to disable tx_enable until it has a chance to
// burst in the desired amount of data.
assign event_tx_empty = ~tx_fifo_rvalid & ~tx_uart_idle_q & tx_uart_idle;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
tx_watermark_prev_q <= 1'b1; // by default watermark condition is true
rx_watermark_prev_q <= 1'b0; // by default watermark condition is false
tx_uart_idle_q <= 1'b1;
end else begin
tx_watermark_prev_q <= tx_watermark_d;
rx_watermark_prev_q <= rx_watermark_d;
tx_uart_idle_q <= tx_uart_idle;
end
end
always_comb begin
unique case(uart_fifo_rxilvl)
3'h0: rx_watermark_d = (rx_fifo_depth >= 6'd1);
3'h1: rx_watermark_d = (rx_fifo_depth >= 6'd4);
3'h2: rx_watermark_d = (rx_fifo_depth >= 6'd8);
3'h3: rx_watermark_d = (rx_fifo_depth >= 6'd16);
3'h4: rx_watermark_d = (rx_fifo_depth >= 6'd30);
default: rx_watermark_d = 1'b0;
endcase
end
assign event_rx_watermark = rx_watermark_d & ~rx_watermark_prev_q;
// rx timeout interrupt
assign uart_rxto_en = reg2hw.timeout_ctrl.en.q;
assign uart_rxto_val = reg2hw.timeout_ctrl.val.q;
assign rx_fifo_depth_changed = (rx_fifo_depth != rx_fifo_depth_prev_q);
assign rx_timeout_count_d =
// don't count if timeout feature not enabled ;
// will never reach timeout val + lower power
(uart_rxto_en == 1'b0) ? 24'd0 :
// reset count if timeout interrupt is set
event_rx_timeout ? 24'd0 :
// reset count upon change in fifo level: covers both read and receiving a new byte
rx_fifo_depth_changed ? 24'd0 :
// reset count if no bytes are pending
(rx_fifo_depth == '0) ? 24'd0 :
// stop the count at timeout value (this will set the interrupt)
// Removed below line as when the timeout reaches the value,
// event occured, and timeout value reset to 0h.
//(rx_timeout_count_q == uart_rxto_val) ? rx_timeout_count_q :
// increment if at rx baud tick
rx_tick_baud ? (rx_timeout_count_q + 24'd1) :
rx_timeout_count_q;
assign event_rx_timeout = (rx_timeout_count_q == uart_rxto_val) & uart_rxto_en;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
rx_timeout_count_q <= 24'd0;
rx_fifo_depth_prev_q <= 6'd0;
end else begin
rx_timeout_count_q <= rx_timeout_count_d;
rx_fifo_depth_prev_q <= rx_fifo_depth;
end
end
assign event_rx_overflow = rx_fifo_wvalid & ~rx_fifo_wready;
assign event_rx_break_err = break_err & (break_st_q == BRK_CHK);
// instantiate interrupt hardware primitives
prim_intr_hw #(.Width(1)) intr_hw_tx_watermark (
.clk_i,
.rst_ni,
.event_intr_i (event_tx_watermark),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.tx_watermark.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.tx_watermark.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.tx_watermark.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.tx_watermark.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.tx_watermark.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.tx_watermark.d),
.intr_o (intr_tx_watermark_o)
);
prim_intr_hw #(.Width(1)) intr_hw_rx_watermark (
.clk_i,
.rst_ni,
.event_intr_i (event_rx_watermark),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.rx_watermark.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.rx_watermark.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.rx_watermark.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.rx_watermark.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.rx_watermark.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.rx_watermark.d),
.intr_o (intr_rx_watermark_o)
);
prim_intr_hw #(.Width(1)) intr_hw_tx_empty (
.clk_i,
.rst_ni,
.event_intr_i (event_tx_empty),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.tx_empty.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.tx_empty.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.tx_empty.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.tx_empty.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.tx_empty.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.tx_empty.d),
.intr_o (intr_tx_empty_o)
);
prim_intr_hw #(.Width(1)) intr_hw_rx_overflow (
.clk_i,
.rst_ni,
.event_intr_i (event_rx_overflow),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.rx_overflow.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.rx_overflow.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.rx_overflow.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.rx_overflow.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.rx_overflow.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.rx_overflow.d),
.intr_o (intr_rx_overflow_o)
);
prim_intr_hw #(.Width(1)) intr_hw_rx_frame_err (
.clk_i,
.rst_ni,
.event_intr_i (event_rx_frame_err),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.rx_frame_err.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.rx_frame_err.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.rx_frame_err.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.rx_frame_err.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.rx_frame_err.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.rx_frame_err.d),
.intr_o (intr_rx_frame_err_o)
);
prim_intr_hw #(.Width(1)) intr_hw_rx_break_err (
.clk_i,
.rst_ni,
.event_intr_i (event_rx_break_err),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.rx_break_err.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.rx_break_err.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.rx_break_err.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.rx_break_err.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.rx_break_err.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.rx_break_err.d),
.intr_o (intr_rx_break_err_o)
);
prim_intr_hw #(.Width(1)) intr_hw_rx_timeout (
.clk_i,
.rst_ni,
.event_intr_i (event_rx_timeout),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.rx_timeout.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.rx_timeout.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.rx_timeout.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.rx_timeout.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.rx_timeout.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.rx_timeout.d),
.intr_o (intr_rx_timeout_o)
);
prim_intr_hw #(.Width(1)) intr_hw_rx_parity_err (
.clk_i,
.rst_ni,
.event_intr_i (event_rx_parity_err),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.rx_parity_err.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.rx_parity_err.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.rx_parity_err.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.rx_parity_err.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.rx_parity_err.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.rx_parity_err.d),
.intr_o (intr_rx_parity_err_o)
);
// unused registers
logic unused_reg;
assign unused_reg = ^reg2hw.alert_test;
endmodule