--- Code for LSU EE 4702 Spring 2001

--- Calculator Example


library ieee;
use ieee.std_logic_1164.all;
library Std_DevelopersKit;
use Std_DevelopersKit.Std_Regpak.all;

 --- Definitions used by calculator code and testbench.

package calc_defs is
  type key_t is array ( 5 downto 0 ) of std_logic;

  -- These must be initialized by string literals to be used in a case stmt.
  constant key_none   : key_t := o"00";
  constant key_never  : key_t := o"01";

  constant key_plus   : key_t := o"10";
  constant key_minus  : key_t := o"11";
  constant key_times  : key_t := o"12";
  constant key_divide : key_t := o"13";
  constant key_equal  : key_t := o"14";
  constant key_clear  : key_t := o"15";

  constant key_0      : key_t := o"20";
  constant key_1      : key_t := o"21";
  constant key_2      : key_t := o"22";
  constant key_3      : key_t := o"23";
  constant key_4      : key_t := o"24";
  constant key_5      : key_t := o"25";
  constant key_6      : key_t := o"26";
  constant key_7      : key_t := o"27";
  constant key_8      : key_t := o"30";
  constant key_9      : key_t := o"31";

end calc_defs;

 --- Calculator

library ieee;
use ieee.std_logic_1164.all;
library Std_DevelopersKit;
use Std_DevelopersKit.Std_Regpak.all;
use work.calc_defs.all;


entity calc is
  generic( precision: integer := 32 );
  port( display_val : out std_logic_vector ( precision-1 downto 0 );
        beep        : out std_logic;
        key_code    : in key_t;
        reset       : in std_logic;
        clk         : in std_logic);
end calc;

architecture a1 of calc is

  -- Internally used constants for categorizing keys.
  constant kty_digit: key_t := o"40";
  constant kty_arith: key_t := o"41";

  -- Other constants.
  constant buffer_max:integer := ( 2 ** precision ) - 1;
  constant zero: std_logic_vector ( precision-1 downto 0 ) := ( others => '0' );

  type state_t is (st_0N, st_0P, st_0C, st_1N, st_1P);
  
  signal beep_time          : std_logic_vector ( 5 downto 0 );
  signal beep_ack, beep_req : std_logic;
  signal pending_op         : key_t;
  signal state              : state_t;
  signal cbuffer, acc       : std_logic_vector ( precision-1 downto 0 );
    
  impure function do_op return std_logic_vector is
  begin
    case pending_op is
      when key_plus   => return acc + cbuffer;
      when key_minus  => return acc - cbuffer;
      when key_times  => return acc * cbuffer;
      when key_divide =>
        if cbuffer = zero then return zero; else return acc / cbuffer; end if;
      when others     =>
        assert false
          report "Error in behavioral description."
          severity failure;
        return zero;  -- Pacify compiler.
    end case;
  end function;

  procedure fatal (constant message:in string) is
  begin
    report "Error in behavioral description: " & message;
    assert false
      report "Execution stopping."
      severity failure;
  end procedure;

  -- Set a std_logic_vector signal to zero.
  procedure clr (signal a :inout std_logic_vector) is
  begin for i in a'range loop a(i) <= '0'; end loop; end;

  -- Set a std_logic_vector signal to an integer.
  procedure set(signal a: inout std_logic_vector; constant i: in integer) is
  begin a <= to_stdlogicvector(i,a'length); end;

begin

  display_val <= cbuffer;

  -- Turn beep on when command by other process and off when timer
  -- reaches zero.
  process
  begin
    wait until falling_edge(clk);
    if reset = '1' then
      beep_ack <= '0';  beep <= '0';  clr(beep_time);
    elsif ( beep_ack xor beep_req ) = '1' then
      beep_ack <= not beep_ack;  set(beep_time,20);  beep <= '1';
    else 
      if   beep_time /= zero
      then beep_time <= beep_time - 1;
      else beep <= '0'; end if;
    end if;
  end process;

  process
    variable nl: std_logic;
    variable key_type: key_t;  -- Key category.
    -- Note: next_state a variable because signals updated at end of time step.
    variable next_state: state_t;
    
    procedure add_digit is
    begin
      if cbuffer < buffer_max then
        cbuffer <= cbuffer * 10
                   + std_logic_vector(key_code) - std_logic_vector(key_0);
      end if;
    end procedure;

    procedure do_beep is
    begin
      beep_req <= not beep_req;
      next_state := state;
    end procedure;

    procedure do_clear is
    begin
      clr(acc);  clr(cbuffer);  next_state := st_0N;
    end procedure;

  begin
    wait until rising_edge(clk);
    if reset = '1' then
      beep_req <= '0';
      nl := '0';
      state <= st_0N;
      clr(cbuffer);
      clr(acc);
      pending_op <= key_never;
    elsif key_code = key_none then
      nl := '1';
    elsif nl = '1' then
      nl := '0';

      case key_code is

        when key_0 | key_1 | key_2 | key_3 | key_4
           | key_5 | key_6 | key_7 | key_8 | key_9 =>
          key_type := kty_digit;

        when key_plus | key_minus | key_times | key_divide =>
          key_type := kty_arith;

        when others =>
          key_type := key_code;

      end case ;

      case state is

        when st_0N  =>
          case key_type is
            when kty_digit => add_digit;  next_state := st_0P;
            when key_clear => do_clear;
            when others    => do_beep;
          end case;

        when st_0P =>
          case key_type is
            when kty_digit => add_digit;  next_state := state;
            when kty_arith => pending_op <= key_code;  
                              acc <= cbuffer;
                              clr(cbuffer);
                              next_state := st_1N;
            when key_clear => do_clear;
            when others    => do_beep;
          end case;  

        when st_0C =>
          case key_type is
            when kty_digit => clr(cbuffer);
                              wait for 0 ns;
                              add_digit;
                              next_state := st_0P;
            when kty_arith => pending_op <= key_code;
                              acc <= cbuffer;
                              clr(cbuffer);
                              next_state := st_1N;
            when key_clear => do_clear;
            when others    => do_beep;
          end case;

        when st_1N =>
          case key_type is
            when kty_digit => add_digit;  next_state := st_1P; 
            when key_clear => do_clear;
            when others    => do_beep;
          end case;

        when st_1P =>
          case key_type is
            when kty_digit => add_digit;  next_state := state;
            when key_equal => cbuffer <= do_op; next_state := st_0C;
            when kty_arith => acc <= do_op;
                              pending_op <= key_code;
                              clr(cbuffer);
                              next_state := st_1N;
            when key_clear => do_clear;
            when others    => do_beep;
          end case;

        when others =>
          fatal("Unknown case.");
                       
      end case;

      state <= next_state;

    end if;
      
  end process;

end a1;


 --- Calculator Testbench (Demo, not really a test.)

library ieee;
use ieee.std_logic_1164.all;
library Std_DevelopersKit;
use Std_DevelopersKit.Std_Regpak.all;
use Std_DevelopersKit.Std_IOpak.all;
use work.calc_defs.all;

entity calctb is end;

architecture a1 of calctb is
  constant prec           : integer:= 30;
  signal display          : std_logic_vector ( prec-1 downto 0 );
  signal key              : key_t;
  signal clk, reset, beep : std_logic:= '0';

begin

  c:entity work.calc(a1) 
    generic map (prec)
    port map (display,beep,key,reset,clk);

  clk <= not clk after 2 ns;
  
  process(beep)
  begin
    if beep = '1' then
      report "Beep starting.";
    elsif beep = '0' then
      report "Beep finished.";
    end if;
  end process;

  process
    variable initialized: integer:= 0;
    type key_array_t is array ( character'low to character'high ) of key_t;
    variable to_key: key_array_t;
    
    procedure command (constant cmd: in string) is
      variable c: character;

    begin

      if initialized /= 1 then

        for i in to_key'range loop to_key(i) := key_never; end loop;
        for i in 0 to 9 loop
          to_key( character'val((i+character'pos('0'))) ) :=
            key_t(std_logic_vector(key_0) + i);
        end loop;
        
        to_key('+'):= key_plus;
        to_key('-'):= key_minus;
        to_key('/'):= key_divide;
        to_key('*'):= key_times;
        to_key('='):= key_equal;
        to_key('c'):= key_clear;
        to_key(' '):= key_none;
        to_key(character'val(0)):= key_none;

        initialized:= 1;

      end if;

      for i in cmd'range loop

        c:= cmd(i);

        wait until rising_edge(clk);
        wait until falling_edge(clk);

        key <= to_key( c );
        
        wait until rising_edge(clk);
        wait until falling_edge(clk);

        assert key /= key_never 
          report "Demo error: illegal key in command: " & to_string(c) & c
          severity failure;

        assert key = key_none
          report "Key " & c & " Display " & to_string(to_integer(display))
          severity note;

        wait until rising_edge(clk);
        wait until falling_edge(clk);

        key <= key_none;

      end loop;

    end procedure;
    
  begin

    reset <= '0';
    for i in 0 to 1 loop wait until falling_edge(clk); end loop;
    reset <= '1';
    for i in 0 to 1 loop wait until falling_edge(clk); end loop;
    reset <= '0';
    for i in 0 to 1 loop wait until falling_edge(clk); end loop;

    command("c 5 c 12 + 34 = ");
    
    command(" 1 + 2 + 3 ++ 4 = - 10 = ");

    assert false
      report "Done with tests"
      severity failure;

  end process;

end a1;