Initial public release

Signed-off-by: Michael Neuling <mikey@neuling.org>
Signed-off-by: Anton Blanchard <anton@ozlabs.org>
master
Michael Neuling 3 years ago
commit ef2f91127c

10
.gitignore vendored

@ -0,0 +1,10 @@
# Python
__pycache__/
/*.egg-info
/.eggs
/dist

# tests
*.v
*.vcd
*.gtkw

@ -0,0 +1,23 @@
© IBM Corp. 2021

Licensed under the Apache License, Version 2.0 (the "License"), as modified by
the terms below; you may not use the files in this repository except in
compliance with the License as modified. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0

Modified Terms:
1) For the purpose of the patent license granted to you in Section 3 of
the License, the "Work" hereby includes implementations of the work of
authorship in physical form.

Unless required by applicable law or agreed to in writing, the reference design
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.

Brief explanation of modifications:

Modification 1: This modification extends the patent license to an
implementation of the Work in physical form i.e.,it unambiguously permits a
user to make and use the physical chip.

@ -0,0 +1,70 @@
# LPC Peripheral Overview

This is an LPC peripheral that implements LPC IO and FW cycles so that
it can boot a host like a POWER9. This peripheral would typically sit
inside a BMC SoC.

# System diagram
```
.
.
LPC LPC Clock . System Clock
pins . LPC FW DMA
+---------+ +------------+ +--------+ Wishbone +--------+ Wishbone
| | | ASYNC | | | | LPC | Master
LCLK | +---->| FIFO WR +---->| +--------->| CTRL +-------->
------->| LPC | | | | | | |
| Front | +------------+ | LOGIC | +--------+
LFRAME | | . | |
------->| | +------------+ | | +--------+
| | | ASYNC | | | | IPMI BT|
LAD | |<----+ FIFO RD |<----+ +--------->| FIFO |<--------
<------>| | | | | | | | IO
+---------+ +------------+ +--------+ LPC IO +--------+ Wishbone
. Wishbone | UART | Slave
. | |
. +--------+
. | CTRL |
| |
+--------+
```

The design translates the LPC IO accesses into a wishbone master. The
same is done for FW accesses.

The LPC IO wishbone master bus has devices attached to it. These
include a an IPMI BT FIFO and standard 16550 UART. The back end of
these can then be access by an external IO wishbone slave (which would
typically come from the BMC CPU).

The LPC FW wishbone master gets translated into an external wishbone
master. This translation provides an offset and mask so the external
wishbone master accesses occur can be controlled. Typically this
external wishbone will be hooked into a DMA path the system bus of the
BMC, so where this can access needs to be controlled.

The LPC front end runs using the LPC clock. The rest of the design
works on the normal system clock. Async FIFOs provide a safe boundary
between the two.

HDL is written in nmigen because that's what all the cool kids are
doing. This is our first nmigen project, and we are software
developers, so be kind!

# Building

This is designed to be integrated into some other project (like
microwatt for libreBMC) not build as a standalone project.

If you want the verilog, do this to produce a lpcperipheral.v
```
python -m lpcperipheral.lpcperipheral
```

# Testing

There are an extensive set of tests in tests/. To run these do:

```
python -m unittest
```

@ -0,0 +1,99 @@
from nmigen import Elaboratable, Module, Signal
from nmigen.back import verilog
from nmigen_soc.wishbone import Decoder as WishboneDecoder
from nmigen_soc.wishbone import Interface as WishboneInterface
from nmigen_soc.memory import MemoryMap

from .ipmi_bt import IPMI_BT
from .vuart_joined import VUartJoined


class IOSpace(Elaboratable):
def __init__(self, vuart_depth=2048, bmc_vuart_addr=0x0, bmc_ipmi_addr=0x1000,
bmc_lpc_ctrl_addr=0x2000,
target_vuart_addr=0x3f8, target_ipmi_addr=0xe4):
self.vuart_depth = vuart_depth
# BMC wishbone is 32 bit wide, so divide addresses by 4
self.bmc_vuart_addr = bmc_vuart_addr // 4
self.bmc_ipmi_addr = bmc_ipmi_addr // 4
self.bmc_lpc_ctrl_addr = bmc_lpc_ctrl_addr // 4
self.target_vuart_addr = target_vuart_addr
self.target_ipmi_addr = target_ipmi_addr

self.bmc_vuart_irq = Signal()
self.bmc_ipmi_irq = Signal()
self.bmc_wb = WishboneInterface(addr_width=14, data_width=8)

self.target_vuart_irq = Signal()
self.target_ipmi_irq = Signal()
self.target_wb = WishboneInterface(addr_width=16, data_width=8, features=["err"])

self.lpc_ctrl_wb = WishboneInterface(addr_width=3, data_width=8)

self.error_wb = WishboneInterface(addr_width=2, data_width=8,
features=["err"])

def elaborate(self, platform):
m = Module()

m.submodules.vuart_joined = vuart_joined = VUartJoined(depth=self.vuart_depth)
m.submodules.ipmi_bt = ipmi_bt = IPMI_BT()

# BMC address decode
m.submodules.bmc_decode = bmc_decode = WishboneDecoder(addr_width=14, data_width=8, granularity=8)

bmc_ipmi_bus = ipmi_bt.bmc_wb
bmc_ipmi_bus.memory_map = MemoryMap(addr_width=3, data_width=8)
bmc_decode.add(bmc_ipmi_bus, addr=self.bmc_ipmi_addr)

bmc_vuart_bus = vuart_joined.wb_a
bmc_vuart_bus.memory_map = MemoryMap(addr_width=3, data_width=8)
bmc_decode.add(bmc_vuart_bus, addr=self.bmc_vuart_addr)

lpc_ctrl_bus = self.lpc_ctrl_wb
lpc_ctrl_bus.memory_map = MemoryMap(addr_width=3, data_width=8)
bmc_decode.add(lpc_ctrl_bus, addr=self.bmc_lpc_ctrl_addr)

m.d.comb += [
self.bmc_ipmi_irq.eq(ipmi_bt.bmc_irq),
self.bmc_vuart_irq.eq(vuart_joined.irq_a),
self.bmc_wb.connect(bmc_decode.bus)
]

# Target address decode
m.submodules.target_decode = target_decode = WishboneDecoder(addr_width=16, data_width=8, granularity=8, features=["err"])

target_ipmi_bus = ipmi_bt.target_wb
target_ipmi_bus.memory_map = MemoryMap(addr_width=2, data_width=8)
target_decode.add(target_ipmi_bus, addr=self.target_ipmi_addr)

target_vuart_bus = vuart_joined.wb_b
target_vuart_bus.memory_map = MemoryMap(addr_width=3, data_width=8)
target_decode.add(target_vuart_bus, addr=self.target_vuart_addr)

target_error_bus = self.error_wb
target_error_bus.memory_map = MemoryMap(addr_width=2, data_width=8)
# Generate a signal when we'd expect an ACK on the target bus
ack_expected = Signal()
m.d.sync += ack_expected.eq(self.target_wb.sel & self.target_wb.cyc &
~ack_expected)
# Generate an error if no ack from ipmi_bt or vuart
m.d.comb += self.error_wb.err.eq(0)
with m.If (ack_expected):
m.d.comb += self.error_wb.err.eq(~ipmi_bt.target_wb.ack &
~vuart_joined.wb_b.ack)
target_decode.add(target_error_bus, addr=0x0)

m.d.comb += [
self.target_ipmi_irq.eq(ipmi_bt.target_irq),
self.target_vuart_irq.eq(vuart_joined.irq_b),
self.target_wb.connect(target_decode.bus)
]

return m


if __name__ == "__main__":
top = IOSpace()
with open("io_map.v", "w") as f:
f.write(verilog.convert(top))

@ -0,0 +1,317 @@
from enum import IntEnum, unique

from nmigen import Signal, Elaboratable, Module, ResetInserter, Cat
from nmigen_soc.wishbone import Interface as WishboneInterface
from nmigen.lib.fifo import SyncFIFOBuffered

from nmigen.back import verilog


@unique
class RegEnum(IntEnum):
BT_CTRL = 0
BMC2HOST_HOST2BMC = 1
BT_INTMASK = 2


@unique
class BMCRegEnum(IntEnum):
IRQ_MASK = 0
IRQ_STATUS = 1
BT_CTRL = 4
BMC2HOST_HOST2BMC = 5


@unique
class StateEnum(IntEnum):
IDLE = 0
ACK = 1


@unique
class BMCIRQEnum(IntEnum):
TARGET_TO_BMC_ATTN = 0
TARGET_NOT_BUSY = 1


class IPMI_BT(Elaboratable):
def __init__(self, depth=64):
self.depth = depth

self.bmc_wb = WishboneInterface(data_width=8, addr_width=3)
self.bmc_irq = Signal()

self.target_wb = WishboneInterface(data_width=8, addr_width=2)
self.target_irq = Signal()

def elaborate(self, platform):
m = Module()

# Reset signals for the FIFOs, since BT_CTRL needs to be able to clear them
reset_from_bmc_fifo = Signal()
reset_from_target_fifo = Signal()
m.d.sync += [
reset_from_bmc_fifo.eq(0),
reset_from_target_fifo.eq(0)
]

# BMC -> Target FIFO
m.submodules.from_bmc_fifo = from_bmc_fifo = ResetInserter(
reset_from_bmc_fifo)(SyncFIFOBuffered(width=8, depth=self.depth))

# Target -> BMC FIFO
m.submodules.from_target_fifo = from_target_fifo = ResetInserter(
reset_from_target_fifo)(SyncFIFOBuffered(width=8, depth=self.depth))

# Wire up wishbone to FIFO write
m.d.comb += [
from_bmc_fifo.w_data.eq(self.bmc_wb.dat_w),
from_target_fifo.w_data.eq(self.target_wb.dat_w)
]

# Some wishbone helpers
is_bmc_write = Signal()
is_bmc_read = Signal()
m.d.comb += [
is_bmc_write.eq(self.bmc_wb.stb & self.bmc_wb.cyc & self.bmc_wb.we),
is_bmc_read.eq(self.bmc_wb.stb & self.bmc_wb.cyc & ~self.bmc_wb.we)
]
is_target_read = Signal()
is_target_write = Signal()
m.d.comb += [
is_target_write.eq(self.target_wb.stb & self.target_wb.cyc & self.target_wb.we),
is_target_read.eq(self.target_wb.stb & self.target_wb.cyc & ~self.target_wb.we)
]

# BMC and target wishbone state machine
bmc_state = Signal(StateEnum, reset=StateEnum.IDLE)
target_state = Signal(StateEnum, reset=StateEnum.IDLE)

m.d.sync += [
from_bmc_fifo.w_en.eq(0),
from_bmc_fifo.r_en.eq(0),
from_target_fifo.w_en.eq(0),
from_target_fifo.r_en.eq(0)
]

m.d.sync += [
self.bmc_wb.ack.eq(0),
self.target_wb.ack.eq(0)
]

# Don't read from empty FIFOs
from_bmc_fifo_read_data = Signal(8)
m.d.comb += from_bmc_fifo_read_data.eq(0)
with m.If(from_bmc_fifo.r_rdy):
m.d.comb += from_bmc_fifo_read_data.eq(from_bmc_fifo.r_data)

from_target_fifo_read_data = Signal(8)
m.d.comb += from_target_fifo_read_data.eq(0)
with m.If(from_target_fifo.r_rdy):
m.d.comb += from_target_fifo_read_data.eq(from_target_fifo.r_data)

# BT_CTRL bits
target_to_bmc_attn = Signal()
bmc_to_target_attn = Signal()
sms_attn = Signal()
platform_reserved = Signal()
bmc_busy = Signal()
target_busy = Signal()

bt_ctrl = Signal(8)
m.d.comb += bt_ctrl.eq(Cat(0, 0, target_to_bmc_attn, bmc_to_target_attn,
sms_attn, platform_reserved, target_busy,
bmc_busy))

# BT_INTMASK (target interrupt mask) bits
bmc_to_target_irq_en = Signal()
bmc_to_target_irq = Signal()

m.d.comb += self.target_irq.eq(bmc_to_target_irq_en & bmc_to_target_irq)

# BMC interrupt bits. These are not architected by the IPMI BT spec. The
# Linux driver expects to get an interrupt whenever target_to_bmc_attn
# goes high (ready to read) or target_busy goes low (ready to write). We
# don't interrupt on bmc_to_target_attn going low (which is also required
# for ready to write) but rely on the target driver setting target_busy low
# right after it sets bmc_to_target_attn low.
bmc_irq_en = Signal(2)
bmc_irq = Signal(2)

m.d.comb += self.bmc_irq.eq(bmc_irq_en & bmc_irq)

# Target wishbone state machine
with m.Switch(target_state):
with m.Case(StateEnum.IDLE):
with m.If(is_target_write):
with m.Switch(self.target_wb.adr):
with m.Case(RegEnum.BT_CTRL):
# Bit 0, write 1 to clear the write fifo (ie from_target_fifo)
with m.If(self.target_wb.dat_w[0]):
m.d.sync += reset_from_target_fifo.eq(1)

# Bit 1 is meant to set the read FIFO to the next valid
# position, but since we only have a single buffer this
# doesn't need to do anything.

with m.If(self.target_wb.dat_w[2]):
# Trigger an interrupt whenever we set target_to_bmc_attn
with m.If(bmc_irq_en[BMCIRQEnum.TARGET_TO_BMC_ATTN]):
m.d.sync += bmc_irq[BMCIRQEnum.TARGET_TO_BMC_ATTN].eq(1)
m.d.sync += target_to_bmc_attn.eq(1)

# Bit 3, write 1 to clear bmc_to_target_attn
with m.If(self.target_wb.dat_w[3]):
m.d.sync += bmc_to_target_attn.eq(0)

# Bit 4, write 1 to clear sms_attn
with m.If(self.target_wb.dat_w[4]):
m.d.sync += sms_attn.eq(0)

# Bit 5, write 1 to set platform reserved
with m.If(self.target_wb.dat_w[5]):
m.d.sync += platform_reserved.eq(1)

# Bit 6, write 1 to toggle target_busy
with m.If(self.target_wb.dat_w[6]):
# Trigger an interrupt whenever we clear target_busy
with m.If(target_busy & bmc_irq_en[BMCIRQEnum.TARGET_NOT_BUSY]):
m.d.sync += bmc_irq[BMCIRQEnum.TARGET_NOT_BUSY].eq(1)
m.d.sync += target_busy.eq(~target_busy)

# Bit 7, read only

with m.Case(RegEnum.BMC2HOST_HOST2BMC):
# Only assert write if there is space
m.d.sync += from_target_fifo.w_en.eq(from_target_fifo.w_rdy)

with m.Case(RegEnum.BT_INTMASK):
# Bit 0, 0/1 write
m.d.sync += bmc_to_target_irq_en.eq(self.target_wb.dat_w[0])

# Bit 1, write 1 to clear interrupt
with m.If(self.target_wb.dat_w[1]):
m.d.sync += bmc_to_target_irq.eq(0)

m.d.sync += [
self.target_wb.ack.eq(1),
target_state.eq(StateEnum.ACK)
]

with m.If(is_target_read):
with m.Switch(self.target_wb.adr):
with m.Case(RegEnum.BT_CTRL):
m.d.sync += self.target_wb.dat_r.eq(bt_ctrl)

with m.Case(RegEnum.BMC2HOST_HOST2BMC):
m.d.sync += [
self.target_wb.dat_r.eq(from_bmc_fifo_read_data),
from_bmc_fifo.r_en.eq(from_bmc_fifo.r_rdy),
]

with m.Case(RegEnum.BT_INTMASK):
m.d.sync += self.target_wb.dat_r.eq(Cat(bmc_to_target_irq_en, bmc_to_target_irq))

m.d.sync += [
self.target_wb.ack.eq(1),
target_state.eq(StateEnum.ACK)
]

with m.Case(StateEnum.ACK):
m.d.sync += [
self.target_wb.ack.eq(0),
target_state.eq(StateEnum.IDLE),
]

# BMC wishbone state machine
with m.Switch(bmc_state):
with m.Case(StateEnum.IDLE):
with m.If(is_bmc_write):
with m.Switch(self.bmc_wb.adr):
with m.Case(BMCRegEnum.BT_CTRL):
# Bit 0, write 1 to clear the write fifo (ie from_bmc_fifo)
with m.If(self.bmc_wb.dat_w[0]):
m.d.sync += reset_from_bmc_fifo.eq(1)

# Bit 1 is meant to set the read FIFO to the next valid
# position, but since we only have a single buffer this
# doesn't need to do anything.

# Bit 2, write to clear target_to_bmc_attn
with m.If(self.bmc_wb.dat_w[2]):
m.d.sync += target_to_bmc_attn.eq(0)

# Bit 3, write 1 to set bmc_to_target_attn
with m.If(self.bmc_wb.dat_w[3]):
# Trigger an interrupt whenever we set bmc_to_target_attn
with m.If(bmc_to_target_irq_en):
m.d.sync += bmc_to_target_irq.eq(1)
m.d.sync += bmc_to_target_attn.eq(1)

# Bit 4, write 1 to set sms_attn
with m.If(self.bmc_wb.dat_w[4]):
# Trigger an interrupt whenever we set sms_attn
with m.If(bmc_to_target_irq_en):
m.d.sync += bmc_to_target_irq.eq(1)
m.d.sync += sms_attn.eq(1)

# Bit 5, write 1 to clear platform reserved
with m.If(self.bmc_wb.dat_w[5]):
m.d.sync += platform_reserved.eq(0)

# Bit 6, read only

# Bit 7, write 1 to toggle bmc_busy
with m.If(self.bmc_wb.dat_w[7]):
m.d.sync += bmc_busy.eq(~bmc_busy)

with m.Case(BMCRegEnum.BMC2HOST_HOST2BMC):
# Only assert write if there is space
m.d.sync += from_bmc_fifo.w_en.eq(from_bmc_fifo.w_rdy)

with m.Case(BMCRegEnum.IRQ_MASK):
m.d.sync += bmc_irq_en.eq(self.bmc_wb.dat_w)

with m.Case(BMCRegEnum.IRQ_STATUS):
m.d.sync += bmc_irq.eq(self.bmc_wb.dat_w)

m.d.sync += [
self.bmc_wb.ack.eq(1),
bmc_state.eq(StateEnum.ACK)
]

with m.If(is_bmc_read):
with m.Switch(self.bmc_wb.adr):
with m.Case(BMCRegEnum.BT_CTRL):
m.d.sync += self.bmc_wb.dat_r.eq(bt_ctrl)

with m.Case(BMCRegEnum.BMC2HOST_HOST2BMC):
m.d.sync += [
self.bmc_wb.dat_r.eq(from_target_fifo_read_data),
from_target_fifo.r_en.eq(from_target_fifo.r_rdy),
]

with m.Case(BMCRegEnum.IRQ_MASK):
m.d.sync += self.bmc_wb.dat_r.eq(bmc_irq_en)

with m.Case(BMCRegEnum.IRQ_STATUS):
m.d.sync += self.bmc_wb.dat_r.eq(bmc_irq)

m.d.sync += [
self.bmc_wb.ack.eq(1),
bmc_state.eq(StateEnum.ACK)
]

with m.Case(StateEnum.ACK):
m.d.sync += [
self.bmc_wb.ack.eq(0),
bmc_state.eq(StateEnum.IDLE),
]

return m


if __name__ == "__main__":
top = IPMI_BT()
with open("ipmi_bt.v", "w") as f:
f.write(verilog.convert(top))

@ -0,0 +1,199 @@
#
# This is an LPC slave to wishbone interface
#
# LPC Clock | System Clock
#
# +---------+ +------------+ +--------+
# LPC | | | ASYNC | | | Wishbone
# pins | +---->| FIFO WR +---->| | IO Space
# | LPC | | | | |<-------->
# <------>| Front | +------------+ | LOGIC |
# | | | |
# | | +------------+ | | Wishbone
# | | | ASYNC | | | FW Space
# | |<----+ FIFO RD |<----+ |<-------->
# | | | | | |
# +---------+ +------------+ +--------+
#
# It takes the lpcfront and and turns it into IO and FW wishbone
# interfaces. The lpcfront operates on the lpc clock domain and the
# wishbone interfaces operate on the standard "sync" domain.
#
# To cross the clock domains async fifos are used. The write fifo
# takes write commands from the lpc interface. The read fifo gets
# information back to respond to these write commands.
#
# The write fifo turns the write commands into either an IO or FW
# wishbone transaction. The read fifo takes the wishbone transaction
# reponses and sends them back to the LPC.
#
# If an address doesn't exist on the wishbone interfaces (common on
# the IO bus), the wishbone interface asserts the err signal (rather
# than the ack). When this occurs the read fifo send an error back to
# the LPC.
#

from nmigen import Signal, Elaboratable, Module
from nmigen import ClockSignal, Cat, DomainRenamer, ResetSignal, ResetInserter
from nmigen.lib.fifo import AsyncFIFO
from nmigen_soc.wishbone import Interface as WishboneInterface
from nmigen.back import verilog

from .lpcfront import lpcfront, LPCCycletype, LPC_FW_DATA_WIDTH, LPC_FW_ADDR_WIDTH, LPC_IO_DATA_WIDTH, LPC_IO_ADDR_WIDTH


class lpc2wb(Elaboratable):

def __init__(self):

# LPC clock pin
self.lclk = Signal()
self.lframe = Signal()
self.lad_in = Signal(4)
self.lad_out = Signal(4)
self.lad_en = Signal()
self.lreset = Signal()

self.io_wb = WishboneInterface(data_width=LPC_IO_DATA_WIDTH,
addr_width=LPC_IO_ADDR_WIDTH,
granularity=8,
features = ["err"])
# 32 bit bus, so address only need to address words
self.fw_wb = WishboneInterface(data_width=LPC_FW_DATA_WIDTH,
addr_width=LPC_FW_ADDR_WIDTH - 2,
granularity=8)

def elaborate(self, platform):
m = Module()

# hook up lclk port to lclk domain
m.d.comb += ClockSignal("lclk").eq(self.lclk)

# Use main reset to reset lclk domain
m.d.comb += ResetSignal("lclk").eq(ResetSignal())

# create lpc front end wth right clock domain
m.submodules.lpc = lpc = DomainRenamer("lclk")(lpcfront())

wr_data = Signal(lpc.wrcmd.data.width)
wr_addr = Signal(lpc.wrcmd.addr.width)
wr_cmd = Signal(lpc.wrcmd.cmd.width)
wr_size = Signal(lpc.wrcmd.size.width)
wr_rdy = Signal()

# hook up lclk port to lclk domain
m.d.comb += ClockSignal("lclkrst").eq(self.lclk)
# Use main reset to reset lclk domain
m.d.comb += ResetSignal("lclkrst").eq(lpc.wrcmd.rst)

# hook up external lpc interface
m.d.comb += lpc.lframe.eq(self.lframe)
m.d.comb += lpc.lreset.eq(self.lreset)
m.d.comb += lpc.lad_in.eq(self.lad_in)
m.d.comb += self.lad_en.eq(lpc.lad_en)
m.d.comb += self.lad_out.eq(lpc.lad_out)

# We have two fifo
# 1) fifowr for getting commands from the LPC and transferring them
# to the wishbone. This has address for writes and reads, data
# for writes and cmd (IO read/write)
# 2) fiford for getting read data from the wishbone back to the LPC.
fifowr = AsyncFIFO(width=lpc.wrcmd.width(), depth=2,
r_domain="sync",
w_domain="lclkrst")
m.submodules += fifowr
fiford = AsyncFIFO(width=lpc.rdcmd.width(), depth=2,
r_domain="lclkrst",
w_domain="sync")
m.submodules += fiford
# lpc clock side
m.d.comb += fifowr.w_data[ 0:32].eq(lpc.wrcmd.data)
m.d.comb += fifowr.w_data[32:60].eq(lpc.wrcmd.addr)
m.d.comb += fifowr.w_data[60:62].eq(lpc.wrcmd.cmd)
m.d.comb += fifowr.w_data[62:64].eq(lpc.wrcmd.size)
m.d.comb += lpc.wrcmd.rdy.eq(fifowr.w_rdy)
m.d.comb += fifowr.w_en.eq(lpc.wrcmd.en)
# system clock side
m.d.comb += wr_data.eq(fifowr.r_data[ 0:32]) # sliced as above
m.d.comb += wr_addr.eq(fifowr.r_data[32:60]) # sliced as above
m.d.comb += wr_cmd.eq (fifowr.r_data[60:62]) # sliced as above
m.d.comb += wr_size.eq(fifowr.r_data[62:64]) # sliced as above
m.d.comb += wr_rdy.eq(fifowr.r_rdy)
m.d.comb += fifowr.r_en.eq(0) # See below for wishbone acks

# turn fifowr into IO wishbone master
m.d.comb += self.io_wb.adr.eq(wr_addr[0:16])
m.d.comb += self.io_wb.dat_w.eq(wr_data[0:8])
m.d.comb += self.io_wb.sel.eq(1)
m.d.comb += self.io_wb.we.eq(wr_cmd == LPCCycletype.IOWR)
with m.If ((wr_cmd == LPCCycletype.IORD) | (wr_cmd == LPCCycletype.IOWR)):
# The fiford should always be ready here but check anyway
m.d.comb += self.io_wb.cyc.eq(wr_rdy & fiford.w_rdy)
m.d.comb += self.io_wb.stb.eq(wr_rdy & fiford.w_rdy)
# turn fifowr into FW wishbone master
m.d.comb += self.fw_wb.adr.eq(wr_addr[2:28])
# data comes in the MSB so we need to shift it down for smaller sizes
m.d.comb += self.fw_wb.dat_w.eq(wr_data)
with m.If (wr_size == 3):
m.d.comb += self.fw_wb.sel.eq(0b1111)
with m.If (wr_size == 1):
with m.If (wr_addr[1] == 0b0):
m.d.comb += self.fw_wb.sel.eq(0b0011)
with m.If (wr_addr[1] == 0b1):
m.d.comb += self.fw_wb.sel.eq(0b1100)
m.d.comb += self.fw_wb.dat_w.eq(wr_data << 16)
with m.If (wr_size == 0):
with m.If (wr_addr[0:2] == 0b00):
m.d.comb += self.fw_wb.sel.eq(0b0001)
with m.If (wr_addr[0:2] == 0b01):
m.d.comb += self.fw_wb.sel.eq(0b0010)
m.d.comb += self.fw_wb.dat_w.eq(wr_data << 8)
with m.If (wr_addr[0:2] == 0b10):
m.d.comb += self.fw_wb.sel.eq(0b0100)
m.d.comb += self.fw_wb.dat_w.eq(wr_data << 16)
with m.If (wr_addr[0:2] == 0b11):
m.d.comb += self.fw_wb.sel.eq(0b1000)
m.d.comb += self.fw_wb.dat_w.eq(wr_data << 24)
m.d.comb += self.fw_wb.we.eq(wr_cmd == LPCCycletype.FWWR)
with m.If ((wr_cmd == LPCCycletype.FWRD) | (wr_cmd == LPCCycletype.FWWR)):
# The fiford should always be ready here but check anyway
m.d.comb += self.fw_wb.cyc.eq(wr_rdy & fiford.w_rdy)
m.d.comb += self.fw_wb.stb.eq(wr_rdy & fiford.w_rdy)
# Arbitrate the acks back into the fifo
with m.If ((wr_cmd == LPCCycletype.IORD) | (wr_cmd == LPCCycletype.IOWR)):
m.d.comb += fifowr.r_en.eq(self.io_wb.ack | self.io_wb.err)
m.d.comb += fiford.w_data[32].eq(self.io_wb.err)
with m.If ((wr_cmd == LPCCycletype.FWRD) | (wr_cmd == LPCCycletype.FWWR)):
m.d.comb += fifowr.r_en.eq(self.fw_wb.ack)
m.d.comb += fiford.w_data[32].eq(0)

# sending data back from IO/FW wishbones to fiford
with m.If (wr_cmd == LPCCycletype.IORD):
m.d.comb += fiford.w_data[0:32].eq(self.io_wb.dat_r)
with m.Elif (wr_cmd == LPCCycletype.FWRD):
m.d.comb += fiford.w_data[0:32].eq(self.fw_wb.dat_r)
with m.If (wr_size == 1):
with m.If (wr_addr[1] == 0b1):
m.d.comb += fiford.w_data[0:16].eq(self.fw_wb.dat_r[16:32])
with m.If (wr_size == 0):
with m.If (wr_addr[0:2] == 0b01):
m.d.comb += fiford.w_data[0:8].eq(self.fw_wb.dat_r[8:16])
with m.If (wr_addr[0:2] == 0b10):
m.d.comb += fiford.w_data[0:8].eq(self.fw_wb.dat_r[16:24])
with m.If (wr_addr[0:2] == 0b11):
m.d.comb += fiford.w_data[0:8].eq(self.fw_wb.dat_r[24:32])
m.d.comb += fiford.w_en.eq(self.io_wb.ack | self.fw_wb.ack | self.io_wb.err)

# lpc side of read fiford
m.d.comb += fiford.r_en.eq(lpc.rdcmd.en)
m.d.comb += lpc.rdcmd.data.eq(fiford.r_data[0:32])
m.d.comb += lpc.rdcmd.error.eq(fiford.r_data[32])
m.d.comb += lpc.rdcmd.rdy.eq(fiford.r_rdy)

return m


if __name__ == "__main__":
top = lpc2wb()
with open("lpc2wb.v", "w") as f:
f.write(verilog.convert(top))

@ -0,0 +1,69 @@
# Typically LPC FW reads/writes directly access system memory of a
# CPU. These accesses need to be offset and windowed to ensure the LPC
# access can only access what the CPU wants it to.
#
# This modules takes a wishbone master from the LPC for firmware
# reads/writes and translates it into something that can be used for
# DMA access into another master wishbone bus. Base and mask registers
# (accessible via an IO wishbone bus) configure this

from nmigen import Elaboratable, Module, Signal
from nmigen_soc.wishbone import Interface as WishboneInterface
from nmigen_soc.csr import Multiplexer as CSRMultiplexer
from nmigen_soc.csr import Element as CSRElement
from nmigen_soc.csr.wishbone import WishboneCSRBridge
from nmigen.back import verilog


class LPC_Ctrl(Elaboratable):
def __init__(self):
self.io_wb = WishboneInterface(data_width=32, addr_width=2, granularity=8)

self.lpc_wb = WishboneInterface(data_width=32, addr_width=26, granularity=8)
self.dma_wb = WishboneInterface(data_width=32, addr_width=30, granularity=8)

def elaborate(self, platform):
m = Module()

base_lo_csr = CSRElement(32, "rw")
base_lo = Signal(32, reset=192*1024*1024)
# Leave space for upper 32 bits, unused for now
base_hi_csr = CSRElement(32, "rw")
mask_lo_csr = CSRElement(32, "rw")
mask_lo = Signal(32, reset=0x3FFFFFF)
# Leave space for upper 32 bits, unused for now
mask_hi_csr = CSRElement(32, "rw")

m.submodules.mux = mux = CSRMultiplexer(addr_width=2, data_width=32)
mux.add(base_lo_csr)
mux.add(base_hi_csr)
mux.add(mask_lo_csr)
mux.add(mask_hi_csr)

m.submodules.bridge = bridge = WishboneCSRBridge(mux.bus)

m.d.comb += self.io_wb.connect(bridge.wb_bus)

m.d.comb += [
base_lo_csr.r_data.eq(base_lo),
mask_lo_csr.r_data.eq(mask_lo),
]

with m.If(base_lo_csr.w_stb):
m.d.sync += base_lo.eq(base_lo_csr.w_data)
with m.If(mask_lo_csr.w_stb):
m.d.sync += mask_lo.eq(mask_lo_csr.w_data)

m.d.comb += [
self.lpc_wb.connect(self.dma_wb),
# bask/mask are in bytes, so convert to wishbone addresses
self.dma_wb.adr.eq((self.lpc_wb.adr & (mask_lo >> 2)) | (base_lo >> 2))
]

return m


if __name__ == "__main__":
top = LPC_Ctrl()
with open("lpc_ctrl.v", "w") as f:
f.write(verilog.convert(top))

@ -0,0 +1,361 @@
#
# 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))

@ -0,0 +1,132 @@
from enum import Enum, unique

from nmigen import Signal, Elaboratable, Module, Cat
from nmigen.back import verilog
from nmigen_soc.wishbone import Interface as WishboneInterface

from .io_space import IOSpace
from .lpc2wb import lpc2wb
from .lpc_ctrl import LPC_Ctrl


@unique
class StateEnum(Enum):
IDLE = 0
ACK = 1


class LPCPeripheral(Elaboratable):
"""
Parameters
----------

Attributes
----------
"""
def __init__(self):
# BMC wishbone. We dont use a Record because we want predictable
# signal names so we can hook it up to VHDL/Verilog
self.adr = Signal(14)
self.dat_w = Signal(8)
self.dat_r = Signal(8)
self.sel = Signal()
self.cyc = Signal()
self.stb = Signal()
self.we = Signal()
self.ack = Signal()

# DMA wishbone
self.dma_adr = Signal(30)
self.dma_dat_w = Signal(32)
self.dma_dat_r = Signal(32)
self.dma_sel = Signal(4)
self.dma_cyc = Signal()
self.dma_stb = Signal()
self.dma_we = Signal()
self.dma_ack = Signal()

# LPC bus
self.lclk = Signal()
self.lframe = Signal()
self.lad_in = Signal(4)
self.lad_out = Signal(4)
self.lad_en = Signal()
self.lreset = Signal()

# Interrupts
self.bmc_vuart_irq = Signal()
self.bmc_ipmi_irq = Signal()

self.target_vuart_irq = Signal()
self.target_ipmi_irq = Signal()

def elaborate(self, platform):
m = Module()

m.submodules.io = io = IOSpace()
m.submodules.lpc = lpc = lpc2wb()
m.submodules.lpc_ctrl = lpc_ctrl = LPC_Ctrl()

m.d.comb += [
# BMC wishbone
io.bmc_wb.adr.eq(self.adr),
io.bmc_wb.dat_w.eq(self.dat_w),
io.bmc_wb.sel.eq(self.sel),
io.bmc_wb.cyc.eq(self.cyc),
io.bmc_wb.stb.eq(self.stb),
io.bmc_wb.we.eq(self.we),
self.dat_r.eq(io.bmc_wb.dat_r),
self.ack.eq(io.bmc_wb.ack),

# target wishbone
io.target_wb.adr.eq(lpc.io_wb.adr),
io.target_wb.dat_w.eq(lpc.io_wb.dat_w),
io.target_wb.sel.eq(lpc.io_wb.sel),
io.target_wb.cyc.eq(lpc.io_wb.cyc),
io.target_wb.stb.eq(lpc.io_wb.stb),
io.target_wb.we.eq(lpc.io_wb.we),
lpc.io_wb.dat_r.eq(io.target_wb.dat_r),
lpc.io_wb.ack.eq(io.target_wb.ack),
lpc.io_wb.err.eq(io.target_wb.err),

# LPC CTRL to DMA wishbone
self.dma_adr.eq(lpc_ctrl.dma_wb.adr),
self.dma_dat_w.eq(lpc_ctrl.dma_wb.dat_w),
self.dma_sel.eq(lpc_ctrl.dma_wb.sel),
self.dma_cyc.eq(lpc_ctrl.dma_wb.cyc),
self.dma_stb.eq(lpc_ctrl.dma_wb.stb),
self.dma_we.eq(lpc_ctrl.dma_wb.we),
lpc_ctrl.dma_wb.dat_r.eq(self.dma_dat_r),
lpc_ctrl.dma_wb.ack.eq(self.dma_ack),

# LPC to LPC CTRL wishbone
lpc.fw_wb.connect(lpc_ctrl.lpc_wb),

# LPC
lpc.lclk.eq(self.lclk),
lpc.lframe.eq(self.lframe),
lpc.lad_in.eq(self.lad_in),
self.lad_out.eq(lpc.lad_out),
self.lad_en.eq(lpc.lad_en),
lpc.lreset.eq(self.lreset),

# Interrupts
self.bmc_vuart_irq.eq(io.bmc_vuart_irq),
self.bmc_ipmi_irq.eq(io.bmc_ipmi_irq),
self.target_vuart_irq.eq(io.target_vuart_irq),
self.target_ipmi_irq.eq(io.target_ipmi_irq),
]

return m


if __name__ == "__main__":
top = LPCPeripheral()
with open("lpcperipheral.v", "w") as f:
f.write(verilog.convert(top, ports=[
top.adr, top.dat_w, top.dat_r, top.sel, top.cyc, top.stb,
top.we, top.ack, top.dma_adr, top.dma_dat_w, top.dma_dat_r,
top.dma_sel, top.dma_cyc, top.dma_stb, top.dma_we, top.dma_ack,
top.lclk, top.lframe, top.lad_in,
top.lad_out, top.lad_en, top.lreset, top.bmc_vuart_irq,
top.bmc_ipmi_irq, top.target_vuart_irq, top.target_ipmi_irq]))

@ -0,0 +1,221 @@
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))

@ -0,0 +1,65 @@
from nmigen import Signal, Elaboratable, Module
from nmigen.back import verilog
from nmigen.lib.fifo import SyncFIFOBuffered
from nmigen_soc.wishbone import Interface as WishboneInterface

from .vuart import VUart


class VUartJoined(Elaboratable):
"""
Two Virtual UARTs connected together via a FIFO. Presents two 16550
style interfaces over two wishbone slaves

Parameters
----------

Attributes
----------
"""
def __init__(self, depth=8):
self.depth = depth

self.irq_a = Signal()
self.wb_a = WishboneInterface(data_width=8, addr_width=3)

self.irq_b = Signal()
self.wb_b = WishboneInterface(data_width=8, addr_width=3)

def elaborate(self, platform):
m = Module()

m.submodules.fifo_a = fifo_a = SyncFIFOBuffered(width=8, depth=self.depth)
m.submodules.fifo_b = fifo_b = SyncFIFOBuffered(width=8, depth=self.depth)
m.submodules.vuart_a = vuart_a = VUart()
m.submodules.vuart_b = vuart_b = VUart()

m.d.comb += [
fifo_a.w_data.eq(vuart_a.w_data),
vuart_a.w_rdy.eq(fifo_a.w_rdy),
fifo_a.w_en.eq(vuart_a.w_en),
vuart_a.r_data.eq(fifo_b.r_data),
vuart_a.r_rdy.eq(fifo_b.r_rdy),
fifo_b.r_en.eq(vuart_a.r_en),

fifo_b.w_data.eq(vuart_b.w_data),
vuart_b.w_rdy.eq(fifo_b.w_rdy),
fifo_b.w_en.eq(vuart_b.w_en),
vuart_b.r_data.eq(fifo_a.r_data),
vuart_b.r_rdy.eq(fifo_a.r_rdy),
fifo_a.r_en.eq(vuart_b.r_en),

self.irq_a.eq(vuart_a.irq),
self.irq_b.eq(vuart_b.irq),

self.wb_a.connect(vuart_a.wb),
self.wb_b.connect(vuart_b.wb),
]

return m


if __name__ == "__main__":
top = VUartJoined()
with open("vuart_joined.v", "w") as f:
f.write(verilog.convert(top))

@ -0,0 +1,6 @@
[build-system]
requires = [
"setuptools>=42",
"wheel"
]
build-backend = "setuptools.build_meta"

@ -0,0 +1,30 @@
[metadata]
name = lpcperipheral
version = 0.0.1
author = Michael Neuling, Anton Blanchard
author_email = mikey@neuling.org, anton@ozlabs.org
description = A simple LPC peripheral, with IPMI BT, Virtual UART and FW DMA
long_description = file: README.md
long_description_content_type = text/markdown
keywords = nmigen
platform = any
license_file = LICENSE
url = https://github.com/OpenPOWERFoundation/lpcperipheral
project_urls =
Bug Tracker = https://github.com/open-power/lpcperipheral/issues
classifiers =
Development Status :: 3 - Alpha
Intended Audience :: Developers
Programming Language :: Python :: 3
Operating System :: OS Independent
Topic :: System :: Hardware

[options]
packages = find:
python_requires = >=3.6
test_suite = tests

[flake8]
max-line-length = 120
exclude = .git, .eggs, __pycache__, tests/, docs/, build/, dist/
ignore = E221

@ -0,0 +1,6 @@
#!/usr/bin/env python

import setuptools

if __name__ == "__main__":
setuptools.setup()

@ -0,0 +1,40 @@
import math
from nmigen import Elaboratable, Module, Memory
from nmigen_soc.wishbone import Interface
from nmigen.back import verilog


class ROM(Elaboratable, Interface):
def __init__(self, data, data_width=32):
self.data = data
self.size = len(data)
self.data_width = data_width

# Initialize wishbone interface
addr_width = math.ceil(math.log2(self.size + 1))
super().__init__(data_width=data_width, addr_width=addr_width)

def elaborate(self, platform):
m = Module()

data = Memory(width=self.data_width, depth=self.size, init=self.data)
read_port = data.read_port()

m.submodules.data = read_port

m.d.comb += [
read_port.addr.eq(self.adr),
self.dat_r.eq(read_port.data),
]

# Ack cycle after cyc and stb are asserted
m.d.sync += self.ack.eq(self.cyc & self.stb)

return m


if __name__ == "__main__":
data = [0x11111111, 0x22222222, 0x33333333, 0x44444444]
top = ROM(data=data)
with open("ROM.v", "w") as f:
f.write(verilog.convert(top))

@ -0,0 +1,52 @@
#!/usr/bin/env python

# This is a little test program so I could work out how multiple clock
# domains work. Not really part of this project but a handy refrence

from nmigen import *
from enum import Enum, unique

class clocks(Elaboratable):

def __init__(self):

# LPC clock pin
self.lclk = Signal()
self.counter = Signal(8)
self.lcounter = Signal(8)

def elaborate(self, platform):
m = Module()

lclk = ClockDomain("lclk")

# hook up lclk port to lclk_domain
m.d.comb += self.lclk.eq(ClockSignal("lclk"))

m.d.sync += self.counter.eq(self.counter + 1)
m.d["lclk"] += self.lcounter.eq(self.lcounter + 1)

return m

# --- TEST ---
from nmigen.sim import Simulator


dut = clocks()
def bench():
for _ in range(10):
yield

def lbench():
for _ in range(10):
yield

sim = Simulator(dut)
sim.add_clock(1e-8) # 100 MHz systemclock
sim.add_clock(3e-8, domain="lclk") # 33 MHz LPC clock
sim.add_sync_process(bench, domain="sync")
sim.add_sync_process(lbench, domain="lclk")

with sim.write_vcd("clocks.vcd"):
sim.run()

@ -0,0 +1,291 @@
import unittest

START_IO = 0b0000
START_FWRD = 0b1101
START_FWWR = 0b1110

CYCLE_IOWRITE = 0b0010
CYCLE_IOREAD = 0b0000

SYNC_READY = 0b0000
SYNC_SHORT_WAIT = 0b0101
SYNC_LONG_WAIT = 0b0110

class Helpers:
def wishbone_write(self, wb, addr, data, sel=1, delay=1):
yield wb.adr.eq(addr)
yield wb.dat_w.eq(data)
yield wb.we.eq(1)
yield wb.cyc.eq(1)
yield wb.stb.eq(1)
yield wb.sel.eq(sel)

# clock
yield

for i in range(delay):
# clock
yield

self.assertEqual((yield wb.ack), 1)
yield wb.we.eq(0)
yield wb.cyc.eq(0)
yield wb.stb.eq(0)
yield wb.sel.eq(0)
# Shouldn't need to clear dat and adr, so leave them set

def wishbone_read(self, wb, addr, expected, sel=1, delay=1):
yield wb.adr.eq(addr)
yield wb.cyc.eq(1)
yield wb.stb.eq(1)
yield wb.we.eq(0)
yield wb.sel.eq(sel)

# clock
yield

for i in range(delay):
# clock
yield

self.assertEqual((yield wb.ack), 1)
self.assertEqual((yield wb.dat_r), expected)
yield wb.cyc.eq(0)
yield wb.stb.eq(0)
yield wb.sel.eq(0)
# Shouldn't need to clear dat and adr, so leave it

# Partial transaction. Useful to test reset cases
def lpc_io_read_partial(self, lpc, cycles):
# Once driven things should start moving
yield lpc.lframe.eq(0)
yield lpc.lad_in.eq(START_IO)
yield

yield lpc.lframe.eq(1)
yield lpc.lad_in.eq(CYCLE_IOREAD)

for _ in range(cycles):
yield

def lpc_io_write(self, lpc, addr, data):
# Once driven things should start moving
yield lpc.lframe.eq(0)
yield lpc.lad_in.eq(START_IO)
yield

yield lpc.lframe.eq(0)
yield lpc.lad_in.eq(START_IO)
yield

yield lpc.lframe.eq(1)
yield lpc.lad_in.eq(CYCLE_IOWRITE)
yield

# 16 bits of addr, little endian, least significant nibble first
for i in reversed(range(0, 16, 4)):
x = (addr >> i) & 0xf
yield lpc.lad_in.eq(x)
yield

# 8 bits of data, big endian, most significant nibble first
for i in range(0, 8, 4):