// Verilog solution to bean counter DQ/MC exam question.

// Includes structural and behavioral solution (beancount) and test
// code (beantest).

`timescale 1ns/1ns

// Resolution of clock simulation.
`define clock_period 7

//`define BEHAVIORAL
`ifdef BEHAVIORAL


module beancount(count, pd1, pd2, clk, reset);
   input pd1;			// Photodetector 1.  Blocked when 1.
   input pd2;			// Photodetector 2.  Blocked when 1.
   input clk;			// Clock.
   input reset;                 // Reset.
   output count;		// The number of beans.
   
   reg [15:0] count;		// Number of beans observed.
   wire       pd1, pd2, clk, reset;

   // Time needed for a bean to move from pd1 to pd2, in cycles.
   reg [15:0] gap_time;
   // Counter  measuring pd1 to pd2 time.
   reg [15:0] gap_timer;

   // Number of cycles before middle of bean reached.
   reg [15:0] bean_timer;

   reg        last_pd1, last_pd2;  // Previous state of photodetector.

   // Update last state of pd1 and pd2
   // Updates: last_pd1, last_pd2.
   always @( posedge clk ) begin last_pd1 <= pd1; last_pd2 <= pd2; end
   
   // Increment bean time counter or reset when new bean cluster arrives.
   // Updates: gap_timer.
   always @( posedge clk )
     if( last_pd1 === 1'b0 && pd1 === 1'b1 )
       gap_timer <= 0;  // New clump, reset.
     else
       gap_timer <= gap_timer + 1;

   // Updates: count, bean_timer, gap_time.
   always @( posedge clk )
     if( reset )                
       begin                    // Reset.
          count <= 0;
          bean_timer <= 16'hffff;
          gap_time <= 16'hffff;
       end
     else if( pd2 === 1'b1 && last_pd2 === 1'b0 )
       begin                    // At pd2 clump arrives.
 	  gap_time <= gap_timer;  
 	  bean_timer <= {1'b0,gap_timer[15:1]};
       end
     else if( pd2 === 1'b1 && last_pd2 === 1'b1 && bean_timer === 0 )
       begin                    // At pd2, middle of bean.
          bean_timer <= gap_time;
	  count <= count + 1;
       end
     else if( pd2 === 1'b1 && last_pd2 === 1'b1 && bean_timer !== 0 )
       begin                    // At pd2, other clump location.
          bean_timer <= bean_timer - 1;
       end
 
endmodule // beancount

`else // !ifdef BEHAVIORAL

// Behavioral description of master-slave edge triggered register.
module DREG16(q,d,c);
   output      q;
   input       d,c;
   reg [15:0]  q, nextq;
   wire [15:0] d;
   wire        c;

   always @( posedge c ) nextq <= d;
   always @( negedge c ) q <= nextq;
endmodule // DREG16

// Behavioral description of master-slave edge triggered flip-flop.
module DREG1(q,d,c);
   output q;
   input  d,c;
   reg    q, nextq;
   wire   d;
   wire        c;
   always @( posedge c ) nextq <= d;
   always @( negedge c ) q <= nextq;
endmodule // DREG1


// Bean Counter Implicit Structural Description
module beancount(count, pd1, pd2, clk, reset);
   input pd1;			// Photodetector 1.  Blocked when 1.
   input pd2;			// Photodetector 2.  Blocked when 1.
   input clk;			// Clock.
   input reset;                 // Reset.
   output count;		// The number of beans observed.
   
   wire [15:0] count;		// Number of beans observed.
   wire        pd1, pd2, clk, reset;

   wire       last_pd1, last_pd2;
   DREG1 last_pd1_reg(last_pd1,pd1,clk);
   DREG1 last_pd2_reg(last_pd2,pd2,clk);

   wire       new_clump_pd1 = pd1 && ~last_pd1;
   wire       new_clump_pd2 = pd2 && ~last_pd2;

   wire [15:0] gap_timer;
   wire [15:0] next_gap_timer = new_clump_pd1 ? 0 : gap_timer + 1;

   DREG16 gap_timer_counter(gap_timer,next_gap_timer,clk);

   wire [15:0] gap_time;
   
   DREG16 gap_time_reg(gap_time,gap_timer,new_clump_pd2);

   wire [15:0] bean_time;
   wire [15:0] next_bean_time;
   wire [15:0] bean_time_m1 = bean_time - 1;

   assign next_bean_time = new_clump_pd2 ? {1'b0,gap_timer[15:1]} :
                           bean_time === 0  ? gap_time : bean_time_m1;
   
   DREG16 bean_time_reg(bean_time,next_bean_time,clk);

   wire [15:0] next_count = reset ? 0 :
               bean_time === 0 && last_pd2 ? count + 1 : count;
   
   DREG16 bean_count(count,next_count,clk);

endmodule // beancount


`endif // !ifdef BEHAVIORAL

`define TEST
`ifdef TEST

module beantest();

   integer beantime; // Time for bean to move from pd1 to pd2.
   integer beans, beans1;    // Number of beans simulated by testbench.
   reg 	   pd1, pd2, clk;
   wire [15:0] cnt;
   integer     errors;
   integer     last_error;      // Difference between executed and actual.
   reg         reset;

   // Instantiate bean counter.
   beancount bc(cnt,pd1,pd2,clk,reset);

   initial
     begin:INIT
        integer inter_clump;    // Time between beans.
        integer min_bean_time;  // Fastest bean speed.
        integer max_bean_time;  // Slowest bean speed.
        integer oldbeantime;

        // Initialize simulation variables.
	min_bean_time = 10 * `clock_period;  // Fastest bean speed.
	max_bean_time = 10 * min_bean_time;  // Slowest bean speed.
	beantime = min_bean_time;
        oldbeantime = 0;
        beans = 0;
        errors = 0; last_error = 0;

        // Initialize simulated hardware.
	pd1 = 0;
	pd2 = 0;
	clk = 0;
        
        // Generate reset signal.
        reset = 0;
        #1;
        reset = 1;
        # (2 * `clock_period);
        reset = 0;
        # (2 * `clock_period);

        // Simulate at least 50 beans.
        while( beans < 50 )
          begin

             // End of previous clump reaches pd1.
             pd1 <= 0;

             // Amount of time between bean clumps.
             inter_clump = ($random>>1) % ( 5 * max_bean_time );
             # inter_clump;

             // Beginning of new clump reaches pd1;
             pd1 <= 1;

             // Amount of time for one bean to pass a point. (E.g., pd1.)
             beantime = min_bean_time +
                        ($random>>1) % ( max_bean_time - min_bean_time );

             // Prevent new clump from smashing into previous clump
             // between photodetectors.
             if( inter_clump + beantime < oldbeantime )
               beantime = oldbeantime + inter_clump ;
             oldbeantime = beantime;

             begin:CLUMP
                forever
                  begin
                     // Bean arrives at pd1. 
                     # beantime;
                     // Bean arrives at pd2.
                     beans = beans + 1;
                     if( $random & 'b1 ) disable CLUMP;
                  end
             end
             
          end 

        // Wait for last bean, plus time for circuit to count it.
        # ( 2 * beantime );

        if( errors === 0 )
          $display("Beans counted properly.\n");
        else
          $display("*** ERROR: bean count off by %d.\n",
                   beans - cnt);

        $stop;

     end // initial begin


   // Clock for bean counter circuit.
   always #`clock_period clk = ~clk;

   // Make sure bean counter count agrees with testbench .

   always @ ( beans )  beans1 <= #(beantime * 0.75 ) beans;
   
   always @( beans1 )
     if( cnt - beans1 !== last_error )
       begin
          errors = errors + 1;
          last_error = cnt - beans1;
          if( errors <= 5 )
            $display("Wrong bean count, %d should be %d.\n", cnt,beans1);
          if( errors == 5 )
            $display("Ignoring future errors.\n");
       end

   // Set pd2 to lag pd1 by a beantime.
   always @( posedge pd1 ) pd2 <= #beantime  1;  
   always @( negedge pd1 ) pd2 <= #beantime  0;

   
endmodule // beantest

`endif // ifdef TEST