Initial public release

Signed-off-by: Michael Neuling <mikey@neuling.org>
Signed-off-by: Anton Blanchard <anton@ozlabs.org>
pull/1/head
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):
x = (data >> i) & 0xf
yield lpc.lad_in.eq(x)
yield

# TAR1 2 cycles
yield lpc.lad_in.eq(0x1) # eyecatcher
yield
self.assertEqual((yield lpc.lad_en), 0)
yield lpc.lad_in.eq(0x2) # eyecatcher
yield
self.assertEqual((yield lpc.lad_en), 0)

# Sync cycles
yield
while (yield lpc.lad_out) == SYNC_LONG_WAIT:
lad = yield lpc.lad_out
# print("Write SYNC wait: LAD:0x%x" % (lad))
self.assertEqual((yield lpc.lad_en), 1)
yield
self.assertEqual((yield lpc.lad_en), 1)
self.assertEqual((yield lpc.lad_out), SYNC_READY)

# TAR2 2 cycles
yield
self.assertEqual((yield lpc.lad_out), 0b1111)
self.assertEqual((yield lpc.lad_en), 1)
yield lpc.lad_in.eq(0xa) # eyecatcher
yield
self.assertEqual((yield lpc.lad_en), 0)

def lpc_io_read(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(1)
yield lpc.lad_in.eq(CYCLE_IOREAD)
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

# TAR1 2 cycles
yield lpc.lad_in.eq(0x1) # eyecatcher
yield
self.assertEqual((yield lpc.lad_en), 0)
yield lpc.lad_in.eq(0x2) # eyecatcher
yield
self.assertEqual((yield lpc.lad_en), 0)

# Sync cycles
yield
while (yield lpc.lad_out) == SYNC_LONG_WAIT:
lad = yield lpc.lad_out
# print("Read SYNC wait: LAD:0x%x" % (lad))
self.assertEqual((yield lpc.lad_en), 1)
yield
self.assertEqual((yield lpc.lad_en), 1)
self.assertEqual((yield lpc.lad_out), SYNC_READY)

# 8 bits of data, big endian, most significant nibble first
for i in range(0, 8, 4):
yield
x = (data >> i) & 0xf
self.assertEqual((yield lpc.lad_out), x)
self.assertEqual((yield lpc.lad_en), 1)

# TAR2 2 cycles
yield
self.assertEqual((yield lpc.lad_en), 1)
self.assertEqual((yield lpc.lad_out), 0b1111)
yield lpc.lad_in.eq(0xa) # eyecatcher
yield
self.assertEqual((yield lpc.lad_en), 0)

def lpc_fw_write(self, lpc, addr, data, size):
assert ((size == 4) | (size == 2) | (size == 1))
# Once driven things should start moving
yield lpc.lframe.eq(0)
yield lpc.lad_in.eq(START_FWWR)
yield

yield lpc.lframe.eq(1)
yield lpc.lad_in.eq(0) # IDSEL
yield

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

# msize encoding. size is in byte
if (size == 1):
yield lpc.lad_in.eq(0b0000)
elif (size == 2):
yield lpc.lad_in.eq(0b0001)
elif (size == 4):
yield lpc.lad_in.eq(0b0010)
else:
assert(0)
yield

# 8 bits of data, big endian, most significant nibble first
for i in range(0, size*8, 4):
x = (data >> i) & 0xf
yield lpc.lad_in.eq(x)
yield

# TAR1 2 cycles
yield lpc.lad_in.eq(0x1) # eyecatcher
yield
self.assertEqual((yield lpc.lad_en), 0)
yield lpc.lad_in.eq(0x2) # eyecatcher
yield
self.assertEqual((yield lpc.lad_en), 0)

# Sync cycles
yield
while (yield lpc.lad_out) == SYNC_LONG_WAIT:
lad = yield lpc.lad_out
# print("Write SYNC wait: LAD:0x%x" % (lad))
self.assertEqual((yield lpc.lad_en), 1)
yield
self.assertEqual((yield lpc.lad_en), 1)
self.assertEqual((yield lpc.lad_out), SYNC_READY)

# TAR2 2 cycles
yield
self.assertEqual((yield lpc.lad_en), 1)
self.assertEqual((yield lpc.lad_out), 0b1111)
yield lpc.lad_in.eq(0xa) # eyecatcher
yield
self.assertEqual((yield lpc.lad_en), 0)

def lpc_fw_read(self, lpc, addr, data, size):
assert ((size == 4) | (size == 2) | (size == 1))
# Once driven things should start moving
yield lpc.lframe.eq(0)
yield lpc.lad_in.eq(START_FWRD)
yield

yield lpc.lframe.eq(1)
yield lpc.lad_in.eq(0) # IDSEL
yield

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

# msize encoding. size is in byte
if (size == 1):
yield lpc.lad_in.eq(0b0000)
elif (size == 2):
yield lpc.lad_in.eq(0b0001)
elif (size == 4):
yield lpc.lad_in.eq(0b0010)
else:
assert(0)
yield

# TAR1 2 cycles
yield lpc.lad_in.eq(0x1) # eyecatcher
yield
self.assertEqual((yield lpc.lad_en), 0)
yield lpc.lad_in.eq(0x2) # eyecatcher
yield
self.assertEqual((yield lpc.lad_en), 0)

# Sync cycles
yield
while (yield lpc.lad_out) == SYNC_LONG_WAIT:
lad = yield lpc.lad_out
# print("Read SYNC wait: LAD:0x%x" % (lad))
self.assertEqual((yield lpc.lad_en), 1)
yield
self.assertEqual((yield lpc.lad_en), 1)
self.assertEqual((yield lpc.lad_out), SYNC_READY)

# 32 bits of data, big endian, most significant nibble first
for i in range(0, size*8, 4):
yield
x = (data >> i) & 0xf
self.assertEqual((yield lpc.lad_out), x)
self.assertEqual((yield lpc.lad_en), 1)

# TAR2 2 cycles
yield
self.assertEqual((yield lpc.lad_out), 0b1111)
self.assertEqual((yield lpc.lad_en), 1)
yield lpc.lad_in.eq(0xa) # eyecatcher
yield
self.assertEqual((yield lpc.lad_en), 0)

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

from nmigen.sim import Simulator

from lpcperipheral.io_space import IOSpace
from lpcperipheral.ipmi_bt import RegEnum, BMCRegEnum

from .helpers import Helpers


class TestSum(unittest.TestCase, Helpers):
def setUp(self):
self.dut = IOSpace()

def test_io_space_vuart(self):
def bench():
yield

# Look for TX empty bits
yield from self.wishbone_read(self.dut.bmc_wb, (0x0 + (5 * 4)) // 4, 0x60)

# Test 1 byte from BMC to target
yield from self.wishbone_write(self.dut.bmc_wb, 0x0 // 4, 0x12)
yield
yield from self.wishbone_read(self.dut.target_wb, 0x3f8, 0x12)

# Test 1 byte from target to BMC
yield from self.wishbone_write(self.dut.target_wb, 0x3f8, 0x13)
yield
yield from self.wishbone_read(self.dut.bmc_wb, 0x0 // 4, 0x13)

# Test 3 bytes from BMC to target
yield from self.wishbone_write(self.dut.bmc_wb, 0x0 // 4, 0x15)
yield from self.wishbone_write(self.dut.bmc_wb, 0x0 // 4, 0x16)
yield from self.wishbone_write(self.dut.bmc_wb, 0x0 // 4, 0x17)
yield from self.wishbone_read(self.dut.target_wb, 0x3f8, 0x15)
yield from self.wishbone_read(self.dut.target_wb, 0x3f8, 0x16)
yield from self.wishbone_read(self.dut.target_wb, 0x3f8, 0x17)

sim = Simulator(self.dut)
sim.add_clock(1e-6) # 1 MHz
sim.add_sync_process(bench)
with sim.write_vcd("test_io_space_vuart.vcd"):
sim.run()

def test_io_space_ipmi_bt(self):
def bench():
yield

# Test 1 byte from BMC to target via IPMI BT
yield from self.wishbone_write(self.dut.bmc_wb, 0x1000//4 + BMCRegEnum.BMC2HOST_HOST2BMC, 0x43)
yield
yield from self.wishbone_read(self.dut.target_wb, 0xe4 + RegEnum.BMC2HOST_HOST2BMC, 0x43)

sim = Simulator(self.dut)
sim.add_clock(1e-6) # 1 MHz
sim.add_sync_process(bench)
with sim.write_vcd("test_io_space_ipmi_bt.vcd"):
sim.run()


if __name__ == '__main__':
unittest.main()

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

from nmigen.sim import Simulator

from lpcperipheral.ipmi_bt import IPMI_BT, RegEnum, BMCRegEnum

from .helpers import Helpers


class TestSum(unittest.TestCase, Helpers):
def setUp(self):
self.dut = IPMI_BT(depth=64)

def test_fifo(self):
def bench():
yield

# Write one byte from target and read from BMC
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BMC2HOST_HOST2BMC, 0x12)
yield
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BMC2HOST_HOST2BMC, 0x12)

# Write 64 bytes from target and read from BMC
for i in range(0, 64):
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BMC2HOST_HOST2BMC, i)
for i in range(0, 64):
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BMC2HOST_HOST2BMC, i)

# Write one byte from BMC and read from target
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BMC2HOST_HOST2BMC, 0x12)
yield
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BMC2HOST_HOST2BMC, 0x12)

# Write 64 bytes from BMC and read from target
for i in range(0, 64):
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BMC2HOST_HOST2BMC, i)
for i in range(0, 64):
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BMC2HOST_HOST2BMC, i)

# Read from empty FIFO (should read 0)
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BMC2HOST_HOST2BMC, 0)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BMC2HOST_HOST2BMC, 0)

# Write to full FIFO (should do nothing)
for i in range(0, 65):
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BMC2HOST_HOST2BMC, i)
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BMC2HOST_HOST2BMC, i)
for i in range(0, 64):
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BMC2HOST_HOST2BMC, i)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BMC2HOST_HOST2BMC, i)

# Test reset of target fifo from target
for i in range(0, 64):
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BMC2HOST_HOST2BMC, 0x5a)
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_CTRL, 0x1)
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BMC2HOST_HOST2BMC, 0x98)
yield # Need a cycle between writing the FIFO and reading it
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BMC2HOST_HOST2BMC, 0x98)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BMC2HOST_HOST2BMC, 0)

# Test reset of BMC fifo from BMC
for i in range(0, 64):
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BMC2HOST_HOST2BMC, 0xa5)
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 0x1)
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BMC2HOST_HOST2BMC, 0x78)
yield # Need a cycle between writing the FIFO and reading it
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BMC2HOST_HOST2BMC, 0x78)
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BMC2HOST_HOST2BMC, 0)

sim = Simulator(self.dut)
sim.add_clock(1e-6) # 1 MHz
sim.add_sync_process(bench)
with sim.write_vcd("test_ipmi_bt_fifo.vcd"):
sim.run()

def test_ctrl(self):
def bench():
# Init value for BT_CTRL
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 0)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 0)

# BT_CTRL bits 2, 5: target set, BMC clear
for b in (2, 5):
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 0x0)
# Write 1 on BMC, should do nothing
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << b)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 0)
# Write 1 from target
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_CTRL, 1 << b)
# Check for 1 on target and bmc
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 1 << b)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << b)
# Write 1 on target, should do nothing
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_CTRL, 1 << b)
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 1 << b)
# Write 1 on bmc, should clear bit
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << b)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 0)

# BT_CTRL bits 3, 4: BMC set, target clear
for b in (3, 4):
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 0x0)
# Write 1 on target, should do nothing
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_CTRL, 1 << b)
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 0)
# Write 1 from BMC
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << b)
# Check for 1 on target and bmc
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 1 << b)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << b)
# Write 1 on BMC, should do nothing
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << b)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << b)
# Write 1 on target, should clear bit
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_CTRL, 1 << b)
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 0)

# BT_CTRL bit 6
# Set bit, read from both target and BMC
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_CTRL, 1 << 6)
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 1 << 6)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << 6)

# Writing from BMC should do nothing
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << 6)
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 1 << 6)

# Writing from target should clear
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_CTRL, 1 << 6)
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 0)

# BT_CTRL bit 7
# Set bit, read from both target and BMC
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << 7)
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 1 << 7)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << 7)

# Writing from target should do nothing
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_CTRL, 1 << 7)
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 1 << 7)

# Writing from BMC should clear
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << 7)
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 0)

# Read and write all bits in BT_CTRL from the target side
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_CTRL, 0xff)
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_CTRL, 0x64)


sim = Simulator(self.dut)
sim.add_clock(1e-6) # 1 MHz
sim.add_sync_process(bench)
with sim.write_vcd("test_ipmi_bt_ctrl.vcd"):
sim.run()

def test_target_interrupts(self):
def bench():
# Test reading/writing BT_INTMASK from target. Only the bottom bit should be
# writeable
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_INTMASK, 0xff)
yield from self.wishbone_read(self.dut.target_wb, RegEnum.BT_INTMASK, 0x1)
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_INTMASK, 0x0)

# With interrupts masked, shouldn't get an interrupt
self.assertEqual((yield self.dut.target_irq), 0)
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << 3)
self.assertEqual((yield self.dut.target_irq), 0)

# With interrupt unmasked, should get interrupt, but only on a 0-1 transition
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_INTMASK, 1 << 0)
self.assertEqual((yield self.dut.target_irq), 0)
# Clear
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_CTRL, 1 << 3)
self.assertEqual((yield self.dut.target_irq), 0)
# and reassert
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << 3)
self.assertEqual((yield self.dut.target_irq), 1)

# Test the interrupt masking bit
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_INTMASK, 0x0)
self.assertEqual((yield self.dut.target_irq), 0)
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_INTMASK, 0x1)
self.assertEqual((yield self.dut.target_irq), 1)

# Finally ack it
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_INTMASK, 0x2)
self.assertEqual((yield self.dut.target_irq), 0)


sim = Simulator(self.dut)
sim.add_clock(1e-6) # 1 MHz
sim.add_sync_process(bench)
with sim.write_vcd("test_ipmi_bt_target_interrupts.vcd"):
sim.run()


def test_bmc_interrupts(self):
def bench():
# Test reading/writing IRQ_MASK from BMC. Only the bottom two bits should be
# writeable
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.IRQ_MASK, 0xff)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.IRQ_MASK, 0x3)
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.IRQ_MASK, 0x0)

# Test reading/writing IRQ_STATUS from BMC. Only the bottom two bits should be
# writeable
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.IRQ_STATUS, 0xff)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.IRQ_STATUS, 0x3)
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.IRQ_STATUS, 0x0)

# With interrupts masked, shouldn't get an interrupt
self.assertEqual((yield self.dut.bmc_irq), 0)
yield from self.wishbone_write(self.dut.target_wb, BMCRegEnum.BT_CTRL, 1 << 2)
self.assertEqual((yield self.dut.bmc_irq), 0)

# With interrupt unmasked, should get interrupt, but only on a 0-1 transition
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.IRQ_MASK, 1 << 0)
self.assertEqual((yield self.dut.bmc_irq), 0)
# Clear
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.BT_CTRL, 1 << 2)
self.assertEqual((yield self.dut.bmc_irq), 0)
# and reassert
yield from self.wishbone_write(self.dut.target_wb, RegEnum.BT_CTRL, 1 << 2)
self.assertEqual((yield self.dut.bmc_irq), 1)

# Test the interrupt masking bit
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.IRQ_MASK, 0x0)
self.assertEqual((yield self.dut.bmc_irq), 0)
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.IRQ_MASK, 0x1)
self.assertEqual((yield self.dut.bmc_irq), 1)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.IRQ_STATUS, 0x1)

# Finally ack it
yield from self.wishbone_write(self.dut.bmc_wb, BMCRegEnum.IRQ_STATUS, 0x0)
self.assertEqual((yield self.dut.bmc_irq), 0)
yield from self.wishbone_read(self.dut.bmc_wb, BMCRegEnum.IRQ_STATUS, 0x0)

# Test target busy goes low interrupt


sim = Simulator(self.dut)
sim.add_clock(1e-6) # 1 MHz
sim.add_sync_process(bench)
with sim.write_vcd("test_ipmi_bt_target_interrupts.vcd"):
sim.run()


if __name__ == '__main__':
unittest.main()

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

from nmigen import Elaboratable, Module, Signal
from nmigen_soc.wishbone import Interface as WishboneInterface
from nmigen.sim import Simulator

from lpcperipheral.lpcperipheral import LPCPeripheral

from .ROM import ROM
from .helpers import Helpers

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

LPC_IO_TESTS = 16
LPC_FW_TESTS = 2


class LPC_AND_ROM(Elaboratable):
def __init__(self):
self.bmc_wb = WishboneInterface(data_width=32, addr_width=14, granularity=8)

# 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.lpc = lpc = LPCPeripheral()

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

# LPC pins
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),
]

# Initialize ROM with the offset so we can easily determine if we are
# reading from the right address
data = range(128)
m.submodules.rom = rom = ROM(data=data)

m.d.comb += [
# DMA wishbone to ROM
rom.adr.eq(lpc.dma_adr),
rom.dat_w.eq(lpc.dma_dat_w),
rom.sel.eq(lpc.dma_sel),
rom.cyc.eq(lpc.dma_cyc),
rom.stb.eq(lpc.dma_stb),
rom.we.eq(lpc.dma_we),
lpc.dma_dat_r.eq(rom.dat_r),
lpc.dma_ack.eq(rom.ack),
]

return m

wb_read_go = 0

class TestSum(unittest.TestCase, Helpers):
def setUp(self):
self.dut = LPC_AND_ROM()

def test_bench(self):
def bench():
global wb_read_go
while wb_read_go == 0:
yield
yield from self.wishbone_read(self.dut.bmc_wb, 0x1014>>2, 0x65)
yield from self.wishbone_read(self.dut.bmc_wb, 0x1014>>2, 0x48)

wb_read_go = 0
while wb_read_go == 0:
yield
yield from self.wishbone_read(self.dut.bmc_wb, 0x1010>>2, 0x4)

def lbench():
global wb_read_go
wb_read_go = 0
yield
yield self.dut.lreset.eq(1)
yield self.dut.lframe.eq(1)
yield

# Write 2 bytes to LPC IPMI-BT FIFO, read it on BMC wishbone
yield from self.lpc_io_write(self.dut, 0xe5, 0x65)
yield from self.lpc_io_write(self.dut, 0xe5, 0x48)
wb_read_go = 1

while wb_read_go == 1:
yield

# Test writing IPMI BT HOST2BMC attn bit, and reading it from the BMC
yield from self.lpc_io_write(self.dut, 0xe4, 0x4)
wb_read_go = 1

#yield from self.lpc_fw_read(self.dut, 0xFFFFFFF, 1, 4)

#yield from self.lpc_fw_read(self.dut, 1, 1, 4)
#yield from self.lpc_fw_read(self.dut, 1, 1, 4)
#yield from self.lpc_fw_read(self.dut, 1, 1, 4)

#yield from self.lpc_fw_read(self.dut, 2, 2, 4)
#yield from self.lpc_fw_read(self.dut, 2, 2, 4)
#yield from self.lpc_fw_read(self.dut, 2, 2, 4)

sim = Simulator(self.dut)
# Make life easy by just running both clocks at same frequency
sim.add_clock(1e-8)
sim.add_clock(3e-8, domain="lclk")
sim.add_clock(3e-8, domain="lclkrst")
#sim._engine.add_clock_process(self.dut.lclk, phase=None, period=1e-8)
sim.add_sync_process(lbench, domain="lclk")
sim.add_sync_process(bench, domain="sync")

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

if __name__ == '__main__':
unittest.main()

@ -0,0 +1,157 @@
import unittest
import random

from nmigen.sim import Simulator

from lpcperipheral.lpc2wb import lpc2wb

from .helpers import Helpers

LPC_IO_TESTS = 16
LPC_FW_TESTS = 128

addr = 0
data = 0
size = 0

class TestSum(unittest.TestCase, Helpers):
def setUp(self):
self.dut = lpc2wb()

def test_bench(self):

def io_bench():
global addr
global data
# ACK wishbone bus
# check data on writes and send data back on reads
for i in range(LPC_IO_TESTS):
# wait for wishbone cycle
while (yield self.dut.io_wb.cyc) == 0:
yield
self.assertEqual((yield self.dut.io_wb.adr), addr)
if (yield self.dut.io_wb.we) == 1:
# check data for LPC writes
# print("Checking write: addr:%x" % (io_addr))
self.assertEqual((yield self.dut.io_wb.dat_w),
data)
else:
# End back hashed data for LPC reads
# print("Sending read: addr:%x" % (io_addr))
yield self.dut.io_wb.dat_r.eq(data)
yield self.dut.io_wb.ack.eq(1)
yield
yield self.dut.io_wb.ack.eq(0)
yield

def fw_bench():
global addr
global data
global size
# ACK wishbone bus
# check data on writes and send data back on reads
for i in range(LPC_FW_TESTS):
# wait for wishbone cycle
while (yield self.dut.fw_wb.cyc) == 0:
yield
yield
yield
yield
self.assertEqual((yield self.dut.fw_wb.adr), addr >> 2) # 4 byte word addr
if (yield self.dut.fw_wb.we) == 1:
# check data for LPC writes
# print("Checking FW write: addr:%x" % (addr))
wb = yield self.dut.fw_wb.dat_w
if (size == 1):
wb = wb >> (8 * (addr & 0x3))
d = data & 0xff
sel = 1 << (addr & 3)
if (size == 2):
wb = wb >> (8 * (addr & 0x2))
d = data & 0xffff
sel = 0b0011 << (addr & 0x2)
if (size == 4):
d = data
sel = 0b1111
self.assertEqual(d, wb)

else: # reads
# End back hashed data for LPC reads
if (size == 1):
d = data & 0xff
d = d << (8 * (addr & 0x3))
yield self.dut.fw_wb.dat_r.eq(d)
sel = 1 << (addr & 3)
if (size == 2):
d = data & 0xffff
d = d << (8 * (addr & 0x2))
sel = 0b0011 << (addr & 0x2)
yield self.dut.fw_wb.dat_r.eq(d)
if (size == 4):
yield self.dut.fw_wb.dat_r.eq(data)
sel = 0b1111
self.assertEqual((yield self.dut.fw_wb.sel), sel)

yield self.dut.fw_wb.ack.eq(1)
yield
yield self.dut.fw_wb.ack.eq(0)
yield

def lpc_bench():
global addr
global data
global size

# lframe = 1 shouldn't move
yield self.dut.lframe.eq(1)
yield self.dut.lreset.eq(1)
for _ in range(4):
yield

# Do a bunch of partial transactions at the start to see
# if it locks up the bus for later transactions
for i in range(8):
yield from self.lpc_io_read_partial(self.dut, i)

for i in range(LPC_FW_TESTS):
addr = random.randrange(0x10000000)
data = random.randrange(0x100000000)
size = 2**random.randrange(3) # 1,2,4
addrmask = 0xffffffff & ~(size - 1)
addr = addr & addrmask # align address
if random.randrange(2):
yield from self.lpc_fw_write(self.dut, addr, data, size)
else:
yield from self.lpc_fw_read(self.dut, addr, data, size)
yield
yield

for _ in range(10):
yield

# do a bunch of random read and write tests
for i in range(LPC_IO_TESTS):
addr = random.randrange(0x10000)
data = random.randrange(0x100)
if random.randrange(2):
yield from self.lpc_io_write(self.dut, addr, data)
else:
yield from self.lpc_io_read(self.dut, addr, data)
yield

yield


sim = Simulator(self.dut)
sim.add_clock(1e-8) # 100 MHz systemclock
sim.add_clock(3e-8, domain="lclk") # 30 MHz LPC clock
sim.add_clock(3e-8, domain="lclkrst") # 30 MHz LPC clock
sim.add_sync_process(lpc_bench, domain="lclk")
sim.add_sync_process(io_bench, domain="sync")
sim.add_sync_process(fw_bench, domain="sync")
with sim.write_vcd("lpc2wb_lbench.vcd"):
sim.run()


if __name__ == '__main__':
unittest.main()

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

from nmigen import Elaboratable, Module
from nmigen_soc.wishbone import Interface as WishboneInterface
from nmigen.sim import Simulator

from lpcperipheral.lpc_ctrl import LPC_Ctrl

from .ROM import ROM
from .helpers import Helpers


class LPC_AND_ROM(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)

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

m.submodules.ctrl = ctrl = LPC_Ctrl()

m.d.comb += [
self.io_wb.connect(ctrl.io_wb),
self.lpc_wb.connect(ctrl.lpc_wb),
]

# Initialize ROM with the offset so we can easily determine if we are
# reading from the right address
data = range(128)
m.submodules.rom = rom = ROM(data=data)
m.d.comb += ctrl.dma_wb.connect(rom)

return m


class TestSum(unittest.TestCase, Helpers):
def setUp(self):
self.dut = LPC_AND_ROM()

def test_read_write(self):
def bench():
# base register, offset 0
# Note CSRs have an extra cycle before ack, hence delay=2
yield from self.wishbone_write(self.dut.io_wb, 0, 0x12345678, delay=2)
yield
yield from self.wishbone_read(self.dut.io_wb, 0, 0x12345678, delay=2)

# mask register, offset 2
yield from self.wishbone_write(self.dut.io_wb, 2, 0xBADC0FFE, delay=2)
yield
yield from self.wishbone_read(self.dut.io_wb, 2, 0xBADC0FFE, delay=2)

sim = Simulator(self.dut)
sim.add_clock(1e-6) # 1 MHz
sim.add_sync_process(bench)
with sim.write_vcd("test_lpc_ctrl_read_write.vcd"):
sim.run()

def test_base_offset(self):
def bench():
yield

# No offset, don't mask off any bits
# Note CSRs have an extra cycle before ack, hence delay=2
yield from self.wishbone_write(self.dut.io_wb, 0, 0x0, delay=2)
yield from self.wishbone_write(self.dut.io_wb, 0x2, 0xffffffff, delay=2)

for i in range(64):
yield from self.wishbone_read(self.dut.lpc_wb, i, i)

# Apply offset and test. The base/mask registers are in bytes
# So we have to convert from wishbone addresses (assuming a 32
# bit wishbone, multiply by 4)
base = 32 # In wishbone units
yield from self.wishbone_write(self.dut.io_wb, 0, base * 4, delay=2)
for i in range(32):
yield from self.wishbone_read(self.dut.lpc_wb, i, i + base, delay=2)

# Apply mask and test
yield from self.wishbone_write(self.dut.io_wb, 0x2, 0xf * 4, delay=2)
for i in range(32):
yield from self.wishbone_read(self.dut.lpc_wb, i, ((i % 0x10) + base), delay=2)

sim = Simulator(self.dut)
sim.add_clock(1e-6) # 1 MHz
sim.add_sync_process(bench)
with sim.write_vcd("test_lpc_ctrl_base_offset.vcd"):
sim.run()


if __name__ == '__main__':
unittest.main()

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

from nmigen.sim import Simulator

from lpcperipheral.vuart import VUart, RegEnum, LCR_DLAB
from .helpers import Helpers


class TestSum(unittest.TestCase, Helpers):
def setUp(self):
self.dut = VUart()

def test_vuart(self):
def bench():
yield

# Test reading and writing to LCR, MCR, MSR, SCR
val = 0xF
for r in (RegEnum.LCR, RegEnum.MCR, RegEnum.MSR, RegEnum.SCR):
yield from self.wishbone_write(self.dut.wb, r, val)
yield from self.wishbone_read(self.dut.wb, r, val)
val = val + 1

# Test writing to FCR (write only)
yield from self.wishbone_write(self.dut.wb, RegEnum.IIR_FCR, val)

# Test reading from LSR (THRE and TEMT bits should be set)
yield from self.wishbone_read(self.dut.wb, RegEnum.LSR, 0b01100000)

# Test reading from LSR with data ready (RD, THRE and TEMT bits should be set)
yield self.dut.r_rdy.eq(1)
yield from self.wishbone_read(self.dut.wb, RegEnum.LSR, 0b01100001)

# Set DLAB bit
yield from self.wishbone_write(self.dut.wb, RegEnum.LCR, 1 << LCR_DLAB)

# Test reading and writing to DLL and DLM
for r in (RegEnum.RXTX_DLL, RegEnum.IER_DLM):
yield from self.wishbone_write(self.dut.wb, r, val)
yield from self.wishbone_read(self.dut.wb, r, val)
val = val + 1

# Clear DLAB bit
yield from self.wishbone_write(self.dut.wb, RegEnum.LCR, 0x00)

# Test read from non empty FIFO
yield self.dut.r_rdy.eq(1)
yield self.dut.r_data.eq(0x45)

yield from self.wishbone_read(self.dut.wb, RegEnum.RXTX_DLL, 0x45)
self.assertEqual((yield self.dut.r_en), 1)
self.assertEqual((yield self.dut.r_data), 0x45)
yield
self.assertEqual((yield self.dut.r_en), 0)

# Test read from empty FIFO, check we don't attempt a read from the FIFO
# and we return 0 over wishbone
yield self.dut.r_rdy.eq(0)
yield self.dut.r_data.eq(0x33)

yield from self.wishbone_read(self.dut.wb, RegEnum.RXTX_DLL, 0x00)
self.assertEqual((yield self.dut.r_en), 0)
yield
self.assertEqual((yield self.dut.r_en), 0)

# Test write to non full FIFO
yield self.dut.w_rdy.eq(1)

yield from self.wishbone_write(self.dut.wb, RegEnum.RXTX_DLL, 0x65)
self.assertEqual((yield self.dut.w_en), 1)
self.assertEqual((yield self.dut.w_data), 0x65)
yield
self.assertEqual((yield self.dut.w_en), 0)

# Test write to full FIFO
yield self.dut.w_rdy.eq(0)

yield from self.wishbone_write(self.dut.wb, RegEnum.RXTX_DLL, 0x77)
self.assertEqual((yield self.dut.w_en), 0)

# XXX Need to test ier lsr
# IIR - read only
# IER

sim = Simulator(self.dut)
sim.add_clock(1e-6) # 1 MHz
sim.add_sync_process(bench)
with sim.write_vcd("test_vuart.vcd"):
sim.run()

def test_vuart_irqs(self):
def bench():
yield

# Clear DLAB bit
yield from self.wishbone_write(self.dut.wb, RegEnum.LCR, 0x00)

# Test RX irq

# No irqs if IER=0
yield from self.wishbone_write(self.dut.wb, RegEnum.IER_DLM, 0x0)
self.assertEqual((yield self.dut.irq), 0)
yield from self.wishbone_read(self.dut.wb, RegEnum.IIR_FCR, 0b0001)

# Set RX FIFO not empty
yield self.dut.r_rdy.eq(1)
yield self.dut.r_data.eq(0x45)
yield
self.assertEqual((yield self.dut.irq), 0)
yield from self.wishbone_read(self.dut.wb, RegEnum.IIR_FCR, 0b0001)

# RX irq if bit 1 is set
yield from self.wishbone_write(self.dut.wb, RegEnum.IER_DLM, 0x1)
yield
self.assertEqual((yield self.dut.irq), 1)
yield from self.wishbone_read(self.dut.wb, RegEnum.IIR_FCR, 0b0100)

# No RX irq if IER=1 but empty RX FIFO
yield self.dut.r_rdy.eq(0)
yield self.dut.r_data.eq(0x00)
yield
self.assertEqual((yield self.dut.irq), 0)
yield from self.wishbone_read(self.dut.wb, RegEnum.IIR_FCR, 0b0001)

# Test TX irq

# TX irq whenever IER bit 2 is set
yield from self.wishbone_write(self.dut.wb, RegEnum.IER_DLM, 0x2)
yield
self.assertEqual((yield self.dut.irq), 1)
yield from self.wishbone_read(self.dut.wb, RegEnum.IIR_FCR, 0b0010)

# Test TX and RX irq together

# Test RX irq priority over TX
yield from self.wishbone_write(self.dut.wb, RegEnum.IER_DLM, 0x3)
yield self.dut.r_rdy.eq(1)
yield self.dut.r_data.eq(0x45)
yield
self.assertEqual((yield self.dut.irq), 1)
yield from self.wishbone_read(self.dut.wb, RegEnum.IIR_FCR, 0b0100)

sim = Simulator(self.dut)
sim.add_clock(1e-6) # 1 MHz
sim.add_sync_process(bench)
with sim.write_vcd("test_vuart_irqs.vcd"):
sim.run()


if __name__ == '__main__':
unittest.main()

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

from nmigen.sim import Simulator

from lpcperipheral.vuart import RegEnum
from lpcperipheral.vuart_joined import VUartJoined

from .helpers import Helpers

class TestSum(unittest.TestCase, Helpers):
def setUp(self):
self.dut = VUartJoined(depth=2)

def test_vuart_joined(self):
def bench():
yield

# Try writing one byte and reading it from the other VUart
yield from self.wishbone_write(self.dut.wb_a, RegEnum.RXTX_DLL, 0x65)
yield # SyncFIFOBuffered needs one cycle for write -> read ready
yield from self.wishbone_read(self.dut.wb_b, RegEnum.RXTX_DLL, 0x65)

# Same test from other VUart
yield from self.wishbone_write(self.dut.wb_b, RegEnum.RXTX_DLL, 0x79)
yield # SyncFIFOBuffered needs one cycle for write -> read ready
yield from self.wishbone_read(self.dut.wb_a, RegEnum.RXTX_DLL, 0x79)

# Try reading from an empty FIFO
yield from self.wishbone_read(self.dut.wb_a, RegEnum.RXTX_DLL, 0x0)
yield from self.wishbone_read(self.dut.wb_b, RegEnum.RXTX_DLL, 0x0)

# Write 2 bytes and read them
yield from self.wishbone_write(self.dut.wb_a, RegEnum.RXTX_DLL, 0x45)
# SyncFIFOBuffered drops w_rdy for 1 cycle on almost (n-1)
# full, likely because there is a separate 1 entry read
# buffer. Bug?
yield
yield from self.wishbone_write(self.dut.wb_a, RegEnum.RXTX_DLL, 0x32)
yield from self.wishbone_read(self.dut.wb_b, RegEnum.RXTX_DLL, 0x45)
yield from self.wishbone_read(self.dut.wb_b, RegEnum.RXTX_DLL, 0x32)

# Write 3 bytes and read 2 (We configured the FIFO to be 2 deep)
yield from self.wishbone_write(self.dut.wb_a, RegEnum.RXTX_DLL, 0x11)
# (n-1) full issue as above
yield
yield from self.wishbone_write(self.dut.wb_a, RegEnum.RXTX_DLL, 0x22)
# (n-1) full issue as above
yield
yield from self.wishbone_write(self.dut.wb_a, RegEnum.RXTX_DLL, 0x33)
yield from self.wishbone_read(self.dut.wb_b, RegEnum.RXTX_DLL, 0x11)
yield from self.wishbone_read(self.dut.wb_b, RegEnum.RXTX_DLL, 0x22)
yield from self.wishbone_read(self.dut.wb_b, RegEnum.RXTX_DLL, 0x00)

sim = Simulator(self.dut)
sim.add_clock(1e-6) # 1 MHz
sim.add_sync_process(bench)
with sim.write_vcd("vuart_joined.vcd"):
sim.run()


if __name__ == '__main__':
unittest.main()
Loading…
Cancel
Save