//top-class : spi_driver
//parameter : file_path: the path of *.txt file
//	      itf: spi interface (type==spi_if)
//	      half_sclk(option): =(half period of sclk)/(period of clk), default 5
//	      interval(option): the time to wait between the adjacent W/R operation/(period of clk), defaut 10
//example case :
/*  spi_driver		my_drv;

  initial begin

    my_drv = new();

    my_drv.file_path	 = "./../cfgdata/enveindex/wave_index_13.txt";
    my_drv.itf		 = my_if;
    my_drv.half_sclk	 = 10;
    my_drv.interval	 = 50;

    my_drv.do_drive();

  end//*/

class BinaryDataReader;
  
  // data_stream
  bit spi_data_queue[$];
  int cmd_head[$];

  // open & read txt_file
  function void read_txt_file(input string filename);
    int file_id;
    string line;
    bit [31:0] value;
    int i;
    //int cmd_head[$];
    int length;
    bit cmd;
    int cnt;

    // open
    file_id = $fopen(filename, "r");
    if (file_id == 0) begin
      $display("Error: Failed to open file %s", filename);
      return;
    end

    cnt = 0;
    // read
    while (!$feof(file_id)) begin
      //if ($fgets(line, file_id)) begin
	  $fscanf(file_id,"%h\n",value);
        //$display("\nvalue = %h",value);
        //$display("cnt = %h",cnt);
        if(cnt == 0) begin
          cmd = value[31];
          
          if(cmd) cmd_head.push_back(value);
          
          cnt ++;
        end
        else if(cnt == 1) begin
          if(cmd) cmd_head.push_back(value);
          
          length = value[15:0] / 4;
          
          if(!cmd) cnt ++;
          else cnt = 0;
        end
        else if((cnt == length-1 +2) | cmd) begin
          cnt = 0;
        end
        else begin
          cnt ++;
        end
        //$display("cmd = %h",cmd);
        //$display("length = %h",length);
        //$display(cmd_head);

        for (i = 31; i >= 0; i--) begin
          spi_data_queue.push_back(value[i]);
        end
      //end
    end
    

    // close
    $fclose(file_id);
  endfunction

  function void get_data_queue(ref bit data_queue[$]);
    data_queue = spi_data_queue;
	spi_data_queue.delete();
  endfunction
  
endclass

//----------------------------------------------------------------------

class spi_item;

  //Properties for Randomizing the MOSI 
  rand bit	  cmd;
  rand bit[24:0]  addr;
  rand bit[ 4:0]  cfgid;
  rand bit[31:0]  data[$];

  //Properties for Randomizing the pkt_sent process
  rand int	  interval;
  rand int	  half_sclk;

  constraint cstr {
    interval    <= 1000;
    half_sclk   >=  5;
    data.size   >= 1;
    data.size   <= 10000;
  }

  function new(string name="spi_item");
  endfunction : new

  extern function bit  compare(spi_item rhs_);
  extern function void print();
  extern function void fprint(integer fid);
  extern function void trprint(integer fid, ref int cmd_head[$]);
  extern function void unpack(ref bit stream[$]);
  extern function void pack(bit stream[$]);
  extern function void adapt(
	  ref bit stream[$]
	, int half_sclk
	, int interval
	);

endclass : spi_item



function bit spi_item::compare(spi_item rhs_);
  bit 		result=1'b1;
  int		i=0;

  if(this.data.size() != rhs_.data.size()) begin
    $display("data_sizes are different");
    result = 1'b0;
  end
  else begin
    for(i=0;i<data.size();i++)begin
    	if(data[i] != rhs_.data[i])
	  result = 1'b0;
    end
  end

  return result;
endfunction : compare


function void spi_item::print();
  int i=0;
  if(!cmd)$display("----------ONE-PKT-DRIVING----------");
  else    $display("---------ONE-PKT-COLLECTING--------");
  $display("cmd:\t%h",cmd);
  $display("addr:\t%h",addr);
  $display("cfgid:\t%h",cfgid);
  for(i=0;i<data.size();i++)begin
    $display("data[%2d]='h%h",i,data[i]);
  end
  $display("-----------------------------------");
endfunction : print


function void spi_item::unpack(ref bit stream[$]);
  int i=0,j=0;

  stream[0]=cmd;

  for(i=0;i<25;i++)
    stream[i+1] = addr[24-i];

  for(i=0;i<5;i++)
    stream[i+26]= cfgid[4-i];

  stream[31]=0;
  
  for(i=0;i<data.size();i++)begin
    //$display("i=%2d,datai=%b",i,data[i]);
    for(j=0;j<32;j++)begin
      stream[32+i*32+j]=data[i][31-j];
      //$display("i=%2d,j=%2d,streamij=%b",i,j,stream[32+i*32+j]);
    end
  end

endfunction


function void spi_item::fprint(integer fid);
  int i=0;
  if(!cmd)$fwrite(fid,"----------ONE-PKT-DRIVING----------\n");
  else    $fwrite(fid,"---------ONE-PKT-COLLECTING--------\n");
  $fwrite(fid,"cmd:\t%h\n",cmd);
  $fwrite(fid,"addr:\t%h\n",addr);
  $fwrite(fid,"length:\t%d\n",data.size());
  for(i=0;i<data.size();i++)begin
    $fwrite(fid,"%h\n",data[i]);
  end
  $fwrite(fid,"-----------------------------------\n");
endfunction : fprint

function void spi_item::trprint(integer fid, ref int cmd_head[$]);
  int i=0;
  $fwrite(fid,"---------ONE-PKT-COLLECTING--------\n");
  $fwrite(fid,"%h\n",cmd_head.pop_front());
  $fwrite(fid,"%h\n",cmd_head.pop_front());
  for(i=0;i<data.size();i++)begin
    $fwrite(fid,"%h\n",data[i]);
  end
  $fwrite(fid,"-----------------------------------\n");
endfunction : trprint


function void spi_item::pack(bit stream[$]);
  int i=0;
  bit[31:0]	data_temp=32'b0;

  //$display(stream);

  cmd = stream.pop_front();

  for(i=0;i<25;i++)
    addr[24-i] = stream.pop_front();

  for(i=0;i<5;i++)
    cfgid[4-i] = stream.pop_front();

  //reserved
  stream.pop_front();
  
  data.delete();
  while(stream.size()>0)begin
    for(i=0;i<32;i++)
      data_temp[31-i] = stream.pop_front();
    data.push_back(data_temp);
  end

endfunction

function void spi_item::adapt(ref bit stream[$], int half_sclk, int interval);
  int i=0;
  bit[31:0]	data_length=32'b0;
  bit[31:0]	data_temp=32'b0;

  cmd = stream.pop_front();

  //reserved
  stream.pop_front();

  for(i=0;i<5;i++)
    cfgid[4-i] = stream.pop_front();

  for(i=0;i<25;i++)
    addr[24-i] = stream.pop_front();

  for(i=0;i<32;i++)
    data_length[31-i] = stream.pop_front();
  data_length = {14'b0,data_length[19:2]};
  
  repeat(data_length) begin
    for(i=0;i<32;i++)
      data_temp[31-i] = cmd ? 1'b0 : stream.pop_front();
    data.push_back(data_temp);
  end

  this.half_sclk = half_sclk;
  this.interval = interval;
endfunction


//-----------------------------------------------------------------

class spi_driver;

  string 		file_path;
  virtual spi_if	itf;
  int			interval  = 10;
  int			half_sclk = 5 ;

  spi_item		m_trans;

  /*static string file_list[$]={
	 "./../cfgdata/instrmem/awg_inst.txt"			//0
	,"./../cfgdata/instrmem/LongFlattop_bin.txt"		//1
	,"./../cfgdata/instrmem/LongFlattopAmpAdj_bin.txt"	//2
	,"./../cfgdata/instrmem/LongRectangle_bin.txt"		//3
	,"./../cfgdata/instrmem/LongRectangle50us_bin.txt"	//4
	,"./../cfgdata/instrmem/SingleWaveACCZ_bin.txt"		//5
	,"./../cfgdata/instrmem/SingleWaveCombine_bin.txt"	//6
	,"./../cfgdata/instrmem/SingleWaveCosine_bin.txt"	//7
	,"./../cfgdata/instrmem/SingleWaveFlattop_bin.txt"	//8
	,"./../cfgdata/instrmem/SingleWaveRectangle_bin.txt"	//9
	,"./../cfgdata/instrmem/WaveHold_bin.txt"		//10
	,"./../cfgdata/instrmem/Condition_bin.txt"		//11
	,"./../cfgdata/instrmem/RabiFreqAmp_bin.txt"		//12
	,"./../cfgdata/instrmem/WaveHoldSingle_bin.txt"		//13
	,"./../cfgdata/instrmem/Cosine9_bin.txt"		//14
	//,"./../cfgdata/datamem/data_mem.txt"			//15
	,"./../cfgdata/datamem/Cosine9_data_bin.txt"		//15
	//,"./../cfgdata/enveindex/enve_index.txt"		//16
	,"./../cfgdata/enveindex/wave_index_13.txt"		//16
	//,"./../cfgdata/envemem/envelop_mem.txt"		//17
	,"./../cfgdata/envemem/wave_bin_13.txt"			//17
	,"./../cfgdata/envemem/rwave_bin_12.txt"		//18
	};//*/		

	
  function new(string name = "spi_seq");
  endfunction : new

  extern task send(ref spi_item pkt);

  task do_drive(integer o_file_path=0);

  	BinaryDataReader	reader = new();
  	//spi_item		m_trans;
  	bit			stream[$];

	reader.read_txt_file(file_path);
	reader.get_data_queue(stream);
	//$display(stream);
	while(stream.size()>0) begin
	//$display(stream.size());
	  m_trans = new("m_trans");
	  m_trans.adapt(stream,half_sclk,interval);
	  //m_trans.print();
	  send(m_trans);
	  m_trans.print();
	  if(o_file_path!=0) begin
	    //m_trans.fprint(o_file_path);
            if(m_trans.cmd) begin
            m_trans.trprint(o_file_path, reader.cmd_head);
            end
	  end
	end
 endtask : do_drive
 
endclass : spi_driver


task spi_driver::send(ref spi_item pkt);
  bit drv_stream[$];
  bit clt_stream[$];
  int i=0,j=0;
  int cs_time;
  int hs = pkt.half_sclk;

  while(!itf.rstn) begin
    itf.csn  = 1'b1;
    itf.sclk = 1'b1;
    @(posedge itf.clk);
  end
  //$display("unpacking");
  //unpack into bitstream
  pkt.unpack(drv_stream);
  //$display(stream);
  //$display("unpackend");
  //initialize chip_select and input_clk
  itf.csn   <= 1'b1;
  itf.sclk  <= 1'b1;
  itf.mosi  <= drv_stream[0];
  itf.cfgid <= pkt.cfgid;
  @(posedge itf.clk);
  itf.csn   <= 1'b0;
  itf.sclk  <= 1'b1;
  //$display("initialize end");
  //csn valid time: time for bitstream to be all sent
  cs_time = drv_stream.size()*2*hs;
  //$display(stream.size());
  //$display("drv_stream.size = %0d", drv_stream.size());
  //$display(cs_time);
  //drive the stream onto interface
  fork
    //Make sclk
    begin
      for(j=0;j<cs_time;j++) begin
        if(j%hs==0 && j!=0)
          itf.sclk <= ~itf.sclk;
	@(posedge itf.clk);
        //$display("j = %0d", j);
      end
      itf.sclk  = 1'b1;
      repeat(4) @(posedge itf.clk);
      itf.csn = 1'b1;
    end

    //Send data(at negedge)
    begin
      
      for(i=0;i<=cs_time;i++) begin 
	// Drive at negedge 
        if(i%(2*hs)==hs) begin
           itf.mosi <= drv_stream[(i/hs-1)/2];
	end
	// Collect at posedge
	else if(i%(2*hs)==0 && i!=0) begin
	   clt_stream.push_back(pkt.cmd ? itf.miso : itf.mosi);
	   //$write(itf.mosi);
	end
        @(posedge itf.clk);
      end
      //clt_stream collect the read_data/write_data, and 
      //the first 32bit should be consistent to the input command word
      for(i=0;i<32;i++) clt_stream[i]=drv_stream[i];
      //$display(clt_stream);
      //$display("i = %0d", i);
      
      pkt.pack(clt_stream);
      
    end
    
  join

  //interval between two pkt send operation 
  repeat(pkt.interval-1) 
    @(posedge itf.clk);

endtask	 
	 

