2

I am attempting to write and test a simple 16-bit width RAM8 chip in Verilog using Icarus Verilog. I'm finding it difficult to understand conceptually why the iverilog simulator is showing me 'x' (undefined) values on certain clock ticks and wondering if anybody can provide a conceptual understanding for this problem.

I've tried two different designs, one of which the output makes sense to me, but I can't parse the output of the second.

The first design makes out a register, and assignment to the output is clocked:

module RAM8(out, address, in, load, clk);
   output[15:0] out;
   input [15:0] in;
   input [2:0]  address;
   input        load, clk;

   reg          out;            // out is a register
   reg [15:0]   ram [7:0];      // 8-element array of 16-bit wide reg

   always @(posedge clk) begin
      if (load)
        ram[address] <= in;
      out <= ram[address];      // clocked assignment
   end
endmodule // RAM8

Whereas the second design, out is a continuously assigned wire:

module RAM8(out, address, in, load, clk);
   output[15:0] out;
   input [15:0] in;
   input [2:0]  address;
   input        load, clk;

   reg [15:0]   ram [7:0];      // 8-element array of 16-bit wide reg

   always @(posedge clk) begin
      if (load)
        ram[address] <= in;
   end
   assign out = ram[address];      // continuous assignment
endmodule // RAM8

The test bench for both of these is the same:

module RAM8_tb();
   wire [15:0] out;
   reg [15:0]  in;
   reg [2:0]   address;
   reg         load;
   reg         clk;

   RAM8 DUT (
         .out(out),
         .in(in),
         .address(address),
         .load(load),
         .clk(clk)
         );

   initial begin
      clk = 0;
      load = 0;
      address = 0;
      in = 0;

      #10 load = 1; address = 0; in = 0;
      #10 load = 0; address = 0;
      #10 load = 1; address = 1; in = 1;
      #10 load = 0; address = 1;
      #10 load = 1; address = 2; in = 2;
      #10 load = 0; address = 2;
      #10 load = 1; address = 3; in = 3;
      #10 load = 0; address = 3;
      #10 load = 1; address = 4; in = 4;
      #10 load = 0; address = 4;
      #10 load = 1; address = 5; in = 5;
   end // initial begin

   always #5 clk = ~clk;

   initial #150 $stop;

   initial
     $monitor("At time %t, clk = %0d, load = %0d, address = %0d, in = %0d, out = %0d",
              $time, clk, load, address, in, out);
endmodule // RAM8_tb

My output running the test bench for the first design is this:

At time                    0, clk = 0, load = 0, address = 0, in = 0, out = x
At time                    5, clk = 1, load = 0, address = 0, in = 0, out = x
At time                   10, clk = 0, load = 1, address = 0, in = 0, out = x
At time                   15, clk = 1, load = 1, address = 0, in = 0, out = x
At time                   20, clk = 0, load = 0, address = 0, in = 0, out = x
At time                   25, clk = 1, load = 0, address = 0, in = 0, out = 0
At time                   30, clk = 0, load = 1, address = 1, in = 1, out = 0
At time                   35, clk = 1, load = 1, address = 1, in = 1, out = x
At time                   40, clk = 0, load = 0, address = 1, in = 1, out = x
At time                   45, clk = 1, load = 0, address = 1, in = 1, out = 1
At time                   50, clk = 0, load = 1, address = 2, in = 2, out = 1
At time                   55, clk = 1, load = 1, address = 2, in = 2, out = x
At time                   60, clk = 0, load = 0, address = 2, in = 2, out = x
At time                   65, clk = 1, load = 0, address = 2, in = 2, out = 2
At time                   70, clk = 0, load = 1, address = 3, in = 3, out = 2
At time                   75, clk = 1, load = 1, address = 3, in = 3, out = x
At time                   80, clk = 0, load = 0, address = 3, in = 3, out = x
At time                   85, clk = 1, load = 0, address = 3, in = 3, out = 3
At time                   90, clk = 0, load = 1, address = 4, in = 4, out = 3
At time                   95, clk = 1, load = 1, address = 4, in = 4, out = x
At time                  100, clk = 0, load = 0, address = 4, in = 4, out = x
At time                  105, clk = 1, load = 0, address = 4, in = 4, out = 4
At time                  110, clk = 0, load = 1, address = 5, in = 5, out = 4
At time                  115, clk = 1, load = 1, address = 5, in = 5, out = x
At time                  120, clk = 0, load = 1, address = 5, in = 5, out = x
At time                  125, clk = 1, load = 1, address = 5, in = 5, out = 5
At time                  130, clk = 0, load = 1, address = 5, in = 5, out = 5
At time                  135, clk = 1, load = 1, address = 5, in = 5, out = 5
At time                  140, clk = 0, load = 1, address = 5, in = 5, out = 5
At time                  145, clk = 1, load = 1, address = 5, in = 5, out = 5

This output makes sense to me. Each time I'm loading a new value, the output is undefined because the loading is happening concurrently to the value that's being loaded being clocked into the output register (hence, garbage values for that clock tick).

However the output for the test bench for the second design is confusing to me:

At time                    0, clk = 0, load = 0, address = 0, in = 0, out = x
At time                    5, clk = 1, load = 0, address = 0, in = 0, out = x
At time                   10, clk = 0, load = 1, address = 0, in = 0, out = x
At time                   15, clk = 1, load = 1, address = 0, in = 0, out = 0
At time                   20, clk = 0, load = 0, address = 0, in = 0, out = 0
At time                   25, clk = 1, load = 0, address = 0, in = 0, out = 0
At time                   30, clk = 0, load = 1, address = 1, in = 1, out = x
At time                   35, clk = 1, load = 1, address = 1, in = 1, out = 1
At time                   40, clk = 0, load = 0, address = 1, in = 1, out = 1
At time                   45, clk = 1, load = 0, address = 1, in = 1, out = 1
At time                   50, clk = 0, load = 1, address = 2, in = 2, out = x
At time                   55, clk = 1, load = 1, address = 2, in = 2, out = 2
At time                   60, clk = 0, load = 0, address = 2, in = 2, out = 2
At time                   65, clk = 1, load = 0, address = 2, in = 2, out = 2
At time                   70, clk = 0, load = 1, address = 3, in = 3, out = x
At time                   75, clk = 1, load = 1, address = 3, in = 3, out = 3
At time                   80, clk = 0, load = 0, address = 3, in = 3, out = 3
At time                   85, clk = 1, load = 0, address = 3, in = 3, out = 3
At time                   90, clk = 0, load = 1, address = 4, in = 4, out = x
At time                   95, clk = 1, load = 1, address = 4, in = 4, out = 4
At time                  100, clk = 0, load = 0, address = 4, in = 4, out = 4
At time                  105, clk = 1, load = 0, address = 4, in = 4, out = 4
At time                  110, clk = 0, load = 1, address = 5, in = 5, out = x
At time                  115, clk = 1, load = 1, address = 5, in = 5, out = 5
At time                  120, clk = 0, load = 1, address = 5, in = 5, out = 5
At time                  125, clk = 1, load = 1, address = 5, in = 5, out = 5
At time                  130, clk = 0, load = 1, address = 5, in = 5, out = 5
At time                  135, clk = 1, load = 1, address = 5, in = 5, out = 5
At time                  140, clk = 0, load = 1, address = 5, in = 5, out = 5
At time                  145, clk = 1, load = 1, address = 5, in = 5, out = 5

My question here is: what are the periodic undefined values at the output caused by? The commonality appears to be when the clk is 0 and the load is 1, but nothing I can recall from my understanding of registers explains why that would cause the output to be garbage. All the registers in my design are triggered on the positive clock edge, so why would a negative edge be changing any value?

I figure I may be confused generally about what's going on under the hood here, and would also appreciate somebody confirming or refuting my explanation for the behavior of the first design as well. Thanks in advance to anybody who takes the time to read and answer.

  • I'd suggest you don't rely on text output to understand what is happening in your design. Waveforms make more sense when debugging hardware. It will help you understand Justin's (correct) point. – rascob Dec 30 '18 at 22:16
  • @rascob I understood his point, however I'm curious if I can use any free or cheap waveform tools along with iverilog? I'm currently limited by RAM on my 2013 MacBook and can't run Vivado. – Isaiah Becker-Mayer Jan 03 '19 at 02:53
  • If you *have to* use iverilog, you can try gtkwave. If not, you can try a free version of Mentor's Modelsim. Altera provides a "Web" edition for free: https://www.intel.com/content/www/us/en/programmable/downloads/download-center.html – rascob Jan 03 '19 at 12:52

1 Answers1

3

In both cases, the X values coming out are are the initial values of ram (which are X since you didn't assign any initial values). If you continued the test and cycled through the same addresses, the next time around you'd see the values you previously wrote.

In the first example, the concurrent read and write isn't an issue. The read is returning the value from before the clock edge, and ram will contain the new value after the clock edge.

In the second example, the event triggering the X is address incrementing, not the clk and load signals. Since out isn't registered and isn't associated with clk, as soon as address changes you see the value at that address appear on out. Then after the clock edge that writes a new value, you see out change to that value at the same time.

Justin N
  • 780
  • 4
  • 7