////////////////////////////////////////////////////////////////////////////////
//
/// LSU EE 4755 Fall 2014 Homework 4
//

 /// Assignment  http://www.ece.lsu.edu/koppel/v/2014f/hw04.pdf

 /// Instructions:
  //
  // (1) Find the undergraduate workstation laboratory, room 126 EE
  //     Building.
  //
  // (2) Locate your account.  If you did not get an account please
  //     E-mail: koppel@ece.lsu.edu
  //
  // (3) Log in to a Linux workstation.
  //     The account should start up with a WIMP interface (windows, icons,
  //     mouse, pull-down menus)  ( :-) ) but one or two things need
  //     to be done from a command-line shell.  If you need to brush up
  //     on Unix commands follow http://www.ece.lsu.edu/koppel/v/4ltrwrd/.
  //
  // (4) If you haven't already, follow the account setup instructions here:
  //     http://www.ece.lsu.edu/koppel/v/proc.html
  //
  // (5) Copy this assignment, local path name
  //     /home/faculty/koppel/pub/ee4755/hw/2014f/hw04
  //     to a directory ~/hw04 in your class account. (~ is your home
  //     directory.) Use this file for your solution.
  //
  // (6) Solve the problems.
  //
  //     Your entire Verilog solution should be in this file.
  //     Put answers to written questions in this file or submit them
  //     by E-mail or on paper.
  //
  //     Do not change module names.
  //
  // (7) Your solution will automatically be copied from your account by
  //     the TA-bot.


 /// Additional Resources
  //
  // Verilog Documentation
  //    The Verilog Standard
  //      http://standards.ieee.org/getieee/1800/download/1800-2012.pdf
  //    Introductory Treatment (Warning: Does not include SystemVerilog)
  //      Brown & Vranesic, Fundamentals of Digital Logic with Verilog, 3rd Ed.
  //
  // Account Setup and Emacs (Text Editor) Instructions
  //      http://www.ece.lsu.edu/koppel/v/proc.html
  //      To learn Emacs look for Emacs tutorial.
  //
  // Help with Cadence Verilog Simulator and Synthesis Programs
  //      http://www.ece.lsu.edu/v/ref.html
  //      UserID is ee3755, password was given in class.
  //
  // Unix Help
  //      http://www.ece.lsu.edu/koppel/v/4ltrwrd/


typedef enum { Char_escape = 1, Char_0 = 48, Char_9 = 57 } Chars_Special;

module asc_to_bin
  #( int size_lg = 4,
     int max_chars = 4,
     int size = 1 << size_lg )
  ( output [7:0] char_out,
    output can_insert, can_remove,
    input [7:0] char_in,
    input insert_req, remove_req,
    input reset, clk);

   logic [7:0] storage [size];

   logic [size_lg:1] head, tail;

   uwire   is_digit = char_in >= Char_0 && char_in <= Char_9;

   uwire   empty = head == tail;
   uwire   full = tail + 1 == head;

   assign can_insert = !full;
   assign can_remove = !empty;

   assign char_out = storage[head];

   // cadence translate_off
   initial for ( int i=0; i<size; i++ ) storage[i] = 255;
   // cadence translate_on

   always @( posedge clk ) if ( reset ) begin

      tail <= 0;

   end else begin

      if ( insert_req ) begin

         storage[tail] <= char_in;
         tail <= tail + 1;

      end

   end

   always @( posedge clk ) if ( reset ) begin

      head <= 0;

   end else if ( remove_req ) begin

      head <= head + 1;

   end

endmodule


// cadence translate_off

module testbench();

   localparam int elts_lg = 4;
   localparam int elts = 1 << elts_lg;
   localparam int int_chars = 2;

   uwire [7:0] char_out;
   uwire       can_insert, can_remove;
   logic [7:0] char_in;
   logic insert_req, remove_req, reset, clk;
   asc_to_bin #(elts_lg,int_chars) b1
     (char_out,can_insert,can_remove,char_in,insert_req,remove_req,reset,clk);

   int cycle_num;

   initial begin
      clk = 0;
      cycle_num = 0;
      fork
         forever #1 clk = !clk;
         forever @( posedge clk ) cycle_num++;
      join
   end

   string in_str = "One 1 two 12 three 317 four 1234 six 123456 ten 1234567890.  There are 60 seconds in a minute and 31536000 in a year.";
   string out_str = "";

   initial begin

      automatic int insert_finished_cyc = 0;
      automatic int out_size = 0;
      automatic bit tb_insert_done = 0;
      automatic bit tb_remove_done = 0;

      /// Reset the module.
      //
      reset = 0;
      insert_req = 0;
      remove_req = 0;
      @( negedge clk ) reset = 1;
      @( negedge clk ) reset = 0;
      @( negedge clk );

      /// Check for one possible error.
      //
      if ( can_insert !== 1 ) begin

         $display("Module did not reset, can_insert: %h\n", can_insert);
         $fatal(1);

      end

      /// Start Main Testing Loops
      //
      fork

         /// Watchdog -- Stop simulation if it's taking too long.
         //
         fork begin

            automatic int cyc_limit = in_str.len() * 100;

            fork
               wait ( cycle_num == cyc_limit );
               wait ( tb_insert_done && tb_remove_done );
            join_any

            if ( cycle_num >= cyc_limit ) begin
               $display("Exceeded cycle limit, exiting.\n");
               $fatal(1);
            end

         end join_none

         /// Trace Execution -- Print Signal Values After Interesting Changes
         //
         while ( !tb_insert_done || !tb_remove_done ) begin

            @( insert_req or remove_req or can_insert or can_remove
               or b1.tail or b1.head or tb_insert_done or tb_remove_done );
            @( negedge clk );

            /// Trace execution by showing removed character and
            /// related information.
            //
            $display( "c In %c  Out %d = %c   tail %d head %d ",
                      char_in, char_out, char_out, b1.tail, b1.head);
               
         end

         /// Insert Characters
         //
         begin

            automatic int in_pos = 0;

            while ( in_pos < in_str.len() ) begin

               @( negedge clk );

               // Flip a coin, and if it comes up tails send a character
               // in if module is ready for one.
               //
               if ( {$random} & 'h1 && can_insert ) begin

                  char_in = in_str[in_pos++];
                  insert_req = 1;

               end else begin

                  insert_req = 0;

               end

            end

            @( negedge clk );

            insert_req = 0;
            insert_finished_cyc = cycle_num;

            $display("Done feeding inputs.");
            tb_insert_done = 1;

         end

         /// Remove Characters
         //
         begin

            int buffer;
            automatic int bytes_remaining = 0;

            while ( insert_finished_cyc == 0
                    || cycle_num < insert_finished_cyc + elts * 10 ) begin

               @( negedge clk );

               if ( {$random} & 1 && can_remove ) begin

                  remove_req = 1;
                  out_size++;

                  if ( bytes_remaining > 0 ) begin

                     buffer = ( buffer << 8 ) + char_out;
                     bytes_remaining--;

                     if ( bytes_remaining == 0 ) begin

                        // Convert binary number back to ASCII.
                        string iasc;
                        iasc.itoa(buffer);
                        out_str = {out_str,iasc};

                     end

                  end else if ( char_out == Char_escape ) begin

                     bytes_remaining = int_chars;
                     buffer = 0;

                  end else begin

                     out_str = {out_str,char_out};

                  end

               end else begin

                  remove_req = 0;

               end

            end

            $display("Done gathering outputs.\n");
            tb_remove_done = 1;

         end

      join

      if ( in_str != out_str )
        $display("** Error - strings don't match.\n");
      else
        $display("Correct output, strings match. %s",
                 ( in_str.len() == out_size ) ? "But no compression!" : "");

      $display("In size %d bytes, out size %d bytes.\n",
               in_str.len(), out_size);
      $display("In - %s\nOut- %s\n",
               in_str, out_str);

      $finish(2);

   end

endmodule

// cadence translate_on