///////////////////////////////////////////////////////////////////////
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// ====================================================================
// Copyright 2002 (c) Lattice Semiconductor Corporation
// ALL RIGHTS RESERVED
//
// This confidential and proprietary software may be used only as
// authorised by a licensing agreement from Lattice Semiconductor
// Corporation.
// The entire notice above must be reproduced on all authorized
// copies and copies may only be made to the extent permitted
// by a licensing agreement from Lattice Semiconductor Corporation.
//

// ====================================================================
// File Details
// ====================================================================
// Project              :       ORSPII
// Filename             :       latspi4_tx.v
// Author               :       Ben_Kerr,bkerr@latticesemi.com
// Created on           :       Thursday June 27 2002 (WW25)
// Last Modified By     :       $Author: bkerr $
// Last Modified On     :       $Date: Mon Oct 28 04:44:27 2002 $
// Revision             :       $Revision: 1.15 $
// Status               :       Development
// Dependencies         :       latspi4_inc.v, latspi4.v
//=====================================================================

//=====================================================================
// Description        :
//=====================================================================
// This module implements a bus-functional model of the transmit (egress)
// SPI4 interface. The following tasks abstract different data transfer
// states that occur on a compliant interface, allowing SPI4 bus transactions
// to be simulated.
//
// Task list
// ---------
// idle        : transmits IDLE control word(s)
// cw          : transmits payload control words
// burst       : transmits a burst of payload data
// training    : transmits a training burst
// cal_rx      : controls a process to receive FIFO status
//
// Flow control
// ------------
// Egress rate control using the FIFO status provided on the TSTAT
// interface is implemented. Before transmitting a burst of data, the
// amount of "credit" granted to the port is checked. If there is
// insufficient credit, IDLE control words will be sent until enough
// credit is available. If the DUT does not grant enough credit, a
// timeout will issue an error, then continue waiting for more credit.
//
//=====================================================================

//=====================================================================
// Revision History
//=====================================================================
// $Log: latspi4_tx.v.rca $
// 
//  Revision: 1.15 Mon Oct 28 04:44:27 2002 bkerr
//  Moved (posedge tsclk) to end of Rx calendar state machine, as spurious
//  unknown rstat word errors were being generated due to the SYNC word not
//  being seen
// 
//  Revision: 1.14 Tue Oct 22 06:21:17 2002 rproudlo
//  Modified DIP4 Error Insertion
// 
//  Revision: 1.13 Thu Oct 17 07:02:12 2002 bkerr
//  Corrected bug that was generating DIP-4 errors on IDLE control words
//  being inserted due to lack of credit
// 
//  Revision: 1.12 Tue Oct  1 05:01:52 2002 rproudlo
//  Moved MB values to the sf' files
// 
//  Revision: 1.11 Fri Sep 27 06:03:48 2002 rproudlo
//  Tempory checkin
// 
//  Revision: 1.10 Thu Sep 26 06:25:46 2002 bkerr
//  Added simulation exit on insufficient credit error to prevent simulation
//  from getting stalled
// 
//  Revision: 1.9 Tue Sep 24 11:40:54 2002 bkerr
//  Corrected loading of CAL_LEN & CAL_M depending on selected calender
// 
//  Revision: 1.8 Tue Jul 30 11:12:42 2002 bkerr
//  Re-inserted logic to add aaaa when sending an EOP burst with
//  a DIP-4 error
// 
//  Revision: 1.7 Mon Jul 29 11:07:08 2002 bkerr
//  Changed method of dip4 error generation
//  burst task now has a dip4_invld input to send an invalid dip4 for
//  the burst
//
//  Revision: 1.6 Tue Jul 16 12:03:36 2002 bkerr
//  Added rx & tx sop and eop counters per port
//
//  Revision: 1.5 Fri Jul 12 09:45:12 2002 bkerr
//  Inserts aaaa as last byte when generating DIP-4 errors
//  Increments toptest.error_count when reporting errors
//
//  Revision: 1.4 Wed Jul 10 12:14:15 2002 bkerr
//  Added dynamic calculation of maxburst1 & maxburst2 based
//  on how the DUT DPRAMs are configured
//
//  Revision: 1.3 Mon Jul  8 12:05:56 2002 bkerr
//  Fixed bug introduced by minimum SOP time enforcement
//  IDLE cycles were being inserted after every short burst, which was
//    breaking the back-to-back EOP test
//
//  Revision: 1.2 Thu Jul  4 12:15:18 2002 bkerr
//  Added minimum SOP->SOP gap time enforcement
//
//  Revision: 1.1 Tue Jul  2 03:53:55 2002 bkerr
//  Initial creation
//=====================================================================

`timescale 1ps/1ps

module latspi4_tx(
   tdclk,            // OUTPUT - data bus clock
   tdat,             // OUTPUT - data bus
   tctl,             // OUTPUT - control word indication
   tsclk,            // INPUT  - FIFO status clock
   tstat             // INPUT  - FIFO status
   );

// ---------------------------------------------------------------------------
// includes
// ---------------------------------------------------------------------------
`include "latspi4_inc.v"

// ---------------------------------------------------------------------------
// parameters
// ---------------------------------------------------------------------------
parameter OP_DELAY         =  1000;    // clk->out delay in ps
parameter CLK_PERIOD       =  2500;    // tdclk period in ps
// parameter CREDIT_TIMEOUT   =  4000;    // number of cycles to wait for credit
parameter CREDIT_TIMEOUT   =  40000;    // number of cycles to wait for credit

// ---------------------------------------------------------------------------
// I/O declarations
// ---------------------------------------------------------------------------
output            tdclk;
output   [15:0]   tdat;
output            tctl;
input             tsclk;
input    [ 1:0]   tstat;

reg               tdclk;
wire     [15:0]   tdat;
wire              tctl;
wire              tsclk;
wire     [ 1:0]   tstat;

// ---------------------------------------------------------------------------
// local variables
// ---------------------------------------------------------------------------
reg    [15:0]     tdat_out;         // clock edge aligned TDAT
reg               tctl_out;         // clock edge aligned TCTL
reg    [ 1:0]     tstat_temp;       // Temp variable for DIP-2 checker

reg    [15:0]     prev_dip16;       // Storage for previously calculated DIP-16 value
reg    [ 1:0]     prev_dip2;        // Storage for previously calculated DIP-2 value
reg    [ 1:0]     prev_eops;        // Storage for EOPS to be inserted into next control word
reg               prev_dip4_invld;  // Storage for invalid DIP-4 to be inserted into next control word

// possible FIFO state machine next states
reg               expect_sync;
reg               expect_cal_sel;
reg               expect_status;
reg               expect_dip2;

reg    [ 7:0]     port_count  [255:0];    // Array of counters for each port
integer           credit      [255:0];    // Credit count per port

// ---------------------------------------------------------------------------
// Events
// ---------------------------------------------------------------------------
// error conditions
event             invalid_cal_sel;        // Invalid calendar select word
event             invalid_tstat;          // Incorrect FIFO status word
event             unknown_tstat;          // Unexpected TSTAT word
event             dip2_error;             // Incorrect DIP-2
event             credit_timeout;         // Timeout waiting for credit

// ---------------------------------------------------------------------------
// continuous assignments
// ---------------------------------------------------------------------------
// Model clk->out delay
assign  #(OP_DELAY) tdat   = tdat_out;
assign  #(OP_DELAY) tctl   = tctl_out;

// ---------------------------------------------------------------------------
// tdclk generation
// ---------------------------------------------------------------------------
initial
   tdclk   = 0;

always #(CLK_PERIOD/2.00)  // Force floating point division by including decimal
   tdclk   = ~tdclk;

// ---------------------------------------------------------------------------
// Configure local variables
// ---------------------------------------------------------------------------
initial
begin : config_main

   integer i;

   prev_dip16        = 16'h0000;
   prev_dip2         =  2'b00;
   prev_eops         =  2'b00;
   prev_dip4_invld   = 1'b0;
   tdat_out          = 16'h000f;
   tctl_out          =  1'b1;

   for(i=0;i<256;i=i+1)
      port_count[i] = 8'h00;

   for(i=0;i<256;i=i+1)
      credit[i] = 0;

   // Possible FIFO status states out of reset
   expect_sync       = 1'b1;
   expect_cal_sel    = 1'b0;
   expect_status     = 1'b0;
   expect_dip2       = 1'b0;
end

// ---------------------------------------------------------------------------
// idle task
// ---------------------------------------------------------------------------
// Description
// -----------
// Transmits SPI4 compliant IDLE control words.  Inserts the EOPS from a
// previous burst into the first IDLE control word sent.
//
// Paramters
// ---------
// num     Number of IDLE control words to send
// ---------------------------------------------------------------------------
task idle;
input [31:0]   num;
integer        i;
begin
   for(i=0;i<num;i=i+1)
   begin
      @(posedge tdclk or negedge tdclk)
      tctl_out <= 1'b1;
      tdat_out <= calc_dip4(1'b1,(i==0 ? {1'b0,prev_eops,13'h000f} : 16'h000f));
      prev_eops = 2'b00;
      prev_dip4_invld = 1'b0;      
   end
end
endtask

// ---------------------------------------------------------------------------
// cw task
// ---------------------------------------------------------------------------
// Description
// -----------
// Transmits SPI4 compliant control words.
//
// Parameters
// ----------
// payl    0 - IDLE/Training control word
//         1 - Payload control word
// sop     start-of-packet indication
// eops    end-of-packet status indication
// port    port number
// ---------------------------------------------------------------------------
task cw;
input          payl;
input          sop;
input   [ 1:0] eops;
input   [ 7:0] port;
begin
   @(posedge tdclk or negedge tdclk)
   tctl_out    <=  1'b1;
   tdat_out    <=  calc_dip4(1'b1,{payl,eops,sop,port,4'hf});
   prev_dip4_invld = 1'b0;
end
endtask

// ---------------------------------------------------------------------------
// burst task
// ---------------------------------------------------------------------------
// Description
// -----------
// Transmits SPI4 compliant bursts of data.  Inserts port and sequence
// information into the payload to allow automatic verification of received
// data.
//
// Parameters
// ----------
// port        port number to send the burst on
// length      length of burst to send in bytes
// sop         start-of-packet indication
// eops        end-of-packet status indication
// dip4_invld  transmit an invalid DIP-4 value in the following control word
// num_ext_cw  number of extended control words to send
// ---------------------------------------------------------------------------
task burst;
input   [ 7:0] port;
input   [31:0] length;
input          sop;
input   [ 1:0] eops;
input          dip4_invld;
input   [31:0] num_ext_cw;
integer        num_words;
integer        credit_required;
integer        i;
integer        timeout;
reg     [ 7:0] count;
begin

   if(latspi4.tx_obey_credit)
   begin
      // Calculate amount of credit required to send burst
      // Bursts less than 16-bytes are rounded-up
      if(length % 16)
         credit_required = (length / 16) + 1;
      else
         credit_required = (length / 16);

      // Wait for credit to become available
      timeout = 0;
      while(credit[port] < credit_required)
      begin
         idle(1);
         timeout = timeout + 1;
         if(timeout >= CREDIT_TIMEOUT)
         begin 

            $display("%t: %m: ERROR: Timeout(%0d clks) waiting for sufficient credit on port %0d. Got:%0d Need:%0d",
                     $time,
                     CREDIT_TIMEOUT,
                     port,
                     credit[port],
                     credit_required
                  );
            $display("%t: *******************************************************",$time);
            $display("%t: ****               T E S T   F A I L E D",$time);
            $display("%t: *******************************************************",$time);
            if($test$plusargs("STOP"))
               $stop(2);
            else
               $finish(2);
          end
      end // while(credit...)
      
      // Subtract burst size from available credit
      credit[port] = credit[port] - credit_required;
   end // if(obey_credit)

   // Get the port_count value from the array
   if(sop)
   begin
      count = 8'h00;     // Reset on SOP
      latspi4.tx_sop_count[port] = latspi4.tx_sop_count[port] + 1;
   end
   else
      count = port_count[port];

   if(eops != `LATSPI4_EOPS_CONT)
      latspi4.tx_eop_count[port] = latspi4.tx_eop_count[port] + 1;

   // Transmit payload control word
   // using EOPS from previous burst
   cw(1'b1,sop,prev_eops,port);
   prev_eops         = eops;

   // Transmit any extended control words
   for(i=0;i<num_ext_cw;i=i+1)
   begin
      if(i != (num_ext_cw-1))
         cw(1'b0,1'b1,2'b00,(port+i+1));   // EXT control word 4'b0001
      else
         cw(1'b0,1'b1,2'b11,(port+i+1));   // EOE control word 4'b0111
   end

   // save dip4_invld for use in next control word
   prev_dip4_invld   = dip4_invld;

   // Determine number of 16-bit payload words to send
   if(length % 2)
      num_words = (length / 2) + 1;
   else
      num_words =  length / 2;

   // Send the payload words
   for(i=0;i<num_words;i=i+1)
   begin
      @(posedge tdclk or negedge tdclk)
      tctl_out   <=  1'b0;
      if(i != (num_words - 1))
         tdat_out   <=  calc_dip4(1'b0,{port,count});
      else
      begin
         case({eops,dip4_invld})
            {`LATSPI4_EOPS_CONT,1'b0} : tdat_out <=  calc_dip4(1'b0,{port,count});
            {`LATSPI4_EOPS_ABRT,1'b0} : tdat_out <=  calc_dip4(1'b0,16'haaaa);    // Abort
            {`LATSPI4_EOPS_2VAL,1'b0} : tdat_out <=  calc_dip4(1'b0,16'heeee);    // Even EOP
            {`LATSPI4_EOPS_1VAL,1'b0} : tdat_out <=  calc_dip4(1'b0,16'hee00);    // Odd EOP
            {`LATSPI4_EOPS_CONT,1'b1} : tdat_out <=  calc_dip4(1'b0,{port,count});
            {`LATSPI4_EOPS_ABRT,1'b1} : tdat_out <=  calc_dip4(1'b0,16'haaaa);    // Abort
            {`LATSPI4_EOPS_2VAL,1'b1} : tdat_out <=  calc_dip4(1'b0,16'haaaa);    // Even EOP
            {`LATSPI4_EOPS_1VAL,1'b1} : tdat_out <=  calc_dip4(1'b0,16'haaaa);    // Odd EOP
         endcase
      end
      count = count + 8'h01;
   end

   // enforce minimum SOP-SOP time
   if(num_words < `LATSPI4_MINSOP && latspi4.tx_minsop && sop)
      idle(`LATSPI4_MINSOP - num_words);

   // save the count back into the array
   port_count[port] = count;
end
endtask

// ---------------------------------------------------------------------------
// training task
// ---------------------------------------------------------------------------
// Description
// -----------
// Transmits SPI4 compliant training sequences.
//
// Parameters
// ----------
// num_tcw     number of training control words to send
// num_tdw     number of training data words to send
// alpha       number of times to repeat training sequence
// ---------------------------------------------------------------------------
task training;
input   [31:0]  num_tcw;
input   [31:0]  num_tdw;
input   [31:0]  alpha;
integer       i;
integer       j;
begin
   // Alpha loop
   for(i=0;i<alpha;i=i+1)
   begin
      // Training control word loop
      for(j=0;j<num_tcw;j=j+1)
      begin
         @(posedge tdclk or negedge tdclk)
         tctl_out   <=  1'b1;
         tdat_out   <=  calc_dip4(1'b1,`LATSPI4_TCW);
      end
      // Training data word loop
      for(j=0;j<num_tdw;j=j+1)
      begin
         @(posedge tdclk or negedge tdclk)
         tctl_out   <=  1'b0;
         tdat_out   <=  calc_dip4(1'b0,`LATSPI4_TDW);
      end
   end
end
endtask

// ---------------------------------------------------------------------------
// cal_rx task
// ---------------------------------------------------------------------------
// Description
// -----------
// Starts and stops a process to receive FIFO status information provided
// on the TSTAT interface
//
// Parameters
// ----------
// start       1 - starts the calendar receive process
// stop        1 - stops the calendar receive process
// ---------------------------------------------------------------------------
task cal_rx;
input          start;
input          stop;
begin
   if(start)
   begin
      cal_rx_main.go         = 1'b1;
   end

   if(stop)
   begin
      disable cal_rx_main;
      cal_rx_main.go = 1'b0;
   end
end
endtask

// ---------------------------------------------------------------------------
// cal_rx_main process
// ---------------------------------------------------------------------------
// Description
// -----------
// Implements a continuous process to receive FIFO status information
// provided on the TSTAT interface.  Once started, the process runs
// continuously.  The process must be disabled before being restarted with
// a new cal_len & cal_m configuration.
// ---------------------------------------------------------------------------
always
begin : cal_rx_main
   reg         go;

   reg   [7:0] cal_port;

   // FIFO status interface state machine
   // current state
   reg         tstat_sync;
   reg         tstat_cal_sel;
   reg         tstat_status;
   reg         tstat_dip2;

   // FIFO status interface counters
   integer     cur_m;
   integer     cur_len;
   integer     cal_index;
   reg         cal_sel;      // Calendar selected by calendar select word

   // Calculated MaxBurst1 & MaxBurst2 values, based on number of ports in test
   integer     maxburst1;
   integer     maxburst2;

   // Non-blocking control
   wait(go === 1'b1);

   // FIFO status state machine
   forever
   begin
      tstat_sync     = 1'b0;
      tstat_cal_sel  = 1'b0;
      tstat_status   = 1'b0;
      tstat_dip2     = 1'b0;

      if( tstat === `LATSPI4_SYNC &&     // SYNC state
          expect_sync)
      begin
         tstat_sync  = 1'b1;
         tstat_temp  = calc_dip2(1'b1,1'b0,tstat);

         expect_sync = 1'b1;
         expect_dip2 = 1'b0;
         if(toptest.CALENDAR_SW_EN_RX)
         begin
            expect_cal_sel  = 1'b1;
            expect_status   = 1'b0;
         end
         else
         begin
            expect_cal_sel  = 1'b0;
            expect_status   = 1'b1;
            cal_sel         = 1'b0;
         end

         // Reset calendar counters
         if(toptest.CALENDAR_SW_EN_RX == 1'b0)
         begin
            cur_m       = toptest.CALENDAR_M_RX;
            cur_len     = toptest.CALENDAR_LEN_RX;
         end
         else
         begin
            if(toptest.CALENDAR_SW_SEL_RX == 1'b0)
            begin
               cur_m       = toptest.CALENDAR_M_RX;
               cur_len     = toptest.CALENDAR_LEN_RX;
            end
            else
            begin
               cur_m       = toptest.CALENDAR_M_RX_SHDW;
               cur_len     = toptest.CALENDAR_LEN_RX_SHDW;
            end
         end
                        
         cal_index   = 0;
         
         // Calculate maxburst1 & maxburst2
         maxburst1   = toptest.MAX_BURST1_RX;
         maxburst2   = toptest.MAX_BURST2_RX;
      end
      else if(expect_cal_sel)    // CAL_SEL state
      begin
         tstat_cal_sel  = 1'b1;
         tstat_temp     = calc_dip2(1'b0,1'b0,tstat);

         // Decode Calendar select word
         if(tstat === 2'b01)
            cal_sel    = 1'b0;
         else if(tstat === 2'b10)
            cal_sel    = 1'b1;
         else if(tstat === 2'b00)
            -> invalid_cal_sel;
         expect_sync    = 1'b0;
         expect_cal_sel = 1'b0;
         expect_status  = 1'b1;
         expect_dip2    = 1'b0;
      end
      else if(expect_status)     // CALENDAR state
      begin
         tstat_status   = 1'b1;
         tstat_temp     = calc_dip2(1'b0,1'b0,tstat);

         if(toptest.CALENDAR_SW_EN_RX)
         begin
            if(cal_sel)
               cal_port = toptest.RX_SHADOW_CAL_SEQ[cal_index];
            else
               cal_port = toptest.RX_MAIN_CAL_SEQ[cal_index];
         end
         else
            cal_port    = toptest.RX_MAIN_CAL_SEQ[cal_index];

         case(tstat)
            `LATSPI4_STARVING  : credit[cal_port]    =  maxburst1;
            `LATSPI4_HUNGRY    : if(credit[cal_port] >= maxburst2)
                                    credit[cal_port] =  credit[cal_port];
                                 else
                                    credit[cal_port] =  maxburst2;
            `LATSPI4_SATISFIED : credit[cal_port]    =  credit[cal_port];
            default            : -> invalid_tstat;
         endcase

         cal_index      = cal_index + 1;

         cur_len        = cur_len - 1;
         if(cur_len == 0)
         begin
            if(toptest.CALENDAR_SW_EN_RX == 1'b0)
            begin
               cur_len     = toptest.CALENDAR_LEN_RX;
            end
            else
            begin
               if(toptest.CALENDAR_SW_SEL_RX == 1'b0)
               begin
                  cur_len     = toptest.CALENDAR_LEN_RX;
               end
               else
               begin
                  cur_len     = toptest.CALENDAR_LEN_RX_SHDW;
               end
            end

            cur_m       = cur_m - 1;
            cal_index   = 0;
         end

         if(cur_m == 0)
         begin
            expect_sync    = 1'b0;
            expect_cal_sel = 1'b0;
            expect_status  = 1'b0;
            expect_dip2    = 1'b1;
         end
         else
         begin
            expect_sync    = 1'b0;
            expect_cal_sel = 1'b0;
            expect_status  = 1'b1;
            expect_dip2    = 1'b0;
         end
      end
      else if(expect_dip2)    // DIP-2 state
      begin
         tstat_dip2     = 1'b1;

         // check DIP2
         if(calc_dip2(1'b0,1'b1,tstat) !== 2'b11)
            -> dip2_error;

         expect_sync    = 1'b1;
         expect_cal_sel = 1'b0;
         expect_status  = 1'b0;
         expect_dip2    = 1'b0;
      end
      else
         -> unknown_tstat;

      @(posedge tsclk);

   end // forever

   go = 1'b0;
end

// ---------------------------------------------------------------------------
// Error reporting
// ---------------------------------------------------------------------------
always @(invalid_cal_sel)
begin
   if(latspi4.error_enable)
   begin
      $display("%t: %m: ERROR: Incorrect Calendar Select word. Got 00, expected 01 or 10",
               $time
            );
      toptest.error_count = toptest.error_count + 1;
   end
end

always @(invalid_tstat)
begin
   if(latspi4.error_enable)
   begin
      $display("%t: %m: ERROR: Incorrect FIFO status word. Got:%b Expecting:00,01,10",
               $time,
               tstat
            );
      toptest.error_count = toptest.error_count + 1;
   end
end

always @(dip2_error)
begin
   if(latspi4.error_enable)
   begin
      $display("%t: %m: ERROR: Incorrect DIP-2",
               $time
            );
      toptest.error_count = toptest.error_count + 1;
   end
end

always @(unknown_tstat)
begin
   if(latspi4.error_enable)
   begin
      $display("%t: %m: ERROR: Unidentified TSTAT word",
               $time
            );
      toptest.error_count = toptest.error_count + 1;
   end
end

always @(credit_timeout)
begin
   if(latspi4.error_enable)
   begin
      $display("%t: %m: ERROR: Timeout waiting for sufficient credit on port %0d. Got:%0d Need:%0d",
               $time,
               burst.port,
               credit[burst.port],
               burst.credit_required
            );
      toptest.error_count = toptest.error_count + 1;
   end
end

// ---------------------------------------------------------------------------
// Functions
// ---------------------------------------------------------------------------
function [15:0]   calc_dip4;
input             ctl;
input   [15:0]    d;
reg     [ 7:0]    dip8;
reg     [ 3:0]    dip4;
begin
   prev_dip16  =   {prev_dip16[0],prev_dip16[15:1]}   ^ d;
   dip8        =    prev_dip16[15:8]                  ^ prev_dip16[7:0];
   dip4        =    dip8      [ 7:4]                  ^ dip8      [3:0];

   if(ctl)
   begin
      calc_dip4   =   {d[15:4],prev_dip4_invld ? ~(dip4) : dip4};
      prev_dip16  =   16'h0000;
   end
   else
      calc_dip4   =   d;
end
endfunction

function [1:0] calc_dip2;
input          sync;
input          dip2;
input    [1:0] d;
begin
   prev_dip2   = {prev_dip2[0],prev_dip2[1]}   ^   d;

   if(sync)      // SYNC word not included in DIP-2 calculation
      prev_dip2   = 2'b00;

   if(dip2)      // Replace DIP-2 word with calculated DIP-2
      calc_dip2   = prev_dip2;
   else
      calc_dip2   = d;
end
endfunction

`ifdef SHOW_IDENTITY
initial
begin
   $display("%M $Id: latspi4_tx.v.rca 1.15 Mon Oct 28 04:44:27 2002 bkerr Experimental $");
end
`endif

endmodule
