Xilinx / Xilinx/HLS

FDSE cells generated with INIT='0' for constant-1 bits in static variable guard pattern

Open
#19 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
No language data
Stars
416
Forks
59
PR merge metrics
No merged PRs in 30d

Description

Summary

Vitis HLS generates FDSE (flip-flop with synchronous SET) cells with INIT => '0' for constant-1 bits in C++ static variable constructors. The FDSE primitive should use INIT => '1' for these bits, since they represent constant values that must be 1 from power-up. The incorrect INIT creates a race condition where the SET signal is permanently disabled before it can fire, leaving these bits stuck at 0.

Vitis HLS Version

2022.1 (also likely affects other versions using the same code generation pattern)

Reproduction

HLS Source (simplified from Xilinx/ETH Zurich 100G Network Stack arp_server_subnet)
struct arpHeader {
    ap_uint<48> macDst;     // bits [47:0]
    ap_uint<48> macSrc;     // bits [95:48]
    ap_uint<64> constant;   // bits [159:96] - EtherType, HW/Proto type, lengths
    // ... more fields

    arpHeader() {
        // Constant ARP header fields: 0x0806, 0x0001, 0x0800, 0x06, 0x04
        constant = 0x0406000801000608ULL;  // has 8 bits that are 1
    }
};

void generate_arp_pkg(hls::stream<...>& in, hls::stream<...>& out) {
    static arpHeader header;  // Constructor fires once via guard variable
    // ... use header.constant in output ...
}
What HLS generates

For the 8 constant-1 bits at positions {99, 105, 106, 120, 131, 145, 146, 154}:

-- WRONG: INIT => '0' for a bit that should always be 1
\header_header_1_reg[105]\: unisim.vcomponents.FDSE
    generic map(
      INIT => '0'        -- BUG: should be '1'
    )
    port map (
      C  => ap_clk,
      CE => header_header_1(0),                 -- shared write-enable
      D  => \header_header_1_reg_n_0_[105]\,    -- self-feedback (Q -> D)
      Q  => \header_header_1_reg_n_0_[105]\,
      S  => \header_header_1[154]_i_1_n_0\      -- SET (active when guard=0)
    );

The guard variable register:

-- Guard has CE => '1' (always enabled) - can transition independently of header CE
\guard_variable_..._reg[0]\: unisim.vcomponents.FDRE
    generic map(INIT => '0')
    port map (
      C  => ap_clk,
      CE => '1',          -- Always enabled!
      D  => guard_lut_output,
      Q  => guard_variable(0),
      R  => '0'
    );

The SET signal LUT:

-- S = 0 whenever guard_1_reg_496 = 1 (upper 32 bits of INIT are all zero)
\header_header_1[154]_i_1\: LUT6
    generic map(INIT => X"00000000C8C8C8CC")
    port map (
      I0..I4 => <same inputs as CE LUT>,
      I5     => guard_variable_1_reg_496(0),   -- pipeline copy of guard
      O      => SET_signal
    );

Root Cause

  1. After FPGA configuration: FDSE Q = INIT = 0 (wrong, should be 1)
  2. The guard variable has CE => '1' (always enabled), so it can transition 0→1 based on FIFO status (e.g., myMacAddress_c_empty_n = 1) without the header register CE being active
  3. The header register CE depends on pipeline registers that also start at INIT=0, so CE=0 during the guard transition
  4. FDSE SET requires CE=1 to take effect (per UG974 FDSE truth table). Since CE=0, the SET is ignored.
  5. The pipeline copy guard_variable_1_reg_496 captures guard=1, making SET permanently 0
  6. FDSE cells are stuck at 0 forever — the self-feedback loop (D=Q) preserves the wrong value

Expected Behavior

FDSE cells for constant-1 bits in static variable constructors should have INIT => '1'. This ensures:

  • Correct value immediately after configuration (no SET pulse needed)
  • Self-feedback loop preserves the correct value
  • SET pulse is belt-and-suspenders, not the sole initialization mechanism

Actual Behavior

FDSE cells have INIT => '0'. The constant-1 bits read as 0 in hardware, producing corrupted output.

Impact

In the 100G Network Stack ARP server, this zeroes bytes 12-19 of every ARP reply frame (EtherType=0x0000, HWType=0x0000, ProtoType=0x0000, HWLen=0, ProtoLen=0). The ARP replies are malformed and silently dropped by all compliant network stacks.

Workaround

Add Vivado constraints to override INIT values after synthesis:

# Fix FDSE INIT values for ARP header constant-1 bits
foreach bit {99 105 106 120 131 145 146 154} {
    set cells [get_cells -quiet -hier -filter "NAME =~ *generate_arp_pkg*header_header_1_reg[$bit] && REF_NAME == FDSE"]
    if {[llength $cells] > 0} {
        set_property INIT 1'b1 $cells
    }
}

Verification

ILA capture on Alveo U55C after JTAG programming confirms:

  • guard_variable = 1 (constructor "fired")
  • All 8 FDSE bits for header_header_1[159:96] = 0 (should be 1)
  • All 4096 samples show the same values (no transient correct state)

Contact: morris@surfworks.energy

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing Vitis HLS code generation for static C++ variable constructors and the FDSE initialization emitted for constant-1 bits. Reproduce the issue with the simplified arpHeader and generate_arp_pkg example, then inspect the generated VHDL. Done when those bits receive the correct power-up initialization and the reported ARP output is no longer corrupted.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.