Virtual Devices - LEAP-FPGA/leap-documentation GitHub Wiki

Virtual Devices

This page explains the architecture of LEAP device support. LEAP’s support for devices

General Virtual Device Architecture

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.

Front Panel, a simple device

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.

A Programmer’s Perspective of Virtual Devices

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 Statements

`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.

Declaring a Latency-insensitive Module

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.

Instantiating the Device Interface

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

Driving the LEDS: a simple rule

Putting the “Virtual” in devices: Hybrid Front Panel

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

Using the Hybrid Front Panel

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.

Devices on FPGAs

Virtual Devices in source

The standard virtual device source is located here. Baseline LEAP programs instantiate these services.

⚠️ **GitHub.com Fallback** ⚠️