[亲测好用] NEDA nSTD.dll 把CSV转换成STDF [C#]

写在前面:NEDA是一个收费软件,nSTD.dll控件需要用NEDA授权才可以使用。 (调试的时候可以申请30天的试用授权)。如果需要免费的控件,可以尝试pystd控件,不过这个控件的效率不是很高,而且有蛮多的bug,需要对于STDF的底层记录需要很了解。

最近接到一个任务,由于客户要求STDF格式的数据,我们需要把我们的CSV测试数据自动转成STDF格式。网络上搜索了一圈,发现一下两个控件可用,对比如下,各位朋友可以参考根据自己的情况选择。

  1. (NEDA) nSTD.dll控件 (编程语言 :C#;缺点 :收费软件; 优点:速度快,好用,生成的STDF数据完整性好)

  2. pystd 控件 (编程语言 :Python; 优点 :免费; 缺点:速度慢,bug多,需要了解STDF底层结构)

由于公司的任务交期紧,我们客户又愿意帮我们支付NEDA永久授权的费用。我们就直接选用了NEDA nSTD.dll控件 (同时还获得了NEDA分析软件,对于我后面的测试工作起到了很大帮助,用来解析STDF和我们的CSV数据进行对比)。

如果你要问我为什么不自己从头写一个C#创建STDF文件的程序,我觉得主要看投资回报比。关于STDF格式,网上可以找得到几十页的英文资料很详细,我虽然没有啃过,但是如果真的把那几十页的英文资料吃透了,写一个创建STDF的程序肯定是可以的。只不过需要花大量时间阅读英文资料,从底层慢慢构建STDF数据格式,记录格式,以及数据相关性的各种逻辑程序,加上前后各种debug调试,然后在实际运行中遇到问题再debug调试,粗略估算一下估计也要3个月左右吧,完全不划算,对比如下。

  1. 使用现用控件开发:交期 :1 周;费用 :9W 加上 程序员1周工资; 优点:程序稳定流畅,bug少,省去了后期大量调试维护时间,

  2. 从零开发:交期 :3个月; 费用 :程序员3个月工资 (以上海为例,大概20W左右);优点 :具有完整源代码,后续项目容易;缺点:程序稳定度和bug未知。

废话不多说了,下面进入正题:

**首先,**我在开发之前还是找我们的PTE兄弟们大致了解了一下STDF的格式和它的结构。在完成开发之后,我发现其实STDF格式的详细信息不需要了解,只需要下面这个图了解一下STDF的大概结构就可以了。主要分三层:Lot Level, Wafer Level 和 Part Level。这里稍微解释一些,给想要了解半导体测试的朋友一点信息。

LOT Level: 代表一批芯片 (可能有几万颗) 从开始测试测试结束。行业术语叫做 StartLot 和 EndLot.

Wafer Level ( FT不需要**):**代表一片晶圆的测试开始到测试结束,行业术语叫 StartWafer 和 EndWafer.

Part Level: 代表一颗 die/晶片的测试开始到测试结束,行业里面叫一个touch down。

具体请看下面的图形,会更加清晰。

**其次:**我们需要开发一个CSV数据读取的C#程序,把CSV的信息读取进内存,然后调用nSTD.dll的对应的方法把数据写入STDF。

我们的CSV数据结构:

  1. Lot相关信息 (LOT_ID, Product ID, Program Name, Tester ID, start time等)
  2. 每颗die的测试结果,每颗die一行,包括序列号,Bin信息,pass/fail, 以及测试项的结果(列)
  3. 整个lot的统计信息(包括 Bin信息,测试项fail统计等)

这种CSV数据结构其实跟STDF的结构非常相似,所以我花很少时间就把CSV读取的程序写好了。

**再次:**调用nSTD.dll把数据写入STDF。由于我们的CSV数据结构很STDF结构相似,所以其实我是边读边写的。(并不需要非常了解STDF的详细结构,只需要对照上面的图形调用相应的方法就可以了)

  1. 在开始写数据之前先要初始化nstd对象
cs 复制代码
//initilize nstd library
nstd std = new nstd(stdfFileName);
if (std.ErrorFlag)
{
    PrintAndLogMessage(MessageTypes.Error, "nSTD控件初始化失败!");
    break;
}
//Must init file before writing any data
std.InitFile();
  1. 读取完 Lot相关信息后调用StartLot方法,并把lot相关信息传入 (实际是从CSV读取传过去的),数据是通过Header对象传入的,它一次性写入了MIR和SDR记录,所以很方便也不容易出错。
cs 复制代码
 Header stdHeader = new Header(new List<byte> { 1 });
 stdHeader.LOT_ID = "T1205369.1";
 stdHeader.STAT_NUM = 1;
 stdHeader.JOB_NAM = "SUPERCORE_TP_QS_V001";
 stdHeader.JOB_REV = "V001";
 stdHeader.SETUP_ID = stdHeader.NODE_NAM = "AMIDA-0088";
 stdHeader.TSTR_TYP = "V93K";
 stdHeader.TEST_COD = "FT";
 stdHeader.PROT_COD = "P";
 stdHeader.MODE_COD = 'P';
 stdHeader.HAND_ID = "EPSON-0012";
 stdHeader.HAND_TYP = "EPSON NS";
 //Write MIR, StartTime: Type->DateTime
 std.StartLot(stdHeader, StartTime);
  1. 读取Wafer相关信息并通过StartWafer()方法写入WIR和WCR, 我们是FT数据,所以没有用这个方法,如果需要的可以去看nSTD.dll的手册。

  2. 读取一个die的信息(CSV的一行), 然后调用StartPart()方法开始一个Part (其实就是写入PIR)

cs 复制代码
 std.StartPart(1, 1); //SiteNum, HeadNum (HeadNum可以忽略,默认为1)
  1. 写入所有测试项的结果,有三个方法:WriteParaTestResult(), std.WriteFuncTestResult() 和WriteMultiPinTestResult()。我只用的了PTR,所以只用到了第一个方法。(需要循环调用这个方法把所有测试项的结果都写入STDF, 对应的信息通过ParaTest对象传入)
cs 复制代码
std.WriteParaTestResult(1, new ParaTest()
    {
        TEST_NUM = 1001, 
        TEST_TXT = "conti_core_vdd", 
        HEAD_NUM = 1, 
        RESULT= 34.598, 
        UNITS = "uA", 
        LO_LIMIT = 20.000, 
        HI_LIMIT = 60.000
    }); 
  1. 结束一个die, 在写完一个die的所有数据之后,需要通过EndPart()来结束这个die。其实就是写入PRR记录,需要把这个die的最终结果:是否pass,fail那个bin等信息传入方法。对应数据通过PartResult对象信息传入。
cs 复制代码
 std.EndPart(1, new PartResult()
 {
     HEAD_NUM = 1, //hardcode HEAD_NUM=1
     PART_ID = csvReadr.PartInfoList[i].PART_ID,
     HARD_BIN = csvReadr.PartInfoList[i].HARD_BIN,
     SOFT_BIN = csvReadr.PartInfoList[i].SOFT_BIN,
     PART_TXT = csvReadr.PartInfoList[i].PART_TXT,
     PassFlag = csvReadr.PartInfoList[i].PASS_FLG,
     TEST_T = csvRdr.PartInfoList[i].TEST_T,
     //X_COORD = csvRdr.PartInfoList[i].X_COORD,//FT data dont need to specify
     //Y_COORD= csvRdr.PartInfoList[i].Y_COORD,//FT data dont need to specify
 });

循环读取所有die的数据,对于每个die都重复 step4,5,6 写入数据。

  1. 调用EndWafer()方法,结束一片wafer. (我们是FT数据,所以没有使用)

下面就要写入整个lot的统计信息了:测试项统计,bin统计,数量统计。

  1. 调用 WriteTestCounts() 方法写入测试项统计(每个测试项有测了多少颗die,多少颗die fail这个测试项),其实就是写入TSR记录。使用的是TestCount对象的List传递数据。注意:只需要写入每个site自己的TestCount, Lot Level总的(HEAD=255)由nSTD.dll自动汇总并写入。
cs 复制代码
 // Write TSR
 List<TestCount> testCountLIst = new List<TestCount>();
 foreach(var idx in csvReadr.TestCountDict.Keys)
 {
     testCountLIst.Add(new TestCount()
     {
         HEAD_NUM = 1,
         SITE_NUM = 1,
         TEST_NUM = csvReadr.TestInfoDict[idx].TEST_NUM,
         TEST_NAM = csvReadr.TestInfoDict[idx].TEST_NAM,
         TEST_TYP = 'P', //P - PTR 固定
         EXEC_CNT = csvReadr.TestCountDict[idx][0], //测试数量
         FAIL_CNT = csvReadr.TestCountDict[idx][1], //Fail数量
     });
 }
 std.WriteTestCounts(testCountLIst);
  1. 调用WriteHBins()方法,写入Hardware Bin信息,使用Bin对象的List来传递数据。注意:只需要写入每个site自己的HBin, Lot Level总的HBin (HEAD=255) 由nSTD.dll自动汇总并写入。
cs 复制代码
//写入HBR
List<Bin> binCountList = new List<Bin>();
foreach(var bin in csvReadr.HardBinCountDict.Keys)
{
    binCountList.Add(new Bin()
    {
        HEAD_NUM = 1, //固定值HEAD_NUM=1
        SITE_NUM = 1, //固定值SITE_NUM = 1
        BIN_NUM = bin,
        BIN_PF = bin == 1 ? 'P' : 'F', //非bin1就设成fail
        BIN_CNT = csvReadr.HardBinCountDict[bin],
        BIN_NAM = BinNameDict[bin],
    });
}
std.WriteHBins(binCountList);
  1. 调用WriteSBins()方法,写入Software Bin信息,使用Bin对象的List来传递数据。注意:只需要写入每个site自己的HBin, Lot Level总的HBin (HEAD=255) 由nSTD.dll自动汇总并写入。
cs 复制代码
//写入SBR
foreach (var bin in csvReadr.SoftBinCountDict.Keys)
{
    binCountList.Add(new Bin()
    {
        HEAD_NUM = 1, //固定值HEAD_NUM=1
        SITE_NUM = 1, //固定值SITE_NUM = 1
        BIN_NUM = bin,
        BIN_PF = bin == 1 ? 'P' : 'F', //非Bin1就是fail
        BIN_CNT = csvReadr.SoftBinCountDict[bin],
        BIN_NAM = SBinNameDict[bin],
    });
}
std.WriteSBins(binCountList);
  1. 调用WritePartCounts()方法写入Site的数量汇总:总数+Pass数量。数据通过PartCount对象的List来传递。注意:只需要写入每个site自己的数量, Lot Level总的数量 (HEAD=255) 由nSTD.dll自动汇总并写入。
cs 复制代码
std.WritePartCounts(new List<PartCount> { new PartCount()
{
     HEAD_NUM = 1, //固定值HEAD_NUM = 1
     SITE_NUM = 1, //固定值SITE_NUM = 1
     PART_CNT = csvReadr.Total,
     GOOD_CNT = csvReadr.Good,
}});
  1. 调用EndLot()来结束整个批次的数据,只需要传入结束时间即可,完工!
cs 复制代码
std.EndLot("", EndTime);//EndTime Type: DateTime

写在最后,当然脚本做好了之后,还需要让它自动运行,自动侦测输入目录的csv数据,然后一个个转化为STDF,并放到输出目录。这个逻辑就不赘述了。

至于脚本的自动运行,在Windows上有两种方法,详情略过...

  1. 使用Windows计划任务

  2. 使用Windows Service