Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
    • STA Foundations
    • Setup, Hold and Slack
    • Skew and Variation
    • Timing Exceptions
      • Taxonomy and Intent
      • Multicycle Setup and Hold Pairing
      • RTL Pattern That Often Needs MCP
      • False Paths and Case Analysis
    • Clock Gating
    • RTL for Timing
    • Clock Architecture
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Timing Exceptions
  1. Topics
  2. RTL Design
  3. Timing & Clocking
  4. Timing Exceptions
  5. RTL Pattern That Often Needs MCP

Part 5 · Timing & Clocking · Intermediate

RTL Pattern That Often Needs MCP

A three-cycle multiplier with stable operands, its constraints, and its stability checks.

On this page

    Why this matters

    A multiplier that misses 200 MHz can be given three cycles instead of a pipeline, but only if the operands cannot change while it works. Reload the operand registers every cycle and the stability check fails with 2000 errors.

    After this lesson you can: build a multicycle datapath whose operands hold still and whose result captures exactly N cycles later, constrain it, and turn the promise into an assertion.

    A structural promise

    The multicycle exception is only safe if the RTL cannot behave otherwise. The pattern has three parts:

    1. Operand registers change only when a new operation is accepted and hold still afterwards.

    2. A small counter or state says when the result register captures: exactly N cycles after the operands changed.

    3. The result register has a clock enable (or the FSM gates it) so it samples only on that cycle.

    Constraints and checks

    This RTL justifies a 3-cycle setup with a matching hold. The table shows what the testbench checks.

    Diagram
    # a_r, b_r change only on 'start'; y captures on the 3rd cycle after that.
    set_multicycle_path 3 -setup -from [get_cells {a_r* b_r*}] -to [get_cells y*]
    set_multicycle_path 2 -hold  -from [get_cells {a_r* b_r*}] -to [get_cells y*]

    Property

    Checked by

    Operands are stable while busy

    dut.a_r/dut.b_r snapshotted at the first busy cycle, then compared on later busy cycles

    Result captured exactly 3 periods after acceptance

    E0 accepts; E1/E2 count; E3 captures and asserts done after NBA. Four inclusive observations, three elapsed periods

    Result correct

    Compared with a * b for 500 random operand pairs

    Mutation check: load operand registers every cycle instead of only on accepted start. The stability checker rejects that change while external inputs change. This is the promise a multicycle constraint would otherwise hide.

    Each slot is the cycle that follows a clock edge. Start is high in the first slot, so the edge that ends it is E0; the edges that end the next three slots are E1, E2 and E3. The inputs a and b are changed right after E0 in this simulation, to show that the registered operands inside the multiplier do not follow them. E4 is the earliest edge at which a new start could be accepted. The bench observes at falling edges after NBA; cycles=4 counts E0..E3 inclusively.

    Read the picture from start down to y: the control lanes explain when y is allowed to change.

    • busy rises after E0 and falls after E3, so the operation owns exactly three clock periods.

    • cnt counts 0, 1, 2 while busy. It reaches 3 after E3 only because a 2-bit counter keeps counting; busy is already low, so it is ignored.

    • y holds its old value (0 in this run) until the slot after E3, then shows the product. It never changes in between, so the multiplier path has all three periods to settle.

    • done is high for one slot only, right after the capture.

    Common mistakes

    Tip

    Turn the stability check into an assertion that lives in the RTL repository: assert property (@(posedge clk) disable iff (!rst_n) (busy && $past(busy)) |-> $stable(a_r) && $stable(b_r));. Then any later change that breaks the promise fails in regression instead of in silicon. $stable compares each sampled value with the previous sample, so at the first busy cycle it would still see the legitimate operand load from the acceptance edge. Gating with $past(busy) therefore checks stability only across consecutive busy cycles, once the operands have settled.

    Common pitfall

    Do not constrain y as a multicycle destination if other logic also writes it or reads its input path in one cycle. The exception applies to the whole path from the named start points to the named end points, regardless of which cycle uses it.

    Interview

    Typical question: “Your multiplier doesn't meet 200 MHz. What do you do?” Option A: pipeline it. Option B: if the surrounding control can wait, make it multi-cycle with a stable-operand guarantee and constrain it accordingly. State the trade-off: B saves registers and keeps latency in cycles; A raises throughput.

    Check yourself Why must the operand registers be separate from the module inputs a and b?

    The inputs may change at any time. The path being given three cycles starts at registers; if it started at the inputs, the constraint would have to cover the outside world, which the block cannot control. The registers freeze the data for the whole window.

    Testbench

    The multiplier plus a finite testbench for operand stability, latency, and results.

    mc_mult.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    `timescale 1ns/1ps
    module mc_mult (
      input  logic        clk, rst_n,
      input  logic        start,
      input  logic [15:0] a, b,
      output logic        busy, done,
      output logic [31:0] y
    );
      logic [15:0] a_r, b_r;          // operand registers: written ONLY when a new operation starts
      logic [1:0]  cnt;
    
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) begin busy <= 1'b0; done <= 1'b0; cnt <= '0; end
        else begin
          done <= 1'b0;
          if (!busy) begin
            if (start) begin busy <= 1'b1; cnt <= '0; end
          end else begin
            cnt <= cnt + 1'b1;
            if (cnt == 2'd2) begin busy <= 1'b0; done <= 1'b1; end   // result is captured on the 3rd cycle
          end
        end
    
      always_ff @(posedge clk) begin
        if (!busy && start) begin a_r <= a; b_r <= b; end          // operands change only here
        if (busy && cnt == 2'd2) y <= a_r * b_r;                    // the product path may take 3 clock periods
      end
    endmodule
    

    Remember

    • Operand registers change only on an accepted start. A counter or FSM says when the result captures, and a clock enable makes it sample only then.

    • The constraint starts at the operand registers, never at the module inputs, because the registers freeze the data for the whole window.

    • Do not use y as a multicycle end point if other logic also writes it or reads its input path in one cycle.

    Rule of thumb: pipeline for throughput; use a multicycle path only when the control can wait and the operands are stable.

    How this was checked

    Mutation check, run with Icarus Verilog 13.0 (2026-10-16): loading the operand registers every cycle makes the stability checker fail with 2000 errors.

    Multiplier bench, run with Icarus Verilog 13.0 (2026-10-16): prints PASS (500 products). The ideal multiply models functionality, not an implementation delay or an STA result.

    Keep going

    • Code lab: Multi-cycle divider step

    • Interview: run a mock interview on this topic

    Related topics

    • False paths and case analysis

    Previous

    134 / 350

    Next: False Paths and Case Analysis

    Up next

    Up nextFalse Paths and Case Analysis

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Taxonomy and Intent
    clkstartbusycnt0123doney02a
    One operation of the multiplier with a = 6 and b = 7: start is accepted at E0, cnt counts 0, 1, 2, and the product 0x2a (42) is captured at E3 with done high one cycle later