3279 lines
148 KiB
Plaintext
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 '>' with '>' in FilteredData
|
|
swap '<' 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
|
|
|
|
|
|
|