Merge pull request #463 from firesim/bridge-walkthrough
[docs] Add a UARTBridge Walkthrough
This commit is contained in:
commit
540ea3b0ef
|
@ -0,0 +1,160 @@
|
|||
.. _bridge-walkthrough:
|
||||
|
||||
Bridge Walkthrough
|
||||
==================
|
||||
In this section, we'll walkthrough a simple Target-to-Host bridge, the UARTBridge, provided with FireSim
|
||||
to demonstrate how to integrate your own. The UARTBridge uses host-MMIO to model
|
||||
a UART device.
|
||||
|
||||
Reading the Bridges section is a prerequisite to reading these sections.
|
||||
|
||||
UART Bridge (Host-MMIO)
|
||||
-----------------------
|
||||
|
||||
Source code for the UART Bridge lives in the following directories:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
sim/
|
||||
├-firesim-lib/src/main/
|
||||
│ ├-scala/bridges/UARTBridge.scala # Target-Side Bridge and BridgeModule Definitions
|
||||
│ ├-cc/brides/uart.cc # Bridge Driver source
|
||||
│ └-cc/brides/uart.h # Bridge Driver header
|
||||
├-src/main/cc/firesim/firesim_top.cc # Driver instantiation in the main simulation driver
|
||||
└-src/main/makefrag/firesim/Makefrag # Build system modifications to compile Bridge Driver code
|
||||
|
||||
Target Side
|
||||
+++++++++++
|
||||
The first order of business when designing a new bridge is to implement its
|
||||
target side. In the case of UART we've defined a Chisel BlackBox [#]_ extending Bridge.
|
||||
We'll instantiate this BlackBox and connect it to UART IO in the
|
||||
top-level of our chip. We first define a class that captures the target-side interface of the Bridge:
|
||||
|
||||
.. literalinclude:: ../../sim/firesim-lib/src/main/scala/bridges/UARTBridge.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: UART Bridge Target-Side Interface
|
||||
:end-before: DOC include end: UART Bridge Target-Side Interface
|
||||
|
||||
|
||||
.. [#] You can also extend a non-BlackBox Chisel Module, but any Chisel source
|
||||
contained within will be removed by Golden Gate. You may wish to do this to
|
||||
enclose a synthesizable model of the Bridge for other simulation backends, or
|
||||
simply to wrap a larger chunk RTL you wish to model in the host-side of the
|
||||
Bridge.
|
||||
|
||||
Here, we define a case class that carries additional metadata to the host-side
|
||||
BridgeModule. For UART, this is simply the clock-division required to produce the
|
||||
baudrate:
|
||||
|
||||
.. literalinclude:: ../../sim/firesim-lib/src/main/scala/bridges/UARTBridge.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: UART Bridge Constructor Arg
|
||||
:end-before: DOC include end: UART Bridge Constructor Arg
|
||||
|
||||
Finally, we define the actual target-side module (specifically, a BlackBox):
|
||||
|
||||
.. literalinclude:: ../../sim/firesim-lib/src/main/scala/bridges/UARTBridge.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: UART Bridge Target-Side Module
|
||||
:end-before: DOC include end: UART Bridge Target-Side Module
|
||||
|
||||
To make it easier to instantiate our target-side module, we've also defined an
|
||||
optional companion object:
|
||||
|
||||
.. literalinclude:: ../../sim/firesim-lib/src/main/scala/bridges/UARTBridge.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: UART Bridge Companion Object
|
||||
:end-before: DOC include end: UART Bridge Companion Object
|
||||
|
||||
That completes the target-side definition.
|
||||
|
||||
Host-Side BridgeModule
|
||||
++++++++++++++++++++++
|
||||
|
||||
The remainder of the file is dedicated to the host-side BridgeModule
|
||||
definition. Here we have to process tokens generated by the target, and
|
||||
expose a memory-mapped interface to the bridge driver.
|
||||
|
||||
Inspecting the top of the class:
|
||||
|
||||
.. literalinclude:: ../../sim/firesim-lib/src/main/scala/bridges/UARTBridge.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: UART Bridge Header
|
||||
:end-before: DOC include end: UART Bridge Header
|
||||
|
||||
|
||||
Most of what follows is responsible for modeling the timing of the UART.
|
||||
As a bridge designer, you're free to take as many host-cycles as you need to
|
||||
process tokens. In simpler models, like this one, it's often easiest to write
|
||||
logic that operates in a single cycle but gate state-updates using a
|
||||
"fire" signal that is asserted when the required tokens are available.
|
||||
|
||||
Now, we'll skip to the end to see how to add registers to the simulator's memory map
|
||||
that can be accessed using MMIO from bridge driver.
|
||||
|
||||
.. literalinclude:: ../../sim/firesim-lib/src/main/scala/bridges/UARTBridge.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: UART Bridge Footer
|
||||
:end-before: DOC include end: UART Bridge Footer
|
||||
|
||||
|
||||
Host-Side Driver
|
||||
++++++++++++++++
|
||||
|
||||
To complete our host-side definition, we need to define a CPU-hosted bridge driver.
|
||||
Bridge Drivers extend the ``bridge_driver_t`` interface, which declares 5 virtual methods
|
||||
a concrete bridge driver must implement:
|
||||
|
||||
|
||||
.. literalinclude:: ../../sim/midas/src/main/cc/bridges/bridge_driver.h
|
||||
:language: c++
|
||||
:start-after: DOC include start: Bridge Driver Interface
|
||||
:end-before: DOC include end: Bridge Driver Interface
|
||||
|
||||
The declaration of the Uart bridge
|
||||
driver lives at ``sim/firesim-lib/src/main/cc/bridges/uart.h``. It is inlined
|
||||
below:
|
||||
|
||||
.. include:: ../../sim/firesim-lib/src/main/cc/bridges/uart.h
|
||||
:code: c++
|
||||
|
||||
The bulk of the driver's work is done in its ``tick()`` method. Here, the driver
|
||||
polls the BridgeModule and then does some work. Note: the name, ``tick`` is vestigial: one
|
||||
invocation of tick() may do work corresponding to an arbitrary number of
|
||||
target cycles. It's critical that tick be non-blocking, as waiting for work
|
||||
from the BridgeModule may deadlock the simulator.
|
||||
|
||||
Registering the Driver
|
||||
++++++++++++++++++++++
|
||||
|
||||
With the Bridge Driver implemented, we now have to register it in the main simulator
|
||||
simulator class defined in ``sim/src/main/cc/firesim/firesim_top.cc``. Here, we
|
||||
rely on the C preprocessor macros to instantiate the bridge driver only when
|
||||
the corresponding BridgeModule is present:
|
||||
|
||||
.. literalinclude:: ../../sim/src/main/cc/firesim/firesim_top.cc
|
||||
:language: c++
|
||||
:start-after: DOC include start: UART Bridge Driver Registration
|
||||
:end-before: DOC include end: UART Bridge Driver Registration
|
||||
|
||||
Build-System Modifications
|
||||
++++++++++++++++++++++++++
|
||||
|
||||
The final consideration in adding your bridge concerns the build system. You
|
||||
should be able to host the Scala sources for your bridge with rest of your
|
||||
target RTL: SBT will make sure those classes are available on the runtime
|
||||
classpath. If you're hosting your bridge driver sources outside of the existing
|
||||
directories, you'll need to modify your target-project Makefrag to include them. The default
|
||||
Chipyard/Rocket Chip-based one lives here:
|
||||
``sim/src/main/makefrag/firesim/Makefrag``
|
||||
|
||||
Here the main order of business is to add header and source files to
|
||||
``DRIVER_H`` and ``DRIVER_CC`` respectively, by modifying the lines below:
|
||||
|
||||
.. literalinclude:: ../../sim/src/main/makefrag/firesim/Makefrag
|
||||
:language: make
|
||||
:start-after: DOC include start: Bridge Build System Changes
|
||||
:end-before: DOC include end: Bridge Build System Changes
|
||||
|
||||
That's it! At this point you should be able to both test your bridge in software
|
||||
simulation using MIDAS-level simulation, or deploy it to an FPGA.
|
|
@ -31,7 +31,8 @@ Bridges enable:
|
|||
|
||||
The use of Bridges in a FireSim simulation has many analogs to doing
|
||||
mixed-language (Verilog-C++) simulation of the same system in software. Where
|
||||
possible, we'll draw analogies.
|
||||
possible, we'll draw analogies. After reading this page we encourage you to read the
|
||||
:ref:`bridge-walkthrough`, which concretely explains the implementation of the UARTBridge.
|
||||
|
||||
|
||||
Terminology
|
||||
|
@ -118,9 +119,9 @@ The host side of a bridge has two components:
|
|||
#. An optional, CPU-hosted, bridge driver (``bridge_driver_t``).
|
||||
|
||||
In general, bridges have both: in FASED memory timing
|
||||
models, the driver configures timing parameters at the start of
|
||||
simulation, and periodically reads instrumentation during execution. In the
|
||||
Block Device model, the driver periodically polls queues in the module checking for
|
||||
models, the BridgeModule contains a timing model that exposes timing
|
||||
parameters as memory-mapped registers that the driver configures at the start
|
||||
of simulation. In the Block Device model, the driver periodically polls queues in the bridge module checking for
|
||||
new functional requests to be served. In the NIC model, the driver moves
|
||||
tokens in bulk between the software switch model and the bridge module, which
|
||||
simply queues up tokens as they arrive.
|
||||
|
|
|
@ -38,6 +38,7 @@ New to FireSim? Jump to the :ref:`firesim-basics` page for more info.
|
|||
Golden-Gate/Overview
|
||||
Golden-Gate/LI-BDN
|
||||
Golden-Gate/Bridges
|
||||
Golden-Gate/Bridge-Walkthrough
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
//See LICENSE for license details
|
||||
|
||||
// Note: struct_guards just as in the headers
|
||||
#ifdef UARTBRIDGEMODULE_struct_guard
|
||||
|
||||
#include "uart.h"
|
||||
|
|
|
@ -5,18 +5,31 @@
|
|||
#include "serial.h"
|
||||
#include <signal.h>
|
||||
|
||||
// The definition of the primary constructor argument for a bridge is generated
|
||||
// by Golden Gate at compile time _iff_ the bridge is instantiated in the
|
||||
// target. As a result, all bridge driver definitions conditionally remove
|
||||
// their sources if the constructor class has been defined (the
|
||||
// <cname>_struct_guard macros are generated along side the class definition.)
|
||||
//
|
||||
// The name of this class and its guards are always BridgeModule class name, in
|
||||
// all-caps, suffixed with "_struct" and "_struct_guard" respectively.
|
||||
|
||||
#ifdef UARTBRIDGEMODULE_struct_guard
|
||||
class uart_t: public bridge_driver_t
|
||||
{
|
||||
public:
|
||||
uart_t(simif_t* sim, UARTBRIDGEMODULE_struct * mmio_addrs, int uartno);
|
||||
~uart_t();
|
||||
void send();
|
||||
void recv();
|
||||
virtual void init() {};
|
||||
virtual void tick();
|
||||
// Our UART bridge's initialzation and teardown procedures don't
|
||||
// require interaction with the FPGA (i.e., MMIO), and so we don't need
|
||||
// to define init and finish methods (we can do everything in the
|
||||
// ctor/dtor)
|
||||
virtual void init() {};
|
||||
virtual void finish() {};
|
||||
// Our UART bridge never calls for the simulation to terminate
|
||||
virtual bool terminate() { return false; }
|
||||
// ... and thus, never returns a non-zero exit code
|
||||
virtual int exit_code() { return 0; }
|
||||
|
||||
private:
|
||||
|
@ -25,6 +38,8 @@ class uart_t: public bridge_driver_t
|
|||
int inputfd;
|
||||
int outputfd;
|
||||
int loggingfd;
|
||||
void send();
|
||||
void recv();
|
||||
};
|
||||
#endif // UARTBRIDGEMODULE_struct_guard
|
||||
|
||||
|
|
|
@ -10,21 +10,53 @@ import freechips.rocketchip.config.Parameters
|
|||
import freechips.rocketchip.subsystem.PeripheryBusKey
|
||||
import sifive.blocks.devices.uart.{UARTPortIO, PeripheryUARTKey}
|
||||
|
||||
// We need to box this Int in a case class for the constructor invocation to
|
||||
// work correctly.
|
||||
case class UARTKey(div: Int)
|
||||
//Note: This file is heavily commented as it serves as a bridge walkthrough
|
||||
//example in the FireSim docs
|
||||
|
||||
// DOC include start: UART Bridge Target-Side Interface
|
||||
class UARTBridgeTargetIO extends Bundle {
|
||||
val uart = Flipped(new UARTPortIO)
|
||||
// Note this reset is optional and used only to reset target-state modelled
|
||||
// in the bridge This reset just like any other Bool included in your target
|
||||
// interface, simply appears as another Bool in the input token.
|
||||
val reset = Input(Bool())
|
||||
}
|
||||
// DOC include end: UART Bridge Target-Side Interface
|
||||
|
||||
// DOC include start: UART Bridge Constructor Arg
|
||||
// Out bridge module constructor argument. This captures all of the extra
|
||||
// metadata we'd like to pass to the host-side BridgeModule. Note, we need to
|
||||
// use a single case-class to do so, even if it is simply to wrap a primitive
|
||||
// type, as is the case for UART (int)
|
||||
case class UARTKey(div: Int)
|
||||
// DOC include end: UART Bridge Constructor Arg
|
||||
|
||||
// DOC include start: UART Bridge Target-Side Module
|
||||
class UARTBridge(implicit p: Parameters) extends BlackBox
|
||||
with Bridge[HostPortIO[UARTBridgeTargetIO], UARTBridgeModule] {
|
||||
// Since we're extending BlackBox this is the port will connect to in our target's RTL
|
||||
val io = IO(new UARTBridgeTargetIO)
|
||||
// Implement the bridgeIO member of Bridge using HostPort. This indicates that
|
||||
// we want to divide io, into a bidirectional token stream with the input
|
||||
// token corresponding to all of the inputs of this BlackBox, and the output token consisting of
|
||||
// all of the outputs from the BlackBox
|
||||
val bridgeIO = HostPort(io)
|
||||
|
||||
// Do some intermediate work to compute our host-side BridgeModule's constructor argument
|
||||
val frequency = p(PeripheryBusKey).frequency
|
||||
val baudrate = 3686400L
|
||||
val div = (p(PeripheryBusKey).frequency / baudrate).toInt
|
||||
|
||||
// And then implement the constructorArg member
|
||||
val constructorArg = Some(UARTKey(div))
|
||||
|
||||
// Finally, and this is critical, emit the Bridge Annotations -- without
|
||||
// this, this BlackBox would appear like any other BlackBox to golden-gate
|
||||
generateAnnotations()
|
||||
}
|
||||
// DOC include end: UART Bridge Target-Side Module
|
||||
|
||||
// DOC include start: UART Bridge Companion Object
|
||||
object UARTBridge {
|
||||
def apply(uart: UARTPortIO)(implicit p: Parameters): UARTBridge = {
|
||||
val ep = Module(new UARTBridge)
|
||||
|
@ -32,30 +64,46 @@ object UARTBridge {
|
|||
ep
|
||||
}
|
||||
}
|
||||
// DOC include end: UART Bridge Companion Object
|
||||
|
||||
class UARTBridgeTargetIO extends Bundle {
|
||||
val uart = Flipped(new UARTPortIO)
|
||||
val reset = Input(Bool())
|
||||
}
|
||||
|
||||
|
||||
// DOC include start: UART Bridge Header
|
||||
// Our UARTBridgeModule definition, note:
|
||||
// 1) it takes one parameter, key, of type UARTKey --> the same case class we captured from the target-side
|
||||
// 2) It accepts one implicit parameter of type Parameters
|
||||
// 3) It extends BridgeModule passing the type of the HostInterface
|
||||
//
|
||||
// While the scala-type system will check if you parameterized BridgeModule
|
||||
// correctly, the types of the constructor arugument (in this case UARTKey),
|
||||
// don't match, you'll only find out later when Golden Gate attempts to generate your module.
|
||||
class UARTBridgeModule(key: UARTKey)(implicit p: Parameters) extends BridgeModule[HostPortIO[UARTBridgeTargetIO]]()(p) {
|
||||
val div = key.div
|
||||
// This creates the interfaces for all of the host-side transport
|
||||
// AXI4-lite for the simulation control bus, =
|
||||
// AXI4 for DMA
|
||||
val io = IO(new WidgetIO())
|
||||
|
||||
// This creates the host-side interface of your TargetIO
|
||||
val hPort = IO(HostPort(new UARTBridgeTargetIO))
|
||||
|
||||
// Generate some FIFOs to capture tokens...
|
||||
val txfifo = Module(new Queue(UInt(8.W), 128))
|
||||
val rxfifo = Module(new Queue(UInt(8.W), 128))
|
||||
|
||||
val target = hPort.hBits.uart
|
||||
val fire = hPort.toHost.hValid && hPort.fromHost.hReady && txfifo.io.enq.ready
|
||||
// In general, your BridgeModule will not need to do work every host-cycle. In simple Bridges,
|
||||
// we can do everything in a single host-cycle -- fire captures all of the
|
||||
// conditions under which we can consume and input token and produce a new
|
||||
// output token
|
||||
val fire = hPort.toHost.hValid && // We have a valid input token: toHost ~= leaving the transformed RTL
|
||||
hPort.fromHost.hReady && // We have space to enqueue a new output token
|
||||
txfifo.io.enq.ready // We have space to capture new TX data
|
||||
val targetReset = fire & hPort.hBits.reset
|
||||
rxfifo.reset := reset.toBool || targetReset
|
||||
txfifo.reset := reset.toBool || targetReset
|
||||
|
||||
hPort.toHost.hReady := fire
|
||||
hPort.fromHost.hValid := fire
|
||||
|
||||
// DOC include end: UART Bridge Header
|
||||
val sTxIdle :: sTxWait :: sTxData :: sTxBreak :: Nil = Enum(4)
|
||||
val txState = RegInit(sTxIdle)
|
||||
val txData = Reg(UInt(8.W))
|
||||
|
@ -127,13 +175,24 @@ class UARTBridgeModule(key: UARTKey)(implicit p: Parameters) extends BridgeModul
|
|||
}
|
||||
rxfifo.io.deq.ready := (rxState === sRxData) && rxDataWrap && rxBaudWrap && fire
|
||||
|
||||
// DOC include start: UART Bridge Footer
|
||||
// Exposed the head of the queue and the valid bit as a read-only registers
|
||||
// with name "out_bits" and out_valid respectively
|
||||
genROReg(txfifo.io.deq.bits, "out_bits")
|
||||
genROReg(txfifo.io.deq.valid, "out_valid")
|
||||
|
||||
// Generate a writeable register, "out_ready", that when written to dequeues
|
||||
// a single element in the tx_fifo. Pulsify derives the register back to false
|
||||
// after pulseLength cycles to prevent multiple dequeues
|
||||
Pulsify(genWORegInit(txfifo.io.deq.ready, "out_ready", false.B), pulseLength = 1)
|
||||
|
||||
// Generate regisers for the rx-side of the UART; this is eseentially the reverse of the above
|
||||
genWOReg(rxfifo.io.enq.bits, "in_bits")
|
||||
Pulsify(genWORegInit(rxfifo.io.enq.valid, "in_valid", false.B), pulseLength = 1)
|
||||
genROReg(rxfifo.io.enq.ready, "in_ready")
|
||||
|
||||
// This method invocation is required to wire up all of the MMIO registers to
|
||||
// the simulation control bus (AXI4-lite)
|
||||
genCRFile()
|
||||
// DOC include end: UART Bridge Footer
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "simif.h"
|
||||
|
||||
// DOC include start: Bridge Driver Interface
|
||||
// Bridge Drivers are the CPU-hosted component of a Target-to-Host Bridge. A
|
||||
// Bridge Driver interacts with their accompanying FPGA-hosted BridgeModule
|
||||
// using MMIO (via read() and write() methods) or CPU-mastered DMA (via pull()
|
||||
|
@ -31,6 +32,7 @@ public:
|
|||
// the FPGA before destructors are called at the end of simulation. Useful
|
||||
// for doing end-of-simulation clean up that requires calling {read,write,push,pull}.
|
||||
virtual void finish() = 0;
|
||||
// DOC include end: Bridge Driver Interface
|
||||
|
||||
protected:
|
||||
void write(size_t addr, data_t data) {
|
||||
|
|
|
@ -36,15 +36,25 @@ firesim_top_t::firesim_top_t(int argc, char** argv)
|
|||
}
|
||||
|
||||
|
||||
#ifdef UARTBRIDGEMODULE_struct_guard
|
||||
// DOC include start: UART Bridge Driver Registration
|
||||
// Here we instantiate our driver once for each bridge in the target
|
||||
// Golden Gate emits a <BridgeModuleClassName>_<id>_PRESENT macro for each instance
|
||||
// which you may use to conditionally instantiate your driver
|
||||
#ifdef UARTBRIDGEMODULE_0_PRESENT
|
||||
// Create an instance of the constructor argument (this has all of
|
||||
// addresses of the BridgeModule's memory mapped registers)
|
||||
UARTBRIDGEMODULE_0_substruct_create;
|
||||
// Instantiate the driver; register it in the main simulation class
|
||||
add_bridge_driver(new uart_t(this, UARTBRIDGEMODULE_0_substruct, 0));
|
||||
#endif
|
||||
|
||||
// Repeat the code above with modified indices as many times as necessary
|
||||
// to support the maximum expected number of bridge instances
|
||||
#ifdef UARTBRIDGEMODULE_1_PRESENT
|
||||
UARTBRIDGEMODULE_1_substruct_create;
|
||||
add_bridge_driver(new uart_t(this, UARTBRIDGEMODULE_1_substruct, 1));
|
||||
#endif
|
||||
// DOC include end: UART Bridge Driver Registration
|
||||
#ifdef UARTBRIDGEMODULE_2_PRESENT
|
||||
UARTBRIDGEMODULE_2_substruct_create;
|
||||
add_bridge_driver(new uart_t(this, UARTBRIDGEMODULE_2_substruct, 2));
|
||||
|
@ -69,7 +79,6 @@ firesim_top_t::firesim_top_t(int argc, char** argv)
|
|||
UARTBRIDGEMODULE_7_substruct_create;
|
||||
add_bridge_driver(new uart_t(this, UARTBRIDGEMODULE_7_substruct, 7));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
std::vector<uint64_t> host_mem_offsets;
|
||||
uint64_t host_mem_offset = -0x80000000LL;
|
||||
|
|
|
@ -47,6 +47,7 @@ $(FIRRTL_FILE) $(ANNO_FILE): $(SCALA_SOURCES) $(FIRRTL_JAR)
|
|||
cd $(base_dir) && \
|
||||
$(SBT) "project $(firesim_sbt_project)" "runMain firesim.firesim.FireSimGenerator $(if $(STROBER),strober,midas) $(common_chisel_args)"
|
||||
|
||||
# DOC include start: Bridge Build System Changes
|
||||
##########################
|
||||
# Driver Sources & Flags #
|
||||
##########################
|
||||
|
@ -62,6 +63,7 @@ DRIVER_CC = $(wildcard $(addprefix $(driver_dir)/, $(addsuffix .cc, firesim/*)))
|
|||
|
||||
TARGET_CXX_FLAGS := -g -I$(firesim_lib_dir) -I$(driver_dir)/firesim -I$(RISCV)/include
|
||||
TARGET_LD_FLAGS :=
|
||||
# DOC include end: Bridge Build System Changes
|
||||
|
||||
################################################################
|
||||
# SW RTL Simulation Args -- for MIDAS- & FPGA-level Simulation #
|
||||
|
|
Loading…
Reference in New Issue