//          _/             _/_/
//        _/_/           _/_/_/
//      _/_/_/_/         _/_/_/
//      _/_/_/_/_/       _/_/_/              ____________________________________________ 
//      _/_/_/_/_/       _/_/_/             /                                           / 
//      _/_/_/_/_/       _/_/_/            /                                 28F256P30 / 
//      _/_/_/_/_/       _/_/_/           /                                           /  
//      _/_/_/_/_/_/     _/_/_/          /                                   256Mbit / 
//      _/_/_/_/_/_/     _/_/_/         /                                single die / 
//      _/_/_/ _/_/_/    _/_/_/        /                                           / 
//      _/_/_/  _/_/_/   _/_/_/       /                  Verilog Behavioral Model / 
//      _/_/_/   _/_/_/  _/_/_/      /                               Version 1.3 / 
//      _/_/_/    _/_/_/ _/_/_/     /                                           /
//      _/_/_/     _/_/_/_/_/_/    /           Copyright (c) 2010 Numonyx B.V. / 
//      _/_/_/      _/_/_/_/_/    /___________________________________________/ 
//      _/_/_/       _/_/_/_/      
//      _/_/          _/_/_/  
// 
//     
//             NUMONYX              
`include "cfi_flash_def.h"
`include "cfi_flash_CUIcommandData.h"
`include "cfi_flash_data.h"
`include "cfi_flash_UserData.h"
`include "cfi_flash_BankLib.h"
`include "cfi_flash_TimingData.h"

// **********************************
//
// Timing Lib Module :
// 
//      checks all timing constraints            
//
// **********************************

module TimingLibModule(A, DQ, W_N, G_N, E_N, L_N, WP_N, CK, VPP);  

   input [`ADDRBUS_dim-1:0] A;           // Address Bus 
   input [`DATABUS_dim-1:0] DQ;          // Data I/0 Bus

   input 		    W_N, G_N, E_N, L_N, WP_N, CK;
   input [`Voltage_range]   VPP;

   integer 		    AddValid_time;
   integer 		    AddNotValid_time;
   integer 		    DataValid_time;
   integer 		    DataXX_time;

   integer 		    WriteEnableLow_time;
   integer 		    WriteEnableHigh_time;
   integer 		    OutputEnableLow_time;
   integer 		    OutputEnableHigh_time;
   integer 		    LatchEnableHigh_time;
   integer 		    LatchEnableLow_time;
   integer 		    ChipEnableLow_time; 
   integer 		    ChipEnableHigh_time;
   integer 		    RisingEdge_time;
   integer 		    FallingEdge_time;

   integer 		    WaitValid_time;

   integer 		    WriteProtectHigh_time;
   integer 		    WriteProtectLow_time;
   integer 		    VPPSupplyHigh_time;
   integer 		    VPPSupplyLow_time;

   reg 			    afterReset;

   reg 			    isValid;
   reg 			    dataValid;
   reg 			    addressValid;
   reg 			    reading;
   reg 			    writing;
   reg 			    dataXX;
   time 		    temp;


initial begin

   AddValid_time         = 0;
   AddNotValid_time      = 0;
   DataValid_time        = 0;
   DataXX_time           = 0;

   WriteEnableLow_time   = 0;
   WriteEnableHigh_time  = 0;
   OutputEnableLow_time  = 0;
   OutputEnableHigh_time = 0;
   LatchEnableHigh_time  = 0;
   LatchEnableLow_time   = 0;
   ChipEnableLow_time    = 0;
   ChipEnableHigh_time   = 0;

   WaitValid_time        = 0;

   WriteProtectHigh_time = 0;
   WriteProtectLow_time  = 0;

   RisingEdge_time  = 0;
   FallingEdge_time = 0;

   dataValid = `FALSE;
   dataXX    = `FALSE;
   addressValid = `FALSE;

   reading = `FALSE;
   writing = `FALSE;

   afterReset = `FALSE;

end


// **************
//
// Change address
//
// **************

always @(A) begin : AddressCheck

   if (`TimingChecks == "off") 
      disable AddressCheck;

   if ($time > `Reset_time)
   begin
      if (isAddValid(A))               // Address Valid
      begin
         temp = $time - AddValid_time;
         checkTiming("tAVAV", TimingData_man.tAVAV, temp, "min");

         temp = $time - WriteEnableHigh_time;
         checkTiming("tWHAV", TimingData_man.tWHAV, temp, "min");

         AddValid_time = $time;        
         addressValid = `TRUE;
      end

      else
      begin
         if (isAddXX(A) || isAddZZ(A))             // Address XXXX o ZZZZ
         begin
            
            if (addressValid)
            begin
               temp = $time - LatchEnableHigh_time;
               checkTiming("tLHAX", TimingData_man.tLHAX, temp, "min");

               temp = $time - WriteEnableHigh_time;
               checkTiming("tWHAX", TimingData_man.tWHAX, temp, "min");

               
               AddNotValid_time = $time;        
            end
            addressValid = `FALSE;
         end
      end
   end           
end


// ***********
//
// Change data
//
// ***********

always @(DQ) begin : DataCheck

   if (`TimingChecks == "off") 
      disable DataCheck;

   if ($time > `Reset_time)
   begin

      if (isDataValid(DQ))               // Data Valid
      begin
         if (ConfigReg_man.isSynchronous)               // Synchronous mode 

         begin
            if (reading)
               if (ConfigReg_man.isRisingClockEdge)
               begin
                  temp = $time - RisingEdge_time;
               end
               else
               begin
                  temp = $time - FallingEdge_time;

               end
         end

         else         // Asynchronous mode
         begin        
            
            temp = $time - AddValid_time;

            temp = $time - LatchEnableLow_time;

            temp = $time - ChipEnableLow_time;

            if (reading)
            begin
               temp = $time - OutputEnableLow_time;
               
               temp = $time - WriteEnableHigh_time;
            end

            DataValid_time = $time;   
            dataValid = `TRUE;
            dataXX = `FALSE;
         end
      end         
      
      else
      begin
         if (isDataXX(DQ))                 // Data XXXX
         begin
            if (dataValid) 
            begin
               temp = $time - AddNotValid_time;

               temp = $time - ChipEnableHigh_time;
               checkTiming("tEHQX", TimingData_man.tEHQX, temp, "min");

               temp = $time - OutputEnableHigh_time;

            end

            else    
            begin
               
               temp = $time - ChipEnableLow_time;
               checkTiming("tELQX", TimingData_man.tELQX, temp, "min");

               if (reading)
               begin
                  temp = $time - OutputEnableLow_time;
                  checkTiming("tGLQX", TimingData_man.tGLQX, temp, "min");
               end        
            end


            DataXX_time = $time;
            dataValid = `FALSE;
            dataXX = `TRUE;
            
         end

         else 
            if (isDataZZ(DQ))        
            begin
               if (dataXX) 
               begin
                  temp = $time - ChipEnableHigh_time;
                  checkTiming("tEHQZ", TimingData_man.tEHQZ, temp, "max");
                  temp = $time - OutputEnableHigh_time;
                  checkTiming("tGHQZ", TimingData_man.tGHQZ, temp, "max");
               end

               if (dataValid)
               begin
                  temp = $time - WriteEnableHigh_time;
                  checkTiming("tWHDX", TimingData_man.tWHDX, temp, "min");
		  
               end
               
               dataValid = `FALSE;
               dataXX  = `FALSE;
            end
      end
   end       
end           

// ******************
//
// Change Chip Enable
//
// ******************

always @(posedge E_N) begin : ChipHighCheck    // Chip Enable High

   if (`TimingChecks == "off") 
      disable ChipHighCheck;

   if ($time > `Reset_time)
   begin
      
      temp = $time - WriteEnableHigh_time;
      checkTiming("tWHEH", TimingData_man.tWHEH, temp, "min");

      ChipEnableHigh_time = $time;
   end
end

always @(negedge E_N) begin : ChipLowCheck    // Chip Enable Low

   if (`TimingChecks == "off") 
      disable ChipLowCheck;

   if ($time > `Reset_time)
   begin
      
      ChipEnableLow_time = $time;
   end    

end

always @(posedge L_N) begin : LatchLowCheck    // Latch Enable High

   if (`TimingChecks == "off") 
      disable LatchLowCheck;

   if ($time > `Reset_time)
   begin
      
      temp = $time - AddValid_time;
      checkTiming("tAVLH", TimingData_man.tAVLH, temp, "min");
      
      temp = $time - LatchEnableLow_time;
      checkTiming("tLLLH", TimingData_man.tLLLH, temp, "min");
      
      temp = $time - ChipEnableLow_time;
      checkTiming("tELLH", TimingData_man.tELLH, temp, "min");
      
      LatchEnableHigh_time = $time;
   end
end

always @(negedge L_N)  begin : LatchHighCheck  // Latch Enable Low

   if (`TimingChecks == "off") 
      disable LatchHighCheck;

   if ($time > `Reset_time)
   begin
      
      temp = $time - WriteEnableHigh_time;
      checkTiming("tWHLL", TimingData_man.tWHLL, temp, "min");

      temp = $time - RisingEdge_time;
      checkTiming("tKHLL", TimingData_man.tKHLL, temp, "min");

      LatchEnableLow_time = $time;
   end

end

always  @(posedge G_N) begin : OutputHighCheck    // Output Enable High

   if (`TimingChecks == "off") 
      disable OutputHighCheck;

   if ($time > `Reset_time)
   begin
      
      OutputEnableHigh_time = $time;
      reading = `FALSE;
   end

end

always @(negedge G_N) begin : OutputLowCheck    // Output Enable Low

   if (`TimingChecks == "off") 
      disable OutputLowCheck;

   if ($time > `Reset_time)
   begin

      
      temp = $time - WriteEnableHigh_time;
      checkTiming("tWHGL", TimingData_man.tWHGL, temp, "min");


      OutputEnableLow_time = $time;
      reading = `TRUE;
   end    

end

always @(posedge W_N) begin : WriteHighCheck    // Write Enable High

   if (`TimingChecks == "off") 
      disable WriteHighCheck;

   if ($time > `Reset_time)
   begin

      
      temp = $time - AddValid_time;
      checkTiming("tAVWH", TimingData_man.tAVWH, temp, "min");

      if (writing)
      begin
         temp = $time - WriteEnableLow_time;
         checkTiming("tWLWH", TimingData_man.tWLWH, temp, "min");
      end        
      
      temp = $time - DataValid_time;
      checkTiming("tDVWH", TimingData_man.tDVWH, temp, "min");

      temp = $time - WriteProtectHigh_time;
      checkTiming("tWPHWH", TimingData_man.tWPHWH, temp, "min");

      temp = $time - VPPSupplyHigh_time;
      checkTiming("tVPHWH", TimingData_man.tVPHWH, temp, "min");

      WriteEnableHigh_time = $time;
      writing = `FALSE;
   end
end

always @(negedge W_N) begin : WriteLowCheck    // Write Enable Low

   if (`TimingChecks == "off") 
      disable WriteLowCheck;

   if ($time > `Reset_time)
   begin
      
      temp = $time - ChipEnableLow_time;
      checkTiming("tELWL", TimingData_man.tELWL, temp, "min");

      temp = $time - WriteEnableHigh_time;
      checkTiming("tWHWL", TimingData_man.tWHWL, temp, "min");

      WriteEnableLow_time = $time;
      writing = `TRUE; 
   end
end

always  @(posedge WP_N) begin : WPHighCheck            // Write Protect High

   if (`TimingChecks == "off") 
      disable WPHighCheck;

   if ($time > `Reset_time)
   begin
      
      WriteProtectHigh_time = $time; 
   end   
end

always  @(negedge WP_N) begin : WPLowCheck               // Write Protect Low

   if (`TimingChecks == "off") 
      disable WPLowCheck;

   if ($time > `Reset_time)
   begin
      
      temp = $time - DataValid_time;
      checkTiming("tQVWPL", TimingData_man.tQVWPL, temp, "min");

      WriteProtectLow_time = $time;
   end   
end

always @(posedge VPP) begin : VPPHighCheck            // Write Protect High

   if (`TimingChecks == "off") 
      disable VPPHighCheck;

   if ($time > `Reset_time)
   begin
      
      VPPSupplyHigh_time = $time; 
   end     
end

always @(negedge VPP) begin : VPPLowCheck               // Write Protect Low

   if (`TimingChecks == "off") 
      disable VPPLowCheck;

   if ($time > `Reset_time)
   begin
      
      temp = $time - DataValid_time;
      checkTiming("tQVVPL", TimingData_man.tQVVPL, temp, "min");

      VPPSupplyLow_time = $time;

   end   
end

always @(posedge CK) begin : RisingCKCheck              

   if (`TimingChecks == "off") 
      disable RisingCKCheck;

   if ($time > `Reset_time)
   begin
      temp = $time - LatchEnableLow_time;
      checkTiming("tLLKH", TimingData_man.tLLKH, temp, "min");

      
      RisingEdge_time = $time;
   end   
end

always @(negedge CK) begin : FallingCKCheck                

   if (`TimingChecks == "off") 
      disable FallingCKCheck;

   if ($time > `Reset_time)
   begin
      temp = $time - LatchEnableLow_time;
      checkTiming("tLLKL", TimingData_man.tLLKL, temp, "min");
      
      FallingEdge_time = $time;
      
   end   
end



// **********************************************
//
// FUNCTION isAddValid :
//      return true if the input address is valid
//
// **********************************************

function isAddValid;

   input [`ADDRBUS_dim - 1 : 0] Add;

   reg [`ADDRBUS_dim - 1 : 0] 	Add;

   reg 				valid;
   integer 			count;

   begin

      valid = `TRUE;
      begin : cycle
         for (count = 0; count <= `ADDRBUS_dim - 1; count = count + 1)
         begin
            if ((Add[count] !== 1'b0) && (Add[count] !== 1'b1))
            begin
               valid = `FALSE;
               disable cycle;
            end
         end
      end                
      
      isAddValid = valid;
   end
endfunction


// *********************************************
//
// FUNCTION isAddXX :
//      return true if the input address is XXXX
//
// *********************************************

function isAddXX;

   input [`ADDRBUS_dim - 1 : 0] Add;

   reg [`ADDRBUS_dim - 1 : 0] 	Add;

   reg 				allxx;
   integer 			count;

   begin

      allxx = `TRUE;
      begin : cycle
         for (count = 0; count <= `ADDRBUS_dim - 1; count = count + 1)
         begin
            if (Add[count] !== 1'bx)
            begin
               allxx = `FALSE;
               disable cycle;
            end
         end
      end                
      
      isAddXX = allxx;
   end
endfunction

// *********************************************
//
// FUNCTION isAddZZ :
//      return true if the input address is ZZZZ
//
// *********************************************

function isAddZZ;

   input [`ADDRBUS_dim - 1 : 0] Add;

   reg [`ADDRBUS_dim - 1 : 0] 	Add;

   reg 				allzz;
   integer 			count;

   begin

      allzz = `TRUE;
      begin : cycle
         for (count = 0; count <= `ADDRBUS_dim - 1; count = count + 1)
         begin
            if (Add[count] !== 1'bz)
            begin
               allzz = `FALSE;
               disable cycle;
            end
         end
      end                
      
      isAddZZ = allzz;
   end
endfunction

// **********************************************
//
// FUNCTION isDataValid :
//      return true if the data is valid
//
// **********************************************

function isDataValid;

   input [`DATABUS_dim - 1 : 0] Data;

   reg [`DATABUS_dim - 1 : 0] 	Data;

   reg 				valid;
   integer 			count;

   begin

      valid = `TRUE;
      begin : cycle
         for (count = 0; count <= `DATABUS_dim - 1; count = count + 1)
         begin
            if ((Data[count] !== 1'b0) && (Data[count] !== 1'b1))
            begin
               valid = `FALSE;
               disable cycle;
            end
         end
      end                
      
      isDataValid = valid;
   end
endfunction

// ***************************************
//
// FUNCTION isDataXX :
//      return true if the data is unknown
//
// ***************************************

function isDataXX;

   input [`DATABUS_dim - 1 : 0] Data;

   reg [`DATABUS_dim - 1 : 0] 	Data;

   reg 				allxx;
   integer 			count;

   begin

      allxx = `TRUE;
      begin : cycle
         for (count = 0; count <= `DATABUS_dim - 1; count = count + 1)
         begin
            if (Data[count] !== 1'bx) 
            begin
               allxx = `FALSE;
               disable cycle;
            end
         end
      end                
      
      isDataXX = allxx;
   end
endfunction

// ************************************
//
// FUNCTION isDataZZ :
//      return true if the data is Hi-Z
//
// ************************************

function isDataZZ;

   input [`DATABUS_dim - 1 : 0] Data;

   reg [`DATABUS_dim - 1 : 0] 	Data;

   reg 				allzz;
   integer 			count;

   begin

      allzz = `TRUE;
      begin : cycle
         for (count = 0; count <= `DATABUS_dim - 1; count = count + 1)
         begin
            if (Data[count] !== 1'bz) 
            begin
               allzz = `FALSE;
               disable cycle;
            end
         end
      end                
      
      isDataZZ = allzz;
   end
endfunction

// *****************************
//
// Task Check Timing
//      check timing constraints
//
// *****************************

task checkTiming;
   input [8*6:1] tstr;
   input [31:0]  tOK, tcheck;
   input [8*3:1] check_str;
   
   begin
      if ((check_str == "min") && (tcheck < tOK)) begin
         $display ("[%t]  !ERROR: %0s timing constraint violation!! ", $time, tstr);
      end         

      else 
         if ((check_str == "max") && (tcheck > tOK))
            $display ("[%t]  !ERROR: %0s timing constraint violation!! ", $time, tstr);
   end
endtask


endmodule


// Protect Manager
// implements the architecture of the memory blocks

module BlockLockModule(address, WP_N, RP_N, Info);
   input [`ADDRBUS_range] address;
   input 		  WP_N, RP_N;
   input 		  Info;

   reg 			  LockArray [`BLOCK_dim - 1 : 0]; 
   reg 			  LockDownArray [`BLOCK_dim - 1 : 0];  

   reg [`BYTE_range] 	  Status;
   integer 		  count;
initial begin              // constructor sequence         
   
   for (count = 0; count <= `BLOCK_dim - 1; count = count + 1)             // all blocks are locked at power-up
   begin 
      LockDownArray[count] = `UNLOCKDOWN;
      if (`BLOCKPROTECT == "on") LockArray[count] = `LOCK;
      else LockArray[count] = `UNLOCK;
   end
end

always @(negedge RP_N) begin
   initLockArray;
end

task initLockArray;
   begin 
      
      for (count = 0; count <= `BLOCK_dim - 1; count = count + 1)             // all blocks are locked at power-up
      begin 
         LockDownArray[count] = `UNLOCKDOWN;
         LockArray[count] = `LOCK;
      end
   end
endtask



// ********************************************
//
// FUNCTION isLocked : return the status of the 
//                     specified block 
//
// ********************************************

function IsLocked;                     // boolean function primitive   
   
   input [`ADDRBUS_range] address;
   
   integer 		  n_block;

   begin
      
      n_block  = BankLib_man.getBlock(address); 
      IsLocked = (LockArray[n_block] == `LOCK) ? `TRUE : `FALSE;
      
   end
endfunction  

// ********************************************
//
// FUNCTION isLocked : return the status of the 
//                     specified block 
//
// ********************************************

function IsUnLocked;                     // boolean function primitive   
   
   input [`ADDRBUS_range] address;
   integer 		  n_block;

   begin
      
      n_block  = BankLib_man.getBlock(address); 
      IsUnLocked = (LockArray[n_block] == `UNLOCK) ? `TRUE : `FALSE;
      
   end
endfunction  


function getLockBit;                     // boolean function primitive   
   
   input [`ADDRBUS_range] address;
   integer 		  n_block;

   begin
      
      n_block  = BankLib_man.getBlock(address); 
      getLockBit = LockArray[n_block];
      
   end
endfunction  

function getLockDownBit;                     // boolean function primitive   
   
   input [`ADDRBUS_range] address;
   integer 		  n_block;

   begin
      
      n_block  = BankLib_man.getBlock(address); 
      getLockDownBit = LockDownArray[n_block];
      
   end
endfunction  


// ********************************
//
// Task UnLock :
//    implements Block UnLock Command
//
// ********************************

task UnLock;
   
   output [`BYTE_range] Status;
   reg [`BYTE_range] 	Status;

   integer 		n_block;
   
   begin
      
      n_block = BankLib_man.getBlock(address);
      Status = `NoError_msg;
      if (LockDownArray[n_block]==`LOCKDOWN && WP_N==`LOW) Status = `NoUnLock_msg;
      else  LockArray[n_block] = `UNLOCK;
   end
endtask

// ********************************
//
// Task Lock :
//    implements Block Lock Command
//
// ********************************

task Lock;
   
   output [`BYTE_range] Status;
   reg [`BYTE_range] 	Status;

   integer 		n_block;
   
   begin
      
      n_block = BankLib_man.getBlock(address);
      Status = `NoError_msg;
      LockArray[n_block] = `LOCK;
   end
endtask

// *****************************************
//
// Task LockDown :
//    implements the Block Lock-Down Command
//
// *****************************************

task LockDown;
   
   output [`BYTE_range] Status;
   reg [`BYTE_range] 	Status;

   integer 		n_block;
   
   begin
      
      n_block = BankLib_man.getBlock(address);
      Status = `NoError_msg;
      LockDownArray[n_block] = `LOCKDOWN;
      
   end 
endtask


endmodule 


// *************************
//
// CFI Query Module
// Implements the CFI memory
//
// *************************

module CFIqueryModule();   //, isCFI);

//input isCFI;

   reg [`BYTE_range] CFIarray [0:`CFI_dim];
   reg 		     error;
   reg [8*20:1]      CFI_file;
   integer 	     i;

initial begin
   if (`organization == "top") CFI_file = "CFImemory_top.vmf";
   else CFI_file= "CFImemory_bottom.vmf";

   for (i=0; i <= `CFI_dim; i = i + 1) CFIarray[i] = {8{`HIGH}};   // CFI Memory Init
   $readmemb(CFI_file,CFIarray);
end 

always @(posedge error)  begin
   Kernel.SetWarning(`RCFI_cmd,16'h00,`CFIAddrRange_msg);
   error = `FALSE;
end

function [`WORD_range] Get;

   input [`ADDRBUS_range] address;
   
   begin
      if (address[`BYTE_range] >= 9'h10 && address[`BYTE_range] <= `CFI_dim )  //verificare se tener conto che il primo indirizzo accessibile e' 10h
      begin 
         if (address[`BYTE_range] >= 9'h39 && address[`BYTE_range] <= 9'h109) begin
            Get = 8'hXX;
            error = `TRUE;
         end else begin

            Get[`LOW_range] = CFIarray[address[`BYTE_range]];
            Get[`HIGH_range] = 8'h00;
         end
         
      end else 
      begin
         Get = 8'hXX;
         error = `TRUE;
      end
   end
endfunction

endmodule

// ********************************
//
// Data Error Module
// 
//      search for errors in data.h
//
// ********************************

module DataErrorModule;  

   reg SevError;

initial begin

   SevError = `FALSE;

   if ((`organization != "top") && (`organization != "bottom")) 
   begin
      SevError = `TRUE;
      $display("!Error: BLOCK ORGANIZATION INVALID: it must be top or bottom!!!"); 
   end        

   if ((`BLOCKPROTECT != "on") && (`BLOCKPROTECT != "off")) 
   begin
      SevError = `TRUE;
      $display("!Error: BLOCK PROTECT INVALID: it must be on or off!!!");
   end

   if ((`TimingChecks != "on") && (`TimingChecks != "off")) 
   begin
      SevError = `TRUE;
      $display("!Error: TIMING CHECKS INVALID: it must be on or off!!!");
   end


   if ((`t_access != 100) && (`t_access != 110))
   begin
      SevError = `TRUE;
      $display("!Error: Access time INVALID: it must be 100 ns or 110 ns!!!");
   end


   if (SevError) $finish;
end

endmodule

// ******************************************
//
// Configuration Register module :
//
//      implements the configuration register
//
// ******************************************

module ConfigRegModule(address,Info);  
   input [`ADDRBUS_range] address;
   input 		  Info;

   reg [`ConfigurationReg_dim - 1 : 0] CR_reg;
   reg [`BYTE_range] 		       Status;

// **********************
//
// Setting default values
//
// **********************

`define ReadMode_bit         15
`define ClockLatency_MSBbit  14
`define ClockLatency_LSBbit  11
`define WaitPolarity_bit     10
`define WaitConfig_bit       8
`define BurstType_bit        7
`define ValidClockEdge_bit   6
`define WrapBurst_bit        3
`define BurstLength_MSBbit   2
`define BurstLength_LSBbit   0

// Interpreter  Config Reg\\

   wire 			       isASynchronous      = CR_reg[`ReadMode_bit] ? `TRUE : `FALSE;
   wire 			       isSynchronous       = CR_reg[`ReadMode_bit] ? `FALSE : `TRUE;
   wire [3:0] 			       Xlatency      = (CR_reg[`ClockLatency_MSBbit : `ClockLatency_LSBbit]<2 &&
							CR_reg[`ClockLatency_MSBbit : `ClockLatency_LSBbit]>15) ? 0 : 
				       CR_reg[`ClockLatency_MSBbit : `ClockLatency_LSBbit];
   wire 			       isWaitPolActiveHigh = CR_reg[`WaitPolarity_bit] ? `TRUE : `FALSE;
   wire 			       isWaitBeforeActive    = CR_reg[`WaitConfig_bit] ? `TRUE : `FALSE;
   wire 			       isRisingClockEdge   = CR_reg[`ValidClockEdge_bit] ? `TRUE : `FALSE;
   wire 			       isWrapBurst         = CR_reg[`WrapBurst_bit] ? `FALSE : `TRUE;
   wire 			       isNoWrapBurst       = CR_reg[`WrapBurst_bit] ? `TRUE : `FALSE;

   wire [4:0] 			       BurstLength   = CR_reg[`BurstLength_MSBbit : `BurstLength_LSBbit] == 1 ? 4 : 
				       CR_reg[`BurstLength_MSBbit : `BurstLength_LSBbit] == 2 ? 8 : 
				       CR_reg[`BurstLength_MSBbit : `BurstLength_LSBbit] == 3 ? 16:
				       0; // continous Burst


   wire [2:0] 			       BurstLength_bit = CR_reg[`BurstLength_MSBbit : `BurstLength_LSBbit] == 1 ? 2 : 
				       CR_reg[`BurstLength_MSBbit : `BurstLength_LSBbit] == 2 ? 3 : 
				       CR_reg[`BurstLength_MSBbit : `BurstLength_LSBbit] == 3 ? 4:
				       0; // continous Burst

initial begin    
   Status = `NoError_msg;
   CR_reg = `ConfigReg_default; 
end 

always @(isSynchronous) begin
   if (Info)
      if (isSynchronous) 
         $write("[%t]  Synchronous Read Mode\n",$time);
      else 
         $write("[%t]  ASynchronous Read Mode\n",$time);
end 
// **********************
//
// ReSet to default value
//
// **********************
always @(Kernel.ResetEvent) begin 
   Status = `NoError_msg;
   CR_reg = `ConfigReg_default;
end

// **************************************
//
// FUNCTION getConfigReg :
//
//      return the Configuration Register
//
// **************************************

function [`ConfigurationReg_dim - 1 : 0] getConfigReg;
   input required;
   begin
      getConfigReg = CR_reg;
   end
endfunction

// *************************************
//
// FUNCTION putConfigReg :
//
//      write the Configuration Register
//
// *************************************

task putConfigReg;
   output [`BYTE_range] outStatus;
   
   reg [`BYTE_range] 	outStatus;

   integer 		count;

   begin
      
      CR_reg = address[`ConfigurationReg_dim - 1 : 0];

      outStatus = Status;        

   end
endtask


endmodule

// ***************************
//
// Electronic Signature Module
//
// ***************************

module SignatureModule;  

   reg error;
   integer i;
   integer n_block;

initial begin
end 

always @(posedge error)  begin
   Kernel.SetWarning(`RSIG_cmd,16'h00,`SignAddrRange_msg);
   error = `FALSE;
end

function [`WORD_range] Get;

   input [`ADDRBUS_range] address;
   
   begin
      if (address[`SignAddress_range] == 9'h00) 
      begin
         Get[`LOW_range] = `ManufacturerCode; 
         Get[`HIGH_range] = 8'h00;
      end
      else if (address[`SignAddress_range] == 9'h01)
      begin 
         if (`organization == "top") Get[`LOW_range] = `TopDeviceCode;  
         else  Get[`LOW_range] = `BottomDeviceCode;  

         Get[`HIGH_range] = 8'h89;
      end
      else if (address[`SignAddress_range] == 9'h02)
      begin 
         Get[`LOW_range] = { 6'b0, BlockLock_man.getLockDownBit(address), BlockLock_man.getLockBit(address) };
         Get[`HIGH_range] =  8'h00; 
      end
      else if (address[`SignAddress_range] == 9'h05)                       // Configuration Register
         Get = ConfigReg_man.getConfigReg(0);       
      else if ((address[`SignAddress_range] >= `REGSTART_addr) && (address[`SignAddress_range] <= `REGEND_addr)) 
      begin 
         Get = ProtectReg_man.RegisterMemory[address[`SignAddress_range] - `REGSTART_addr ];

      end
      else begin
         Get = 8'hXX;
         error = `TRUE;
      end
   end
endfunction

endmodule

// ********************
//
// CUI decoder module :
//      decode commands
//
// ********************

module CUIdecoder1(DataBus,Name,Cmd,CmdAllowed,Info);
   input [`BYTE_range] DataBus, Cmd;
   input [8*35:1]      Name;
   input 	       Info;
   input 	       CmdAllowed;         
always @Kernel.CUIcommandEvent begin
   #1;
   if (DataBus == Cmd  && CmdAllowed) begin  // is a First Command ?
      #1 -> Kernel.VerifyEvent;
      Kernel.CommandDecode1[Cmd] = !Kernel.CommandDecode1[Cmd];
      if (Info) $display("[%t]  Command Issued: %0s",$time,Name);
   end
   else begin
      if (`FALSE) $display("[%t]  The %0s instruction decode unit is waiting for operation to complete.",$time,Name);
      @(Kernel.CompleteEvent or Kernel.ErrorEvent)
	 if (`FALSE) $display("[%t]  The %0s instruction decode unit is listening for next command.",$time,Name);
   end
end
endmodule


// ********************
//
// CUIdecoder manager :
//      decode commands
//
// ********************

module CUIdecoder2(DataBus,Name,Cmd1,Cmd2,CmdAllowed,Info);
   input [`BYTE_range] DataBus, Cmd1, Cmd2;
   input [8*27:1]      Name;
   input 	       Info;
   input 	       CmdAllowed; 

always @Kernel.CUIcommandEvent begin
   if (DataBus == Cmd1 && CmdAllowed) begin
      #1 -> Kernel.VerifyEvent;

      @Kernel.CUIcommandEvent

	 if (DataBus == Cmd2 && CmdAllowed)  begin
	    #1  -> Kernel.VerifyEvent;

            Kernel.CommandDecode2[{Cmd1,Cmd2}] = !Kernel.CommandDecode2[{Cmd1,Cmd2}];
            if (Info) $display("[%t]  Command Issued: %0s",$time,Name);
	 end
   end
   else begin
      if (`FALSE) $display("%t  The %0s instruction decode unit is waiting for operation to complete.",$time,Name);
      @(Kernel.CompleteEvent or Kernel.ErrorEvent)
	 if (`FALSE) $display("%t  The %0s instruction decode unit is listening for next command",$time,Name);
   end
end

endmodule


// ****************************
//
// CUI Decoder Manager :
//      decode the cUI commands
//
// ****************************

module CUIdecoder_Busy1(DataBus,Name,Cmd,CmdAllowed,Info);
   input [`BYTE_range] DataBus, Cmd;
   input [8*8:1]       Name;
   input 	       Info;
   input 	       CmdAllowed; 

always @Kernel.CUIcommandEvent begin
   if ((DataBus == Cmd) && CmdAllowed) begin
      -> Kernel.VerifyEvent;
      Kernel.CommandDecode1[Cmd] = !Kernel.CommandDecode1[Cmd];
      if (Info) $display("[%t]  Command Issued: %0s",$time,Name);
   end



end

endmodule


// Erase Manager
// manage the erase functionality

module EraseModule(address, data, progVoltOK, progHighVoltOK,Info);
   input [`ADDRBUS_range] address;
   input [`WORD_range] 	  data;

   input 		  progVoltOK, progHighVoltOK;

   input 		  Info;
   event 		  ErrorCheckEvent, CompleteEvent;

   reg [`BYTE_range] 	  Status;
   reg [`ADDRBUS_range]   hold_address;
   reg [`BLOCKADDR_range] hold_block;

   reg 			  Busy, Suspended, first_time;
   integer 		  i;
   time 		  startTime, delayTime,Erase_time;

initial begin                   // constructor sequence             
   Busy       = `FALSE;                                                    
   Suspended  = `FALSE;                                               
   Erase_time = `MainBlockErase_time;
   delayTime  =  Erase_time;                                      

end         


function IsBusy;                // boolean function primitive       
   input obbl;                     // all functions require a parameter
   IsBusy = Busy;                // return Boolean value             
endfunction                                                         

function IsSuspended;           // boolean function primitive       
   input obbl;                     // all functions require a parameter
   IsSuspended = Suspended;      // return Boolean value             
endfunction                                                         

function IsAddrSuspended;       // boolean function primitive       
   input [`ADDRBUS_range] addr;
   IsAddrSuspended = (Suspended && (addr == hold_address));
endfunction

function IsBlockSuspended;       // boolean function primitive       
   input [`ADDRBUS_range] addr;
   IsBlockSuspended = (Suspended && ((BankLib_man.getBlock(addr) == BankLib_man.getBlock(/*hold_*/addr/*ess*/))));
endfunction

// *********************
//
// Task checkConfirm :
//    check confirm code
//
// *********************

task checkConfirm;
   
   output  [`BYTE_range] outStatus;
   
   reg [`BYTE_range] 	 outStatus;

   begin

      if (data == `BLKEEconfirm_cmd) outStatus = `NoError_msg;
      else outStatus = `WrongEraseConfirm_msg;

   end
endtask


task Suspend;
   output [`BYTE_range] outStatus;
   reg [`BYTE_range] 	outStatus;
   begin
      delayTime = delayTime - ($time - startTime);
      #`EraseSuspendLatency_time
	 outStatus = `NoError_msg;
      Status = `Suspend_msg;
      Suspended = `TRUE;
      -> CompleteEvent;
   end
endtask

task Resume;
   output [`BYTE_range] Status;
   begin
      Suspended = `FALSE;
      BlockErase(Status);
   end
endtask

task BlockErase;
   output [`BYTE_range] outStatus;
   reg [`BYTE_range] 	outStatus;
   begin


      if (progHighVoltOK) 
         if (BankLib_man.isMainBlock(address)) Erase_time = `FastMainBlockErase_time;
         else  Erase_time = `FastParameterBlockErase_time;
      else 
         if (BankLib_man.isMainBlock(address)) Erase_time   = `MainBlockErase_time;
         else  Erase_time  = `ParameterBlockErase_time;

      delayTime 					      = Erase_time;
      hold_address 					      = address;
      hold_block 					      = BankLib_man.getBlock(address);



      fork
         begin : Operation
            Busy = `TRUE;
            startTime = $time;
            -> ErrorCheckEvent;
            #delayTime Memory_man.EraseBlock(hold_block,Status);
            delayTime = Erase_time;
            -> CompleteEvent;
         end
         @CompleteEvent
            disable Operation;
      join
      outStatus = Status;
      Busy = `FALSE;
   end
endtask

always @(ErrorCheckEvent) begin
   Status = `NoError_msg;
   if (BlockLock_man.IsLocked(hold_address))
      Status = `BlockLock_msg;
   else if (Memory_man.IsBlockSuspended(hold_address))      
      Status = `SuspCmd_msg;
   else if (!progVoltOK)
      Status = `InvVDD_msg;

   if (Status != `NoError_msg)
      ->CompleteEvent;
   else
      fork : ErrorCheck
         @(negedge progVoltOK) Status = `InvVDD_msg;
         @(Status) -> CompleteEvent;
         @(CompleteEvent) disable ErrorCheck;
      join
end

endmodule  //end module Erase


// *********************
//
// Memory Manager :
//      the memory array
// 
// *********************

module MemoryModule(Info);
   input Info;
   reg [`WORD_range] memory [0:(`MEMORY_dim) - 1];     // the Memory: word organization

initial begin 
   LoadMemory;
end 

task LoadMemory;                                // Initialize and load the memory from a file
   integer i;     
   begin
      #0 if (Info) $display("[%t] Inizialize the Memory to default value",$time);
      
      for (i = 0; i < `MEMORY_dim; i = i + 1)  memory[i] = {16{`HIGH}};    // Memory Init
      
      if (`FILENAME_mem !== "") begin 
         $readmemh(`FILENAME_mem, memory);

         if (Info) $display("[%t] Load Memory from file: %s",$time, `FILENAME_mem);
         else if (Info) $display("[%t] Warning: File: %s not found",$time, `FILENAME_mem);
      end
   end
endtask


function [`WORD_range] Get;
   input [`ADDRBUS_range] address;
   Get = memory[address];
endfunction


function IsSuspended;
   input [`ADDRBUS_range] address;
   IsSuspended = Program_man.IsAddrSuspended(address) || Erase_man.IsAddrSuspended(address) || ProgramBuffer_man.IsAddrSuspended(address);
endfunction

function IsBlockSuspended;
   input [`ADDRBUS_range] address;
   IsBlockSuspended = Program_man.IsBlockSuspended(address) || Erase_man.IsBlockSuspended(address);
endfunction


task Program;
   input [`WORD_range] data;
   input [`ADDRBUS_range] address;
   output [`BYTE_range]   Status;
begin
   Status = `NoError_msg;
   memory[address] = memory[address] & data;
   if (memory[address] != data) Status = `PreProg_msg;
end
endtask

task EraseBlock;
   
   input [`INTEGER] block;
   
   output [`BYTE_range] ErrFlag;

   reg [`ADDRBUS_range] start_address;
   reg [`ADDRBUS_range] end_address;
   reg [`ADDRBUS_range] address;

   
   begin
      ErrFlag 	     = `NoError_msg;
      start_address  = BankLib_man.getBlockAddress(block);
      end_address    = BankLib_man.BlockBoundaryEndAddr[block];

      if (start_address > end_address)
      begin
         address = start_address;
         start_address = end_address;
         end_address = address;
      end
      
      
      for (address = start_address; address <= end_address; address = address + 1)
         memory[address] = `WORDNP;

   end
endtask

task BlockBlankCheck;
   
   input [`INTEGER] block;
   
   output [`BYTE_range] ErrFlag;

   reg [`ADDRBUS_range] start_address;
   reg [`ADDRBUS_range] end_address;
   reg [`ADDRBUS_range] address;

   
   begin
      ErrFlag = `NoError_msg;
      start_address = BankLib_man.BlockBoundaryStartAddr[block];
      end_address   = BankLib_man.BlockBoundaryEndAddr[block];

      if (start_address > end_address)
      begin
         address = start_address;
         start_address = end_address;
         end_address = address;
      end
      
      ErrFlag = `NoError_msg;
      address = start_address;
      while (memory[address] == `WORDNP && address <= end_address ) 
         address = address + 1;
      if (memory[address] != `WORDNP)  
         ErrFlag = `BlankCheckFailed_msg;

   end
endtask 



endmodule //end MemoryModule 


// ***************************************
//
// Output Buffer :
//
//      manage the communication between 
//      the memory and the output data bus
//
// ***************************************

module OutputBufferModule(DataInput, DataInputBurst, DataOutput, OutputEnable);
   input [`WORD_range] DataInput;
   input [`WORD_range] DataInputBurst;
   output [`WORD_range] DataOutput;
   input 		OutputEnable;
   reg [`WORD_range] 	DataOutput;
   time 		timeDataV, timeDataX, timeDataZ;

initial begin
   timeDataV=0;
   timeDataX=0;
   timeDataZ=0;
   SetZ(0);
end

task SetValid;
   input [63:0] delayTime;
   begin

      if ((delayTime+$time > timeDataV) || (timeDataV < $time)) begin
         timeDataV = delayTime + $time;


         disable waitValid;


         disable goValid;

      end
   end
endtask

always   
   fork 
      begin:  goValid

	 #(timeDataV - $time) 
         if (OutputEnable == 1'b0) begin 
            
            if (ConfigReg_man.isASynchronous) DataOutput = DataInput;
            else DataOutput = DataInputBurst;
         end                                  
         
      end // goValid
      begin: waitValid
	 wait (`FALSE);
      end
   join

task SetX;
   input [63:0] delayTime;
   begin
      if ((delayTime+$time < timeDataX) || (timeDataX < $time)) begin
         timeDataX = delayTime + $time;
         disable waitX;


      end
   end
endtask

always fork
   begin : goX
      #(timeDataX - $time) if ((OutputEnable == `LOW) || (timeDataZ > timeDataX))
         DataOutput = 16'hX;
   end // goX
   begin: waitX
      wait (`FALSE);
   end
join

task SetZ;
   input [63:0] delayTime;
   begin
      if ((delayTime+$time < timeDataZ) || (timeDataZ < $time)) begin
         timeDataZ = delayTime + $time;
         disable waitZ;
         if (timeDataZ < timeDataV)
            disable goValid;
         if (timeDataZ < timeDataX)
            disable goX;
      end
   end
endtask

always begin: waitZ
   #(timeDataZ - $time) DataOutput = 16'hZ;
   wait (`FALSE);
end

endmodule


// *********************************
//
// Program module :
//
//      manage the program operation
//
// *********************************

module ProgramModule(address,data, progVoltOK, progHighVoltOK, Info);
   input [`WORD_range] data;
   input [`ADDRBUS_range] address;
   input 		  progVoltOK,progHighVoltOK;
   input 		  Info;
   event 		  ErrorCheckEvent, CompleteEvent;
   reg [`BYTE_range] 	  Status;
   reg [`WORD_range] 	  hold_data;
   reg [`ADDRBUS_range]   hold_address;
   reg 			  Busy, Suspended;

   integer 		  i;
   time 		  startTime, delayTime, WordProgram_time;

initial begin                 // constructor sequence
   Busy = `FALSE;
   Suspended = `FALSE;
   WordProgram_time = `WordProgram_time;
   delayTime = WordProgram_time;
end

always @(progHighVoltOK) begin
   if (progHighVoltOK) WordProgram_time=`FastWordProgram_time;
   else WordProgram_time=`WordProgram_time;
end 

function IsBusy;              // boolean function primitive
   input obbl;                   // all functions require a parameter
   IsBusy = Busy;              // return Boolean value
endfunction

function IsSuspended;         // boolean function primitive
   input obbl;                   // all functions require a parameter
   IsSuspended = Suspended;    // return Boolean value 
endfunction                                                       

function IsAddrSuspended;     // boolean function primitive       
   input [`ADDRBUS_range] addr;
   IsAddrSuspended = (Suspended && (addr == hold_address));
endfunction

function IsBlockSuspended;    // return true if block is suspended
   input [`ADDRBUS_range] addr; begin
      IsBlockSuspended  = (Suspended && (BankLib_man.getBlock(addr) == BankLib_man.getBlock(/*hold_*/addr/*ess*/)));
   end
endfunction


task Suspend;
   output [`BYTE_range] suspErrFlag;
   reg [`BYTE_range] 	suspErrFlag;
   begin
      delayTime = delayTime - ($time - startTime);
      #`ProgramSuspendLatency_time suspErrFlag = `NoError_msg;
      Status = `Suspend_msg;
      Suspended = `TRUE;
      -> CompleteEvent;
   end
endtask

task Resume;
   output [`BYTE_range] ErrFlag;
   begin
      Suspended = `FALSE;
      Program(ErrFlag);
end
endtask

task Program;
   output [`BYTE_range] outErrFlag;
   reg [`BYTE_range] 	outErrFlag;
begin
   if (delayTime == WordProgram_time) begin
      hold_data = data;
      hold_address = address;
   end
   fork
      begin : Operation
         Busy = `TRUE;
         startTime = $time;
         -> ErrorCheckEvent;
         #delayTime Memory_man.Program(hold_data,hold_address,Status);
delayTime = `WordProgram_time;
-> CompleteEvent;
end
      @CompleteEvent disable Operation;
   join
   outErrFlag = Status;
   Busy = `FALSE;
end
endtask

always @(ErrorCheckEvent) begin
   Status = `NoError_msg;
   if (BlockLock_man.IsLocked(hold_address))
      Status = `BlockLock_msg;
   else
      if (Memory_man.IsSuspended(hold_address))
	 Status = `SuspAcc_msg;
      else if (!progVoltOK)
	 Status = `InvVDD_msg;

   if (Status != `NoError_msg) ->CompleteEvent;
   else
      fork : ErrorCheck
         @(negedge progVoltOK) Status = `InvVDD_msg;
         @(Status) -> CompleteEvent;
         @(CompleteEvent) disable ErrorCheck;
      join
end

endmodule // end PrograModule 


// *********************************
//
// Buffer Ehnanced Program module :
//
//      program buffer functionality
//
// *********************************

module BuffEnhancedFactProgramModule(address, data, progVoltOK, progHighVoltOK, Info);
   input [`ADDRBUS_range] address;
   input [`WORD_range] 	  data;
   input 		  progVoltOK, progHighVoltOK, Info;

   event 		  ErrorCheckEvent,ErrorCheckEvent_inVerify, CompleteEvent, WatchAddressEvent;
   reg [`BYTE_range] 	  Status;
   reg [`WORD_range] 	  hold_data, hold_StartBlock;
   reg [`ADDRBUS_range]   hold_address, startAddress;
   reg [`WORD_range] 	  bufferData [`BuffEnhProgramBuffer_range];

   reg 			  Busy;
   time 		  Program_time;
   integer 		  i,Len;

initial begin                 // constructor sequence             
   Busy = `FALSE;
   Status = `NoError_msg;
   Program_time = `WordProgram_time;
   EmptyBuffer;
end                                                               

task EmptyBuffer;
   begin 
      for (i = 0; i < `BuffEnhProgramBuffer_dim; i = i + 1) 
         bufferData[i] = 16'hFFFF;
      Len=0;
   end 
endtask

function IsBusy;              // boolean function primitive       
   input obbl;               // all functions require a parameter
   IsBusy = Busy;              // return Boolean value             
endfunction                                                       

task Setup;
   output [`BYTE_range] outStatus;
   begin
      Status 	       = `NoError_msg;
      Len	       =0;
      startAddress     = address;
      hold_address     = address;

      hold_StartBlock  = BankLib_man.getBlock(address);
      -> ErrorCheckEvent; 
      #0 outStatus=Status;
      if (Status == `NoError_msg) begin 
         if (Info) $display("[%t]  Enhanced Factory Program -> Setup Phase",$time);
         if (Info) $display("[%t]  Enhanced Factory Program: Start address: %h",$time,startAddress);
         #`EnhBuffProgramSetupPhase_time;
         Busy = `TRUE;
      end
   end 
endtask

task Exit;
   output [`BYTE_range] outStatus;
   begin
      Busy = `FALSE;
      outStatus = Status;
      if (Info) $display("[%t]  Enhanced Factory Program -> Exit Phase",$time);
      if (Len != `BuffEnhProgramBuffer_dim)  
         $display("[%t] Warning --- The buffer must be completely filled for programming to occur",$time);
   end
endtask

task Load;
   output [`BYTE_range] outStatus;
   begin
      if (BankLib_man.getBlock(address) != hold_StartBlock) Status = `ExitPHASE_BEFP_msg;
      else begin
         bufferData[Len] = data;
         if (Info) $display("[%t]  Enhanced Factory Program -> Load: data[%d]=%h ",$time,Len,bufferData[Len]);
         Len = Len + 1;
         if (Len == `BuffEnhProgramBuffer_dim) Status = `ProgramPHASE_BEFP_msg;
         
      end 
      outStatus = Status;
   end
endtask

task Program;
   output [`BYTE_range] outStatus;
   reg [`BYTE_range] 	outStatus;
begin
   fork
      begin : Operation
         if (Info) $display("[%t]  Enhanced Factory Program {Program Phase}",$time); 
         #`EnhBuffProgram_time

            if (Info) $display("[%t]  Enhanced Factory Program {End of Program Phase}",$time); 
         for (i = startAddress;i < (`BuffEnhProgramBuffer_dim + startAddress); i = i + 1) begin
            Memory_man.Program(bufferData[i - startAddress],i,Status);
end
         -> CompleteEvent;      //end of program
      end
      @CompleteEvent begin 
         disable Operation;
      end 
   join
   if (Status == `ProgramPHASE_BEFP_msg) begin //prova
      Status = `NoError_msg;
   end
   outStatus = Status;
end
endtask

always @(ErrorCheckEvent) begin
   Status = `NoError_msg;
   if (BlockLock_man.IsLocked(hold_address))
      Status = `BlockLock_msg;
   else if (!progVoltOK)
      Status = `InvVDD_msg;
   else if (!progHighVoltOK)
      Status =  `InvVPP_msg;
   if (Status != `NoError_msg)
      ->CompleteEvent;
   else
      fork : ErrorCheck
         @(negedge progVoltOK) Status = `InvVDD_msg;
         @(negedge progHighVoltOK) Status = `InvVPP_msg;
         @(CompleteEvent) disable ErrorCheck;
      join
end



endmodule

// ******************************************
//
// Protect Register module :
//
//      operations on the protection register
//
// ******************************************

module ProtectRegModule(address, data, voltOK, Info);
   input [`ADDRBUS_range] address;
   input [`DATABUS_range] data;
   input 		  voltOK, Info;
   reg [`WORD_range] 	  RegisterMemory[`REG_dim - 1 :0];
   reg [`BYTE_range] 	  Status;
   reg 			  Busy;
   reg [`ADDRBUS_range]   AddressLatched;
   event 		  ErrorCheckEvent, CompleteEvent;
   integer 		  i;
   reg [`ADDRBUS_range]   hold_addr;
   reg [`DATABUS_range]   hold_data;


initial begin                         // constructor sequence             
   Busy = `FALSE;
   RegisterMemory[0] = `PRL_default;
   for (i = 1; i < `REG_dim; i = i + 1) begin
      RegisterMemory[i] = `WORDNP;
   end  
end


function IsBusy;              // boolean function primitive       
   input required;               // all functions require a parameter
   IsBusy = Busy;              // return Boolean value             
endfunction                                                       

function UDNisLocked;            // boolean function primitive
   input obbl;                           // input is required
   if ((RegisterMemory[`PROTECTREGLOCK_addr - `REGSTART_addr] | `UDNprotect_bit) == `UDNprotect_bit) 
      UDNisLocked = `TRUE;
   else 
      UDNisLocked = `FALSE;
endfunction

function UPisLocked;            // boolean function primitive
   input obbl;                   // input is required
   UPisLocked = ((RegisterMemory[`PROTECTREGLOCK_addr - `REGSTART_addr] | `UPprotect_bit) == `UPprotect_bit) ? `TRUE : `FALSE;
endfunction

function isUDNaddress;
   input [`ADDRBUS_range] address;
   if ((address >= `UDNREGSTART_addr) && ( address <= `UDNREGEND_addr)) // Check UDN register Address Bound 
      isUDNaddress = `TRUE;
   else isUDNaddress = `FALSE;
endfunction

function isUPaddress;
   input [`ADDRBUS_range] address;
   if ((address >= `UPREGSTART_addr) && (address <= `UPREGEND_addr)) // Check UP register Address Bound 
      isUPaddress = `TRUE;
   else isUPaddress = `FALSE;
endfunction

function [`BYTE_range] ExtIndexPRL;            // bit index of PRL register 
   input [`ADDRBUS_range] addr;
   ExtIndexPRL=(addr - `ExtREGSTART_regionaddr) / `ExtREG_regiondim;
endfunction

function isExtLocked;            // boolean function primitive
   input [`ADDRBUS_range] addr;                   // input is required
   reg [`BYTE_range] 	  bitIndex;
   begin 
      bitIndex = ExtIndexPRL(addr);  // protect bit index of Extended Protection Register Memory
      isExtLocked = !(RegisterMemory[(`ExtPROTECTREGLOCK_addr - `REGSTART_addr)][bitIndex]);
   end
endfunction

function isExtValidAddress;
   input [`ADDRBUS_range] address;
   if ((address >= `ExtREGSTART_regionaddr) && (address <= `ExtREGEND_regionaddr) ) // Check ExtRegister Address Bound 
      isExtValidAddress = `TRUE;
   else isExtValidAddress = `FALSE;
endfunction

task Program;
   output [`BYTE_range] outStatus;
   reg [`BYTE_range] 	outStatus;
begin
   Busy = `TRUE;
   hold_addr = address[`REG_addrbitRange];
   hold_data = data;
   if (Info) $write("[%t]  OTP Program Memory[%h]=%h\n",$time,hold_addr,data);
   fork
      begin : Operation
         -> ErrorCheckEvent;
         #`WordProgram_time RegisterMemory[hold_addr - `REGSTART_addr] = RegisterMemory[hold_addr - `REGSTART_addr] & hold_data;   
         -> CompleteEvent;
      end
      @CompleteEvent disable Operation;
   join
   outStatus = Status;
   Busy = `FALSE;
end
endtask

always @(ErrorCheckEvent) begin
   Status = `NoError_msg;
   if (( address < `REGSTART_addr) || ( address > `REGEND_addr)) // Check Address Bound 
      Status = `AddrRange_msg;
   else if ( isUDNaddress(address) && UDNisLocked(1'bX) ) 
      Status = `UDNlock_msg;
   else if ((isUPaddress(address) && UPisLocked(1'bX)))
      Status = `UPlock_msg;
   else if ( isExtValidAddress(hold_addr) & isExtLocked(hold_addr) )
      Status = `ExtREGLock_msg;
   
   else if (Kernel.Suspended)
      Status = `SuspCmd_msg;
   else if (!voltOK)
      Status = `InvVDD_msg;

   if (Status != `NoError_msg)
      ->CompleteEvent;
   else
      fork : ErrorCheck
         @(negedge voltOK) Status = `InvVDD_msg;
         @(Status) -> CompleteEvent;
         @(CompleteEvent) disable ErrorCheck;
      join
end
endmodule //end ProtectRegModule


// Read Manager
// Manage the read operation

module ReadModule(dataOutput,address,voltOK,Info);
   output [`WORD_range] dataOutput;
   input [`ADDRBUS_range] address;
   input 		  voltOK;
   input 		  Info;
   reg [`WORD_range] 	  dataOutput, regRead;
   reg [1:0] 		  Mode, oldMode;
   reg [`BYTE_range] 	  Status;

   integer 		  i;

initial begin
   regRead = 0; 
   Mode = `ReadArray_bus;
   oldMode = `ReadArray_bus;
   dataOutput = `DATABUS_dim'hzzzz;
end

task SetMode;
   input [1:0] newMode;
   output [`BYTE_range] Status;
   begin
      Status = `NoError_msg;
      if (Info && (newMode!=Mode)) begin
         case (newMode)
            `ReadArray_bus        : $display ("[%t]  Device now in Read Array mode ", $time);
            `ReadCFI_bus          : $display ("[%t]  Device now in Read CFI mode ", $time);
            `ReadSignature_bus    : $display ("[%t]  Device now in Read Electronic Signature Mode ", $time);
            `ReadStatusReg_bus    : $display ("[%t]  Device now in Read Status Register Mode ", $time); 
            default               : $display ("[%t]  !!!Model Error: Read mode not recognized!!!", $time);
         endcase

         oldMode=Mode;
         Mode = newMode;
      end
   end
endtask


always @Kernel.ResetEvent begin
   Mode = `ReadArray_bus;
end

always @(negedge Kernel.Ready) begin   // Configure according to status register
   Mode = `ReadStatusReg_bus;
end

always @Kernel.ReadEvent begin          // Main execution of a read is based on an event


   case (Mode)
      `ReadArray_bus       : begin 
         dataOutput = Memory_man.Get(address);
         if (Info) $display("[%t]  Data Read result: memory[%h]=%h", $time,address,dataOutput);
      end 
      `ReadCFI_bus         : begin
         dataOutput = CFIquery_man.Get(address);
         if (Info) $display("[%t]  Data Read result: CFI_memory[%h]=%h", $time,address,dataOutput);
      end  
      `ReadSignature_bus   :  begin
         dataOutput = Signature_man.Get(address);
         if (Info) $display("[%t]  Read Device Identifier(addr=%h) :%h", $time,address,dataOutput);
      end

      `ReadStatusReg_bus   : begin
         dataOutput = SR_man.SR;
         if (Info) $display("[%t]  Read Status Register: %b", $time,dataOutput[`BYTE_range]);
      end 

      default              : $display("[%t]  !!!Model Error: Read mode not recognized!!!", $time);
   endcase
   if ((Mode == `ReadArray_bus) && (Memory_man.IsSuspended(address) == `TRUE)) begin
      dataOutput = 16'hXX;
      Kernel.SetWarning(`RD_cmd,8'hXX,`SuspAcc_msg);
   end
end

endmodule
// end Module Read


// *************************************************
//
// Status Register module :
//
//      implements the Status Register of the device
//
// *************************************************

module StatusRegModule(Info);
   input Info;


   reg 	 EraseStatus, ProgramStatus, 
	 VpenStatus, BlockProtectionStatus, BW_status;

   reg [`BYTE_range] Status;

   wire [7:0] 	     SR = {Kernel.Ready,                        // bit 7 
			   Erase_man.IsSuspended(1'bX),         // bit 6
			   EraseStatus,                         // bit 5
			   ProgramStatus,                       // bit 4
			   VpenStatus,                          // bit 3
			   Program_man.IsSuspended(1'bX) ||  ProgramBuffer_man.IsSuspended(1'bX),       // bit 2
			   BlockProtectionStatus,               // bit 1
			   BW_status};                          // bit 0
   wire [7:0] 	     SR_Info =  SR;



//-----------------
// Init
//-----------------

initial begin
   EraseStatus=1'b0;
   ProgramStatus=1'b0;
   VpenStatus=1'b0;
   BlockProtectionStatus=1'b0;
   BW_status=1'b0;
end


always @(SR_Info) if (Kernel.Ready!=1'bZ)
   if (Info) $display("[%t]  Status Register Update: %b",$time, SR_Info);

always @(Kernel.ResetEvent) begin
   Clear(Status);
end


always @(Kernel.Ready,ProtectReg_man.Busy, BuffEnhancedFactProgram_man.Busy) 
begin
   if (Kernel.Ready) 
      BW_status = `FALSE;
   else 
      if (BuffEnhancedFactProgram_man.Busy == `TRUE) 
         BW_status=`TRUE;

end

always @(Kernel.ErrorEvent) begin //Update status register bits upon specific errors
   #0;
   case(Kernel.GetError(1'bX))
      `InvVDD_msg       : begin VpenStatus = `TRUE; end
      `InvVPP_msg       : begin VpenStatus = `TRUE; end
      `BlockLock_msg    : begin BlockProtectionStatus = `TRUE;  end
      `UDNlock_msg      : begin ProgramStatus = `TRUE; end
      `UPlock_msg       : begin ProgramStatus = `TRUE; end

      `ProtRegAddrRange_msg : begin 
         BlockProtectionStatus = `TRUE;  
      end
      `ExtREGLock_msg   : begin 
         BlockProtectionStatus = `TRUE;  
      end

      `CmdSeq_msg       : begin ProgramStatus = `TRUE; EraseStatus = `TRUE; end
      `AddrRange_msg    : begin ProgramStatus = `TRUE; EraseStatus = `TRUE; end
      `AddrTog_msg      : begin ProgramStatus = `TRUE; EraseStatus = `TRUE; end
      `PreProg_msg      : begin ProgramStatus = `TRUE; end 
      `WrongEraseConfirm_msg : begin ProgramStatus = `TRUE; EraseStatus   = `TRUE; end
      `WrongBlankCheckConfirm_msg : begin 
         ProgramStatus = `TRUE; EraseStatus   = `TRUE; 
      end
      `BlankCheckFailed_msg : begin 
         EraseStatus   = `TRUE; 
      end
      `LeastAddr0:        begin 
         ProgramStatus = `TRUE; 
      end
      

   endcase
   case(Kernel.GetCmd(4'h1))
      `PG_cmd           : begin ProgramStatus = `TRUE; end
      `PRREG_cmd        : begin ProgramStatus = `TRUE; end
      `PB_cmd           : begin ProgramStatus = `TRUE; end
      `BLKEE_cmd        : begin EraseStatus = `TRUE; end
      `BL_cmd           : if (Kernel.GetCmd(4'h2) == `BLconfirm_cmd) ProgramStatus = `TRUE;
      `BUL_cmd          : if (Kernel.GetCmd(4'h2) ==`BULconfirm_cmd) EraseStatus   = `TRUE;
      `BLD_cmd          : if (Kernel.GetCmd(4'h2) ==`BLDconfirm_cmd) ProgramStatus = `TRUE;
      `BuffEnhProgram_cmd :
         if (Kernel.GetCmd(4'h2) == `BuffEnhProgramCfrm_cmd) 
            ProgramStatus = `TRUE;

   endcase 
end  

task Clear;
   output [`BYTE_range] Status;
   begin
      Status = `NoError_msg;
      EraseStatus = `FALSE;
      ProgramStatus = `FALSE;
      VpenStatus  = `FALSE;
      BlockProtectionStatus   = `FALSE;
      BW_status = `FALSE;
   end
endtask

endmodule  // end module status register


// *************
//
// Kernel Module 
//
// *************

module KernelModule(VDD, VDDQ, VPP, Info);
   input [`Voltage_range] VDD, VDDQ, VPP;
   input 		  Info;
   event 		  CUIcommandEvent, VerifyEvent, ErrorEvent, CompleteEvent, ResetEvent, ReadEvent, ProgramCompleteEvent, EraseCompleteEvent;

   reg 			  voltOK, progVoltOK, eraseVoltOK, lockVoltOK, ioVoltOK, lockOverrideOK;
   reg 			  progHighVoltOK, eraseHighVoltOK;
   reg [8'hFF:0] 	  CommandDecode1;
   reg [16'hFFFF:0] 	  CommandDecode2;
   reg [7:0] 		  lastStatus, lastCmd1, lastCmd2;

// Device Status

   wire 		  Ready = (!Program_man.Busy && !ProgramBuffer_man.Busy  && !BuffEnhancedFactProgram_man.Busy
				   && !Erase_man.Busy && !ProtectReg_man.Busy && !BlankCheck_man.Busy);


   wire 		  Suspended = Program_man.Suspended || Erase_man.Suspended || ProgramBuffer_man.Suspended;

initial begin                 // constructor sequence
   CommandDecode1 = 8'h00;         // initialize decode success status variables
   CommandDecode2 = 16'h0000;
end


always @(voltOK) begin
   if (!voltOK) begin 
      $display("[%t]  !ERROR: Invalid VDD Voltage.",$time);
      -> ErrorEvent;
   end
   else     
      $display("[%t]  VDD Voltage is OK",$time);
end

always @(ioVoltOK) begin
   if (!ioVoltOK) begin 
      $display("[%t]  !ERROR: Invalid VDDQ I/O voltage.", $time);
      -> ErrorEvent;
   end 
   else 
      $display("[%t]  VDDQ Voltage is OK",$time);
   
end

always @(VDD) begin        
   if ((VDD < `VDDmin) | (VDD > `VDDmax))
      voltOK = `FALSE;
   else
      voltOK = `TRUE;
end


always @(VDDQ) begin  // check i/o voltage constraints
   if ((VDDQ >= `VDDQmin) && (VDDQ <= `VDDQmax))
      ioVoltOK = `TRUE;
   else 
      ioVoltOK = `FALSE;
end

always @(VPP) begin // program/erase/lock
   if ((VPP>=`VPPmin && VPP<=`VPPmax))  begin
      progVoltOK  = `TRUE;
      eraseVoltOK = `TRUE;
      lockVoltOK  = `TRUE;
      progHighVoltOK  = `FALSE;
      eraseHighVoltOK = `FALSE;
   end
   else if ((VPP>=`VPPHmin) && (VPP<=`VPPHmax)) begin 
      progVoltOK  = `TRUE;
      eraseVoltOK = `TRUE;
      lockVoltOK  = `TRUE;
      progHighVoltOK  = `TRUE;
      eraseHighVoltOK = `TRUE;
   end
   else begin 
      progVoltOK  = `FALSE;
      eraseVoltOK = `FALSE;
      lockVoltOK  = `FALSE;
      progHighVoltOK  = `FALSE;
      eraseHighVoltOK = `FALSE;
   end 
end


function [7:0] GetError;
   input required;
   GetError = lastStatus;
endfunction

function [7:0] GetCmd;
   input commandNum;
   GetCmd = (commandNum == 1) ? lastCmd1 : lastCmd2;
endfunction

task SetWarning;
   input [7:0] Cmd1, Cmd2;
   input [7:0] Status;
   begin
      Report(Cmd1,Cmd2,Status);
      lastStatus = Status;
   end
endtask

task SetError;
   input [7:0] Cmd1, Cmd2;
   input [7:0] ErrFlag;
   begin
      SetWarning(Cmd1,Cmd2,ErrFlag);
      -> ErrorEvent; // Only errors set error event
   end
endtask


task Report;
   input [7:0] Cmd1, Cmd2;
   input [7:0] Status;
   begin
      lastStatus = Status;
      lastCmd1 = Cmd1;
      lastCmd2 = Cmd2;
      if ((lastStatus != `NoError_msg) || Info) begin //Display error .
         $write("[%t] ",$time);
         case(Status)
            `NoError_msg         : begin $write(" Command Completion Successful "); end
            `CmdSeq_msg         : begin $write(" !Error:   [Invalid Command]\n Sequence Command Unknown"); -> ErrorEvent; end
            `SuspCmd_msg        : begin $write(" !Error:   [Invalid Command]\n Cannot execute this command during suspend"); -> ErrorEvent; end
            `SuspAcc_msg        : begin $write(" !Error:   [Invalid Command]\n Cannot access this address due to suspend"); -> ErrorEvent; end
            `SignAddrRange_msg  : begin $write(" !Error:   [Invalid Address]\n Signature Address out of range"); end
            `CFIAddrRange_msg   : begin $write(" !Error:   [Invalid Address]\n CFI Address out of range"); end
            `AddrRange_msg      : begin $write(" !Error:   [Invalid Address]\n Address out of range"); -> ErrorEvent; end
            `AddrTog_msg        : begin $write(" !Error:   [Program Buffer]\n Cannot change block address during command sequence"); -> ErrorEvent; end
            `BuffSize_msg       : begin $write(" !Error:   [Program Buffer]\n Buffer size is too large (Max Size is %d) ",`ProgramBuffer_dim); -> ErrorEvent; end
            `InvVDD_msg         : begin $write(" !Error:   [Invalid Supply]\n Voltage Supply must be: VDD>VDDmin and VDD<VDDmax "); -> ErrorEvent; end
            `InvVPP_msg         : begin $write(" !Error:   [Invalid Program Supply]\n Program Supply Voltage must be: VPP>VPPHmin and VPP<VPPHmax for this Operation"); -> ErrorEvent; end
            `ByteToggle_msg     : begin $write(" !Error:   [BYTE_N Toggled]\n Cannot toggle BYTE_N while busy"); -> ErrorEvent; end
            `PreProg_msg        : begin $write(" !Error:   [Program Failure]\n Program Failure due to cell failure"); -> ErrorEvent; end 
            `UDNlock_msg        : begin $write(" !Error:   [Program Failure]\n Unique Device Number Register is locked"); -> ErrorEvent; end
            `UPlock_msg         : begin $write(" !Error:   [Program Failure]\n User Programmable Register is locked"); -> ErrorEvent; end
            `ExtREGLock_msg     : begin $write(" !Error:   [Program Failure]\n Extended User Programmable OTP is locked"); -> ErrorEvent; end
            `NoUnLock_msg       : begin $write(" #Warning: [Locked Down Warning]\n  Invalid UnLock Block command in Locked-Down Block"); end
            `SuspAccWarn_msg    : begin $write(" #Warning: [Invalid Access]\n  It isn't possible access this address due to suspend"); end
            `BlockLock_msg      : begin $write(" !Error:   [Locked Error]\n Cannot complete operation when the block is locked "); -> ErrorEvent; end
            `BlkBuffer_msg      : begin $write(" !Error: [Program Buffer]  Program Buffer cannot cross block boundary"); end
            `AddrCFI_msg        : begin $write(" #Warning: [Invalid CFI Address]\n CFI Address out of range"); end
            `NoBusy_msg         : begin $write(" #Warning: [NO Busy]\n Device is not Busy"); end
            `NoSusp_msg         : begin $write(" #Warning: [NO Suspend]\n Nothing previus suspend command"); end
            `Suspend_msg        : begin $write("  Suspend of "); end
            `WrongEraseConfirm_msg : begin
               $write(" !Error: [Wrong Erase Confirm Code "); 
               -> ErrorEvent;
            end   
            `LeastAddr0         : begin 
               $write(" !Error:   [Program Failure]\n Least Significative bit [%2d downto 0] of Start Address must be 0",`ProgramBuffer_addrDim-1); 
               -> ErrorEvent;
            end
            
            `WrongBlankCheckConfirm_msg : begin
               $write(" !Error: [Confirm Code] Wrong Blank Check Confirm Code "); 
               -> ErrorEvent;
            end   

            `WrongBlankCheckBlock:  begin 
               $write(" !Error:   [Blank Check Failure]\n The block must be a main block"); 
               -> ErrorEvent;
            end

            `BlankCheckFailed_msg : begin $write(" !Error:   [Blank Check]\n Blank Check Failed "); 
               -> ErrorEvent; 
            end

            default             : begin $write(" !ERROR: [Unknown error]\n Flag=%h, cmd1=%hh, cmd2=%hh",Status,Cmd1,Cmd2); -> ErrorEvent; end
         endcase 
         case (Cmd1)
            16'hXX              : $display(" !Error: [General Error}\n Error not defined");
            `RD_cmd             : $display(" { Read Array }");
            `RSR_cmd            : $display(" { Read Status Register }");
            `RSIG_cmd           : $display(" { Read Electronic Signature }");
            `RCFI_cmd           : $display(" { Read CFI }");
            `PG_cmd             : $display(" { Program }");
            `BuffEnhProgram_cmd : $display(" { Buffer Enhanced Factory Program }");

            `SCR_cmd | `BL_cmd | `BUL_cmd |  `BLD_cmd 
               : begin 
                  if (Cmd2 == `SCRconfirm_cmd) $display(" { Set Configuration Register }");
                  if (Cmd2 == `BLconfirm_cmd)  $display(" { Block Lock }");
                  if (Cmd2 == `BULconfirm_cmd) $display(" { Block UnLock }");
                  if (Cmd2 == `BLDconfirm_cmd) $display(" { Block Lock-Down }");
               end
            `PER_cmd          : $display(" { Program/Erase Resume }");                    
            `PRREG_cmd        : $display(" { Protection Register Command }");
            `BLKEE_cmd        : $display(" { Block Erase }");
            `BLNKCHK_cmd      : $display(" { Blank Check }");
            `CLRSR_cmd        : $display(" { Clear Status Register }");
            `PES_cmd          : $display(" { Program/Erase Suspend }");
            `PB_cmd           : $display(" { Write to Buffer and Program }");
            default           : $display(" {unknown command:  %hh}", Cmd1);
         endcase
      end
   end
endtask

task CheckTime;
   input [8*6:1] tstr;
   input [31:0]  tdiff, tprev;
   
   begin
      if ($time - tprev < tdiff) begin
	 $display ("[%t]  !ERROR: %0s timing constraint violation:  %0d-%0d < %0dns ", $time, tstr, $time, tprev, tdiff);
	 -> ErrorEvent;
      end
   end
endtask

endmodule // end module Kernel 





module x28fxxxp30(A, DQ, W_N, G_N, E_N, L_N, K, WAIT, WP_N, RP_N, VDD, VDDQ, VPP, Info);

// Signal Bus
   input [`ADDRBUS_dim-1:0] A;           // Address Bus 
   inout [`DATABUS_dim-1:0] DQ;          // Data I/0 Bus
// Control Signal
   input 		    W_N;                           // Write Enable 
   input 		    G_N;                           // Output Enable
   input 		    E_N;                           // Chip Enable
   input 		    L_N;                           // Latch Enable
   input 		    K;                             // Clock
   input 		    WP_N;                          // Write Protect
   input 		    RP_N;                          // Reset/Power-Down

// Voltage signal rappresentad by integer Vector which correspond to millivolts
   input [`Voltage_range]   VDD;           // Supply Voltage
   input [`Voltage_range]   VDDQ;          // Input/Output Supply Voltage
   input [`Voltage_range]   VPP;           // Optional Supply Voltage for fast Program & Erase

// Others Signal       
   output 		    WAIT;                          // Wait
   reg 			    wait_;
assign WAIT = wait_;
   input 		    Info;                           // Enable/Disable Information of the operation in the memory 
   wire 		    CLK;
assign CLK = (K ~^ ConfigReg_man.isRisingClockEdge);
   reg 			    CLOCK;
// === Internal Signal ===
// Chip Enable 
   wire 		    CE_N = E_N & Kernel.voltOK & RP_N;

// Output Enable 
   wire 		    OE_N = G_N | CE_N | !Kernel.ioVoltOK | !RP_N;
// Write Enable 
   wire 		    WE_N = W_N | CE_N;

// Latch Enable

   wire 		    LE_N = L_N | CE_N;
// === Bus Latch ===
// Data Bus
   wire [`DATABUS_dim-1:0]  DataBusIn;
   wire [`DATABUS_dim-1:0]  DataBurst;

// read burst is in wait state
   wire 		    isWait; 

// Address Bus
   reg [`ADDRBUS_dim - 1:0] AddrBusIn;

// Status
//aggiunti stati buffenha...e blank....
   reg [`BYTE_range] 	    KernelStatus, ReadStatus, EraseStatus, ProgramStatus, BuffEnhancedProgramStatus,
			    LockStatus, ConfigStatus, BufferStatus,BlankCheckStatus,ProgramBufferStatus,
			    SuspendStatus, ResumeStatus, ClearSRStatus, ProtectRegStatus;


   reg [`BYTE_range] 	    status=`Free_pes;

//address latching in read operation
always @(negedge LE_N) if (W_N==`HIGH)  begin
   if (KernelStatus == `READY && ConfigReg_man.isASynchronous) 
      @(posedge LE_N) begin
         if (L_N)

            AddrBusIn = A;                // AddressBus has been Latched

      end
end

always @(negedge LE_N) if (W_N==`HIGH) begin :latching_a
   if (KernelStatus == `READY) begin
      if(ConfigReg_man.isSynchronous)
         fork
            
            begin : L_Address

               @(posedge LE_N) if (L_N) begin
                  
                  AddrBusIn = A;                // AddressBus has been Latched
                  disable K_Address;

               end
            end    
            
            begin : K_Address

               @(posedge CLK) begin

                  AddrBusIn = A;                // AddressBus has been Latched
                  disable L_Address;
               end
            end 
         join

   end
end


always @(negedge WE_N) begin
   if (KernelStatus==`READY) 
      @(posedge WE_N) begin
         if(OE_N==`HIGH)
            AddrBusIn = A;                // AddressBus has been Latched

      end                
end

   integer i;
   integer n_block;

// Wait Driver 
   time    timeWaitDriver,timeWaitDriverZ;

   reg 	   PB_init=0;
   reg 	   P_init=0;
   reg 	   BP_init=0;
   reg 	   Prog_init=0;

always @(PB_init,P_init,BP_init) begin
   Prog_init=(PB_init ||P_init || BP_init);
end

   wire [`BYTE_range] AccessTime;

// ****************
// 
// Modules Istances
//
// ****************

DataErrorModule   DataError_man();              // Check for errors on UserData.h

CUIdecoder1       ReadArray_Command  (DQ[`LOW_range], "Read Array                         ", `RD_cmd, (Kernel.Ready && !Prog_init),        Info),
   ReadSR_Command     (DQ[`LOW_range], "Read Status Register               ", `RSR_cmd, !Prog_init,        Info),
   ReadSign_Command   (DQ[`LOW_range], "Read Electronic Signature          ", `RSIG_cmd, (Kernel.Ready && !Prog_init),       Info),
   ReadCFI_Command    (DQ[`LOW_range], "Read CFI                           ", `RCFI_cmd, (Kernel.Ready && !Prog_init),      Info),
   Program_Command    (DQ[`LOW_range], "Program                            ", `PG_cmd,   (Kernel.Ready && !Prog_init),      Info),

   ProgramBuffer_Command    (DQ[`LOW_range], "Program Buffer                     ", `PB_cmd, (Kernel.Ready && !Prog_init),        Info),
   ProgramReg_Command (DQ[`LOW_range], "Protection Register Program        ", `PRREG_cmd,  (Kernel.Ready && !Prog_init),    Info),
   Resume_Command     (DQ[`LOW_range], "Resume                             ", `PER_cmd,  (Kernel.Ready && Kernel.Suspended),      Info),
   BlockErase_Command  (DQ[`LOW_range], "Block Erase                        ", `BLKEE_cmd,(Kernel.Ready && !Prog_init),      Info),
   ClearSR_Command    (DQ[`LOW_range], "Clear Status Register              ", `CLRSR_cmd, (Kernel.Ready && !Prog_init),     Info),

   BlankCheck_Command  (DQ[`LOW_range], "Blank Check                        ", `BLNKCHK_cmd, (Kernel.Ready && !Prog_init),   Info),
   BuffEnhactoryProgram_Command (DQ[`LOW_range], "Buffer Enh.Factory Program  [Setup]", `BuffEnhProgram_cmd,(Kernel.Ready && !Prog_init), Info);


CUIdecoder_Busy1  Suspend_Command    (DQ[`LOW_range], "Suspend ",   `PES_cmd, !Kernel.Ready, Info);

CUIdecoder2        BlockLock_Command (DQ[`LOW_range], "Block Lock                 ", `BL_cmd,       `BLconfirm_cmd, (Kernel.Ready && !Prog_init),    Info),
   BlockUnlock_Command (DQ[`LOW_range], "Block UnLock               ", `BUL_cmd,      `BULconfirm_cmd, (Kernel.Ready && !Prog_init),   Info),
   BlockLockDown_Command (DQ[`LOW_range], "Block Lock-Down            ", `BLD_cmd,      `BLDconfirm_cmd, (Kernel.Ready && !Prog_init),   Info),
   SetConfigReg_Command (DQ[`LOW_range], "Set Configuration Register ", `SCR_cmd,      `SCRconfirm_cmd, (Kernel.Ready && !Prog_init),   Info);

KernelModule            Kernel            (VDD, VDDQ, VPP, Info);
ReadModule              Read_man          (DataBusIn, AddrBusIn, Kernel.ioVoltOK, Info);
OutputBufferModule      OutputBuffer_man  (DataBusIn, DataBurst, DQ, OE_N);
StatusRegModule         SR_man            (Info);
MemoryModule            Memory_man        (Info);
ProgramModule           Program_man       (AddrBusIn, DQ, Kernel.progVoltOK, Kernel.progHighVoltOK, Info);

BuffEnhancedFactProgramModule BuffEnhancedFactProgram_man(AddrBusIn, DQ, Kernel.progVoltOK, Kernel.progHighVoltOK, Info);
ProtectRegModule        ProtectReg_man    (AddrBusIn, DQ, Kernel.progVoltOK, Info);
EraseModule             Erase_man         (AddrBusIn, DQ, Kernel.eraseVoltOK, Kernel.progHighVoltOK, Info);


BlankCheckModule        BlankCheck_man    (AddrBusIn, DQ, Kernel.eraseVoltOK, Kernel.progHighVoltOK, Info);

BlockLockModule         BlockLock_man     (AddrBusIn, WP_N, RP_N, Info);
ProgramBufferModule   ProgramBuffer_man (AddrBusIn, DQ, Kernel.progVoltOK, Info);
SignatureModule         Signature_man     ();        // , `FALSE);
CFIqueryModule          CFIquery_man      ();        // , `TRUE);
ConfigRegModule         ConfigReg_man     (AddrBusIn,Info);                              // implements the Configuration Register
BurstModule             Burst_man         (AddrBusIn, DataBurst, isWait, CLK, CLOCK, L_N, G_N,W_N, Info);

BankLib                 BankLib_man       ();
TimingDataModule        TimingData_man    ();
TimingLibModule         TimingLib_man     (A,DQ,W_N,G_N,E_N,L_N,WP_N,K,VPP);

initial begin

   $timeformat(-9, 0, " ns", 12);               // Format time displays to screen
   -> Kernel.ResetEvent;                        // Reset Device 
   KernelStatus = `BUSY;                        // Device is Busy
   $display ("[%t]  --- Device is Busy  (start up time) --- ", $time);
   #(TimingData_man.tVDHPH) KernelStatus = `READY;              // End of Start-Up Time
   $display ("[%t]  --- Device is Ready (end of start-up time) --- ", $time);

   AddrBusIn = `ADDRBUS_dim'hZ;

   wait_ = 1'hZ;
   CLOCK = 1'b0;
end

// Recognize command input
always @(negedge WE_N) begin
   if (KernelStatus==`READY) 
      @(posedge WE_N) begin

         -> Kernel.CUIcommandEvent;               // new command has been written into Kernel.

      end
end

// Check error
always @(Kernel.CUIcommandEvent) begin : Timeout  
   #3
      -> Kernel.ErrorEvent;                      
   disable Verify;                                 
end

// Verify command issued 
always @(Kernel.CUIcommandEvent) begin : Verify   
   @(Kernel.VerifyEvent)  

      disable Timeout;          
end

// Default to Read Array command
always @(negedge OE_N) begin
   if (OE_N == `LOW && (ConfigReg_man.isASynchronous)) begin
      if (L_N==0) AddrBusIn=A;
      #1 
         -> Kernel.ReadEvent;
   end      
end

// Page Read
always @(A) begin
   
   if ((OE_N == `LOW) &&  (A !== `ADDRBUS_dim'hZ) && (A !== `ADDRBUS_dim'hx) && (ConfigReg_man.isASynchronous))  begin
      AddrBusIn = A;
      #0 -> Kernel.ReadEvent;
      
   end 
end 


// Reset the Kernel
always @(negedge RP_N) begin 
   -> Kernel.ResetEvent;
   if (Info) $display ("[%t]  Device has been reset ", $time);
   KernelStatus = `BUSY;
   @(posedge RP_N) KernelStatus = `READY;
end

// ----- Recognize Command Input -----
always @(Kernel.CommandDecode1[`RD_cmd]) if (KernelStatus==`READY)  begin                        // Read Array
   Read_man.SetMode(`ReadArray_bus, ReadStatus);
   Kernel.Report(`RD_cmd, 8'hXX, ReadStatus);
   #1 -> Kernel.CompleteEvent;
end

always @(Kernel.CommandDecode1[`RSR_cmd]) if (KernelStatus==`READY) begin                       // Read Status Register
   Read_man.SetMode(`ReadStatusReg_bus, ReadStatus);
   Kernel.Report(`RSR_cmd, 8'hXX, ReadStatus); 
   #1 -> Kernel.CompleteEvent;
end


always @(Kernel.CommandDecode1[`RSIG_cmd]) if (KernelStatus==`READY )  begin                      // Read Electronic Signature
   Read_man.SetMode(`ReadSignature_bus, ReadStatus);
   Kernel.Report(`RSIG_cmd, 8'hXX, ReadStatus);
   #1 -> Kernel.CompleteEvent;
end

always @(Kernel.CommandDecode1[`RCFI_cmd]) if (KernelStatus==`READY)  begin                      // Read CFI 
   Read_man.SetMode(`ReadCFI_bus, ReadStatus);
   Kernel.Report(`RCFI_cmd, 8'hXX, ReadStatus); 
   #1 -> Kernel.CompleteEvent;
end

always @(Kernel.CommandDecode1[`PG_cmd]) if (KernelStatus==`READY) begin                     // Program
   P_init=1;
   @Kernel.CUIcommandEvent
	  #1 -> Kernel.VerifyEvent;
   Program_man.Program(ProgramStatus);
Kernel.Report(`PG_cmd, 8'hXX, ProgramStatus);
-> Kernel.CompleteEvent;
P_init=0;
end


always @(Kernel.CommandDecode1[`PRREG_cmd]) if (KernelStatus==`READY)  begin                      // Protection Register Program
   @Kernel.CUIcommandEvent 
      #1 -> Kernel.VerifyEvent;
   ProtectReg_man.Program(ProtectRegStatus);
Kernel.Report(`PRREG_cmd, 8'hXX, ProtectRegStatus);
-> Kernel.CompleteEvent;
end

always @(Kernel.CommandDecode1[`PES_cmd]) if (KernelStatus==`READY) begin                       // Suspend
   if (Program_man.IsBusy(1'bX))
      Program_man.Suspend(SuspendStatus);
   else if (ProgramBuffer_man.IsBusy(1'bX))
      ProgramBuffer_man.Suspend(SuspendStatus); 
   else if (Erase_man.IsBusy(1'bX))
      Erase_man.Suspend(SuspendStatus);
   -> Kernel.CompleteEvent;
end


always @(Kernel.CommandDecode1[`PER_cmd]) if (KernelStatus==`READY) begin                       // Program/Erase Resume
   ResumeStatus = `NoError_msg;
   if (Program_man.IsSuspended(1'bX)) begin
      Program_man.Resume(ProgramStatus);
      Kernel.Report(`PG_cmd, 8'hXX, ProgramStatus);
   end
   else if (ProgramBuffer_man.IsSuspended(1'bX)) begin 
      ProgramBuffer_man.Resume(BufferStatus);
      Kernel.Report(`PB_cmd, 8'hXX, BufferStatus);
   end
   else if (Erase_man.IsSuspended(1'bX)) begin
      Erase_man.Resume(EraseStatus);
      Kernel.Report(`BLKEE_cmd, 8'hXX, EraseStatus);
   end
   else
      ResumeStatus = `NoSusp_msg;
   Kernel.Report(`PER_cmd, 8'hXX, ResumeStatus);
   -> Kernel.CompleteEvent;

end

always @(Kernel.CommandDecode1[`BLKEE_cmd]) if (KernelStatus==`READY) begin                    // Block Erase
   Read_man.SetMode(`ReadStatusReg_bus,ReadStatus);
   EraseStatus=`NoError_msg;
   @Kernel.CUIcommandEvent
               Erase_man.checkConfirm(EraseStatus);
   #1 -> Kernel.VerifyEvent;
   if (EraseStatus != `NoError_msg)
      Kernel.Report(`BLKEE_cmd, `BLKEEconfirm_cmd, EraseStatus);
   else
   begin
      Erase_man.BlockErase(EraseStatus);
      Kernel.Report(`BLKEE_cmd, `BLKEEconfirm_cmd , EraseStatus);
      -> Kernel.CompleteEvent;
   end        
end

always @(Kernel.CommandDecode1[`CLRSR_cmd]) if (KernelStatus==`READY)  begin                    // Clear Status Register
   SR_man.Clear(ClearSRStatus);
   Kernel.Report(`CLRSR_cmd, 8'hXX, ClearSRStatus); 
   #1 -> Kernel.CompleteEvent;
end


//aggiunta ************************************************
// PB Fast Program Commands
always @(Kernel.CommandDecode1[`PB_cmd]) if (KernelStatus==`READY)  begin                       // Write to Program and Buffer 
   ProgramBufferStatus = `NoError_msg;
   PB_init=1;
   Read_man.SetMode(`ReadStatusReg_bus, ReadStatus);
   @Kernel.CUIcommandEvent 
      ProgramBuffer_man.SetCount(ProgramBufferStatus);
   #1 -> Kernel.VerifyEvent;
   
   if (ProgramBufferStatus == `NoError_msg) begin
      for (i=1; i <= ProgramBuffer_man.GetCount(1'bX); i=i+1) begin : GetData
         @Kernel.CUIcommandEvent
             #1;

         ProgramBuffer_man.Load(ProgramBufferStatus);
         #1 -> Kernel.VerifyEvent;
         if (ProgramBufferStatus != `NoError_msg)
            disable GetData;
      end
      @Kernel.CUIcommandEvent
         if (DQ[`BYTE_range] != `PBcfm_cmd)
            ProgramBufferStatus = `CmdSeq_msg;
         else begin
            #1 -> Kernel.VerifyEvent;
            ProgramBuffer_man.Program(ProgramBufferStatus);
end
   end
   Kernel.Report(`PB_cmd, 8'hXX, ProgramBufferStatus);
   ->Kernel.CompleteEvent;
   PB_init=0;
end
//*************************************************************************

always @(Kernel.CommandDecode2[{`BL_cmd,`BLconfirm_cmd}]) if (KernelStatus==`READY)  begin      // Block Lock
   BlockLock_man.Lock(LockStatus);
   Kernel.Report(`BL_cmd, `BLconfirm_cmd, LockStatus);
   -> Kernel.CompleteEvent;
end

always @(Kernel.CommandDecode2[{`BUL_cmd,`BULconfirm_cmd}]) if (KernelStatus==`READY) begin    // Block UnLock
   BlockLock_man.UnLock(LockStatus);
   Kernel.Report(`BUL_cmd,`BULconfirm_cmd, LockStatus);
   -> Kernel.CompleteEvent;
end

always @(Kernel.CommandDecode2[{`BLD_cmd,`BLDconfirm_cmd}]) if (KernelStatus==`READY)  begin    // Block Lock-Down
   BlockLock_man.LockDown(LockStatus);
   Kernel.Report(`BLD_cmd,`BLDconfirm_cmd, LockStatus);
   -> Kernel.CompleteEvent;
end

always @(Kernel.CommandDecode2[{`SCR_cmd,`SCRconfirm_cmd}]) if (KernelStatus==`READY) begin    // Set Configuration Register
   ConfigReg_man.putConfigReg(ConfigStatus);
   Kernel.Report(`SCR_cmd,`SCRconfirm_cmd, ConfigStatus);
   -> Kernel.CompleteEvent;
end


// BC
always @(Kernel.CommandDecode1[`BLNKCHK_cmd]) if (KernelStatus==`READY)  begin                    // Blank Check
   BlankCheckStatus=`NoError_msg;
   Read_man.SetMode(`ReadStatusReg_bus, ReadStatus);
   @Kernel.CUIcommandEvent
      BlankCheck_man.checkConfirm(BlankCheckStatus);
   #1 -> Kernel.VerifyEvent;
   
   if (BlankCheckStatus != `NoError_msg) begin
      
      Kernel.Report(`BLNKCHK_cmd, `BLNKCHKconfirm_cmd, BlankCheckStatus);
   end else
   begin
      BlankCheck_man.BlankCheck(BlankCheckStatus);
      Kernel.Report(`BLNKCHK_cmd, `BLNKCHKconfirm_cmd, BlankCheckStatus);
   end        
   -> Kernel.CompleteEvent;

end
// BEFP 
always @(Kernel.CommandDecode1[`BuffEnhProgram_cmd]) if (KernelStatus==`READY)  begin    // Buffer Enhanced Factory Program: Setup Phase
   Read_man.SetMode(`ReadStatusReg_bus, ReadStatus);
   BP_init=1;
   @Kernel.CUIcommandEvent
           #1 -> Kernel.VerifyEvent;
   if (Kernel.Suspended | !Kernel.Ready)
      BuffEnhancedProgramStatus = `SuspCmd_msg;
   else begin 
      if (DQ[`LOW_range]!=`BuffEnhProgramCfrm_cmd)  
         BuffEnhancedProgramStatus=`CmdSeq_msg;     
      else begin 
         if (Info) $display("[%t]  Command Issued: Buffer Enh.Factory Program  [Confirm]",$time);
         
         BuffEnhancedFactProgram_man.Setup(BuffEnhancedProgramStatus);
         if (BuffEnhancedProgramStatus == `NoError_msg) begin 
            while (BuffEnhancedProgramStatus == `NoError_msg)  begin               // Loop Program - Enhanced Factory Program: Program Phase
               if (Info) $display("[%t]  Enhanced Factory Program -> Load Phase",$time);
               
               while (BuffEnhancedProgramStatus == `NoError_msg ) begin               // Loop Load - Enhanced Factory Program: Load Phase
                  @Kernel.CUIcommandEvent
						 #1 -> Kernel.VerifyEvent;
                  BuffEnhancedFactProgram_man.Load(BuffEnhancedProgramStatus);
               end 
               if (BuffEnhancedProgramStatus==`ProgramPHASE_BEFP_msg) begin
                  BuffEnhancedFactProgram_man.Program(BuffEnhancedProgramStatus);
end        
            end 
            BuffEnhancedFactProgram_man.Exit(BuffEnhancedProgramStatus);
         end
      end 
      if (BuffEnhancedProgramStatus == `ExitPHASE_BEFP_msg) 
         BuffEnhancedProgramStatus = `NoError_msg;
   end 
   Kernel.Report(`BuffEnhProgram_cmd,`BuffEnhProgramCfrm_cmd, BuffEnhancedProgramStatus);
   -> Kernel.CompleteEvent;
   BP_init=0;
end



//***********************************************************

// Decode Delays for Page Mode Reads

//******************************************************

// Page mode
always 
begin :nopage
   @(A[`ADDRBUS_dim - 1:4])
      disable page;          

   OutputBuffer_man.SetValid(TimingData_man.tAVQV);
end 

// Page mode
always
begin :page
   @(A[3:0]) //pagina di 16 words
      OutputBuffer_man.SetValid(TimingData_man.tAVQV1);
end 


// Output Buffer delays 

always @(negedge E_N) begin
   OutputBuffer_man.SetX(TimingData_man.tELQX);
   OutputBuffer_man.SetValid(TimingData_man.tELQV);

end

always @(negedge G_N) begin
   #0;
   OutputBuffer_man.SetX(TimingData_man.tGLQX);
   OutputBuffer_man.SetValid(TimingData_man.tGLQV);

end

always @(posedge CLK) begin
   CLOCK = !CLOCK;
end

always @(negedge CLK) begin
   CLOCK = !CLOCK;
end



   reg waiting=1;

always @(posedge G_N) begin

   waiting=1;

end



always @(CLK) begin

   if ((!G_N) && (CE_N == `LOW) && (ConfigReg_man.isSynchronous) && (CLK)) begin

      if (ConfigReg_man.isWaitBeforeActive && Burst_man.firstEOWL && waiting) begin
         OutputBuffer_man.SetX(TimingData_man.tKHQX);
         @(posedge (CLK))
            OutputBuffer_man.SetX(TimingData_man.tKHQX);
         OutputBuffer_man.SetValid(TimingData_man.tKHQV);
         waiting=0;

      end else begin
         
         OutputBuffer_man.SetX(TimingData_man.tKHQX);
         OutputBuffer_man.SetValid(TimingData_man.tKHQV);
         
      end
      
   end 
end 

always @(negedge L_N) if(W_N==`HIGH)begin
   if (ConfigReg_man.isSynchronous && CE_N==`LOW) begin
      OutputBuffer_man.SetValid(TimingData_man.tLLQV);
      

   end
end


always @(RP_N) begin
   if (RP_N == `HIGH)
      OutputBuffer_man.SetValid(TimingData_man.tPHWL);
end

always @(posedge CE_N) begin
   OutputBuffer_man.SetZ(TimingData_man.tEHQZ);
end

always @(posedge G_N) begin
   OutputBuffer_man.SetZ(TimingData_man.tGHQZ);
   OutputBuffer_man.SetZ(TimingData_man.tGHTZ);

end



////////////////////////////////
always @(CE_N) begin 
   if (CE_N == `LOW && W_N==`HIGH && G_N == `LOW) begin 
      if (ConfigReg_man.isSynchronous)  
         wait_ = #(TimingData_man.tELTV) ConfigReg_man.isWaitPolActiveHigh;
      else wait_ = #(TimingData_man.tELTV) !ConfigReg_man.isWaitPolActiveHigh;
   end
   else
      wait_ = #(TimingData_man.tEHTZ) 1'hZ;
end 

always @(G_N) begin 
   if (G_N == `LOW && CE_N == `LOW && W_N==`HIGH) begin
      if (ConfigReg_man.isSynchronous) begin
         wait_ = #(TimingData_man.tGLTV) ConfigReg_man.isWaitPolActiveHigh;
      end else begin
         wait_ = #(TimingData_man.tGLTV) !ConfigReg_man.isWaitPolActiveHigh;
      end
   end
   else begin if (G_N == `HIGH )  
      wait_ = #(TimingData_man.tGHTZ) 1'hZ;
      
      disable Burst_man.pollingBurst;
   end
end 




always @(isWait) begin

   if ((CE_N == `LOW) && (G_N == `LOW) && ConfigReg_man.isSynchronous ) begin
      
      if (CLK) begin
         if (isWait == `LOW ) begin
            if(!Burst_man.nWait) wait_ = #(TimingData_man.tKHTV) ConfigReg_man.isWaitPolActiveHigh;
            else wait_ = #(TimingData_man.tKHTX) ConfigReg_man.isWaitPolActiveHigh;
         end else begin
            if (!Burst_man.nWait) wait_ = #(TimingData_man.tKHTV) !ConfigReg_man.isWaitPolActiveHigh;
            else wait_ = #(TimingData_man.tKHTX) !ConfigReg_man.isWaitPolActiveHigh;
         end        

      end else 
	 
         fork 
            
            begin
               @(posedge(CLK)) 
		  
		  if (isWait == `LOW) begin
                     if(!Burst_man.nWait) wait_ = #(TimingData_man.tKHTV) ConfigReg_man.isWaitPolActiveHigh;
                     else wait_ = #(TimingData_man.tKHTX) ConfigReg_man.isWaitPolActiveHigh;
		  end else begin
                     if (!Burst_man.nWait) wait_ = #(TimingData_man.tKHTV) !ConfigReg_man.isWaitPolActiveHigh;
                     else wait_ = #(TimingData_man.tKHTX) !ConfigReg_man.isWaitPolActiveHigh;
		  end 
            end
	    

            begin
               
               @(isWait)
		  
		  if (CLK) begin
		     if (isWait == `LOW) begin
			if(!Burst_man.nWait) wait_ = #(TimingData_man.tKHTV) ConfigReg_man.isWaitPolActiveHigh;
			else wait_ = #(TimingData_man.tKHTX) ConfigReg_man.isWaitPolActiveHigh;
		     end else begin
			if (!Burst_man.nWait) wait_ = #(TimingData_man.tKHTV) !ConfigReg_man.isWaitPolActiveHigh;
                        else wait_ = #(TimingData_man.tKHTX) !ConfigReg_man.isWaitPolActiveHigh;
		     end

		  end
            end   

	 join
      
      
   end else if  (G_N == `HIGH && isWait == `HIGH && W_N==`HIGH)
      $display("%t --- WARNING --- WAIT should be deasserted but OE# is not yet LOW. Please check the timings!",$time);
end 


endmodule



// *********************************
//
// Burst module :
//
//      manage the Read Burst operation
//
// *********************************

module BurstModule(address, data, ISWAIT, CLK, CLOCK, L_N, G_N, W_N, Info);
   input [`ADDRBUS_range] address;
   output [`WORD_range]   data;
   reg [`WORD_range] 	  data;

   input 		  CLK;
   input 		  CLOCK;
   input 		  L_N;
   input 		  G_N;
   input 		  W_N;
   output 		  ISWAIT;
   input 		  Info;

   reg [`ADDRBUS_range]   Start_address, Sync_address,new_address;
   reg 			  EnableBurst, isValidData, IsNowWait, endSingleSynchronous;
   reg [2:0] 		  incLSBaddress, incMSBaddress, temp_address;

   wire 		  isSingleSynchronous = (Read_man.Mode != `ReadArray_bus) ? `TRUE : `FALSE;

   integer 		  WaitState,nWait,nRead,xLatency;
//aggiunta per il calcolo degli nwait
   integer 		  boundary,offset;
   reg 			  firstEOWL;

initial begin                 // constructor sequence
   Start_address = `ADDRBUS_dim'h000000;
   EnableBurst = `FALSE;
   endSingleSynchronous = `FALSE;
   data = 16'hZZ;
   nWait = 0;
   IsNowWait = `FALSE;
   isValidData = `FALSE;
   xLatency=0;
   nRead=0;
   WaitState=0;
   firstEOWL=0;
end

always @(G_N) if (G_N==`TRUE) begin
   IsNowWait = `FALSE;
   isValidData = `FALSE;
   EnableBurst = `FALSE;
   endSingleSynchronous = `FALSE;
   data = 16'hZZ;
   nWait = 0;
   xLatency=0;
   nRead=0;
   WaitState=0;
   firstEOWL=0;
end


always @(isValidData) begin
   case (isValidData)

      1: if (!ConfigReg_man.isWaitBeforeActive) begin
         
         IsNowWait = `TRUE;
      end 

      0: begin if (!ConfigReg_man.isWaitBeforeActive) 
         IsNowWait = `FALSE;

         
      end
      
   endcase
end 


assign ISWAIT = (IsNowWait) ? `TRUE : `FALSE;


always @(negedge L_N) if(W_N==`HIGH) begin  : pollingBurst
   fork  : pollingBurst

      begin: L_lacthing
	 @(posedge L_N) if (ConfigReg_man.isSynchronous) begin 
            #1;
            Start_address = address;
            Sync_address =  address;
            firstEOWL=0;
            disable K_lacthing;
            
            @(posedge CLK) begin

               case(ConfigReg_man.BurstLength)

                  0: begin
                     boundary =16;
                     offset = address[3:0];
                  end
                  
                  16:begin
                     boundary =16;
                     offset = address[3:0];
                  end

                  8: begin
                     boundary =8;
                     offset = address[2:0];
                  end


                  4:begin
                     boundary =4;
                     offset = address[1:0];
                  end
               endcase
               
               xLatency = ConfigReg_man.Xlatency;
               WaitState = xLatency - (boundary - offset);
               
               if (WaitState < 0) WaitState =0; 
               nWait = 0;
               EnableBurst = `TRUE;
               data = 16'hXX;
               nRead = 0;
               isValidData = `FALSE;
               endSingleSynchronous=`FALSE;
               disable pollingBurst;
            end
         end
         else EnableBurst = `FALSE;
      end

      begin: K_lacthing
	 @(posedge CLK) if (ConfigReg_man.isSynchronous && L_N==`LOW) begin 
            #1;
            Start_address = address;
            Sync_address =  address;
            firstEOWL=0;
            disable L_lacthing;

            @(posedge CLK) begin

               case(ConfigReg_man.BurstLength)

                  0: begin
                     boundary =16;
                     offset = address[3:0];
                  end
                  
                  16:begin
                     boundary =16;
                     offset = address[3:0];
                  end

                  8: begin
                     boundary =8;
                     offset = address[2:0];
                  end


                  4:begin
                     boundary =4;
                     offset = address[1:0];
                  end
               endcase
               
               xLatency = ConfigReg_man.Xlatency;
               WaitState = xLatency - (boundary - offset); //
               if (WaitState < 0) WaitState =0; 
               nWait = 0;
               EnableBurst = `TRUE;
               data = 16'hXX;
               nRead = 0;
               isValidData = `FALSE;
               endSingleSynchronous=`FALSE;
               disable pollingBurst;
            end
         end
         else EnableBurst = `FALSE;
      end
   join
   $display("  %t address=%h",$time,Start_address);
end


always @(posedge (CLK)) if(G_N==`LOW) begin 
   if (EnableBurst) begin
      if (xLatency == 2 && ConfigReg_man.isWaitBeforeActive)
	 IsNowWait = `TRUE;

      if (xLatency == 1) begin
         isValidData = `TRUE;
         if (offset == 4'd15 && ConfigReg_man.isWaitBeforeActive && WaitState!=0 && (ConfigReg_man.isNoWrapBurst || ConfigReg_man.BurstLength == 5'd00)) begin
            IsNowWait = `FALSE;

	 end

	 
      end
      if (xLatency) xLatency = xLatency - 1; //vuol dire se xLatency e' >1 o diverso da zero????
   end     
end

always @(nRead) begin 
   if (isSingleSynchronous && nRead>=1) begin //End of SingleBurstRead???
      endSingleSynchronous=`TRUE;
      isValidData = `FALSE;
   end
   if((offset + nRead) == 4'd15 && ConfigReg_man.isWaitBeforeActive && WaitState!=0 && (ConfigReg_man.isNoWrapBurst || ConfigReg_man.BurstLength == 5'd00)) begin
      IsNowWait = `FALSE;

   end
   
end


always @(CLK) begin 
   
   if (EnableBurst) begin
      
      if (!xLatency) begin // burst is ongoing(after xLatency)
         
         if (!G_N) begin 
            
            if (nWait || endSingleSynchronous) data = `DATABUS_dim'hXXXX; //Wait State;
            
            else begin  // Read is Possible!
               // -- \\ 
               case (Read_man.Mode)
                  `ReadArray_bus       : begin  
                     data = Memory_man.Get(Sync_address);
                     @(posedge (CLK)) if (Info && !G_N) $write("[%t]  Burst Read: Memory[%h]=%h\n",$time,Sync_address,data);
                  end
                  `ReadCFI_bus         : begin 
                     data = CFIquery_man.Get(Sync_address);
                     @(posedge (CLK)) if (Info && !G_N) $write("[%t]  Burst Read: CFIMemory[%h]=%h\n",$time,Sync_address,data);
                  end
                  `ReadSignature_bus   : begin 
                     data = Signature_man.Get(Sync_address);
                     @(posedge (CLK)) if (Info && !G_N) $write("[%t]  Burst Read: Electronic Signature[%h]=%h\n",$time,Sync_address,data);
                  end
                  `ReadStatusReg_bus   : begin 
                     data = SR_man.SR;
                     @(posedge (CLK)) if (Info && !G_N) $write("[%t]  Burst Read: StatusRegister: %b\n",$time,data[`BYTE_range]);
                  end
                  default             : $display("[%t]  !!!Model Error: Read mode not recognized!!!", $time);
               endcase
               // -- \\     
            end 
            if((CLK)) begin
               
               if (!nWait) // Wait State??? if no calculate next address
               begin
                  
                  new_address = Sync_address + 1;
                  nRead = nRead + 1;
                  
               end
               if (!isSingleSynchronous) begin

                  // Calcultate Address for Sequential and Wrap Burst 
                  if ((ConfigReg_man.BurstLength != 5'd00) && ConfigReg_man.isWrapBurst)  begin      
                     case (ConfigReg_man.BurstLength_bit) 
                        3'd2: new_address = {Sync_address[`ADDRBUS_dim - 1 : 2], new_address[1:0] };
                        3'd3: new_address = {Sync_address[`ADDRBUS_dim - 1 : 3], new_address[2:0] };
                        3'd4: new_address = {Sync_address[`ADDRBUS_dim - 1 : 4], new_address[3:0] };
                     endcase
                  end 
		  
                  // Calculate Next Wait State
                  if (ConfigReg_man.isNoWrapBurst || (ConfigReg_man.BurstLength == 5'd00) )  //Calculate WAIT STATE
                     if ((new_address[3:0]==4'd0) && (Sync_address[3:0] == 4'd15)) begin
			
			if(!ConfigReg_man.isWaitBeforeActive)  begin

                           if (nWait<WaitState && !firstEOWL) begin
                              nWait = nWait+1; // Another Wait State???
                              isValidData = `FALSE;
                           end else begin 
                              nWait = 0;       // end of wait state
                              Sync_address = new_address;
                              isValidData = `TRUE;
                              firstEOWL =1;

                           end

			end else begin
                           if (nWait<WaitState-1 && !firstEOWL ) begin
                              nWait = nWait+1; // Another Wait State???
                              IsNowWait = `FALSE;
                           end else begin
                              nWait = 0;       // end of wait state
                              Sync_address = new_address;
                              IsNowWait = `TRUE;
                              firstEOWL =1;

                           end
			end 

		     end  
		  if (!nWait) 
                     if ((nRead<ConfigReg_man.BurstLength) || (ConfigReg_man.BurstLength==5'd00) // Read Data is Over Burst Lenght???
                         && !endSingleSynchronous) // end of SingleSinchronous Burst Read ???
                        Sync_address = new_address;
               end  // !isSyn
               
            end //aggiunta
            
         end //G_N
      end // XLatency
   end //Enable Burst
end


endmodule // end Burst Module 


// Erase Manager
// manage the erase functionality

module BlankCheckModule(address, data, progVoltOK, progHighVoltOK,Info);

   input [`WORD_range] data;
   input [`ADDRBUS_range] address;
   input 		  progVoltOK, progHighVoltOK;
   input 		  Info;

   event 		  ErrorCheckEvent, CompleteEvent;

   reg [`BYTE_range] 	  Status;
   reg [`ADDRBUS_range]   hold_address;
   reg [`BLOCKADDR_range] hold_block;


   reg 			  Busy;
   integer 		  i;
   time 		  startTime, delayTime, Erase_time;

initial begin                   // constructor sequence             
   Busy       = `FALSE;                                                    
   Erase_time = `MainBlockErase_time; //modificato
   delayTime  =  Erase_time;                                      
end         

always @(progVoltOK,progHighVoltOK,address) begin
   if (progHighVoltOK) 
      if (BankLib_man.isMainBlock(address)) Erase_time=`FastMainBlockErase_time;
      else  Erase_time=`FastParameterBlockErase_time;
   else 
      if (BankLib_man.isMainBlock(address)) Erase_time=`MainBlockErase_time;
      else  Erase_time=`ParameterBlockErase_time;
end 


function IsBusy;                // boolean function primitive       
   input obbl;                     // all functions require a parameter
   IsBusy = Busy;                // return Boolean value             
endfunction                                                         



// *********************
//
// Task checkConfirm :
//    check confirm code
//
// *********************

task checkConfirm;
   
   output  [`BYTE_range] outStatus;
   
   reg [`BYTE_range] 	 outStatus;

   begin

      if (data == `BLNKCHKconfirm_cmd) outStatus = `NoError_msg;
      
      else outStatus = `WrongBlankCheckConfirm_msg;
   end
endtask





// ****************
//
// Task Blank Check
// 
// ****************

task BlankCheck;
   
   output  [`BYTE_range] outStatus;
   
   reg [`BYTE_range] 	 outStatus;

   integer 		 hold_block;
   reg [`ADDRBUS_range]  hold_address;

   begin
      hold_address = address;
      hold_block = BankLib_man.getBlock(hold_address);
      
      if (BankLib_man.isMainBlock(address)) begin
         // Main Block
         delayTime = `MainBlankCheck_time;
      end else  // Parameter Block
         -> ErrorCheckEvent;
      disable Operation;

      fork
         begin: Operation
            Busy = `TRUE;
            startTime = $time;
            -> ErrorCheckEvent; 
            #delayTime         
               Memory_man.BlockBlankCheck(hold_block, Status);
            -> CompleteEvent;
         end
         @CompleteEvent
            disable Operation; 
      join
      outStatus = Status;
      Busy = `FALSE;
   end
endtask



always @(ErrorCheckEvent) begin
   Status = `NoError_msg;
   if (!progVoltOK)
      Status = `InvVDD_msg;
   if (BankLib_man.isParameterBlock(address)) begin
      
      Status = `WrongBlankCheckBlock;
      $display("parameter block");
   end    
   if (Status != `NoError_msg) begin
      ->CompleteEvent;
      disable ErrorCheck;
   end
   else
      fork : ErrorCheck
         @(negedge progVoltOK) Status = `InvVDD_msg;
         @(Status) -> CompleteEvent;
         @(CompleteEvent) disable ErrorCheck;
      join
end

endmodule  //end module Erase


// *********************************
//
// Program Buffer module :
//
//      program buffer functionality
//
// *********************************

module ProgramBufferModule(address,data,voltOK,Info);
   input [`ADDRBUS_range] address;
   input [`WORD_range] 	  data;
   input 		  voltOK, Info;
   event 		  ErrorCheckEvent, CompleteEvent, WatchAddressEvent;
   reg [`BYTE_range] 	  Status;
   reg [`DATABUS_dim-1:0] Count;

   reg [`WORD_range] 	  bufferData [`ProgramBuffer_range];

   reg [`ADDRBUS_range]   AddressLatched, startAddress,newAddress;
   reg 			  Busy, Suspended, Empty;
   time 		  startTime, delayTime;
   integer 		  i;

initial begin                 // constructor sequence             
   Busy = `FALSE;                                                  
   Suspended = `FALSE;
   Empty = `TRUE;
   delayTime = `ProgramBuffer_time;                                        
end                                                               

function IsBusy;              // boolean function primitive       
   input obbl;               // all functions require a parameter
   IsBusy = Busy;              // return Boolean value             
endfunction                                                       

function IsSuspended;         // boolean function primitive       
   input obbl;               // all functions require a parameter
   IsSuspended = Suspended;    // return Boolean value             
endfunction                                                       

function IsAddrSuspended;     // boolean function primitive       
   input [`ADDRBUS_range] addr;
   IsAddrSuspended = (Suspended && ((addr >= startAddress) && (addr < (startAddress + Count))));
endfunction


function [`DATABUS_dim-1:0] GetCount;
   input 		  required;
   GetCount = Count;
endfunction

task SetCount;                // sets the number of writes
   output [`BYTE_range] outStatus;
   reg [`BYTE_range] 	outStatus;
   begin
      outStatus = `NoError_msg;
      AddressLatched = address;
      Count = data + 1;
      

      if (Count > `ProgramBuffer_dim)
         outStatus = `BuffSize_msg;
      else if (BankLib_man.getBlock(AddressLatched) != BankLib_man.getBlock(AddressLatched + Count - 1))
	 
	 outStatus = `BlkBuffer_msg;
      else
         -> WatchAddressEvent;
   end
endtask

task Suspend;
   output [`BYTE_range] outStatus;
   reg [`BYTE_range] 	outStatus;
   begin
      delayTime = delayTime - ($time - startTime);
      #`ProgramSuspendLatency_time;
      outStatus = `NoError_msg;
      Status = `Suspend_msg;
      Suspended = `TRUE;
      -> CompleteEvent;
   end
endtask

task Resume;
   output [`BYTE_range] Status;
   begin
      Suspended = `FALSE;
      Program(Status);
end
endtask

task Load;
   output [`BYTE_range] Status;
   begin
      if (Empty) begin
         startAddress = address;
         if (Info) $display("[%t]  Buffer start address: %h",$time,startAddress);
         Empty = `FALSE;
         
      end
      bufferData[address[`ProgramBuffer_addrRange]] = data;

   end
endtask

task Program;
   output [`BYTE_range] outStatus;
   reg [`BYTE_range] 	outStatus;
begin

   fork
      begin : Operation
         Busy = `TRUE;
	 
         startTime = $time;
         -> ErrorCheckEvent;
         -> WatchAddressEvent; // disable address watch
         #delayTime
            for (i = startAddress;i < ( Count + startAddress); i = i + 1) begin
               Memory_man.Program(bufferData[i[`ProgramBuffer_addrRange]],i,Status);

end
         delayTime = `ProgramBuffer_time;
         -> CompleteEvent;
      end
      @CompleteEvent
         disable Operation;

   join
   if(!Suspended)
      for (i = 0; i < `ProgramBuffer_dim; i = i + 1) begin
	 bufferData[i] =16'hFFFF;
      end 
   Empty = `TRUE;
   outStatus = Status;
   Busy = `FALSE;
end
endtask

always @(ErrorCheckEvent) begin
   Status = `NoError_msg;
   if (BlockLock_man.IsLocked(AddressLatched))
      Status = `BlockLock_msg;
   else if (Suspended)
      Status = `SuspAcc_msg;
   else if (!voltOK)
      Status = `InvVDD_msg;

   if (Status != `NoError_msg)
      ->CompleteEvent;
   else
      fork : ErrorCheck
         @(negedge voltOK) Status = `InvVDD_msg;
         @(Status) -> CompleteEvent;
         @(CompleteEvent) disable ErrorCheck;
      join
end

always @(WatchAddressEvent) fork : AddressWatch
   while (`TRUE)
      @address
         if (BankLib_man.getBlock(address) != BankLib_man.getBlock(AddressLatched)) begin
            Status = `AddrTog_msg;
            -> CompleteEvent;
         end
   @WatchAddressEvent
      disable AddressWatch;
join

endmodule
