Search code examples
verilogxilinxvivadozynq

Vivado infers incorrect FREQ_HZ for AXI busses to my module


I'm working on a design in Vivado. My top level design is a block diagram. The block diagram has IP blocks and my Verilog RTL modules. Whenever I change my main module and Verilog updates the block diagram, it always incorrectly infers the clock frequency for my module. How can I fix this?

This issue is infuriating, ask it breaks my design. All of the other AXI busses in the design are correctly using 10MHz, but whenever I change main and the block diagram is updated, Vivado decides main's AXI busses are at 100MHz. And as long as the clocks are mismatched, I can't build. I can manually update the frequency in the properties for the block in the block diagram, but those changes are wiped every time I update main (which is frequently, because it's my main module).

I've tried adding a dedicated clock and reset for each AXI bus (even though they all connect to the same net). I've messed around with X_INTERFACE_PARAMETER (is this documented anywhere?). All to no avail.

Also, M_AXIS_CMD is a master AXI interface and should be on the output side of the block for main. Not sure what's up with that. But that's a pretty minor issue compared to the clock.

Source: https://gitlab.com/tessera/pcd8544-tests

Block diagram

Module interface:

module main #(
        CLK_FREQ = 50000000// : CLK_FREQ > 0 // clock frequency
    )(
        (* X_INTERFACE_PARAMETER = "XIL_INTERFACENAME core_clk, ASSOCIATED_RESET core_rst, FREQ_HZ 10000000" *)
        (* X_INTERFACE_INFO = "xilinx.com:signal:clock:1.0 core_clk CLK" *)
        input clk,
        (* X_INTERFACE_PARAMETER = "XIL_INTERFACENAME core_rst, POLARITY ACTIVE_HIGH" *)
        (* X_INTERFACE_INFO = "xilinx.com:signal:reset:1.0 core_rst RST" *)
        input rst,
        (* X_INTERFACE_PARAMETER = "XIL_INTERFACENAME axi_rst, POLARITY ACTIVE_LOW" *)
        (* X_INTERFACE_INFO = "xilinx.com:signal:reset:1.0 axi_rst RST" *)
        input axi_rst,

        (* X_INTERFACE_INFO = "xilinx.com:interface:fifo_write:1.0 DOUT FULL" *)
        input wr_full,
        (* X_INTERFACE_INFO = "xilinx.com:interface:fifo_write:1.0 DOUT WR_DATA" *)
        output reg [8:0] wr_data,
        (* X_INTERFACE_INFO = "xilinx.com:interface:fifo_write:1.0 DOUT WR_EN" *)
        output reg wr_valid,

        // slave AXI-Lite write channel FROM PS
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG AWADDR"  *) input  wire [4:0]  s_axi_reg_awaddr,  // address
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG AWPROT"  *) input  wire [2:0]  s_axi_reg_awprot,  // channel protection type
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG AWVALID" *) input  wire        s_axi_reg_awvalid, // address valid
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG AWREADY" *) output wire        s_axi_reg_awready, // address ready
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG WDATA"   *) input  wire [31:0] s_axi_reg_wdata,   // data
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG WSTRB"   *) input  wire [3:0]  s_axi_reg_wstrb,   // strobes - one bit per byte of data
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG WVALID"  *) input  wire        s_axi_reg_wvalid,  // data/strobes valid
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG WREADY"  *) output wire        s_axi_reg_wready,  // data/strobes ready
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG BRESP"   *) output wire [1:0]  s_axi_reg_bresp,   // response
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG BVALID"  *) output wire        s_axi_reg_bvalid,  // response valid
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG BREADY"  *) input  wire        s_axi_reg_bready,  // response ready

        // slave AXI-Lite read channel FROM PS
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG ARADDR"  *) input  wire [4:0]  s_axi_reg_araddr,  // address
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG ARPROT"  *) input  wire [2:0]  s_axi_reg_arprot,  // channel protection type
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG ARVALID" *) input  wire        s_axi_reg_arvalid, // address valid
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG ARREADY" *) output wire        s_axi_reg_arready, // address ready
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG RDATA"   *) output wire [31:0] s_axi_reg_rdata,   // data
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG RRESP"   *) output wire [1:0]  s_axi_reg_rresp,   // response
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG RVALID"  *) output wire        s_axi_reg_rvalid,  // data/response valid
        (* X_INTERFACE_INFO = "xilinx.com:interface:aximm_rtl:1.0 S_AXI_REG RREADY"  *) input  wire        s_axi_reg_rready,  // data/response ready



        // master AXI-Stream command channel TO DataMover
        (* X_INTERFACE_INFO = "xilinx.com:interface:axis_rtl:1.0 M_AXIS_CMD TDATA"   *) output reg  [71:0] m_axis_cmd_data,
        (* X_INTERFACE_INFO = "xilinx.com:interface:axis_rtl:1.0 M_AXIS_CMD TREADY"  *) output reg         m_axis_cmd_valid,
        (* X_INTERFACE_INFO = "xilinx.com:interface:axis_rtl:1.0 M_AXIS_CMD TVALID"  *) input  wire        m_axis_cmd_ready,

        // slave AXI-Stream status channel FROM DataMover
        (* X_INTERFACE_INFO = "xilinx.com:interface:axis_rtl:1.0 S_AXIS_STS TDATA"   *) input  wire [7:0]  s_axis_status_data,
        (* X_INTERFACE_INFO = "xilinx.com:interface:axis_rtl:1.0 S_AXIS_STS TKEEP"   *) input  wire [0:0]  s_axis_status_keep,
        (* X_INTERFACE_INFO = "xilinx.com:interface:axis_rtl:1.0 S_AXIS_STS TLAST"   *) input  wire        s_axis_status_last,
        (* X_INTERFACE_INFO = "xilinx.com:interface:axis_rtl:1.0 S_AXIS_STS TREADY"  *) input  wire        s_axis_status_valid,
        (* X_INTERFACE_INFO = "xilinx.com:interface:axis_rtl:1.0 S_AXIS_STS TVALID"  *) output reg         s_axis_status_ready,

        // slave AXI-Stream data channel FROM DataMover
        (* X_INTERFACE_INFO = "xilinx.com:interface:axis_rtl:1.0 S_AXIS_DATA TDATA"  *) input  wire [31:0] s_axis_stream_data,
        (* X_INTERFACE_INFO = "xilinx.com:interface:axis_rtl:1.0 S_AXIS_DATA TKEEP"  *) input  wire [3:0]  s_axis_stream_keep,
        (* X_INTERFACE_INFO = "xilinx.com:interface:axis_rtl:1.0 S_AXIS_DATA TLAST"  *) input  wire        s_axis_stream_last,
        (* X_INTERFACE_INFO = "xilinx.com:interface:axis_rtl:1.0 S_AXIS_DATA TREADY" *) input  wire        s_axis_stream_valid,
        (* X_INTERFACE_INFO = "xilinx.com:interface:axis_rtl:1.0 S_AXIS_DATA TVALID" *) output reg         s_axis_stream_ready,

        // DataMover memory-mapped to stream error
        input wire datamover_mm2s_err
    );

Solution

  • it always incorrectly infers the clock frequency for my module. How can I fix this?

    I pulled from your git repo and it worked fine in Vivado 2017.4. You may be conflating a couple different things, and are probably expecting something to happen that isn't supposed to. Which is all totally understandable given the dearth of documentation surrounding the propriety (but useful) Vivado IP packager and block diagram tools.

    • 2017.4 is functionally the same as 2017.3 with added parts, so this shouldn't be causing any differences

    • I removed the FREQ_HZ from the (* X_INTERFACE_PARAMETER ... *) for the clock. This might have caused the clock mismatch you mentioned, but even when I put it back, it works OK for me now. However, FREQ_HZ should only be used on output (generated) clocks; see below.

    • When I first open the diagram or update main.v, and click on the input pin, the properties say 100MHz, as you metioned. But after an F6 "Validate" command, the pin reports 10MHz correctly. This is all expected.

    inputpin, pinprops

    • You should NOT expect the CLK_FREQ Verilog parameter to magically update based on the pin. Whenever the module is new the parameters in the GUI will take on the default values. They can be edited and should stay stay through an update cycle, but it's possible they will have to be reset due to major edits. It's up to you to setup these parameters to match.

    • Some Xilinx cores, like AXI Uartlite, use some fancy undocumented TCL scripting to automatically transfer the pin frequency to the Verilog parameters at verification. You can see how this is done, for example, in the /opt/Xilinx/Vivado/2017.4/data/ip/xilinx/axi_uartlite_v2_0/bd/bd.tcl file.

    is this documented anywhere?

    There is some documentation about using HDL modules in UG994, "Inferring Control Signals in a RTL Module", but it is not very complete. The best documentation for this is in the "Lite Bulb" guy (a distant cousin of Clippy) you get to by pressing the lite bulb in the toolbar of an editor.

    litebulbguy

    • Unsolicited advice #1 - Using RTL modules sound like a great idea. Much easier than going through the work of packaging your own IP. Until you find out that: a) it's just packaging the module anyway in the background and automatically re-runs on each edit, b) it's flakier than even the packager, and c) you have no control over the interface inference and so on. Learn to use the packager to create real IP packages.

    • Unsolicited advice #2 - Save the block diagrams and project files at TCL scripts. Don't put the whole project or block diagram .xci and .xml files in Git. See commands like: write_bd_tcl and write_project_tcl. If you (often) need to regenerate the project if it get corrupted, this allows completely consistent results.

    • Finally, here's a snippet of the header the way I'd setup the (* X_INTERFACE... *) stuff. If the clock runs an AXI bus, add the ASSOCIATED_BUSIF flag too:

      (* X_INTERFACE_INFO = "xilinx.com:signal:clock:1.0 core_clk CLK" *)
      (* X_INTERFACE_PARAMETER = "ASSOCIATED_RESET reset" *)
      input clk,
      (* X_INTERFACE_INFO = "xilinx.com:signal:reset:1.0 core_rst RST" *)
      (* X_INTERFACE_PARAMETER = "POLARITY ACTIVE_HIGH" *)
      input rst,