Module 7: Advanced RTL Design Techniques ยท 7.6 Low-Power Design Techniques

Operand Isolation

The last two articles dealt with blocks that are idle for a while. This one deals with a block that is active, powered and clocked, and still wasting power.

Consider a multiplier that is fully powered, whose clock is not gated, and whose result is not used this cycle, perhaps because a mux downstream is about to pick a different unit’s output. Nothing has told the multiplier to stop. Its inputs are still toggling every cycle, and every toggle ripples switching activity through its whole internal logic. That is real dynamic power spent on a result nobody asked for.

The waste, concretely

Picture an ALU with an adder and a multiplier in parallel, feeding a mux that selects one result by opcode. On a cycle where the opcode is add, the multiplier’s result is discarded by the mux. But if the multiplier’s inputs, the same a and b feeding the adder, are still changing every cycle regardless of which operation was selected, the multiplier’s internal logic is still switching and still burning power computing a product nobody looks at.

Adder and multiplier sharing operands; the unselected unit still switches
Figure 1: Where the wasted switching happens

Operand isolation removes this waste, not by powering the multiplier down (that is the heavy machinery from the previous article) and not by stopping its clock (this multiplier may be purely combinational with no clock to gate), but by freezing its inputs so nothing inside switches on cycles where the output is not going to be used.

The technique: hold the operands

`timescale 1ns / 1ps
`default_nettype none

module alu_with_isolation #(parameter WIDTH = 16) (
    input  wire [WIDTH-1:0]   a, b,
    input  wire                op_is_multiply,   // 1 when the multiplier's result will actually be used
    output wire [WIDTH-1:0]    add_result,
    output wire [2*WIDTH-1:0]  mul_result,
    output wire [2*WIDTH-1:0]  final_result
);
    // Isolated operands: held at their previous value whenever the multiplier's
    // result is not going to be selected, so its internal logic does not switch.
    reg [WIDTH-1:0] a_isolated, b_isolated;

    always @(*) begin
        if (op_is_multiply) begin
            a_isolated = a;
            b_isolated = b;
        end
        // else: a_isolated and b_isolated hold their previous value.
        // This is an intentional level-sensitive latch, used deliberately here
        // to freeze switching activity.
    end

    assign add_result = a + b;                       // adder still sees live operands
    assign mul_result = a_isolated * b_isolated;      // multiplier only switches when its result matters
    assign final_result = op_is_multiply ? mul_result : {{WIDTH{1'b0}}, add_result};
endmodule

This is one of the very few places in the course where an inferred latch is the correct, deliberate choice rather than the bug Section 5.2 spent an article teaching you to remove. The latch is not an accident of an incomplete sensitivity list; it is the mechanism. Hold the multiplier’s inputs steady whenever its result is not wanted, and the combinational logic downstream has nothing to react to.

A common alternative, and the one synthesis tools usually insert when they do this automatically, is to gate the operands to zero with AND gates instead of holding them: a & {WIDTH{op_is_multiply}}. That avoids a latch on a combinationally derived enable, at the cost of one toggle to zero and back each time the unit is selected. Both count as operand isolation; which is better depends on how often the selection changes.

Why not isolate everything?

Operand isolation is not free. It costs the isolation latches or gates themselves (area, and a little switching power whenever they do update), plus some delay in the path feeding the isolated inputs. For a small, cheap unit the overhead can exceed the saving; isolating a 2-input AND gate is very unlikely to be worth the latch in front of it.

The technique earns its cost on large, switching-heavy blocks: wide multipliers, big adder trees, anything with deep combinational logic where one toggling input can ripple through many gates. A synthesis tool with power optimisation enabled will often apply isolation on its own to blocks it judges large enough, based on power intent hints or its own heuristics. Understanding the technique yourself is what lets you apply it deliberately to a bottleneck a power analysis tool has flagged, rather than hoping the tool finds every case.

Operand isolation and clock gating solve the same underlying problem, wasted switching on data nobody needs this cycle, on different kinds of logic. Clock gating stops registers updating when their result will not be used. Operand isolation stops combinational logic switching internally when its result will not be used, which matters for large purely combinational blocks that clock gating cannot reach, since there is no register or clock in the picture.

Practice Exercise

A design has a 64-bit barrel shifter whose output is discarded by a mux on about 90 percent of cycles, based on a control signal known one cycle in advance. Explain why this is a strong candidate for operand isolation, referring to both the size of the block and how often its result goes unused.

Solution

Two factors point the same way. A 64-bit barrel shifter is a large piece of combinational logic (from Module 2, $\log_2(64) = 6$ stages of muxes), so a toggling input ripples through a meaningful amount of internal logic, unlike a small gate where the isolation overhead would dominate. And with the result discarded 90 percent of the time, most of that switching is spent on results nobody reads, exactly the waste operand isolation removes. Because the selection signal is known a cycle ahead, there is no timing obstacle to placing the isolation cleanly in front of the shifter’s inputs. Both “is the block big enough” and “is its result wasted often enough” come out yes.

Operand isolation freezes a combinational block’s inputs, with a deliberate latch or a set of AND gates, on cycles where its output is known to be unused, which stops internal switching that neither clock gating nor power gating can reach. It costs area and a little delay, so it is applied selectively to large, switching-heavy blocks whose results are discarded often. In the next article, the last in the module, we put clock gating, power gating and operand isolation together into the activity-aware view that decides which one belongs where.

โ† Power Gating Activity-Aware Design โ†’