#!/usr/bin/perl
#bist6.pl:  BIST perl script for exercising T6 BIST
# 
use strict;
use warnings;
use FindBin ();
use lib "$FindBin::Bin";
use JLIB;
use T6_RW;
use Getopt::Long;

############################################################################
#                  Usage and Details Strings                               #
############################################################################

=item todo

TODO:  
    EDC_REF register:
       ENABLE_PERF        
       ECC BYPASS mode
       RefFreq:  Values other than 0x35.

      If CMD_ADDR has to be aligned to 64 Byte boundary,
      why are only bits 0 to 2 Reserved?  Should it be
      0 to 5 reserved?  Or should I shift address by 3?


    Possible documentation issues:

      + I cannot clear the ECC int cause bits until after I clear
        the ECC_STATUS register.  Talked to Swami, and he says
        a PR was filed on this by DV, but the result will probably
        be to leave this alone.

      + RDATA will not get updated after an error unless you also
        clear other error registers.  Clearing all of NUM_ERR, 
        ERR_FIRST_ADDR, ECC_STATUS and INT_CAUSE works, I have not
        determined precisely which ones need to be cleared to renable
        reads.

      + ECC errors prodused via ECC BYPASS do not result in increment 
        of NUM_ERR or setting of ERR_FIRST_ADDR.

        Is there some other register that reports Address of ECC errors?
        There is none in EDC module, but maybe the main memory controller
        module?

      + behavior change from T4:  -read no longer updates RDATA except
        on error and on backdoor read using -pat 0xc.  T4 would
        update RDATA with last 64 bytes read if no error regardless of 
        data pattern.


=cut

my ($Usagestr,$Detailsstr);

{ #brackets around EOS definition keeps xemacs auto-indenter from going nuts.
$Usagestr = <<EOS;
 Brief: example.pl tests the T3_RW.pm library.
 Usage: example.pl [options] <filename>

 Desc:
 options:
  -help              print this message
  -details           print even more verbose help message
  -i     <interface>

  
  -clear             clear the WDATA, RDATA, NUM_ERR and ERR_FIRST_ADDR
  -dump              dump bist register set.
  -init              perform write operations to fill both edc0 and edc1
  -read              perform a read operation (implicit -clear before read)
  -write             perform a write operation
  -writeread         perform "write followed by read and compare" operation.
  -wr                perform a write operation, then perform read operation.
  -memdump           perform a series of reads using -pat 12 to dump memory.

  -ck                After read or writeread or wr command, read number
                     of errors in ERR register and return that as status
                     of the command.


  -edc0              Perform test on EDC0 (base addr 0x50000)
  -edc1              Perform test on EDC1 (base addr 0x50800)
  -mc                Perform test on MC0  (base addr 0x41400)
  -mc0               Perform test on MC0  (base addr 0x41400)

  -eccbypass [0|1]   clear/set ECC_BYPASS bit in EDC_REF
  -perf      [0|1]   clear/set ENABLE_PERF bit in EDC_REF.
  -gap   <0-255>     Gap between successive BIST commands.0=256, 1=0 gap.
  -burstlen [0|1|2]  Burst len 0=64b, 1=128b, 2=256b.  (defaults to 0)
                     Note: if burstlen==0, minimum value for -len is 64.
                           if burstlen==1, minimum value for -len is 128.
                           if burstlen==2, minimum value for -len is 256.

  -addr <addr>       starting byte address.  Must be multiple of 64 bytes.
                     default is 0.
  -len  <len>        length, in bytes (defaults to minimum value for the
                     selected burstlen, which is 64 for default burstlen==0)
  -maxlen            Use maximum length for specified memory.
  -pat  <pattern>    data pattern.  Values for <pattern>:
                      0:        00 pattern
                      1:        FF pattern
                      2:        AA pattern
                      3:        55 pattern
                      4:        invert every 32bit.
                      5:        invert every 64bit.
                      6:        invert every 128bit.
                      7:        random pattern (not yet fully implemented)
                      8:        User pattern
                      9:        walking 1 pattern
                     10 (0xA):  walking 0 pattern
                     11 (0xB):  write address pattern
                     12 (0xC):  backdoor read/write
                     13 (0xD):  pseudo random pattern

   -all              run a -wr test for all test patterns except 
                     random, user and backdoor.  Run on all memories 
                     unless a specific one is set.  Run maximum length 
                     of memory, unless length is set.  Start at address 0
                     unless address is set.

   -seed <seed>      Seed to use for psuedo-random data pattern 13.
   -seedrand         Use a new random seed for each operation.

   -ignore           Ignore errors when performing a memdump.

 Note:  For FPGA you must first:
                   cxgbtool eth1 reg 0x7900=0x40 #to enable EDC0
                   cxgbtool eth1 reg 0x7980=0x40 #to enable EDC1
                   cxgbtool eth1 reg 0x7500=0x3  #to enable MC


EOS
}

{
$Detailsstr = <<EOS;
 Details:
  bist6.pl can be used to run T6 BIST engine test, dump BIST
  related registers, etc.

EOS
}

sub Usage {
  print $Usagestr;
  if ($_[0]) {print $Detailsstr;}
}

sub Usage_err {
  print $Usagestr;
  print "ERROR: $_[0]\n";
  exit 1;
}



##--------------------------------------------------------------------------
# GLOBAL VARIABLES, if any.
##--------------------------------------------------------------------------



use constant EDC_MAX_LEN  => 0x400000;  #4 MBytes of memory per EDC.
use constant MC_MAX_LEN   => 0x20000000;

use constant PAT_BACKDOOR_READ =>12;

my $g_mc0_bist_regH =
  {
   memblock       => "mc",
   CMD            => 0x41400,
   CMD_ADDR       => 0x41404,
   CMD_LEN        => 0x41408,
   DATA_PATTERN   => 0x4140C,
   USER_WDATA0    => 0x41414,
   USER_WDATA1    => 0x41418,
   USER_WDATA2    => 0x4141C,
   NUM_ERR        => 0x41480,
   ERR_FIRST_ADDR => 0x41484,
   RDATA          => 0x41488,
   SEED           => 0x414d0,

   INT_ENABLE     => 0x41314,
   INT_CAUSE      => 0x41318,
   ECC_STATUS     => 0x4131C,
   ECC_CTRL       => 0x41308,

   MC_CFG_STATUS     => 0x41324,

   min_len       => 128,
   max_len       => MC_MAX_LEN, 
  };


my $g_edc0_bist_regH =
  {
   memblock       => "edc0",
   REF            => 0x50000,
   CMD            => 0x50004,
   CMD_ADDR       => 0x50008,
   CMD_LEN        => 0x5000C,
   DATA_PATTERN   => 0x50010,
   USER_WDATA0    => 0x50014,
   USER_WDATA1    => 0x50018,
   USER_WDATA2    => 0x5001C,
   NUM_ERR        => 0x50020,
   ERR_FIRST_ADDR => 0x50024,
   RDATA          => 0x50028,
   SEED           => 0x50400,

   INT_ENABLE     => 0x50074,
   INT_CAUSE      => 0x50078,
   ECC_STATUS     => 0x5007C,
   
   min_len        => 64,
   max_len        => EDC_MAX_LEN, 
  };

my $g_edc1_bist_regH =
  {
   memblock       => "edc1",
   REF            => 0x50800,
   CMD            => 0x50804,
   CMD_ADDR       => 0x50808,
   CMD_LEN        => 0x5080C,
   DATA_PATTERN   => 0x50810,
   USER_WDATA0    => 0x50814,
   USER_WDATA1    => 0x50818,
   USER_WDATA2    => 0x5081C,
   NUM_ERR        => 0x50820,
   ERR_FIRST_ADDR => 0x50824,
   RDATA          => 0x50828,
   SEED           => 0x50C00,

   INT_ENABLE     => 0x50874,
   INT_CAUSE      => 0x50878,
   ECC_STATUS     => 0x5087C,

   min_len        => 64,
   max_len        => EDC_MAX_LEN,
  };


my $g_regH=$g_edc0_bist_regH;

my $g_busymsg=1;


##--------------------------------------------------------------------------
# PARSING LOOP:  For most simple options, you can just edit $opts.
#                More complex options require custom code in the
#                parser below.
##--------------------------------------------------------------------------

my $opts = 
  {
   help    => {def=>0      ,     switch=>"flag", aliases=>[qw(what)], },
   details => {def=>0      ,     switch=>"flag" ,                  },
   edc0    => {def=>0      ,     switch=>"flag" ,                  },
   edc1    => {def=>0      ,     switch=>"flag" ,                  },
   mc      => {def=>0      ,     switch=>"flag" ,                  },
   mc0     => {def=>0      ,     switch=>"flag" ,                  },

   clear   => {def=>0      ,     switch=>"flag" ,                  },
   dump    => {def=>0      ,     switch=>"flag" ,                  },
   init    => {def=>0      ,     switch=>"flag" ,                  },
   read    => {def=>0      ,     switch=>"flag" ,                  },
   ck      => {def=>0      ,     switch=>"flag" ,                  },
   write   => {def=>0      ,     switch=>"flag" ,                  },
   writeread => {def=>0    ,     switch=>"flag" ,                  },
   wr      => {def=>0      ,     switch=>"flag" ,                  },
   memdump => {def=>0      ,     switch=>"flag" ,                  },

   perf    => {def=>undef   ,    switch=>"dx",                     },
   eccbypass => {def=>undef ,    switch=>"dx",                     },

   addr    => {def=>"0"     ,    switch=>"dx",                     },
   len     => {def=>undef   ,    switch=>"dx",                     },
   maxlen  => {def=>0      ,     switch=>"flag" ,                  },
   pat     => {def=>undef   ,    switch=>"dx",                     },
   all     => {def=>0      ,     switch=>"flag" ,                  },
   seed    => {def=>undef  ,     switch=>"dx",                     },
   seedrand => {def=>0      ,     switch=>"flag" ,                 },   
   gap     => {def=>undef  ,     switch=>"dx",                     },
   burstlen => {def=>0     ,     switch=>"dx",                     },
   terse   => {def=>0      ,     switch=>"flag" ,                  },   
   ignore  => {def=>0      ,     switch=>"flag" ,                  },
  };


sub cla_parser {
  my $cla={};
  my $go={};

  #START CLA and GO hash construction 
  #-----------------------------
  foreach my $key (keys %$opts) {
    my $optH=$opts->{$key};
    $cla->{$key}=$optH->{def};

    if (exists $optH->{switch}) {
      my @aliases=($key);
      if (exists $optH->{aliases}) {push (@aliases,@{$optH->{aliases}});}
      foreach my $alias (@aliases) {
	if ($optH->{switch} eq "flag") {
	  $go->{$alias} = \$cla->{$key};
	} elsif ($optH->{switch} =~ /^(|s|x|dx)$/) {
	  $go->{"$alias=s"} = \$cla->{$key};
	}
      }
    }
  }
  #  print indent_str(sprintf_hash($go),5)
  #END OF CLA and GO HASH construction.
  #-----------------------------------



  my @SAVED_ARGV=@ARGV;  #save the original array

  #START EXTRNAL LIBRARY SWITCH PARSING
  #---------------------------------------
  @ARGV=JLIB_init(@ARGV);
  @ARGV=set_iface(@ARGV);  
  #END EXTERNAL LIBRARY SWITCH PARSING
  #--------------------------------------

  #configure parser, depending on whether you want case insensitivy, etc.
  #  Getopt::Long::Configure("no_ignore_case", "no_auto_abbrev");
  Getopt::Long::Configure("ignore_case", "auto_abbrev");

  #parse any switches.  They'll be removed from @ARGV if present.
  if (!GetOptions( %$go)) {Usage_err("command line argument errors\n");}

  #HELP and details go here, and ignore other command line arguments
  if ($cla->{help} or $cla->{details}) {
    Usage($cla->{details});
    exit 0;
  }


  #if we still have arguments left, they are errors.
  if (scalar @ARGV)  {Usage_err("unknown command line arguments: @ARGV\n");}


  #START POSTPROCESSING:  
  #--------------------
  #Put number conversions, argument checking here.  Also Could check
  #for required arguments or conflicting argument checks, etc.
  #If you don't need postprocessing or you do it in later, delete this section.
  my $name;
  my $type;
  my $value;
  my $err="";
  foreach my $key (keys %$opts) {
    $name=$key;
    my $optH=$opts->{$key};
    $value=$cla->{$key};
    if (not defined $value) {next}  #undefined values were not entered
    $type="";
    if   (exists $optH->{switch}) {$type=$optH->{switch}}
    elsif (exists $optH->{type})  {$type=$optH->{type}}

    #    print "NAME: $name, TYPE: $type,  VALUE: $value\n";
    
    $err="1"; #assume an error;
    if ($type eq 'x') {
      if (!x2n($value,\$cla->{$key}))  {
	$err="$name requires a hex number as an argument, not \"$value\"\n";
	last; #exit with $err ne "";
      } 
    } elsif ($type eq 'dx') {
      if (!dx2n($value,\$cla->{$key})) {
	$err="$name requires a number as an argument, not \"$value\"\n";
	last;
      } 
    } elsif ($type eq '-e') {
      if (!(-e $value)) {
	$err="The filename $value doesn't exist\n";
	last;
      }
    }
    $err=""; #if we got here, no error.
  }

  if ($err) {
    if ($err ne "1") {Usage_err($err);}
    Usage_err("The command line argument $name had an invalid ".
	    "parameter: $value\n");
  }

  



  #If user specified memories to test, use those.  If not, then
  #if -all, use them all, else use edc0.
  $cla->{memblock_list}=[];  #set list to null
  if ($cla->{edc0}) {
    push (@{$cla->{memblock_list}},"edc0");
    $g_regH=$g_edc0_bist_regH;
  }
  if ($cla->{edc1}) {
    push (@{$cla->{memblock_list}},"edc1");
    $g_regH=$g_edc1_bist_regH;
  }
  if ($cla->{mc} or $cla->{mc0}) {
    push (@{$cla->{memblock_list}},"mc0");
    $g_regH=$g_mc0_bist_regH;
    get_mc_lens($cla);
  }

  #if no memory specified, use edc0 unless -all specified, in which case 
  #use all memories.
  if (0==scalar @{$cla->{memblock_list}}) {
    if ($cla->{all}) {
      $cla->{memblock_list}=[qw(edc0 edc1 mc0)];  #put all memories in list
      $g_regH=$g_mc0_bist_regH;  #needed for get_mc_lens call.
      get_mc_lens($cla);
    } else {
      push (@{$cla->{memblock_list}},"edc0");
    }
    $g_regH=$g_edc0_bist_regH;
  }

  if (0==scalar @{$cla->{memblock_list}}) {
      push (@{$cla->{memblock_list}},"edc0");
      $g_regH=$g_edc0_bist_regH;
  }


  #if length not specified, use minimum of 128 unless -all was set.
  #if -all set, use the size of the memory.
  my $min_len=128;
  if    ($cla->{burstlen}==1) {$min_len= 128;}
  elsif ($cla->{burstlen}==2) {$min_len= 256;}

  if (not defined $cla->{len}) {
      if ($cla->{all}) {
	  $cla->{len}=1000;
	  $cla->{maxlen}=1;
      } else {
	  $cla->{len}=$min_len;
      }
  }

  if ($cla->{len} < $min_len) {
    Usage_err("The minimum length for this command is $min_len bytes");
  }



  #If user specified pattern, use that, else if -all or -patall, use all except
  # random, user and backdoor.  If not -all, default to backdoor read.
  $cla->{pat_list}=[];
  if (defined $cla->{pat}) {
    push (@{$cla->{pat_list}},$cla->{pat});
  } else {
    if ($cla->{all} or $cla->{patall}) {
      $cla->{pat}=0;
      $cla->{pat_list}=[ 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 13 ];
    } 
    else {
      $cla->{pat}=0x0C;
      push (@{$cla->{pat_list}},$cla->{pat});
    }
  }

  $cla->{test}="dump";
  if    ($cla->{clear})     {$cla->{test}="clear"}
  elsif ($cla->{init})      {$cla->{test}="init"}
  elsif ($cla->{read})      {$cla->{test}="read"}
  elsif ($cla->{write})     {$cla->{test}="write"}
  elsif ($cla->{writeread}) {$cla->{test}="writeread"}
  elsif ($cla->{wr})        {$cla->{test}="write_then_read"}
  elsif ($cla->{memdump})   {$cla->{test}="memdump"}

  # END OF POSTPROCESSING
  #----------------------

  return $cla;
}


##-------------------------------------------------------------------------
#                     register access functions
##-------------------------------------------------------------------------

sub bist_rreg {
  my $regname=shift;
  (exists $g_regH->{$regname}) or 
    die "Programming error: No $g_regH->{$regname} register";
  return rreg($g_regH->{$regname});
}

sub bist_wreg {
  my $regname=shift;
  (exists $g_regH->{$regname}) or 
    die "Programming error: No $g_regH->{$regname} register";
  my $val=shift;
  return wreg($g_regH->{$regname},$val);
}


sub bist_wait_not_busy {
  my $ctl = ($#_>=0) ? shift : "";
  
  my $busy=1;  #assume we will timeout.

  my $start=time;
  my $now=$start;
  my $last_update_time=$now;

  my $timeout=240;
  my $update_interval=1;
  my $update_cnt=0;
#  print "START TIME: $start\n";


  while ($busy and $now-$start<$timeout) {
    my $raw_busy=bist_rreg("CMD");
    $busy=$raw_busy>>31 & 1;
    if (! $busy and $update_cnt>0) {
      if ($g_busymsg) {printf "BUSY: 0x%x (not busy)\n",$raw_busy;}
    } else {
      $now=time;
      if ($now-$last_update_time>=$update_interval) {
	if ($g_busymsg) {printf "BUSY: 0x%x (still busy)\n",$raw_busy;}
	$last_update_time=$now;
	$update_cnt++;
      }
    }
  }


  if ($busy and $ctl=~ /\bdie\b/) {
    die("Timeout in bist_wait_not_busy. Bist test failed to complete\n".
	"within $timeout seconds\n");
  }

  return $busy;
}

##-------------------------------------------------------------------------
#                     get_mc_lens
##-------------------------------------------------------------------------

my $den_xlat = [
		0x200_0000,   #0->64Mbytes/2
		0x400_0000,   #1->128Mbytes/2
		0x800_0000,   #2->256Mbytes/2
		0x1000_0000,  #3->512Mbytes/2
		0x2000_0000,  #4->1024Mbytes/2
		];

my $width_xlat = [
		  8,         #0->64-bit*2
		  4,         #1->32-bit*2
		  2,         #2->16-bit*2
		  1,         #3->8-bit*2
		  16,        #4->128-bit*2
		  ];
		


sub get_mc_lens {
    my $cla=shift;

    my $val=bist_rreg("MC_CFG_STATUS");
    my $den=($val>>6)&0x7;
    my $width=($val>>1)&0x7;

    if ($den>4 or $width>4) {
	printf("Invalid MC_CFG_STATUS: 0x%x\n",$val);
	exit 1;
    }
    
    my $max_len=$den_xlat->[$den]*$width_xlat->[$width];
    #printf("MEM SIZE: %x, $den,$width, %x * %x = %x\n",$val,$den_xlat->[$den],$width_xlat->[$width],$max_len);

    $g_regH->{max_len}=$max_len;
    if ($width==4) {$g_regH->{min_len}=128;}
    else           {$g_regH->{min_len}=64;}

    return $max_len;
}



##-------------------------------------------------------------------------
#                     bist_clear
##-------------------------------------------------------------------------

sub bist_clear {
  my $cla=shift;

  #Clear the write data and the read data
  if ($cla->{clear}) {  #only clear write data is user -clear.
      for (my $i=0 ; $i<3 ; ++$i) {
	  wreg($g_regH->{USER_WDATA0}+($i<<2),0);
      }
  }

  for (my $i=0 ; $i<18 ; ++$i) {
      wreg($g_regH->{RDATA}+($i<<2),0xFFFFFFFF);  #write 1's to clear?
  }

  #clear num_err.
  bist_wreg("NUM_ERR",0xFFFFFFFF);  #write 1's to clear?
  #clear err_first_addr
  bist_wreg("ERR_FIRST_ADDR",0xFFFFFFFF);  #write 1's to clear?

  #Must clear ECC_STATUS before INT_CAUSE, or INT_CAUSE won't clear.
  bist_wreg("ECC_STATUS",0xFFFFFFFF);  #write 1's to clear.
  bist_wreg("INT_CAUSE",0xFFFFFFFF);  #write 1's to clear.

}


##-------------------------------------------------------------------------
#                     bist_dump
##-------------------------------------------------------------------------
##: bist_dump
#:  ---------
#:  
#:  Return number of errors reported by hardware.

sub bist_reg_dump {
  my $regname=shift;
  my $s=sprintf("[0x%4x] %-15s:  0x%x\n",  
		$g_regH->{$regname},$regname,bist_rreg($regname));
  return $s;
}


sub bist_dump {
  my $cla=shift;
  my $ctl= ($#_>=0) ? shift : "";
  my $num_err=0;
  my $int_cause=0;
  my $ecc_status=0;

  my $s="";
  my $terse_msg="";
  if ($g_regH->{memblock} eq "edc0" or $g_regH->{memblock} eq "edc1") {
    $s=sprintf("[0x%4x] REF:             0x%x\n",  
	       $g_regH->{REF},bist_rreg("REF"));
  }
  
  $s.=bist_reg_dump("CMD");
  $s.=bist_reg_dump("CMD_ADDR");
  $s.=bist_reg_dump("CMD_LEN");
  $s.=bist_reg_dump("DATA_PATTERN");

  $s.=sprintf("[0x%4x-0x%4x] WDATA: ",  
	      $g_regH->{USER_WDATA0},$g_regH->{USER_WDATA1});
  for (my $i=0 ; $i<3 ; ++$i) {
    my $val=rreg($g_regH->{USER_WDATA0}+($i<<2));
    $s.=sprintf " %08x",$val;
  }
  $s.="\n";

  $num_err=bist_rreg("NUM_ERR");
  $s.=sprintf("[0x%4x] %-15s:  0x%x",  
	      $g_regH->{NUM_ERR},"NUM_ERR",$num_err);
  if ($num_err) {$s.="      *****";}
  $s.="\n";
  
  $s.=bist_reg_dump("ERR_FIRST_ADDR");

  $s.=sprintf("[0x%4x-0x%4x] RDATA:\n", 
	      $g_regH->{RDATA},$g_regH->{RDATA}+(17<<2));
  for (my $i=0 ; $i<18 ; ++$i) {
    my $val=rreg($g_regH->{RDATA}+($i<<2));
    $s.=sprintf " %08x",$val;
    if ($i % 8 ==7) {$s.="\n";}
  }
  $s.="\n";
  $s.=bist_reg_dump("SEED");

  $s.=bist_reg_dump("INT_ENABLE");
  $int_cause=bist_rreg("INT_CAUSE");
  $s.=sprintf("[0x%4x] %-15s:  0x%x\n",  
	      $g_regH->{"INT_CAUSE"},"INT_CAUSE",$int_cause);
  $ecc_status=bist_rreg("ECC_STATUS");
  $s.=sprintf("[0x%4x] %-15s:  0x%x\n",  
	      $g_regH->{"ECC_STATUS"},"ECC_STATUS",$ecc_status);


  if ($ctl eq "") {
    if (not $cla->{terse} or $num_err>0, or $int_cause or $ecc_status) {
      print $s."\n";
    } else {
      print "No Errors";
    }
    return ($num_err,$int_cause,$ecc_status);
  }

  return ($num_err,$int_cause,$ecc_status,$s);
}




##-------------------------------------------------------------------------
#                     bist_write
##-------------------------------------------------------------------------

sub bist_write {
  my $cla=shift;

  if ($cla->{test} eq "writeread") {
      bist_clear($cla);  #clear WDATA/RDATA and any prior errors.
  }

  my $burstlen=$cla->{burstlen};
  if ($burstlen>2) {$burstlen=2;}

  bist_wreg("CMD_ADDR", $cla->{addr});
  bist_wreg("CMD_LEN", $cla->{len} >> $burstlen);
  bist_wreg("DATA_PATTERN",$cla->{pat});

  #For now, fill in constant data pattern.
#  bist_wreg("USER_WDATA0",0x12345678);
#  bist_wreg("USER_WDATA1",0x9abcdef0);
#  bist_wreg("USER_WDATA2",0x00);  #The ECC word.

  my $gap=$cla->{gap};
  if    (!defined $gap) {   #default gap is 1 for pure writes, 2 if writeread
    $gap=1;
    if ($cla->{test} eq "writeread") {$gap=2;}
  } 
  elsif ($gap>0xFF) {$gap=0xFF;}
  $gap=$gap<<8;


  


  my $opcode=0;
  if ($cla->{test} eq "writeread") {$opcode=2;}
  
  bist_wreg("CMD",0x8000_0000 | ($burstlen<<16) | $gap | $opcode);

  bist_wait_not_busy("die");

}


##-------------------------------------------------------------------------
#                     bist_read
##-------------------------------------------------------------------------

##: bist_read
#:  ---------
#:  
#:  Return number of errors reported by hardware.

sub bist_read {
  my $cla=shift;
  my $ctl= ($#_>=0) ? shift : "";
  my $ret=0;
  my $s;

  if ($cla->{pat}!=0x0c) {
    bist_clear($cla);  #clear WDATA/RDATA and any prior errors.
  }

  my $burstlen=$cla->{burstlen};
  if ($burstlen>2) {$burstlen=2;}
  bist_wreg("CMD_ADDR", $cla->{addr});
  bist_wreg("CMD_LEN", $cla->{len}>>$burstlen);
  bist_wreg("DATA_PATTERN",$cla->{pat});

  my $gap=$cla->{gap};
  if    (! defined $gap) {$gap=2}    #default gap is 2 for reads.
  elsif ($gap>0xFF) {$gap=0xFF;}
  $gap=$gap<<8;


  bist_wreg("CMD",0x8000_0001 | ($burstlen<<16) | $gap);


  bist_wait_not_busy("die");

  if ($ctl eq "") {
      return bist_dump($cla,$ctl);
  } else {
      return bist_dump($cla,$ctl);
  }
}


##-------------------------------------------------------------------------
#                     bist_memdump
##-------------------------------------------------------------------------

##: memdump_64_bytes
#:  ----------------
#:  read 64 bytes from the RDATA registers, and dump them as hexdump.

sub memdump_64_bytes {
  my $cla=shift;
  my $ctl=shift;
  my $off=shift;
  
  my $A=[];
  for (my $i=0 ; $i<64 ; $i+=4) {
    my $val=rreg($g_regH->{RDATA}+(60-$i));
    #this endianess matches xxd -g1 /sys/kernel/debug/cxgb/<dbsf>/edc0
    $A->[$i+3] =  $val    &0xFF;
    $A->[$i+2] = ($val>>8)&0xFF;
    $A->[$i+1] = ($val>>16)&0xFF;
    $A->[$i]   = ($val>>24)&0xFF;
  }
  

  
  for (my $i = 0 ; $i<64 ; $i++) {
    if (0 == ($i % 16)) {printf("\n%08x: ",$off+$i);}
    elsif (0 == ($i % 8)) { print("  ");}
    printf(" %02x",$A->[$i]);
  }

}

##: bist_memdump
#:  ---------
#:  
#:  Perform a series of reads to dump memory.

sub bist_memdump {
  my $cla=shift;
  my $ctl= ($#_>=0) ? shift : "";
  my $ret=0;
  my $s;

  bist_clear($cla);

  my $off=$cla->{addr};
  my $len=0;
  
  while ($len < $cla->{len}) {
    bist_wreg("CMD_ADDR", $off);
    bist_wreg("CMD_LEN", $g_regH->{min_len});
    bist_wreg("DATA_PATTERN",PAT_BACKDOOR_READ);
    my $gap=$cla->{gap};
    if    (! defined $gap) {$gap=2}    #default gap is 2 for reads.
    elsif ($gap>0xFF) {$gap=0xFF;}
    $gap=$gap<<8;
    bist_wreg("CMD",0x8000_0001 | $gap);
    bist_wait_not_busy("die");

    if (not $cla->{"ignore"}) {
      my $num_err  =bist_rreg("NUM_ERR");   
      my $int_cause=bist_rreg("INT_CAUSE");
      if ($num_err>0 or ($int_cause & 0x7)) {
	print "\n";
	$ret=bist_dump($cla,$ctl);
	printf("Error Reading memory at address 0x%08x\n",$off);
	print $ret;
	return $ret;
      }
    }

    memdump_64_bytes($cla,$ctl,$off);
    $off+=64;
    $len+=64;
  }

  return $ret;
}


##-------------------------------------------------------------------------
#                     bist_multi_loop
##-------------------------------------------------------------------------

##: bist_multi_loop
#:  
#:  RET: Number of errors detected.
sub bist_multi_loop {
  my $cla=shift;

  my $total_err_cnt=0;
  my $total_int_cause=0;
  my $total_ecc_status=0;

  foreach my $memblock (@{$cla->{memblock_list}}) {
    foreach my $pat (@{$cla->{pat_list}}) {

      $cla->{pat}     =$pat;


      if    ($memblock eq "edc0") {$g_regH=$g_edc0_bist_regH;}
      elsif ($memblock eq "edc1") {$g_regH=$g_edc1_bist_regH;}
      elsif ($memblock eq "mc0" ) {$g_regH=$g_mc0_bist_regH;}

      if ($cla->{maxlen}) {
	$cla->{len}=$g_regH->{max_len};
      }

      my $seed=undef;
      if ($pat==13 and defined $cla->{seed} or $cla->{seedrand}) {
	  if (defined $cla->{seed}) {$seed=$cla->{seed}}
	  else {$seed=get_random_uint(0,0xffff_ffff);}
	  bist_wreg("SEED",$seed);
      }

      printf("BIST: -$memblock  -pat %2u -addr 0x%x ".
	     "-len 0x%x ($cla->{len})",$cla->{pat},$cla->{addr},$cla->{len});
      if (defined $seed) {printf(" -seed 0x%x",$seed);}
      if (!$cla->{terse}) {print "\n"}

      my $out="";
      my $err_cnt=0;
      my $int_cause=0;
      my $ecc_status=0;
      if ($cla->{test} eq "write") {
	bist_write($cla);
      }elsif ($cla->{test} eq "read") {
	($err_cnt,$int_cause,$ecc_status,$out)=bist_read($cla,"noprint");
      }elsif ($cla->{test} eq "writeread") {
	bist_write($cla);
	($err_cnt,$int_cause,$ecc_status,$out)=bist_dump($cla,"noprint");
      }elsif ($cla->{test} eq "write_then_read") {
	bist_write($cla);
	############
	### uncomment to test error reporting an clean up after error.
	#	if ($cla->{pat}==10) {$cla->{pat}=0;}  #artificial error
	###########
	($err_cnt,$int_cause,$ecc_status,$out)=bist_read($cla,"noprint");	
      }
      $total_err_cnt+=$err_cnt;
      $total_int_cause|=$int_cause;
      $total_ecc_status+=$ecc_status;
	  

      if (not $cla->{terse}) {
	print $out."\n";
      } else {
	if (0==$err_cnt) { print ": No Errors\n";} 
	else {print ": Errors:\n".$out."\n";}
      }

    } #end foreach pat
  }  #end foreach memblock



  return ($total_err_cnt,$total_int_cause,$total_ecc_status);
}



##-------------------------------------------------------------------------
#                     main_exec()
##-------------------------------------------------------------------------

sub main_exec {
  my $cla = $_[0];
  my $ret=0;
  my $err_cnt=0;
  my $int_cause=0;
  my $ecc_status=0;

  #I like to print the interface out as a double check, but for some scripts
  #you'll want to delete this as it messes up the display.
#  print "iface = $iface\n";  

  if (DBG_is_on("\LFindBin")) {
    # FindBin can be used to find directory that scripts reside in
    # so that you can find associated data files.  FindBin::Bin
    # is what you would typically use unless links are involved.
    print '$Bin: '."$FindBin::Bin\n";
    print '$Script: '."$FindBin::Script\n";
    print '$RealBin: '."$FindBin::RealBin\n";
    print '$RealScript: '."$FindBin::RealScript\n";
  }

  #display the parsing results.
  if (DBG_is_on("cla")) {
    print "Command Line Argument Hash:\n";
    print indent_str(sprintf_hash($cla),5);
    print "\n";
  }


  if ($cla->{terse}) {$g_busymsg=0}

  if (defined $cla->{perf}) {
    ($cla->{memblock} ne "mc0")  or 
      die "Can't set perf on MC as no MC_REF register\n";
    my $val=bist_rreg("REF");
    if ($cla->{perf}) {$val |= 1<<17;}
    else              {$val &= ~(1<<17);}
    bist_wreg("REF",$val);
  }
  if (defined $cla->{eccbypass}) {
    if ($cla->{memblock} ne "mc0") {
      my $val=bist_rreg("REF");
      if ($cla->{eccbypass}) {$val |= 1<<16;}
      else              {$val &= ~(1<<16);}
      bist_wreg("REF",$val);
    } else { #MC0
      my $val=bist_rreg("ECC_CTRL");
      if ($cla->{eccbypass}) {$val |= 1<<1;}
      else              {$val &= ~(1<<1);}
      bist_wreg("ECC_CTRL",$val);
    }
  }


  

  if ($cla->{test} eq "clear") {
    bist_clear($cla);
    ($err_cnt,$int_cause,$ecc_status)=bist_dump($cla);
  }elsif ($cla->{test} eq "dump") {
    bist_dump($cla);
  }elsif ($cla->{test} eq "memdump") {
    bist_memdump($cla);
  }elsif ($cla->{test} =~ /^(write|read|writeread|write_then_read)$/) {
    ($err_cnt,$int_cause,$ecc_status)=bist_multi_loop($cla);
  }elsif ($cla->{test} eq "init") {
    $cla->{len}=EDC_MAX_LEN;
    $cla->{memblock}="edc0";
    $g_regH=$g_edc0_bist_regH;
    bist_write($cla);
    bist_dump($cla);
    $cla->{memblock}="edc1";
    $g_regH=$g_edc1_bist_regH;
    bist_write($cla);
    bist_dump($cla);
  }

  if ($cla->{ck}) {
    print "\nBIST RESULTS: $err_cnt errors reported\n";
  } else {
    $err_cnt=0;
  }

  #For O.S., okay=0, non-zero == error code.
  $ret = (0==$err_cnt and 0==$int_cause and 0==$ecc_status) ? 0 : 1;  
  print "\n";
  return $ret;
}

##-------------------------------------------------------------------------
#                    main execution
##-------------------------------------------------------------------------

my $cla=cla_parser();
my $ret=main_exec($cla);
exit ($ret);



