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
    • Flip-Flops and Registers
    • Counters and Timers
      • Counting as Controlled State Evolution
      • Up/Down Counter with Reload
      • A Prescaler Feeding a Timer Tick
      • Watchdog Timer Example
      • A Gray-Code Counter
      • Mini Project: a Programmable Timer
    • Shift Registers and LFSRs
    • Pipelining
    • Stalls, Bubbles and Hazards
    • Retiming
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Counters and Timers
  1. Topics
  2. RTL Design
  3. Sequential Design
  4. Counters and Timers
  5. A Gray-Code Counter

Part 3 · Sequential · Intermediate

A Gray-Code Counter

A counter whose output changes one bit per step: conversion both ways, two RTL styles, the register rule for crossings, and why most moduli break it.

On this page

    Why this matters

    A binary count going from 7 to 8 changes four bits at once, so a reader in another clock domain can catch any of 16 values. A Gray count changes one bit per step and halves the toggles, but only if it is built from flops and wraps on a suitable modulus.

    After this lesson you can: convert between binary and Gray by hand, write a Gray counter in either RTL style, and say which moduli keep the one-bit property at the wrap.

    One bit per step

    A 4-bit binary counter going from 7 to 8 changes all four bits at once: 0111 becomes 1000. If another clock domain samples the counter during that change, each bit can be caught old or new, so the reader can see any of 16 values. A Gray counter visits the same 16 values in a different order, chosen so that every step, including the wrap, changes exactly one bit. A reader that samples mid-change then sees either the old value or the new one, never a third.

    The same property saves switching. Over one full lap the 4-bit binary count flips 30 bits; the Gray count flips 16, one per step. The bench at the end of this page measures both.

    Read each slot as one clock cycle, and look for which of the four bottom lanes moved since the slot before: there is always exactly one.

    • Slots 2 to 3: binary goes 7 to 8 and all four binary bits change; of the Gray bits only g3 moves (it rises).

    • Slot 7: binary goes b to c, three binary bits change; only g2 falls.

    • Slot 11: the wrap from f to 0 clears four binary bits; only g3 falls.

    • g0 changes on every second step and g3 only twice in the trace: the higher the bit, the less often it moves.

    Converting between binary and Gray

    Binary to Gray is one row of XOR gates: each Gray bit is the binary bit XOR the binary bit above it, and the top bit is copied.

    g=b⊕(b≫1)

    Gray to binary runs the other way, from the top bit down. Each binary bit is the Gray bit XOR the binary bit just computed above it: a prefix XOR, so bit 0 depends on every Gray bit.

    bW−1​=gW−1​,bi​=bi+1​⊕gi​

    Worked example: decode Gray 1101. b3 = g3 = 1. b2 = b3 XOR g2 = 1 XOR 1 = 0. b1 = b2 XOR g1 = 0 XOR 0 = 0. b0 = b1 XOR g0 = 0 XOR 1 = 1. The binary value is 1001, which is 9. Check by encoding: 1001 XOR 0100 = 1101.

    The 3-bit table shows the whole cycle. Bold marks the one Gray bit that changed from the row above (row 0 is reached from row 7 by the wrap).

    Count

    Binary

    Binary bits changed from the row above

    Gray

    0

    000

    3

    000

    1

    001

    1

    001

    2

    010

    2

    011

    3

    011

    1

    010

    4

    100

    3

    110

    5

    101

    1

    111

    6

    110

    2

    101

    7

    111

    1

    100

    The binary column changes 14 bits per lap and the Gray column 8. In general an N-bit binary counter flips 2 x (2ᴺ − 1) bits per lap and a Gray counter 2ᴺ, so Gray roughly halves the toggles.

    Two ways to build it

    Both styles below keep the Gray value in flops. They differ in what else they store.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    // snippet: style A keeps a binary register and registers the Gray code of the NEXT count
    assign bin_next = bin + en;
    always_ff @(posedge clk or negedge rst_n)
      if (!rst_n) begin bin <= '0; gray <= '0; end
      else begin
        bin  <= bin_next;
        gray <= bin_next ^ (bin_next >> 1);   // convert before the flop, not after
      end
    design.sv
    1
    2
    3
    4
    5
    6
    // snippet: style B keeps only the Gray register: decode, add one, encode
    assign bin_now  = gray2bin(gray);         // prefix XOR, W - 1 gates deep
    assign bin_next = bin_now + en;
    always_ff @(posedge clk or negedge rst_n)
      if (!rst_n) gray <= '0;
      else        gray <= bin_next ^ (bin_next >> 1);

    Style A: binary plus Gray

    Style B: Gray only

    Flops

    2W

    W

    Path into the flops

    Adder, then one XOR

    XOR chain, adder, one XOR: longer

    Local binary value

    Free, from the bin register

    Must be decoded again

    Toggles per lap

    Binary and Gray both switch

    Gray only: the full saving

    Typical use

    FIFO pointers, where the binary value addresses memory

    Small counters where area or switching matter

    Style A converts bin_next, not bin. Converting bin would make the Gray register lag the binary one by a cycle, a classic source of off-by-one flags in FIFOs.

    The Gray value must come straight from a flop

    The one-bit property holds for the stored values, not for the gates that compute them. The tempting shortcut is to keep only a binary register and decode it with a continuous assignment:

    design.sv
    // bad-example: combinational Gray decode in front of a crossing
    always_ff @(posedge clk) bin <= bin + 1'b1;
    assign gray_out = bin ^ (bin >> 1);        // feeds a synchroniser in another domain

    On the 7 to 8 step all four binary bits change, each arriving at the XOR gates at a slightly different time. While they settle, gray_out can briefly pass through values that differ from both the old and new code in several bits, and a receiving flop can capture one of them. Put the XOR before the flop, as both styles do, and send nothing but flop outputs across. The bus synchroniser lesson shows this failure in simulation.

    A Gray crossing still needs each bit synchronised and the bits kept within a bounded skew of each other. How an asynchronous FIFO uses two Gray pointers to compute full and empty is the subject of Pointers and Gray conversion.

    Common mistakes

    Common pitfall

    A modulus that is not a power of two breaks the wrap. A mod-10 counter that runs 0 to 9 and then returns to 0 changes three Gray bits on the wrap (1101 to 0000), so a reader can catch a value that was never counted. The cure is to count a different range: for an even modulus M in W bits, use the M codes centred in the 2ᵂ cycle, from 2ᵂ⁻¹ − M/2 to 2ᵂ⁻¹ + M/2 − 1. For M = 10 that is 3 to 12, and the wrap from 12 back to 3 changes one bit (1010 to 0010). An odd modulus cannot work at all: each step flips one bit, so the number of ones alternates between odd and even, and an odd number of steps can never return to the starting code.

    The bench below runs both mod-10 counts through the same encoder. Its output:

    Diagram
    naive  mod-10: 9 -> 0   gray 1101 -> 0000   bits changed: 3
    offset mod-10: 12 -> 3   gray 1010 -> 0010   bits changed: 1
    PASS: naive mod-10 wrap changes 3 bits; offset 3..12 changes 1 bit on every step
    design.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
    29
    30
    31
    32
    33
    module tb_gray_mod10;
      function automatic logic [3:0] to_gray(input logic [3:0] b);
        return b ^ (b >> 1);
      endfunction
      function automatic int ones(input logic [3:0] x);   // bits set in x
        int k = 0;
        for (int i = 0; i < 4; i++) k += x[i];
        return k;
      endfunction
      logic [3:0] now_g, next_g;
      int next_n, flips, worst_naive = 0, worst_offset = 0;
      initial begin
        // naive mod 10: count 0..9, then wrap to 0
        for (int n = 0; n <= 9; n++) begin
          next_n = (n == 9) ? 0 : n + 1;
          now_g = to_gray(n[3:0]); next_g = to_gray(next_n[3:0]);
          flips = ones(now_g ^ next_g);
          if (flips > worst_naive) worst_naive = flips;
          if (flips != 1) $display("naive  mod-10: %0d -> %0d   gray %b -> %b   bits changed: %0d", n, next_n, now_g, next_g, flips);
        end
        // offset mod 10: count 3..12, the ten codes centred in the 16-code cycle
        for (int n = 3; n <= 12; n++) begin
          next_n = (n == 12) ? 3 : n + 1;
          now_g = to_gray(n[3:0]); next_g = to_gray(next_n[3:0]);
          flips = ones(now_g ^ next_g);
          if (flips > worst_offset) worst_offset = flips;
          if (n == 12) $display("offset mod-10: %0d -> %0d   gray %b -> %b   bits changed: %0d", n, next_n, now_g, next_g, flips);
        end
        if (worst_naive != 3 || worst_offset != 1) $fatal(1, "FAIL: worst naive=%0d offset=%0d", worst_naive, worst_offset);
        $display("PASS: naive mod-10 wrap changes %0d bits; offset 3..12 changes 1 bit on every step", worst_naive);
        $finish;
      end
    endmodule

    The same rule covers any counter that jumps: a reload, a skip or a count by two changes several Gray bits in one step. If the value can jump, cross it with a handshake instead.

    Check yourself Decode the Gray value 0110 to binary, step by step.

    b3 = 0. b2 = 0 XOR 1 = 1. b1 = 1 XOR 1 = 0. b0 = 0 XOR 0 = 0. Binary 0100, which is 4. Check: 0100 XOR 0010 = 0110.

    Check yourself A 3-bit Gray counter must count modulo 6 and cross a clock domain. Which six counts should it use, and what are the Gray codes at the wrap?

    Centre six codes in the 8-code cycle: from 4 − 3 = 1 to 4 + 3 − 1 = 6. The wrap goes from 6 (Gray 101) back to 1 (Gray 001), which changes only the top bit. Counting 0 to 5 instead would wrap from Gray 111 to 000, three bits at once.

    Testbench

    Both styles side by side for two full laps of a 4-bit count. Every step checks that exactly one Gray bit changed, that decoding the Gray value in the bench gives the expected count, and that the two styles agree; holding en low must freeze the value. The bench also counts toggles.

    gray_ctr_a.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
    29
    30
    31
    32
    33
    34
    35
    36
    // Style A: count in binary, register the Gray code of the NEXT binary value.
    module gray_ctr_a #(parameter int W = 4) (
      input  logic         clk, rst_n, en,
      output logic [W-1:0] bin,              // binary count, for local use
      output logic [W-1:0] gray              // a flop output: safe to send across a clock domain
    );
      logic [W-1:0] bin_next;
      assign bin_next = bin + {{(W-1){1'b0}}, en};
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) begin
          bin  <= '0;
          gray <= '0;
        end else begin
          bin  <= bin_next;
          gray <= bin_next ^ (bin_next >> 1); // convert before the flop, not after
        end
    endmodule
    
    // Style B: keep only the Gray register; decode, add one, encode again.
    module gray_ctr_b #(parameter int W = 4) (
      input  logic         clk, rst_n, en,
      output logic [W-1:0] gray
    );
      function automatic logic [W-1:0] gray2bin(input logic [W-1:0] g);
        logic [W-1:0] b;                      // prefix XOR from the top bit down
        b[W-1] = g[W-1];
        for (int i = W - 2; i >= 0; i--) b[i] = b[i+1] ^ g[i];
        return b;
      endfunction
      logic [W-1:0] bin_now, bin_next;
      assign bin_now  = gray2bin(gray);
      assign bin_next = bin_now + {{(W-1){1'b0}}, en};
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) gray <= '0;
        else        gray <= bin_next ^ (bin_next >> 1);
    endmodule
    
    Diagram
    toggles over 32 steps: binary 60, Gray 32
    PASS: both styles agree, one Gray bit per step across 32 steps and 2 wraps, round trip exact

    Sixty binary toggles over two laps is 30 per lap, against 16 for Gray. In style A both registers switch, so that saving only appears on the wires that carry the Gray bits; style B keeps it everywhere.

    Remember

    • Binary to Gray is g = b XOR (b >> 1). Gray to binary is a prefix XOR from the top bit down.

    • Register the Gray value: convert the next binary count before the flop, and send only flop outputs across a crossing.

    • Only an even modulus can stay one-bit at the wrap, and only when it uses the codes centred in the cycle; an odd modulus never can.

    Rule of thumb: a Gray count is safe to cross only if it comes straight from flops and moves by exactly one step at a time.

    How this was checked

    Tool: Icarus Verilog 13.0, run on 2026-10-17. Commands: iverilog -g2012 -Wall -o sim.out gray.sv tb_gray.sv && vvp sim.out (the two counter styles and tb_gray_ctr shown on the page); iverilog -g2012 -Wall -o m.out tb_mod10.sv && vvp m.out (tb_gray_mod10).

    Result: tb_gray_ctr prints toggles over 32 steps: binary 60, Gray 32 and its PASS line; tb_gray_mod10 prints the naive 9 to 0 wrap changing 3 bits (1101 to 0000), the offset 12 to 3 wrap changing 1 bit (1010 to 0010), and PASS. Both are also run by run-rtl-testbenches.mjs.

    The waveform is generated by node scripts/tools/gen-waves.mjs sequential:counters-timers from wave-src/counters-timers/gray-count.sv (style A, W = 4, skip 5, 12 cycles) with expect strings checked; a second run left the generated file unchanged.

    The benches count toggles with a small ones() function: in this Icarus build $countones returned wrong values inside a loop for a one-bit difference, while Verilator 5.048 gave the right one.

    Scope and limits: zero-delay functional simulation. It checks values at clock edges, not the glitches of a combinational decode, synchroniser behaviour or bit-to-bit skew; toggle counts are register bit flips, not measured power.

    Keep going

    • Practice: Gray code counter for a CDC-friendly FIFO pointer

    • Practice: FSM Gray encoding for low power and CDC safety

    • Code lab: Gray code counter

    • Interview: run a mock interview on this topic

    Related topics

    • Pointers and Gray conversion in an async FIFO

    • Do not synchronise buses bit by bit

    Previous

    71 / 350

    Next: Mini Project: a Programmable Timer

    Up next

    Up nextMini Project: a Programmable Timer

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Counting as Controlled State Evolution
    • Up/Down Counter with Reload
    • A Prescaler Feeding a Timer Tick
    clkbin56789abcdef0g3g2g1g0
    Simulation of the style A Gray counter: the binary count in hex and the four Gray bits as separate lanes, from count 5 to the wrap at 0