From ef2f91127c14f9f49759f614f512f3b79f659327 Mon Sep 17 00:00:00 2001 From: Michael Neuling Date: Thu, 23 Sep 2021 09:09:41 +1000 Subject: [PATCH] Initial public release Signed-off-by: Michael Neuling Signed-off-by: Anton Blanchard --- .gitignore | 10 + LICENSE | 23 +++ README.md | 70 +++++++ lpcperipheral/__init__.py | 0 lpcperipheral/io_space.py | 99 +++++++++ lpcperipheral/ipmi_bt.py | 317 +++++++++++++++++++++++++++++ lpcperipheral/lpc2wb.py | 199 ++++++++++++++++++ lpcperipheral/lpc_ctrl.py | 69 +++++++ lpcperipheral/lpcfront.py | 361 +++++++++++++++++++++++++++++++++ lpcperipheral/lpcperipheral.py | 132 ++++++++++++ lpcperipheral/vuart.py | 221 ++++++++++++++++++++ lpcperipheral/vuart_joined.py | 65 ++++++ pyproject.toml | 6 + setup.cfg | 30 +++ setup.py | 6 + tests/ROM.py | 40 ++++ tests/__init__.py | 0 tests/clocks.py | 52 +++++ tests/helpers.py | 291 ++++++++++++++++++++++++++ tests/test_io_space.py | 63 ++++++ tests/test_ipmi_bt.py | 250 +++++++++++++++++++++++ tests/test_lpc.py | 152 ++++++++++++++ tests/test_lpc2wb.py | 157 ++++++++++++++ tests/test_lpc_ctrl.py | 93 +++++++++ tests/test_vuart.py | 151 ++++++++++++++ tests/test_vuart_joined.py | 62 ++++++ 26 files changed, 2919 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 lpcperipheral/__init__.py create mode 100644 lpcperipheral/io_space.py create mode 100644 lpcperipheral/ipmi_bt.py create mode 100644 lpcperipheral/lpc2wb.py create mode 100644 lpcperipheral/lpc_ctrl.py create mode 100644 lpcperipheral/lpcfront.py create mode 100644 lpcperipheral/lpcperipheral.py create mode 100644 lpcperipheral/vuart.py create mode 100644 lpcperipheral/vuart_joined.py create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100755 setup.py create mode 100644 tests/ROM.py create mode 100644 tests/__init__.py create mode 100755 tests/clocks.py create mode 100644 tests/helpers.py create mode 100644 tests/test_io_space.py create mode 100644 tests/test_ipmi_bt.py create mode 100644 tests/test_lpc.py create mode 100644 tests/test_lpc2wb.py create mode 100644 tests/test_lpc_ctrl.py create mode 100644 tests/test_vuart.py create mode 100644 tests/test_vuart_joined.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7906257 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Python +__pycache__/ +/*.egg-info +/.eggs +/dist + +# tests +*.v +*.vcd +*.gtkw \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f283c2a --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +© IBM Corp. 2021 + +Licensed under the Apache License, Version 2.0 (the "License"), as modified by +the terms below; you may not use the files in this repository except in +compliance with the License as modified. You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 + +Modified Terms: + 1) For the purpose of the patent license granted to you in Section 3 of + the License, the "Work" hereby includes implementations of the work of + authorship in physical form. + +Unless required by applicable law or agreed to in writing, the reference design +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations under +the License. + +Brief explanation of modifications: + +Modification 1: This modification extends the patent license to an +implementation of the Work in physical form – i.e.,it unambiguously permits a +user to make and use the physical chip. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2e6f0fc --- /dev/null +++ b/README.md @@ -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 +``` diff --git a/lpcperipheral/__init__.py b/lpcperipheral/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lpcperipheral/io_space.py b/lpcperipheral/io_space.py new file mode 100644 index 0000000..6f4a32e --- /dev/null +++ b/lpcperipheral/io_space.py @@ -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)) diff --git a/lpcperipheral/ipmi_bt.py b/lpcperipheral/ipmi_bt.py new file mode 100644 index 0000000..2d6c353 --- /dev/null +++ b/lpcperipheral/ipmi_bt.py @@ -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)) diff --git a/lpcperipheral/lpc2wb.py b/lpcperipheral/lpc2wb.py new file mode 100644 index 0000000..6594966 --- /dev/null +++ b/lpcperipheral/lpc2wb.py @@ -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)) diff --git a/lpcperipheral/lpc_ctrl.py b/lpcperipheral/lpc_ctrl.py new file mode 100644 index 0000000..612d5b8 --- /dev/null +++ b/lpcperipheral/lpc_ctrl.py @@ -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)) diff --git a/lpcperipheral/lpcfront.py b/lpcperipheral/lpcfront.py new file mode 100644 index 0000000..50a258a --- /dev/null +++ b/lpcperipheral/lpcfront.py @@ -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)) diff --git a/lpcperipheral/lpcperipheral.py b/lpcperipheral/lpcperipheral.py new file mode 100644 index 0000000..ce32f18 --- /dev/null +++ b/lpcperipheral/lpcperipheral.py @@ -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])) diff --git a/lpcperipheral/vuart.py b/lpcperipheral/vuart.py new file mode 100644 index 0000000..d873493 --- /dev/null +++ b/lpcperipheral/vuart.py @@ -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)) diff --git a/lpcperipheral/vuart_joined.py b/lpcperipheral/vuart_joined.py new file mode 100644 index 0000000..5e85c1d --- /dev/null +++ b/lpcperipheral/vuart_joined.py @@ -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)) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..374b58c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel" +] +build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..32b277c --- /dev/null +++ b/setup.cfg @@ -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 diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..bac24a4 --- /dev/null +++ b/setup.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +import setuptools + +if __name__ == "__main__": + setuptools.setup() diff --git a/tests/ROM.py b/tests/ROM.py new file mode 100644 index 0000000..cffeeb8 --- /dev/null +++ b/tests/ROM.py @@ -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)) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/clocks.py b/tests/clocks.py new file mode 100755 index 0000000..1bdb7c8 --- /dev/null +++ b/tests/clocks.py @@ -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() + diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 0000000..7dedb01 --- /dev/null +++ b/tests/helpers.py @@ -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) diff --git a/tests/test_io_space.py b/tests/test_io_space.py new file mode 100644 index 0000000..425b4c2 --- /dev/null +++ b/tests/test_io_space.py @@ -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() diff --git a/tests/test_ipmi_bt.py b/tests/test_ipmi_bt.py new file mode 100644 index 0000000..0536646 --- /dev/null +++ b/tests/test_ipmi_bt.py @@ -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() diff --git a/tests/test_lpc.py b/tests/test_lpc.py new file mode 100644 index 0000000..0b11a7d --- /dev/null +++ b/tests/test_lpc.py @@ -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() diff --git a/tests/test_lpc2wb.py b/tests/test_lpc2wb.py new file mode 100644 index 0000000..a5a8c16 --- /dev/null +++ b/tests/test_lpc2wb.py @@ -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() diff --git a/tests/test_lpc_ctrl.py b/tests/test_lpc_ctrl.py new file mode 100644 index 0000000..31078f5 --- /dev/null +++ b/tests/test_lpc_ctrl.py @@ -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() diff --git a/tests/test_vuart.py b/tests/test_vuart.py new file mode 100644 index 0000000..04dd2da --- /dev/null +++ b/tests/test_vuart.py @@ -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() diff --git a/tests/test_vuart_joined.py b/tests/test_vuart_joined.py new file mode 100644 index 0000000..615eaa4 --- /dev/null +++ b/tests/test_vuart_joined.py @@ -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()