Add UART receive module
Add the recieve half of the UART. It's more or less the inverse of the transmit half, except we manage the state explicitly. I originally did this in hopes that yosys would recode the FSM, but it doesn't like the subtraction in the D* states. I left in the async reset anyway since it reduces the LUT count. Signed-off-by: Sean Anderson <seanga2@gmail.com>
This commit is contained in:
parent
3f61f85a1f
commit
a549fca957
1
Makefile
1
Makefile
|
@ -143,6 +143,7 @@ MODULES += pmd_dp83223
|
||||||
MODULES += pmd_dp83223_rx
|
MODULES += pmd_dp83223_rx
|
||||||
MODULES += scramble
|
MODULES += scramble
|
||||||
MODULES += uart_tx
|
MODULES += uart_tx
|
||||||
|
MODULES += uart_rx
|
||||||
MODULES += wb_mux
|
MODULES += wb_mux
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
|
|
|
@ -205,6 +205,12 @@ This module implements a scrambler as described in ANSI X3.264-1995 section
|
||||||
A standard UART transmit module, accepting AXI-stream. 8n1 only. Supports
|
A standard UART transmit module, accepting AXI-stream. 8n1 only. Supports
|
||||||
115,200 and 4,000,000 baud.
|
115,200 and 4,000,000 baud.
|
||||||
|
|
||||||
|
=== `uart_rx`
|
||||||
|
|
||||||
|
A standard UART receive module, outputting AXI-stream. 8n1 only. Supports
|
||||||
|
115,200 and 4,000,000 baud. Properly detects breaks as (single) frame errors,
|
||||||
|
and ignores runt start bits.
|
||||||
|
|
||||||
=== `wb_mux`
|
=== `wb_mux`
|
||||||
|
|
||||||
This implements a simple Wishbone mux, allowing a single master to access
|
This implements a simple Wishbone mux, allowing a single master to access
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-Only
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
||||||
|
*
|
||||||
|
* 8n1@115200; no one uses anything else (and neither do I)
|
||||||
|
*/
|
||||||
|
|
||||||
|
`include "common.vh"
|
||||||
|
|
||||||
|
module uart_rx (
|
||||||
|
input clk,
|
||||||
|
input rst,
|
||||||
|
|
||||||
|
output reg [7:0] data,
|
||||||
|
input ready,
|
||||||
|
output reg valid,
|
||||||
|
|
||||||
|
input rx,
|
||||||
|
|
||||||
|
/* Run at 4M for testing */
|
||||||
|
input high_speed,
|
||||||
|
/* No one ready */
|
||||||
|
output reg overflow,
|
||||||
|
/* Missing stop bit */
|
||||||
|
output reg frame_error
|
||||||
|
);
|
||||||
|
|
||||||
|
/* 1085 cycles, for 115200 baud with a 125 MHz clock */
|
||||||
|
parameter SLOW_FULL = 11'h78c;
|
||||||
|
parameter SLOW_HALF = 11'h202;
|
||||||
|
/* 31 cycles, for 4M baud with a 125 MHz clock */
|
||||||
|
parameter FAST_FULL = 11'h68e;
|
||||||
|
parameter FAST_HALF = 11'h34c;
|
||||||
|
|
||||||
|
localparam ERROR = 11;
|
||||||
|
localparam IDLE = 10;
|
||||||
|
localparam START = 9;
|
||||||
|
localparam D0 = 8;
|
||||||
|
localparam D1 = 7;
|
||||||
|
localparam D2 = 6;
|
||||||
|
localparam D3 = 5;
|
||||||
|
localparam D4 = 4;
|
||||||
|
localparam D5 = 3;
|
||||||
|
localparam D6 = 2;
|
||||||
|
localparam D7 = 1;
|
||||||
|
localparam STOP = 0;
|
||||||
|
|
||||||
|
reg [3:0] state, state_next;
|
||||||
|
reg [10:0] lfsr, lfsr_next;
|
||||||
|
reg [7:0] bits, bits_next, data_next;
|
||||||
|
reg ready_last;
|
||||||
|
reg valid_next, overflow_next, frame_error_next;
|
||||||
|
|
||||||
|
always @(*) begin
|
||||||
|
state_next = state;
|
||||||
|
lfsr_next = { lfsr[9:0], lfsr[10] ^ lfsr[8] };
|
||||||
|
bits_next = bits;
|
||||||
|
data_next = data;
|
||||||
|
valid_next = valid && !ready_last;
|
||||||
|
overflow_next = 0;
|
||||||
|
frame_error_next = 0;
|
||||||
|
|
||||||
|
case (state)
|
||||||
|
IDLE: if (!rx) begin
|
||||||
|
state_next = START;
|
||||||
|
lfsr_next = high_speed ? FAST_HALF : SLOW_HALF;
|
||||||
|
end
|
||||||
|
START: if (&lfsr) begin
|
||||||
|
state_next = rx ? IDLE : D0;
|
||||||
|
lfsr_next = high_speed ? FAST_FULL : SLOW_FULL;
|
||||||
|
end
|
||||||
|
D0, D1, D2, D3, D4, D5, D6, D7: if (&lfsr) begin
|
||||||
|
lfsr_next = high_speed ? FAST_FULL : SLOW_FULL;
|
||||||
|
bits_next = { rx, bits[7:1] };
|
||||||
|
state_next = state - 1;
|
||||||
|
end
|
||||||
|
STOP: if (&lfsr) begin
|
||||||
|
lfsr_next = high_speed ? FAST_FULL : SLOW_FULL;
|
||||||
|
if (rx) begin
|
||||||
|
state_next = IDLE;
|
||||||
|
if (valid_next)
|
||||||
|
overflow_next = 1;
|
||||||
|
else
|
||||||
|
data_next = bits;
|
||||||
|
valid_next = 1;
|
||||||
|
end else begin
|
||||||
|
frame_error_next = 1;
|
||||||
|
state_next = ERROR;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ERROR: if (&lfsr) begin
|
||||||
|
lfsr_next = high_speed ? FAST_FULL : SLOW_FULL;
|
||||||
|
if (rx)
|
||||||
|
state_next = IDLE;
|
||||||
|
end
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
lfsr <= lfsr_next;
|
||||||
|
bits <= bits_next;
|
||||||
|
data <= data_next;
|
||||||
|
end
|
||||||
|
|
||||||
|
always @(posedge clk, posedge rst) begin
|
||||||
|
if (rst) begin
|
||||||
|
state <= IDLE;
|
||||||
|
valid <= 0;
|
||||||
|
ready_last <= 0;
|
||||||
|
overflow <= 0;
|
||||||
|
frame_error <= 0;
|
||||||
|
end else begin
|
||||||
|
state <= state_next;
|
||||||
|
valid <= valid_next;
|
||||||
|
ready_last <= ready;
|
||||||
|
overflow <= overflow_next;
|
||||||
|
frame_error <= frame_error_next;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
|
@ -0,0 +1,80 @@
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-Only
|
||||||
|
# Copyright (C) 2023 Sean Anderson <seanga2@gmail.com>
|
||||||
|
|
||||||
|
import cocotb
|
||||||
|
from cocotb.binary import BinaryValue
|
||||||
|
from cocotb.clock import Clock
|
||||||
|
from cocotb.triggers import FallingEdge, Timer
|
||||||
|
from cocotb.utils import get_sim_time, get_sim_steps
|
||||||
|
|
||||||
|
from .axis_replay_buffer import recv_packet
|
||||||
|
|
||||||
|
BAUD = 4e6
|
||||||
|
BIT_STEPS = get_sim_steps(1 / BAUD, 'sec', round_mode='round')
|
||||||
|
|
||||||
|
def as_bits(c):
|
||||||
|
for _ in range(8):
|
||||||
|
yield c & 1
|
||||||
|
c >>= 1
|
||||||
|
|
||||||
|
@cocotb.test(timeout_time=1, timeout_unit='ms')
|
||||||
|
async def test_rx(uart):
|
||||||
|
uart.clk.value = BinaryValue('Z')
|
||||||
|
uart.rst.value = 1
|
||||||
|
uart.ready.value = 1
|
||||||
|
uart.rx.value = 1
|
||||||
|
uart.high_speed.value = 1
|
||||||
|
|
||||||
|
await Timer(1)
|
||||||
|
uart.rst.value = 0
|
||||||
|
await cocotb.start(Clock(uart.clk, 8, units='ns').start())
|
||||||
|
await FallingEdge(uart.clk)
|
||||||
|
|
||||||
|
async def putchar(c):
|
||||||
|
for bit in (0, *as_bits(c), 1):
|
||||||
|
uart.rx.value = bit
|
||||||
|
await Timer(BIT_STEPS)
|
||||||
|
|
||||||
|
msg = b"Hell\0"
|
||||||
|
signals = {
|
||||||
|
'clk': uart.clk,
|
||||||
|
'data': uart.data,
|
||||||
|
'valid': uart.valid,
|
||||||
|
'ready': uart.ready,
|
||||||
|
}
|
||||||
|
|
||||||
|
await cocotb.start(recv_packet(signals, msg))
|
||||||
|
for c in msg:
|
||||||
|
await putchar(c)
|
||||||
|
|
||||||
|
overflows = 0
|
||||||
|
frame_errors = 0
|
||||||
|
|
||||||
|
async def count_errors():
|
||||||
|
nonlocal overflows
|
||||||
|
nonlocal frame_errors
|
||||||
|
while True:
|
||||||
|
await FallingEdge(uart.clk)
|
||||||
|
overflows += uart.overflow.value
|
||||||
|
frame_errors += uart.frame_error.value
|
||||||
|
|
||||||
|
monitor = await cocotb.start(count_errors())
|
||||||
|
|
||||||
|
uart.rx.value = 0
|
||||||
|
await Timer(BIT_STEPS * 20)
|
||||||
|
|
||||||
|
uart.rx.value = 1
|
||||||
|
await Timer(BIT_STEPS)
|
||||||
|
|
||||||
|
assert frame_errors == 1
|
||||||
|
|
||||||
|
uart.ready.value = 0
|
||||||
|
await putchar(0xFF)
|
||||||
|
await putchar(0)
|
||||||
|
|
||||||
|
assert overflows == 1
|
||||||
|
|
||||||
|
uart.ready.value = 1
|
||||||
|
await recv_packet(signals, (0xFF,))
|
||||||
|
|
||||||
|
monitor.kill()
|
Loading…
Reference in New Issue