Found small bug in HgCV data upload where the variable used in LoadRunDataToDatabase was being nulled out in calling service function.
2975 lines
135 KiB
Plaintext
2975 lines
135 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
|
|
|
|
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
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
// 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'
|
|
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08DDUPSFS6420\Source\MET08DDUPSFS6420\'
|
|
Case Machine _EQC 'HgCV'
|
|
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESIHGCV\Source\MET08RESIHGCV\'
|
|
Case Machine _EQC 'CDE'
|
|
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESIMAPCDE\Source\MET08RESIMAPCDE\'
|
|
Case Machine _EQC 'Biorad'
|
|
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08THFTIRQS408M\Source\MET08THFTIRQS408M\'
|
|
Case Machine _EQC 'Stratus'
|
|
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08THFTIRSTRATUS\Source\MET08THFTIRSTRATUS\'
|
|
Case Machine _EQC 'SP1'
|
|
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08DDUPSP1TBI\Source\MET08DDUPSP1TBI\'
|
|
Case Machine _EQC 'SPV'
|
|
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESISRP2100\Source\MET08RESISRP2100\'
|
|
Case Machine _EQC 'SRP'
|
|
DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08ANLYSDIFAAST230\Source\MET08ANLYSDIFAAST230\'
|
|
Case Otherwise$
|
|
Error_Services('Add', 'Error in ':Service:' service. Unsupported Machine "':Machine:'" passed into service')
|
|
End Case
|
|
|
|
If Error_Services('NoError') then
|
|
|
|
InitDir DataPath:'*.txt'
|
|
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()
|
|
|
|
OSREAD RunData FROM DataPath:FileName THEN
|
|
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
|
|
|
|
Set_Status(0)
|
|
OSWrite RunData to RepoPath:FileName
|
|
status_code = ''
|
|
If Get_Status(status_code) then
|
|
ErrorMessage = 'Error writing run data file to repository: status_code = ' : status_code
|
|
Metrology_Services('LogResults', '', Machine, 'UID001', Service : ' : ' : ErrorMessage)
|
|
Set_Status(0)
|
|
end
|
|
|
|
SWAP '|' WITH @VM IN RunData
|
|
SWAP CRLF$ WITH @FM IN RunData
|
|
|
|
LOOP
|
|
LastChar = RunData[-1,1]
|
|
UNTIL LastChar NE @FM
|
|
RunData[-1,1] = ''
|
|
REPEAT
|
|
|
|
*************************
|
|
* Import metrology data *
|
|
*************************
|
|
|
|
Metrology_Services('ImportMetrologyRunData', RunData, FileName, Machine)
|
|
|
|
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
|
|
|
|
END else
|
|
Metrology_Services('LogResults', '', Machine, 'UID001', 'Read : ' : FileName : ', Size : ' : FileSize)
|
|
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
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
// ImportMetrologyRunData
|
|
//
|
|
// RunData. - [Required]
|
|
//
|
|
// Imports the metrology run data into the RDS_TEST database table. Returns True$ if successful and False$ if
|
|
// unsuccessful.
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
Service ImportMetrologyRunData(RunData, FileName, Machine)
|
|
|
|
If RunData NE '' then
|
|
|
|
Convert Tab$ to @FM in RunData
|
|
// Scan the run data for machine specific information. Then call the relevant update service for the specific
|
|
// machine.
|
|
Begin Case
|
|
Case IndexC(RunData, 'Stratus', 1)
|
|
Metrology_Services('ImportStratusData', RunData)
|
|
MachineType@ = 'Stratus'
|
|
|
|
Case IndexC(RunData, 'Bio-Rad', 1)
|
|
Metrology_Services('ImportBioRadData', RunData, FileName)
|
|
MachineType@ = 'Bio-Rad'
|
|
|
|
Case IndexC(RunData, 'ResMap', 1) OR IndexC(RunData, 'CDE', 1)
|
|
Metrology_Services('ImportCDEData', RunData, FileName)
|
|
MachineType@ = 'CDE'
|
|
|
|
Case IndexC(RunData, 'PROBE', 1) OR IndexC(RunData, 'HgProbe', 1) OR IndexC(RunData, 'HGCV', 1)
|
|
Metrology_Services('ImportHgCVData', RunData, FileName)
|
|
MachineType@ = 'HgProbe'
|
|
|
|
Case IndexC(RunData, 'SP1', 1)
|
|
Metrology_Services('ImportSP1Data', RunData)
|
|
MachineType@ = 'SP1'
|
|
|
|
Case IndexC(RunData, 'TENCOR', 1) OR IndexC(RunData, 'ROTR', 1)
|
|
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 else
|
|
Error_Services('Add', 'RunData argument was missing')
|
|
Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage'))
|
|
end
|
|
|
|
end service
|
|
|
|
|
|
Service ImportStratusData(RunData)
|
|
|
|
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.
|
|
PSN = RunData<9>
|
|
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 = ''
|
|
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'
|
|
WaferIndex = 1
|
|
Case Slot EQ 'L'
|
|
WaferIndex = 25
|
|
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 ImportBioRadData(RunData, FileName)
|
|
Machine = 'BioRad'
|
|
IsProdTest = False$
|
|
ParseArray = ''
|
|
FieldPos = 13
|
|
FieldPosIncrement = 2
|
|
Offset = 1
|
|
Decimals = 2
|
|
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
|
|
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
|
|
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 else
|
|
// RDS Biorad metrology file
|
|
IsViewerFile = Index(FileName, 'Viewer', 1)
|
|
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 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
|
|
|
|
end service
|
|
|
|
|
|
Service ImportCDEData(RunData, FileName)
|
|
|
|
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>
|
|
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
|
|
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 else
|
|
// Regular metrology file
|
|
IsViewerFile = Index(FileName, 'Viewer', 1)
|
|
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
|
|
|
|
end service
|
|
|
|
|
|
Service ImportHgCVData(RunData, FileName)
|
|
|
|
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>
|
|
PSN = RunData<5>
|
|
QualFile = ( (PSN EQ 'Low') or (PSN EQ 'Mid') or (PSN EQ 'High') or (PSN EQ 'Thin') )
|
|
If QualFile then
|
|
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<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 else
|
|
// Regular metrology file
|
|
IsViewerFile = Index(FileName, 'Viewer', 1)
|
|
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
|
|
|
|
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 RDSKeyID is not a number or not 6 digits, then ignore. This means it is an engineering run.
|
|
If Num(RDSKeyID) AND (Len(RDSKeyID) EQ 6) 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
|
|
If Num(RDSKeyID) AND (Len(RDSKeyID) GT 0) then
|
|
ErrorMessage = 'Invalid RDS Key ID.'
|
|
Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : ErrorMessage)
|
|
end
|
|
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 RDSKeyID is not a number or not 6 digits, then ignore. This means it is an engineering run.
|
|
If Num(RDSKeyID) AND (Len(RDSKeyID) EQ 6) 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
|
|
If Num(RDSKeyID) AND (Len(RDSKeyID) GT 0) then
|
|
Error_Services('Add', 'Invalid RDS Key ID.')
|
|
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 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
|
|
|
|
|
|
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>
|
|
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
|
|
for each wafer in Positions using @VM setting dPos
|
|
WMORec<WM_OUT_MU_WAFER_THK_RESULT$, wafer> = DataPoints<0, dPos>
|
|
Next wafer
|
|
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>
|
|
Begin Case
|
|
Case Slot EQ '1'
|
|
WaferIndex = 1
|
|
Case Slot EQ 'L'
|
|
WaferIndex = 25
|
|
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 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://oi-metrology-viewer-prod.mes.infineon.com:4432/product-thick-and-res-health-reduced-web.html"
|
|
URL = "https://messa020ec.infineon.com:4438/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
|
|
|