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.

362 lines
15 KiB
Python

#
# This is a LPC slave front end. It runs the LPC statemachine for FW
# and IO Read and write cycles. It's clocked off the LPC lclk and no
# other clock is needed. The LPC/front end is just the
# LAD/lframe/lreset lines.
#
# This collects address/data/cmd info and presents it to a back end
# interface which is split into a write and read interface. Incoming
# LPC transactions are presented on the write interface via
# LPCWRCMDInterface. The back end reponds via the read interface via
# LPCRDCMDInterface. Both of these interfaces use the LPC clock.
#
# To ensure that the back end can respond to requests, the LPC sync
# cycle is held in a long wait until the be back end responds. If
# there is an error in the back end (eg an address doesn't exist),
# the error signal can be asserted, and the SYNC cycle will ERROR and
# then release the LAD lines. If there is no error, the SYNC will
# respond with READY and finish the LPC transaction. This happens on
# both LPC reads and writes.
#
# DMA and MEM read/write cycles are not supported currently. LPC
# interrupts (SERIRQ) is also not supported currently
#
from enum import Enum, unique
from nmigen import Signal, Elaboratable, Module, unsigned, Cat
from nmigen.back import verilog
import math
@unique
class LPCStates(Enum):
START = 0
CYCLETYPE = 1 # IO
IOADDR = 2
FWIDSEL = 3 # FW
FWADDR = 4
FWMSIZE = 5
RDTAR1 = 6 # Reads
RDSYNC = 7
RDDATA = 8
WRDATA = 9 # Writes
WRTAR1 = 10
WRSYNC = 11
TAR2 = 12 # End common for Reads and Writes
@unique
class LPCCycletype(Enum):
IORD = 0
IOWR = 1
FWRD = 2
FWWR = 3
class LPCStartType():
MEMIODMA = 0b0000
FWRD = 0b1101
FWWR = 0b1110
class LPCSyncType():
READY = 0b0000
SHORT_WAIT = 0b0101
LONG_WAIT = 0b0110
ERROR = 0b1010
LPC_IO_DATA_WIDTH = 8
LPC_IO_ADDR_WIDTH = 16
LPC_FW_DATA_WIDTH = 32
LPC_FW_ADDR_WIDTH = 28
class LPCWRCMDInterface():
def __init__(self, *, addr_width, data_width):
self.addr = Signal(addr_width)
self.data = Signal(data_width)
self.cmd = Signal(LPCCycletype)
self.size = Signal(2) # upto 4 bytes
self.rdy = Signal()
self.en = Signal()
self.rst = Signal()
# width of fifo needed to transport this
def width(self):
return self.addr.width + self.data.width + self.cmd.width + self.size.width
class LPCRDCMDInterface():
def __init__(self, *, data_width):
self.data = Signal(data_width)
self.error = Signal()
self.rdy = Signal() # data is ready
self.en = Signal() # data has been read
self.rst = Signal()
# width of fifo needed to transport this
def width(self):
return self.data.width + self.error.width
class lpcfront(Elaboratable):
"""
LPC slave
"""
def __init__(self):
# Ports
self.lframe = Signal()
self.lad_in = Signal(4)
self.lad_out = Signal(4)
self.lad_en = Signal()
self.lreset = Signal()
# synthetic tristate signals
self.lad_tri = Signal(4)
self.wrcmd = LPCWRCMDInterface(addr_width=LPC_FW_ADDR_WIDTH,
data_width=LPC_FW_DATA_WIDTH)
self.rdcmd = LPCRDCMDInterface(data_width=LPC_FW_DATA_WIDTH)
def elaborate(self, platform):
m = Module()
state = Signal(LPCStates)
statenext = Signal(LPCStates)
cycletype = Signal(LPCCycletype)
# FW data is 32 bites with 4 bits per cycle = 8 cycles
cyclecount = Signal(math.ceil(math.log2(LPC_FW_DATA_WIDTH/4)))
addr = Signal(LPC_FW_ADDR_WIDTH)
data = Signal(LPC_FW_DATA_WIDTH)
size = Signal(unsigned(2)) # 1, 2 or 4 bytes
lframesync = Signal()
# fifo interface
m.d.comb += self.wrcmd.addr.eq(addr)
m.d.comb += self.wrcmd.data.eq(data)
m.d.comb += self.wrcmd.size.eq(size)
m.d.comb += self.wrcmd.en.eq(0) # default, also set below
m.d.comb += self.rdcmd.en.eq(0) # default, also set below
m.d.sync += state.eq(statenext) # state machine
m.d.comb += self.lad_en.eq(0) # set below also
# run the states
m.d.comb += statenext.eq(state) # stay where we are by default
with m.Switch(state):
# with m.Case(LPCStates.START):
# this is handled at the end as an override since lframe
# can be aserted at any point
with m.Case(LPCStates.CYCLETYPE):
m.d.comb += statenext.eq(LPCStates.IOADDR)
m.d.sync += cyclecount.eq(3)
with m.Switch(self.lad_in):
with m.Case("000-"):
m.d.sync += cycletype.eq(LPCCycletype.IORD)
with m.Case("001-"):
m.d.sync += cycletype.eq(LPCCycletype.IOWR)
with m.Default():
m.d.comb += statenext.eq(LPCStates.START) # Bail
with m.Case(LPCStates.IOADDR):
m.d.sync += cyclecount.eq(cyclecount - 1)
m.d.sync += addr.eq(Cat(self.lad_in, addr[:24]))
# Make sure the read fifo is cleared out of any
# entries before adding another. This could happen on
# an LPC transaction abort (ie. when lframe/lreset is
# asserted during a transaction).
m.d.comb += self.rdcmd.en.eq(1)
with m.If(cyclecount == 0):
m.d.sync += size.eq(0) # IO cycles are 1 byte
m.d.sync += cyclecount.eq(1) # TAR 2 cycles
with m.If(cycletype == LPCCycletype.IORD):
m.d.comb += statenext.eq(LPCStates.RDTAR1)
with m.Elif(cycletype == LPCCycletype.IOWR):
m.d.comb += statenext.eq(LPCStates.WRDATA)
with m.Else():
m.d.comb += statenext.eq(LPCStates.START) # Bail
with m.Case(LPCStates.FWIDSEL):
# Respond to any IDSEL
m.d.comb += statenext.eq(LPCStates.FWADDR)
m.d.sync += cyclecount.eq(6) # 7 cycle FW addr
with m.Case(LPCStates.FWADDR):
m.d.comb += statenext.eq(LPCStates.FWADDR)
m.d.sync += addr.eq(Cat(self.lad_in, addr[:24]))
# Make sure the read fifo is cleared out of any
# entries before adding another. This could happen on
# an LPC transaction abort (ie. when lframe/lreset is
# asserted during a transaction).
m.d.comb += self.rdcmd.en.eq(1)
m.d.sync += cyclecount.eq(cyclecount - 1)
with m.If(cyclecount == 0):
m.d.comb += statenext.eq(LPCStates.FWMSIZE)
with m.Case(LPCStates.FWMSIZE):
with m.Switch(self.lad_in):
with m.Case(0b0000): # 1 byte
m.d.sync += size.eq(0)
m.d.sync += cyclecount.eq(1) # 1 byte = 2 nibbles
with m.Case(0b0001): # 2 bytes
m.d.sync += size.eq(1)
m.d.sync += cyclecount.eq(3) # 2 byte = 2 nibbles
with m.Case(0b0010): # 4 bytes
m.d.sync += size.eq(3)
m.d.sync += cyclecount.eq(7) # 4 byte = 8 nibbles
with m.Default():
m.d.comb += statenext.eq(LPCStates.START) # Bail
with m.If(cycletype == LPCCycletype.FWRD):
m.d.sync += cyclecount.eq(1) # TAR 2 cycles
m.d.comb += statenext.eq(LPCStates.RDTAR1)
with m.Elif(cycletype == LPCCycletype.FWWR):
m.d.comb += statenext.eq(LPCStates.WRDATA)
with m.Else():
m.d.comb += statenext.eq(LPCStates.START) # Bail
# LPC FW and IO reads
with m.Case(LPCStates.RDTAR1):
# send off the command to the fifo in the first cycle
m.d.comb += self.wrcmd.en.eq(cyclecount == 1)
with m.If(cycletype == LPCCycletype.IORD):
m.d.comb += self.wrcmd.cmd.eq(LPCCycletype.IORD)
with m.Else():
m.d.comb += self.wrcmd.cmd.eq(LPCCycletype.FWRD)
m.d.sync += cyclecount.eq(cyclecount - 1)
with m.If(cyclecount == 0):
m.d.comb += statenext.eq(LPCStates.RDSYNC)
with m.Case(LPCStates.RDSYNC):
m.d.comb += self.lad_out.eq(LPCSyncType.LONG_WAIT)
m.d.comb += self.lad_en.eq(1)
with m.If(self.rdcmd.rdy):
m.d.comb += self.rdcmd.en.eq(1)
m.d.comb += statenext.eq(LPCStates.RDDATA)
m.d.comb += self.lad_out.eq(LPCSyncType.READY) # Ready
with m.Switch(size):
with m.Case(0): # 1 byte
m.d.sync += cyclecount.eq(1) # 1 byte = 2 nibbles
m.d.sync += data.eq(Cat(self.rdcmd.data[0:8],0))
with m.Case(1): # 2 bytes
m.d.sync += cyclecount.eq(3) # 2 byte = 2 nibbles
m.d.sync += data.eq(Cat(self.rdcmd.data[0:16],0))
with m.Case(3): # 4 bytes
m.d.sync += cyclecount.eq(7) # 4 byte = 8 nibbles
m.d.sync += data.eq(self.rdcmd.data) # grab the data
with m.Default():
m.d.comb += statenext.eq(LPCStates.START) # Bail
# we shouldn't get FW errors, but here for completeness
with m.If(self.rdcmd.error):
m.d.comb += statenext.eq(LPCStates.START)
m.d.comb += self.lad_out.eq(LPCSyncType.ERROR)
with m.Case(LPCStates.RDDATA):
m.d.comb += self.lad_en.eq(1)
m.d.sync += data.eq(Cat(data[4:], data[:28]))
m.d.comb += self.lad_out.eq(data[:4])
m.d.sync += cyclecount.eq(cyclecount - 1)
with m.If(cyclecount == 0):
m.d.comb += statenext.eq(LPCStates.TAR2)
m.d.sync += cyclecount.eq(1) # TAR cycles = 2
# LPC IO and FW writes
with m.Case(LPCStates.WRDATA):
with m.Switch(size):
with m.Case(0): # 1 byte
m.d.sync += data.eq(Cat(data[4:8],self.lad_in))
with m.Case(1): # 2 bytes
m.d.sync += data.eq(Cat(data[4:16],self.lad_in))
with m.Case(3): # 4 bytes
m.d.sync += data.eq(Cat(data[4:32],self.lad_in))
with m.Default():
m.d.comb += statenext.eq(LPCStates.START) # Bail
m.d.sync += cyclecount.eq(cyclecount - 1)
with m.If(cyclecount == 0):
m.d.sync += cyclecount.eq(1) # 2 cycle tar
m.d.comb += statenext.eq(LPCStates.WRTAR1)
with m.Case(LPCStates.WRTAR1):
# send off the command to the fifo in the first cycle
m.d.comb += self.wrcmd.en.eq(cyclecount == 1)
with m.If(cycletype == LPCCycletype.IOWR):
m.d.comb += self.wrcmd.cmd.eq(LPCCycletype.IOWR)
with m.Else():
m.d.comb += self.wrcmd.cmd.eq(LPCCycletype.FWWR)
m.d.sync += cyclecount.eq(cyclecount - 1)
with m.If(cyclecount == 0):
m.d.comb += statenext.eq(LPCStates.WRSYNC)
with m.Case(LPCStates.WRSYNC):
m.d.comb += self.lad_out.eq(LPCSyncType.LONG_WAIT)
m.d.comb += self.lad_en.eq(1)
with m.If(self.rdcmd.rdy): # wait for ack
m.d.comb += self.rdcmd.en.eq(1)
m.d.comb += statenext.eq(LPCStates.TAR2)
m.d.comb += self.lad_out.eq(LPCSyncType.READY)
m.d.sync += cyclecount.eq(1) # 2 cycle tar
# we shouldn't get FW errors, but here for completeness
with m.If(self.rdcmd.error):
m.d.comb += statenext.eq(LPCStates.START)
m.d.comb += self.lad_out.eq(LPCSyncType.ERROR)
with m.Case(LPCStates.TAR2):
m.d.comb += self.lad_en.eq(1)
m.d.comb += self.lad_out.eq(0b1111)
m.d.sync += cyclecount.eq(cyclecount - 1)
with m.If(cyclecount == 0):
m.d.comb += self.lad_en.eq(0)
m.d.comb += statenext.eq(LPCStates.START) # Done
with m.Default():
m.d.comb += statenext.eq(LPCStates.START) # Bail
# reset override
with m.If(self.lreset == 0):
m.d.comb += statenext.eq(LPCStates.START)
m.d.comb += self.lad_en.eq(0) # override us driving
# Start cycle can happen anywhere
with m.If(self.lframe == 0):
m.d.comb += self.wrcmd.rst.eq(1)
m.d.comb += self.rdcmd.rst.eq(1)
m.d.comb += self.lad_en.eq(0) # override us driving
with m.Switch(self.lad_in):
with m.Case(LPCStartType.MEMIODMA):
m.d.comb += statenext.eq(LPCStates.CYCLETYPE)
with m.Case(LPCStartType.FWRD):
m.d.comb += statenext.eq(LPCStates.FWIDSEL)
m.d.sync += cycletype.eq(LPCCycletype.FWRD)
with m.Case(LPCStartType.FWWR):
m.d.comb += statenext.eq(LPCStates.FWIDSEL)
m.d.sync += cycletype.eq(LPCCycletype.FWWR)
with m.Default():
m.d.comb += statenext.eq(LPCStates.START) # Bail
# fifo reset needs to be held for two cycles
m.d.sync += lframesync.eq(self.lframe)
m.d.comb += self.wrcmd.rst.eq(0)
m.d.comb += self.rdcmd.rst.eq(0)
with m.If((self.lframe == 0) | (lframesync == 0)):
m.d.comb += self.wrcmd.rst.eq(1)
m.d.comb += self.rdcmd.rst.eq(1)
# Synthetic tristate LAD for looking at in Simulation. Can be removed.
with m.If(self.lad_en):
m.d.comb += self.lad_tri.eq(self.lad_out)
with m.Else():
m.d.comb += self.lad_tri.eq(self.lad_in)
return m
if __name__ == "__main__":
top = lpcfront()
with open("lpcfront.v", "w") as f:
f.write(verilog.convert(top))