6.14. IOBinders and HarnessBinders
In Chipyard we use special Parameters
keys, IOBinders
and HarnessBinders
to bridge the gap between digital system IOs and TestHarness collateral.
6.14.1. IOBinders
The IOBinder
functions are responsible for instantiating IO cells and IOPorts in the ChipTop
layer.
IOBinders
are typically defined using the OverrideIOBinder
or ComposeIOBinder
macros. An IOBinder
consists of a function matching Systems
with a given trait that generates IO ports and IOCells, and returns a list of generated ports and cells.
For example, the WithUARTIOCells
IOBinder will, for any System
that might have UART ports (HasPeripheryUARTModuleImp
, generate ports within the ChipTop
(ports
) as well as IOCells with the appropriate type and direction (cells2d
). This function returns a the list of generated ports, and the list of generated IOCells. The list of generated ports is passed to the HarnessBinders
such that they can be connected to TestHarness
devices.
class WithUARTIOCells extends OverrideIOBinder({
(system: HasPeripheryUART) => {
val (ports: Seq[UARTPort], cells2d) = system.uart.zipWithIndex.map({ case (u, i) =>
val p = system.asInstanceOf[BaseSubsystem].p
val (port, ios) = IOCell.generateIOFromSignal(u, s"uart_${i}", p(IOCellKey), abstractResetAsAsync = true)
val where = PBUS // TODO fix
val bus = system.asInstanceOf[HasTileLinkLocations].locateTLBusWrapper(where)
val freqMHz = bus.dtsFrequency.get / 1000000
(UARTPort(() => port, i, freqMHz.toInt), ios)
}).unzip
(ports, cells2d.flatten)
}
})
6.14.2. HarnessBinders
The HarnessBinder
functions determine what modules to bind to the IOs of a ChipTop
in the TestHarness
. The HarnessBinder
interface is designed to be reused across various simulation/implementation modes, enabling decoupling of the target design from simulation and testing concerns.
For SW RTL or GL simulations, the default set of
HarnessBinders
instantiate software-simulated models of various devices, for example external memory or UART, and connect those models to the IOs of theChipTop
.For FireSim simulations, FireSim-specific
HarnessBinders
instantiateBridges
, which faciliate cycle-accurate simulation across the simulated chip’s IOs. See the FireSim documentation for more details.In the future, a Chipyard FPGA prototyping flow may use
HarnessBinders
to connectChipTop
IOs to other devices or IOs in the FPGA harness.
Like IOBinders
, HarnessBinders
are defined using macros (OverrideHarnessBinder, ComposeHarnessBinder
), and match Systems
with a given trait. However, HarnessBinders
are also passed a reference to the TestHarness
(th: HasHarnessSignalReferences
) and the list of ports generated by the corresponding IOBinder
(ports: Seq[Data]
).
For exmaple, the WithUARTAdapter
will connect the UART SW display adapter to the ports generated by the WithUARTIOCells
described earlier, if those ports are present.
class WithUARTAdapter extends HarnessBinder({
case (th: HasHarnessInstantiators, port: UARTPort, chipId: Int) => {
val div = (th.getHarnessBinderClockFreqMHz.toDouble * 1000000 / port.io.c.initBaudRate.toDouble).toInt
UARTAdapter.connect(Seq(port.io), div, false)
}
})
The IOBinder
and HarnessBinder
system is designed to enable decoupling of concerns between the target design and the simulation system.
For a given set of chip IOs, there may be not only multiple simulation platforms (“harnesses”, so-to-speak), but also multiple simulation strategies. For example, the choice of whether to connect the backing AXI4 memory port to an accurate DRAM model (SimDRAM
) or a simple simulated memory model (SimAXIMem
) is isolated in HarnessBinders
, and does not affect target RTL generation.
Similarly, for a given simulation platform and strategy, there may be multiple strategies for generating the chip IOs. This target-design configuration is isolated in the IOBinders
.