Blog › ICP guides
VHDL developer on retainer: signal vs variable semantics, delta cycles, and FPGA VHDL on monthly retainer
October 3, 2026 · ~14 min read
A VHDL developer was maintaining a VHDL design for an industrial motor controller on a Xilinx Zynq-7000 FPGA. The motor controller regulated PWM duty cycle for a three-phase induction motor driving a conveyor belt in a food processing facility. The VHDL design included a safety protection process that was supposed to limit the PWM duty cycle to 12.5% (represented as x“20” in an 8-bit unsigned value) whenever an overheat flag was asserted. The overheat flag was driven by an on-chip temperature sensor monitoring the FPGA die temperature. The developer had added the overheat protection as a new branch in an existing process that also computed the normal operating duty cycle from a speed reference input.
The process had two signal assignments to pwm_duty_out: first, inside an if overheat_flag = ‘1’ then branch, pwm_duty_out <= x“20” (limit to 12.5%); second, after the if-else block concluded, an unconditional assignment pwm_duty_out <= computed_duty where computed_duty was a variable computed earlier in the same process. In VHDL, multiple signal assignments to the same signal within a single process evaluation produce only one update: the last assignment wins. Signal assignments in VHDL do not take effect immediately when the assignment statement executes — they schedule a future update that takes effect after the process suspends, at the next delta cycle. When a process contains multiple assignments to the same signal, only the most recent scheduled update is applied. The unconditional pwm_duty_out <= computed_duty assignment, which appeared after the if-else block, was always the last assignment in the process evaluation, so it always took effect. The overheat protection assignment of x“20” was scheduled and then silently overridden by the unconditional assignment that followed.
The delta cycle model in VHDL simulation is the key to understanding why this class of bug behaves as it does. When a process executes, each signal assignment places a transaction on that signal’s driver. When the process suspends (at a wait statement or at the end of the process body), VHDL applies the transactions: if multiple transactions exist on the same signal driver within the same simulation time step, the final one is the one that takes effect. The process then re-evaluates at the next delta cycle if any signals in its sensitivity list changed. The fix was to restructure the two assignments into a single conditional assignment: if overheat_flag = ‘1’ then pwm_duty_out <= x“20”; else pwm_duty_out <= computed_duty; end if;. With this structure, only one signal assignment to pwm_duty_out executes per process evaluation — either the safety limit or the computed duty, never both — so there is no last-assignment-wins override. Three overheating events where the motor ran at full duty cycle despite the overheat flag being asserted dropped to zero after the fix. The investigation — identifying that the overheat flag was correctly asserted, tracing the duty cycle output through the process to identify the unconditional override, and verifying the fix in simulation — was three and a half hours.
The reason this class of bug is systematically invisible is that the VHDL signal assignment model is a simulation model, not an execution model. A developer accustomed to imperative programming languages expects that if overheat_flag = ‘1’ then pwm_duty_out <= x“20”; end if; followed by pwm_duty_out <= computed_duty; will assign x“20” when overheat_flag is ‘1’ and then assign computed_duty. In VHDL, both assignments schedule transactions to the same signal, and only the last transaction is applied. The first assignment is not “corrected by” the second — it is silently replaced. No simulation tool raises a warning for multiple assignments to the same signal in a process, because the behavior is defined by the VHDL standard (IEEE 1076). The safety protection appeared to be present in code review: the if-branch was there, the assignment was there, the code looked correct. The bug was invisible without understanding VHDL’s transaction scheduling model.
VHDL: IEEE 1076 standard, signal vs variable semantics, and the delta cycle simulation model
VHDL standardization follows a progression anchored to IEEE 1076. The original IEEE 1076-1987 established the language; IEEE 1076-1993 became the most widely implemented revision and remains the baseline for most legacy FPGA and ASIC designs. IEEE 1076-2000 and 1076-2002 made incremental refinements. IEEE 1076-2008 (VHDL-2008) was a significant update: it added fixed-point and floating-point packages to the standard library, improved generics (allowing unconstrained types as generic parameters), added conditional and selected signal assignments in processes (reducing the need for verbose if-else and case structures for combinational logic), and enhanced case and loop syntax. IEEE 1076-2019 (the current revision) added improvements to protected types, enhanced generics and interfaces, and better support for external names for testbench access to internal signals. GHDL, the dominant open-source VHDL simulator, supports IEEE 1076-1993, 2000, 2002, and 2008; nvc, a newer open-source simulator, targets VHDL-2019. Vivado and Quartus Prime both support the IEEE 1076-2008 subset required for synthesis.
The signal vs variable distinction is the most consequential semantic difference between VHDL and imperative programming languages, and the source of the most common class of VHDL retainer bugs. A signal (declared with the keyword signal in an entity port or architecture declarative region) does not update immediately when an assignment statement executes. Instead, the assignment schedules a transaction — a future update — on that signal’s driver. Transactions are applied when the process suspends, at the boundary between the current delta cycle and the next. If a process contains multiple assignments to the same signal in a single evaluation, all assignments schedule transactions, but only the last transaction is applied; all earlier ones are discarded. A variable (declared with the keyword variable in a process declarative region) behaves like an imperative language variable: the assignment v := new_value; takes effect immediately, and the very next statement in the same process sees the updated value. The most common mistake is using a signal where a variable is needed: a developer writes sig_a <= x + 1; sig_b <= sig_a * 2; inside a process, expecting sig_b to receive (x + 1) * 2, but sig_b instead receives the old value of sig_a multiplied by 2, because sig_a’s transaction has not been applied yet during the process execution.
The delta cycle simulation model underlies all VHDL simulation semantics. VHDL simulation proceeds in simulation cycles, each consisting of one or more delta cycles. A delta cycle is an infinitesimal time step: simulation time does not advance during a delta cycle, but signal updates propagate. When a process suspends, VHDL applies all pending signal transactions from that process. If any signals changed value, the processes sensitive to those signals are re-scheduled to run in the next delta cycle at the same simulation time. This models zero-propagation-delay digital logic: combinational logic in simulation converges to a stable state through multiple delta cycles without the clock advancing. Delta cycles also explain why last-assignment-wins is defined and not a simulation error — the transaction model is the mechanism by which VHDL models concurrent hardware: all processes in a design evaluate concurrently (from the perspective of the simulation time step), and their signal updates are applied atomically at the delta boundary. Concurrent signal assignments outside a process directly schedule transactions; multiple concurrent signal assignment statements driving the same resolved signal (such as std_logic, which has a resolution function defined in IEEE 1164) invoke the resolution function to determine the resulting value. Driving an unresolved type from multiple sources produces a simulation error.
The entity/architecture structure in VHDL separates interface from implementation. An entity defines the port interface — the signals that cross the module boundary — with direction modes in, out, inout, and buffer. An architecture defines the behavior or structure of the entity: a behavioral architecture contains processes and concurrent statements; a structural architecture contains component instantiations connected by internal signals. A single entity may have multiple architectures, enabling behavioral modeling (for simulation-only test models) alongside structural or RTL architectures (for synthesis). A configuration statement binds component instantiations in a structural architecture to specific entity/architecture pairs. This separation is a deliberate design choice in VHDL’s history — the language was designed for hardware description and documentation as much as simulation, and the entity/architecture/configuration triad reflects that intent.
FPGA synthesis with VHDL, process patterns, and simulation tools
The synchronous process pattern is the foundation of synthesizable RTL VHDL. A clocked process takes the form process(clk) begin if rising_edge(clk) then ... end if; end process;; synthesis infers D flip-flops for all signal assignments inside the rising_edge guard, with the signal’s current value latched on the rising clock edge. Asynchronous reset is added by including the reset signal in the sensitivity list and adding a priority branch: process(clk, rst) begin if rst = ‘1’ then ... elsif rising_edge(clk) then ... end if; end process;; synthesis infers flip-flops with asynchronous reset. Combinational processes require a complete sensitivity list: every signal that is read inside the process must appear in the sensitivity list, or the simulation will not re-evaluate when that signal changes — the simulation result diverges from the synthesis result, which always models combinational logic as immediately responsive to any input change. VHDL-2008 addresses this with process(all), equivalent to Verilog’s always @(*), which automatically includes all signals read within the process in the sensitivity list. Synthesis tools generally accept process(all) in VHDL-2008 mode; legacy VHDL-1993 code requires explicit listing.
The VHDL type system is expressive and strongly typed, which is both a strength (type mismatches are compile-time errors) and a source of verbosity. std_logic (defined in IEEE 1164) is the standard single-bit logic type, with nine values: ‘0’ (forcing zero), ‘1’ (forcing one), ‘X’ (unknown, forcing), ‘U’ (uninitialized), ‘Z’ (high impedance), ‘W’ (unknown, weak), ‘L’ (weak zero), ‘H’ (weak one), and ‘-’ (don’t care). std_logic_vector(7 downto 0) is an 8-bit vector. The numeric_std package (IEEE 1076.6) provides unsigned and signed types with proper arithmetic semantics; these are preferred over the older std_logic_arith and std_logic_unsigned packages, which are non-standard and produce ambiguous operator overloads. integer range 0 to 255 is a bounded integer type synthesizable as a fixed-width binary value. Enumeration types are the canonical approach for FSM state encoding; the synthesis tool chooses binary, one-hot, or Gray encoding based on optimization settings. Record types are synthesizable in VHDL-2008 and allow structured grouping of related signals, reducing the verbosity of large port lists.
The synthesis and simulation tool landscape for VHDL spans open-source and commercial offerings. Vivado (Xilinx/AMD) is the dominant synthesis and implementation tool for Xilinx FPGAs (7 Series, UltraScale, UltraScale+, Versal) and supports IEEE 1076-2008 for synthesis. Quartus Prime (Intel/Altera) serves Intel FPGA families (Cyclone, Arria, Stratix) with comparable VHDL-2008 support. Synopsys Design Compiler and Cadence Genus are the industry-standard ASIC synthesis tools, targeting standard-cell libraries with more aggressive timing and area optimization than FPGA synthesis. For simulation, GHDL is the mature open-source VHDL simulator supporting IEEE 1076-1993, 2000, 2002, and 2008, with an LLVM or GCC backend for compiled simulation; it integrates with GTKWave for waveform viewing. ModelSim and Questa (Siemens EDA, formerly Mentor Graphics) are the dominant commercial simulators, offering mixed-language simulation (VHDL and Verilog/SystemVerilog in the same testbench), advanced debugging, and code coverage. nvc is a modern open-source VHDL-2019 simulator with improving standards compliance. Vivado’s integrated simulator (xsim) supports VHDL-2008 and is adequate for RTL-level functional simulation without requiring a separate simulator license.
Timing analysis is the final phase of FPGA implementation and determines whether the synthesized design can operate at the target clock frequency. A setup time violation (negative worst negative slack, WNS) means that the combinational path between two flip-flops is too long to complete before the receiving flip-flop’s setup time window relative to the clock edge; the circuit is unreliable at the target frequency. A hold time violation means that a signal changes too quickly after the clock edge, before the receiving flip-flop has captured its intended value. Vivado’s Timing Analyzer reports WNS for setup (goal: ≥0) and worst hold slack (WHS, goal: ≥0) after place-and-route. Timing constraints are specified in XDC (Xilinx Design Constraints) files for Vivado or SDC (Synopsys Design Constraints) files for Quartus; they define primary clocks, generated clocks, I/O timing relative to external interfaces, multicycle paths (where two clock cycles are allowed for a combinational path), and false paths (where no timing constraint applies, such as between unrelated asynchronous clock domains). Incorrect or incomplete timing constraints are among the most common sources of VHDL FPGA retainer work: a design that appears to pass timing with incomplete constraints may fail in hardware at temperature or voltage corners.
Typical VHDL retainer work and what it looks like in a work log
The last-assignment-wins signal override bug is the most invisible category of VHDL retainer work. The pattern: a developer adds a safety or protection branch to an existing process that already has an unconditional signal assignment after the new if-else block; the new assignment inside the if-branch schedules a transaction that is silently overridden by the existing post-if-else unconditional assignment; the signal receives the wrong value in all process evaluations regardless of the branch condition. Work log entry: “pwm_control_proc: pwm_duty_out — two assignments: conditional safety assignment <= x“20” inside if overheat_flag = ‘1’ branch, followed by unconditional <= computed_duty after if-else block; VHDL transaction scheduling (last assignment wins per IEEE 1076): unconditional assignment always last, always overrides safety limit; symptom: overheat flag asserted, full duty cycle output; fix: restructure to single if/else conditional assignment; overheating events: 3 → 0; 3.5h.”
Signal vs variable misuse is the second pattern. A developer uses a signal as an intermediate computation register inside a process, expecting the update to be visible to the next statement: sig_a <= x + 1; sig_b <= sig_a * 2; — sig_b reads the old value of sig_a (the value from before the process evaluation began), not x + 1, because sig_a’s transaction has not been applied within the current process execution. The fix is to declare a process variable and use immediate assignment: variable v : unsigned(7 downto 0); ... v := x + 1; sig_b <= v * 2;. The variable v is updated immediately by :=, so the subsequent statement reads the correct value. Work log entry: “compute_coefficients process: sig_a used as intermediate computation register — read old value in next statement; declared variable v := x + 1; sig_b <= v * 2; wrong coefficient values: 5 → 0; 2h.”
Sensitivity list incompleteness is the third pattern. A combinational process has process(a, b) in its sensitivity list but reads signal c inside the process body. When c changes, the process does not re-evaluate in simulation: the output holds its stale value. Meanwhile, synthesis infers combinational logic that is genuinely responsive to c — the synthesized circuit updates immediately when c changes. The result is a simulation vs synthesis behavioral divergence: the simulation says the output is stale, the silicon says it updated. This class of bug passes simulation testing (because simulation tests the stale behavior) and fails in hardware (or vice versa, depending on which behavior the test expected). Work log entry: “decode_ctrl process: c_in missing from sensitivity list; synthesis read c_in combinatorially; simulation held stale output when c_in changed; added c_in to sensitivity list (or converted to process(all)); wrong decode outputs: 3 → 0; 2h.” A fourth pattern — uninitialized signals at simulation start — arises because VHDL signals default to ‘U’ (uninitialized) at time zero. A process that reads a signal before it is driven propagates ‘U’ through logic gates (AND with ‘U’ produces ‘U’; OR with ‘U’ produces ‘U’ unless one input is ‘1’), producing all-X waveforms in simulation. The fix is to add a reset phase to the testbench that drives all inputs to known values before releasing reset, or to use a reset process in the design that drives all registers to their initial values on power-up. In synthesis, flip-flop initial values are specified via the INIT attribute or the asynchronous reset path; the ‘U’ propagation is a simulation-only artifact.
Track VHDL developer retainer hours without the status emails
When a 3.5-hour FPGA debug session traces a motor controller overheat-protection failure to last-assignment-wins signal transaction semantics inside a VHDL process — the safety-critical assignment silently overridden because VHDL applies only the final transaction after the process suspends — the work log must name the process, the signal, the assignment ordering, and the event count before and after. HourTab gives your VHDL retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log naming the HDL construct and the fix. No client login. No status emails. CSV in, URL out.
See HourTab pricing →How HourTab tracks VHDL developer retainer hours
VHDL retainer work is invisible by the same mechanism that makes the signal transaction model non-intuitive: the code looks correct to a developer expecting imperative semantics. The overheat protection if-branch was present, the assignment was present, the code read as “if overheat_flag then limit duty cycle.” The last-assignment-wins behavior requires understanding that VHDL signal assignments schedule future transactions rather than executing immediately, and that all transactions to the same signal from a single process evaluation are collapsed to the last one. No compile-time warning, no lint message, no simulation assertion failure — the signal simply received the wrong value.
The work log needs to name the mechanism: which process, which signal, which assignments and in what order, what the VHDL transaction scheduling model means for that ordering (last assignment wins per IEEE 1076), and what the event count change was before and after the fix. A log entry that says “fixed PWM output signal assignment, 3.5h” is not auditable. A log entry that names the process, the two assignments to pwm_duty_out, the delta cycle transaction model, the overheating event count before and after, and the restructuring fix is auditable and defensible.
HourTab gives VHDL developers a public retainer-hours URL they send to clients — FPGA IP design houses, defense electronics contractors, industrial automation system integrators, and semiconductor companies maintaining ASIC-grade VHDL production designs. For VHDL retainers, each work log entry should name the mechanism: which process and signal, which VHDL transaction scheduling rule applies, which simulation vs synthesis behavioral difference was confirmed by waveform comparison, and what event count change confirmed the fix. Comparative context: VHDL retainer work has structural overlap with adjacent HDL languages where the signal update model and simulation semantics differ. Verilog retainers cover a language where blocking vs non-blocking assignment (= vs <=) determines whether pipeline register stages sample pre-clock-edge values or already-updated values — structurally analogous to VHDL’s signal vs variable distinction but with different assignment operators and different default behaviors in always blocks. ABAP retainers cover a similarly legacy-dominated enterprise language where SAP-specific semantics (FOR ALL ENTRIES IN empty-table behavior) differ non-obviously from the general case, producing bugs that are invisible until a specific runtime condition occurs.
FAQ: VHDL developer retainers
What does a VHDL developer on retainer typically do?
A VHDL developer on monthly retainer covers last-assignment-wins signal override analysis (identifying where multiple assignments to the same signal exist within a single process evaluation, and the intended safety-critical or primary assignment is overridden by a later unconditional assignment; the fix is restructuring to a single conditional assignment); signal vs variable misuse diagnosis (identifying where a signal is used as an intermediate computation register where a variable is needed, and subsequent statements in the same process read the old value rather than the just-computed value; the fix is declaring a process variable and using := immediate assignment); sensitivity list completeness auditing (identifying combinational processes missing signals from the sensitivity list, causing simulation to diverge from synthesis; the fix is adding the missing signals or converting to process(all) in VHDL-2008); uninitialized signal debugging (identifying where ‘U’ values propagate through the design at simulation start, preventing functional simulation until a reset sequence is added to the testbench); and timing constraint development (writing XDC or SDC timing constraints that correctly define all clock domains, I/O timing, and multicycle paths for the Vivado or Quartus timing analyzer).
What VHDL work is most commonly underlogged?
Last-assignment-wins signal override bugs are the most systematically underlogged VHDL retainer work. The pattern: a developer adds a safety or error-handling branch to an existing process that already has a signal assignment after the new if-else block; the new assignment inside the if-branch schedules a transaction that is silently overridden by the existing post-if-else assignment; the signal receives the wrong value; no simulation tool warns about multiple assignments to the same signal because the behavior is well-defined by IEEE 1076. The bug survives code review because the if-branch looks correct. It is only detected by a simulation trace that observes the signal value at the end of the delta cycle, confirming that the safety-critical assignment was overridden. The 3.5-hour investigation — confirming the overheat flag was asserted, tracing the signal assignment through the process to identify the override, understanding the transaction scheduling model — produces no visible artifact: one restructuring of a two-part assignment to a single conditional.
What are typical VHDL developer retainer rates?
Entry-level VHDL developers with experience in basic digital design, RTL coding in VHDL, and testbench simulation typically bill at $85 to $155 per hour. Mid-level VHDL engineers with experience in FPGA implementation, timing closure, VHDL-2008 features, and IP core integration typically bill at $130 to $225 per hour. Senior VHDL architects with deep knowledge of VHDL simulation model semantics, advanced testbench methodology (OSVVM, UVVM), timing analysis, and ASIC-grade synthesis flows typically bill at $180 to $340 per hour. Monthly retainer ranges: $2,000 to $3,500 per month for advisory engagements covering VHDL code review, simulation methodology, and timing constraint development (15 to 25 hours per month); $3,000 to $7,500 per month for active maintenance including RTL bug fixes, simulation vs synthesis divergence investigations, and FPGA implementation support.
What should a VHDL developer retainer agreement include?
A VHDL developer retainer agreement should specify: VHDL standard version (IEEE 1076-1993, 1076-2002, 1076-2008, or 1076-2019; VHDL-2008 is the most widely supported modern version in synthesis tools and simulators); target FPGA or ASIC technology (Xilinx Zynq-7000, UltraScale+, Intel Arria 10, or ASIC standard-cell library; technology constraints affect synthesis optimization targets, IP core choices, and timing constraint methodology); simulator (GHDL, ModelSim/Questa, Riviera-PRO, nvc; simulation tool determines which VHDL-2008/2019 features are available and how simulation seeds are managed); synthesis tool and version (Vivado, Quartus Prime, Synopsys Design Compiler; version matters because synthesis inference rules for processes, FSMs, and memory change between releases); timing constraint scope (whether the retainer covers writing or auditing XDC/SDC constraints, multicycle path declarations, and false path declarations for the timing analyzer); and testbench methodology (whether the retainer covers OSVVM, UVVM, or custom verification IP development, and what simulation coverage metrics are required).
How should VHDL developer retainer hours be logged?
Log each VHDL retainer session with: for last-assignment-wins signal override bugs, the process name (e.g., pwm_control_proc), the signal name (pwm_duty_out), the number of assignments and their ordering (conditional safety assignment at x“20” followed by unconditional computed_duty assignment), the VHDL mechanism (last transaction applied per IEEE 1076 §14.7.3), the symptom (overheat flag asserted but full duty cycle output observed), the fix (restructure to single conditional assignment), and the event count before and after (overheating events: 3 → 0; 3.5h). For signal vs variable misuse: the process name, the signal used as intermediate register, the statement that read the old value, the fix (process variable with :=), and the wrong value count. For sensitivity list incompleteness: the process name, the missing signal, the simulation symptom (stale output when signal changed), the synthesis behavior (combinational logic reads the signal), the fix (add to sensitivity list or process(all)), and the wrong output count.