Function CLEAN_INSP_Actions(Action, CalcColName, FSList, Handle, Name, FMC, Record, Status, OrigRecord, Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9, Param10) /*********************************************************************************************************************** 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 Infineon. Name : CLEAN_INSP_Actions Description : Handles calculated columns and MFS calls for the current table. Notes : This function uses @ID, @RECORD, and @DICT to make sure {ColumnName} references work correctly. If called from outside of a calculated column these will need to be set and restored. Parameters : Action [in] -- Name of the action to be taken CalcColName [in] -- Name of the calculated column that needs to be processed. Normally this should only be populated when the CalcField action is being used. FSList [in] -- The list of MFSs and the BFS name for the current file or volume. This is an @SVM delimited array, with the current MFS name as the first value in the array, and the BFS name as the last value. Normally set by a calling MFS. Handle [in] -- The file handle of the file or media map being accessed. Note, this does contain the entire handle structure that the Basic+ Open statement would provide. Normally set by a calling MFS. Name [in] -- The name (key) of the record or file being accessed. Normally set by a calling MFS. FMC [in] -- Various functions. Normally set by a calling MFS. Record [in] -- The entire record (for record-oriented functions) or a newly-created handle (for "get handle" functions). Normally set by a calling MFS. Status [in/out] -- Indicator of the success or failure of an action. Normally set by the calling MFS but for some actions can be set by the action handler to indicate failure. OrigRecord [in] -- Original content of the record being processed by the current action. This is automatically being assigned by the WRITE_RECORD and DELETE_RECORD actions within BASE_MFS. Param1-10 [in/out] -- Additional request parameter holders ActionFlow [out] -- Used to control the action chain (see the ACTION_SETUP insert for more information.) Can also be used to return a special value, such as the results of the CalcField method. History : (Date, Initials, Notes) 07/28/10 dmb Original programmer. 10/13/10 dmb Fix logic to extract the file handle if file has an index 03/26/11 dmb Add logic to save and restore @FILE.ERROR 01/03/17 dmb Updated the ROTR compare logic to search for a recipe match. 04/09/18 dmb Updated the ROTR compare logic to be limited to FWI stages. Also modified the default logic to be Failed unless proven otherwise. 04/19/18 djs Update ROTR compare logic to support multiple spec recipes. 05/04/18 dmb Update the special metrology log entry log to use the GetServer service rather than the @STATION global variable. 06/13/18 dmb Replaced CLEAN_INSP_SCAN_SUM_OF_DEF_AVG$ with CLEAN_INSP_SCAN_SUM_OF_DEF_MAX$ when comparing against the Sum of Defects in the SurfScan recipe. - [IREPIOI-43] 09/24/18 djs Adjusted the OConv call on the ScanDefect variable within the WRITE_RECORD_PRE action. 10/29/18 djs Updated the WRITE_RECORD_PRE event to calculate the SOD average to only use wafers that are below the USL (SOD Max) as per ROTR Project requirements. Also updated the UCL request using QA_Services to include the scanned Tencor Recipe name in order to return the UCL associated with the particular recipe being processed at that time. 06/07/19 djs Updated the WRITE_RECORD_PRE GoSub to support ROTR requirements for POST stage SurfScan rundata files. 08/12/20 djs Added logging within the WRITE_RECORD_PRE GoSub to gather data for a bug within the Metrology_Services('ImportMetrologyService') subroutine. Occasionally the DB server is getting an out of memory error when importing metrology files. ***********************************************************************************************************************/ #pragma precomp SRP_PreCompiler $insert APP_INSERTS $insert FILE.SYSTEM.EQUATES $insert ACTION_SETUP $insert CLEAN_INSP_EQUATES $insert REACTOR_EQUATES $insert RDS_EQUATES $insert WM_OUT_EQUATES $insert WO_LOG_EQUATES $insert ROTR_EQUATES $insert WO_MAT_EQUATES Equ Comma$ to ',' Declare function Error_Services, Database_Services, Environment_Services, QA_Services, RDS_Services, MemberOf Declare subroutine Error_Services, Database_Services, Post_Metrology_Manual_Data_Entry_Log Declare subroutine Qa_Services If KeyID then GoSub Initialize_System_Variables Begin Case Case Action _EQC 'CalculateColumn' ; GoSub CalculateColumn Case Action _EQC 'READ_RECORD_PRE' ; GoSub READ_RECORD_PRE Case Action _EQC 'READ_RECORD' ; GoSub READ_RECORD Case Action _EQC 'READONLY_RECORD_PRE' ; GoSub READONLY_RECORD_PRE Case Action _EQC 'READONLY_RECORD' ; GoSub READONLY_RECORD Case Action _EQC 'WRITE_RECORD_PRE' ; GoSub WRITE_RECORD_PRE Case Action _EQC 'WRITE_RECORD' ; GoSub WRITE_RECORD Case Action _EQC 'DELETE_RECORD_PRE' ; GoSub DELETE_RECORD_PRE Case Action _EQC 'DELETE_RECORD' ; GoSub DELETE_RECORD Case Otherwise$ ; Status = 'Invalid Action' End Case If KeyID then GoSub Restore_System_Variables If Assigned(ActionFlow) else ActionFlow = ACTION_CONTINUE$ Return ActionFlow //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Calculated Columns // // The typical structure of a calculated column will look like this: // // Declare function Database_Services // // @ANS = Database_Services('CalculateColumn') //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// CalculateColumn: // Make sure the ActionFlow return variable is cleared in case nothing is calculated. ActionFlow = '' Begin Case Case CalcColName EQ 'INSP_REQ' ; GoSub INSP_REQ Case CalcColName EQ 'RDS_ROTR_ACTION' ; GoSub RDS_ROTR_ACTION End Case return INSP_REQ: SkipInsp = Xlate('WO_MAT', {WO_MAT_KEY}, 'CONVERTED_MATERIAL', 'X') If SkipInsp EQ '' then RDSNo = {RDS_NO} WONo = {WO_NO} WOQty = Xlate('WO_LOG', WONo, 'QTY', 'X') RunNo = Xlate('RDS', RDSNo, 'RUN_ORDER_NUM', 'X') InspInterval = {INSP_INTERVAL} LastRun = ( (RunNo * 25) EQ WOQty ) InspReq = ( (Mod((RunNo - 1), InspInterval) EQ 0) or LastRun ) ActionFlow = InspReq end else ActionFlow = False$ end return RDS_ROTR_ACTION: ROTRAction = {ROTR_ACTION} If ROTRAction EQ 'F' then // Before returning a Fail, check to see if there is an acceptance for the results. AcceptedSignature = {SIGN_ROTR_SIGNATURE} AcceptedReason = {SIGN_ROTR_REASON} If (AcceptedSignature NE '') AND (AcceptedReason NE '') then ROTRAction = 'A' end ActionFlow = ROTRAction return // ----- MFS calls ----------------------------------------------------------------------------------------------------- READ_RECORD_PRE: // In order to stop a record from being read in this action these lines of code must be used: // // OrigFileError = 100 : @FM : KeyID // Status = 0 // Record = '' // ActionFlow = ACTION_STOP$ return READ_RECORD: // In order to stop a record from being read in this action these lines of code must be used: // // OrigFileError = 100 : @FM : KeyID // Status = 0 // Record = '' return READONLY_RECORD_PRE: // In order to stop a record from being read in this action these lines of code must be used: // // OrigFileError = 100 : @FM : KeyID // Status = 0 // Record = '' // ActionFlow = ACTION_STOP$ return READONLY_RECORD: // In order to stop a record from being read in this action these lines of code must be used: // // OrigFileError = 100 : @FM : KeyID // Status = 0 // Record = '' return WRITE_RECORD_PRE: OrigRecCopy = OrigRecord RecCopy = Record Convert @VM to '' in OrigRecCopy Convert @VM to '' in RecCopy // LWII Specification limits OrigLWIISpecLPD = Trim(OrigRecCopy) OrigLWIISpecScr = Trim(OrigRecCopy) OrigLWIISpecScrLen = Trim(OrigRecCopy) OrigLWIISpecPits = Trim(OrigRecCopy) OrigLWIISpecMounds = Trim(OrigRecCopy) OrigLWIISpecStack = Trim(OrigRecCopy) OrigLWIISpecSpikes = Trim(OrigRecCopy) OrigLWIISpecSpots = Trim(OrigRecCopy) OrigLWIISpecBLDef = Trim(OrigRecCopy) OrigLWIISpecFOV = Trim(OrigRecCopy) NewLWIISpecLPD = Trim(RecCopy) NewLWIISpecScr = Trim(RecCopy) NewLWIISpecScrLen = Trim(RecCopy) NewLWIISpecPits = Trim(RecCopy) NewLWIISpecMounds = Trim(RecCopy) NewLWIISpecStack = Trim(RecCopy) NewLWIISpecSpikes = Trim(RecCopy) NewLWIISpecSpots = Trim(RecCopy) NewLWIISpecBLDef = Trim(RecCopy) NewLWIISpecFOV = Trim(RecCopy) // LWII Signature OrigLWIISig = Trim(OrigRecCopy) OrigLWIIDtm = Trim(OrigRecCopy) NewLWIISig = Trim(RecCopy) NewLWIIDtm = Trim(RecCopy) // LWIS Specification Limits OrigLWISRecipe = Trim(OrigRecCopy) OrigLWISSpecDef = Trim(OrigRecCopy) OrigLWISSpecHaze = Trim(OrigRecCopy) NewLWISRecipe = Trim(RecCopy) NewLWISSpecDef = Trim(RecCopy) NewLWISSpecHaze = Trim(RecCopy) // LWIS Signature OrigLWISSig = Trim(OrigRecCopy) OrigLWISDtm = Trim(OrigRecCopy) NewLWISSig = Trim(RecCopy) NewLWISDtm = Trim(RecCopy) RDSKeyID = RecCopy // Check if run aborted and wafers were removed WafersRemoved = Trim(RecCopy) RDSNo = RDSKeyID FinalQA = False$ RDSRow = Database_Services('ReadDataRow', 'RDS', RDSNo) WorkOrderNo = RDSRow WorkOrderRow = Database_Services('ReadDataRow', 'WO_LOG', WorkOrderNo) ReactType = WorkOrderRow // Check to see if the Final QA signature is in place. If ReactType EQ 'EPP' then CassetteNos = RDSRow OutNCRNos = RDSRow For Each CassetteNo in CassetteNos using @VM setting vPos OutNCRNo = OutNCRNos<0, vPos> // Ignore NCRd wafers If OutNCRNo EQ '' then WMOutKey = RDSRow : '*' : CassetteNo WMOutRow = Database_Services('ReadDataRow', 'WM_OUT', WMOutKey) If WMOutRow NE '' then FinalQA = True$ end end Until FinalQA Next CassetteNo end else If RDSRow NE '' then FinalQA = True$ end end // If Final QA signature is in place, implement the control logic. If FinalQA EQ True$ then // User is attempting to modify this row. Set the FS104 error and log the event. Error_Services('Add', 'FS104:FQA has already been signed. CLEAN_INSP record cannot be updated!') OrigFileError = 104 : @FM : 'FQA has already been signed. CLEAN_INSP record cannot be updated!' Status = 0 Record = '' ActionFlow = ACTION_STOP$ end else If ( (OrigLWIISpecLPD NE '') and (NewLWIISpecLPD EQ '') ) | or ( (OrigLWIISpecScr NE '') and (NewLWIISpecScr EQ '') ) | or ( (OrigLWIISpecScrLen NE '') and (NewLWIISpecScrLen EQ '') ) | or ( (OrigLWIISpecPits NE '') and (NewLWIISpecPits EQ '') ) | or ( (OrigLWIISpecMounds NE '') and (NewLWIISpecMounds EQ '') ) | or ( (OrigLWIISpecStack NE '') and (NewLWIISpecStack EQ '') ) | or ( (OrigLWIISpecSpikes NE '') and (NewLWIISpecSpikes EQ '') ) | or ( (OrigLWIISpecSpots NE '') and (NewLWIISpecSpots EQ '') ) | or ( (OrigLWIISpecBLDef NE '') and (NewLWIISpecBLDef EQ '') ) | or ( (OrigLWIISpecFOV NE '') and (NewLWIISpecFOV EQ '') ) | or ( (OrigLWIISig NE '') and (NewLWIISig EQ '') ) | or ( (OrigLWIIDtm NE '') and (NewLWIIDtm EQ '') ) | or ( (OrigLWISRecipe NE '') and (NewLWISRecipe EQ '') ) | or ( (OrigLWISSpecDef NE '') and (NewLWISSpecDef EQ '') ) | or ( (OrigLWISSpecHaze NE '') and (NewLWISSpecHaze EQ '') ) | or ( (OrigLWISSig NE '') and (NewLWISSig EQ '') ) | or ( (OrigLWISDtm NE '') and (NewLWISDtm EQ '') ) and Not(MemberOf(@User4, 'OI_ADMIN')) then // User is attempting to erase data that is prohibited from being erased Set the FS104 error and block the write. Error_Services('Add', 'FS104:Specification limits or signature data cannot be removed. Clean & Insp record cannot be updated!') OrigFileError = 104 : @FM : 'Specification limits or signature data cannot be removed. Clean & Insp record cannot be updated!' Status = 0 Record = '' ActionFlow = ACTION_STOP$ end else Stage = Record IsEpiPro = Rds_Services('IsEpiPro', RDSKeyID) // This ROTR logic is only applicable to LWI clean & inspection rows. CleanInspKeyID = Name // Get the current signature required and signature entries. SigRequired = Record ScanSigs = Record // Get the specifications SpecRecipes = Record SpecDefects = Record ; // USL - Spec SOD Max SpecHazes = Record SpecQuantities = Record // Get the new scanned values ScanRecipes = Record ScanTools = Record ScanDefectsAvg = Record ScanDefects = Record ScanHazes = Record ScanMismatch = Record QtyMismatch = Record // Get scan results ScanRecipeResults = Record ScanRecipeReasons = Record // Get the original scanned values SpecRecipesOrig = OrigRecord ScanRecipesOrig = OrigRecord ScanDefectsAvgOrig = OrigRecord ScanDefectsOrig = OrigRecord ScanHazesOrig = OrigRecord ScanMismatchOrig = OrigRecord QtyMismatchOrig = OrigRecord // Get the original signature required and signature entries. SigRequiredOrig = OrigRecord ScanSigsOrig = OrigRecord // Get the individual SOD values per wafer. SODPerWafer = Record SODPerWaferOrig = OrigRecord // Get the 100 failure scan flags, which is null by default and set/cleared by this MFS. FailScanFlags = Record FailScanReq = Sum(FailScanFlags) If FailScanReq GT 0 then FailScanReq = True$ end else FailScanReq = False$ end CIReactor = Xlate('CLEAN_INSP', CleanInspKeyID, 'REACT_NO', 'X') ROTREnabled = Xlate('REACTOR', CIReactor, 'ENABLE_ROTR', 'X') ROTRFailLimit = Xlate('REACTOR', CIReactor, 'ROTR_FAIL_LIMIT', 'X') // Get ROTR Reactor values ROTRReactorStatus = Record ROTRReactorStatusReason = Record FailedWafers = Record If OrigRecord NE Record then // Backlog Req 731 - ROTR Post Supercede // Increase POST stage surfscan sample quantity specification to number of LWI stage failed wafers. If Stage EQ 'POST' then RDSNo = Record LWICIKey = Xlate('RDS', RDSNo, 'LWI_CI_NO', 'X') If LWICIKey NE '' then LWIFailedWafers = Xlate('CLEAN_INSP', LWICIKey, 'FAILED_WAFERS', 'X') If LWIFailedWafers NE '' then NewPostSpecSampleQty = Sum(LWIFailedWafers) PostSpecRecipes = Record NumRecipes = DCount(PostSpecRecipes, @VM) If NumRecipes GT 0 then For RecipeIndex = 1 to NumRecipes CurrSpec = Record If ( (CurrSpec EQ '') or (CurrSpec LT NewPostSpecSampleQty) ) then Record = NewPostSpecSampleQty end Next RecipeIndex end end end end // Reset ROTR update flag so that future ROTR requests will re-trigger this routine (via BASE_MFS). Record = False$ // Tencor data has been updated. Update the ROTR_ACTION data column in the CLEAN_INSP record. // Check if ScanMismatch field has been set by Metrology_Services. If so, then the user attempted to // load run data, which containted the wrong recipe name. (i.e. it did not match the spec recipe) If (ScanMismatch EQ '') then RDSKey = Record NumScanRecipes = DCount(ScanRecipes, @VM) ROTRAction = '' ROTRActionReason = '' Begin Case Case Stage _EQC 'LWI' If SpecRecipes NE '' then ReactorBlocked = False$ ;// Assume false until proven otherwise. // ROTR Reactor/PSN Status Health Check - Uses parameters set within the ROTR Parameters form. // We only need to run this once per CLEAN_INSP record. ROTRStatus = 'P' ; // Assume pass until proven otherwise. If ( (ROTREnabled EQ True$) and (ROTRReactorStatus EQ '') ) then ROTRReactorStatus = 'W' ROTRReactorStatusReason = 'ROTR awaiting processing' // Set ROTR Reactor processing status so that the WRITE_RECORD_POST event will // post an ROTR request to calculate the reactor ROTR health for this run. QA_Services('PostROTRRequest', RDSKey) end // Check each scan recipe. For each ScanRecipeName in ScanRecipes using @VM setting ScanRecipeIndex Locate ScanRecipeName in SpecRecipes using @VM setting SpecRecipeIndex then // Get UCL value from SPC for each spec recipe if they have not yet been retrieved. UCL = Record If (UCL EQ '') or (UCL EQ 0) then // The UCL has not yet been retrieved from SPC or an error could have // occurred when the last attempt to retrieve it, so try to get it now. UCL = QA_Services('PostUCLRequest', RDSKey, ScanRecipeName) If (UCL NE '') and (UCL NE 0) then Record = UCL end else ErrorMessage = 'Error retrieving UCL value from QA_Services in CLEAN_INSP_ACTIONS' Error_Services('Add', ErrorMessage) end end SpecDefect = Oconv(SpecDefects<0, SpecRecipeIndex>, 'MD0') SpecHaze = Oconv(SpecHazes<0, SpecRecipeIndex>, 'MD2') SpecQty = SpecQuantities<0, SpecRecipeIndex> ScanDefect = Oconv(ScanDefects<0, ScanRecipeIndex>, 'MD0') ScanHaze = Oconv(ScanHazes<0, ScanRecipeIndex>, 'MD3') ScanSig = ScanSigs<0, ScanRecipeIndex> FailureScan = False$ // RDS < UCL (SOD Spec Avg) Check ScanSODPerWafer = Record ScanSortPerWafer = Record WaferCount = 0 SODAvgSum = 0 SODAvg = OConv(ScanDefectsAvg<0, ScanRecipeIndex>, 'MD3') NumFailedWafers = 0 For each WaferSOD in ScanSODPerWafer using @SVM setting WaferIndex WaferSort = ScanSortPerWafer<0, ScanRecipeIndex, WaferIndex> If ( (WaferSOD NE '') or (WaferSort NE '') ) then // We have SOD data for this wafer so check if it is < SOD Max If ( (WaferSOD LE SpecDefect) and (WaferSort NE 'FAIL') ) then WaferCount += 1 SODAvgSum += WaferSOD FailedWafers<0, WaferIndex> = False$ end else NumFailedWafers += 1 FailedWafers<0, WaferIndex> = True$ end end Next WaferSOD Record = NumFailedWafers If (OrigRecord) EQ '' then // This is the first time we are recording the scanned SOD average value. Store this // for determining reactor health. (ROTR Status) Record = IConv(SODAvg, 'MD3') end If WaferCount GT 0 and SODAvgSum GT 0 then SODAvg = SODAvgSum / WaferCount // Update scanned SOD average to not include wafers exceeding SOD max // Adjust value to conform to internal storage format ScanDefectsAvg<0, ScanRecipeIndex> = IConv(SODAvg, 'MD3') Record = ScanDefectsAvg end If (ROTREnabled EQ True$) then // ROTR Sign-off Project // We need to check if reactor recently underwent ROTR maintenance. If so, then // we must also check if this RDS is one of the first two runs after maintenance. // If this is the case, then the RDS must undergo a 100% scan. MaintScanReq = QA_Services('GetMaintenanceScanStatus', RDSKey) FailScanReq = (FailScanReq or MaintScanReq or WafersRemoved) // Search for '100' in the recipe name to determine if this is a failure scan. If (WafersRemoved) then FailureScan = Index(ScanRecipes, '25', 1) end else FailureScan = (Index(ScanRecipes, '100', 1) or Index(ScanRecipes, '25', 1)) end If FailureScan GT 0 then FailureScan = True$ If (FailScanReq EQ True$) and (FailureScan EQ True$) then // Fail scan was required and submitted, so turn off all fail scan required flags. FailScanReq = False$ NumScans = DCount(ScanRecipes, @VM) For ScanIndex = 1 to NumScans FailScanFlags<0, ScanIndex> = False$ Next ScanIndex WafersRemoved = False$ end end Begin Case Case WafersRemoved ROTRAction = 'F' ROTRActionReason = '100% scan required due to aborted run.' Case (FailScanReq EQ True$) and (FailureScan EQ False$) and (ROTREnabled EQ True$) If (MaintScanReq EQ True$) then ROTRAction = 'F' ROTRActionReason = '100% scan required due to ROTR maint.' end else // If ROTR maintenance flag not set, then wafer fail limit must have been met in // an earlier Tencor run. We must set it here again due to the design of this // MFS. ROTRAction = 'F' ROTRActionReason = 'Wafer fail limit met - 100% scan required' FailScanReq = True$ FailScanFlags<0, ScanRecipeIndex> = FailScanReq end Case (ROTRReactorStatus EQ 'W') and (ROTREnabled EQ True$) // Awaiting ROTR results. Request in queue. This MFS will be triggered // once the request is processed. ROTRAction = 'F' ROTRActionReason = ROTRReactorStatusReason Case (ROTRReactorStatus EQ 'F') and (ROTREnabled EQ True$) // Check this case first as to not miss blocking the reactor if necessary. // Block load signature on this reactor for future lots until overriden at Load button // signature click event by supervisor, lead, or engineer. ROTRAction = 'F' ROTRActionReason = ROTRReactorStatusReason CIReactorRec = Database_Services('ReadDataRow', 'REACTOR', CIReactor) CIReactorRec = 'F' CIReactorRec = ROTRReactorStatusReason Database_Services('WriteDataRow','REACTOR',CIReactor,CIReactorRec, True$, False$, True$) ReactorBlocked = True$ Case (ScanDefect EQ '') OR (ScanHaze EQ '') ROTRAction = 'F' ROTRActionReason = 'SurfScan data is missing.' Case (SigRequired EQ True$) AND (ScanSig EQ '') ROTRAction = 'F' ROTRActionReason = 'SurfScan signature is missing.' Case (ScanDefect GT SpecDefect) or (NumFailedWafers GE ROTRFailLimit) If (ROTREnabled EQ True$) then // If a Post Clean is required, then disregard the ROTRFailLimit per Tom Tillery. CleanReq = False$ PSNo = Xlate('CLEAN_INSP', CleanInspKeyID, 'PS_NO', 'X') PRSStages = Xlate('PROD_SPEC', PSNo, 'PRS_STAGE_KEY', 'X') If Index(PRSStages, 'POST', 1) then CleanReq = Xlate('PRS_STAGE', PSNo:'*POST', 'CLEAN_SIG_REQ', 'X') end If (NumFailedWafers LT ROTRFailLimit) or (CleanReq EQ True$) or (FailureScan EQ True$) then ROTRAction = 'F' ROTRActionReason = 'SurfScan data out of bounds.' end else ROTRAction = 'F' ROTRActionReason = 'Wafer fail limit met - 100% scan required' FailScanReq = True$ FailScanFlags<0, ScanRecipeIndex> = FailScanReq end end else ROTRAction = 'F' ROTRActionReason = 'SurfScan data out of bounds.' end Case (ScanHaze GT SpecHaze) ROTRAction = 'F' ROTRActionReason = 'SurfScan Haze out of bounds.' Case Otherwise$ // This scan data is within spec. ROTRAction = 'P' ROTRActionReason = 'Passed' End Case // Store this result for final disposition ScanRecipeResults<0, ScanRecipeIndex> = ROTRAction ScanRecipeReasons<0, ScanRecipeIndex> = ROTRActionReason end Next ScanRecipeName FinalROTRAction = 'P' FinalROTRActionReason = '' // Final disposition // If no scan data or if any scan data has failed, then FinalROTRAction = F. NumScanRecipes = DCount(ScanRecipes, @VM) If NumScanRecipes EQ 0 then FinalROTRAction = 'F' If WafersRemoved then FinalROTRActionReason = '100% scan required due to aborted run.' end else FinalROTRActionReason = 'Scan data is missing.' end end else NumFailures = Count(ScanRecipeResults, 'F') Begin Case Case NumFailures EQ 0 // No failures so this is a pass. FinalROTRAction = 'P' FInalROTRActionReason = '' Case NumFailures EQ 1 // Display the failure reason Locate 'F' in ScanRecipeResults using @VM setting ScanRecipeIndex then FailureReason = ScanRecipeReasons<0, ScanRecipeIndex> FinalROTRAction = 'F' FinalROTRActionReason = FailureReason end Case NumFailures GT 1 // Inform the user that multiple scans failed FinalROTRAction = 'F' FinalROTRActionReason = 'Multiple scans failed to meet criteria.' End Case end ROTRAction = FinalROTRAction ROTRActionReason = FinalROTRActionReason end Case Stage _EQC 'POST' If SpecRecipes NE '' then // Check each scan recipe. For each ScanRecipeName in ScanRecipes using @VM setting ScanRecipeIndex Locate ScanRecipeName in SpecRecipes using @VM setting SpecRecipeIndex then // Get UCL value from SPC for each spec recipe if they have not yet been retrieved. UCL = Record If (UCL EQ '') or (UCL EQ 0) then // The UCL has not yet been retrieved from SPC or an error could have // occurred when the last attempt to retrieve it, so try to get it now. UCL = QA_Services('PostUCLRequest', RDSKey, ScanRecipeName) If (UCL NE '') and (UCL NE 0) then Record = UCL end else ErrorMessage = 'Error retrieving UCL value from QA_Services in CLEAN_INSP_ACTIONS' Error_Services('Add', ErrorMessage) end end SpecSampleQty = Record SpecDefect = Oconv(SpecDefects<0, SpecRecipeIndex>, 'MD0') SpecHaze = Oconv(SpecHazes<0, SpecRecipeIndex>, 'MD2') SpecQty = SpecQuantities<0, SpecRecipeIndex> ScanDefect = Oconv(ScanDefects<0, ScanRecipeIndex>, 'MD0') ScanHaze = Oconv(ScanHazes<0, ScanRecipeIndex>, 'MD3') ScanSig = ScanSigs<0, ScanRecipeIndex> FailureScan = False$ // RDS < UCL (SOD Spec Avg) Check ScanSODPerWafer = Record ScanSortPerWafer = Record WaferCount = 0 SODAvgSum = 0 SODAvg = OConv(ScanDefectsAvg<0, ScanRecipeIndex>, 'MD3') NumFailedWafers = 0 For each WaferSOD in ScanSODPerWafer using @SVM setting WaferIndex WaferSort = ScanSortPerWafer<0, ScanRecipeIndex, WaferIndex> If ( (WaferSOD NE '') or (WaferSort NE '') ) then // We have SOD data for this wafer so check if it is < SOD Max If ( (WaferSOD LE SpecDefect) and (WaferSort NE 'FAIL') ) then WaferCount += 1 SODAvgSum += WaferSOD FailedWafers<0, WaferIndex> = False$ end else NumFailedWafers += 1 FailedWafers<0, WaferIndex> = True$ end end Next WaferSOD Record = NumFailedWafers If (OrigRecord) EQ '' then // This is the first time we are recording the scanned SOD average value. Store this // for determining reactor health. (ROTR Status) Record = IConv(SODAvg, 'MD3') end If WaferCount GT 0 and SODAvgSum GT 0 then SODAvg = SODAvgSum / WaferCount // Update scanned SOD average to not include wafers exceeding SOD max // Adjust value to conform to internal storage format ScanDefectsAvg<0, ScanRecipeIndex> = IConv(SODAvg, 'MD3') Record = ScanDefectsAvg end Begin Case Case (ScanDefect EQ '') OR (ScanHaze EQ '') ROTRAction = 'F' ROTRActionReason = 'SurfScan data is missing.' Case (SigRequired EQ True$) AND (ScanSig EQ '') ROTRAction = 'F' ROTRActionReason = 'SurfScan signature is missing.' Case (ScanDefect GT SpecDefect) or (NumFailedWafers GE ROTRFailLimit) ROTRAction = 'F' ROTRActionReason = 'SurfScan data out of bounds.' Case (ScanHaze GT SpecHaze) ROTRAction = 'F' ROTRActionReason = 'SurfScan Haze out of bounds.' Case Otherwise$ // This scan data is within spec. ROTRAction = 'P' ROTRActionReason = 'Passed' End Case // Store this result for final disposition ScanRecipeResults<0, ScanRecipeIndex> = ROTRAction ScanRecipeReasons<0, ScanRecipeIndex> = ROTRActionReason end Next ScanRecipeName FinalROTRAction = 'P' FinalROTRActionReason = '' // Final disposition // If no scan data or if any scan data has failed, then FinalROTRAction = F. NumScanRecipes = DCount(ScanRecipes, @VM) If NumScanRecipes EQ 0 then FinalROTRAction = 'F' FinalROTRActionReason = 'Scan data is missing.' end else TotalScanWfrCnt = Count(FailedWafers, '0') + Count(FailedWafers, '1') Begin Case Case (SpecSampleQty EQ '') QtyMismatch = 'Error determining sample qty spec' Case (TotalScanWfrCnt LT SpecSampleQty) QtyMismatch = 'Min sample quantity not met.' Case Otherwise$ QtyMismatch = '' // Set flag to inform FQA that POST surfscan failure results should be used instead. RDSRec = Database_Services('ReadDataRow', 'RDS', RDSNo) RDSRec = True$ Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$) End Case NumFailures = Count(ScanRecipeResults, 'F') Begin Case Case ( (QtyMismatch NE '') and Not(IsEpiPro) ) FinalROTRAction = 'F' FInalROTRActionReason = QtyMismatch Case NumFailures EQ 0 // No failures so this is a pass. FinalROTRAction = 'P' FInalROTRActionReason = '' Case NumFailures EQ 1 // Display the failure reason Locate 'F' in ScanRecipeResults using @VM setting ScanRecipeIndex then FailureReason = ScanRecipeReasons<0, ScanRecipeIndex> FinalROTRAction = 'F' FinalROTRActionReason = FailureReason end Case NumFailures GT 1 // Inform the user that multiple scans failed FinalROTRAction = 'F' FinalROTRActionReason = 'Multiple scans failed to meet criteria.' End Case end ROTRAction = FinalROTRAction ROTRActionReason = FinalROTRActionReason end End Case end else // Recipe mismatch field set. Set ROTR to failed and inform user. ROTRAction = 'F' ROTRActionReason = ScanMismatch:' does not match spec.' end If ROTRAction EQ 'P' then // Clear any previously set failure reasons or failure scan flags. ROTRActionReason = '' NumScans = DCount(ScanRecipes, @VM) For ScanIndex = 1 to NumScans FailScanFlags<0, ScanIndex> = False$ Next ScanIndex Record = False$ end Record = ROTRAction Record = ROTRActionReason Record = FailScanFlags Record = ScanRecipeResults Record = ScanRecipeReasons Record = FailedWafers SaveRecord = Record Server = Environment_Services('GetServer') If (Server NE 'MESSA005') AND (Server NE 'MESSA01EC') then // The metrology data was updated by an end user on a workstation. Add this information to a special log // for Balan. Post_Metrology_Manual_Data_Entry_Log(@USER4,'Tencor',Record:' / ':CleanInspKeyID) end end end end return WRITE_RECORD: WONo = Record ReactType = Xlate('WO_LOG', WONo, 'REACT_TYPE', 'X') EpiPro = (ReactType EQ 'EPP') GaN = (ReactType EQ 'GAN') NonEpiPro = ( (ReactType NE 'EPP') and (ReactType NE 'GAN') ) Stage = Record If Stage EQ 'LWI' then RDSNo = Record PostCIKey = Xlate('RDS', RDSNo, 'POST_CI_NO', 'X') If PostCIKey NE '' then FailedWafers = Record If FailedWafers NE '' then NewPostSpecSampleQty = Sum(FailedWafers) PostCIRec = Database_Services('ReadDataRow', 'CLEAN_INSP', PostCIKey) PostSpecRecipes = PostCIRec NumRecipes = DCount(PostSpecRecipes, @VM) If NumRecipes GT 0 then For RecipeIndex = 1 to NumRecipes CurrSpec = PostCIRec If ( (CurrSpec EQ '') or (CurrSpec LT NewPostSpecSampleQty) ) then PostCIRec = NewPostSpecSampleQty end Next RecipeIndex Database_Services('WriteDataRow', 'CLEAN_INSP', PostCIKey, PostCIRec, True$, False$, True$) end end end end If NonEpiPro then // Sync up Insp, Clean, and SurfScan signatures with WO_MAT signature profile Stage = {STAGE} WOMatKey = {WO_MAT_KEY} // Update failed wafer list QA_Services('UpdateFailedWafers', WOMatKey) WOMatRec = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey) WOMatSigProf = WOMatRec WOMatSigs = WOMatRec WOMatSigDTMs = WOMatRec OrigInspSig = OrigRecord OrigInspSigDTM = OrigRecord InspSig = Record InspSigDTM = Record OrigCleanSig = OrigRecord OrigCleanSigDTM = OrigRecord CleanSig = Record CleanSigDTM = Record OrigScanSig = OrigRecord OrigScanSigDTM = OrigRecord ScanSig = Record ScanSigDTM = Record SigProfUpdate = False$ If ( (OrigInspSig NE InspSig) or (OrigInspSigDTM NE InspSigDTM) ) then WOMatStage = '1':Stage:'I' Locate WOMatStage in WOMatSigProf using @VM setting vPos then WOMatSigs<0, vPos> = InspSig[-1, 'B':@VM] WOMatSigDTMs<0, vPos> = InspSigDTM[-1, 'B':@VM] SigProfUpdate = True$ end end If ( (OrigCleanSig NE CleanSig) or (OrigCleanSigDTM NE CleanSigDTM) ) then WOMatStage = '1':Stage:'C' Locate WOMatStage in WOMatSigProf using @VM setting vPos then WOMatSigs<0, vPos> = CleanSig[-1, 'B':@VM] WOMatSigDTMs<0, vPos> = CleanSigDTM[-1, 'B':@VM] SigProfUpdate = True$ end end If ( (OrigScanSig NE ScanSig) or (OrigScanSigDTM NE ScanSigDTM) ) then WOMatStage = '1':Stage:'S' Locate WOMatStage in WOMatSigProf using @VM setting vPos then WOMatSigs<0, vPos> = ScanSig[-1, 'B':@VM] WOMatSigDTMs<0, vPos> = ScanSigDTM[-1, 'B':@VM] SigProfUpdate = True$ end end If SigProfUpdate then NumSteps = DCount(WOMatSigProf, @VM) WOMatRec = Field(WOMatSigs, @VM, 1, NumSteps) WOMatRec = Field(WOMatSigDTMs, @VM, 1, NumSteps) Database_Services('WriteDataRow', 'WO_MAT', WOMatKey, WOMatRec, True$, False$, True$) end end return DELETE_RECORD_PRE: return DELETE_RECORD: return // ----- Internal Methods ---------------------------------------------------------------------------------------------- Initialize_System_Variables: // Save these for restoration later SaveDict = @DICT SaveID = @ID SaveRecord = @RECORD OrigFileError = @FILE.ERROR // Now make sure @DICT, ID, and @RECORD are populated CurrentDictName = '' If @DICT then DictHandle = @DICT<1, 2> Locate DictHandle in @TABLES(5) Using @FM Setting fPos then CurrentDictName = Field(@TABLES(0), @FM, fPos, 1) end end If CurrentDictName NE DictName then Open DictName to @DICT else Status = 'Unable to initialize @DICT' end @ID = KeyID If Record else // Record might not have been passed in. Read the record from the database table just to make sure. @FILE.ERROR = '' Open TableName to hTable then FullFSList = hTable[1, 'F' : @VM] BFS = FullFSList[-1, 'B' : @SVM] LastHandle = hTable[-1, 'B' : \0D\] FileHandle = \0D\ : LastHandle[1, @VM] Call @BFS(READO.RECORD, BFS, FileHandle, KeyID, FMC, Record, ReadOStatus) end end @RECORD = Record return Restore_System_Variables: Transfer SaveDict to @DICT Transfer SaveID to @ID Transfer SaveRecord to @RECORD @FILE.ERROR = OrigFileError return