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