Virtual Devices - LEAP-FPGA/leap-documentation GitHub Wiki
This page explains the architecture of LEAP device support. LEAP’s support for devices
The following image shows a generalized virtual device implementation.
Virtual device implementations typically involve two layers. The top layer is the virtual device interface, a HAL-like interface based on Soft connections. The user program is written against this interface, which is portable across platforms. The virtual device layer interfaces with a device-specific driver, which can and will change across platforms. By insulating the User program from the particulars of a physical platform, LEAP maintains a high degree of portability and implementation flexibility.
In the diagram, and for the rest of this tutorial, we will consider a device that driver LEDs on a PCB. The diagram shows a stylized version of this wrapper.
To help illustrate the implementation of a LEAP device, we’ll examine the LEAP front panel device. This device manages the interface to the simple devices, like buttons and switches, typically found on FPGA PCBs.
To get started, check out the leap-examples repository:
> awb-shell checkout package leap-examples
What distinguishes a device from any other module in LEAP is that it manages a physical, wired interface.
Let’s consider a basic example of interfacing with Front Panel:
cd <workspace> awb-shell configure model config/pm/leap/examples/simple/echo_exe.apm cd <workspace>/build/default/echo_exe/pm scons
This command sequence builds the following simple LEAP program, described in <workspace>/src/leap-examples/modules/apps/examples/switch_to_led/switch_to_led_identity_function/System.bsv
:
`include "awb/provides/librl_bsv_base.bsh" `include "soft_connections.bsh" `include "front_panel.bsh" // This basic system copies the value provided by the FPGA switches // to the leds. module [CONNECTED_MODULE] mkSystem (); // Instantiate interface to the front_panel device. Connection_Send#(FRONTP_MASKED_LEDS) link_leds <- mkConnection_Send("fpga_leds"); Connection_Receive#(FRONTP_SWITCHES) link_switches <- mkConnection_Receive("fpga_switches"); // Whenever we get an update to the switch devices, update the LEDs rule switch_to_led (True); // Receive update from switch device FRONTP_SWITCHES sw = link_switches.receive(); link_switches.deq(); Bit#(4) inp = sw[3:0]; // this assumes FRONTP_SWITCHES has at least 4 bits // Update the LEDs with the new switch value link_leds.send(FRONTP_MASKED_LEDS{ state: truncate(inp), mask: ~0 }); endrule endmodule
`include "awb/provides/librl_bsv_base.bsh" `include "soft_connections.bsh" `include "front_panel.bsh"
This program requires three headers.
-
librl_bsv_base
provides simple library functions for FPGAs. -
soft_connections
give access to the latency-insensitive channel library. - @front_panel gives access to the front panel control types.
module [CONNECTED_MODULE] mkSystem ();
There are two critical pieces to declaring a LEAP latency-insensitive module. The first is the interface type. Generally, RTL modules have an interface type which consists of a set of wires. In Bluespec the interface type is sugared, and appears in parenthesis by the module name. In this case the parenthesis are empty, ()
, denoting a module with no interface. Since wired interfaces are difficult to reason about beyond the cycle level, LEAP constrains latency-insensitive modules to have an Empty wired interface. Many of the abstractions provided by the LEAP environment are built upon this fundamental abstraction.
If the interface of the module is Empty, how can the module actually communicate with the front panel device? After all, this device is not present in the user code. The answer to this question lies in the [CONNECTED_MODULE]
syntax. Although LEAP prohibits arbitrary wired interfaces, it does allow stylized latency-insensitive interfaces framed in terms of latency-insensitive channels. These channels are maintained by LEAP, and the [CONNECTED_MODULE]
syntax reflects their presence. In the subsequent section, we’ll see how to instantiate the latency-insensitive interface to the front panel device.
Communications to LEAP-provided services occur through latency-insensitive interfaces.
In this example, we instantiate latency-insensitive channels directly. However some LEAP service interfaces, for example STDIO may provide a thin wrapper around the underlying LI interface. As an example, the following code provides a similar interface paradigm for the front-panel:
`include "awb/provides/librl_bsv_base.bsh" `include "soft_connections.bsh" `include "front_panel.bsh" module [CONNECTED_MODULE] mkFrontPanelInterface (FRONT_PANEL_LI_IFC); // Instantiate interface to the front_panel device. Connection_Send#(FRONTP_MASKED_LEDS) link_leds <- mkConnection_Send("fpga_leds"); Connection_Receive#(FRONTP_SWITCHES) link_switches <- mkConnection_Receive("fpga_switches"); method putLEDs(FRONTP_MASKED_LEDS leds); link_leds.send(leds); endmethod method ActionValue#(FRONTP_SWITCHES) getSwitches(); link_switches.deq(); return link_switches.receive(); endmethod endmodule
The chief advantage of decoupling device through latency-insensitive channels is that multiple device implementations are possible. For example, a memory device could be backed by either SRAM or DRAM. Alternatively, if no physical device is available, the device may be virtualized, or emulated by either some FPGA-based logic or by a software implementation.
The following listing shows the implementation of a virtual device providing PCB-like functionality.
module [CONNECTED_MODULE] mkFrontPanel#(LowLevelPlatformInterface llpi) (Empty); // Connections to user code. Note that the connections are optional. Connection_Receive#(FRONTP_MASKED_LEDS) linkLEDs <- mkConnectionRecvOptional("fpga_leds"); Connection_Send#(FRONTP_SWITCHES) linkSwitches <- mkConnectionSendOptional("fpga_switches"); Connection_Send#(FRONTP_BUTTON_INFO) linkButtons <- mkConnectionSendOptional("fpga_buttons"); // state Reg#(FRONTP_INPUT_STATE) inputCache <- mkReg(0); Reg#(FRONTP_LEDS) ledState <- mkReg(0); // RRR stubs for communication with software side ServerStub_FRONT_PANEL server_stub <- mkServerStub_FRONT_PANEL(); ClientStub_FRONT_PANEL client_stub <- mkClientStub_FRONT_PANEL(); // read incoming updates for switch/button state rule probeUpdates (True); UINT32 data <- server_stub.acceptRequest_UpdateSwitchesButtons(); inputCache <= unpack(data); endrule // return switch state from input cache rule readSwitches; linkSwitches.send(unpack(inputCache[3:0])); endrule // return switch state from input cache rule readButtons; linkButtons.send(unpack(inputCache[8:4])); endrule // write to LEDs rule writeLEDs; let incoming_state = linkLEDs.receive(); linkLEDs.deq(); FRONTP_LEDS new_state = (ledState & ~incoming_state.mask) | (incoming_state.state & incoming_state.mask); if (new_state != ledState) begin ledState <= new_state; client_stub.makeRequest_UpdateLEDs(zeroExtend(pack(new_state))); end endrule endmodule
Now that we understand the Front Panel implementation for simulation, let’s see how to run it. First, we must configure a benchmark:
> cd <workspace> awb-shell setup benchmark config/bm/leap/demos.cfx/benchmarks/null.cfg --model config/pm/leap/examples/simple/echo_exe.apm cd <workspace>/build/default/echo_exe/bm/null
As a test, execute with --help
which lists the command line option available for the particular executable:
[keflemin-vs-fpga-3 null(71)] ./run --help Arguments: [--gdb] Invokes the software side in gdb [--noprogram] Skips the FPGA load and reservation steps [--noreserve] Skips the FPGA reservation steps [--onlycompare] Only compare output files (without running) [--nocompare] Skip comparison of output files [--force-load] Load a bitfile to the FPGA even if it has errors [--tr=[</regex/[=012]]] Set trace level by regular expression. Can be given multiple times. If not specified, the trace level will default to 1 and the regex to .* [--listparam] List dynamic parameters [--param NAME=VALUE] Set a dynamic parameter [--funcp="<args>"] Arguments for the functional platform [--workload="<args>"] Workload name (affects .stats file name) [--modeldir=<dir>] Model directory [--DEVICE_DICTIONARY] No help provided [--bluesim="<args>"] Arguments to Bluesim [--showfp[=gui|stdout|none]] [--stdio-cond-printf-mask=<n>] Enable FPGA-side masked STDIO printf
In this case, we need to run with the hybrid Front Panel GUI enabled:
./run --showfp=gui
Upon execution a QT GUI should appear:
Clicking start gives:
Finally, we can toggle the GUI switches, which are “echoed” through to the LEDs:
Toggling the switches sends a message from the QT GUI, through the host executable to the RTL simulator. Within the RTL, the message flows through the Front Panel device, hybrid-fp.bsv
, and finally arrives at the user code in System.bsv
. The single user rule fires, and sends a message in reverse through the stack, eventually arriving at the GUI and toggling the LED.
Clicking Exit
terminates the GUI and the echo program.
The standard virtual device source is located here. Baseline LEAP programs instantiate these services.