Electrical – How to design Gray code synchronous counters of large widths using SystemVerilog

counterdigital-logicrtlsequential-logicsystem-verilog

I want to design a synchronous gray code counter which is 10 bits wide in SystemVerilog. The counter should have an Active HIGH synchronous reset.

I know how to design a 3 bit gray code counter like this:

module gray_code3(input logic reset, clk, output logic [2:0]q); 
always_ff @(posedge clk) begin
if (reset)
 q <= 0;
else
  case (q)
   3'b000:  q <= 3'b001;
   3'b001:  q <= 3'b011;
   3'b011:  q <= 3'b010;
   3'b010:  q <= 3'b110;
   3'b110:  q <= 3'b111;
   3'b111:  q <= 3'b101;
   3'b101:  q <= 3'b100;
   3'b100:  q <= 3'b000;
   default: q <= 3'bx;
  endcase
end
endmodule    

Using the same technique would be very tedious for gray code counter of large widths. How to design gray code counters of large widths?

Best Answer

The design is partitioned into 2 parts - one for combinational logic and another for sequential logic.

  1. In the sequential logic part, an always_ff block is used. Counter is an internal signal used to store the values and it gets incremented on the positive edge of the clock. Counter is a binary counter, and it has a modulus of 2 raised to the power of its width.

  2. In the combinational logic part, an always_comb block is used, where the binary code to gray code conversion is done. This is done by observing two facts:

    1. The Most Significant Bit is the same as the Most Significant Bit of the binary counter. This assignment can be done using an assign statement.

    2. For other bits, they are the result of the Ex-Or operation on the corresponding bit of the binary counter and the bit to the immediate right of it. Hence a foreach loop is suitable to iterate over all the bits and perform the Ex-Or operation over the respective bits of the binary counter.

Design:

module gray_ctr
  #(parameter WIDTH=4)
  (input logic clk,                
    input logic reset,
    output logic [WIDTH-1:0] q);    
   
  logic [WIDTH-1:0] counter;
  
  always_ff @ (posedge clk) begin
    if (reset) begin
      counter <= 0;
    end  
    else begin
      counter <= counter + 1;    
    end  
  end
   
  always_comb begin
  q[WIDTH-1] = counter[WIDTH-1];
  foreach (counter[i]) 
    q[i-1] = counter[i]^counter[i-1]; 
  end  
  
endmodule

Testbench:

module tb;
  parameter WIDTH = 4;
  
  reg clk;
  reg rstn;
  wire [WIDTH-1:0] out;
  
  gray_ctr #(WIDTH) u0 (.clk (clk),
                .reset (rstn),
                .q (out));
  
  always #10 clk = ~clk;
  
  initial begin
    {clk, rstn} = 0;
    $dumpfile("dump.vcd");
    $dumpvars(1,tb);                
    $monitor ("T=%0t out=%b", $time, out);
    #50 rstn = 1;
    #50 rstn = 0;
    #50 rstn = 1;
    #50 rstn = 0;
    #800 
    $finish;
  end
  
endmodule