This project closes timing on a 32-bit adder in two ways: first with layout repair alone, then by pipelining the adder into two stages. It shows where repair stops, what pipelining buys and what it costs, and two traps found on the way: a repair that aims for exactly zero slack on an estimate, and a Verilog width mistake that leaves only a small trace in the netlist. All numbers come from the lab's flow.

8 min read | Intermediate

What You'll Learn

  • Find the clock limit of a design from a sweep of Place and Route runs
  • Read a carry chain in the critical path
  • Use a setup margin so repair leaves room for routing
  • Pipeline a datapath and measure the gain and the cost

The One-Stage Adder


module add32 (
  input             clk,
  input      [31:0] a,
  input      [31:0] b,
  output reg [32:0] s
);
  reg [31:0] ra, rb;

  always @(posedge clk) begin
    ra <= a;
    rb <= b;
    s  <= ra + rb;
  end
endmodule

Inputs and output are registered, so the whole addition sits between two flip-flop stages: 97 flip-flops (32 + 32 + 33) and 2,902 um2 of cells after synthesis. Run through the lab's flow at a sweep of clock periods:

Clock periodWorst setup slackTotal negative slack
3 ns-3.21 ns-49.4 ns
4 ns-2.19 ns-27.6 ns
5 ns-1.19 ns-9.4 ns
6 ns-0.23 ns-0.7 ns
7 ns-0.01 ns-0.01 ns

Slack improves by about 1 ns per nanosecond of clock period, as it should, until 6 ns. Then it improves by only 0.22 ns, and 7 ns still misses, by 6 picoseconds. A design that needs 6.23 ns at 6 ns should have most of a nanosecond to spare at 7 ns, so something other than the logic is involved.

Trap 1: Repairing to Exactly Zero

The repair table in each log explains it:

6 ns:  WNS -1.975 before repair  ->  -0.145 after repair  (32 cells resized, 18 buffers)
7 ns:  WNS -0.975 before repair  ->  +0.010 after repair  (12 cells resized, 8 buffers)

At 6 ns the repair ran out of useful moves at -0.145 ns. At 7 ns it stopped as soon as its own estimate reached zero slack. That estimate is made before routing, from the placement and the lab's wire model; after routing, the real wires cost a little more, and the final slack lands just below zero. At 7 ns the repair simply had less to do, so it did less.

The fix is to ask for margin. repair_timing -setup -setup_margin 0.3 keeps repairing until the estimate shows 0.3 ns of slack:

One-stage adderNo marginSetup margin 0.3 ns
6 ns-0.23 ns-0.24 ns
7 ns-0.01 ns+0.28 ns

At 7 ns the margin closes the design with room to spare. At 6 ns it does not: the repair runs out of useful moves before it reaches the margin, and the path is simply too long.

A second way to leave room is to repair again after global routing, when the estimate already sees the planned wires. With the lab's PRO option for it, the one-stage adder met 7 ns with +0.31 ns, without any margin.

The Carry Chain

The worst path at 7 ns, after the margin, shows why:

   0.346 ^ _303_/CLK (sky130_fd_sc_hd__dfxtp_1)
   0.677 v _303_/Q (sky130_fd_sc_hd__dfxtp_1)
   0.783 ^ _145_/Y (sky130_fd_sc_hd__nand2_1)
   0.920 v _198_/Y (sky130_fd_sc_hd__o21ai_1)
   1.276 v _200_/X (sky130_fd_sc_hd__maj3_2)
   1.631 v _202_/X (sky130_fd_sc_hd__maj3_2)
   ...                                         (five more maj3 cells)
   3.827 v _214_/X (sky130_fd_sc_hd__maj3_2)
   4.049 ^ _216_/Y (sky130_fd_sc_hd__a211oi_2)
   ...                                         (ten AOI/OAI cells, a maj3 and five buffers)
   6.716 v _241_/Y (sky130_fd_sc_hd__nor2_1)
   6.971 ^ _253_/Y (sky130_fd_sc_hd__xnor2_1)
   6.971 ^ _298_/D (sky130_fd_sc_hd__dfxtp_1)
           7.253   data required time
           0.281   slack (MET)

27 cells after the flip-flop, five of them buffers that repair inserted. The maj3 cells are the carry function of a full adder, and the carry ripples through them one bit at a time at about 0.35 ns per bit; a chain of AOI and OAI gates then carries it through the upper bits. Repair can make each of these cells a little faster, but it cannot shorten the chain.

Quick Check

Q: At 7 ns the repair step reported +0.010 ns, but the routed design missed by 0.006 ns. Why, and why did the margin help so much?

Show Answer

The repair stopped as soon as its pre-routing estimate showed zero slack, after upsizing only 12 cells. After routing, the real wires cost a few more picoseconds, so the result ended just below zero. With a setup margin, the repair keeps going until the estimate shows 0.3 ns, which leaves room to lose; here it upsized 17 cells and the routed result kept +0.28 ns.

Pipelining

A pipeline register in the middle of the carry chain halves the chain. The low 16 bits are added in the first stage, with their carry stored; the high 16 bits, delayed by one cycle, are added in the second stage with that carry:


module add32p (
  input             clk,
  input      [31:0] a,
  input      [31:0] b,
  output reg [32:0] s
);
  reg [31:0] ra, rb;
  reg [16:0] lo;          // stage 1: low half and its carry
  reg [15:0] ha, hb;      // stage 1: high halves, delayed one cycle

  always @(posedge clk) begin
    ra <= a;
    rb <= b;
    lo <= ra[15:0] + rb[15:0];
    ha <= ra[31:16];
    hb <= rb[31:16];
    s  <= {{1'b0, ha} + hb + lo[16], lo[15:0]};   // stage 2: high half, 17 bits wide
  end
endmodule

Trap 2: A width mistake that hides in the netlist

The first version of the second stage was s <= {ha + hb + lo[16], lo[15:0]};. It synthesizes without a message. Inside a concatenation, each part is sized by itself: ha + hb + lo[16] is 16 bits wide, so the carry out of the high half was dropped, the concatenation was only 32 bits, and s[32] was tied to a constant 0. In a flow without tie cells that constant is a net without a driver, and detailed routing stops with DRT-0305 Net zero_ of signal type GROUND is not routable, as in Project: Place and Route a UART Transmitter. The lab's synthesis maps constants to tie cells, so there the run completes, even faster than the correct design (+0.37 ns at 5 ns, because the top carry logic is gone), and the only trace is one sky130_fd_sc_hd__conb_1 tie cell driving s[32]. Widening the high half to 17 bits with {1'b0, ha} fixes the logic and removes the constant. A simulation that checked the sum against a + b would have caught it first.

Results

Pipelined adderNo marginSetup margin 0.3 ns
3 ns-0.75 ns-
4 ns-0.06 ns+0.28 ns
5 ns-0.01 ns+0.30 ns

Without a margin, the pipelined adder misses even 5 ns, by 0.01 ns: there the repair found no violation in its estimate and changed nothing, and routing cost the last few picoseconds. With the margin it closes at 5 ns and at 4 ns; with a repair after global routing instead, it met 5 ns with +0.31 ns.

What Pipelining Cost

One stage, 7 nsTwo stages, 4 ns
Clock frequency143 MHz250 MHz
Flip-flops (clock sinks)97146
Clock buffers917
Design area3,331 um25,086 um2
Latency1 cycle (register to register)2 cycles

Pipelining raised the clock frequency by 75 % for 53 % more area, 49 more flip-flops and twice the clock buffers, and one more cycle of latency. The extra flip-flops also add clock power on every cycle. Whether that is a good trade depends on the system: throughput-bound datapaths pipeline readily, control loops that need a result in the next cycle often cannot.

Both adders also had antenna violations after global routing, 18 nets in the one-stage adder and 17 in the pipelined one. The lab's flow fixed all of them with antenna diodes and a few jumpers, as explained in The Antenna Effect and Diode Insertion, and the final check reports none.

Quick Check

Q: The pipelined adder at 4 ns has two cycles of latency, 8 ns in total; the one-stage adder at 7 ns has one cycle, 7 ns. Is the pipelined adder slower?

Show Answer

For a single addition, yes: the result appears after 8 ns instead of 7 ns. But it accepts a new pair of operands every 4 ns instead of every 7 ns, so it completes 75 % more additions per second. Pipelining trades latency for throughput.

Running It in ChipVerify Lab

Open the one-stage example, synthesize it, and run Place and Route at 5, 6 and 7 ns; find the repair table and the worst path in each log. Then open the pipelined example and run it at 4 and 5 ns. With PRO, add -setup_margin 0.3 to the repair_timing -setup line of the script and rerun the 7 ns, 5 ns and 4 ns cases. The pipelined example is pd-add32p; the one-stage example is linked below.

Common Mistakes

Mistake #1: Repairing to zero slack before routing

Routing and the propagated clock always cost some slack. Repair with a margin, or the routed design lands just below zero.

Mistake #2: Expecting layout to fix a long carry chain

Repair made the one-stage adder close at 7 ns, not at 6 ns. A shorter chain, by pipelining or a faster adder structure, is the fix.

Mistake #3: Losing bits in concatenations

Each part of a concatenation is sized by itself. Make the carry explicit, check the result in simulation, and watch for constants in the netlist.

Practice Exercise

Challenge: A Three-Stage Adder

The one-stage carry chain had eight maj3 cells at about 0.35 ns each before the AOI/OAI section, and the whole path took about 6.3 ns of logic. If the adder were split into three stages of 11, 11 and 10 bits, estimate the logic delay per stage and the clock period it might reach. What would it cost in flip-flops compared with the two-stage version?

Hint

Assume the logic delay scales roughly with the number of bits per stage, and count the registers each stage boundary needs.

Solution

About 6.3 x 11 / 32 = 2.2 ns of logic per stage, so with clock-to-Q, setup and margin roughly 3 ns. The two-stage version adds 49 flip-flops at its boundary (17 for the low sum and carry, 32 for the delayed high halves). A three-stage version needs 12 + 42 = 54 after the first stage (11 sum bits, the carry and the 21 unfinished bits of each operand) and 23 + 20 = 43 after the second (22 finished sum bits, the carry and 10 bits of each operand): 97 extra flip-flops, 48 more than with two stages. Each extra stage gives less gain, because clock-to-Q, setup and margin do not shrink.

Open the ready-made example in the lab, synthesize it and run Place and Route.

Summary

  • The one-stage 32-bit adder missed 6 ns and closed at 7 ns only with a setup margin; its worst path is a carry chain of 27 cells.
  • Without a margin, repair stopped at zero slack on its estimate, and routing pushed the result just below zero.
  • Pipelining into two 16-bit stages closed 4 ns with a margin: 75 % higher frequency for 53 % more area, 49 more flip-flops and one more cycle of latency.
  • A concatenation width mistake dropped the carry and left a constant output, visible only as a tie cell in the netlist.
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5
Cron Job Starts