Quickstart

A minimal Verilog testbench instantiates the BFM, resets an interface, and issues a transfer. For a runnable project, see Run the PerfTest Example.

// Drop the BFM in where the synthesis okHost would go.
okHost #(.SIM_BWMODE(3)) okHost_i ( /* aclk, the three aresetn, your AXI interfaces */ );

integer    error_code;
reg [63:0] transfer_byte_count, transaction_count;
reg [7:0]  read_byte;

initial begin
    okHost_i.axi_full_reset(error_code);

    // Write 256 bytes: fill the write buffer, then issue the write.
    okHost_i.set_write_buffer(0, 8'hA5);
    okHost_i.axi_full_write(64'h0000, 256, 16, 1.0,
        error_code, transfer_byte_count, transaction_count);

    // Read them back, then take one byte out of the read buffer.
    okHost_i.axi_full_read(64'h0000, 256, 16, 1.0,
        error_code, transfer_byte_count);
    okHost_i.get_read_buffer(0, read_byte);

    if (error_code != 0) $display("okHost error %0d", error_code);
endCode language: Verilog (verilog)

The concept is on the parent’s Introduction to Host Simulation, how closely it matches hardware on Fidelity and Limitations, and the shared behaviors on The BFM Behavioral Contract. The sections below cover only what is specific to Verilog. To set up your own design, see Vivado Project Setup.

Using the BFM

One Instance, Procedural Tasks

A testbench holds a single okHost instance and calls its tasks directly. The example defines a macro, `define OKHOST tb_perftest.okHost_i, and calls tasks through it, for example `OKHOST.axi_full_reset(error_code), so it does not repeat the full instance path each time. Calling by the full instance path works too.

Result Codes

Every task returns its result as an integer output parameter, not a return value; check it against the error codes. Declare the codes you compare against in your own testbench: the example collects the ones it uses as localparam integer OK_* in tb_common.vh, for example OK_NO_ERROR = 0.

The Data Buffer Pattern

Data does not pass through the task arguments; it moves through the BFM’s write and read buffers. Fill the write buffer before a write, and read the read buffer after a read. Two granularities are available. The byte tasks move one byte at a time:

okHost_i.set_write_buffer(offset, byte_value);   // before a write
okHost_i.get_read_buffer(offset, byte_value);    // after a readCode language: Verilog (verilog)

The register tasks move one 32-bit register at a time, which suits AXI-Lite bulk transfers:

okHost_i.set_write_buffer_register(index, data); // 32-bit, before a bulk write
okHost_i.get_read_buffer_register(index, data);  // 32-bit, after a bulk readCode language: Verilog (verilog)

Timeouts

The hardware FrontPanel API takes a timeout in whole milliseconds. The BFM’s timeout argument is a real and accepts sub-millisecond values to cut the simulated wait, for example 0.005 for 5 µs.

Stall Injection

The BFM’s stall injection is configured per channel with these tasks:

ChannelPercent taskPattern task
AXI-Full write (WVALID)set_axi_full_write_stall_percentset_axi_full_write_stall_pattern
AXI-Full read (RREADY)set_axi_full_read_stall_percentset_axi_full_read_stall_pattern
AXI-Stream write (TVALID)set_axi_stream_write_stall_percentset_axi_stream_write_stall_pattern
AXI-Stream read (TREADY)set_axi_stream_read_stall_percentset_axi_stream_read_stall_pattern

AXI-Lite has no stall injection.

Board Ready and DNA Outputs

board_ready, dna, and dna_valid carry the same meaning as on hardware, under Board Ready and DNA Outputs, but the model drives fixed values rather than reproducing board bring-up or reading a real device. board_ready is high from the start of simulation, so logic you gate on it is released immediately, with no bring-up delay to wait on. dna presents a fixed placeholder value, with dna_valid high, not a real or per-device Device DNA. Leave any of them unconnected if you do not use it.

Operations by Interface

These tables map the hardware C++ SDK API to its Verilog equivalent, so you can match a method you already use on hardware to the task that does the same in simulation. Verilog is not object-oriented and its tasks cannot return values, so each C++ method is flattened into a plain task on the single okHost instance, and whatever the method returned comes back through output task arguments you read after the call. The okTOperationStatistics result, for example, arrives as the two arguments transfer_byte_count and transaction_count.

AXI-Lite

AXI-Lite is fixed at 32-bit registers with 64-bit addresses, and has no width getter. A single transfer takes its value directly as a task argument; a bulk transfer moves a run of registers through the buffer, with count giving the number of registers.

Verilog taskC++ methodNotes
axi_lite_reset(error_code)Reset
axi_lite_write(addr, value, timeout, error_code)Writeone register; value passed as an argument
axi_lite_read(addr, timeout, value, error_code)Readone register; value returned as an argument
axi_lite_write_bulk(addr, count, timeout, error_code, byte_count, txn_count)WriteBulkcount registers; fill the buffer first
axi_lite_read_bulk(addr, count, timeout, error_code, byte_count)ReadBulkcount registers; read the buffer after

AXI-Full

AXI-Full takes a burst length of 1 to 256. A write returns both a transfer byte count and a transaction count; a read returns a transfer byte count.

Verilog taskC++ methodNotes
axi_full_reset(error_code)Reset
axi_full_get_datapath_width_byte_count(width)GetDatapathWidthByteCountdata-path width in bytes
axi_full_write(addr, len, burst, timeout, error_code, byte_count, txn_count)Writefill the buffer first
axi_full_read(addr, size, burst, timeout, error_code, byte_count)Readread the buffer after

AXI-Stream

The AXI-Stream write and read directions have separate data-path widths, so each has its own width getter.

Verilog taskC++ methodNotes
axi_stream_reset(error_code)Reset
axi_stream_get_write_datapath_width_byte_count(width)GetWriteDatapathWidthByteCountm_axis width (host to FPGA)
axi_stream_get_read_datapath_width_byte_count(width)GetReadDatapathWidthByteCounts_axis width (FPGA to host)
axi_stream_write(len, timeout, error_code, byte_count)Writefill the buffer first; host to FPGA
axi_stream_read(size, timeout, error_code, byte_count)Readread the buffer after; FPGA to host

Run the PerfTest Example

The PerfTest example design ships in the FrontPanel SDK, under Examples\Simulation\USB3.2. Its README.md holds the authoritative instructions; the steps below summarize the flow. The design, its data patterns, and the behaviors the suite verifies are on the parent’s PerfTest Example and the PerfTest example page. You need Vivado with the built-in XSim simulator and your FrontPanel Platform install, which supplies both the FrontPanel SDK and the simulation okHost.vp.

Step 1: Extract the FrontPanel SDK. In the FrontPanel Platform application, click Download Developer Kit in the lower left and pick a destination folder. The SDK extracts into a single named directory, FrontPanel-DevKit-<version>, at that location. Alongside the examples it carries the FrontPanel API headers, shared libraries, and the C/C++, Python, and Java bindings, and the FrontPanel-over-IP server, password tool, and example certificates.

Step 2: Copy the example to a writable location. Copy the entire Examples\Simulation\USB3.2 directory to a directory you own.

Step 3: Select the key-year folder that covers your Vivado release. The simulation okHost.vp is encrypted and organized by key-year, under <FrontPanel Platform>\Simulation\USB3.2\Vivado-<year>. Choose the Vivado-<year> folder whose window covers the Vivado release you simulate with. The layout and that rule are on Delivery and Files and Vivado Version Compatibility.

Step 4: Add the simulation okHost. Copy okHost.vp from that folder into your working copy. On Windows the default FrontPanel Platform install is C:\Program Files\Opal Kelly\FrontPanel-Platform.

Step 5: Point Vivado at the working copy. Open Vivado, and in the Tcl Console change to your copy:

cd <path-to-working-copy>Code language: HTML, XML (xml)

Step 6: Optionally select a bandwidth mode. The default is mode 3, and the example accepts modes 1 through 7. Each mode sets the AXI data widths and clock; the modes are on USB Transport.

set mode 2Code language: JavaScript (javascript)

Step 7: Build and run. Source the run script:

source run.tclCode language: CSS (css)

It creates the sim_prj project, compiles the testbench and DUT, launches XSim, runs the suite, and opens the waveform with the AXI interfaces grouped under a FrontPanel okHost wave configuration. Because the model is encrypted, its ports and the AXI interfaces are visible in the waveform but its internals are not.

The Tcl Console ends with the summary:

[...] Passed: 184
[...] Failed: 0
[...] ALL TESTS PASSED!Code language: Tcl (tcl)

The passed count is for the default mode 3; other modes run a different number of checks. To retest another mode in the open project, set a new mode and re-source run.tcl; the project is reused.

Vivado Project Setup

Because the simulation okHost is footprint-compatible with the synthesis okHost, your design connects to it the same way whether you build a bit file or run a simulation. You set the project up once and switch between the two.

Add both okHost files to the project and scope each to one flow, marking the synthesis okHost used in synthesis and the simulation okHost used in simulation. Vivado then compiles the synthesis core when you generate a bit file and elaborates the simulation core when you run a behavioral simulation. If you build with the FrontPanel Vivado IP core, this split is already handled, so you instantiate the IP and it supplies the right okHost for each flow. Connect okHost as shown on System Design, and leave the transport pins as width-1 stubs.

See Also