Initial public release
Signed-off-by: Michael Neuling <mikey@neuling.org> Signed-off-by: Anton Blanchard <anton@ozlabs.org>pull/1/head
commit
ef2f91127c
@ -0,0 +1,10 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
/*.egg-info
|
||||||
|
/.eggs
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# tests
|
||||||
|
*.v
|
||||||
|
*.vcd
|
||||||
|
*.gtkw
|
@ -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…
Reference in New Issue