XSim's New AXI Analyzer

Motivation

If you've developed AXI machinery in Vivado, you're likely familiar with this:


This screenshot from Xilinx's built-in simulator (xsim) shows two AXI4-Lite writes followed by a single read. It's functional but, for designs of any complexity, it's tough to keep track of both the forest (the high-level behaviour you're presumably debugging) and the trees (the nitty-gritty associated with each AXI transaction).

Sometimes I use markers and signal colors to make navigation easier, but these are throwaway techniques that aren't useful for more than a single simulator session at a time.

I often wish for some easily available visual cues that support moving around the simulation without losing focus on the problem I'm trying to solve.

AXI Analyzer in Block Designs

You may be surprised to hear that Vivado 2019.1 added something better. From the 2019.1 release notes:

Vivado Simulator

  • "Mark Simulation" feature in block diagram to add AXI interfaces in waveform viewer directly

That's a little oblique. Since a picture speaks a thousand words:


That thing at the bottom is a "protocol analyzer", and if you're doing your development in a Block Design, you may have already stumbled across it. Lucky you.

Using the AXI Analyzer in RTL-Only Designs

For RTL designs, unfortunately, it's not so easy. It does not appear that the Vivado machinery that infers AXI buses from port names and a few judicious X_INTERFACE-style attributes (VHDL) or comments (Verilog) is hooked up here.

Unofficially, here's how you can get this working in Vivado 2019.2.

Design Heirarchy

Because we need to name design units within the heirarchy, I've included a screenshot showing the heirarchy itself.


This is a perverse mixture of VHDL, SystemVerilog, and Xilinx cores.

  • The simulation top-level (top) is SystemVerilog (because I'm using the AXI VIP, which relies on SystemVerilog mojo).
  • My custom AXI4-Lite peripheral (axi_hw) is written in VHDL, since that's what the majority of my designs use.
  • For AXI stimulus, I'm using Xilinx's AXI VIP. (As an aside, the project settings matter here: Vivado 2019.2 segfaults when generating the simulator unless the VIP's generated RTL is in Verilog, and the project settings are what determines this.)

Since the top-level SystemVerilog is relatively short, I've pasted it in entirety here.

`timescale 1ns / 1ps

import axi_vip_pkg::*;
import axi_vip_0_pkg::*;

module top #(
    parameter integer C_AXI_ADDR_WIDTH=12,
    parameter integer C_AXI_DATA_WIDTH=32) ();

logic m_axi_aclk=0, m_axi_aresetn=0;

logic [3:0] led;

logic [C_AXI_ADDR_WIDTH-1:0] m_axi_awaddr, m_axi_araddr;
logic m_axi_awvalid, m_axi_awready, m_axi_wvalid, m_axi_wready;
logic m_axi_arvalid, m_axi_arready, m_axi_rvalid, m_axi_rready;
logic m_axi_bvalid, m_axi_bready;
logic [C_AXI_DATA_WIDTH-1:0] m_axi_wdata, m_axi_rdata;
logic [C_AXI_DATA_WIDTH/8-1:0] m_axi_wstrb;
logic [1:0] m_axi_bresp, m_axi_rresp;

always #5ns m_axi_aclk = ~m_axi_aclk;

axi_hw #(C_AXI_ADDR_WIDTH, C_AXI_DATA_WIDTH) dut (
    .s_axi_aclk(m_axi_aclk),
    .s_axi_aresetn(m_axi_aresetn),
    .s_axi_awaddr(m_axi_awaddr),
    .s_axi_awvalid(m_axi_awvalid),
    .s_axi_awready(m_axi_awready),
    .s_axi_wdata(m_axi_wdata),
    .s_axi_wstrb(m_axi_wstrb),
    .s_axi_wvalid(m_axi_wvalid),
    .s_axi_wready(m_axi_wready),
    .s_axi_bresp(m_axi_bresp),
    .s_axi_bvalid(m_axi_bvalid),
    .s_axi_bready(m_axi_bready),
    .s_axi_araddr(m_axi_araddr),
    .s_axi_arvalid(m_axi_arvalid),
    .s_axi_arready(m_axi_arready),
    .s_axi_rdata(m_axi_rdata),
    .s_axi_rresp(m_axi_rresp),
    .s_axi_rvalid(m_axi_rvalid),
    .s_axi_rready(m_axi_rready),
    .led(led));

axi_vip_0 vip(
    .aclk(m_axi_aclk),
    .aresetn(m_axi_aresetn),
    .m_axi_awaddr(m_axi_awaddr),
    .m_axi_awvalid(m_axi_awvalid),
    .m_axi_awready(m_axi_awready),
    .m_axi_wdata(m_axi_wdata),
    .m_axi_wstrb(m_axi_wstrb),
    .m_axi_wvalid(m_axi_wvalid),
    .m_axi_wready(m_axi_wready),
    .m_axi_bresp(m_axi_bresp),
    .m_axi_bvalid(m_axi_bvalid),
    .m_axi_bready(m_axi_bready),
    .m_axi_araddr(m_axi_araddr),
    .m_axi_arvalid(m_axi_arvalid),
    .m_axi_arready(m_axi_arready),
    .m_axi_rdata(m_axi_rdata),
    .m_axi_rresp(m_axi_rresp),
    .m_axi_rvalid(m_axi_rvalid),
    .m_axi_rready(m_axi_rready));

/* AXI VIP stimulus */
axi_vip_0_mst_t master_agent;
logic [31:0] x=0;
xil_axi_resp_t resp;
initial begin
    /* Create an agent */
    master_agent = new("axi_vip_0_mst",vip.inst.IF);
    master_agent.set_agent_tag("Master VIP");
    master_agent.start_master();

    /* Remove reset */
    #50ns m_axi_aresetn  = 1;

    /* Poke LED bit 0 */
    #20ns master_agent.AXI4LITE_WRITE_BURST(
             12'h000,0,32'h1,resp);

    /* Poke LED bit 1 */
    #20ns master_agent.AXI4LITE_WRITE_BURST(
             12'h004,0,32'h1,resp);

    // Peek LED bit 0 */
    #20ns master_agent.AXI4LITE_READ_BURST(
             12'h000,0,x,resp);

    #100ns
    $finish;
end
endmodule

Creating the Protoinst File

When using block designs, Vivado creates .protoinst files to annotate AXI buses. These files are passed to xsim in order to create protocol analyzers.

Xilinx is not yet ready to generate protoinst files for RTL-only designs, so we do it by hand:

{
   "version": "1.0",
   "modules": {
      "top": {
         "proto_instances": {
            "/M_AXI": {
               "interface": "xilinx.com:interface:aximm:1.0",
               "ports": {
                  "ACLK": { "actual": "m_axi_aclk"},
                  "ARADDR": { "actual": "m_axi_araddr"},
                  "ARESETN": { "actual": "m_axi_aresetn"},
                  "ARREADY": { "actual": "m_axi_arready"},
                  "ARVALID": { "actual": "m_axi_arvalid"},
                  "AWADDR": { "actual": "m_axi_awaddr"},
                  "AWREADY": { "actual": "m_axi_awready"},
                  "AWVALID": { "actual": "m_axi_awvalid"},
                  "BREADY": { "actual": "m_axi_bready"},
                  "BRESP": { "actual": "m_axi_bresp"},
                  "BVALID": { "actual": "m_axi_bvalid"},
                  "RDATA": { "actual": "m_axi_rdata"},
                  "RREADY": { "actual": "m_axi_rready"},
                  "RRESP": { "actual": "m_axi_rresp"},
                  "RVALID": { "actual": "m_axi_rvalid"},
                  "WDATA": { "actual": "m_axi_wdata"},
                  "WREADY": { "actual": "m_axi_wready"},
                  "WSTRB": { "actual": "m_axi_wstrb"},
                  "WVALID": { "actual": "m_axi_wvalid"}
               }
            }
         }
      }
   }
}

You will likely need to modify the instance name ("top") and AXI signal names ("m_axi_awaddr" etc.) to match your design.

Launching the Simulator

Once your .protoinst file matches your design, you need to pass it to xsim. I find this most convenient to do on the command line, outside of Vivado. The first step is to find your simulation directory:

# from somewhere in the Vivado project directory:
$ find . -name simulate.sh
./my_design.sim/sim_1/behav/xsim/simulate.sh
$ cd my_design.sim/sim_1/behav/xsim

This directory contains a simulate.sh script generated by Vivado that invokes xsim:

$ tail -2 simulate.sh
xsim top_behav -key {Behavioral:sim_1:Functional:top} -tclbatch top.tcl -log simulate.log

All we need to do is add the "-protoinst" option (pointing to our JSON file):

$ xsim top_behav -key {Behavioral:sim_1:Functional:top} \
        -tclbatch top.tcl -log simulate.log \
        -protoinst my_design.protoinst

Fully Expanded AXI Analyzer

That's it! You should now be able to annotate AXI transactions in an RTL-only design. I'll leave you with a fully expanded image of the AXI analyzer showing what signals and derived information are visible.


share -