Friday, June 21, 2024

I used Perl today

Let me dump the script first, then I'll talk about it:

#!perl
# Combine_KeysightResistance_Data_Into_Excel_20240620.pl
# Jovan Trujillo
# AEP Core - MTW
# Arizona State University
# 8/7/2023
#
#
# Usage: perl Combine_KeysightResistance_Data_Into_Excel_20240620.pl "path-to-data-folder"
# ChangeLog:
# Version 0.1 - Need to standardize on data input file format. Not sure it this script works yet.
# Version 0.2 - Porting what does work from Combine_Capacitance_Data_Into_Excel.pl
#

use Modern::Perl;
use File::Find;
use Switch;
use Excel::Writer::XLSX;

our $Excelbook = Excel::Writer::XLSX->new('Combined_KeysightResistance_Data.xlsx');
        die "Problems creating new Excel file: $!" unless defined $Excelbook;
our $Excelsheet = $Excelbook->add_worksheet('Summary');
our $row = 0;
our $col = 0;

sub main {
    if ($#ARGV != 0) {
        print "Usage: perl Combine_KeysightResistance_Data_Into_Excel_20240620.pl path-to-data-folder\n";
    } else {
       
        # ControlPanel will provide interface for Excel Macros embedded into the data collected into this spreadsheet. Will be useful for generating the plots I want, unless PDL can do it more efficiently for me.
        # Need to write some chart making macros in Excel and export the VBA as a binary. The Excel::Writer::XLSX module has a program to help me do that.
        # From the command line.
       
        #Create header in Summary worksheet.
        $Excelsheet->write($row,$col,"Lot");
        $col = $col + 1;
        $Excelsheet->write($row,$col,"Wafer");
        $col = $col + 1;
        $Excelsheet->write($row,$col,"X");
        $col = $col + 1;
        $Excelsheet->write($row,$col,"Y");
        $col = $col + 1;
        $Excelsheet->write($row,$col,"Die #");
        $col = $col + 1;
        $Excelsheet->write($row,$col,"Avg. Resistance");
        $col = $col + 1;
        $Excelsheet->write($row,$col,"StdDev. Resistance");
        $col = $col + 1;
        $Excelsheet->write($row,$col,"Date");
        $col = $col + 1;
        $Excelsheet->write($row,$col,"Time");
       
        my $folder = shift @ARGV;
        print "Search for resistance data in: " . $folder . "\n";
        my (@dir) = $folder;
        find(\&process_file, @dir);
        $Excelbook->close();
    }
}

sub process_file {
    # print $File::Find::name."\n";
    my $filepath = $File::Find::name;
    my $date;
    my $time;
    my $X;
    my $Y;
    my $DieNum;
    my $Wafer;
    my $Lot;
    my $Excelbook = shift @_;
    
    my $filename = $_;
    #print "filename: $filename\n";
    if ( -f $filepath) {
        # print " This is a file: $filename \n";
        if ( $filename =~ /Residual\sR\s\[(\w\w\w\w\w\-\d\d\d)-([\d]*)\s_\s(\d+)\s(\d+)\s\s\(([\d]*)\)\s;\s([\d\_]*)\s([\w\s\_]*)\]\.csv/ ) {
            #print " This is a file : $filename \n";
            $Lot = $1;
            $Wafer = $2;
            $X = $3;
            $Y = $4;
            $DieNum = $5;
            $date = $6;
            $time = $7;
            #print "\nProcessing: $filename\n\n";
            &analysis($filepath, $filename, $Lot, $Wafer, $X, $Y, $DieNum, $date, $time);
        } else {
            print "Not valid filename regex: " . $filename . "\n";
        }
    } else {
        # Keep looking for valid data files.
        say "Not a valid file path\n";
    }
}

sub analysis() {
    # Calculate average residual resistance for each file and save to Excel spreadsheet.
    my $filepath = shift @_;
    my $filename = shift @_;
    my $Lot = shift @_;
    my $Wafer = shift @_;
    my $X = shift @_;
    my $Y = shift @_;
    my $DieNum = shift @_;
    my $date = shift @_;
    my $time = shift @_;
    my $resTot = 0.0;
    my $count = 0;
    my $resAvg = 0.0;
    my $Label;
    my $Vm;
    my $If;
    my $Ta;
    my $ResidualR;
    my $resDev = 0.0;
    my @resData;
    open DATA, "<$filepath" or die "Error opening file $filename: $!\n";
    # There is a lot of cruft I need to sift through before I get to the meat of the data file
    while (<DATA>) {
        # Look for DataName     Vm     If     Ta     ResidualR Field and Parse for ResidualR data
        # Look for DataValue in line to know when to split for ResidualR data values.
        if ($_ =~ /DataValue/) {
            ($Label, $Vm, $If, $Ta, $ResidualR) = split(/,/,$_,5);
            push(@resData, $ResidualR);
            $resTot = $resTot + $ResidualR;
            $count = $count + 1;
        }
    }
    # Done reading data file, time to caculate average resistance from the dataset.
    $resAvg = $resTot/$count;
    for (my $i=0; $i < $count; $i++) {
        $resDev = $resDev + ($resData[$i]-$resAvg)**2;
    }
    $resDev = sqrt($resDev / $count);
    undef @resData;
    print "$Lot, $Wafer, $X, $Y, $DieNum, $resAvg, $resDev, $date, $time\n";
    
    # Save data to Excel Workbook
    #Columns are:
    # 0 - Lot
    # 1 - Wafer
    # 2 - Die X Index
    # 3 - Die Y Index
    # 4 - Die #
    # 5 - Average Resistance
    # 6 - Standard Deviation Resistance
    # 7 - Date
    # 8 - Time
    $row = $row + 1;
    $Excelsheet->write($row,0,$Lot);
    $Excelsheet->write($row,1,$Wafer);
    $Excelsheet->write($row,2,$X);
    $Excelsheet->write($row,3,$Y);
    $Excelsheet->write($row,4,$DieNum);
    $Excelsheet->write($row,5,$resAvg);
    $Excelsheet->write($row,6,$resDev);
    $Excelsheet->write($row,7,$date);
    $Excelsheet->write($row,8,$time);
    
    close(DATA);
    
}


&main();

1;

 

This was needed to compile the hundreds of CSV text files my Keysight B1505A instrument was spewing out while it collected contact resistance data from 100 die in each of 8 wafers tested. A lot of data, and each file contained 10 resistance values that I needed to average out and calculate the standard deviation of. Overall I was actually able to reuse a previous script that did similar work for Kelvin sheet resistance data that I was collecting using the Keithley 4200. In that case each file contained coordinate data and anywhere from 1 to 40 resistivity points for me to compile and average out into a Spreadsheet to make a pretty map of the sheet resistance variation on a wafer's surface. I'm doing the same thing here, but the file name for the text files is a lot messier. Here is an example of what I'm talking about:

Residual R [E2424-002-02 _ 3 7  (77) ; 6_20_2024 10_46_59 AM].csv 

That was a fun regular expression exercise parsing everything I needed out of that file name. But I did it, and it worked, and it created this nice table of values in an Excel spreadsheet for the team to analyze. 


 Now I can use Minitab to slurp up this Excel spreadsheet and compare differences between wafers and within wafers. Some fine data munging done today with some proper code reuse to boot. 



Friday, March 22, 2024

If you don't use it you lose it.

It's been a while since I needed to write some data processing scripts. And boy am I rusty! Here is the script I wrote to average out 40 samples per data point on a wafer map, and outputting the results in an Excel spreadsheet. 

#!perl
# processResistivityMap.pl
# Take 40-pt Kelvin Resistivity Data and generate single resistivity per location on wafer.
# Works with one data file at a time representing the wafer map.
# Jovan Trujillo
# Advanced Electronics and Photonics Core Facility
# Arizona State University
# 3/20/2024
# Changelog
# v1.0 - Use last point in 40-pt data file for resistivity map output.
# v1.1 - Added k correction factor to analysis() subroutine.
# v1.2 - Parse X,Y,Z and Temperature from file.

use Modern::Perl;
use File::Find;
use Switch;
use Excel::Writer::XLSX;
our $Excelbook;
our $Excelsheet;
our $row;
our $col;

sub main {
    if ($#ARGV != 1) {
        print "Usage: perl processResistivityMap.pl path-to-data-file output-file-name\n";
    } else {
       
        # ControlPanel will provide interface for Excel Macros embedded into the data collected into this spreadsheet. Will be useful for generating the plots I want, unless PDL can do it more efficiently for me.
        # Need to write some chart making macros in Excel and export the VBA as a binary. The Excel::Writer::XLSX module has a program to help me do that.
        # From the command line.
        my $file = shift @ARGV;
        my $outputName = shift @ARGV;
        $Excelbook = Excel::Writer::XLSX->new($outputName);
                die "Problems creating new Excel file: $!" unless defined $Excelbook;
        $Excelsheet = $Excelbook->add_worksheet('Summary');
        $row = 0;
        $col = 0;
       
        $Excelsheet->write($row,0,"X (um)");
    
        $Excelsheet->write($row,1,"Y (um)");
    
        $Excelsheet->write($row,2,"Z (um)");
    
        $Excelsheet->write($row,3,"T (deg C.");
    
        $Excelsheet->write($row,4,"R (Ohms/sq)");
        $row = $row + 1;
       
        print "\n\nSearching for resistivity data in: " . $file . "\n\n";
        &process_file($file);
        $Excelbook->close();
    }
}

sub process_file {
    #print "filepath: " . $File::Find::name . "\n";
    my $filepath = shift @_;
    my $sampleNum;
    my $wafer;
    my $lotName;
    #my $Excelbook = shift @_;
    
    #my $filename;
    #print "filename: $filename\n";
    if ( -f $filepath) {
        # print " This is a file : $filename \n";
        &analysis($filepath);
    } else {
        # Keep looking for valid data files.
        say "Not a valid file path\n";
    }
}

sub analysis() {
    my $filepath = shift @_;
    #my $filename = shift @_;
    #my $sampleNum = shift @_;
    #my $wafer = shift @_;
    #my $lotName = shift @_;
    #my $Excelbook = shift @_;
    my $DI;
    my $DV;
    my $AV;
    my $CV;
    
    my $Vdiff;
    my $Temperature;
    my $Resistivity=0.0;
    my $count=0;
    my $sum=0;
    my $k = 1;
    my $x;
    my $y;
    my $z;
    
    open DATA, "<$filepath" or die "Error opening file $filepath: $!\n";
    #$header = <DATA>;
    
    while (my $line = <DATA>) {
        # We skip the first 10 points of resistivity data and average out the rest. Or try just the last point. Want to see which
        # analysis will give us the best uniformity.
        if ( $line =~  /\(([\d.]+),([\d.]+),([\d.]+)\)\s*([\d.]+)C\s*/ ) {
            say "\nFound coordinates\n";
            if ($count > 0) {
                print "Average Resistance = " . $sum/$count . "\n";
                $Excelsheet->write($row,4,$sum/$count);
                $sum = 0;
                $count = 0;
                $row = $row + 1;
            }
            $x = $1;
            $y = $2;
            $z = $3;
            $Temperature = $4;
            say "x = $x";
            say "y = $y";
            say "z = $z";
            say "Temperature = $Temperature";
            $Excelsheet->write($row,0,$x);
            $Excelsheet->write($row,1,$y);
            $Excelsheet->write($row,2,$z);
            $Excelsheet->write($row,3,$Temperature);
           
           
        } else {
            if ($line =~ /C|Resistivity|Inf|^\s*$/) {
                #say "Found header line. Skipping it.";
            } else {
                #say "Reading data from: $line";
                ($DI,$DV,$CV,$AV,$Resistivity) = split(/\t\s*/,$line,5);
                # say "DI = $DI";
                # say "DV = $DV";
                # say "CV = $CV";
                # say "AV = $AV";
                #say "Resistivity = $Resistivity";
                $sum = $sum + $Resistivity;
                $count = $count + 1;
               
            }
        }
    }
    
    if ($count > 0) {
        print "Average Resistance = " . $sum/$count . "\n";
        $Excelsheet->write($row,4,$sum/$count);
        $sum = 0;
        $count = 0;
        $row = $row + 1;
    }

    # Save data to Excel Workbook
    #Columns are:
    # 0 - x
    # 1 - y
    # 2 - z
    # 3 - temperature
    # 4 - resistivity
    
    say "All done!";
    close(DATA);
}


    
    

&main();
1;

I had trouble remembering my regex syntax, and the data input file had some sneaky spaces in places that took me a while to see. But in the end I got it to work, after an afternoon and a morning working on it.