You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
222 lines
7.4 KiB
Python
222 lines
7.4 KiB
Python
from enum import Enum, unique
|
|
|
|
from nmigen import Signal, Elaboratable, Module
|
|
from nmigen_soc.wishbone import Interface as WishboneInterface
|
|
from nmigen.back import verilog
|
|
|
|
|
|
@unique
|
|
class RegEnum(Enum):
|
|
RXTX_DLL = 0
|
|
IER_DLM = 1
|
|
IIR_FCR = 2
|
|
LCR = 3
|
|
MCR = 4
|
|
LSR = 5
|
|
MSR = 6
|
|
SCR = 7
|
|
|
|
|
|
IER_ERBFI = 0
|
|
IER_ETBEI = 1
|
|
|
|
LCR_DLAB = 7
|
|
|
|
|
|
class VUart(Elaboratable):
|
|
"""
|
|
Virtual UART. Presents a 16550 style interface over a wishbone slave
|
|
and connects that to a read and write FIFO.
|
|
|
|
Parameters
|
|
----------
|
|
|
|
Attributes
|
|
----------
|
|
"""
|
|
def __init__(self):
|
|
# Write port of FIFO A
|
|
self.w_data = Signal(8)
|
|
self.w_rdy = Signal()
|
|
self.w_en = Signal()
|
|
|
|
# Read port of FIFO B
|
|
self.r_data = Signal(8)
|
|
self.r_rdy = Signal()
|
|
self.r_en = Signal()
|
|
|
|
self.irq = Signal()
|
|
|
|
# Wishbone slave
|
|
self.wb = WishboneInterface(data_width=8, addr_width=3)
|
|
|
|
def elaborate(self, platform):
|
|
m = Module()
|
|
|
|
# 16550 registers
|
|
ier = Signal(4) # Interrupt enable register
|
|
# ERBFI: enable RX interrupt
|
|
# ETBEI: enable TX interrupt
|
|
iir = Signal(4, reset=0b1) # Interrupt identification register
|
|
# Interrupt pending
|
|
# Interrupt ID
|
|
fcr = Signal(8) # FIFO control register, ignore
|
|
lcr = Signal(8) # Line control register, ignore all but DLAB bit
|
|
mcr = Signal(5) # Modem control register, ignore
|
|
lsr = Signal(8) # Line status register
|
|
# DR: 1 when something in the fifo, reset to 0 when fifo empty
|
|
# OE: RX fifo full and new character was attempted to add, need signal
|
|
# from remote, not fifo full condition
|
|
# THRE: always 1
|
|
# TEMPT: always 1
|
|
msr = Signal(8) # Modem status register, ignore
|
|
scr = Signal(8) # Scratch register, ignore
|
|
dll = Signal(8) # Divisor latch LS, ignore
|
|
dlm = Signal(8) # Divisor latch MS, ignore
|
|
|
|
# Some helpers
|
|
is_write = Signal()
|
|
is_read = Signal()
|
|
m.d.comb += [
|
|
is_write.eq(self.wb.stb & self.wb.cyc & self.wb.we),
|
|
is_read.eq(self.wb.stb & self.wb.cyc & ~self.wb.we),
|
|
]
|
|
|
|
dlab = Signal()
|
|
m.d.comb += dlab.eq(lcr[LCR_DLAB])
|
|
|
|
# Don't read from an empty FIFO
|
|
read_data = Signal(8)
|
|
m.d.comb += read_data.eq(0)
|
|
with m.If(self.r_rdy):
|
|
m.d.comb += read_data.eq(self.r_data)
|
|
|
|
m.d.sync += self.r_en.eq(0)
|
|
m.d.sync += self.w_en.eq(0)
|
|
|
|
# IRQ handling.
|
|
#
|
|
# On RX we raise an interrupt if there is anything in the RX FIFO. We
|
|
# could optimise this by checking for a certain FIFO depth as well as
|
|
# a timeout, similar to real 16550 hardware.
|
|
#
|
|
# For TX we can't assume the other end is consuming entries from the FIFO,
|
|
# so we can't hook the interrupt, THRE and TEMPT bits to it. For now we
|
|
# just always claim the TX FIFO is empty.
|
|
m.d.comb += self.irq.eq(0)
|
|
m.d.comb += iir.eq(0b0001) # IIR bit 0 is high for no IRQ
|
|
|
|
# Lower priority is the TX interrupt. In this case we always raise an
|
|
# interrupt if the enable bit is set
|
|
with m.If(ier[IER_ETBEI]):
|
|
m.d.comb += [
|
|
self.irq.eq(1),
|
|
iir.eq(0b0010),
|
|
]
|
|
|
|
# Highest priority is the RX interrupt. This overrides the TX interrupt
|
|
# above.
|
|
with m.If(ier[IER_ERBFI]):
|
|
# Are there any entries in the RX FIFO?
|
|
with m.If(self.r_rdy):
|
|
m.d.comb += [
|
|
self.irq.eq(1),
|
|
iir.eq(0b0100),
|
|
]
|
|
|
|
with m.FSM():
|
|
with m.State('IDLE'):
|
|
# Write
|
|
with m.If(is_write):
|
|
with m.Switch(self.wb.adr):
|
|
with m.Case(RegEnum.RXTX_DLL):
|
|
with m.If(dlab):
|
|
m.d.sync += dll.eq(self.wb.dat_w)
|
|
with m.Else():
|
|
m.d.sync += self.w_data.eq(self.wb.dat_w)
|
|
with m.If(self.w_rdy):
|
|
m.d.sync += self.w_en.eq(1)
|
|
|
|
with m.Case(RegEnum.IER_DLM):
|
|
with m.If(dlab):
|
|
m.d.sync += dlm.eq(self.wb.dat_w)
|
|
with m.Else():
|
|
m.d.sync += ier.eq(self.wb.dat_w)
|
|
|
|
with m.Case(RegEnum.IIR_FCR):
|
|
m.d.sync += fcr.eq(self.wb.dat_w)
|
|
|
|
with m.Case(RegEnum.LCR):
|
|
m.d.sync += lcr.eq(self.wb.dat_w)
|
|
|
|
with m.Case(RegEnum.MCR):
|
|
m.d.sync += mcr.eq(self.wb.dat_w)
|
|
|
|
with m.Case(RegEnum.MSR):
|
|
m.d.sync += msr.eq(self.wb.dat_w)
|
|
|
|
with m.Case(RegEnum.SCR):
|
|
m.d.sync += scr.eq(self.wb.dat_w)
|
|
|
|
m.d.sync += self.wb.ack.eq(1)
|
|
m.next = 'ACK'
|
|
|
|
# Read
|
|
with m.Elif(is_read):
|
|
with m.Switch(self.wb.adr):
|
|
with m.Case(RegEnum.RXTX_DLL):
|
|
with m.If(dlab):
|
|
m.d.sync += self.wb.dat_r.eq(dll)
|
|
with m.Else():
|
|
m.d.sync += self.wb.dat_r.eq(read_data)
|
|
with m.If(self.r_rdy):
|
|
m.d.sync += self.r_en.eq(1)
|
|
|
|
with m.Case(RegEnum.IER_DLM):
|
|
with m.If(dlab):
|
|
m.d.sync += self.wb.dat_r.eq(dlm)
|
|
with m.Else():
|
|
m.d.sync += self.wb.dat_r.eq(ier)
|
|
|
|
with m.Case(RegEnum.IIR_FCR):
|
|
m.d.sync += self.wb.dat_r.eq(iir)
|
|
|
|
with m.Case(RegEnum.LCR):
|
|
m.d.sync += self.wb.dat_r.eq(lcr)
|
|
|
|
with m.Case(RegEnum.MCR):
|
|
m.d.sync += self.wb.dat_r.eq(mcr)
|
|
|
|
with m.Case(RegEnum.LSR):
|
|
m.d.sync += [
|
|
self.wb.dat_r.eq(0),
|
|
self.wb.dat_r[0].eq(self.r_rdy),
|
|
# Should we do something with the OE bit?
|
|
self.wb.dat_r[1].eq(0),
|
|
# Set THRE always to 1
|
|
self.wb.dat_r[5].eq(1),
|
|
# Set TEMT always to 1
|
|
self.wb.dat_r[6].eq(1)
|
|
]
|
|
|
|
with m.Case(RegEnum.MSR):
|
|
m.d.sync += self.wb.dat_r.eq(msr)
|
|
|
|
with m.Case(RegEnum.SCR):
|
|
m.d.sync += self.wb.dat_r.eq(scr)
|
|
|
|
m.d.sync += self.wb.ack.eq(1)
|
|
m.next = 'ACK'
|
|
|
|
with m.State('ACK'):
|
|
m.d.sync += self.wb.ack.eq(0)
|
|
m.next = 'IDLE'
|
|
|
|
return m
|
|
|
|
|
|
if __name__ == "__main__":
|
|
top = VUart()
|
|
with open("vuart.v", "w") as f:
|
|
f.write(verilog.convert(top))
|