[Fwd: Bottom testing loop in Verilog HDL]

From: Anders Nordstrom (Anders.Nordstrom.andersn@nt.com)
Date: Mon Oct 26 1998 - 10:28:31 PST


Content-type: message/rfc822
Content-transfer-encoding: 7bit
Content-Disposition: inline
Date: Mon, 26 Oct 1998 18:28:51 +0000

Received: from zcars00t.ca.nortel.com by zcard00n.ca.nortel.com
          with SMTP (Microsoft Exchange Internet Mail Service Version 5.0.1460.8)
          id V3BGH963; Mon, 26 Oct 1998 00:00:32 -0500
Received: from smtpott2.nortel.ca (actually zcars00w) by zcars00t;
          Sun, 25 Oct 1998 23:55:51 -0500
Received: from roper.uwyo.edu by smtpott2; Sun, 25 Oct 1998 23:58:06 -0500
Received: from asuwlink.uwyo.edu by ROPER.UWYO.EDU (PMDF V5.1-10 #27528)
          with ESMTP id <0F1F00H9M4F8NJ@ROPER.UWYO.EDU>
          for Anders.Nordstrom.andersn@nt.com;
          Sun, 25 Oct 1998 21:57:08 -0700 (MST)
Received: (from marnold@localhost) by asuwlink.uwyo.edu (8.8.8/8.8.7)
          id VAA21216 for Anders.Nordstrom.andersn@nt.com;
          Sun, 25 Oct 1998 21:57:07 -0700 (MST)
Date: Sun, 25 Oct 1998 21:57:07 -0700 (MST)
From: Mark G Arnold <marnold@UWYO.EDU>
Subject: Re: Bottom testing loop in Verilog HDL
To: "Anders Nordstrom" <Anders.Nordstrom.andersn@nt.com>
Message-id: <199810260457.VAA21216@asuwlink.uwyo.edu>

Thank you for the opportunity to describe the usage of my proposed
bottom testing loop construct in Verilog input/output and testbenches.
To illustrate, I have appended four Verilog files at the end of this
ASCII message:

   A. Reading LF terminated strings from a file
   B. Interactive I/O with data validation
   C. Testbench for a machine with a data request signal
   D. Testbench with transport delay and protocol conversion
   
There are many possible syntaxes for the proposed bottom testing loop.
The one on the right is what I used for my IVC '98 paper and in my proposal to
the behavioral task force, but the one on the left can be simulated with macros
in the current version of Verilog for the purpose of this message:

             `dowhile ( <cond> ) or repeat
                `begin begin
                   <body> <body>
                 end end
                                            while ( <cond> );

<p>The above differ only by whether the condition is located textually above or below
the <body>. Although the semantics of the two are intended to be identical,
I feel the original proposal (repeat ... while) is superior:
   a) It is intuitive (like the corresponding do..while in C/C++/Java)
       because <cond> is listed at the bottom, exactly at the point where it
       would be tested the first time.
   b) It adds no new reserved words to the language.
   
Other choices exist, such as Pascal's repeat ... until which negates
<cond>, but the repeat ... while syntax seems the best match to its usage.
The purpose of this message is not to advocate any particular syntax, but
rather to show the need for the bottom testing semantics in Verilog.

There are four ways to emulate a bottom testing loop in the current
Verilog standard:
                  
    1. duplication of code
              <body>
              while ( <cond> )
                begin
                  <body>
                end
                            
    2. flag variable (used by the `dowhile and `begin macros)
              reg f;
              ...
              f=1;
              while (f| ( <cond> ))
                begin
                  f=0;
                  <body>
                end
                
    3. disable inside forever
              begin : <label>
                forever
                  begin
                    <body>
                    if (~cond) disable <label>;
                  end
              end
     
    4. Ad hoc code to force <cond> to be true prior to loop
              <code to make condition true>
              while (<cond>)
                begin
                  <body>
                end

All of these techniques have drawbacks:

1. Duplication of code becomes unwieldy. For example, naive duplication
makes the code for reading terminated strings from a file twice as long as
in the bottom test version given at the end of this message (Example A):
         
      line = -1;
      line = line+1;
      length[line] = -1;
      length[line] = length[line]+1;
      text_file.getc(string[{line,length[line]}]);
      while (string[{line,length[line]}] != `END_OF_LINE
                    & string[{line,length[line]}] != `END_OF_FILE)
                begin
                  length[line] = length[line]+1;
                  text_file.getc(string[{line,length[line]}]);
                end
      while (string[{line,length[line]}]!=`END_OF_FILE)
          begin
            line = line+1;
            length[line] = -1;
            length[line] = length[line]+1;
            text_file.getc(string[{line,length[line]}]);
            while (string[{line,length[line]}] != `END_OF_LINE
                    & string[{line,length[line]}] != `END_OF_FILE)
                begin
                  length[line] = length[line]+1;
                  text_file.getc(string[{line,length[line]}]);
                end
          end

   Admittedly, this code could be simplified (for example, line=0; instead
   of the first two lines of code), but the problem remains that nested bottom
   testing loops unwind to fairly complicated duplication of while loops.
   It takes more while loop constructs to accomplish the same operation
   that can be accomplished using fewer bottom testing loop constructs.
   
2. The flag variable approach requires declaration of the flag. Also, <cond>
   is at the top, which is unnatural for a bottom testing loop. (The
   reason <cond> is at the top of the loop for the `dowhile and `begin macros
   is that this technique is the only one suitable for macros.)

3. The forever/disable approach is the most natural (<cond> at bottom, no duplication
   or ad hoc code), but it may have undesired semantics when time or event related
   code (such as non-blocking assignment, @, #, or ->) occur in <body>. This prevents
   forever/disable from being used in many testbench situations. For example, the
   correct output from the transport delay testbench (Example D) should be:

     before $time= 0 data=xx lastbyte=x
     bottom $time= 50 data=01 lastbyte=0
            $time= 50 data=01
     bottom $time= 150 data=02 lastbyte=0
            $time= 150 data=02
     bottom $time= 250 data=03 lastbyte=1
     after $time= 250 data=03 lastbyte=1
            $time= 250 data=03
            $time= 350 data=ff
     received terminator
 
   If one attempts to use the forever/disable approach:

       begin : lab
        forever
          begin
           wait (strobe_producer===0);
           strobe_consumer <= #`TRANSPORT_DELAY 0;
           wait (strobe_producer===1);
           strobe_consumer <= #`TRANSPORT_DELAY 1;
           data_consumer <= #`TRANSPORT_DELAY data_producer;
           $strobe("bottom $time=%d data=%x lastbyte=%b",$time,data_consumer,lastbyte);
           if (lastbyte) disable lab;
          end
       end
      
   many simulators will interpret that the disable should undo the non-blocking
   assignment, and on such simulators, this code will malfunction:
   
     before $time= 0 data=xx lastbyte=x
     bottom $time= 50 data=01 lastbyte=0
            $time= 50 data=01
     bottom $time= 150 data=02 lastbyte=0
            $time= 150 data=02
     bottom $time= 250 data=02 lastbyte=1
     after $time= 250 data=02 lastbyte=1
            $time= 350 data=ff
     received terminator

   Rather than transmitting the correct message (01 02 03 ff), the forever/disable
   testbench transmits an incorrect message (01 02 02 ff).

4. Ad hoc code is specific to the application, and is more likely to develop bugs if
   supposedly unrelated code is changed. Often, it is not possible to use the ad
   hoc approach due to indexing or time problems. For instance, the testbench
   in example C is required to work with a machine that sends a request signal,
   which is not valid at the time of entry into the loop. The testbench designer does
   not have the authority to alter the nature of the signal provided by the
   machine_under_test, and thus he is forced to ignore the value (1'bx) of
   request upon entry to the loop. No ad hoc code is possible because the code
   that generates request is inside the machine_under_test.
   
The main point here is not to describe the limitations of the current Verilog
standard any further, but to illustrate why the bottom testing loop is sufficiently
useful for I/O and testbenches to be included in the new standard. Here are
four examples where the bottom testing loop is useful:

A. Reading LF terminated strings from a file

        This type of problem arises in two contexts: When a Verilog simulation
        is reading text data from the host computer's physical disk, or when
        one codesigns hardware and software, and the testbench emulates the
        I/O transactions of the software. Since the current version of Verilog
        is limited in its file input capabilities, example A is more like
        simulated I/O for codesign, but the getc method of the file module
        could just as easily be using the system tasks for Verilog I/O that
        will be added to the new standard.
        
        The pseudo-code for this approach is:
 
             `dowhile (character is not a terminator)
               `begin
                  ...
                  text_file.getc(character);
                end
        
        The bottom testing loop is natural for this application because
        the character variable is not defined before the first call
        to getc, but that call should naturally be inside the loop.
        
        This example is further complicated by several issues:
             1. The file has both `END_OF_LINE and `END_OF_FILE terminators
             2. Nested bottom testing loops are needed (the inner
                one for processing characters within a line, and the outer
                one for processing each line.)
             3. The characters are to be put in an array, thus the value
                of the index at the point where the condition evaluates is critical.
                It is invalid to evaluate the condition prior to the first
                iteration because the index will not be meaningful.
             4. The length of each line is to be recorded in a second array.
               
   B. Interactive I/O with data validation

         This could occur in the two contexts of example A:
         new system tasks (or PLI calls) for interactive
         I/O during a simulation, or hardware/software codesign. Example
         B simulates I/O with a getcecho routine.
         
         Example B performs at least one iteration of some desired computation.
         The user is queried whether he wishes another iteration
         to be performed. The valid answers are "y" and "n". Any other
         response causes the user to be prompted again. The iterations continue
         until the user enters "n".

         As with example A, it is not known whether the loop should continue
         until after the first call to the task, thus the test of the result of
         that call should occur at the bottom of the loop.

   C. Testbench for a machine with a data request signal

         This testbench supplies vectors (in the vect array) at the $times indicated
         by the delay array. The machine_under_test (which cannot be changed by the testbench
         designer) causes request to be true when it wants an additional byte
         of data. In this particular example the machine_under_test chooses to
         ask for another byte based on whether the data sent from the testbench
         is a terminator byte. Since that byte is not defined by the testbench
         until entry into the loop, request is 1'bx at the time the loop is entered.
         Only after the testbench has sent the first data byte does the machine
         output a well-defined request signal (1 or 0).

         The following illustrates how the bottom testing loop
         reacts properly when the terminator(8'h67) is present in
         the test vectors:

           before $time= 0 data=xx request=x
                  $time= 0 request=x
                  $time= 0 data=xx
                  $time= 10 data=12
                  $time= 12 request=1
           bottom $time= 13 data=12 request=1
                  $time= 33 data=34
           bottom $time= 36 data=34 request=1
                  $time= 46 data=56
           bottom $time= 49 data=56 request=1
                  $time= 59 data=67
                  $time= 61 request=0
           bottom $time= 62 data=67 request=0
           after $time= 62 data=67 request=0
               
         The following illustrates how using a while loop by itself
         causes the testbench to malfunction:

           before $time= 0 data=xx request=x
           after $time= 0 data=xx request=x
                  $time= 0 request=x
                  $time= 0 data=xx

         
   D. Testbench with transport delay and protocol conversion

         This example has two modules being tested by the testbench: the
         producer and the consumer. The testbench needs to model
         two things: the transport delay of sending data from the
         producer to the consumer, and the protocol difference between
         what the producer creates and what the consumer demands.

         The protocol that the producer uses in this example is a separate
         lastbyte signal to indicate the termination of the message.
         On the other hand, the consumer wants a terminator byte of
         8'hff.
          
         This testbench receives data from the producer and introduces
         transport delay using non-blocking assignment.
         Also, the testbench translates from the lastbyte protocol
         signal to the delimiter protocol.
 
 
In summary, there are many applications that could benefit from a bottom testing
construct to make the coding of loops in Verilog more convenient and concise.
Although all of my example applications have a common similarity (initiating
an action inside a loop and then using the reaction to decide whether to continue),
the fact that the applications come from such a variety of contexts argues that many
designers will benefit from the inclusion of a bottom testing construct in
the new Verilog standard.

--Mark Arnold

Verilog code examples

//========Example A. Reading LF terminated strings from a file======================

// Example to show how a bottom testing loop is useful for reading
// terminated strings from files

<p>// Macro to emulate bottom testing loop

`define dowhile f=1; while (f||(
`define begin )) begin f=0;

<p><p>// Unix-like terminators:

`define END_OF_LINE 10
`define END_OF_FILE 3

<p>// Module to emulate a text file

module file;
  integer p;
  reg [7:0] a[100:0];

  initial
    begin
      p=0;
      a[0]= "f";
      a[1]= "i";
      a[2]= "r";
      a[3]= "s";
      a[4]= "t";
      a[5]= " ";
      a[6]= "l";
      a[7]= "i";
      a[8]= "n";
      a[9]= "e";
      a[10]=`END_OF_LINE;
      a[11]=`END_OF_LINE;
      a[12]="s";
      a[13]="e";
      a[14]="c";
      a[15]="o";
      a[16]="n";
      a[17]="d";
      a[18]=" ";
      a[19]="n";
      a[20]="o";
      a[21]="n";
      a[22]=" ";
      a[23]="e";
      a[24]="m";
      a[25]="p";
      a[26]="t";
      a[27]="y";
      a[28]=" ";
      a[29]="l";
      a[30]="i";
      a[31]="n";
      a[32]="e";
      a[33]=`END_OF_FILE;
    end
    
  task getc;
    output ch;
    reg [7:0] ch;

    begin
      ch = a[p];
      p = p+1;
      if (ch==`END_OF_FILE)
        p = 0;
    end
  endtask
endmodule

<p>// The following reads lines delimited by `END_OF_LINE from an emulated text
// file into the string array. The file itself is delimited by `END_OF_FILE,
// which also acts like end of line. The string array is arranged in row major
// order as 8 lines of at most 32 characters each. The actual number of characters
// on each line is given by the length array. Because of the number of bits
// declared in variables such as line and col, string[{line,col}] emulates 2D
// access to the character at the given coordinate.
//
// The bottom test loops in the following are useful because without the
// bottom test construct, the looping conditions would evaluate to 'bx
// prior to the first iteration. This would happen because string is
// uninitialized and line is set to an inappropriate value (-1) for indexing

<p>module test;
  file text_file(); //emulate text file
  
  reg [2:0] k,line; // line number in range 0..7
  reg [4:0] col; // column position in range 0..31
  reg [7:0] string[255:0]; // 8 lines of 32 ASCII (8 bit) chars each
  reg [4:0] length[7:0]; // length of string on each line in range 0..31
  
  reg f; //for bottom test loop

  initial
    begin
      line = -1;
      `dowhile (string[{line,length[line]}]!=`END_OF_FILE)
         `begin
            line = line+1;
            length[line] = -1;
            `dowhile (string[{line,length[line]}] != `END_OF_LINE
                    & string[{line,length[line]}] != `END_OF_FILE)
               `begin
                  length[line] = length[line]+1;
                  text_file.getc(string[{line,length[line]}]);
                end
          end

      for (k=0; k<=line; k=k+1)
        begin
          $write("%d ",length[k]);
          for (col=0; col<length[k]; col=col+1)
            $write("%c",string[{k,col}]);
          $display(" ");
        end
    end
endmodule

<p>//========Example B. Interactive I/O with data validation===========================

// Example to show use of bottom testing loop in interactive I/O

// Macros to emulate a bottom testing loop

`define dowhile f=1; while (f||(
`define begin )) begin f=0;

<p><p>// Module to emulate interactive input

module file;
  integer p;
  reg [7:0] a[100:0];

  initial
    begin
      p=0;
      a[0]="u"; //invalid
      a[1]="i"; //invalid
      a[2]="y"; //yes, do a second
      a[3]="y"; //yes, do a third
      a[4]="o"; //invalid
      a[5]="y"; //yes, do a fourth
      a[6]="9"; //invalid
      a[7]="n"; //no, stop the process
    end

  //method to get char w/o echo
  task getc;
    output ch;
    reg [7:0] ch;

    begin
      ch = a[p];
      p = p+1;
      if (ch==0)
        p = 0;
    end
  endtask

<p> //method to get char with echo (as is typ. in interactive dialog)
  task getcecho;
    output ch;
    reg [7:0] ch;
    
    begin
      getc(ch);
      $display("%c",ch);
    end
  endtask

endmodule

<p>// module that performs some iterative process (in this case, simply
// incrementing i) interactively, i.e., the user (simulated by the
// console instance of the file module) determines how many iterations
// occur by the answer to the "Do you want to continue" questions.

module interactive;
  reg [7:0] continue;
  file console();
  reg f;
  integer i;
  
  initial
    begin
      i = 1;
      `dowhile ( continue == "y" )
         `begin
            $display("iteration %d",i);
            i = i + 1;
            `dowhile ( (continue!="y")&(continue!="n") )
              `begin
                 $write("Do you want to continue (y/n)? ");
                 console.getcecho(continue);
               end
         end
    end
endmodule

<p>//========Example C. Testbench for a machine with a data request signal=============

// Example to illustrate need for bottom testing loop
// in Verilog for testbenchs

// For the purposes of this limited example,
// the bottom testing loop can be emulated by
// the following two macros:

`define dowhile f=1; while (f||(
`define begin )) begin f=0;

<p>// The following is the delay for the machine_under_test
// to respond with the next data request

`define RESPOND_DELAY 3

<p>// The following is the maximum number of test vectors
// to be used

`define MAX_VECT 5

// This module emulates a machine to be tested.
// It consumes data until a terminator byte (not
// known to the testbench, but which is the constant 8'h67
// in this example) is received by the machine under test.
// To signal whether the machine desires an additional
// byte, the machine outputs the request signal.
// In the fabricated hardware, the request signal would
// be generated by a comparison of 8'h67 against
// the data bus. Thus request is not valid before the
// testbench drives the data bus with the first vector.

module machine_under_test(request,data);
  output request;
  input data;
  wire request;
  wire [7:0] data;
  assign #2 request=data!=8'h67;
  always @(request)
    $strobe(" $time=%d request=%b",$time,request);
  always @(data)
    $strobe(" $time=%d data=%h",$time,data);
endmodule

<p>// The testbench drives the data bus with new test
// vectors from the vect array at the times indicated by
// the delay array. The testbench is then to wait for
// an additional `RESPOND_DELAY before testing whether
// machine_under_test requests another byte
// The testbench must stop when the machine under
// test no longer requests data, even if not all of
// the vectors in vect have been consumed.
  
module testbench;
  reg [7:0] vect[0:`MAX_VECT-1];
  integer delay[0:`MAX_VECT-1];
  reg [7:0] data;
  reg f; //for bottom test
  integer i;
  
  machine_under_test m(request, data);
  
  initial
    begin
      vect[0]=8'h12; delay[0]=10;
      vect[1]=8'h34; delay[1]=20;
      vect[2]=8'h56; delay[2]=10;
      vect[3]=8'h68; delay[3]=10;
      vect[4]=8'h89; delay[4]=30;
      
      i = 0;
      $strobe("before $time=%d data=%h request=%b",$time,data,request);
       
      `dowhile (request & i<`MAX_VECT)
        `begin
          data <= #(delay[i]) vect[i];
          #(delay[i]+`RESPOND_DELAY);
          i = i + 1;
          $strobe("bottom $time=%d data=%x request=%b",$time,data,request);
         end

      $strobe("after $time=%d data=%h request=%b",$time,data,request);
    end
endmodule

<p>//========Example D. Testbench with transport delay and protocol conversion=========

// The following is the delay for
// a non-blocking assignment that models a delay line
// Using zero will illustrate the problems with the
// forever/disable implementation of the bottom testing loop
// most easily, but other values for the delay suffer
// similar problems on many simulators

`define TRANSPORT_DELAY 0

<p>// This module emulates a machine that consumes data
// It expects a terminator byte of 8'hff

module consumer(strobe,data);
  input strobe,data;
  wire [7:0] data;
  always @(posedge strobe)
    $strobe(" $time=%d data=%h",$time,data);
  initial
    begin
      wait(data==8'hff);
      #1 $display("received terminator");
    end
endmodule

<p>// This module emulates a machine that produces data.
// Valid data is indicated by the rising edge of the strobe.
// The final byte of the packet is indicated by lastbyte.
// For the purpose of this example, the message is simply
// the three bytes 1 2 and 3 repeated over and over:
// _ _ ___ ___ ___ ___ ___ ___ ___
// dataout /x\/0\/ 1 \/ 2 \/ 3 \/ 1 \/ 2 \/ 3 \/ 1 \ ...
// \_/\_/\___/\___/\___/\___/\___/\___/\___/
// __ ___ ___
// lastbyte x |_____________| |__________| |_____ ...
// __ _ _ _ _ _ _ _
// strobe x |___| |__| |__| |__| |__| |__| |__| |__ ...
// 0 25 50 150 250 350 450 550 650
//
// Note that the output of the machine is not defined
// during the first 25 units of $time. dataout has
// an irrelevant value of 0 prior to the first strobe
// to simplify this module.
              
module producer(strobe,dataout,lastbyte);
  output strobe,dataout,lastbyte;
  reg strobe;
  reg [7:0] dataout;

  assign lastbyte = dataout == 3;
  
  initial #25
    begin
      strobe <= 0;
      dataout <= 0;
    end
    
  always #50
    begin
      if (~lastbyte)
        dataout <= dataout + 1;
      else
        dataout <= 1;
      strobe <= 1;
      #50 strobe <= 0;
    end
endmodule
        
//
// This testbench receives data from the producer and introduces
// transport delay using non-blocking assignment.
// Also, the testbench translates from the separate lastbyte
// signal to a delimiter byte of 8'hff. This testbench is supposed to
// transmit only the first message. Later repetitions of the same
// message will be ignored.
//
// +--------+ strobe_producer +-------+ strobe_consumer +--------+
// | |----------------->| test- |----------------->| |
// |producer| data_producer | bench | data_consumer |consumer|
// | |--------------/-->| |--------------/-->| |
// | | lastbyte 8 | | 8 +--------+
// | |----------------->| |
// +--------+ +-------+
//
// undelayed delayed
// packet delimited by packet delimited by 8'hff
// separate lastbyte signal
//
// The bottom test loop is necessary because the lastbyte signal
// is not defined at $time 0
// __ ____ ____
// lastbyte x |_____________| |_________| |____ ...
//
// _ _ ___ ___ ___ ___ ___ ___ ___
// data_producer /x\/0\/ 1 \/ 2 \/ 3 \/ 1 \/ 2 \/ 3 \/ 1 \ ...
// \_/\_/\___/\___/\___/\___/\___/\___/\___/
// __ _ _ _ _ _ _ _
// strobe_producer x |___| |__| |__| |__| |__| |__| |__| |__ ...
//
// ______ ___ ___ ___ _________________
// data_consumer / x \/ 1 \/ 2 \/ 3 \/ ff
// \______/\___/\___/\___/\_________________
// ____ _ _ _ _
// strobe_consumer x |___| |__| |__| |__| |_______________
//

              
module testbench;
  reg f; //for bottom test loop

  reg [7:0] data_consumer;
  reg strobe_consumer;
  wire [7:0] data_producer;
  wire strobe_producer;
  wire lastbyte;
  
  producer p(strobe_producer, data_producer,lastbyte);
  consumer c(strobe_consumer, data_consumer);

  initial
    begin
      $strobe("before $time=%d data=%h lastbyte=%b",$time,data_consumer,lastbyte);
      
      `dowhile (~lastbyte)
        `begin
          wait (strobe_producer===0);
          strobe_consumer <= #`TRANSPORT_DELAY 0;
          wait (strobe_producer===1);
          strobe_consumer <= #`TRANSPORT_DELAY 1;
          data_consumer <= #`TRANSPORT_DELAY data_producer;
          $strobe("bottom $time=%d data=%x lastbyte=%b",$time,data_consumer,lastbyte);
         end
/*
      begin : lab
       forever
         begin
          wait (strobe_producer===0);
          strobe_consumer <= #`TRANSPORT_DELAY 0;
          wait (strobe_producer===1);
          strobe_consumer <= #`TRANSPORT_DELAY 1;
          data_consumer <= #`TRANSPORT_DELAY data_producer;
          $strobe("bottom $time=%d data=%x lastbyte=%b",$time,data_consumer,lastbyte);
          if (lastbyte) disable lab;
         end
      end
*/
      $strobe("after $time=%d data=%h lastbyte=%b",$time,data_consumer,lastbyte);
      
      #50 strobe_consumer <= #`TRANSPORT_DELAY 0;
      #50 strobe_consumer <= #`TRANSPORT_DELAY 1;
          data_consumer <= #`TRANSPORT_DELAY 8'hff;
      #50 strobe_consumer <= #`TRANSPORT_DELAY 0;
      $finish;
    end
endmodule



This archive was generated by hypermail 2.1.4 : Mon Jul 08 2002 - 12:53:00 PDT and
sponsored by Boyd Technology, Inc.