open-insight/LSL2/STPROC/METROLOGY_SERVICES.txt
Mike Phares bf82640ed2 Common ResourceID and IsViewerFile
Enable process-data-standard-format for HgCV and Tencor
2025-06-03 13:26:49 -07:00

3279 lines
148 KiB
Plaintext

Function Metrology_Services(@Service, @Params)
/***********************************************************************************************************************
This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
permission from SRP Computer Solutions, Inc.
Name : Metrology_Services
Description : Handler program for all module related services.
Notes : The generic parameters should contain all the necessary information to process the services. Often
this will be information like the data Record and Key ID.
Parameters :
Service [in] -- Name of the service being requested
Param1-10 [in/out] -- Additional request parameter holders
Response [out] -- Response to be sent back to the Controller (MCP) or requesting procedure
Metadata :
UID000 - 'Success'
UID001 - Failure. See the description in the log for further details.
History : (Date, Initials, Notes)
09/04/16 dmb Original programmer. - [EPIOI-8]
01/03/18 dmb Fix logic for Tencor and SP1 imports to correctly identify the value index for imported
data.
02/23/18 dmb Add support for the special 14 point thickness reading.
03/16/18 dmb Change logic to perform recipe matching for SP1.
04/26/18 djs Added logic within ImportTencorData to ensure Tencor recipe matches SurfScan list.
06/06/18 djs Added logic within LoadRunDataToDatabase to store the 14 point QA THICK_ONLY data points.
Previously only the average of these points was being stored. Also added logic within
ImportHgCVData to store the HgCV QA data points.
06/08/18 djs Added logic within LoadRunDataToDatabase to store the Min and Max of the data points.
06/12/18 djs Added logic within ImportedHgCVData to store the HgCV phase angle data points
06/19/18 djs Added logic within ImportHgCVData to store additional QA Metrology Data.
Previously only the average of the imported data points was being stored.
Now, in addition to the average, the min, max, edge mean delta, and range percent
of the QA data points are being calculated and stored in the WO_MAT_QA table.
Finally, the data points themselves are also now being stored in the WO_MAT_QA table.
07/23/18 djs Added logic with the ImportBioRadData service to differentiate 6 inch THICK_ONLY QA
metrology tests from traditional RDS tests. Also added code to store this data within the
WO_MAT_QA database table.
09/18/18 djs Added code within the ImportTencorData service to post wafer image pdfs to a staging table
where they will be stored locally on the App server.
09/11/19 fdr Add new service "ImportStratusData"
12/05/24 djs Updated GetIQSViolations service to store reactor violations in the respective reactor record.
***********************************************************************************************************************/
#pragma precomp SRP_PreCompiler
$insert LOGICAL
$insert SERVICE_SETUP
$insert RDS_EQUATES
$insert PROD_SPEC_EQUATES
$insert RDS_LAYER_EQUATES
$insert PRS_LAYER_EQUATES
$insert RDS_TEST_EQUATES
$insert TOOL_CLASS_EQUATES
$insert TEST_POINT_MAP_EQUATES
$insert CLEAN_INSP_EQUATES
$insert REACT_RUN_EQUATES
$insert REACTOR_EQUATES
$insert WO_MAT_QA_EQUATES
$insert PRS_STAGE_EQUATES
$insert SRPMail_Inserts
$insert WO_MAT_EQUATES
$Insert NOTIFICATION_EQUATES
$Insert RLIST_EQUATES
$Insert WM_OUT_EQUATES
$Insert IQS_VIOL_DATA_EQUATES
Common /MetrologyServices/ MachineType@, RDSNo@
Equ RETRY_ATTEMPTS$ TO 3
Equ MINUTES_UNTIL_RETRY$ TO 3
Equ ORP$THICK_READS TO 1
Equ ORP$SHEET_RHO_READS TO 2
Equ ORP$HGCV1_READS TO 3
Equ EPI_READS$READ_NO TO 1
Equ EPI_READS$THICKNESS TO 2
Equ EPI_READS$SHEET_RHO TO 3
Equ EPI_READS$HGCV1 TO 4
Equ EPI_READS$HGCV2 TO 5
Equ Tab$ to \09\
Equ CRLF$ to \0D0A\
Equ LF$ to \0A\
Equ Comma$ to ','
Declare subroutine SRP_Stopwatch, Error_Services, obj_Tables, Metrology_Services, obj_RDS_Test, SRP_JSON
Declare subroutine RTI_Set_Debugger, Database_Services, Btree.Extract, Set_Status, QA_Services, obj_Notes
Declare subroutine Logging_Services, SRP_Send_Mail, SRP_Run_Command, PM_Services, Httpclient_Services
Declare subroutine Tool_Services, Mona_Services, Reactor_Services
Declare function SRP_Sort_Array, Metrology_Services, obj_RDS_Test, obj_Test_Point_Map, Database_Services, UCase
Declare function Work_Order_Services, SRP_JSON, Logging_Services, Environment_Services, SRP_Trim, Min, Max
Declare function QA_Services, SRP_Join_Arrays, Get_Status, Obj_Clean_Insp, Datetime, SRP_Datetime
Declare function Httpclient_Services, PM_Services, Signature_Services, SRP_Array, Math_Services
Declare function Tool_Class_Services, obj_wo_mat
Declare function SRP_String
LogPath = Environment_Services('GetApplicationRootPath') : '\LogFiles\Metrology'
LogDate = Oconv(Date(), 'D4/')
LogTime = Oconv(Time(), 'MTS')
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Metrology Log.csv'
Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Tencor Metrology Log.csv'
Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objTencorLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' HgCV Metrology Log.csv'
Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objHgCVLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' CDE Metrology Log.csv'
Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objCDELog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Biorad Metrology Log.csv'
Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objBioradLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Stratus Metrology Log.csv'
Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objStratusLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' SP1 Metrology Log.csv'
Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objSP1Log = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' SPV Metrology Log.csv'
Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objSPVLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' SRP Metrology Log.csv'
Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objSRPLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Metrology Performance Log.csv'
Headers = 'Logging DTM' : @FM : 'Machine Type' : @FM : 'RDS Key ID': @FM : 'Notes'
objPerfLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LogPath = Environment_Services('GetApplicationRootPath') : '\LogFiles\SOD'
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' SOD Performance Log.csv'
Headers = 'Logging DTM' : @FM : 'Machine Type' : @FM : 'RDS Key ID': @FM : 'Notes'
objSODPerfLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LogPath = Environment_Services('GetApplicationRootPath') : '\LogFiles\POST'
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' POST Performance Log.csv'
Headers = 'Logging DTM' : @FM : 'Machine Type' : @FM : 'RDS Key ID': @FM : 'Notes'
objPOSTPerfLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LoggingDTM = LogDate : ' ' : LogTime ; // Logging DTM
GoToService else
Error_Services('Set', Service : ' is not a valid service request within the ' : ServiceModule : ' services module.')
end
Return Response else ''
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Services
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------------------------------------
// LaunchMetrologyViewer
//
// Launches the metrology viewer URL with the client's default browser.
//----------------------------------------------------------------------------------------------------------------------
Service LaunchMetrologyViewer
MetrologyURL = Environment_Services('GetMetrologyViewerURL')
Command = 'start ':MetrologyURL
SRP_Run_Command(Command)
end service
Service GetJsonFromProcessDataStandardFormat(Text)
json = '';
HeaderId = -1;
SubgroupId = -1;
FoundEndOfFile = False$;
SWAP '|' WITH @VM IN Text;
SWAP CRLF$ WITH @FM IN Text;
LineCount = DCOUNT(Text, @FM);
FOR LineLoopIndex = 1 TO LineCount
Line = Text<LineLoopIndex>;
IF FoundEndOfFile EQ True$ THEN
json = json:Line;
END
IF Line EQ 'EOF' THEN
FoundEndOfFile = True$;
Line = Text<LineLoopIndex - 1>;
Convert Tab$ to @FM IN Line
SWAP '=' WITH @VM IN Line;
SWAP ';' WITH @FM IN Line;
IF Line<2, 1> EQ 'B_HeaderId' THEN
HeaderId = Line<2, 2>
END
IF Line<3, 1> EQ 'B_SubgroupId' THEN
SubgroupId = Line<3, 2>
END
END
NEXT LineLoopIndex
Response = json;
end service
Service GetStratus(Handle)
Result = ''; // Service ImportStratusData(
Offset = 1
FieldPosition = 13
FieldPositionIncrement = 2
Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count');
Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence');
Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId');
Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId');
FOR RecordIndex = 1 TO Result<1, 1>
IF RecordIndex EQ 1 THEN
Result<2> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DateTime'); // Timestamp
Result<3> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MesEntity'); // Tool
Result<4> = 'FQA Thickness'; // DataType
Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Employee'); // Operator
Result<6> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Recipe'); // Recipe
Result<7> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // Reactor
Result<8> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSNo
Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].PSN'); // PSN
Result<10> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Batch'); // BatchID
Result<11> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Cassette'); // Cassette
Result<12> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].GradeMean'); // ThickAvg
END
ForOffset = (RecordIndex - 1) * FieldPositionIncrement;
Result<FieldPosition + ForOffset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Slot'); // Position
Result<FieldPosition + ForOffset + Offset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Mean'); // DataPoint
NEXT RecordIndex
Response = Result;
end service
Service GetBioRad(Handle)
Result = ''; // Service ImportBioRadData(
Offset = 1
FieldPosition = 13
FieldPositionIncrement = 2
Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count');
Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence');
Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId');
Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId');
FOR RecordIndex = 1 TO Result<1, 1>
IF RecordIndex EQ 1 THEN
Result<2> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DateTime'); // TimeStamp
Result<4> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Recipe'); // ScanRecipe
Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // ReactorID
Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // ToolID
Result<6> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID
Result<7> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].PSN'); // PSN
Result<8> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Layer'); // RunDataLayer
Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Zone'); // RunDataZone
Result<11> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Wafer'); // DataSlotId
END
ForOffset = (RecordIndex - 1) * FieldPositionIncrement;
Result<FieldPosition + ForOffset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Position'); // Position
Result<FieldPosition + ForOffset + Offset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Thickness'); // DataPoint
NEXT RecordIndex
Response = Result;
end service
Service GetCDE(Handle)
Result = ''; // Service ImportCDEData(
Offset = 3
FieldPosition = 23
FieldPositionIncrement = 5
Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count');
Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence');
Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId');
Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId');
FOR RecordIndex = 1 TO Result<1, 1>
IF RecordIndex EQ 1 THEN
Result<3> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Date'); // Timestamp
Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RecipeName'); // ScanRecip
Result<6> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // ReactorID
Result<7> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID
Result<8> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].PSN'); // PSN
Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Layer'); // RunDataLayer
Result<10> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Zone'); // RunDataZo
END
ForOffset = (RecordIndex - 1) * FieldPositionIncrement;
Result<FieldPosition + ForOffset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].InferredPoint'); // Position
Result<FieldPosition + ForOffset + Offset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Data'); // DataPoint
Result<FieldPosition + ForOffset + FieldPositionIncrement - 1> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MeritGOF'); // N/A
NEXT RecordIndex
Response = Result;
end service
Service GetHgCV(Handle)
Result = ''; // Service ImportHgCVData(
Offset = 5
PhaseOffset = 7
FieldPosition = 53
FieldPositionIncrement = 9
Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count');
Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence');
Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId');
Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId');
FOR RecordIndex = 1 TO Result<1, 1>
IF RecordIndex EQ 1 THEN
Result<2> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MesEntity'); // ToolID
Result<3> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // ReactorID
Result<4> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID
Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].PSN'); // PSN
Result<8> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Wafer'); // LayerZonePair
Result<11> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Date'); // Timestamp
END
ForOffset = (RecordIndex - 1) * FieldPositionIncrement;
Result<FieldPosition + ForOffset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Site'); // Position
Result<FieldPosition + ForOffset + Offset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RhoAvg'); // HgCVDataPoint
Result<FieldPosition + ForOffset + PhaseOffset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Phase'); // PhaseDataPoint
Result<FieldPosition + ForOffset + FieldPositionIncrement - 1> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Grade'); // N/A
NEXT RecordIndex
Response = Result;
end service
Service GetTencor(Handle)
Result = ''; // Service ImportTencorData(
Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count');
Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence');
Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId');
Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId');
FOR RecordIndex = 1 TO Result<1, 1>
IF RecordIndex EQ 1 THEN
Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Date'); // Timestamp
Result<10> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].HazeAverageAvg'); // HazeAvg
Result<28> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID
Result<30> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].WaferRecipe'); // ScanRecipe
Result<39> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].SumOfDefectsAvg'); // SoDAvg
Result<40> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].SumOfDefectsMax'); // SoDMax
Result<41> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].SumOfDefectsMin'); // SoDMin
Result<43> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MesEntity'); // ScanTool
END
NEXT RecordIndex
Response = Result;
end service
Service GetSP1(Handle)
Result = ''; // Service ImportSP1Data(
Result<5, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count');
Result<5, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence');
Result<5, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId');
Result<5, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId');
FOR RecordIndex = 1 TO Result<5, 1>
IF RecordIndex EQ 1 THEN
Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Date'); // Timestamp
Result<10> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnHazeAvgMean'); // HazeAvg
Result<28> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID
Result<30> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Session'); // ScanRecipe
IsMisfit = IndexC(Result<30>, 'MISFIT', 1)
IF IsMisfit THEN
Result<3> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMean'); // SoDAvg
Result<2> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMax'); // SoDMax
Result<1> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMin'); // SoDMin
END ELSE
Result<39> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMean'); // SoDAvg
Result<40> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMax'); // SoDMax
Result<41> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMin'); // SoDMin
END
Result<43> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MesEntity'); // ScanTool
Result<44> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAreaMean'); // DCNMM2
END
NEXT RecordIndex
Response = Result;
end service
//----------------------------------------------------------------------------------------------------------------------
// ImportMetrologyFiles
//
// Looks for available Metrology files that are ready to be imported into the MES system.
//----------------------------------------------------------------------------------------------------------------------
Service ImportMetrologyFiles(Machine)
If Machine NE '' then
hSysLists = Database_Services('GetTableHandle', 'SYSLISTS')
Lock hSysLists, ServiceKeyID:'*':Machine then
********************************
* Verify Metrology data folder *
********************************
Begin Case
Case Machine _EQC 'Tencor'
SearchPattern = '*.pdsf';
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08DDUPSFS6420\Source\MET08DDUPSFS6420\'
Case Machine _EQC 'HgCV'
SearchPattern = '*.pdsf';
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESIHGCV\Source\MET08RESIHGCV\'
Case Machine _EQC 'CDE'
SearchPattern = '*.pdsf';
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESIMAPCDE\Source\MET08RESIMAPCDE\'
Case Machine _EQC 'Biorad'
SearchPattern = '*.txt';
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08THFTIRQS408M\Source\MET08THFTIRQS408M\'
Case Machine _EQC 'Stratus'
SearchPattern = '*.txt';
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08THFTIRSTRATUS\Source\MET08THFTIRSTRATUS\'
Case Machine _EQC 'SP1'
SearchPattern = '*.txt';
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08DDUPSP1TBI\Source\MET08DDUPSP1TBI\'
Case Machine _EQC 'SPV'
SearchPattern = '*.txt';
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESISRP2100\Source\MET08RESISRP2100\'
Case Machine _EQC 'SRP'
SearchPattern = '*.txt';
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08ANLYSDIFAAST230\Source\MET08ANLYSDIFAAST230\'
Case Otherwise$
SearchPattern = '*.txt';
Error_Services('Add', 'Error in ':Service:' service. Unsupported Machine "':Machine:'" passed into service')
End Case
If Error_Services('NoError') then
InitDir DataPath:SearchPattern
FileList = DirList()
FileNames = ''
LOOP
FileName = FileList[1,@FM]
FileList[1,Col2()] = ""
LOCATE FileName IN FileNames BY 'AR' USING @FM SETTING Pos ELSE
FileNames = INSERT(FileNames,Pos,0,0,FileName)
END
UNTIL FileList = ""
REPEAT
IF FileNames[-1,1] = @FM THEN
FileNames[-1,1] = ''
END
FileCnt = COUNT(FileNames,@FM) + (FileNames NE '')
FOR FileLoopIndex = 1 TO FileCnt
***********************
* Read metrology file *
***********************
ProcessNow = True$
FileName = FileNames<FileLoopIndex>
ImportFileList = Database_Services('ReadDataRow', 'APP_INFO', UCase(Machine):'_FILE_LIST')
ImportAttemptCounts = Database_Services('ReadDataRow', 'APP_INFO', UCase(Machine):'_ATTEMPT_COUNTS')
ImportAttemptDTMs = Database_Services('ReadDataRow', 'APP_INFO', UCase(Machine):'_ATTEMPT_DTMS')
CurrDTM = Datetime()
Locate FileName in ImportFileList using @FM setting FilePos then
LastAttemptDTM = ImportAttemptDTMs<FilePos>
MinutesSince = SRP_Datetime('MinuteSpan', LastAttemptDTM, CurrDTM)
If MinutesSince GE MINUTES_UNTIL_RETRY$ then
ImportAttempts = ImportAttemptCounts<FilePos>
ImportAttempts += 1
ImportAttemptCounts<FilePos> = ImportAttempts
ImportAttemptDTMs<FilePos> = CurrDTM
end else
ProcessNow = False$
end
end else
ImportFileList<FilePos> = FileName
ImportAttempts = 1
ImportAttemptCounts<FilePos> = ImportAttempts
ImportAttemptDTMs<FilePos> = CurrDTM
end
If ProcessNow then
FileInfo = Dir(DataPath:FileName)
FileSize = FileInfo<1>
Metrology_Services('LogResults', '', Machine, 'UID000', 'Dir : ' : FileName : ', Size : ' : FileSize)
ImportStartTime = Time()
Metrology_Services('LogResults', '', Machine, 'UID000', 'Read : ' : FileName : ', Size : ' : FileSize)
// Copy Run Data files to repository for troubleshooting purposes
Begin Case
Case Machine _EQC 'Tencor'
RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08DDUPSFS6420\Source\MET08DDUPSFS6420\'
Case Machine _EQC 'HgCV'
RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08RESIHGCV\Source\MET08RESIHGCV\'
Case Machine _EQC 'CDE'
RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08RESIMAPCDE\Source\MET08RESIMAPCDE\'
Case Machine _EQC 'Biorad'
RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08THFTIRQS408M\Source\MET08THFTIRQS408M\'
Case Machine _EQC 'Stratus'
RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08THFTIRSTRATUS\Source\MET08THFTIRSTRATUS\'
Case Machine _EQC 'SP1'
RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08DDUPSP1TBI\Source\MET08DDUPSP1TBI\'
Case Machine _EQC 'SPV'
RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08RESISRP2100\Source\MET08RESISRP2100\'
Case Machine _EQC 'SRP'
RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08ANLYSDIFAAST230\Source\MET08ANLYSDIFAAST230\'
End Case
IF SearchPattern = '*.pdsf' THEN
OSREAD Text FROM DataPath:FileName THEN
json = Metrology_Services('GetJsonFromProcessDataStandardFormat', Text);
END ELSE
json = '';
END
IF LEN(json) GT 0 THEN
RunData = Metrology_Services('GetRunData', Machine, json);
END ELSE
Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to get PDSF json');
END
END ELSE
OSREAD RunData FROM DataPath:FileName THEN
SWAP '|' WITH @VM IN RunData
SWAP CRLF$ WITH @FM IN RunData
LOOP
LastChar = RunData[-1,1]
UNTIL LastChar NE @FM
RunData[-1,1] = ''
REPEAT
Convert Tab$ to @FM in RunData
END else
Metrology_Services('LogResults', '', Machine, 'UID001', 'Read : ' : FileName : ', Size : ' : FileSize)
end
END
*************************
* Import metrology data *
*************************
IF RunData NE '' then
Metrology_Services('ImportMetrologyRunData', Machine, DataPath, FileName, RunData)
END ELSE
Error_Services('Add', 'RunData argument was missing')
Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage'))
END
If Error_Services('NoError') then
Continue = True$
end else
ErrorMessage = Error_Services('GetMessage')
FQAError = IndexC(ErrorMessage, 'FQA has already been signed', 1)
If ( Index(ErrorMessage, 'UID002', 1) and (ImportAttempts LE RETRY_ATTEMPTS$) and Not(FQAError) ) then
Continue = False$
end else
Continue = True$
end
end
******************************
* Delete metrology data file *
******************************
If (Continue) then
Status() = 0
OSDELETE DataPath:FileName ;* Deletes local copy of data file
ImportFileList = Delete(ImportFileList, FilePos, 0, 0)
ImportAttemptCounts = Delete(ImportAttemptCounts, FilePos, 0, 0)
ImportAttemptDTMs = Delete(ImportAttemptDTMs, FilePos, 0, 0)
ErrCode = Status()
If ErrCode EQ 0 then
Metrology_Services('LogResults', '', Machine, 'UID000', 'Delete : ' : FileName : ', Size : ' : FileSize)
end else
Metrology_Services('LogResults', '', Machine, 'UID001', 'Delete : ' : FileName : ', Size : ' : FileSize : ', Error : ' : ErrCode)
end
END else
ErrorMessage = Error_Services('GetMessage')
MetrologyLog = Database_Services('ReadDataRow', 'SYSLISTS', UCase(Machine):'_METROLOGY_LOG')
MetrologyLog := ErrorMessage : @FM
Database_Services('WriteDataRow', 'SYSLISTS', UCase(Machine):'_METROLOGY_LOG', MetrologyLog, True$)
end
Database_Services('WriteDataRow', 'APP_INFO', UCase(Machine):'_FILE_LIST', ImportFileList)
Database_Services('WriteDataRow', 'APP_INFO', UCase(Machine):'_ATTEMPT_COUNTS', ImportAttemptCounts)
Database_Services('WriteDataRow', 'APP_INFO', UCase(Machine):'_ATTEMPT_DTMS', ImportAttemptDTMs)
end
NEXT FileLoopIndex
end else
Metrology_Services('LogResults', '', '', 'UID001', Error_Services('GetMessage'))
end
Unlock hSysLists, ServiceKeyID:'*':Machine else Null
end
end else
Metrology_Services('LogResults', '', '', 'UID001', 'Null Machine passed into service')
end
end service
Service GetRunData(Machine, json)
Result = '';
Handle = '';
If Assigned(Handle) else Handle = 0
If Handle LE 0 then
ParseError = SRP_JSON(Handle, 'PARSE', json);
IF ParseError EQ '' THEN
Begin Case
Case Machine _EQC 'Stratus'
Result = Metrology_Services('GetStratus', Handle);
Case Machine _EQC 'Biorad'
Result = Metrology_Services('GetBiorad', Handle);
Case Machine _EQC 'CDE'
Result = Metrology_Services('GetCDE', Handle);
Case Machine _EQC 'HgCV'
Result = Metrology_Services('GetHgCV', Handle);
Case Machine _EQC 'SP1'
Result = Metrology_Services('GetSP1', Handle);
Case Machine _EQC 'Tencor'
Result = Metrology_Services('GetTencor', Handle);
End Case
SRP_JSON(Handle, 'RELEASE');
END ELSE
Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to parse PDSF json');
END
END
Response = Result;
end service
//----------------------------------------------------------------------------------------------------------------------
// ImportMetrologyRunData
//
// RunData. - [Required]
//
// Imports the metrology run data into the RDS_TEST database table. Returns True$ if successful and False$ if
// unsuccessful.
//----------------------------------------------------------------------------------------------------------------------
Service ImportMetrologyRunData(Machine, DataPath, FileName, RunData)
// Scan the run data for machine specific information. Then call the relevant update service for the specific
// machine.
ResourceID = Field(FileName, ' ', 1, 1)
IsViewerFile = Index(FileName, 'Viewer', 1)
Begin Case
Case Machine _EQC 'Stratus'
PSN = RunData<9>
Metrology_Services('ImportStratusData', RunData, ResourceID, PSN)
MachineType@ = 'Stratus'
Case Machine _EQC 'Biorad'
PSN = RunData<7>
QualFile = ( (PSN EQ 'T-Low') or (PSN EQ 'T-Mid') or (PSN EQ 'T-High') or (PSN EQ 'T_LOW') or (PSN EQ 'T_MID') or (PSN EQ 'T_HIGH') )
IF QualFile THEN
Metrology_Services('ImportBioRadQualData', RunData, ResourceID, PSN)
END ELSE
Metrology_Services('ImportBioRadData', RunData, ResourceID, IsViewerFile, PSN, FileName)
END
MachineType@ = 'Bio-Rad'
Case Machine _EQC 'CDE'
PSN = RunData<8>
QualFile = ( (PSN EQ 'RLOW_STD') or (PSN EQ 'RMID_STD') or (PSN EQ 'RHI_STD') or (PSN EQ 'THINSPC') )
IF QualFile THEN
Metrology_Services('ImportCDEQualData', RunData, ResourceID, PSN)
END ELSE
Metrology_Services('ImportCDEData', RunData, ResourceID, IsViewerFile, PSN)
END
MachineType@ = 'CDE'
Case Machine _EQC 'HgCV'
PSN = RunData<5>
QualFile = ( (PSN EQ 'Low') or (PSN EQ 'Mid') or (PSN EQ 'High') or (PSN EQ 'Thin') )
IF QualFile THEN
Metrology_Services('ImportHgCVQualData', RunData, ResourceID, PSN)
END ELSE
Metrology_Services('ImportHgCVData', RunData, ResourceID, IsViewerFile, PSN)
END
MachineType@ = 'HgProbe'
Case Machine _EQC 'SP1'
Metrology_Services('ImportSP1Data', RunData)
MachineType@ = 'SP1'
Case Machine _EQC 'Tencor'
Metrology_Services('ImportTencorData', RunData)
MachineType@ = 'TENCOR'
Case Machine _EQC 'SRP'
Error_Services('Add', 'SRP data import is currently disabled.')
Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage'))
MachineType@ = 'SRP'
Case Machine _EQC 'SPV'
Error_Services('Add', 'SPV data import is currently disabled.')
Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage'))
MachineType@ = 'SPV'
Case Otherwise$
Error_Services('Add', 'Unrecognized machine used to create metrology run data')
Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage'))
MachineType@ = 'Unrecognized'
End Case
end service
Service ImportStratusData(RunData, ResourceID, PSN)
Machine = 'Stratus'
ParseArray = ''
FieldPos = 13
FieldPosIncrement = 2
Offset = 1
Decimals = 2
Timestamp = RunData<2>
Tool = RunData<3>
DataType = RunData<4>
Operator = RunData<5>
Recipe = RunData<6>
Reactor = RunData<7>
RDSNo = RunData<8> ; // If Non-EpiPro this will be an RDS Key, otherwise it will be a work order key.
BatchID = RunData<10>
Cassette = RunData<11>
ThickAvg = RunData<12>
Positions = ''
DataPoints = ''
Loop
Position = Trim(RunData<FieldPos>)
DataPoint = Trim(RunData<FieldPos + Offset>)
Until Position EQ ''
Positions := Position : @VM
DataPoints := DataPoint : @VM
FieldPos += FieldPosIncrement
Repeat
Positions[-1, 1] = '' ; // Strip final @VM
DataPoints[-1, 1] = '' ; // Strip final @VM
// Clean the cassette user input.
Swap '.' with '*' in Cassette
Swap 'o' with '' in Cassette
Swap 'O' with '' in Cassette
// Determine if the key ID is an RDS or WM_OUT key
IsEpiPro = false$
Begin Case
// Check for WM_OUT first because a work order key may also be an RDS key.
Case RowExists('WM_OUT', Cassette)
WorkOrderNo = Field(Cassette, '*', 1)
CassNo = Field(Cassette, '*', 3)
RDSNo@ = Cassette ; // This is used for logging purposes.
IsEpiPro = true$
Case RowExists('RDS', RDSNo)
WorkOrderNo = Xlate('RDS', RDSNo, 'WO', 'X')
CassNo = Xlate('RDS', RDSNo, 'CASS_NO', 'X')
RDSNo@ = RDSNo ; // This is used for logging purposes.
Case Otherwise$
Error_Services('Add', 'Unrecognized cassette ID ':Cassette:'.')
End Case
If Error_Services('NoError') then
// Update WO_MAT record
StdDev = ''
WOMatKey = WorkOrderNo : '*' : CassNo
WOMatRec = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
If Error_Services('NoError') then
NumVals = 0
For each Position in Positions using @VM setting vPos
If Position NE '' then
WOMatRec<WO_MAT_MU_WAFER_THK_RESULT$, Position> = DataPoints<0, vPos>
NumVals += 1
end
Next Position
If NumVals EQ 25 then
StdDevType = 'POPULATION'
end else
StdDevType = 'SAMPLE'
end
If NumVals GT 0 then
Vals = WOMatRec<WO_MAT_MU_WAFER_THK_RESULT$>
Vals = SRP_Array('Clean', Vals, 'Trim', @VM)
StdDev = Math_Services('GetStdDev', Vals, StdDevType)
StdDev = OConv(IConv(StdDev, 'MD3'), 'MD3')
end
Database_Services('WriteDataRow', 'WO_MAT', WOMatKey, WOMatRec, True$, False$, True$)
If Error_Services('HasError') then
Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
// Update the WM_OUT record for EpiPro
If IsEpiPro then
NumVals = 0
WMORec = Database_Services('ReadDataRow', 'WM_OUT', RDSNo@)
For each Wafer in Positions using @VM setting dPos
If Wafer NE '' then
WMORec<WM_OUT_MU_WAFER_THK_RESULT$, Wafer> = DataPoints<0, dPos>
NumVals += 1
end
Next Wafer
If NumVals EQ 25 then
StdDevType = 'POPULATION'
end else
StdDevType = 'SAMPLE'
end
If NumVals GT 0 then
Vals = WMORec<WM_OUT_MU_WAFER_THK_RESULT$>
Vals = SRP_Array('Clean', Vals, 'Trim', @VM)
StdDev = Math_Services('GetStdDev', Vals, StdDevType)
StdDev = OConv(IConv(StdDev, 'MD3'), 'MD3')
end
Database_Services('WriteDataRow', 'WM_OUT', RDSNo@, WMORec, True$, False$, True$)
end
// Update WO_MAT_QA record
StdDevMax = ''
WOMatQAID = WorkOrderNo : '*' : CassNo
WOMatQARec = Database_Services('ReadDataRow', 'WO_MAT_QA', WOMatQAID)
If Error_Services('NoError') then
SpecRecipes = WOMatQARec<WO_MAT_QA_RECIPE$>
ProfSteps = ''
Profiles = WOMatQARec<WO_MAT_QA_PROFILE$>
ProfileCnt = DCount(WOMatQARec<WO_MAT_QA_PROFILE$>, @VM)
Stages = WOMatQARec<WO_MAT_QA_STAGE$>
Slots = WOMatQARec<WO_MAT_QA_SLOT$>
SpecQty = WOMatQARec<WO_MAT_QA_WFR_QTY$>
pPos = ''
WOMatSlotProfile = obj_WO_Mat('SlotWaferIDs',WOMatKey:@RM:WOMatRec)
For each Profile in Profiles using @VM setting pPos
Stage = Stages<0, pPos>
If ( (Profile EQ '1ADE') and ( (Stage EQ 'QA') or (Stage EQ 'MO_QA') ) ) then
Slot = Slots<0, pPos>
If ( (Slot EQ 'A') or (SpecQty EQ 'A') or (SpecQty GT 5) ) then
MetMin = WOMatQARec<WO_MAT_QA_MIN$, pPos>
MetMax = WOMatQARec<WO_MAT_QA_MAX$, pPos>
StdDevMax = ((MetMin + MetMax) / 2) * (0.02)
StdDevMax = IConv(StdDevMax,'MD3')
WOMatQARec<WO_MAT_QA_STD_RESULT$, pPos> = StdDev
WOMatQARec<WO_MAT_QA_STD_MAX$, pPos> = StdDevMax
end
Begin Case
Case Slot EQ '1'
For WaferIndex = 1 to 25
SlotIsEmpty = WOMatSlotProfile<1, WaferIndex> EQ ''
Until SlotIsEmpty EQ False$
Next WaferIndex
Case Slot EQ 'L'
For WaferIndex = 25 to 1 Step -1
SlotIsEmpty = WOMatSlotProfile<1, WaferIndex> EQ ''
Until SlotIsEmpty EQ False$
Next WaferIndex
Case Slot EQ 'A'
WaferIndex = 1
Case Otherwise$
WaferIndex = Slot
End Case
Locate WaferIndex in Positions using @VM setting dPos then
WOMatQARec<WO_MAT_QA_RESULT$, pPos> = DataPoints<0, dPos>
end
end
Next Profile
Database_Services('WriteDataRow', 'WO_MAT_QA', WOMatQAID, WOMatQARec, True$, False$, True$)
If Error_Services('NoError') then
Metrology_Services('LogResults', RDSNo@, Machine, 'UID000', Service : ' : Success.')
end else
Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end service
Service ImportBioRadQualData(RunData, ResourceID, PSN)
Machine = 'BioRad'
URL = "https://oi-metrology-viewer-prod.mes.infineon.com/api/InfinityQSV3/":ResourceID:"/header"
TimeoutDuration = HTTPClient_Services('GetTimeoutDuration')
If TimeoutDuration NE 30 then Httpclient_Services('SetTimeoutDuration', 30)
Response = Httpclient_Services('SendHTTPRequest', 'GET', URL, '', '', '', '', '', '', '')
If Response NE '' then
objJSON = ''
If SRP_JSON(objJSON, 'Parse', Response) EQ '' then
SumOOS = SRP_JSON(objJSON, 'GetValue', 'Results[1].iq_sum')
ToolID = RunData<5>
TimeStamp = RunData<2>
If SumOOS NE '' then
Swap 'T_LOW' with 'T-Low' in PSN
Swap 'T_MID' with 'T-Mid' in PSN
Swap 'T_HIGH' with 'T-High' in PSN
Pass = (SumOOS EQ 0)
QualResponse = PM_Services('ProcessQual', PSN, ToolID, TimeStamp, Pass)
StatusCode = QualResponse<1>
Message = QualResponse<2>
Metrology_Services('LogResults', PSN, Machine, StatusCode, Service : ' : ' : Message)
end else
LogMessage = 'IQS response missing Results[1].iq_sum. Error message: ':SRP_JSON(objJSON, 'GetValue', 'message')
Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : LogMessage)
end
SRP_JSON(objJSON, 'Release')
end else
Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to parse IQS JSON response')
end
end else
Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Null response from IQS API.')
end
end service
Service ImportBioRadData(RunData, ResourceID, IsViewerFile, PSN, FileName)
Machine = 'BioRad'
IsProdTest = False$
ParseArray = ''
FieldPos = 13
FieldPosIncrement = 2
Offset = 1
Decimals = 2
// RDS Biorad metrology file
Timestamp = RunData<2>
RDSKeyID = RunData<6>
RunDataLayer = RunData<8>
RunDataZone = RunData<9>
ReactorID = RunData<5>
ScanRecipe = RunData<4>
ToolClassID = 'FTIR'
PatternNameIndex = RDS_TEST_SPEC_THICK_MPATTERN$
ToolClassIndex = RDS_TEST_SPEC_THICK_MTOOL$
DataIndex = RDS_TEST_READ_THICK$
DTMIndex = RDS_TEST_TEST_RUN_THICKNESS_DTM$
// Clean the cassette user input.
Cassette = RDSKeyID
Swap '.' with '*' in Cassette
Swap 'o' with '' in Cassette
Swap 'O' with '' in Cassette
if RowExists('WM_OUT', Cassette) then
//Redirect to EPP FQA Import
Metrology_Services('ImportBioRadEPPFQAData', RunData, FileName)
end else
RDSNo@ = RDSKeyID
RDSRec = Database_Services('ReadDataRow', 'RDS', RDSKeyID)
If Error_Services('NoError') then
WorkOrderNo = RDSRec<RDS_WO$>
// HgCV Project Development Code -------------------------------------------------------------------------------
DevelopmentFlag = Xlate('DEVELOPMENT', 'HGCV', 'STATUS', 'X')
If (DevelopmentFlag EQ True$) then
IsProdTest = ( Indexc(ScanRecipe, 'PROD', 1) GT 0 )
If IsProdTest EQ True$ then Metrology_Services('LogResults',RDSKeyID,Machine,'HgCV','Product test recognized. Recipe name: ':ScanRecipe)
end
// -------------------------------------------------------------------------------------------------------------
CassNo = RDSRec<RDS_CASS_NO$>
WorkOrder = Work_Order_Services('GetWorkOrder', WorkOrderNo, False$)
GoSub ParseWorkOrder
IsEpiPro = (WOReactorType _EQC 'EpiPro') OR (WOReactorType _EQC 'EPP')
RunDataZone = Metrology_Services('FormatZoneKeyID', RunDataZone) ; // 1, 2
RunDataLayer = Metrology_Services('FormatLayerKeyID', RunDataLayer) ; // L1, L2, 2
ZoneForValidation = RunDataZone
LayerForValidation = RunDataLayer
If LayerForValidation EQ '' then
LayerForValidation = 'L1'
end
CalculatedZone = Metrology_Services('GetCalculatedZone', RDSKeyID, IsEpiPro, RunDataZone)
If RunDataZone NE CalculatedZone then
Error_Services('Add', 'Entered Zone [' : RunDataZone : '] and calculated Zone [' : CalculatedZone : '] do not match.')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
Transfer CalculatedZone to RunDataZone
CalculatedLayer = Metrology_Services('GetCalculatedLayer', RDSKeyID, Machine, RunDataZone, IsEpiPro, RunDataLayer)
If (RunDataLayer NE CalculatedLayer) then
Error_Services('Add', 'Entered Layer [' : RunDataLayer : '] and calculated Layer [' : CalculatedLayer : '] do not match for the Zone [' : RunDataZone : '].')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
Transfer CalculatedLayer to RunDataLayer
ReactorRec = Database_Services('ReadDataRow', 'REACTOR', ReactorID)
If Error_Services('NoError') then
ReactorType = ReactorRec<REACTOR_REACT_TYPE$>
If ReactorType _EQC 'EpiPro' OR ReactorType _EQC 'EPP' OR ReactorType _EQC 'P' then
TestPointMapping = True$
If RunDataLayer EQ '' OR RunDataZone EQ '' then
Message = 'Layer and Zone are required for EpiPro.'
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Message)
Error_Services('Add', Message)
end
end else
TestPointMapping = False$
If RunDataLayer NE '' then
RunDataZone = ''
end else
Message = 'Layer is required for non-EpiPro.'
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Message)
Error_Services('Add', Message)
end
end
If Error_Services('NoError') then
Positions = ''
DataPoints = ''
Loop
Position = Trim(RunData<FieldPos>)
DataPoint = Trim(RunData<FieldPos + Offset>)
Until Position EQ ''
Positions := Position : @VM
DataPoints := DataPoint : @VM
FieldPos += FieldPosIncrement
Repeat
Positions[-1, 1] = '' ; // Strip final @VM
DataPoints[-1, 1] = '' ; // Strip final @VM
RawDataPoints = DataPoints
DataPoints = Iconv(DataPoints, 'MD' : Decimals)
Swap ' AM' with 'AM' in Timestamp
Swap ' PM' with 'PM' in Timestamp
Timestamp = IConv(Timestamp,'DT')
RDSLayerKeyID = RDSKeyID : '*' : RunDataLayer
NumDataPoints = DCount(DataPoints, @VM)
RDSLayerRec = Database_Services('ReadDataRow', 'RDS_LAYER', RDSLayerKeyID)
If Error_Services('NoError') then
RDSTestKeyIDs = RDSLayerRec<RDS_LAYER_RDS_TEST_KEYS$>
Found = False$
If RDSTestKeyIDs NE '' then
For Each RDSTestKeyID in RDSTestKeyIDs using @VM setting mkPos
RDSTestRec = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID)
If Error_Services('NoError') then
RDSTestZoneID = RDSTestRec<RDS_TEST_ZONE$>
// First test - Look for a Zone ID match.
If (RDSTestZoneID EQ RunDataZone) then
// A match has been found. No need to continue searching.
Found = True$
end
end
Until Found
Next RDSTestKeyID
end else
Error_Services('Add', 'Unable to obtain a valid Metrology Test Key ID.')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
// If not found this is because there was no zone. However, if this is not EpiPro, this is still a valid
// test so set the found flag to true.
If Not(Found) AND Not(IsEpiPro) then Found = True$
If (Found EQ True$) then
// Check if QA Metrology run or conventional RDS Metrology run.
// Recipe name match requirement has been turned off per Tom Tillery. djs 10/03/18
Begin Case
Case IsProdTest
// WO_MAT_QA THICK_ONLY test -> import and send to SPC
LogPath = Environment_Services('GetSpcFilesharePath')
LogPath2 = Environment_Services('GetApplicationRootPath') : '\Metrology'
SendToSPC = False$
Begin Case
Case NumDataPoints EQ 5
// Create 5 point SPC file
objSPC = Logging_Services('NewLog', LogPath, OConv(Datetime(), 'DT_^1_HS_'):'_Thickness_5Points.txt', CRLF$, Comma$, '', '', False$, True$)
NotesLog = Logging_Services('NewLog', LogPath2, 'Thickness_5Points.txt', CRLF$, Comma$, '', '', False$, False$)
SendToSPC = True$
Case NumDataPoints EQ 9
// Create 9 point SPC file
objSPC = Logging_Services('NewLog', LogPath, OConv(Datetime(), 'DT_^1_HS_'):'_Thickness_9Points.txt', CRLF$, Comma$, '', '', False$, True$)
NotesLog = Logging_Services('NewLog', LogPath2, 'Thickness_9Points.txt', CRLF$, Comma$, '', '', False$, False$)
SendToSPC = True$
Case NumDataPoints EQ 10
objSPC = Logging_Services('NewLog', LogPath, OConv(Datetime(), 'DT_^1_HS_'):'_Thickness_10Points.txt', CRLF$, Comma$, '', '', False$, True$)
NotesLog = Logging_Services('NewLog', LogPath2, 'Thickness_10Points.txt', CRLF$, Comma$, '', '', False$, False$)
SendToSPC = True$
Case NumDataPoints EQ 14
objSPC = Logging_Services('NewLog', LogPath, OConv(Datetime(), 'DT_^1_HS_'):'_Thickness_14Points.txt', CRLF$, Comma$, '', '', False$, True$)
NotesLog = Logging_Services('NewLog', LogPath2, 'Thickness_14Points.txt', CRLF$, Comma$, '', '', False$, False$)
SendToSPC = True$
Case Otherwise$
// Log this as it is unexpected
NotesLog = Logging_Services('NewLog', LogPath2, 'Thickness_Points_Error.txt', CRLF$, Comma$, '', '', False$, False$)
End Case
LogData = ''
LogData<1> = Oconv(Timestamp, 'DT/^HS') ; // Metrology date/time stamp
LogData<2> = ReactorID ; // Reactor
LogData<3> = RDSKeyID ; // RDS No
LogData<4> = RunData<7> ; // PSN
// Log data positions 5 through 10, 14, or 18 will be populated with data point measurements.
Convert @VM to @FM in RawDataPoints
Min = RawDataPoints<1>
Max = RawDataPoints<1>
For each DataPoint in RawDataPoints using @FM Setting DataPos
Min = Min(Min, DataPoint)
Max = Max(Max, DataPoint)
LogData<DataPos + 4> = DataPoint
Next DataPoint
LogData := @RM : ' '
If SendToSPC then Logging_Services('AppendLog', objSPC, LogData, @RM, @FM, True$)
Logging_Services('AppendLog', NotesLog, LogData, @RM, @FM, True$)
Average = Sum(RawDataPoints) / NumDataPoints
Average = Iconv(Average, 'MD3')
Average = Oconv(Average, 'MD3')
WOMatKey = Xlate('RDS', RDSKeyID, 'WO_MAT_KEY', 'X')
WOMatQAID = WorkOrderNo : '*' : CassNo
WOMatQARec = Database_Services('ReadDataRow', 'WO_MAT_QA', WOMatQAID)
SpecRecipes = WOMatQARec<WO_MAT_QA_RECIPE$>
WOStepNo = RDSRec<RDS_WO_STEP_KEY$>[-1, 'B*']
ProfSteps = ''
ProfileCnt = DCount(WOMatQARec<WO_MAT_QA_PROFILE$>, @VM)
For vPos = 1 to ProfileCnt
ProfSteps<1, vPos> = WOMatQARec<WO_MAT_QA_PROFILE$, vPos> : '*' : WOMatQARec<WO_MAT_QA_STAGE$, vPos>
Next vPos
LastProfSig = WOMatQARec<WO_MAT_QA_PROFILE$, ProfileCnt>
SigProfKeys = 'THICK_ONLY'
SigCnt = DCount(SigProfKeys, @VM)
Stages = 'UNLOAD'
SigProfFound = False$
For Each SigProfKey in SigProfKeys using @VM setting vPos
Stage = Stages<1, vPos>
If Num(SigProfKey[1, 1]) else
SigProfKey = WOStepNo : SigProfKey
end
ProfStep = SigProfKey : '*' : Stage
Locate ProfStep in ProfSteps using @VM setting Pos then
SigProfFound = True$
UnloadSigned = Signature_Services('GetStageSummary', WoMatKey, 'UNLOAD')<2>
IF (Stage EQ 'UNLOAD' AND UnloadSigned EQ True$) OR Stage NE 'UNLOAD' then
// verify recipe is correct
WoMatQaRecipe = WOMatQARec<WO_MAT_QA_RECIPE$, Pos>
WoMatQaRecipeMatchesScanRecipe = ScanRecipe _EQC WoMatQaRecipe
If WoMatQaRecipeMatchesScanRecipe EQ False$ then
ErrMsg = 'Scan recipe [ ' : ScanRecipe : ' ] does not match WoMatQa recipe [ ' : WoMatQaRecipe : ' ] for RDS [ ' : RDSKeyID : ' ] layer [ ' : CalculatedLayer : ' ].'
Error_Services('Add', ErrMsg)
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
// verify number of points is correct
WoMatQaToolClass = WOMatQARec<WO_MAT_QA_TOOL_CLASS$, Pos>
WoMatQaRecipePattern = WOMatQARec<WO_MAT_QA_RECIPE_PATTERN$, Pos>
NumDataPointsInSpec = Tool_Class_Services('GetNumberOfPointsForPattern', WoMatQaToolClass, WoMatQaRecipePattern)
WoMatQaRecipePatternNumPointsMatchesScanNumDataPoints = NumDataPointsInSpec EQ NumDataPoints
If WoMatQaRecipePatternNumPointsMatchesScanNumDataPoints EQ False$ then
ErrMsg = 'Scan data point count [ ' : NumDataPoints : ' ] does not match WoMatQa recipe pattern data point count [ ' : NumDataPointsInSpec : ' ] for RDS [ ' : RDSKeyID : ' ] layer [ ' : CalculatedLayer : ' ].'
Error_Services('Add', ErrMsg)
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
If (WOMatQARec<WO_MAT_QA_RESULT$, Pos> EQ '' OR IsViewerFile) then
If WoMatQaRecipeMatchesScanRecipe and WoMatQaRecipePatternNumPointsMatchesScanNumDataPoints then
WOMatQARec<WO_MAT_QA_RESULT$, Pos> = Average
WOMatQARec<WO_MAT_QA_MIN_RESULT$, Pos> = Oconv(Iconv(Min, 'MD3'), 'MD3')
WOMatQARec<WO_MAT_QA_MAX_RESULT$, Pos> = Oconv(Iconv(Max, 'MD3'), 'MD3')
SpecMin = WOMatQARec<WO_MAT_QA_MIN$, Pos>
SpecMax = WOMatQARec<WO_MAT_QA_MAX$, Pos>
For each DataPoint in RawDataPoints using @FM setting SubValuePos
FormatedData = Oconv(Iconv(DataPoint, 'MD3'), 'MD3')
If NumDataPoints LT 14 then
If ( ( FormatedData LT SpecMin ) OR ( FormatedData GT SpecMax ) and (SubValuePos LT 10) ) then
WOMatQARec<WO_MAT_QA_OUT_OF_SPEC$, Pos> = True$
end
end else
If ( FormatedData LT SpecMin ) OR ( FormatedData GT SpecMax ) then
WOMatQARec<WO_MAT_QA_OUT_OF_SPEC$, Pos> = True$
end
end
WOMatQARec<WO_MAT_QA_DATA_POINTS$, Pos, SubValuePos> = FormatedData
Next DataPoint
WOMatQARec<WO_MAT_QA_SIG$, Pos> = ''
WOMatQARec<WO_MAT_QA_SIG_DTM$, Pos> = ''
Database_Services('WriteDataRow', 'WO_MAT_QA', WOMatQAID, WOMatQARec, True$, False$, True$)
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - Data for this metrology test already exists and is not from the metrology viewer.')
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - Unload signature must first be signed for this data to be uploaded.')
end
end
Until SigProfFound
Next SigProfKey
If SigProfFound EQ True$ then
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Sig Prof. Found')
end else
Error_Services('Add', 'Unable to locate the signature profile [' : ProfStep : '].')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
Case Otherwise$
// If not an above case, then the run is a conventional THICK_ONLY RDS metrology test.
RDSLayerKeyID = RDSKeyID : '*' : LayerForValidation
RDSLayerRec = Database_Services('ReadDataRow', 'RDS_LAYER', RDSLayerKeyID)
If Error_Services('NoError') then
RDSTestKeyIDs = RDSLayerRec<RDS_LAYER_RDS_TEST_KEYS$>
// Verify the scan recipe matches the spec recipe
RecipeMatches = Metrology_Services('ScanRecipeMatchesRdsTestSpecThickMrecipe', RDSKeyID, LayerForValidation, ScanRecipe, RDSTestKeyIDs, ZoneForValidation)
If RecipeMatches EQ False$ then
ErrMsg = 'Scan recipe [ ' : ScanRecipe : ' ] does not match RDS Test recipe for RDS [ ' : RDSKeyID : ' ] layer [ ' : CalculatedLayer : ' ] zone [ ' : CalculatedZone : ' ].'
Error_Services('Add', ErrMsg)
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
// Verfiy the scan number of points matches the spec number of points
NumPointsMatches = Metrology_Services('ScanNumDataPointsMatchesRdsTestSpecThickMPattern', RDSKeyID, LayerForValidation, NumDataPoints, RDSTestKeyIDs, 'FTIR', ZoneForValidation)
If NumPointsMatches EQ False$ then
ErrMsg = 'Scan data point count [ ' : NumDataPoints : ' ] does not match RDS Test recipe pattern data point count for RDS [ ' : RDSKeyID : ' ] layer [ ' : CalculatedLayer : ' ] zone [ ' : CalculatedZone : ' ].'
Error_Services('Add', ErrMsg)
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
If RecipeMatches and NumPointsMatches then
GoSub LoadRunDataToDatabase
end
end else
ErrMsg = 'RDS Test scan failed for RDS [' : RDSKeyID : '] layer [' : LayerForValidation : '], because unable to get RDS_LAYER record [' : RDSLayerKeyID : ']'
Error_Services('Add', ErrMsg)
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
End Case
end else
Error_Services('Add', 'Data type / Spec not found for Layer [' : RunDataLayer : '].')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end
end service
Service ImportCDEQualData(RunData, ResourceID, PSN)
Machine = 'CDE'
ResourceID = Field(FileName, ' ', 1, 1)
URL = "https://oi-metrology-viewer-prod.mes.infineon.com/api/InfinityQSV3/":ResourceID:"/header"
TimeoutDuration = HTTPClient_Services('GetTimeoutDuration')
If TimeoutDuration NE 30 then Httpclient_Services('SetTimeoutDuration', 30)
Response = Httpclient_Services('SendHTTPRequest', 'GET', URL, '', '', '', '', '', '', '')
If Response NE '' then
If SRP_JSON(objJSON, 'Parse', Response) EQ '' then
SumOOS = SRP_JSON(objJSON, 'GetValue', 'Results[1].iq_sum')
ToolID = RunData<6>
TimeStamp = RunData<3>
If SumOOS NE '' then
Pass = (SumOOS EQ 0)
QualResponse = PM_Services('ProcessQual', PSN, ToolID, TimeStamp, Pass)
StatusCode = QualResponse<1>
Message = QualResponse<2>
Metrology_Services('LogResults', PSN, Machine, StatusCode, Service : ' : ' : Message)
end else
LogMessage = 'IQS response missing Results[1].iq_sum. Error message: ':SRP_JSON(objJSON, 'GetValue', 'message')
Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : LogMessage)
end
SRP_JSON(objJSON, 'Release')
end else
Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to parse IQS JSON response')
end
end else
Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Null response from IQS API.')
end
end service
Service ImportCDEData(RunData, ResourceID, IsViewerFile, PSN)
Machine = 'CDE'
ParseArray = ''
FieldPos = 23
FieldPosIncrement = 5
Offset = 3
Decimals = 3
Timestamp = RunData<3>
RDSKeyID = RunData<7>
RunDataLayer = RunData<9>
RunDataZone = RunData<10>
ReactorID = RunData<6>
ScanRecipe = RunData<5>
// Regular metrology file
ToolClassID = '4PP'
PatternNameIndex = RDS_TEST_SPEC_RES_MPATTERN$
ToolClassIndex = RDS_TEST_SPEC_RES_MTOOL$
DataIndex = RDS_TEST_READ_SHEET_RHO$
DTMIndex = RDS_TEST_TEST_RUN_SHEET_RHO_DTM$
IsProdTest = False$
RDSNo@ = RDSKeyID
RDSRec = Database_Services('ReadDataRow', 'RDS', RDSKeyID)
If Error_Services('NoError') then
// HgCV Project Development Code -------------------------------------------------------------------------------
DevelopmentFlag = Xlate('DEVELOPMENT', 'HGCV', 'STATUS', 'X')
If (DevelopmentFlag EQ True$) then
IsProdTest = ( Indexc(ScanRecipe, 'PROD', 1) GT 0 )
If IsProdTest EQ True$ then Metrology_Services('LogResults',RDSKeyID,Machine,'HgCV','Product test recognized. Recipe name: ':ScanRecipe)
end
// -------------------------------------------------------------------------------------------------------------
WorkOrderNo = RDSRec<RDS_WO$>
CassNo = RDSRec<RDS_CASS_NO$>
WorkOrder = Work_Order_Services('GetWorkOrder', WorkOrderNo, False$)
GoSub ParseWorkOrder
IsEpiPro = (WOReactorType _EQC 'EpiPro') OR (WOReactorType _EQC 'EPP')
RunDataZone = Metrology_Services('FormatZoneKeyID', RunDataZone) ; // 1, 2
RunDataLayer = Metrology_Services('FormatLayerKeyID', RunDataLayer) ; // L1, L2, 2
CalculatedZone = Metrology_Services('GetCalculatedZone', RDSKeyID, IsEpiPro, RunDataZone)
If RunDataZone NE CalculatedZone then
Error_Services('Add', 'Entered Zone [' : RunDataZone : '] and calculated Zone [' : CalculatedZone : '] do not match.')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
Transfer CalculatedZone to RunDataZone
CalculatedLayer = Metrology_Services('GetCalculatedLayer', RDSKeyID, Machine, RunDataZone)
If RunDataLayer NE CalculatedLayer then
Error_Services('Add', 'Entered Layer [' : RunDataLayer : '] and calculated Layer [' : CalculatedLayer : '] do not match for the Zone [' : RunDataZone : '].')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
Transfer CalculatedLayer to RunDataLayer
ReactorRec = Database_Services('ReadDataRow', 'REACTOR', ReactorID)
If Error_Services('NoError') then
ReactorType = ReactorRec<REACTOR_REACT_TYPE$>
If ReactorType _EQC 'EpiPro' OR ReactorType _EQC 'EPP' OR ReactorType _EQC 'P' then
TestPointMapping = True$
If RunDataLayer EQ '' OR RunDataZone EQ '' then
Message = 'Layer and Zone are required for EpiPro.'
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Message)
Error_Services('Add', Message)
end
end else
TestPointMapping = False$
If RunDataLayer NE '' then
RunDataZone = ''
end else
Message = 'Layer is required for non-EpiPro.'
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Message)
Error_Services('Add', Message)
end
end
If Error_Services('NoError') then
Positions = ''
DataPoints = ''
Loop
Position = Trim(RunData<FieldPos>)
DataPoint = Trim(RunData<FieldPos + Offset>)
Until Position EQ ''
Positions := Position : @VM
DataPoints := DataPoint : @VM
FieldPos += FieldPosIncrement
Repeat
RawDataPoints = DataPoints
Positions[-1, 1] = '' ; // Strip final @VM
DataPoints[-1, 1] = '' ; // Strip final @VM
DataPoints = Iconv(DataPoints, 'MD' : Decimals)
Swap ' AM' with 'AM' in Timestamp
Swap ' PM' with 'PM' in Timestamp
Timestamp = IConv(Timestamp,'DT')
RDSLayerKeyID = RDSKeyID : '*' : RunDataLayer
NumDataPoints = DCount(DataPoints, @VM)
RDSLayerRec = Database_Services('ReadDataRow', 'RDS_LAYER', RDSLayerKeyID)
If Error_Services('NoError') then
RDSTestKeyIDs = RDSLayerRec<RDS_LAYER_RDS_TEST_KEYS$>
Found = False$
If RDSTestKeyIDs NE '' then
For Each RDSTestKeyID in RDSTestKeyIDs using @VM setting mkPos
RDSTestRec = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID)
If Error_Services('NoError') then
RDSTestZoneID = RDSTestRec<RDS_TEST_ZONE$>
// First test - Look for a Zone ID match.
If (RDSTestZoneID EQ RunDataZone) then
// A match has been found. No need to continue searching.
Found = True$
end
end
Until Found
Next RDSTestKeyID
end else
Error_Services('Add', 'Unable to obtain a valid Metrology Test Key ID.')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
// If not found this is because there was no zone. However, if this is not EpiPro, this is still a valid
// test so set the found flag to true.
If Not(Found) AND Not(IsEpiPro) then Found = True$
If (Found EQ True$) then
If (IsProdTest EQ True$) then
// QA Metrology test (i.e. not RDS Metrology test)
Convert @VM to @FM in RawDataPoints
Min = RawDataPoints<1>
Max = RawDataPoints<1>
For each DataPoint in RawDataPoints using @FM Setting DataPos
Min = Min(Min, DataPoint)
Max = Max(Max, DataPoint)
Next DataPoint
Average = Sum(RawDataPoints) / NumDataPoints
Average = Iconv(Average, 'MD3')
Average = Oconv(Average, 'MD3')
WOMatQAID = WorkOrderNo : '*' : CassNo
WOMatQARec = Database_Services('ReadDataRow', 'WO_MAT_QA', WOMatQAID)
SpecRecipes = WOMatQARec<WO_MAT_QA_RECIPE$>
WOStepNo = RDSRec<RDS_WO_STEP_KEY$>[-1, 'B*']
ProfStep = ''
ProfSteps = ''
ProfileCnt = DCount(WOMatQARec<WO_MAT_QA_PROFILE$>, @VM)
For vPos = 1 to ProfileCnt
ProfSteps<1, vPos> = WOMatQARec<WO_MAT_QA_PROFILE$, vPos> : '*' : WOMatQARec<WO_MAT_QA_STAGE$, vPos>
Next vPos
LastProfSig = WOMatQARec<WO_MAT_QA_PROFILE$, ProfileCnt>
SigProfKeys = '1LW_RHO'
SigCnt = DCount(SigProfKeys, @VM)
Stages = 'UNLOAD'
SigProfFound = False$
UnloadSigned = Signature_Services('GetStageSummary', WOMatQAID, 'UNLOAD')<2>
For Each SigProfKey in SigProfKeys using @VM setting vPos
Stage = Stages<1, vPos>
if (Stage EQ 'UNLOAD' AND UnloadSigned EQ True$) OR Stage NE 'UNLOAD' then
If Num(SigProfKey[1, 1]) else
SigProfKey = WOStepNo : SigProfKey
end
ProfStep = SigProfKey : '*' : Stage
Locate ProfStep in ProfSteps using @VM setting Pos then
SigProfFound = True$
IF IsViewerFile OR WOMatQARec<WO_MAT_QA_RESULT$, Pos> = '' then
WOMatQARec<WO_MAT_QA_RESULT$, Pos> = Average
WOMatQARec<WO_MAT_QA_MIN_RESULT$, Pos> = Oconv(Iconv(Min, 'MD3'), 'MD3')
WOMatQARec<WO_MAT_QA_MAX_RESULT$, Pos> = Oconv(Iconv(Max, 'MD3'), 'MD3')
SpecMin = WOMatQARec<WO_MAT_QA_MIN$, Pos>
SpecMax = WOMatQARec<WO_MAT_QA_MAX$, Pos>
For each DataPoint in RawDataPoints using @FM setting SubValuePos
FormatedData = Oconv(Iconv(DataPoint, 'MD3'), 'MD3')
If ( ( FormatedData LT SpecMin ) OR ( FormatedData GT SpecMax ) and (SubValuePos LT 10) ) then
WOMatQARec<WO_MAT_QA_OUT_OF_SPEC$, Pos> = True$
end
WOMatQARec<WO_MAT_QA_DATA_POINTS$, Pos, SubValuePos> = FormatedData
Next DataPoint
Database_Services('WriteDataRow', 'WO_MAT_QA', WOMatQAID, WOMatQARec, True$, False$, True$)
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - Data for this metrology test already exists and is not from the metrology viewer.')
end
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - Unload signature must first be signed for this data to be uploaded.')
end
Until SigProfFound
Next SigProfKey
If SigProfFound EQ True$ then
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Success.')
end else
Error_Services('Add', 'Unable to locate the signature profile [' : ProfStep : '].')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
GoSub LoadRunDataToDatabase
end
end else
Error_Services('Add', 'Data type / Spec not found for Layer [' : RunDataLayer : '].')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end service
Service ImportHgCVQualData(RunData, ResourceID, PSN)
Machine = 'HgCV' /* Resistivity */
URL = "https://oi-metrology-viewer-prod.mes.infineon.com/api/InfinityQSV3/":ResourceID:"/header"
TimeoutDuration = HTTPClient_Services('GetTimeoutDuration')
If TimeoutDuration NE 30 then Httpclient_Services('SetTimeoutDuration', 30)
Response = Httpclient_Services('SendHTTPRequest', 'GET', URL, '', '', '', '', '', '', '')
If Response NE '' then
If SRP_JSON(objJSON, 'Parse', Response) EQ '' then
SumOOS = SRP_JSON(objJSON, 'GetValue', 'Results[1].iq_sum')
ToolID = RunData<2>
TimeStamp = RunData<11>
If SumOOS NE '' then
Pass = (SumOOS EQ 0)
QualResponse = PM_Services('ProcessQual', PSN, ToolID, TimeStamp, Pass)
StatusCode = QualResponse<1>
Message = QualResponse<2>
Metrology_Services('LogResults', PSN, Machine, StatusCode, Service : ' : ' : Message)
end else
LogMessage = 'IQS response missing Results[1].iq_sum. Error message: ':SRP_JSON(objJSON, 'GetValue', 'message')
Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : LogMessage)
end
SRP_JSON(objJSON, 'Release')
end else
Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to parse IQS JSON response')
end
end else
Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Null response from IQS API.')
end
end service
Service ImportHgCVData(RunData, ResourceID, IsViewerFile, PSN)
Machine = 'HgCV' /* Resistivity */
FieldPos = 53
FieldPosIncrement = 9
Offset = 5
PhaseOffset = 7
Decimals = 3
Timestamp = RunData<11>
RDSKeyID = RunData<4>
LayerZonePair = RunData<8>
ReactorID = RunData<3>
// Regular metrology file
ToolClassID = 'HGCV'
PatternNameIndex = RDS_TEST_SPEC_RES_MPATTERN$
ToolClassIndex = RDS_TEST_SPEC_RES_MTOOL$
DataIndex = RDS_TEST_READ_HGCV1_RES$
DTMIndex = RDS_TEST_TEST_RUN_HGCV_DTM$
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : ' : 'Beginning ImportHgCVData')
RDSNo@ = RDSKeyID
RDSRec = Database_Services('ReadDataRow', 'RDS', RDSKeyID)
If Error_Services('NoError') then
WorkOrderNo = RDSRec<RDS_WO$>
CassNo = RDSRec<RDS_CASS_NO$>
WorkOrder = Work_Order_Services('GetWorkOrder', WorkOrderNo, False$)
GoSub ParseWorkOrder
IsEpiPro = (WOReactorType _EQC 'EpiPro') OR (WOReactorType _EQC 'EPP')
Convert @Lower_Case to @Upper_Case in LayerZonePair
Convert 'Z' to '-' in LayerZonePair
Swap '--' with '-' in LayerZonePair
RunDataLayer = Field(LayerZonePair, '-', 1)
RunDataZone = Field(LayerZonePair, '-', 2)
RunDataZone = Metrology_Services('FormatZoneKeyID', RunDataZone) ; // 1, 2
RunDataLayer = Metrology_Services('FormatLayerKeyID', RunDataLayer) ; // L1, L2, 2
CalculatedZone = Metrology_Services('GetCalculatedZone', RDSKeyID, IsEpiPro, RunDataZone)
If RunDataZone NE CalculatedZone then
Error_Services('Add', 'Entered Zone [' : RunDataZone : '] and calculated Zone [' : CalculatedZone : '] do not match.')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
Transfer CalculatedZone to RunDataZone
CalculatedLayer = Metrology_Services('GetCalculatedLayer', RDSKeyID, Machine, RunDataZone)
If RunDataLayer NE CalculatedLayer then
Error_Services('Add', 'Entered Layer [' : RunDataLayer : '] and calculated Layer [' : CalculatedLayer : '] do not match for the Zone [' : RunDataZone : '].')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
Transfer CalculatedLayer to RunDataLayer
ReactorRec = Database_Services('ReadDataRow', 'REACTOR', ReactorID)
If Error_Services('NoError') then
ReactorType = ReactorRec<REACTOR_REACT_TYPE$>
If ReactorType _EQC 'EpiPro' OR ReactorType _EQC 'EPP' OR ReactorType _EQC 'P' then
TestPointMapping = True$
If RunDataLayer EQ '' OR RunDataZone EQ '' then
Message = 'Layer and Zone are required for EpiPro.'
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Message)
Error_Services('Add', Message)
end
end else
TestPointMapping = False$
If RunDataLayer NE '' then
RunDataZone = ''
end else
Message = 'Layer is required for non-EpiPro.'
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Message)
Error_Services('Add', Message)
end
end
If Error_Services('NoError') then
Position = ''
Positions = ''
DataPoints = ''
HgCVDataPoints = ''
Loop
Position = Trim(RunData<FieldPos>)
HgCVDataPoint = Trim(RunData<FieldPos + Offset>)
Until Position EQ ''
Positions := Position : @VM
HgCVDataPoints := HgCVDataPoint : @VM
FieldPos += FieldPosIncrement
Repeat
Positions[-1, 1] = '' ; // Strip final @VM
HgCVDataPoints[-1, 1] = '' ; // Strip final @VM
HgCVDataPoints = Iconv(HgCVDataPoints, 'MD' : Decimals)
Swap ' AM' with 'AM' in Timestamp
Swap ' PM' with 'PM' in Timestamp
Timestamp = IConv(Timestamp,'DT')
RDSLayerKeyID = RDSKeyID : '*' : RunDataLayer
NumDataPoints = DCount(HgCVDataPoints, @VM)
RDSLayerRec = Database_Services('ReadDataRow', 'RDS_LAYER', RDSLayerKeyID)
If Error_Services('NoError') then
RDSTestKeyIDs = RDSLayerRec<RDS_LAYER_RDS_TEST_KEYS$>
Found = False$
If RDSTestKeyIDs NE '' then
For Each RDSTestKeyID in RDSTestKeyIDs using @VM setting mkPos
RDSTestRec = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID)
If Error_Services('NoError') then
RDSTestZoneID = RDSTestRec<RDS_TEST_ZONE$>
// Determine if this is Hg Concentration Resistivity.
IsHgCvCRes = RDSTestRec<RDS_TEST_SPEC_CRES_MTOOL$> _EQC 'HGCV'
// First test - Look for a Zone ID match.
If (RDSTestZoneID EQ RunDataZone) then
// A match has been found. No need to continue searching.
Found = True$
end
end
Until Found
Next RDSTestKeyID
If (Found EQ True$) then
// Only continue checking if there are data points. Otherwise, there is no data by which
// the code can calculate values to store.
If NumDataPoints GT 0 then
// Second test - If HgCv Res, dig deeper to see if this is a possible CRES entry..
If IsHgCvCRes then
WOMatQAID = WorkOrderNo : '*' : CassNo
WOMatQARec = Database_Services('ReadDataRow', 'WO_MAT_QA', WOMatQAID)
If Error_Services('NoError') then
Locate 'CRES' in WOMatQARec<WO_MAT_QA_PROP$> using @VM setting vPos then
Stage = WOMatQARec<WO_MAT_QA_STAGE$, vPos>
RequireUnload = False$
IF (WOMatQARec<WO_MAT_QA_RESULT$, vPos, 1> EQ '' OR IsViewerFile) then
PhaseDataPoints = ''
Positions = ''
Position = ''
FieldPos = 53
Loop
Position = Trim(RunData<FieldPos>)
PhaseDataPoint = Trim(RunData<FieldPos + PhaseOffset>)
Until Position EQ ''
Positions := Position : @VM
PhaseDataPoints := PhaseDataPoint : @VM
FieldPos += FieldPosIncrement
Repeat
Positions[-1, 1] = '' ; // Strip final @VM
PhaseDataPoints[-1, 1] = '' ; // Strip final @VM
PhaseDataPoints = Iconv(PhaseDataPoints, 'MD' : Decimals)
DataPoints = HgCVDataPoints
* GoSub CalculateQAData
Response = QA_Services('CalculateHgCVData', Datapoints)
HgCVMin = Response<0, 2>
HgCVMax = Response<0, 3>
HgCVAvg = Response<0, 1>
HgCVEdgeMean = Response<0, 4>
HgCVRangePct = Response<0, 5>
EdgeMean4mm = Response<0, 6>
Avg9Point = Response<0, 7>
EdgeMean10mm = Response<0, 8>
ResStdDev = Response<0, 9>
DataPoints = PhaseDataPoints
Response = QA_Services('CalculateHgCVData', Datapoints)
* GoSub CalculateQAData
PhaseMin = Response<0, 2>
PhaseMax = Response<0, 3>
PhaseAvg = Response<0, 1>
PhaseEdgeMean = Response<0, 4>
PhaseRangePct = Response<0, 5>
// Format data. Round to significant digits.
* HgCVAvg = Oconv(Iconv(Oconv(HgCVAvg, 'MD3L'), 'MD3L'), 'MD3L')
* PhaseAvg = Oconv(Iconv(Oconv(PhaseAvg, 'MD3L'), 'MD3L'), 'MD3L')
* HgCVMin = Oconv(Iconv(Oconv(HgCVMin, 'MD3L'), 'MD3L'), 'MD3L')
* HgCVMax = Oconv(Iconv(Oconv(HgCVMax, 'MD3L'), 'MD3L'), 'MD3L')
* PhaseMin = Oconv(Iconv(Oconv(PhaseMin, 'MD3L'), 'MD3L'), 'MD3L')
* PhaseMax = Oconv(Iconv(Oconv(PhaseMax, 'MD3L'), 'MD3L'), 'MD3L')
* HgCVEdgeMean = OConv(IConv(HgCVEdgeMean, 'MD3L'), 'MD3L')
* PhaseEdgeMean = Oconv(IConv(PhaseEdgeMean, 'MD3L'), 'MD3L')
* HgCVRangePct = Oconv(Iconv(HgCVRangePct, 'MD3L'), 'MD3L')
* PhaseRangePct = Oconv(Iconv(PhaseRangePct, 'MD3L'), 'MD3L')
WOMatQARec<WO_MAT_QA_RESULT$, vPos> = HgCVAvg : @SVM : PhaseAvg
WOMatQARec<WO_MAT_QA_MIN_RESULT$, vPos> = HgCVMin : @SVM : PhaseMin
WOMatQARec<WO_MAT_QA_MAX_RESULT$, vPos> = HgCVMax : @SVM : PhaseMax
WOMatQARec<WO_MAT_QA_RANGE_PCT_RESULT$, vPos> = HgCVRangePct : @SVM : PhaseRangePct
WOMatQARec<WO_MAT_QA_EDGE_MEAN_RESULT$, vPos> = HgCVEdgeMean : @SVM : PhaseEdgeMean
SpecMin = WOMatQARec<WO_MAT_QA_MIN$, vPos>
SpecMax = WOMatQARec<WO_MAT_QA_MAX$, vPos>
SpecPhaseMin = WOMatQARec<WO_MAT_QA_PHASE_MIN$, vPos>
If SpecPhaseMin EQ '' then
// Phase Min Spec was not added to WO_MAT_QA record when it should have been.
// This bug should be fixed now, but in the meantime current records in production
// may not have a phase min specification value. We need to add it here. - djs - 10/15/18
PSN = Xlate('RDS', RDSKeyID, 'PROD_SPEC_ID', 'X')
If PSN NE '' then
PRSStageKey = PSN:'*UNLOAD'
Database_Services('ActivateRecord', 'PRS_STAGE', PRSStageKey)
MetProps = {MET_PROP}
Locate 'CRES' in MetProps using @VM setting PropPos then
SpecPhaseMinCol = {MET_PHASE_MIN}
SpecPhaseMin = SpecPhaseMinCol<1, PropPos>
WOMatQARec<WO_MAT_QA_PHASE_MIN$, vPos> = SpecPhaseMin
end
end
end
CriticalPoints = '1,2,5,6,9'
// Store data points for HgCV Data Table Project --------------------------------------------------------------
For Index = 1 to NumDataPoints
CriticalPoint = Index(CriticalPoints, Index, 1)
HgCVData = Oconv(Iconv(Oconv(HgCVDataPoints<0,Index>, 'MD3L'), 'MD3L'), 'MD3L')
PhaseData = Oconv(Iconv(Oconv(PhaseDataPoints<0,Index>, 'MD3L'), 'MD3L'), 'MD3L')
If ( (HgCVData LT SpecMin) OR (HgCVData GT SpecMax) OR (PhaseData LT SpecPhaseMin) ) and CriticalPoint then
// Data point out of spec
WOMatQARec<WO_MAT_QA_OUT_OF_SPEC$, vPos> = True$
end
DataRow = HgCVData : @TM : PhaseData
WOMatQARec<WO_MAT_QA_DATA_POINTS$, vPos, Index> = DataRow
Next Index
// ------------------------------------------------------------------------------------------------------------
// Send data to SPC -------------------------------------------------------------------------------------------
LogPath = Environment_Services('GetSpcFilesharePath')
SpcFileDtm = OConv(Datetime(), 'DT2^HS ')
Convert ' ' to '_' in SpcFileDtm
SpcLogFilename = 'HgCV_Unload_Res_9Points_':SpcFileDtm:'.txt'
objSPC = Logging_Services('NewLog', LogPath, SpcLogFilename, CRLF$, Comma$, '', '', False$, True$)
If Error_Services('NoError') then
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Successfully created blank SPC file.')
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Failed to create blank SPC file. Error message: ':Error_Services('GetMessage'))
end
LogData = ''
LogData<1> = Oconv(Timestamp, 'DT/^HS') ; // Metrology date/time stamp
LogData<2> = ReactorID ; // Reactor
LogData<3> = RDSKeyID ; // RDS No
LogData<4> = Xlate('RDS', RDSKeyID, 'PROD_SPEC_ID', 'X'); // PSN
WaferSize = Xlate('RDS', RDSKeyID, 'WAFER_SIZE', 'X')
WaferSize = Field(WaferSize, ' ', 3)
LogData<5> = WaferSize:' Inch' ; // Wafer Size
FormattedData = Oconv(HgCVDataPoints, 'MD3L')
Swap @VM with ',' in FormattedData
LogData<6> = FormattedData
LogData<7> = OConv(PhaseAvg, 'MD43L')
LogData<8> = OConv(HgCVAvg, 'MD43L') ; // HgCV Res Avg
LogData<9> = OConv(ResStdDev, 'MD83L') ; // HgCV Res Std Dev
LogData<10> = HgCVRangePct ; // HgCV Res Range %
LogData<11> = OConv(EdgeMean4mm, 'MD43L') ; // HgCV 4mm Edge Mean
LogData<12> = OConv(Avg9Point, 'MD83L') ; // HgCV 9 Point Mean
LogData<13> = OConv(EdgeMean10mm, 'MD43L') ; // HgCV 10mm Edge Mean
LogData<14> = HgCVEdgeMean ; // HgCV Edge Mean Delta %
LogData := @RM : ' '
Logging_Services('AppendLog', objSPC, LogData, @RM, @FM, True$)
If Error_Services('NoError') then
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Successfully updated SPC file with metrology data.')
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Failed to update SPC file with metrology data. Error message: ':Error_Services('GetMessage'))
end
// ------------------------------------------------------------------------------------------------------------
WOMatQARec<WO_MAT_QA_SIG$, vPos> = ''
WOMatQARec<WO_MAT_QA_SIG_DTM$, vPos> = ''
Database_Services('WriteDataRow', 'WO_MAT_QA', WOMatQAID, WOMatQARec, True$, False$, True$)
If Error_Services('NoError') then
RDSTestRec<DTMIndex> = Timestamp
// Save results
Database_Services('WriteDataRow', 'RDS_TEST', RDSTestKeyID, RDSTestRec, True$, False$, True$)
If Error_Services('NoError') then
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Success - Hg CRes.')
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID002', Service : ' : UID002 - ' : Error_Services('GetMessage'))
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
// We know it is a CRES test, so we can now safely import the Phase Angle data
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - Data for this metrology test already exists and is not from the metrology viewer.')
end
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
// The correct RDSTestKeyID was found. If this is a HgCv measurement and HgConcentration RDS test,
// the data has already been written to the database in the above logic. In all other cases, the
// data needs to be written to the database using the following logic that works off of the test
// point map.
DataPoints = HgCVDataPoints
GoSub LoadRunDataToDatabase
end
end else
Error_Services('Add', 'No data points found.')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Error_Services('Add', 'Data type / Spec not found for Layer [' : RunDataLayer : '].')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Error_Services('Add', 'Unable to obtain a valid Metrology Test Key ID.')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end service
Service ImportTencorData(RunData)
ErrorMessage = ''
Machine = 'Tencor'
Timestamp = RunData<9>
HazeAvg = RunData<10>
RDSKeyID = RunData<28>
ScanRecipe = RunData<30>
SoDAvg = RunData<39>
SoDMax = RunData<40>
SoDMin = RunData<41>
ScanTool = RunData<43>
Swap '%' with ' ' in ScanTool
RDSNo@ = RDSKeyID
Convert @Lower_Case to @Upper_Case in ScanTool
Swap ' AM' with 'AM' in Timestamp
Swap ' PM' with 'PM' in Timestamp
Timestamp = IConv(Timestamp,'DT')
If RowExists('RDS', RDSKeyID) then
// Try to read the CleanInsp datarow. Use this to identify the RDSKeyID.
ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSKeyID)
If Error_Services('NoError') then
// Get the PSN to determine if this recipe is part of the LWI stage or the POST stage.
PSN = Xlate('RDS', RDSKeyID, 'PROD_SPEC_ID', 'X')
PRSStageKeys = Xlate('PROD_SPEC', PSN, 'PRS_STAGE_KEY', 'X')
StageFound = False$
Stage = ''
For each PRSStageKey in PRSStageKeys using @VM setting kPos
SSRecipes = Xlate('PRS_STAGE', PRSStageKey, 'SURFSCAN_RECIPE', 'X')
Locate ScanRecipe in SSRecipes using @VM setting rPos then
StageFound = True$
Stage = Field(PRSStageKey, '*', 2, 1)
end
Next PRSStageKey
CIKeyStages = ReactRunRec<REACT_RUN_CI_STAGE$>
CIKeyIDs = ReactRunRec<REACT_RUN_CI_NO$>
Locate Stage in CIKeyStages using @VM setting vPos then
// Stage is prescribed or user created the record from the UI.
CIKeyID = CIKeyIDs<0, vPos>
If (CIKeyID NE '') then
HaveLock = Database_Services('GetKeyIDLock', 'CLEAN_INSP', CIKeyID)
If HaveLock EQ True$ then
If ScanRecipe NE '' then
CleanInspRec = Database_Services('ReadDataRow', 'CLEAN_INSP', CIKeyID)
NumRecipes = DCount(CleanInspRec<CLEAN_INSP_SCAN_RECIPE$>, @VM)
ScanIndex = NumRecipes + 1
NumTools = DCount(CleanInspRec<CLEAN_INSP_SCAN_TOOL$>, @VM)
SpecRecipeList = CleanInspRec<CLEAN_INSP_SPEC_SURFSCAN_RECIPE$>
NumWfrs = 0
Locate ScanRecipe In SpecRecipeList Using @VM Setting RecipeIndex then
// Recipe found in spec list
SpecSampleQty = CleanInspRec<CLEAN_INSP_SPEC_SS_SAMP_QTY$, RecipeIndex>
SODWaferArray = ''
SODStartTime = Time()
SODWaferArray = QA_Services('GetSODPerWafer', RDSKeyID, ScanRecipe, Timestamp)
SODStopTime = Time()
TimeTaken = SODStopTime - SODStartTime
LogData = ''
LogData<1> = Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTS')
LogData<2> = MachineType@
LogData<3> = RDSNo@
LogData<4> = 'Time taken to import SOD data: ':TimeTaken:' seconds.'
Logging_Services('AppendLog', objSODPerfLog, LogData, @RM, @FM)
If SODWaferArray NE '' then
WaferNos = SODWaferArray<1>
SODVals = SODWaferArray<2>
SortVals = SODWaferArray<3>
For each Wfr in SODVals using @VM setting vPos
NumWfrs += (Wfr NE '')
Next Wfr
For each WaferNo in WaferNos using @VM
If WaferNo NE '' then
CleanInspRec<CLEAN_INSP_SCAN_SOD_PER_WAFER$, ScanIndex, WaferNo> = SODVals<1,WaferNo>
CleanInspRec<CLEAN_INSP_SCAN_SORT_PER_WAFER$, ScanIndex, WaferNo> = SortVals<1,WaferNo>
QA_Services('PostWaferImageRequest', RDSKeyID, WaferNo, ScanRecipe)
end
Next Wafer
end
// Since a matching recipe was found, clear the recipe mismatch field in case it was
// previously set by a user attempting to load rundata with the wrong recipe name.
CleanInspRec<CLEAN_INSP_SCAN_RECIPE_MISMATCH$> = ''
If NumTools GT NumRecipes then
NumRecipes = NumTools
end
NumRecipes += 1
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_RECIPE$, NumRecipes, 0, ScanRecipe)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_TOOL$, NumRecipes, 0, ScanTool)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_MIN$, NumRecipes, 0, SoDMin)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_MAX$, NumRecipes, 0, SoDMax)
SoDAvg = Iconv(SoDAvg, 'MD3')
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_AVG$, NumRecipes, 0, SoDAvg)
HazeAvg = Iconv(HazeAvg, 'MD3')
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_HAZE_AVG_AVG$, NumRecipes, 0, HazeAvg)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SIG$, NumRecipes, 0, '')
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SIG_DTM$, NumRecipes, 0, '')
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_INSP_TEST_RUN_DTM$, NumRecipes, 0, Timestamp)
// Save results (this will trigger an ROTR_REQUEST via CLEAN_INSP_ACTIONS)
Database_Services('WriteDataRow', 'CLEAN_INSP', CIKeyID, CleanInspRec, True$, False$, True$)
end else
ErrorMessage = 'Tencor recipe ' : ScanRecipe : ' not found in SurfScan recipe list.'
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : ErrorMessage)
// Set recipe mismatch flag for MFS purposes
CleanInspRec<CLEAN_INSP_SCAN_RECIPE_MISMATCH$> = ScanRecipe
Database_Services('WriteDataRow', 'CLEAN_INSP', CIKeyID, CleanInspRec, True$, False$, True$)
end
end else
ErrorMessage = 'Scan Recipe is missing.'
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : ErrorMessage)
end
Database_Services('ReleaseKeyIDLock', 'CLEAN_INSP', CIKeyID)
end else
// 01/31/23 - djs - Modified error message to use UID002 instead of UID001 to ensure the
// metrology file is not deleted so that the service can try to import it
// again once the lock is released.
ErrorMessage = 'Error in service ':Service:'. Failed to lock CLEAN_INSP record: ':CIKeyID:'.'
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID002', Service : ' : ' : ErrorMessage)
end
end else
ErrorMessage = 'CI Key ID is missing.'
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : ErrorMessage)
end
end else
// Added 8/13/2020 JRO
// We can enter in some conditional logic here for Production UAT
// If statement to gate from production below
PostStartTime = Time()
if Indexc(ScanRecipe, 'POST', 1) then
//Locate 'POST' in ScanRecipe Using "" setting pPos then
ReactRunRec = Xlate('REACT_RUN',RDSKeyID, '', 'X')
WONo = ReactRunRec<REACT_RUN_WO_NO$>;//WONo
WOStep = ReactRunRec<REACT_RUN_WO_STEP$>;//WOStep
CassNo = ReactRunRec<REACT_RUN_CASS_NO$>;//CassNo
Stage = 'POST';//Stages
PSNo = XLATE('RDS',RDSKeyID,RDS_PROD_SPEC_ID$,'X');//PSNo
DefectSpec = Xlate('PRS_STAGE', PSNo : '*LWI', PRS_STAGE_SURF_DEFECTS$, 'X');//Since there is no set spec for a non-existent stage we need to take one from the unloading step.
HazeSpec = Xlate('PRS_STAGE', PSNo : '*LWI', PRS_STAGE_SURF_HAZE$, 'X');//Since there is no set spec for a non-existent stage we need to take one from the unloading step.
//Check if CI was created already(usually during cleans) JRO-9-28
CIStages = ReactRunRec<REACT_RUN_CI_STAGE$>
Locate 'POST' in CIStages using @VM setting sPos then
//Existing POST CI record found, fetch it.
CINo = ReactRunRec<REACT_RUN_CI_NO$,sPos>
CleanInspRec = Xlate('CLEAN_INSP', CINo,'','X')
IF CleanInspRec<CLEAN_INSP_SCAN_SOD_PER_WAFER$> NE '' THEN
//Append a value mark to the end of the field so that we can add another set of data
CleanInspRec<CLEAN_INSP_SCAN_SOD_PER_WAFER$> = CleanInspRec<CLEAN_INSP_SCAN_SOD_PER_WAFER$> : @VM
END
exists = True$
end else
//No Exisiting POST CI record found, Create new CI record
exists = False$
oCIParms = ''
oCIParms = WONo:@RM
oCIParms := WOStep:@RM
oCIParms := CassNo:@RM
oCIParms := Stage:@RM
oCIParms := RDSKeyID:@RM
oCIParms := PSNo:@RM
CleanInspRec = ''
CleanInspRec<CLEAN_INSP_WO_NO$> = WONo
CleanInspRec<CLEAN_INSP_WO_STEP$> = WOStep
CleanInspRec<CLEAN_INSP_CASS_NO$> = CassNo
CleanInspRec<CLEAN_INSP_STAGE$> = Stage
CleanInspRec<CLEAN_INSP_RDS_NO$> = RDSKeyID
end
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_RECIPE$, -1, 0, ScanRecipe)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_TOOL$, -1, 0, ScanTool)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_MIN$, -1, 0, SoDMin)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_MAX$, -1, 0, SoDMax)
SoDAvg = Iconv(SoDAvg, 'MD3')
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_AVG$, -1, 0, SoDAvg)
HazeAvg = Iconv(HazeAvg, 'MD3')
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_HAZE_AVG_AVG$, -1, 0, HazeAvg)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SIG$, -1, 0, '')
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SIG_DTM$, -1, 0, '')
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_INSP_TEST_RUN_DTM$, -1, 0, Timestamp)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SPEC_SURFSCAN_RECIPE$, -1, 0, ScanRecipe)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SPEC_SURF_DEFECTS$, -1, 0, DefectSpec)
//Here I am attempting to create the scan details.
SODWaferArray = ''
SODWaferArray = QA_Services('GetSODPerWafer', RDSKeyID, ScanRecipe, Timestamp)
If SODWaferArray NE '' then
WaferNos = SODWaferArray<1>
SODVals = SODWaferArray<2>
SortVals = SODWaferArray<3>
NumRecipes = DCount(CleanInspRec<CLEAN_INSP_SCAN_RECIPE$>, @VM)
NumWfrs = ''
For each Wfr in SODVals using @VM setting vPos
NumWfrs += (Wfr NE '')
Next Wfr
For each WaferNo in WaferNos using @VM
If WaferNo NE '' then
CleanInspRec<CLEAN_INSP_SCAN_SOD_PER_WAFER$, NumRecipes, WaferNo> = SODVals<1,WaferNo>
CleanInspRec<CLEAN_INSP_SCAN_SORT_PER_WAFER$, NumRecipes, WaferNo> = SortVals<1,WaferNo>
QA_Services('PostWaferImageRequest', RDSKeyID, WaferNo, ScanRecipe)
end
Next Wafer
end
if exists Eq False$ then
oCIParms := CleanInspRec ;
CINo = obj_Clean_Insp('Create',oCIParms)
If Error_Services('NoError') then
ReactRunRec = INSERT(ReactRunRec,REACT_RUN_CI_NO$,-1,0,CINo)
ReactRunRec = INSERT(ReactRunRec,REACT_RUN_CI_STAGE$,-1,0,Stage)
obj_Tables('WriteRec','REACT_RUN':@RM:RDSKeyID:@RM:@RM:ReactRunRec)
obj_Tables('WriteRec','CLEAN_INSP':@RM:CINo:@RM:@RM:CleanInspRec)
PostStopTime = Time()
TimeTaken = PostStopTime - PostStartTime
LogData = ''
LogData<1> = Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTS')
LogData<2> = MachineType@
LogData<3> = RDSNo@
LogData<4> = 'Time taken to import POST data: ':TimeTaken:' seconds.'
Logging_Services('AppendLog', objPOSTPerfLog, LogData, @RM, @FM)
end else
// Record error. Ensure metrology run data file is not deleted by including UID002 in the
// error message that is placed onto error stack in Error_Services.
ErrorMessage = 'Failed to create CLEAN_INSP record. Error UID002.'
end
end else
//write to existing CI rec.
obj_Tables('WriteRec','CLEAN_INSP':@RM:CINo:@RM:@RM:CleanInspRec)
end
end else
// Unexpected recipe -> notify users if not a QUAL scan
QualScan = (Indexc(ScanRecipe, 'QUAL', 1) GT 0)
If QualScan EQ False$ then
Recipients = XLATE('NOTIFICATION','TENCOR_NOTIFICATIONS',NOTIFICATION_USER_ID$,'X')
SentFrom = 'OI Admin'
Subject = 'Tencor SurfScan Import Failure for RDS ':RDSKeyID:'.'
Message = 'Scanned recipe ':ScanRecipe:' not found in PSN ':PSN:' for RDS ':RDSKeyID:'.'
AttachWindow = ''
AttachKey = ''
SendToGroup = ''
Parms = Recipients:@RM:SentFrom:@RM:Subject:@RM:Message:@RM:AttachWindow:@RM:AttachKey:@RM:SendToGroup
obj_Notes('Create',Parms)
end
end
end
end else
ErrorMessage = Error_Services('GetMessage')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : ErrorMessage)
end
end else
ErrorMessage = 'Invalid RDS Key ID.'
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : ErrorMessage)
end
If ErrorMessage NE '' then Error_Services('Add', ErrorMessage)
end service
Service ImportSP1Data(RunData)
Machine = 'SP1'
Timestamp = RunData<9>
HazeAvg = RunData<10>
RDSKeyID = RunData<28>
ScanRecipe = RunData<30>
CompareScanRecipe = ScanRecipe
IsMisfit = IndexC(ScanRecipe, 'MISFIT', 1)
If IsMisfit then
// Sum of Defects coming from LPD.
SoDAvg = RunData<3>
SoDMax = RunData<2>
SoDMin = RunData<1>
end else
// Sum of Defects coming from All.
SoDAvg = RunData<39>
SoDMax = RunData<40>
SoDMin = RunData<41>
end
ScanTool = RunData<43>
DCNMM2 = RunData<44>
* Swap '%' with ' ' in ScanTool
// The 4th character can be 0 or %, but needs to be ' ' in OpenInsight
ScanTool[4,1] = ' '
Convert @Lower_Case to @Upper_Case in ScanTool
Convert @Lower_Case to @Upper_Case in CompareScanRecipe
* Convert ' ' to '' in CompareScanRecipe
Swap ' AM' with 'AM' in Timestamp
Swap ' PM' with 'PM' in Timestamp
Timestamp = IConv(Timestamp,'DT')
RDSNo@ = RDSKeyID
RDSRec = Database_Services('ReadDataRow', 'RDS', RDSKeyID)
If Error_Services('NoError') then
WorkOrderNo = RDSRec<RDS_WO$>
CassNo = RDSRec<RDS_CASS_NO$>
WorkOrder = Work_Order_Services('GetWorkOrder', WorkOrderNo, True$)
GoSub ParseWorkOrder
If RowExists('RDS', RDSKeyID) then
// Try to read the CleanInsp datarow. Use this to identify the RDSKeyID.
ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSKeyID)
// Check to confirm that the LWI stage has a recipe. If so, then update the UNLOAD stage.
If PSN NE '' then
PRSStageRow = Database_Services('ReadDataRow', 'PRS_STAGE', PSN : '*LWI')
PRSSurfscanRecipe = PRSStageRow<PRS_STAGE_SURFSCAN_RECIPE$>
Convert @Lower_Case to @Upper_Case in PRSSurfscanRecipe
TrimPRSRecipes = SRP_Trim(PRSSurfscanRecipe, 'FMB')
TrimScanRecipe = SRP_Trim(CompareScanRecipe, 'FMB')
Locate TrimScanRecipe in TrimPRSRecipes using @VM setting vPos then
* If SRP_Trim(PRSSurfscanRecipe, 'FMB') EQ SRP_Trim(CompareScanRecipe, 'FMB') then
Stage = 'LWI' ; // Viewed in the Unloading form.
end else
// LWI does not have a recipe. Check the POST stage. If so, then update the POST stage.
Transfer PRSSurfscanRecipe to LWIPRSSurfscanRecipe
PRSStageRow = Database_Services('ReadDataRow', 'PRS_STAGE', PSN : '*POST')
PRSSurfscanRecipe = PRSStageRow<PRS_STAGE_SURFSCAN_RECIPE$>
Convert @Lower_Case to @Upper_Case in PRSSurfscanRecipe
TrimPRSRecipes = SRP_Trim(PRSSurfscanRecipe, 'FMB')
TrimScanRecipe = SRP_Trim(CompareScanRecipe, 'FMB')
Locate TrimScanRecipe in TrimPRSRecipes using @VM setting vPos then
* If SRP_Trim(PRSSurfscanRecipe, 'FMB') EQ SRP_Trim(CompareScanRecipe, 'FMB') then
Stage = 'POST' ; // Viewed in the Post EPI form.
end else
// No recipe was found. Set an error and skip.
Stage = ''
Error_Services('Set', 'No matching recipe was found. PSN=' : PSN : ', Scan Recipe=' : ScanRecipe : ', LWI Recipe=' : LWIPRSSurfscanRecipe : ', POST Recipe=' : PRSSurfscanRecipe)
end
end
end else
// No PRN was found. Set an error and skip.
Stage = ''
Error_Services('Set', 'No PRN was found.')
end
If Error_Services('NoError') then
CIKeyStages = ReactRunRec<REACT_RUN_CI_STAGE$>
CIKeyIDs = ReactRunRec<REACT_RUN_CI_NO$>
Locate Stage in CIKeyStages using @VM setting vPos then
CIKeyID = CIKeyIDs<0, vPos>
If (CIKeyID NE '') then
If ScanRecipe NE '' then
CleanInspRec = Database_Services('ReadDataRow', 'CLEAN_INSP', CIKeyID)
NumRecipes = DCount(CleanInspRec<CLEAN_INSP_SCAN_RECIPE$>, @VM)
NumTools = DCount(CleanInspRec<CLEAN_INSP_SCAN_TOOL$>, @VM)
If NumTools GT NumRecipes then
NumRecipes = NumTools
end
NumRecipes += 1
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_RECIPE$, NumRecipes, 0, ScanRecipe)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_TOOL$, NumRecipes, 0, ScanTool)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_MIN$, NumRecipes, 0, SoDMin)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_MAX$, NumRecipes, 0, SoDMax)
SoDAvg = Iconv(SoDAvg, 'MD3')
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_AVG$, NumRecipes, 0, SoDAvg)
HazeAvg = Iconv(HazeAvg, 'MD3')
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_HAZE_AVG_AVG$, NumRecipes, 0, HazeAvg)
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SIG$, NumRecipes, 0, '')
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SIG_DTM$, NumRecipes, 0, '')
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_INSP_TEST_RUN_DTM$, NumRecipes, 0, Timestamp)
DCNMM2 = Iconv(DCNMM2, 'MD3')
CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_DCN_MM2$, NumRecipes, 0, DCNMM2)
Database_Services('WriteDataRow', 'CLEAN_INSP', CIKeyID, CleanInspRec, True$, False$, True$)
If Error_Services('NoError') then
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Success.')
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Error_Services('Add', 'Scan Recipe is missing.')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Error_Services('Add', 'CI Key ID is missing.')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Error_Services('Add', 'Invalid RDS Key ID.')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end service
Service FormatLayerKeyID(RunDataLayer)
If RunDataLayer NE '' then
// Development code
OriginalRunDataLayer = RunDataLayer
CONVERT @LOWER_CASE TO @UPPER_CASE IN RunDataLayer
// Allow for 'P', which represents a Product (ie. QA) Test - Solution abandoned 08/14/18 per Francois
Convert 'ABCDEFGHIJKLMNOPQRSTUVWXYZ- ' to '' in RunDataLayer
Begin Case
Case (RunDataLayer = '1')
RunDataLayer = 'L1'
Case (RunDataLayer = '2')
RunDataLayer = 'L2'
Case (RunDataLayer = '3')
RunDataLayer = '2'
* Case (RunDataLayer = 'P')
* Null ; // Leave it be.
Case Otherwise$
Error_Services('Add', 'Invalid RunDataLayer [' : OriginalRunDataLayer : '].')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
End Case
end
Response = RunDataLayer
end service
Service FormatZoneKeyID(RunDataZone)
If RunDataZone NE '' then
CONVERT @LOWER_CASE TO @UPPER_CASE IN RunDataZone
Convert 'ABCDEFGHIJKLMNOPQRSTUVWXYZ- ' to '' in RunDataZone
End
Response = RunDataZone
end service
Service GetCalculatedZone(RDSKeyID, IsEpiPro, RunDataZone)
CalculatedZone = ''
If (RDSKeyID NE '') AND (IsEpiPro NE '') then
If IsEpiPro then
// This is Epi Pro, so there must be a zone. If a zone is specified then verify that there are RDS_TEST Keys
// IDs associated with this zone. If so, then accept the zone. If not, check to see if the other zone has
// RDS_TEST Key IDs. If so, then use that zone. If neither zone has RDS_TEST Key IDs then set an error. If
// no zone is specified then verify if one and only one zone has RDS_TEST KeyIDS. If so, use the zone found.
// If not, set an error.
// Check for the RDS_TEST Key IDS for both zones first.
Zone2RDSTestKeyIDs = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS_Z2', 'X')
Zone1RDSTestKeyIDs = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS_Z1', 'X')
// Run the tests.
Begin Case
Case (RunDataZone EQ 1) AND (Zone1RDSTestKeyIDs NE '')
CalculatedZone = 1
Case (RunDataZone EQ 2) AND (Zone2RDSTestKeyIDs NE '')
CalculatedZone = 2
Case (Zone1RDSTestKeyIDs NE '') AND (Zone2RDSTestKeyIDs EQ '')
CalculatedZone = 1
Case (Zone1RDSTestKeyIDs EQ '') AND (Zone2RDSTestKeyIDs NE '')
CalculatedZone = 2
Case (Zone1RDSTestKeyIDs EQ '') AND (Zone2RDSTestKeyIDs EQ '')
Error_Services('Add', 'RDS Key ID [' : RDSKeyID : '] has no RDS_TEST Keys for any zone.')
Case (Zone1RDSTestKeyIDs NE '') AND (Zone2RDSTestKeyIDs NE '')
Error_Services('Add', 'No zone was entered for RDS Key ID [' : RDSKeyID : '] and both zones have RDS_TEST Keys.')
End Case
end else
// Non-Epi Pro, so there is never a Zone.
CalculatedZone = ''
end
end else
Error_Services('Add', 'The RDSKeyID or IsEpiPro argument is missing.')
end
Response = CalculatedZone
end service
Service GetCalculatedLayer(RDSKeyID, Machine, RunDataZone, WoIsEpiPro, ScannedLayer)
CalculatedLayer = ''
If (RDSKeyID NE '') AND (Machine NE '') then
Begin Case
Case RunDataZone EQ 2
RDSTestKeyIDs = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS_Z2', 'X')
Case RunDataZone EQ 1
RDSTestKeyIDs = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS_Z1', 'X')
Case RunDataZone EQ ''
RDSTestKeyIDs = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS', 'X')
Case Otherwise$
Error_Services('Add', 'Unrecognized RunDataZone [' : RunDataZone : '] in the [' : Service : '] service.')
End Case
If Error_Services('NoError') then
If RDSTestKeyIDs NE '' then
Begin Case
Case Machine _EQC 'BioRad'
// Just get the last RDS Test Key ID whether there is one or multiple Key IDs.
RDSTestKeyID = RDSTestKeyIDs[-1, 'B' : @VM]
RDSTestRow = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID)
If Error_Services('NoError') then
CalculatedLayer = RDSTestRow<RDS_TEST_LS_ID$>
end
If Dcount(RDSTestKeyIDs, @VM) GT 1 and WoIsEpiPro = False$ and (ScannedLayer _EQC 'L1' or ScannedLayer _EQC 'L2' or ScannedLayer _EQC '2') then
CalculatedLayer = ScannedLayer
end
Case Machine _EQC 'CDE'
ToolClassID = Metrology_Services('GetToolClassID', Machine)
ToolMatch = False$ ; // Assume no match for now.
For Each RDSTestKeyID in RDSTestKeyIDs using @VM
RDSTestRow = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID)
If Error_Services('NoError') then
If RDSTestRow<RDS_TEST_SPEC_RES_MTOOL$> _EQC ToolClassID then ToolMatch = True$
end
Until ToolMatch
Next RDSTestKeyID
If ToolMatch then
CalculatedLayer = RDSTestRow<RDS_TEST_LS_ID$>
end else
Error_Services('Add', 'Unable to find the RDS Key ID [' : RDSKeyID : '] layer that has this Tool Class ID [' : ToolClassID : '].')
end
Case Machine _EQC 'HgCV'
ToolClassID = Metrology_Services('GetToolClassID', Machine)
ToolMatch = False$ ; // Assume no match for now.
For Each RDSTestKeyID in RDSTestKeyIDs using @VM
RDSTestRow = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID)
If Error_Services('NoError') then
If (RDSTestRow<RDS_TEST_SPEC_RES_MTOOL$> _EQC ToolClassID) OR (RDSTestRow<RDS_TEST_SPEC_CRES_MTOOL$> _EQC ToolClassID) then ToolMatch = True$
end
Until ToolMatch
Next RDSTestKeyID
If ToolMatch then
CalculatedLayer = RDSTestRow<RDS_TEST_LS_ID$>
end else
Error_Services('Add', 'Unable to find the RDS Key ID [' : RDSKeyID : '] layer that has this Tool Class ID [' : ToolClassID : '].')
end
End Case
end else
Error_Services('Add', 'No RDS Test Key IDS were found for RDS Key ID [' : RDSKeyID : '] and Zone Key ID [' : RunDataZone : '] in the [' : Service : '] service.')
end
end
end else
Error_Services('Add', 'The RDSKeyID or Machine argument is missing in the [' : Service : '] service.')
end
Response = CalculatedLayer
end service
Service GetToolClassID(Machine)
ToolClassID = ''
If Machine NE '' then
Begin Case
Case Machine _EQC 'BioRad' ; ToolClassID = 'FTIR'
Case Machine _EQC 'CDE' ; ToolClassID = '4PP'
Case Machine _EQC 'HgCV' ; ToolClassID = 'HGCV'
Case Otherwise$ ; Error_Services('Add', 'Unrecognized Machine in the [' : Service : '] service.')
End Case
end else
Error_Services('Add', 'The Machine argument is missing in the [' : Service : '] service.')
end
Response = ToolClassID
end service
Service LogResults(RDSKeyID, Machine, UID, Results)
Begin Case
Case Machine _EQC 'Tencor'
LogHandle = objTencorLog
Case Machine _EQC 'HgCV'
LogHandle = objHgCVLog
Case Machine _EQC 'CDE'
LogHandle = objCDELog
Case Machine _EQC 'Biorad'
LogHandle = objBioradLog
Case Machine _EQC 'Stratus'
LogHandle = objStratusLog
Case Machine _EQC 'SP1'
LogHandle = objSP1Log
Case Machine _EQC 'SPV'
LogHandle = objSPVLog
Case Machine _EQC 'SRP'
LogHandle = objSRPLog
Case Otherwise$
LogHandle = objLog
End Case
LogData = ''
LogData<1> = LoggingDTM
LogData<2> = RDSKeyID ; // RDS Key ID
LogData<3> = Machine ; // Type (i.e., Machine)
LogData<4> = UID ; // UID000=Success, UID001=Failure
LogData<5> = Results
Logging_Services('AppendLog', LogHandle, LogData, @RM, @FM)
If UID NE 'UID000' then
Error_Services('Add', UID:' ':Results)
end
end service
//----------------------------------------------------------------------------------------------------------------------
// RemoveOldMetrology
//
// Runs a database query logic to find pre-existing metrology data already linked to future RDS Key IDs and then removes
// them from the database to avoid appearing in RDS rows when they get created. This service will only remove metrology
// data related to 300 future RDS Key IDs at a time. This allow the system to always stay ahead of the operators but
// leaves as much history as possible in the system for as long as possible.
//----------------------------------------------------------------------------------------------------------------------
Service RemoveOldMetrology()
NextRDSNo = Database_Services('ReadDataRow', 'DICT.RDS', '%SK%')
If Error_Services('NoError') then
Flag = ''
LastRDSNo = NextRDSNo + 600
// Remove old RDS_LAYER records
Open 'RDS_LAYER' to RLHandle then
For CurrRDS = NextRDSNo to LastRDSNo
Delete RLHandle, CurrRDS:'*L1' else Null
Delete RLHandle, CurrRDS:'*L2' else Null
Delete RLHandle, CurrRDS:'*2' else Null
Next CurrRDS
end
hDictRDSTest = Database_Services('GetTableHandle', 'DICT.RDS_TEST')
SearchString = 'RDS_NO' : @VM : (NextRDSNo - 1) : '~' : (LastRDSNo + 1) : @FM
Btree.Extract(SearchString, 'RDS_TEST', hDictRDSTest, RDSTestKeyIDs, '', Flag)
If Flag EQ 0 then
If RDSTestKeyIDs NE '' then
LogPath = Environment_Services('GetApplicationRootPath') : '\LogFiles\RemoveMetrology'
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : '.log'
Headers = 'Logging DTM' : @FM : 'Results'
ColumnWidths = 20 : @FM : 50
objLog2 = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, ' ', Headers, ColumnWidths, False$, False$)
For Each RDSTestKeyID in RDSTestKeyIDs using @VM
If Len(RDSTestKeyID) LT 7 then
Database_Services('DeleteDataRow', 'RDS_TEST', RDSTestKeyID, True$)
If Error_Services('NoError') then
Results = RDSTestKeyID : ' deleted'
end else
Results = RDSTestKeyID : ' not deleted. Error : ' : Error_Services('GetMessage')
end
LogData = ''
LogData<1> = LoggingDTM
LogData<2> = Results
Logging_Services('AppendLog', objLog2, LogData, @RM, @FM)
end
Next RDSTestKeyID
end
end
end
end service
/*
ImportBioRadEPPFQAData
Modified and annotated by JRO 4/18/2025
Written to import data specifically for EpiPro QA/FQA Thickness.
Note: Due to the nature of the forms used to validate EpiPro FQA, we currently only support single value data import.
If we wish to extend this to multi value data import than we need to write the values with Sub Value Marks at the Value Mark position of the test profile.
Then we also need to modify the form usage to support parsing those multi-values.
I am leaving the iterating methods in here with the possibility that we can extend this to support multi valued data should the business need it.
This service requires the following parameters:
1. RunData, The internal delimited run data from the parent calling routine ImportBioRadData.
The service does the following actions:
1. Parses through the run data to extract the WMO Lot id, reactor id, recipe id, and slot id.
2. Writes the data point to the slot position in the WM_OUT record - > MU_WAFER_THK_RESULT field, for all wafers whether they are MU wafers or not.
This allows us to detect that Any and all MU wafers have thickness data.
3. Reads the WO_MAT_QA record.
4. Iterates through WO_MAT_QA record -> PROFILE fields @VM values, IF the profile is 1ADE, or QA, or MO_QA then
5. Determines the spec slot by reading the WO_MAT_QA record -> SLOT field
6. If the detected RunDataSlot matches the spec slot, writes the datapoint to the WO_MAT_QA record - > RESULT field in the same @VM that the iterator is currently set to.
*/
Service ImportBioRadEPPFQAData(RunData, FileName)
Timestamp = RunData<2>
WMOKeyID = RunData<6>
Swap '.' with '*' in WMOKeyID
Swap 'o' with '' in WMOKeyID
Swap 'O' with '' in WMOKeyID
RunDataLayer = RunData<8>
ReactorID = RunData<5>
ScanRecipe = RunData<4>
DataSlotId = RunData<11>
DataSlotId = SRP_Trim(DataSlotId, 'F', 0)
FieldPos = 13
FieldPosIncrement = 2
Offset = 1
Decimals = 2
Positions = ''
DataPoints = ''
WMORec = Database_Services('ReadDataRow', 'WM_OUT', WMOKeyID)
WOMatQAKey = Field(WMOKeyID, '*', 1) : '*' : Field(WMOKeyID, '*', 3)
Loop
Position = Trim(RunData<FieldPos>)
DataPoint = Trim(RunData<FieldPos + Offset>)
Until Position EQ ''
Position = SRP_Trim(Position, 'F', '0')
Positions := Position : @VM
DataPoints := DataPoint : @VM
FieldPos += FieldPosIncrement
Repeat
Positions[-1, 1] = '' ; // Strip final @VM
DataPoints[-1, 1] = '' ; // Strip final @VM
If Error_Services('NoError') then
If Error_Services('HasError') then
Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
//Update the WM_OUT record for EpiPro
WMORec<WM_OUT_MU_WAFER_THK_RESULT$, DataSlotId> = DataPoints
Database_Services('WriteDataRow', 'WM_OUT', WMOKeyID, WMORec, True$, False$, True$)
end else
Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
// Update WO_MAT_QA record
WOMatQAID = Field(WMOKeyID, '*', 1) : '*' : Field(WMOKeyID, '*', 3)
WOMatQARec = Database_Services('ReadDataRow', 'WO_MAT_QA', WOMatQAID)
If Error_Services('NoError') then
SpecRecipes = WOMatQARec<WO_MAT_QA_RECIPE$>
ProfSteps = ''
Profiles = WOMatQARec<WO_MAT_QA_PROFILE$>
ProfileCnt = DCount(WOMatQARec<WO_MAT_QA_PROFILE$>, @VM)
Stages = WOMatQARec<WO_MAT_QA_STAGE$>
Slots = WOMatQARec<WO_MAT_QA_SLOT$>
pPos = ''
For each Profile in Profiles using @VM setting pPos
Stage = Stages<0, pPos>
If ( (Profile EQ '1ADE') and ( (Stage EQ 'QA') or (Stage EQ 'MO_QA') ) ) then
Slot = Slots<0, pPos>
//There is a specific scenario where when the spec wafers are 1 and L, and there is a makeup wafer in each of the first and last slots, they would then want to
//measure the next open slot, either incremented down from the last wafer position or incremented up from the first wafer position.
//We may need to add that in when the definition becomes clearer.
Begin Case
Case Slot EQ '1'
For WaferIndex = 1 to 25
SlotIsEmpty = Database_Services('ReadDataColumn', 'WM_OUT', WMOKeyID, WM_OUT_RDS$, True$, 0, False$)<1, WaferIndex> EQ ''
Until SlotIsEmpty EQ False$
Next WaferIndex
Case Slot EQ 'L'
For WaferIndex = 25 to 1 Step -1
SlotIsEmpty = Database_Services('ReadDataColumn', 'WM_OUT', WMOKeyID, WM_OUT_RDS$, True$, 0, False$)<1, WaferIndex> EQ ''
Until SlotIsEmpty EQ False$
Next WaferIndex
Case Slot EQ 'A'
WaferIndex = 1
Case Otherwise$
WaferIndex = Slot
End Case
If DataSlotId EQ WaferIndex then
WOMatQARec<WO_MAT_QA_RESULT$, pPos> = DataPoints
end
end
Next Profile
Database_Services('WriteDataRow', 'WO_MAT_QA', WOMatQAID, WOMatQARec, True$, False$, True$)
If Error_Services('NoError') then
Metrology_Services('LogResults', RDSNo@, Machine, 'UID000', Service : ' : Success.')
end else
Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end service
//----------------------------------------------------------------------------------------------------------------------
// CleanRepository
//
// Input: NumDays - The number of days which determines whether or not a file will be deleted.
//
// Removes rundata files from the Run Data Repository folder that are more than the desired number of days old.
//----------------------------------------------------------------------------------------------------------------------
Service CleanRepository(NumDays)
RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\'
InitDir RepoPath:'*.txt'
FileList = DirList()
Today = Date()
For each Filename in FileList
FileInfo = Dir(RepoPath:Filename)
LastWriteDate = FileInfo<2>
FileAge = Today - LastWriteDate
If FileAge GT NumDays then
OSDelete RepoPath:Filename
end
Next Filename
end service
//----------------------------------------------------------------------------------------------------------------------
// MonitorQueue
//
// Input: NumFiles - The number of files at which manual data entry for ROTR will be allowed by all users.
//
// Monitors the Metrology run data import queue. If the number of files exceed the NumFiles variable, then
// the restriction of manual data entry of ROTR data by LEADS, SUPERVISORS, ENGINEERING, and FINAL_QA will be lifted.
// Once the queue drops back down below the NumFiles variable, then the restriction will be put back into place.
//----------------------------------------------------------------------------------------------------------------------
Service MonitorQueue(NumFiles)
RunDataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Data\'
InitDir RunDataPath:'*.txt'
FileList = DirList()
CurrFileCount = DCount(FileList, @FM)
If CurrFileCount LT NumFiles then
// Enable manual entry restriction - check current status to avoid unnecessary writes.
CurrLockStatus = Database_Services('ReadDataRow', 'APP_INFO', 'ROTR_DATA_ENTRY_LOCK')
If CurrLockStatus EQ False$ then
Database_Services('WriteDataRow', 'APP_INFO', 'ROTR_DATA_ENTRY_LOCK', True$)
end
end else
// Lift manual entry restriction - check current status to avoid unnecessary writes.
CurrLockStatus = Database_Services('ReadDataRow', 'APP_INFO', 'ROTR_DATA_ENTRY_LOCK')
If CurrLockStatus EQ True$ then
Database_Services('WriteDataRow', 'APP_INFO', 'ROTR_DATA_ENTRY_LOCK', False$)
end
end
end service
Service GetIQSViolations()
hSysLists = Database_Services('GetTableHandle', 'SYSLISTS')
Lock hSysLists, ServiceKeyID then
Response = 0
ParsedIQSData = ''
Pass = 1
URL = "https://messa04ec.infineon.com/product-thick-and-res-health-reduced-web.html"
TimeoutDuration = HTTPClient_Services('GetTimeoutDuration')
If TimeoutDuration NE 30 then Httpclient_Services('SetTimeoutDuration', 30)
IQSResponse = Httpclient_Services('SendHTTPRequest', 'GET', URL, '', '', '', '', '', '', '')
swap CRLF$ with '' in IQSResponse
PublishDateStart = Indexc(IQSResponse, 'Published: ', 1) + 11
PublishDateCnt = Indexc(IQSResponse, '</p>', 1) - PublishDateStart
PublishDate = IQSResponse[PublishDateStart, PublishDateCnt]
swap ' AM' with 'AM' in PublishDate
swap ' PM' with 'PM' in PublishDate
ConvDateTime = IConv(PublishDate, 'DT')
DataTableBegin = Indexc(IQSResponse, '<table', 1)
DataTableEnd = Indexc(IQSResponse, '</table>', 1) + 7
DataTable = IQSResponse[DataTableBegin, DataTableEnd]
swap '<table' with @RM in DataTable
swap '<tr>' with @FM in DataTable
swap '<th' with @VM in DataTable
swap '</td>' with '' in DataTable
swap '</tr>' with '' in DataTable
DataTable = Delete(DataTable, 1,0,0)
DataTable = Delete(DataTable, 1,0,0)
FilteredData = ''
for i = 1 to DCount(DataTable, @FM)
if Indexc(DataTable<i>, '<td nowrap align=left bgcolor="#0000FF">', 1) then
FilteredData<-1> = DataTable<i>
end
Next i
swap '<td nowrap align=left bgcolor="#0000FF">' with @VM in FilteredData
for i = 1 to DCount(FilteredData, @FM)
//Delete blank empty value
FilteredData = Delete(FilteredData, i,1,0)
//Delete useuless Entire Database column
FilteredData = Delete(FilteredData, i,1,0)
ParsedIQSData<IQS_VIOL_DATA.REACTOR$, i> = FilteredData<i, IQS_VIOL_DATA.REACTOR$>
ParsedIQSData<IQS_VIOL_DATA.TEST$, i> = FilteredData<i, IQS_VIOL_DATA.TEST$>
ParsedIQSData<IQS_VIOL_DATA.USL$, i> = FilteredData<i, IQS_VIOL_DATA.USL$>
ParsedIQSData<IQS_VIOL_DATA.TAR$, i> = FilteredData<i, IQS_VIOL_DATA.TAR$>
ParsedIQSData<IQS_VIOL_DATA.LSL$, i> = FilteredData<i, IQS_VIOL_DATA.LSL$>
ParsedIQSData<IQS_VIOL_DATA.LAST_ENTRY$, i> = FilteredData<i, IQS_VIOL_DATA.LAST_ENTRY$>
ParsedIQSData<IQS_VIOL_DATA.LAST_ALARM$, i> = FilteredData<i, IQS_VIOL_DATA.LAST_ALARM$>
swap '&gt;' with '>' in FilteredData
swap '&lt;' with '<' in FilteredData
ParsedIQSData<IQS_VIOL_DATA.ALARM$, i> = FilteredData<i, IQS_VIOL_DATA.ALARM$>
ParsedIQSData<IQS_VIOL_DATA.SUBGROUPS$, i> = FilteredData<i, IQS_VIOL_DATA.SUBGROUPS$>
ParsedIQSData<IQS_VIOL_DATA.MEAN$, i> = FilteredData<i, IQS_VIOL_DATA.MEAN$>
Next i
LastWriteTime = IConv(PublishDate, 'DT')
ParsedIQSData<IQS_VIOL_DATA.LAST_WRITE_TIME$> = LastWriteTime
Database_Services('WriteDataRow', 'CONFIG', 'IQS_VIOL_DATA', ParsedIQSData, True$, False$, True$)
//Send status of last write time
If SRP_Datetime('MinuteSpan', LastWriteTime, SRP_Datetime('Now')) GT 10 then
Mona_Services('SendBufferedStatus', 'IQS_VIOLATION_DATA', 'LastDataTimestamp', 'CRITICAL')
end else
Mona_Services('SendBufferedStatus', 'IQS_VIOLATION_DATA', 'LastDataTimestamp', 'OK')
end
Reactor_Services('UpdateReactorIQSViolations')
Unlock hSysLists, ServiceKeyID else Null
end
end service
Service ScanRecipeMatchesRdsTestSpecThickMrecipe(RdsNo, RecipeLayer, RecipeInScan, RdsTestKeysFromRdsLayer, Zone='')
If Unassigned(RecipeLayer) or RecipeLayer EQ '' then
RecipeLayer = 'L1'
end
MatchFound = False$
ValidArgs = Assigned(RdsNo) and RdsNo NE '' and Assigned(RecipeInScan) and RecipeInScan NE ''
ValidArgs = ValidArgs and Assigned(RdsTestKeysFromRdsLayer) and Dcount(RdsTestKeysFromRdsLayer, @VM) GT 0
If ValidArgs then
For Each RDSTestKeyID in RdsTestKeysFromRdsLayer using @VM setting mkPos
RdsTestRec = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID)
If Error_Services('NoError') then
RdsTestRdsNo = RdsTestRec<RDS_TEST_RDS_NO$>
If RdsNo EQ RdsTestRdsNo then
RdsTestRecLayer = RdsTestRec<RDS_TEST_LS_ID$>
If RecipeLayer EQ RdsTestRecLayer then
Continue = True$
If Zone NE '' then
RdsTestRecZone = RdsTestRec<RDS_TEST_ZONE$>
If RdsTestRecZone NE Zone then
Continue = False$
end
end
If Continue then
SpecThickMrecipe = RdsTestRec<RDS_TEST_SPEC_THICK_MRECIPE$>
If RecipeInScan _EQC SpecThickMrecipe then
MatchFound = True$
end
end
end
end
end
Until MatchFound
Next RDSTestKeyID
end
Response = MatchFound
end service
Service ScanNumDataPointsMatchesRdsTestSpecThickMPattern(RdsNo, RecipeLayer, ScanNumDataPoints, RdsTestKeysFromRdsLayer, ToolClass, Zone='')
If Unassigned(RecipeLayer) or RecipeLayer EQ '' then
RecipeLayer = 'L1'
end
MatchFound = False$
ValidArgs = Assigned(RdsNo) and RdsNo NE '' and Assigned(ScanNumDataPoints) and Num(ScanNumDataPoints)
ValidArgs = ValidArgs and Assigned(RdsTestKeysFromRdsLayer) and Dcount(RdsTestKeysFromRdsLayer, @VM) GT 0
ValidArgs = ValidArgs and Assigned(ToolClass) and ToolClass NE ''
If ValidArgs then
For Each RDSTestKeyID in RdsTestKeysFromRdsLayer using @VM setting mkPos
RdsTestRec = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID)
If Error_Services('NoError') then
RdsTestRdsNo = RdsTestRec<RDS_TEST_RDS_NO$>
If RdsNo EQ RdsTestRdsNo then
RdsTestRecLayer = RdsTestRec<RDS_TEST_LS_ID$>
If RecipeLayer EQ RdsTestRecLayer then
Continue = True$
If Zone NE '' then
RdsTestRecZone = RdsTestRec<RDS_TEST_ZONE$>
If RdsTestRecZone NE Zone then
Continue = False$
end
end
If Continue then
SpecPatternName = RdsTestRec<RDS_TEST_SPEC_THICK_MPATTERN$>
SpecNumDataPoints = Tool_Class_Services('GetNumberOfPointsForPattern', ToolClass, SpecPatternName)
If ScanNumDataPoints EQ SpecNumDataPoints then
MatchFound = True$
end
end
end
end
end
Until MatchFound
Next RDSTestKeyID
end
Response = MatchFound
end service
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Internal GoSubs
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ClearCursors:
For Cursor = 1 to 8
ClearSelect Cursor
Next Cursor
return
CalculateQAData:
// Find the Min, Max, Average, Edge Mean Delta, and Range % values
Sum = 0
Min = DataPoints<0,1>
Max = DataPoints<0,1>
Delta1 = 0
Delta2 = 0
RangeMin = DataPoints<0,1>
RangeMax = DataPoints<0,1>
RangeAvg = 0
RangePoints = '1,2,5,6,9'
For each DataPoint in DataPoints using @VM setting Index
Sum = Sum + DataPoint
// Min & Max
Min = Min(Min, DataPoint)
Max = Max(Max, DataPoint)
// Edge Mean Delta
If Index GE 6 AND Index LE 9 then
Delta1 = Delta1 + DataPoint
end
If Index GE 2 AND Index LE 5 then
Delta2 = Delta2 + DataPoint
end
// Range %
Locate Index in RangePoints using ',' setting unusedIndex then
RangeMin = Min(RangeMin, DataPoint)
RangeMax = Max(RangeMax, DataPoint)
RangeAvg = RangeAvg + DataPoint
end
Next DataPoint
// Edge Mean Delta
EdgeMeanDelta = ''
If ( (Delta1 NE 0) and (Delta2 NE 0) ) then
Delta1Avg = Delta1/4
Delta2Avg = Delta2/4
EdgeMeanDelta = ( (Delta1Avg - Delta2Avg) / Delta1Avg) * 100
end
// Range %
Range = RangeMax - RangeMin
NumRangePoints = DCount(RangePoints, ',')
RangeAvg = RangeAvg / NumRangePoints
RangePct = (Range / RangeAvg) * 100
// Average
NumDataPoints = DCount(DataPoints, @VM)
Average = Sum / NumDataPoints
return
CalculateResults:
If IsEpiPro then
// Using logic adapted from COMM_DIALOG_EPI_PRO_MET > Done event handler.
MetRec = RDSTestRec
TestPointMap = BaseMapID
MetNo = RDSTestKeyID
RDSNo = MetRec<RDS_TEST_RDS_NO$>
LSId = MetRec<RDS_TEST_LS_ID$>
Zone = MetRec<RDS_TEST_ZONE$>
TestPointMap = MetRec<RDS_TEST_TEST_POINT_MAP$>
IF Zone = '' THEN MetKeys = XLATE('REACT_RUN',RDSNo,'MET_KEYS','X')
IF Zone = 1 THEN MetKeys = XLATE('REACT_RUN',RDSNo,'MET_KEYS_Z1','X')
IF Zone = 2 THEN MetKeys = XLATE('REACT_RUN',RDSNo,'MET_KEYS_Z2','X')
RdsLSKeys = XLATE('RDS',RDSNo,RDS_RDS_LAYER_KEYS$,'X')
ReactorNo = XLATE('REACT_RUN',RDSNo,REACT_RUN_REACTOR$,'X')
ReactType = XLATE('REACTOR',ReactorNo,REACTOR_REACT_TYPE$,'X')
Continue = True$ ; // Assue we will continue for now.
IF INDEX(RdsLSKeys,@VM,1) THEN
* Multiple layers
DepTimeTargets = ''
LSCnt = COUNT(RdsLSKeys,@VM) + (RdsLSKeys NE '')
DepTimeTargets = XLATE('RDS_LAYER',RdsLSKeys,RDS_LAYER_EPI_TIME$,'X')
DepTimeTargets = OCONV(DepTimeTargets,'MD1')
LS1DepTime = ''
LS2DepTime = ''
FOR I = 1 TO LSCnt
RdsLSKey = RdsLSKeys<1,I>
IF INDEX(RdsLSKey,'L1',1) THEN LS1DepTime = DepTimeTargets<1,I>
IF INDEX(RdsLSKey,'L2',1) THEN LS2DepTime = DepTimeTargets<1,I>
NEXT I
IF LS1DepTime NE '' AND LS2DepTime NE '' THEN
TotDepTime = LS1DepTime + LS2DepTime
LS1Ratio = LS1DepTime/TotDepTime
MetReadings = ''
FOR I = 1 TO COUNT(MetKeys,@VM) + (MetKeys NE '')
MetKey = MetKeys<1,I>
LMetReadings = obj_RDS_Test('GetReadSet',MetKey)
IF I = 1 THEN
MetReadings<1> = LMetReadings<1> ;* Line Numbers
MetReadings<3> = LMetReadings<3> ;* SheetRho
MetReadings<4> = LMetReadings<4> ;* Hgcv
END
IF I = 2 THEN
MetReadings<5> = LMetReadings<4> ;* Hgcv
END
IF I = 3 THEN
MetReadings<2> = LMetReadings<2> ;* Thickness readings
END
NEXT I
end else
Continue = False$
end
END ELSE
LS1Ratio = 1
MetReadings = obj_RDS_Test('GetReadSet',MetNo)
END
if Continue then
CONVERT @FM TO @RM IN MetReadings
oTPM_Parms = TestPointMap:@RM:MetReadings
Results = obj_Test_Point_Map('PointToResult',oTPM_Parms)
ThicknessArray = FIELD(Results,@FM,2,4)
ThickReads = ThicknessArray<1>
SheetRhoReads = ThicknessArray<2>
HgCV1ResReads = ThicknessArray<3>
HgCV2ResReads = ThicknessArray<4>
L1Ratio = LS1Ratio
ReadingNos = ''
FOR I = 1 TO 9
ReadingNos<1,I> = I
NEXT I
oTPM_Parms = TestPointMap:@RM:ReadingNos:@RM:ThickReads:@RM:SheetRhoReads:@RM:HgCV1ResReads:@RM:HgCV2ResReads
EpiReads = obj_Test_Point_Map('ResultToPoint',oTPM_Parms)
BEGIN CASE
CASE Zone = '1' ; MetKeys = XLATE('REACT_RUN',RDSNo,'MET_KEYS_Z1','X')
CASE Zone = '2' ; MetKeys = XLATE('REACT_RUN',RDSNo,'MET_KEYS_Z2','X')
CASE 1 ; MetKeys = XLATE('REACT_RUN',RDSNo,'MET_KEYS','X')
END CASE
IF INDEX(MetKeys,@VM,1) THEN
// MetNo might be from a layer other than the last one. Get the last layer and assign it to the MetNo
// variable.
MetNo = MetKeys[-1, 'B' : @VM]
LOCATE MetNo IN MetKeys USING @VM SETTING Pos THEN
MetKeys = DELETE(MetKeys,1,Pos,0)
END
oRTParms_L1 = ''
oRTParms_L2 = ''
FOR J = 1 TO COUNT(EpiReads<1>,@VM) + (EpiReads<1> NE '')
oRTParms_L1<ORP$THICK_READS,J> = OCONV(ICONV(EpiReads<EPI_READS$THICKNESS,J> * L1Ratio, 'MD2'),'MD2')
oRTParms_L2<ORP$THICK_READS,J> = EpiReads<EPI_READS$THICKNESS,J> - oRTParms_L1<ORP$THICK_READS,J>
IF EpiReads<EPI_READS$SHEET_RHO,J> NE '' THEN
oRTParms_L1<ORP$SHEET_RHO_READS,J> = EpiReads<EPI_READS$SHEET_RHO,J>
oRTParms_L2<ORP$SHEET_RHO_READS,J> = ''
EpiReads<EPI_READS$SHEET_RHO,J> = ''
END ELSE
oRTParms_L1<ORP$SHEET_RHO_READS,J> = ''
oRTParms_L2<ORP$SHEET_RHO_READS,J> = ''
END
IF EpiReads<EPI_READS$HGCV1,J> NE '' THEN
oRTParms_L1<ORP$HGCV1_READS,J> = EpiReads<EPI_READS$HGCV1,J>
END
IF EpiReads<EPI_READS$HGCV2,J> NE '' THEN
oRTParms_L2<ORP$HGCV1_READS,J> = EpiReads<EPI_READS$HGCV2,J>
END
NEXT J
CONVERT @FM TO @RM IN oRTParms_L1
CONVERT @FM TO @RM IN oRTParms_L2
obj_RDS_Test('SetReadSet',MetKeys<1,1>:@RM:oRTParms_L1)
obj_RDS_Test('SetReadSet',MetKeys<1,2>:@RM:oRTParms_L2)
END
oRTParms = ''
oRTParms<ORP$THICK_READS> = EpiReads<EPI_READS$THICKNESS>
oRTParms<ORP$SHEET_RHO_READS> = EpiReads<EPI_READS$SHEET_RHO>
oRTParms<ORP$HGCV1_READS> = EpiReads<EPI_READS$HGCV1>
CONVERT @FM TO @RM IN oRTParms
obj_RDS_Test('SetReadSet',MetNo:@RM:oRTParms)
end
end else
// Non-Epi Pro calculations. Using logic from adapted from COMM_RDS_TEST > ReadingsPC method.
Readings = RDSTestRec<DataIndex>
Conversion = 'MD' : Decimals
Readings = Oconv(Readings, Conversion)
rv = Set_Status(0)
Stats = obj_RDS_Test('CalcStats', Readings : @RM : Conversion)
ErrorCode = ''
If Get_Status(ErrorCode) EQ 0 then
Convert @RM to @FM in Stats
Stats<1> = Iconv(Stats<1>, Conversion)
Stats<2> = Iconv(Stats<2>, 'MD4')
Stats<3> = Iconv(Stats<3>, 'MD2')
Stats<4> = Iconv(Stats<4>, Conversion)
Stats<5> = Iconv(Stats<5>, Conversion)
Stats<6> = Iconv(Stats<6>, Conversion)
Stats<7> = Iconv(Stats<7>, 'MD4')
Begin Case
Case Machine _EQC 'BioRad'
RDSTestRec<RDS_TEST_THICK_AVG$> = Stats<1>
RDSTestRec<RDS_TEST_THICK_STDV$> = Stats<2>
RDSTestRec<RDS_TEST_THICK_RANGE$> = Stats<6>
RDSTestRec<RDS_TEST_THICK_RANGE_PCNT$> = Stats<7>
RDSTestRec<RDS_TEST_THICK_UNIF$> = Stats<3>
RDSTestRec<RDS_TEST_THICK_MIN$> = Stats<4>
RDSTestRec<RDS_TEST_THICK_MAX$> = Stats<5>
Case Machine _EQC 'CDE'
RDSTestRec<RDS_TEST_SHEETRHO_AVG$> = Stats<1>
RDSTestRec<RDS_TEST_SHEETRHO_STDV$> = Stats<2>
RDSTestRec<RDS_TEST_SHEETRHO_RANGE$> = Stats<6>
RDSTestRec<RDS_TEST_SHEETRHO_RANGE_PCNT$> = Stats<7>
RDSTestRec<RDS_TEST_SHEETRHO_UNIF$> = Stats<3>
RDSTestRec<RDS_TEST_SHEETRHO_MIN$> = Stats<4>
RDSTestRec<RDS_TEST_SHEETRHO_MAX$> = Stats<5>
Case Machine _EQC 'HgCV'
RDSTestRec<RDS_TEST_HGCV1_RES_AVG$> = Stats<1>
RDSTestRec<RDS_TEST_HGCV1_RES_STDV$> = Stats<2>
RDSTestRec<RDS_TEST_HGCV1_RES_RANGE$> = Stats<6>
RDSTestRec<RDS_TEST_HGCV1_RES_RANGE_PCNT$> = Stats<7>
RDSTestRec<RDS_TEST_HGCV1_RES_UNIF$> = Stats<3>
RDSTestRec<RDS_TEST_HGCV1_RES_MIN$> = Stats<4>
RDSTestRec<RDS_TEST_HGCV1_RES_MAX$> = Stats<5>
End Case
//Log returned Data for the specific tool:
Metrology_Services('LogResults', RDSKeyID, Machine, '', Service : ' : Stats for ': Machine :' : ' : Stats)
Readings = obj_RDS_Test('Resistivity', RDSTestKeyID : @RM : RDSTestRec : @RM : 1)
Metrology_Services('LogResults', RDSKeyID, Machine, '', Service : ' : Readings: ' : Readings)
Conversion = 'MD4'
Readings = Oconv(Readings, Conversion)
rv = Set_Status(0)
Stats = obj_RDS_Test('CalcStats', Readings : @RM : Conversion)
//JRO Logging
StatsToLog = Stats
swap @RM with @FM in StatsToLog
Metrology_Services('LogResults', RDSKeyID, Machine, '', Service : ' : Stats: ' : StatsToLog)
If Get_Status(ErrorCode) EQ 0 then
Convert @RM to @FM in Stats
Stats<1> = Iconv(Stats<1>, Conversion)
Stats<2> = Iconv(Stats<2>, 'MD4')
Stats<3> = Iconv(Stats<3>, 'MD2')
Stats<4> = Iconv(Stats<4>, Conversion)
Stats<5> = Iconv(Stats<5>, Conversion)
Stats<6> = Iconv(Stats<6>, Conversion)
Stats<7> = Iconv(Stats<7>, 'MD4')
RDSTestRec<RDS_TEST_RES_AVG$> = Stats<1>
RDSTestRec<RDS_TEST_RES_STDV$> = Stats<2>
RDSTestRec<RDS_TEST_RES_RANGE$> = Stats<6>
RDSTestRec<RDS_TEST_RES_RANGE_PCNT$> = Stats<7>
RDSTestRec<RDS_TEST_RES_UNIF$> = Stats<3>
RDSTestRec<RDS_TEST_RES_MIN$> = Stats<4>
RDSTestRec<RDS_TEST_RES_MAX$> = Stats<5>
//JRO Logging
Metrology_Services('LogResults', RDSKeyID, Machine, '', Service : ' : Stats To Write: ' : Stats)
end
Database_Services('WriteDataRow', 'RDS_TEST', RDSTestKeyID, RDSTestRec, True$, False$, True$)
If Error_Services('NoError') then
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Success - Calculated Results.')
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID002', Service : ' : UID002 - ' : Error_Services('GetMessage'))
end
end
end
return
ParseWorkOrder:
If Assigned(objWorkOrder) else objWorkOrder = 0
If objWorkOrder LE 0 then
SRP_JSON(objWorkOrder, 'PARSE', WorkOrder)
end
If objWorkOrder GT 0 then
WorkOrderNo = SRP_JSON(objWorkOrder, 'GETVALUE', 'WorkOrderNumber')
EpiPartNo = SRP_JSON(objWorkOrder, 'GETVALUE', 'EpiPartNumber')
WOReactorType = SRP_JSON(objWorkOrder, 'GETVALUE', 'ReactorType')
PSN = SRP_JSON(objWorkOrder, 'GETVALUE', 'PSN')
Recipe = SRP_JSON(objWorkOrder, 'GETVALUE', 'Recipe')
HotLot = Iconv(SRP_JSON(objWorkOrder, 'GETVALUE', 'HotLot'), 'BYes,No')
Closed = Iconv(SRP_JSON(objWorkOrder, 'GETVALUE', 'Closed'), 'BYes,No')
TotalWafers = SRP_JSON(objWorkOrder, 'GETVALUE', 'TotalWafers')
WafersRemaining = SRP_JSON(objWorkOrder, 'GETVALUE', 'WafersRemaining')
PercentComplete = SRP_JSON(objWorkOrder, 'GETVALUE', 'PercentComplete')
CustNameShort = SRP_JSON(objWorkOrder, 'GETVALUE', 'Company.NameShort')
SRP_JSON(objWorkOrder, 'RELEASE')
end else
EpiPartNo = ''
WOReactorType = ''
PSN = ''
Recipe = ''
HotLot = ''
Closed = ''
TotalWafers = ''
WafersRemaining = ''
PercentComplete = ''
CustNameShort = ''
end
return
LoadRunDataToDatabase:
// Thickness OR Resistivity OR Hg Resistivity Section Read the tool class base map
BaseMapID = RDSTestRec<RDS_TEST_TEST_POINT_MAP$>
Convert @Lower_Case to @Upper_Case in BaseMapID
TestPointMapRec = Database_Services('ReadDataRow', 'TEST_POINT_MAP', BaseMapID)
If Error_Services('NoError') then
BaseMapResults = TestPointMapRec<TEST_POINT_MAP_RESULT_NO$>
BaseMapPoints = TestPointMapRec<TEST_POINT_MAP_POINT_ID$>
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
// Read the metrology tool map
PatternName = Trim(RDSTestRec<PatternNameIndex>)
Convert @Lower_Case to @Upper_Case in PatternName
ToolMapID = RDSTestRec<ToolClassIndex>
Convert @Lower_Case to @Upper_Case in ToolMapID
TestPointMapRec = Database_Services('ReadDataRow', 'TEST_POINT_MAP', ToolMapID)
If Error_Services('NoError') then
ToolMapResults = TestPointMapRec<TEST_POINT_MAP_RESULT_NO$>
ToolMapPoints = TestPointMapRec<TEST_POINT_MAP_POINT_ID$>
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
ToolClassRow = Database_Services('ReadDataRow', 'TOOL_CLASS', ToolClassID)
If Error_Services('NoError') then
PatternNames = ToolClassRow<TOOL_CLASS_PATTERN$>
Convert @Lower_Case to @Upper_Case in PatternNames
Locate PatternName in PatternNames using @VM setting fPos then
PatternSize = ToolClassRow<TOOL_CLASS_PATTERN_SIZE$,fpos>
If (NumDataPoints = PatternSize) then
ExistingData = false$
// Calculate the array offset for the thickness measurements.
If IsEpiPro then
// Perform test point mapping.
VirtualSize = DCount(BaseMapResults, @VM)
end else
VirtualSize = PatternSize
end
Offset = 17 - VirtualSize
If Offset LT 0 then Offset = 0
Offset = Int((Offset / 2) + 0.5)
// Clear the read values before populating with the new ones.
RDSTestRecPrev = RDSTestRec
RDSTestRec<DataIndex> = ''
For ReadingCnt = 1 to NumDataPoints
If IsEpiPro then
// Perform test point mapping.
Locate Positions<0, ReadingCnt> in ToolMapResults using @VM setting tmPos then
ToolMapPoint = ToolMapPoints<0, tmPos>
Locate ToolMapPoint in BaseMapPoints using @VM setting bmPos then
Position = bmPos
end
end
end else
Position = Positions<0, ReadingCnt>
end
DataPoint = DataPoints<0, ReadingCnt>
IF RDSTestRecPrev<DataIndex, Position + Offset> NE '' then
ExistingData = true$
end
RDSTestRec<DataIndex, Position + Offset> = DataPoint
Next ReadingCnt
RDSTestRec<DTMIndex> = Timestamp
// Save results
if Not(ExistingData) OR IsViewerFile then
Database_Services('WriteDataRow', 'RDS_TEST', RDSTestKeyID, RDSTestRec, True$, False$, True$)
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - Data for this metrology test already exists and is not from the metrology viewer.')
end
If Error_Services('NoError') then
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Success.')
GoSub CalculateResults
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - ' : Error_Services('GetMessage'))
end
end else
Error_Services('Add', 'Number of data points does not match the pattern size [' : PatternSize : '].')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Error_Services('Add', 'Unable to locate the pattern name [' : PatternName : '].')
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
end else
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
end
return