#!/usr/bin/env python
import os
import sys
import re
import argparse
import json
import asyncio
import websockets
import time
import subprocess
import difflib
import glob
import shutil

def gversion():
    version = "11.8.44"
    return version
def get_gof_path(self):
    if os.path.exists("./gfront"):
        print("Found gofdir to be current")
        return "."
    gofdir = os.environ.get("GOFDIR")
    if gofdir != 'None':
        print("Found gofdir defined by GOFDIR variable")
        return gofdir+"/bin"
    script_path = os.path.dirname(os.path.abspath(__file__))
    gof_dir0 = script_path+"/../bin"
    gof_dir1 = script_path+"/../gofeco/GOF64/bin"
    aprint(f"Check to see if exist {gof_dir0} and {gof_dir1}", self)
    if os.path.exists(gof_dir0):
        gof_dir = gof_dir0
    elif os.path.exists(gof_dir1):
        gof_dir = gof_dir1
    aprint(f"GOF PATH = {gof_dir}\n", self)
    return gof_dir
class NoGuiApp:
    def __init__(self, run_id='default'):
        sharedInit(self, run_id, 1)


def fromLLM(message, host="localhost", port=1998, randkey="", self=None):
    fromClient = asyncio.run(clientSocket(message, host, port, randkey, self))
    fromClient = re.sub("___ENDAA___", "", fromClient)
    return fromClient

async def clientSocket(message, host, port, randkey, self):
    uri = f"ws://{host}:{port}"
    stream = 0
    async with websockets.connect(uri, open_timeout=60, ping_timeout=240) as websocket:
        if re.search(r"^\s*LLM\w+:", message):
            if re.search(r"^\s*LLM_\d+_\d+_\d+\w*: ", message):
                sel_vend = int(re.search(r"^\s*LLM_(\d+)_\d+_\d+\w*: ", message).group(1))
                sel_model = int(re.search(r"^\s*LLM_\d+_(\d+)_\d+\w*: ", message).group(1))
                stream = int(re.search(r"^\s*LLM_\d+_\d+_(\d+)\w*: ", message).group(1))
            request = message
        else:
            setting = getSetting(1, 0) # new_topic=1, stream=0
            request = setting + message
        frequest = randkey+request
        await sendMessage(frequest, websocket)
        response = "" #await recvMessage(websocket)
        #if stream == 2:
        #    await sendMessage("ack", websocket)
        while True:
            subres = await recvMessage(websocket)
            if not subres:
                break
            response += subres
            if stream == 2:
                await sendMessage("ack", websocket)
            pinfo = re.sub("___ENDAA___", "", subres)
            #pinfo = re.sub(r'^> build \S \S+', '', pinfo, re.MULTILINE) 
            aprint(pinfo, self)
        return response
async def sendMessage(string, websocket):
    string += "___ENDQQ___"
    await websocket.send(string)
async def recvMessage(websocket):
    try:
        return await websocket.recv()
    except websockets.exceptions.ConnectionClosedOK:
        return None
        
def callLLM(sarrs, self):
    sel_vend = self.para['sel_vend']
    messages = []
    if sel_vend == 0 or sel_vend == 1: # claude or google
        cont_nm = "content"
        if sel_vend == 1: # google
            cont_nm = "parts"
    
        text_arr = []
        cnt = 0
        for onem in sarrs:
            if cnt == 0:
                messages.append({'role':'system','content':onem})
            else:
                #text_arr.append({'type':'text', 'text': onem})
                text_arr.append({'text': onem})
            cnt += 1
        messages.append({'role':'user',f'{cont_nm}':text_arr})
        
    else: # others not supported yet
        messages = sarrs
    # Read and decode the JSON file from the home directory
    home_dir = os.path.expanduser("~")
    if not os.path.exists(os.path.join(home_dir, ".gofai")) or not os.path.isfile(os.path.join(home_dir, ".gofai", "gofai.json")):
        print("No gofai config file found, please run ai_server.py first")
        return ""

    json_path = os.path.join(home_dir, ".gofai", "gofai.json")
    with open(json_path, "r", encoding="utf-8") as f:
        gofai_config = json.load(f)
    prompt1 = json.dumps(messages)
    if self.use_model == "fast":
        llm_setting = self.llm_fast
    else:
        llm_setting = self.llm_slow
    
    ip = gofai_config['HOST'] #"127.0.0.1" # "192.168.1.77" 
    port = gofai_config['PORT'] #1998
    randkey = gofai_config['RANDKEY']
    if self.support:
        #setting = "{'area':'gof','user':'anm','model':1,'new_topic':1,'user_info':'nothing','stream':'0'}"
        answer =  fromLLM(prompt1,  host=ip, port=port, randkey=randkey, self=self)
    else:
        answer =  fromLLM(llm_setting+prompt1,  host=ip, port=port, randkey=randkey, self=self)
    return answer
        

def createMessagesList(self):
    selfiles = self.selected_files
    command = self.query
    messages = []
    if len(selfiles) > 1:
        for onefile in selfiles:
            if os.path.isdir(onefile):
                continue
            if onefile.endswith(('.v', '.sv')):
                limit = 10000
            else:
                limit = 12000  # multi selected files
            try:
                with open(onefile, 'r', encoding='utf-8', errors='replace') as hfile:
                    fullst = hfile.read()
                    scont = fullst[0:limit]  # Limit content size
                    messages.append(f"```{onefile}:\n{scont}\n```\n")
                    fsize = len(fullst)
                    if len(fullst) > limit:
                        print(f"Appended file {onefile} cut file size to {limit}")
                    else:
                        print(f"Reading file {onefile} size {fsize}")
            except Exception as e:
                print(f"Error in reading file {onefile} {e}")
    elif len(selfiles) == 1:
        
        onefile = selfiles[0]  # Use the stored full path directly
        fullcont = readFile(onefile)
        if re.search(r"\.logx$", onefile):
            limit = 500000
        else:
            if onefile.endswith(('.v', '.sv')):
                limit = 100000
            else:
                limit = 120000  # multi selected files
            
        chopcont = fullcont[0:limit]
        messages.append(f"```{selfiles[0]}:\n{chopcont}\n```\n")
    if self.selected_text:
        messages.append(f"The user has selected these text:\n```text\n{self.selected_text}\n```\n")
    messages.append(f"Query: {command}\n")
    return messages

        
def sharedInit(self, run_id='default', nogui=0):
    if not os.path.exists(".gofai"):
         os.mkdir(".gofai")
    self.run_id = run_id
    self.current_dir = os.getcwd()
    self.home_dir = os.getcwd()
    self.selected_files = []
    self.selected_paths = ['.'] # default to have current path
    self.include_paths = []
    self.include_files = []
    self.package_files = []
    self.lib_cells = []
    self.noa = 0 # Default with agent
    self.dfix = 0
    self.yes = 0
    self.support = 0 # not in support mode by default
    self.no_agent = 0 # GUI
    self.defines = []
    self.nogui = nogui
    self.cli_platform = ""
    if nogui:
        self.cli_mode = 1
    else:
        self.cli_mode = 0
    self.lib_files = []
    self.lib_hash = {}
    self.formal_num = 2
    
    self.selected_path = None
    self.allsetted = 0
    self.para = setupDB('gui')
    self.selected_text = ""
    vend_idx = self.para.get('sel_vend', 0)
    model_idx = self.para.get('sel_model', 0)
    self.llm_setting = f"LLM_{vend_idx}_{model_idx}_1: "
    self.llm_fast = f"LLM_1_1_0: " # 2.0 flash
    self.llm_slow = f"LLM_{vend_idx}_{model_idx}_1: "
    self.use_model = "slow"
    self.gof_config = {"Library": [], "Path": []}

    if os.path.exists("./gof.cfg"):
        #print(f"Exists gof.cfg")
        with open("./gof.cfg", 'r') as f:
            current_section = None
            cur_sec = ""
            for line in f:
                line = line.strip()
                #print(f"Do line {line}")
                if re.search(r"^=\w+=", line):
                    cur_sec = re.search(r"^=(\w+)=", line).group(1)
                    #print(f"Now section = {cur_sec}")
                elif line:
                    #print(f" append {line}")
                    if cur_sec == "Library" or cur_sec == "Path":
                        self.gof_config[cur_sec].append(line)
                    else:
                        self.gof_config[cur_sec] = line
        #print(f"GOF_CONFIG is set to {self.gof_config}")
    else:
        createGOFCFG(self)
    self.initial_model = self.para['all_models'][vend_idx][model_idx]
def createGOFCFG(self):
    st = ""
    st += "=Library=\n";
    st += "#Full-path-of-library-file\n"
    st += "=Path=\n"
    st += "#Library-directory\n"
    #st += "=gof=\n"
    #st += ".\n"
    writeFile("./gof.cfg", st)
def writeFile(filename, cont):
    with open(filename, 'w', encoding='utf-8') as f:
        f.write(cont)
    f.close()
def readFile(filename):
    cont = ""
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            cont = f.read()
    except UnicodeDecodeError:
        with open(filename, 'r', encoding='latin-1') as f:
            cont = f.read() 
    f.close()
    return cont

def setupDB(type=None):
    version = gversion()
    print(f"GOFAI {version}")
    para = {}
    if 1:
        # added local DS
        para['all_models'] = [  ["claude-3-5-haiku-20241022", "claude-3-5-sonnet-20241022", "claude-sonnet-4-20250514"], 
                                ['gemini-3.1-flash-lite-preview', 'gemini-3.1-flash-lite-preview', 'gemini-3.1-flash-lite-preview', 'gemini-3.1-pro-preview', 'gemini-3.1-pro-preview'], 
                                ["open-mistral-nemo"], #, "open-mixtral-8x7b", "mistral-medium-latest", "mistral-large-2407"], # large2 $9/1m
                                ['deepseek-chat', 'deepseek-reasoner'],
                                ['us.deepseek-chat', 'us.deepseek.r1-v1:0']
                              ]
        para['sel_vend'] = 1  # gemini
        para['sel_model'] = 3 # 2.5 pro 3-gemini3.1
        para['ip'] = 'localhost' #127.0.0.1'
        para['port'] = 1998

    return para


def parse_args():
    parser = argparse.ArgumentParser(description='GOFAI APP')
    parser.add_argument('--id', type=str, help='Specify an ID for the current run.')
    parser.add_argument('--prompt', type=str, help='The prompt.')
    parser.add_argument('--debug', type=int, help='Enable debug mode.')
    parser.add_argument('--files', nargs='*', help='List of files for RTL processing.')
    parser.add_argument('--packages', nargs='*', help='List of packages.')
    parser.add_argument('--support', action='store_true', help='Tech Support.')
    parser.add_argument('--noa', action='store_true', help='No agent, use LLM directly.')
    parser.add_argument('--dfix', action='store_true', help='Direct fix on netlist.')
    parser.add_argument('--yes', action='store_true', help='Bypass user permission, default to yes.')
    parser.add_argument('instruction', type=str, nargs='*', help='The task to perform')
    return parser.parse_args()


def do_llm_call(self, effort, command):
    selfiles = self.selected_files
    #print(f"Selfiles={selfiles} in do llm_call")
    #exit(0)
    contentofdis = self.in_display
    #system = """Help to complete the user's requirement. In code modification, unless user requests full new code output, you only need to output the modified portion of code. """ #If a code is generated, enclose the output in ```code```.\n"""
     #messages.append(system)
    need_judge = 1 # force judge in all cases
    if len(selfiles)==0:
        need_judge = 1 # only specify query
    extensions = set()
    for onefile in selfiles:
        if onefile.endswith(('.v', '.sv')):
            need_judge = 1
            print(f"{onefile} is Verilog do judging")
            break
        if onefile.endswith(('.md')):
            need_judge = 1
            print(f"{onefile} is MD do judging")
            break
        if onefile.endswith(('.php')):
            need_judge = 0
            print(f"{onefile} is PHP no judging")
            break
        ext = os.path.splitext(onefile)[1]
        extensions.add(ext)
    if len(extensions) == 1 and len(selfiles) == 2:
        need_judge = 1 # Can be diff
        
    # Initial messages list
    messages = createMessagesList(self)

    #messages.append(f"Your Answer:\n")
    totallen = 0
    for one in messages:
        totallen += len(one)
    print(f"Total bytes in messages list: {totallen}")
    

    if self.no_agent == 1 or self.support:
        print(f"no agent set {self.no_agent} or support set {self.support}")
        need_judge = 0
    
    taskdone = 0
    print(f"Need judge = {need_judge}")
    if need_judge:
        judge_messages = list(messages)
        judge_prompt = userIntention(self)

        judge_messages.insert(0, judge_prompt)
        totallen = 0
        for one in judge_messages:
             totallen += len(one)
        print(f"Total bytes in judge messages list: {totallen}")
        self.use_model = "fast"
        answer = callLLM(judge_messages, self)
        aprint("\n", self)
       
        if not os.path.exists("sandbox"):
            os.makedirs("sandbox")
        #print(f"DEBUG {answer}")
        if re.search(r"\{[^\}]+\}", answer): #re.search(r"```json", answer):  #or 
            #jstring = re.search(r'```json(.*?)```', answer, flags=re.DOTALL).group(1)
            jstring = re.search(r"(\{.*?\})", answer, flags=re.DOTALL).group(1)
            jobj = expandEnvVars(eval(jstring))
            action_match = jobj['action']
           
            if 'debug' in jobj:
                self.debug = jobj['debug']
            if 'no_exit' in jobj:
                self.debug = 1
            if 'in_files' in jobj:
                files = jobj['in_files'].split(',')
                for onefile in files:
                    if onefile in self.selected_files:
                        continue
                    self.selected_files.append(onefile)
                    dir_name = os.path.dirname(onefile)
                    if not dir_name in self.selected_paths:
                        self.selected_paths.append(dir_name)
            if 'modified_query' in jobj:
                newcommand = jobj['modified_query']
                if newcommand:
                    self.query = newcommand
                    aprint(f"Modified query to: {newcommand}\n", self)
            main_file = self.selected_files[0] if len(self.selected_files) else ""
            if os.path.exists(main_file) and main_file.endswith(('.v','.sv')) and (action_match == 'update_verilog' or action_match == 'verilog2spec'):
                cont = readFile(main_file)
                mcont = contWOCom(cont, self)
                module_names = re.findall(r'^\s*module\s+(\w+)', mcont, re.MULTILINE) 
                if len(module_names) > 1:
                    rtldir = ".gofai/split_rtl"
                    if not os.path.exists(rtldir):
                        os.makedirs(rtldir)
                    allmodules = re.findall(r'(?m)^\s*module\s+(\w+)\s*(.*?)\bendmodule\b', mcont, re.DOTALL)
                    for mod_name,mod_body in allmodules:
                        fmod = f"module {mod_name} {mod_body}\nendmodule\n"
                        writeFile(f"{rtldir}/{mod_name}.v", fmod)
                    sel_module = module_names[0]
                    if 'module_name' in jobj:
                        sel_module = jobj['module_name']
                    if sel_module in module_names:
                        self.selected_files[0] = f"{rtldir}/{sel_module}.v"
                        if not rtldir in self.selected_paths:
                            self.selected_paths.insert(0, rtldir)
                        aprint(f"The main Selected module {sel_module} is forced to be {self.selected_files[0]}\n", self)
            
            if not re.search(r'^(gof_|write_script)', action_match):
                messages = createMessagesList(self)
            if self.support or self.noa or self.no_agent==1: # highest priority
                taskdone = 0 # go with callLLM
            elif action_match == "linux_command":
                cmds = jobj['command']
                cmdst = " ".join(cmds)
                user_input = input(f"Do you want to run the command {cmdst} (y/n) ").strip()
                if user_input == 'y':
                    subprocess.run(cmdst, shell=True) #stdout=sys.stdout, stderr=sys.stderr)
                taskdone = 1
            elif action_match == "update_verilog":
                print("LLM indicated to update verilog.")
                processVerilog(1, messages, self)
                taskdone = 1
            elif action_match == "write_verilog":
                print("LLM indicated to write verilog from Spec.")
                processMD(self)
                taskdone = 1
            elif action_match == "verilog2spec":
                print("LLM indicated to convert verilog to spec.")
                processVerilog(2,  messages, self)
                taskdone = 1
            elif action_match == "diff_verilog":
                print(f"LLM indicated to diff verilog files {selfiles}.")
                diffVerilog(selfiles, command, self)
                taskdone = 1
            elif action_match == "diff_file":
                print("LLM indicated to diff files.")
                diffFile(selfiles, command, self)
                taskdone = 1
            elif action_match == "assertion_verilog":
                # Check if first file has more than two modules
                if len(selfiles) > 0 and selfiles[0].endswith(('.v', '.sv')):
                    module_count = len(re.findall(r'^\s*module\s+\w+', readFile(selfiles[0]), re.MULTILINE))
                    if module_count > 2:
                        print(f"Warning: {selfiles[0]} contains {module_count} modules (more than 2)")
                print("LLM indicated to SysV assertion.")
                sysVAssertion(messages, self)
                taskdone = 1
            elif action_match == "logic":
                print("LLM indicated to find logic.")
                keywords = []
                if 'keywords' in jobj:
                    keywords = jobj['keywords'].split(',')
                findLogic(command, selfiles, keywords, self)
                taskdone = 1
            elif action_match == "assertion_verilog":
                print("LLM indicated to SysV assertion.")
                sysVAssertion(messages, self)
                taskdone = 1
            elif re.search(r'^gof_', action_match): # gof_eco/gof_sch/gof_lec/gof_run/gof_debug/gof_ana
                print(f"LLM indicated to {action_match}.")
                netlistECO(action_match, command, self)
                taskdone = 1
            elif action_match == "need_file_content" and "in_file" in jobj:
                thefile = jobj['in_file']
                if not os.path.exists(thefile):
                    aprint(f"ERROR! Input file {thefile} doesn't exist\n", self)
                    return
                incont = readFile(jobj['in_file'])
                if re.search(r"^\s*\/\/", incont, re.MULTILINE):
                    self.query += "(Those lines with // in the beginning are commented out and should be not considered)\n"
                self.query += f"```{jobj['in_file']}\n{incont}\n```\n"
                command = self.query
                do_llm_call(self, effort, command)
                taskdone = 1
    if not taskdone:
        # Pass the stop_event to the LLM call to allow checking during processing
        aprint(f"Query: {command}\n", self)
        aprint("Answer: ", self)
        if len(selfiles):
            system = """Help to complete the user's requirement. In code modification, unless user requests full new code output, you only need to output the modified portion of code. """ #If a code is generated, enclose the output in ```code```.\n"""
            messages.insert(0, system)
            self.use_model = "fast" #slow"
            answer = callLLM(messages, self)
        else:
            messages = [command]
            if self.support:
                 self.use_model = "fast"
            else:
                self.use_model = "slow"
            answer = callLLM(messages, self)
            #ip = "127.0.0.1" # "192.168.1.77" 
            #answer = fromLLM(command,  host=ip, port=1998, self=self)
        if 'out_file' in jobj and re.search(r"```\w+", answer):
            code = re.search(r"```\w+\n(.*)```", answer, flags=re.DOTALL).group(1)
            writeFile(jobj['out_file'], code)
            aprint(f"\nWrite to file {jobj['out_file']}\n", self)
    outputlen = len(answer)
    print(f"END-OF-TASK")
    
def userIntention(self):
    prompt = """
Read the full context of the following files and the query. And give an answer only about the intention of the user. Return a JSON string with
proper fields:
'module_name' --- If user specifies module name
'action' --- The action to be taken
'command' --- The Linux command if the 'action' is 'linux_command'
'in_files' --- The input file names invovled, separated by ','
'keywords' --- The keyworkds in the query, separated by ','
'modified_query' --- If the query needs to be modified for better clarity
'out_file' --- If user specifies file output 
'debug' --- Debug mode
'no_exit' --- Do not exit
If the user asks to follow instruction in a file but doesn't provide the file content, return {'action':'need_file_content','in_file':'the_file'}        
If the user wants to write a script, return {'action':'write_script'}  
If the user wants to update or modify the Verilog, return {'action':'update_verilog'}  
If the user wants to write Verilog from spec, return {'action':'write_verilog'}  
If the user wants to convert Verilog to spec, return {'action':'verilog2spec'}  
If the user wants to create System Verilog assertion, return {'action':'assertion_verilog'}  
If the user wants to use llm to directly fix netlist by the diff information of two verilog RTL files, return {'action':'gof_dfix'}  
If the user wants to diff two verilog RTL files for ECO analysis, return {'action':'gof_ana'}  
If the user wants to diff two files for changes, return {'action':'diff_file'}  
If the user wants to find out the logic driver of signal abc_def, return {'action':'logic'}  
If the user wants to do netlist ECO, return {'action':'gof_eco'}
If the user wants to find the reason why one instance is in REF but not in IMP netlist, return {'action':'gof_map'}    
If the user wants to do schematic, return {'action':'gof_sch'}  
If the user wants to run LEC on two netlist files, return {'action':'gof_lec'}
If the user wants to do general query on some context, return {'action':'general_query'}
If the user wants to run a script on netlist, return {'action':'gof_run'}
If the user wants to load a netlist for further debug, return {'action':'gof_debug'}
If Linux command is need to complete user query, return {'action':'linux_command','command':['the_command','arg1',arg2']} Warning! Never remove files
If the user wants to write a document or an article, return {'action':'gen_doc'}  
If the user wants to generate an image, return {'action':'gen_img'}  
For other cases, return {'action':'other'}  
Ouput the result without explanation. Always use ```json``` to wrapper around the answer 
These examples are for your referene

EXAMPLE INPUT:
```abc.v
module abc(a,b,c);
input a,b;
output c;
assign c = a&b;
endmodule
```
Query: change the signal 'c' to 'a' xor 'b'

EXPECTED OUTPUT:
```json
{'action':'update_verilog'}
```

EXAMPLE INPUT:
```abc_ctrl.v
module abc(a_b_i,b_c_i,c_d_o,z_uu_oo);
input a_b_i,b_c_i;
output c_d_o,z_uu_oo;
assign c_d_o = a_b_i&b_c_i;
assign z_uu_oo = a_b_i|b_c_i;
endmodule
```
Query: What drives the signal z_uu_oo in the design? And what is the fanout of a_b_i?

EXPECTED OUTPUT:
```json
{'action':'logic','keywords':'z_uu_oo,a_b_i'}
```

EXAMPLE INPUT:
What is a possible ECO for the following module?
```abc_ctrl.v
module abc(a_b_i,b_c_i,c_d_o,z_uu_oo);
input a_b_i,b_c_i;
output c_d_o,z_uu_oo;
assign c_d_o = a_b_i&b_c_i;
assign z_uu_oo = a_b_i|b_c_i;
endmodule
```

EXPECTED OUTPUT:
```json
{'action':'general_query'}
```

EXAPMPLE INPUT:
Query: Convert the module 'e_compress' in Verilog file /proj/aichip/rtl/nv_chip.v to Spec, use library file /proj/aichip/lib/dig_cells.v, no formal run
EXPECTED OUTPUT:
```json        
{'action':'verilog2spec','in_files':'/proj/aichip/rtl/nv_chip.v,/proj/aichip/lib/dig_cells.v','module_name':'e_compress','modified_query':'Convert the module e_compress to spec, no formal run'}
```

EXAPMPLE INPUT:
Query: Convert the spec file myspec.current/micro_spec.md to Verilog and write out the Verilog to sram_control.v, search in the current directory and sub-directories for library cells
EXPECTED OUTPUT:
```json        
{'action':'write_verilog','in_files':'./myspec.current/micro_spec.md,.','modified_query':'Convert the spec to Verilog file sram_control.v'}
```

EXAPMPLE INPUT:
Query: Do a RTL guided ECO, the netlist and RTL are in the current directory, debug mode set to 2
EXPECTED OUTPUT:
```json        
{'action':'gof_eco','module_name':'','debug':2}
```

EXAPMPLE INPUT:
Query: Load netlist mynet.v and load schematic of u_abc/con_reg, do not exit
EXPECTED OUTPUT:
```json        
{'action':'gof_sch','no_exit':1}
```
    
EXAPMPLE INPUT:
Query: Load netlist mynet.v and apply the_man_change.pl on it, set debug mode to 1
EXPECTED OUTPUT:
```json        
{'action':'gof_run','debug':1}
```

EXAPMPLE INPUT:
Query: Load netlist mynet.v 
EXPECTED OUTPUT:
```json        
{'action':'gof_debug'}
```

EXAMPLE INPUT:
Query: analyze the original rtl rtl1.sv and modified rtl rtl2.sv, give suggestion on how to improve the ECO and minimize the patch size
EXPECTED OUTPUT:
```json
{'action':'gof_ana'}
```
    
EXAMPLE INPUT:
Query: use llm to directly fix netlist by the diff information of two verilog RTL files
EXPECTED OUTPUT:
```json
{'action':'gof_dfix'}
```
    
EXAMPLE INPUT:
Query: debug this error, reference instance u_con/u_def/U123 is not in IMP netlist my_imp.gv, the REF netlist is my_ref.gv
EXPECTED OUTPUT:
```json
{'action':'gof_map'}
```

    
EXAMPLE INPUT:
Do as the file myask.md says
EXPECTED OUTPUT:
```json
{'action':'need_file_content','in_file':'myask.md'}
```

EXAMPLE INPUT:
List the log files in the current directory
EXPECTED OUTPUT:
```json
{'action': 'linux_command', 'command':['ls', '*.log']}
```
    
"""
    return prompt


def aprint(info, self=None):
    
    print(f"{info}", end='')
    logfile = f"log.gofai.{self.run_id}.log"
    loghand = open(logfile, 'a')
    loghand.write(f"{info}\n")
    loghand.flush()
def writeFile(filename, cont):
    with open(filename, 'w', encoding='utf-8') as f:
        f.write(cont)
    f.close()
def readFile(filename):
    cont = ""
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            cont = f.read()
    except UnicodeDecodeError:
        with open(filename, 'r', encoding='latin-1') as f:
            cont = f.read() 
    f.close()
    return cont


def expandEnvVars(obj):
    """Recursively expand Linux environment variables ($VAR, ${VAR}) and a
    leading '~' in every string value of a decoded JSON structure. Dicts,
    lists and scalar values are traversed/copied accordingly. Unknown
    variables are left untouched by os.path.expandvars, so non-path strings
    are not affected."""
    if isinstance(obj, str):
        return os.path.expanduser(os.path.expandvars(obj))
    if isinstance(obj, dict):
        return {k: expandEnvVars(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [expandEnvVars(v) for v in obj]
    return obj


def _clear_dir(directory_path):
    """Clears a directory or creates it if it doesn't exist."""
    if os.path.exists(directory_path):
        for filename in os.listdir(directory_path):
            file_path = os.path.join(directory_path, filename)
            try:
                if os.path.isfile(file_path) or os.path.islink(file_path):
                    os.unlink(file_path)
                elif os.path.isdir(file_path):
                    shutil.rmtree(file_path)
            except Exception as e:
                print(f"Failed to delete {file_path}. Reason: {e}")
    else:
        os.makedirs(directory_path)

def _create_temp_module_files(temp_dir, full_verilog_file_path):
    """
    Splits a single Verilog file into individual module files in a temporary directory.
    Each module is saved as <module_name>.v.
    """
    vcont = readFile(full_verilog_file_path)
    # Split by 'endmodule' and then filter for actual modules
    # Use re.split with a capturing group to keep the delimiter
    vmods = re.split(r'(endmodule)', vcont, flags=re.IGNORECASE | re.DOTALL)

    current_module_content = ""
    for part in vmods:
        current_module_content += part
        if part.lower() == 'endmodule':
            # Check if the accumulated content contains a module definition
            submod_name_match = re.search(r"module\s+(\w+)\s*\(", current_module_content, re.IGNORECASE)
            if submod_name_match:
                submod_name = submod_name_match.group(1)
                writeFile(os.path.join(temp_dir, f"{submod_name}.v"), current_module_content.strip())
            current_module_content = "" # Reset for next module
    # Handle any remaining content after the last endmodule (e.g., comments, directives)
    if current_module_content.strip():
        # If it contains a module definition, save it. Otherwise, ignore.
        if re.search(r"module\s+(\w+)\s*\(", current_module_content, re.IGNORECASE):
            submod_name_match = re.search(r"module\s+(\w+)\s*\(", current_module_content, re.IGNORECASE)
            if submod_name_match:
                submod_name = submod_name_match.group(1)
                writeFile(os.path.join(temp_dir, f"{submod_name}.v"), current_module_content.strip())

# --- Core Diff and LLM Interaction Logic (adapted from diffRTL2ECO.py) ---

def _real_diff_op(self, cnt, file1, file2, alldiff, subtop):
    """
    Performs the actual LLM calls for ECO suggestions and change list generation
    for a given diff chunk.
    """
    aprint(f"\n## ECO Suggestion for {subtop}\n", self)
    #button_command = f'tkdiff "{file1}" "{file2}"'
    aprint(f"tkdiff {file1} {file2}\n", self)

    #aprint(f"Diffing {os.path.basename(file1)} and {os.path.basename(file2)}...\n", self)
    if not alldiff.strip():
        aprint(f"No significant differences found between {os.path.basename(file1)} and {os.path.basename(file2)}.\n", self)
        return 0

    # LLM call for ECO suggestion
    jinfo_eco = []
    sys_string = f"""You are Verilog digital design and synthesis expert. You are requested to review the changes from Verilog1 {os.path.basename(file1)} to Verilog2 {os.path.basename(file2)}. Suggest an ECO process to re-model the RTL changes to minimize the ECO patch size. Examples:

EXAMPLE INPUT:
1. Tie a hierarchical input pin to zero, the lines listed below starting with '-' is from Verilog1, '+' is from Verilog2, other lines are shared: 
    mod_abc u_abc (
        .clk(clk),
-       .ina(ina[7:0]),
+       .ina(8'h0),
        .inb(inb[4:0]),
        .outa(outa)
     );      

EXPECTED OUTPUT:
```perl
for(my $i=0; $i<8; $i++) {{
    change_pin("u_abc/ina[$i]", "1'b0");
}}
```
Note: The API change_pin should be used on single bit port only. If the port is a bus, change_pin should apply on each bit one by one.

EXAMPLE INPUT:
2. Insert a simple logic to a hierarchical pin. Diffs are:
    mod_abc u_abc (
        .clk(clk),
        .ina(ina[7:0]),
-       .inb(inb[4:0]),
+       .inb(inb_new[4:0]),
        .outa(outa)
     );
+   assign inb_new = test_mode? 5'b10011 : inb[4:0];

EXPECTED OUTPUT:
```perl
do_auto_change("action=insert_gate", "instance_pin=u_abc/inb[4], "gate_type=or2", "connections=test_mode");
do_auto_change("action=insert_gate", "instance_pin=u_abc/inb[3], "gate_type=and2", "connections=test_mode");
do_auto_change("action=insert_gate", "instance_pin=u_abc/inb[2], "gate_type=and2", "connections=test_mode");
do_auto_change("action=insert_gate", "instance_pin=u_abc/inb[1], "gate_type=or2", "connections=test_mode");
do_auto_change("action=insert_gate", "instance_pin=u_abc/inb[0], "gate_type=or2", "connections=test_mode");
```

EXAMPLE INPUT:
--- algo.v
+++ algo.v
@@ -1,22 +1,22 @@
 output [8:0] adval;
 reg [8:0] adval;
 always @(posedge clk or negedge rst) begin
    if(~rst) adval <= 9'h0;
-   else adval <= in1 + in2 + aen;
+   else adval <= in1 + in2 + aen + in3[1:0];
 end

EXAMPLE OUTPUT:
// To minimize the patch, the RTL is suggested to re-modifed:
// 1. Keep arithmatic logic untouched
// 2. Add the new logic by pipeline flops and outside adder
output [8:0] adval;
reg [8:0] iadval;
reg [1:0] in3_d;
always @(posedge clk or negedge rst) begin
   if(~rst) in3_d <= 2'b0;
   else in3_d <= in3[1:0];
end
assign adval = iadval + in3_d;

always @(posedge clk or negedge rst) begin
   if(~rst) iadval <= 9'h0;
   else iadval <= in1 + in2 + aen;
end

    
    
"""
    jinfo_eco.append(sys_string)

    diff_string = f"""The difference string is defined here: {alldiff}"""
    jinfo_eco.append(diff_string)

    user_string = "Please review the difference and provide ECO suggestions"
    jinfo_eco.append(user_string)

    aprint(f"Calling LLM for ECO suggestion for module {subtop}...\n", self)
    answer_eco = callLLM(jinfo_eco, self)

    #aprint(answer_eco + "\n", self)
    return 1
def changeList(answer_eco, subtop, self):
    if re.search(r"```perl", answer_eco, re.IGNORECASE):
        scont = re.search(r"```perl\n([\s\S]*?)\n```", answer_eco, re.IGNORECASE).group(1)
        if 'ecoscript_output' not in self.para:
            self.para['ecoscript_output'] = ""
        self.para['ecoscript_output'] += f"push_top(\"{subtop}\");\n"
        self.para['ecoscript_output'] += scont + "\n"
        self.para['ecoscript_output'] += "pop_top();\n"
        aprint("\nECO script extracted and appended.\n", self)
    #else:
    #    aprint("No Perl ECO script found in the LLM response.\n", self)

    # LLM call for Change List
    jinfo_changelist = []
    jinfo_changelist.append(f"""You are Verilog digital design and synthesis expert. You are requested to review the changes from Verilog1 {os.path.basename(file1)} to Verilog2 {os.path.basename(file2)}.""")
    jinfo_changelist.append(f"The difference string is defined here: {alldiff}")
    jinfo_changelist.append(f"Please describe the change list from {os.path.basename(file1)} to {os.path.basename(file2)}.")

    aprint(f"\n\n## Change List for {subtop}\n", self)
    answer_changelist = callLLM(jinfo_changelist, self)
    #
    #aprint(answer_changelist + "\n", self)
    if 'changelist_output' not in self.para:
        self.para['changelist_output'] = ""
    self.para['changelist_output'] += f"\n## Module {subtop} Change list\n"
    self.para['changelist_output'] += answer_changelist + "\n"

    return 1

def _diff_files(self, cnt, file1, file2, subtop):
    """
    Generates a unified diff between two files and processes it in chunks
    for LLM analysis.
    """
    fcont1 = readFile(file1)
    fcont2 = readFile(file2)
    lines1 = fcont1.strip().splitlines()
    lines2 = fcont2.strip().splitlines()
    alldiff = ""
    ret = 0
    # Using n=50 for context lines, similar to original script
    for line in difflib.unified_diff(lines1, lines2, fromfile=os.path.basename(file1), tofile=os.path.basename(file2), lineterm='', n=150):
        alldiff += line + "\n"
        # Chunk large diffs for LLM processing
        if len(alldiff) > 40000:
            ret += _real_diff_op(self, cnt + ret, file1, file2, alldiff, subtop)
            alldiff = ""
    if alldiff:
        ret += _real_diff_op(self, cnt + ret, file1, file2, alldiff, subtop)
    return ret

def _compare_dirs(self, dir1, dir2, top_module_name):
    """
    Compares all Verilog module files within two directories, identifying
    added, deleted, and changed modules.
    """
    aprint(f"Comparing modules in '{dir1}' and '{dir2}'...\n", self)
    cnt = 0 # Count of changed/added/deleted files
    same = 0 # Count of identical files

    # Get all files from both directories to ensure we catch added/deleted files too
    files1 = {f for f in os.listdir(dir1) if os.path.isfile(os.path.join(dir1, f)) and f.lower().endswith(('.v', '.sv'))}
    files2 = {f for f in os.listdir(dir2) if os.path.isfile(os.path.join(dir2, f)) and f.lower().endswith(('.v', '.sv'))}

    all_module_files = sorted(list(files1.union(files2)))

    for file_name in all_module_files:

        fullfile1 = os.path.join(dir1, file_name)
        fullfile2 = os.path.join(dir2, file_name)

        # Extract subtop name (assuming filename is module_name.v)
        subtop_match = re.search(r"(\w+)\.(v|sv)$", file_name, re.IGNORECASE)
        subtop = subtop_match.group(1) if subtop_match else file_name # Fallback to filename

        if os.path.exists(fullfile1) and os.path.exists(fullfile2):
            # Both files exist, perform diff
            diff_result = _diff_files(self, cnt, fullfile1, fullfile2, subtop)
            if diff_result > 0:
                cnt += 1
            else:
                same += 1
        elif os.path.exists(fullfile1) and not os.path.exists(fullfile2):
            # File deleted in new version
            aprint(f"Module '{subtop}' ({file_name}) deleted in new version.\n", self)
            if 'changelist_output' not in self.para: self.para['changelist_output'] = ""
            self.para['changelist_output'] += f"\n=== Module {subtop} Change list ===\n"
            self.para['changelist_output'] += f"Module {subtop} was deleted.\n"
            cnt += 1
        elif not os.path.exists(fullfile1) and os.path.exists(fullfile2):
            # File added in new version
            aprint(f"Module '{subtop}' ({file_name}) added in new version.\n", self)
            if 'changelist_output' not in self.para: self.para['changelist_output'] = ""
            self.para['changelist_output'] += f"\n=== Module {subtop} Change list ===\n"
            self.para['changelist_output'] += f"Module {subtop} was added.\n"
            cnt += 1

    aprint(f"Total changed/added/deleted modules: {cnt}, same modules: {same}\n", self)


    
def processMD(self):
    command = self.query
    dfile = self.selected_files[0]
    llm_setting = self.llm_setting
    sel_vend = self.para['sel_vend']
   
    mdcontent = readFile(dfile)    # Pass the stop_event to the LLM call to allow checking during processing
    system = """Use the content of MD file as spec and user requirement. Convert to Verilog module. Enclose the output in ```verilog```."""
    messages = []
    messages.append(system)
    messages.append(f"The content of the Mark Down file {dfile}:\n```context\n{mdcontent}\n```\n")
    messages.append(f"Complete the user command {command}\n")

    #answer = callLLM(messages, self)
    processVerilog(0, messages, self)
    
def _monitor_log_file(log_file_path, process, app_queue, self):
    """
    Monitors a log file for new content and puts it into a queue for the main thread.
    """
    # Wait for the log file to be created (it might not exist immediately)
    max_wait_time = 100 # seconds
    start_time = time.time()
    while not os.path.exists(log_file_path) and (time.time() - start_time < max_wait_time):

        time.sleep(0.1) # Wait a bit before checking again

    if not os.path.exists(log_file_path):
        app_queue.put(f"Error: Log file '{log_file_path}' was not created within {max_wait_time} seconds.\n")
        app_queue.put("STOP")
        return

    try:
        with open(log_file_path, 'r', encoding='utf-8', errors='replace') as f:
            # Should read all, f.seek(0, os.SEEK_END) # Go to the end of the file initially
            # Read any existing content first
            for line in f:
                app_queue.put(line)

            # Loop while the process is running OR there's still content to read
            while process.poll() is None or f.tell() < os.fstat(f.fileno()).st_size:

                line = f.readline()
                if line:
                    app_queue.put(line) # Put new line into the queue for the main thread
                else:
                    time.sleep(0.1) # Wait a bit if no new lines are available yet

            # After the process finishes, read any remaining lines to ensure nothing is missed
            for line in f:
                app_queue.put(line)
    except Exception as e:
        app_queue.put(f"Error monitoring log file '{log_file_path}': {e}\n")
        traceback.print_exc()
    finally:
        app_queue.put("STOP") # Signal that monitoring is complete

def getPinList(candidate_file):
    """
    Reads a Verilog file, extracts port declarations, and returns a dictionary
    of port information.

    Args:
        candidate_file (str): The path to the Verilog file.

    Returns:
        dict: A dictionary where keys are port names and values are dictionaries
              containing 'dir' (direction) and 'bus' (bus information).
              Example:
              {
                  'abc': {'dir': 'input', 'bus': [1, 7, 0]},
                  'def': {'dir': 'output', 'bus': [0]}
              }
              Returns an empty dictionary if the file cannot be read or no ports are found.
    """
    pinlist = {}
    if not os.path.isfile(candidate_file):
        print(f"Error: File not found at {candidate_file}")
        return pinlist

    ivcont = readFile(candidate_file) # Use readFile as requested
    vcont = removeComments(ivcont)
    pinlist = extractPinList(vcont)
    return pinlist
    
def removeComments(ivcont):
    # 1. Remove all comments
    # Remove block comments /* ... */
    vcont = re.sub(r'/\*.*?\*/', '', ivcont, flags=re.DOTALL)
    # Remove single-line comments // ...
    vcont = re.sub(r'//.*', '', vcont)
    return vcont
    
def extractPinList(vcont):
    pinlist = {}
    # regex to capture Verilog port declarations in both ANSI and non-ANSI styles.
    # It finds direction (input/output/inout), optional type (wire/reg/logic),
    # optional bus range, and the port name. It is designed to work on the whole
    # file content to find all declarations.
    port_pattern = re.compile(
        r"(input|output|inout)\s+"                  # Group 1: Direction (input, output, inout)
        r"(?:wire|reg|logic)?\s*"                   # Optional type like wire, reg, logic
        r"(?:\[\s*(\d+)\s*:\s*(\d+)\s*\]\s*)?"      # Group 2, 3: Optional bus range [max:min]
        r"(\w+)",                                  # Group 4: Port name
        re.MULTILINE | re.IGNORECASE
    )

    for match in port_pattern.finditer(vcont):
        direction = match.group(1)
        bit_max_str = match.group(2)
        bit_min_str = match.group(3)
        port_name = match.group(4)

        pin_info = {'dir': direction}

        if bit_max_str is not None and bit_min_str is not None:
            # It's a bus
            bit_max = int(bit_max_str)
            bit_min = int(bit_min_str)
            pin_info['bus'] = [1, bit_max, bit_min]
        else:
            # Not a bus
            pin_info['bus'] = [0]

        pinlist[port_name] = pin_info

    return pinlist

def createVerilog(module_name, pinlist):
    """
    Creates a Verilog module definition string (module and port list)
    from a given module name and a dictionary of pin information.

    Args:
        module_name (str): The name of the Verilog module.
        pinlist (dict): A dictionary where keys are port names and values are dictionaries
                        containing 'dir' (direction) and 'bus' (bus information).
                        Example:
                        {
                            'abc': {'dir': 'input', 'bus': [1, 7, 0]},
                            'def': {'dir': 'output', 'bus': [0]}
                        }

    Returns:
        str: The Verilog module definition string.
    """
    ports_declarations = []
    # Sort ports for consistent output
    sorted_port_names = sorted(pinlist.keys())

    for port_name in sorted_port_names:
        pin_info = pinlist[port_name]
        direction = pin_info['dir']
        bus_info = pin_info['bus']
        
        port_declaration = f"    {direction}"
        if bus_info[0] == 1: # It's a bus
            bit_max = bus_info[1]
            bit_min = bus_info[2]
            port_declaration += f" [{bit_max}:{bit_min}]"
        
        port_declaration += f" {port_name}"
        ports_declarations.append(port_declaration)

    # Join port declarations with ',\n' and add indentation
    ports_string = ",\n".join(ports_declarations)

    verilog_code = f"module {module_name} (\n{ports_string}\n);\n\n"
    verilog_code += f"endmodule\n"

    return verilog_code


def extractVerilogInfo(incr, userm, self):
    topmodule = "verilog_inc"
   
    if incr==1:
        intend = "update the verilog file"
    elif incr==2:
        intend = "convert verilog to spec"
    else:
        intend = "write a verilog file from the spec"
    system = f"""You are Verilog expert. The user intends to {intend}. Analyze the context and extract related parameters and return JSON string with proper fields.
    'top_module' : The top level module name
    'lib_file' : Verilog library files, if user specifies Verilog library file, add it into 'lib_file' field. Multiple files separated by ','. Use the full path of the files.
    'lib_cell' : Verilog library cells, when an instance is not resolved in the top level module, it should be treated as library cell. Do not list unresolved cells from library file. Mutiple cells should be separated by ','.
    'define'   : User defined variables, multiple definitions are separated by ',', like "'define':'ONE_BCD=1,DEF_TWO=2'", ignore `define statements in Verilog file.
    'out_file' : Write out file name
    'formal_num'  : Run Formal/LEC numbers, 0 to 5, default set to 1, if user specify a number out of range, set it to 5
    'is_testbench' : Set it to one if the context means to fix a testbench
    'package_file' : " package file name when there is such statement "module mod_abc import one_pack::*;", only name without path. Separated multiple package files by ','
    'include_file' : "Include file name extract from `include "state_file.v", only name without path. Separated multiple include files by ','


    EXAMPLE INPUT:
    ```spec1.md
    The Mark Down spec:
    Build a circuit that always output a HIGH, define USE_HIGH_CELL to one, write the generated verilog to log_high.v. No formal run.
    module TopModule (
      output HIGHT
    );
    
    EXPECTED OUTPUT:
    ```json
    {{'top_module':'TopModule','lib_cell':'','out_file':'log_high.v','define':'USE_HIGH_CELL=1','formal_num':0}}
    ```

    EXAMPLE INPUT:
    ```rr_sm.v
    module rr_sm import my_pack::*; (valid,inc,clk,rst,state);
    import extra_pack::*;
    input valid, inc, clk, rst;
    output [1:0] state;
    `include "rr_proj_def.v"
    wire inc_out;
    reg [B_W-1:0] state, next_state;
    always @(posedge clk or negedge rst) begin
      if(~rst) state <= 2'b00;
      else state <= next_state;
    end
    inc_block u_inc_block(.inc(inc),.inc_out(inc_out),.rst(rst),.clk(clk));
    my_and U_DONT(.in1(inc),.in2(inc_out),.out(pulse_out));
    always @(*) begin
      case(state)
        2'b00: if(valid | pulse_out) next_state <= 2'b01;
        2'b01: if(inc_out) next_state <= 2'b11;
        default: next_state <= 2'b00;
      endcase
    end
    endmodule
    ```
    ```/prj/top_ai/lib/dig_lib.v
    module my_and(in1,in2,out);
    input in1,in2;
    output out;
    assign out = in1 & in2;
    endmodule
    ```
    User: Modify the verilog to add a synchronization cell sync2_cell to valid signal, use dig_lib.v as lib file, run formal two times

    EXPECTED OUTPUT:
    ```json
     {{'top_module':'rr_sm','lib_cell':'inc_block,sync2_cell,my_and','package_file':'my_pack.v,extra_pack.v','include_file':'rr_proj_def.v','lib_file':'/prj/top_ai/lib/dig_lib.v','formal_num':2}}
    ```
    
    """

    messages = []
    messages.append(system)
    messages.append("The following is the user context:\n======\n")
    for onem in userm:
        messages.append(onem)
    messages.append("\n=====\nThe end of user context\n")
    messages.append(f"Output the JSON\n")

    answer = callLLM(messages, self)
    aprint("\n", self)
   

    if re.search(r'```json', answer):
        jstring = re.search(r'```json(.*?)```', answer, flags=re.DOTALL).group(1)
        jobj = expandEnvVars(eval(jstring))
        topmodule = jobj['top_module']
        self.top_module = topmodule
        if "out_file" in jobj:
            self.out_file = jobj['out_file']
        else:
            self.out_file = ""
        if "formal_num" in jobj:
            self.formal_num = jobj['formal_num']
        for item in ('lib_cell', 'package_file', 'include_file', 'lib_file', 'define'):
            if item in jobj:
                sub_items = [cell.strip() for cell in jobj[item].split(',') if cell.strip()]
                
                if item == "package_file":
                    findPackageFiles(sub_items, self)
                elif item == "include_file":
                    findIncludeFiles(sub_items, self)
                elif item == "define":
                    self.defines = sub_items
                elif item == "lib_cell":
                    self.lib_cells = sub_items
                elif item == "lib_file":
                    self.lib_files = sub_items
                if len(sub_items):
                    aprint(f"INFO! {item} is assigned to {sub_items}\n", self)
 
    workOnLibs(self)
    for onelib in self.lib_cells:
        if not onelib in self.lib_hash:
            aprint(f"ERROR! {onelib} can't be resolved\n", self)
            return 1
    aprint(f"INFO! All lib cells resolved\n", self)
    return 0
def withInterface(vfile, self):
    cont = readFile(vfile)
    simple = contWOCom(cont, self)
    tmpfile = f"sandbox/tmpvfile.v"
    writeFile(tmpfile, simple)
    pkgs = re.findall(r'\bimport\s+(\w+)::', simple)
    pro_files = [vfile]
    for pkg in pkgs:
        rootpkg = pkg+".v"
        altpkg = pkg+".sv"
        pkg_file = searchOneFile([rootpkg, altpkg], self)
        pro_files.insert(0, pkg_file)
    for pkg in self.package_files:
        pro_files.append(pkg)
    gof_path = get_gof_path(self) #self.gof_config['gof']
    gsvp = f"{gof_path}/gsvp"
    file_st = " ".join(pro_files)
    tmp_out = "sandbox/tmp_outvfile.v"
    if os.path.exists(tmp_out):
        os.remove(tmp_out)
    defst = ""
    for define in self.defines:
        defst += f"--define={define} "
    incst = ""
    for path in self.include_paths:
        incst += f"--incdir={path} "
    for onedir in self.selected_paths:
        incst += f"--incdir={onedir} "
    cmd = f"{gsvp} {defst} {incst} {file_st} > {tmp_out}"
    print(f"Do gsvp {cmd}\n")
    subprocess.run(cmd, shell=True)
    vstring = readFile(tmp_out)
    result = ""
    vstring = re.sub(r'\bfunction\b.*?\bendfunction\b', '', vstring, flags=re.DOTALL)
    vstring = re.sub(r'\bimport\b\s+\w+::.*?;', '', vstring)
    vstring = re.sub(r'\n\s*\n', '\n', vstring)
    modules = re.findall(r'\bmodule\s+(\w+)\s*([^;]+);(.*?)(?=module|\Z)', vstring, re.DOTALL)
    for mod_name,ports,cont in modules:
        ports_clean = re.sub(r'\s+', ' ', ports.strip())
        port_declare = []
        port_lines = re.findall(r'^\s+((parameter|input|output|inout).*?;)', cont, re.MULTILINE)
        for pline in port_lines:
            line = pline[0]
            line_clean = re.sub(r'\s+', ' ', line.strip())
            line_clean = re.sub(r'\b(signed|logic)\b', '', line_clean)
            port_declare.append(f"    {line_clean}")
        module_output = f"module {mod_name} {ports_clean};\n"
        for declare in port_declare:
            module_output += f"{declare}\n"
        module_output += "\nendmodule\n"
        result += module_output
    return result

def libFilePro(libfile, self):
    cache_dir = ".gofai/lib_cache"
    os.makedirs(cache_dir, exist_ok=True)
    if os.path.exists(cache_dir+"/"+libfile) and not os.path.exists(libfile):
        libfile = cache_dir+"/"+libfile
    aprint(f"Do lib file {libfile}\n", self)
    mod_cont = withInterface(libfile, self)
    if not os.path.exists(libfile):
        aprint(f"Lib file {libfile} doesn't exist, LLM may return wrong information\n", self)
        return
    ori_cont = removeComments(readFile(libfile))
    ori_allmodules = re.findall(r'(?m)^\s*module\s+(\w+)\s*(.*?)\bendmodule\b', ori_cont, re.DOTALL)
    mod_cont = withInterface(libfile, self)
    #print(f"Lib file {libfile} content after processing:\n{mod_cont}\n")
    allmodules = re.findall(r'(?m)^\s*module\s+(\w+)\s*(.*?)\bendmodule\b', mod_cont, re.DOTALL)

    len1 = len(ori_allmodules)
    len2 = len(allmodules)
    for index in range(len1):
        mod_name = allmodules[index][0]
        mod_body = allmodules[index][1]
        if len1 == len2:
            ori_mod_name = ori_allmodules[index][0]
            if mod_name != ori_mod_name: # gsvp can change module name
                mod_name = ori_mod_name
        fmod = f"module {mod_name} {mod_body}\nendmodule\n"
        writeFile(f"{cache_dir}/{mod_name}.v", fmod)
        self.lib_hash[mod_name] = fmod
        aprint(f"In process lib file {libfile} get module {mod_name}\n", self)

def workOnLibs(self):
    for one in self.lib_files:
        libFilePro(one, self)
    for onelib in self.lib_cells:
        if onelib in self.lib_hash:
            continue
        vname = f"{onelib}.v"
        svname = f"{onelib}.sv"
        lfile = searchOneFile([vname,svname], self)
        if lfile:
            libFilePro(lfile, self)
            
    
      
        
def findPackageFiles(pkg_files, self):
    in_searching = 1
    nxt_pack = []
   
    while in_searching:
        in_searching = 0
        for root_pack in pkg_files:
            if not re.search(r'\.', root_pack):
                root_pack += '.sv'
            alt_pack = root_pack
            if re.search(r'\.sv$', root_pack):
                alt_pack = re.sub(r'\.sv$', ".v", root_pack)
            else:
                alt_pack = re.sub(r'\.v$', '.sv', root_pack)
            find_package = searchOneFile([root_pack, alt_pack], self)
            if find_package:
                cache_dir = ".gofai/package_cache"
                os.makedirs(cache_dir, exist_ok=True)
                aprint(f"INFO! Found {find_package} package file\n", self)
                self.package_files.insert(0,find_package)
                basename = os.path.basename(find_package)
                if not os.path.exists(f"{cache_dir}/{basename}"):
                    shutil.copy(find_package, f"{cache_dir}/{basename}")
                pkg_content = contWOCom(readFile(find_package), self)
                new_pkgs = re.findall(r'\bimport\s+(\w+)::', pkg_content, re.DOTALL)
                for onep in new_pkgs:
                    aprint(f"INFO! Found {onep} package imported by {find_package} do more\n", self)
                    nxt_pack.append(onep)
            else:
                aprint(f"ERROR! Package file {root_pack}/{alt_pack} doesn't exist\n", self)
                return 1
        if len(nxt_pack):
            pkg_files = nxt_pack
            in_searching = 1
            aprint(f"INFO! {nxt_pack} imported by parent package\n", self)
        else:
            aprint(f"Done package searching\n", self)
    return 0
def searchOneFile(file_arr, self):
    for one_file in file_arr:
        for onedir in self.selected_paths:
            if os.path.exists(f"{onedir}/{one_file}"):
                return f"{onedir}/{one_file}"
        for onedir in self.selected_paths:
            for root,dirs,files in os.walk(onedir):
                if one_file in files:
                    return os.path.join(root, one_file)
    return ""
def contWOCom(cont, self):
    retst = re.sub(r'/\*.*?\*/', '', cont, flags=re.DOTALL)
    retst = re.sub(r'//.*', '', retst)
    return retst
def findIncludeFiles(includes, self):
    for one_inc in includes:
        full_inc = searchOneFile([one_inc], self)
        if not full_inc:
            aprint(f"ERROR! Include file {one_inc} can't be found\n", self)
            return 1
        inc_path = os.path.dirname(full_inc)
        self.include_paths.append(inc_path)
        self.include_files.append(full_inc)
        aprint(f"INFO! Include file added {full_inc}\n", self)
        
def getLibraryHash(self):
    libhash = {}
    for onelibfile in self.verilog_config.get("library_file", []):
        if not onelibfile or not os.path.isfile(onelibfile):
            continue
        try:
            vcont = readFile(onelibfile)
        except Exception as e:
            print(f"Warning: Could not read library file {onelibfile}. Reason: {e}")
            continue
        vcont = removeComments(vcont)
        
        # 2. Split file content into individual modules, similar to _create_temp_module_files
        vmods = re.split(r'endmodule', vcont, flags=re.IGNORECASE | re.DOTALL)
       
        for part in vmods:
            if re.search(r"module\s+(\w+)", part):
                modname = re.search(r"module\s+(\w+)", part).group(1)
                pinlist = extractPinList(part)
                bbstring = createVerilog(modname, pinlist)
                libhash[modname] = bbstring
                print(f"One lib {modname} --- {bbstring}")
                
 
    return libhash
def verilog2spec(self):
    verilog = self.selected_files[0]
    topmodule = self.top_module
    
    print(f"DO Verilog to spec for module {topmodule} verilog-file={verilog}")
    messages = []
    system = """
    You are an Expert Digital Designer for Verilog. Create a microarchitecture spec description for the following RTL.
    The spec should be in markdown format and should include these:
    - The module name of the Verilog
    - The list of inputs and outputs with the types and escriptions
    - Internal Registers description should include synchronous or asynchronous set/reset if they have
    - Use natural language instead of logic operation symbols like XOR to replace '^', and AND to replace '&'
      For example, "abc=bin[0] ^ bin[5] ^ bin[9]" can be described as "Signal abc is XOR together of 0,5,9 bits of 'bin'
    - You should cover every signal with accurate logic description
    - Rule above all: keep the detail of how signals are generated. In describing any signal logic, make sure when it is converted back to Verilog and the Verilog should be equivalent to the original logic
    """
    messages.append(system)
    in_file_info = f"Convert this Verilog file {verilog} to a Spec:"
    cont = readFile(verilog)
    in_file_info += f"""```{verilog}\n{cont}\n```\n\n"""
    if len(self.lib_cells):
        in_file_info += "The following cells are lib cells, include them in the spec\n"
        for onelib in self.lib_cells:
            celldef = self.lib_hash[onelib]
            in_file_info += f"```{onelib}.v\n{celldef}\n```\n"
    if len(self.package_files):
        in_file_info += f"The following is the package files content:\n"
        for pkg_file in self.package_files:
            pkg_st = readFile(pkg_file)
            in_file_info += f"```{pkg_file}\n{pkg_st}\n```\n"
    if len(self.include_files):
        in_file_info += "The following is the include files content:\n"
        for inc_file in self.include_files:
            inc_st = readFile(inc_file)
            in_file_info += f"```{inc_file}\n{inc_st}\n```\n"
    if self.debug:
        aprint(f"Verilog to Spec input message:\n{in_file_info}\n", self)
    messages.append(in_file_info)
    file_size = len(cont)
    nlinea = re.findall(r"\n", cont)
    line_cnt = len(nlinea)
    self.rtl_line_num = line_cnt
    
    reset_info = ""
    #if re.search(r"posedge\s+\w+\s+or\s+posedge\s+
    maxnum = 3
    nownum = 0
    while nownum < maxnum:
        nownum += 1
        aprint(f"===Do spec attempt {nownum}\n", self)
        the_spec = callLLM(messages, self)
        ctime = int(time.time())
        spec_file = f"sandbox/{topmodule}.{ctime}.md"
        final_spec = the_spec
        if len(self.lib_cells):
            final_spec += "\nThe following is name and port definition for library cells used in the design with logic conent being ommitted:\n"
            for onelib in self.lib_cells:
                celldef = self.lib_hash[onelib]
                final_spec += f"```{onelib}.v\n{celldef}\n```\n"
        final_spec += "\nIn converting to Verilog, do not redefine library cells or external componets in the output Verilog.\n"
        final_spec += """Guideline to use generate statement, it should use the following fomat:
generate
if(PARAMETER_ONE>1) begin
  always_comb begin
    for(int i=0; i<PARAMETER_TWO;i++) begin
      val_sum_part[i] = (SUM_W)`(err[i]);
      for(int j=0;j<PARAMETER_ONE;j++) begin
        val_sum_part[i] = val_sum_par[i]+(val_in[(i*PARAMETER_ONE+j)*IN_W +: IN_W]);
      end
    end
  end
endgenerate
"""
        writeFile(spec_file, final_spec)
        aprint(f"\nThe generated Spec is saved to {spec_file}\n", self)
        vfile = convertSpec(spec_file, topmodule, self)
        if vfile:
            aprint(f"=== Check LEC round {nownum}===\n", self)
            noneq = checkLEC(vfile, verilog, topmodule, self)
            aprint(f"INFO! LEC returned non-equivalent {noneq}\n", self)
            if not noneq:
                if self.out_file:
                    final_spec = self.out_file
                else:
                    final_spec = f"sandbox/{topmodule}.md"
                shutil.copy(spec_file, final_spec)
                aprint(f"INFO! The spec is saved to {final_spec}\n", self)
                break
    return
def checkLEC(v_imp, v_ref, topmod, self):
    gpl = f"strict;\n"   
    if self.debug:
        gpl += "setdbg('glbeco,kpm,umkey');\n"
    ctime = int(time.time())
    if len(self.package_files):
        rfile = f"sandbox/rrtl.{ctime}.v"
        ifile = f"sandbox/irtl.{ctime}.v"
        rcont = ""
        icont = ""
        for pkg in self.package_files:
            icont += readFile(pkg) + "\n"
            rcont += readFile(pkg) + "\n"
        icont += readFile(v_imp)
        writeFile(ifile, icont)
        rcont += readFile(v_ref)
        writeFile(rfile, rcont)
    else:
        rfile = v_ref
        ifile = v_imp   
    if len(self.lib_cells):
        for onelib in self.lib_cells:
           onelibfile = f".gofai/lib_cache/{onelib}.v"
           gpl += f"read_library('-v', '{onelibfile}');\n"        
    if len(self.defines):
        for onedef in self.defines:
            twoitems = onedef.split('=', 1)
            gpl += f"set_define('{twoitems[0]}', '{twoitems[1]}');\n"
    if len(self.include_paths):
        for onepath in self.include_paths:
            gpl += f'set_inc_dirs("{onepath}");\n'
    if self.debug:
        gpl += f"""my $neq = rtl_lec("{ifile}", "{rfile}", "{topmod}", "-debug", 1);\n"""
    else:
        gpl += f"""my $neq = rtl_lec("{ifile}", "{rfile}", "{topmod}");\n"""
    gpl += "gexit;\n"
    pl_file = f"sandbox/tmp_lec.{ctime}.gpl"
    writeFile(pl_file, gpl)
    gof_path = get_gof_path(self) #self.gof_config['gof']

    cmds = [f'{gof_path}/gof', '-run', pl_file]
    cmdst = " ".join(cmds)
    aprint(f"Run LEC command {cmdst}\n", self)
    result = subprocess.run(cmds, capture_output=True, text=True)
    output = result.stdout  # The command's output as a string
    error = result.stderr
    #print(f"Outout of lec {output}")
    if error:
        aprint(f"ERROR! LEC run returned {error}\n", self)
        return 9999
    if re.search(r'\d+\snon-equiv', output):
        noneq = int(re.search(r'(\d+)\snon-equivalent', output).group(1))
        return noneq
    return 9999
    
def convertSpec(spec_file, topmodule, self):
    aprint(f"=== Convert {spec_file} to Verilog ===\n", self)
    messages = []
    system = f"""Convert the spec to Verilog. Enclose the output in ```verilog```\n"""
    messages.append(system)
    speccont = readFile(spec_file)
    spec_info = f"""```spec\n{speccont}\n```\n\n"""
    messages.append(spec_info)
    maxnum = 5
    nownum = 0
    outfile = ""
    while nownum < maxnum:
        nownum += 1
        answer = callLLM(messages, self)
        if re.search(r"```verilog", answer):
            v_cont = re.search(r'```verilog(.*?)```', answer, flags=re.DOTALL).group(1)
            ctime = int(time.time())
            v_file = f"sandbox/{topmodule}.{ctime}.v"
            writeFile(v_file, v_cont)
            aprint(f"Verilog is saved to {v_file}\n", self)
            outfile = v_file
            break
    return outfile
# First arg is to indicate incremental update verilog
def processVerilog(incr, messages, self):
    sel_files = self.selected_files
    if incr == 2 and len(sel_files)==1:
        if not sel_files[0].endswith(('.v','.sv')):
            aprint(f"ERROR! User should specify a verilog by -files option\n", self)
            return
    #libcellhash = getLibraryHash(self)
    extractVerilogInfo(incr, messages, self)
    topmodule = self.top_module
    
    for onelib in self.lib_cells:
        celldef = self.lib_hash[onelib]
        messages.append(f"```{onelib}.v\n{celldef}\n```\n")
   
    if incr == 2:
        verilog2spec(self)
        return
    topstring = "Do verilog coding by following the spec and code below. Enclose the output in ```verilog```."
    for one in messages:
        topstring += f"\n{one}"
    current_second = int(time.time())
    current_time = current_second
    if not os.path.exists("sandbox"):
        os.makedirs("sandbox")
    outfile = f"sandbox/{topmodule}.{current_second}.md"
    with open(outfile, "w", encoding="utf-8") as f:
        f.write(topstring)

    if incr == 0 and self.formal_num==0:
        vfile = convertSpec(outfile, topmodule, self)
        if vfile:
            out_v = self.out_file
            shutil.copy(vfile, out_v)
            aprint(f"INFO! the final verilog is saved to {out_v}\n", self)
        return   
    # If all_lib_string is not empty, create a sandbox/libcell.{current_time}.v file
    readlib = ""
    
    for onelib in self.lib_cells:
        libfile = f".gofai/lib_cache/{onelib}.v"
        readlib += f"""read_library("-v", "{libfile}");\n""" 
    
    ## Verilog setting
    server_ip = self.para['ip']
    server_port = self.para['port']
    print(f"Server in Verilog {server_ip} and {server_port}")
    gof_path = get_gof_path(self) #self.gof_config['gof']
    if not gof_path:
        gof_path = "."
    gof_app = "gof"
    if gof_path:
        gof_app = f"{gof_path}/gof"
    
    log_filename = f"t_{topmodule}.{current_time}.log"
    full_log_file_path = os.path.join(os.getcwd(), log_filename) 
    #for onelibfile in self.verilog_config["library_file"]:
    #    if onelibfile:
    #        readlib += f"""read_library("-v", "{onelibfile}");\n""" 
    
    gofscript = f"""
use strict;
my $start = time();
setup_eco("verilog_{topmodule}");
set_log_file("{log_filename}");
"""
    if readlib:
        gofscript += readlib

    vend_idx = self.para.get('sel_vend', 0)
    model_idx = self.para.get('sel_model', 0)
    llm_setting = f"LLM_{vend_idx}_{model_idx}_0"

    #my $sonnet4 = "LLM_0_2_0";
    #my $deepseek = "LLM_3_1_0";
    #my $gemini25 = "LLM_1_2_0";
    #my $awsds = "LLM_4_1_0";

    rtl_file = ""
    if self.out_file:
        rtl_file = self.out_file
    gofscript +=f"""
set_ai_config('-fm_num', 1, '-algo_type', 4); 
set_ai_remote_server("{server_ip}", {server_port}, 0, "___ENDQQ___", "___ENDAA___", "{llm_setting}", 0); 

spec_to_rtl("{outfile}", "{rtl_file}");
gexit();
"""
    gofscript_path = f"sandbox/gofai.{current_second}.pl"
    with open(gofscript_path, "w", encoding="utf-8") as gof_file:
        gof_file.write(gofscript)
    rungof = f"{gof_app} -gofai -run {gofscript_path}"
    aprint(f"Running command: {rungof}\n", self)



    try:
        # Start the 'gof' process in the background.
        # Redirect stdout and stderr to DEVNULL as we are monitoring a specific log file.
        # The 'cwd' is not explicitly set, so it inherits the Python script's current working directory.
        process = subprocess.Popen(
            rungof,
            shell=True,
            stdout=subprocess.DEVNULL, # Discard stdout
            stderr=subprocess.DEVNULL, # Discard stderr
        )
        
        # Initialize a queue for thread-safe communication if it doesn't exist on 'self'.
        # In a real GUI application, 'self' would typically have a dedicated message queue
        # and a periodic method (e.g., app.after in Tkinter, QTimer in PyQt) to process it.
        if not hasattr(self, 'message_queue'):
            self.message_queue = queue.Queue()
        
        # Start the log monitoring thread
        log_monitor_thread = threading.Thread(
            target=_monitor_log_file,
            args=(full_log_file_path, process, self.message_queue, self)
        )
        log_monitor_thread.daemon = True # Allow the program to exit even if this thread is running
        log_monitor_thread.start()

        aprint(f"Monitoring log file: {full_log_file_path}\n", self)
        
        # This loop processes messages from the queue in the main thread.
        # It will block the `processVerilog` function until the background process
        # and log monitoring are complete, similar to the original blocking behavior
        # of reading stdout/stderr.
        while True:
            try:
                # Get messages with a short timeout to allow checking stop_event and process status
                message = self.message_queue.get(timeout=0.1) 
                if message == "STOP":
                    break # Monitoring thread signaled completion
                aprint(message, self) # Update GUI with the new log line
            except queue.Empty:
                # If the queue is empty, check if the process has finished and no more messages are expected
                if process.poll() is not None and self.message_queue.empty():
                    break # Process finished and queue is empty, so we can stop
                time.sleep(0.1) # Small sleep to prevent busy-waiting if queue is empty

        # Ensure the monitor thread has finished cleanly before proceeding
        log_monitor_thread.join() 
        
        # Check the return code of the subprocess
        return_code = process.wait()
        if return_code != 0:
            aprint(f"Command '{rungof}' exited with code {return_code}, completed!\n", self)
        else:
            aprint(f"Command '{rungof}' completed successfully.\n", self)

    except Exception as e:
        aprint(f"Failed to run command: {e}\n", self)
        traceback.print_exc() 




def diffFile(selfiles, command, self):
    file1 = selfiles[0]
    file2 = selfiles[1]
    alldiff = ""
    ret = 0
    cnt = 0
    if os.path.exists(file1) and os.path.exists(file2):
        fcont1 = readFile(file1)
        fcont2 = readFile(file2)
        lines1 = fcont1.strip().splitlines()
        lines2 = fcont2.strip().splitlines()
        # Both files exist, perform diff
        print(f"\nDo diff of file {file1} vs {file2}")
        for line in difflib.unified_diff(lines1, lines2, fromfile=os.path.basename(file1), tofile=os.path.basename(file2), lineterm='', n=50):
            alldiff += line + "\n"
            if len(alldiff) > 40000:
                print(f"Do diff {cnt} info: {alldiff}")
                ret += file_diff_op(self, cnt + ret, file1, file2, alldiff)
                alldiff = ""
                cnt += 1
        if alldiff:
            print(f"LAST_DIFF: {alldiff}")
            ret += file_diff_op(self, cnt + ret, file1, file2, alldiff)
    return ret
def file_diff_op(self, cnt, file1, file2, alldiff):
    sysstr = f"""Review the diff of file {file1} and file {file2}, and describe the difference in natural language\n"""
    messages = []
    messages.append(sysstr)
    messages.append(f"The difference string is defined here: {alldiff}\n");
    messages.append(f"Describe the difference of two files\n");
    answer = callLLM(messages, self)
    return 1;


def diffVerilog(selfiles, command, self):
    """
    Performs a Verilog ECO analysis by diffing two selected Verilog files.
    It splits the files into individual modules, compares them, and generates
    ECO suggestions and a change list using LLM.
    """
    aprint("Starting Verilog ECO analysis...\n", self)

    if len(selfiles) != 2:
        aprint("Error: Please select exactly two Verilog files for ECO analysis.\n", self)
        return

    file1_path = selfiles[0]
    file2_path = selfiles[1]

    if os.path.getmtime(file1_path) > os.path.getmtime(file2_path):
        file1_path, file2_path = file2_path, file1_path # Swap

    aprint(f"INFO! {file1_path} is the existing RTL, {file2_path} is the modified RTL\n", self)

    if not (file1_path.lower().endswith(('.v', '.sv')) and file2_path.lower().endswith(('.v', '.sv'))):
        aprint("Error: Both selected files must be Verilog (.v or .sv) files.\n", self)
        return

    # Determine the top module name for the ECO script
    top_module_name_match = re.search(r"(\w+)\.(v|sv)$", os.path.basename(file1_path), re.IGNORECASE)
    top_module_name = top_module_name_match.group(1) if top_module_name_match else "unknown_top"

    # Check if a specific top module is mentioned in the command
    command_top_match = re.search(r"top\s*=\s*(\w+)", command, re.IGNORECASE)
    if command_top_match:
        top_module_name = command_top_match.group(1)

    temp_dir_base = "sandbox"
    temp_dir1 = os.path.join(temp_dir_base, "temp_verilog_diff_1")
    temp_dir2 = os.path.join(temp_dir_base, "temp_verilog_diff_2")
    output_dir = os.path.join(temp_dir_base, f"eco_output_{top_module_name}")

    # Clear and create temporary directories
    _clear_dir(temp_dir1)
    _clear_dir(temp_dir2)
    _clear_dir(output_dir) # Clear output directory as well

    aprint(f"Splitting '{os.path.basename(file1_path)}' into modules in '{temp_dir1}'...\n", self)
    _create_temp_module_files(temp_dir1, file1_path)
 
    aprint(f"Splitting '{os.path.basename(file2_path)}' into modules in '{temp_dir2}'...\n", self)
    _create_temp_module_files(temp_dir2, file2_path)

    # Initialize output parameters in self.para
    self.para['ecoscript_output'] = ""
    self.para['changelist_output'] = ""

    # Compare the temporary directories
    _compare_dirs(self, temp_dir1, temp_dir2, top_module_name)
def anaRTL4ECO(jobj, command, self):
    
    aprint("Starting Verilog ECO analysis...\n", self)

    imp_rtl = jobj['imp_rtl']
    ref_rtl = jobj['ref_rtl']
    file1_path = imp_rtl
    file2_path = ref_rtl
    if "netlist_eco_module_name" in jobj:
        top_module_name = jobj["netlist_eco_module_name"]
    else:
        top_module_name = "not_specified"

    
    temp_dir_base = "sandbox"
    temp_dir1 = os.path.join(temp_dir_base, "temp_verilog_diff_1")
    temp_dir2 = os.path.join(temp_dir_base, "temp_verilog_diff_2")
    output_dir = os.path.join(temp_dir_base, f"eco_output_{top_module_name}")

    # Clear and create temporary directories
    _clear_dir(temp_dir1)
    _clear_dir(temp_dir2)
    _clear_dir(output_dir) # Clear output directory as well

    aprint(f"Splitting '{os.path.basename(file1_path)}' into modules in '{temp_dir1}'...\n", self)
    _create_temp_module_files(temp_dir1, file1_path)
 
    aprint(f"Splitting '{os.path.basename(file2_path)}' into modules in '{temp_dir2}'...\n", self)
    _create_temp_module_files(temp_dir2, file2_path)
 
    # Initialize output parameters in self.para
    self.para['ecoscript_output'] = ""
    self.para['changelist_output'] = ""

    # Compare the temporary directories
    _compare_dirs(self, temp_dir1, temp_dir2, top_module_name)


def genFinalReport(top_module_name, self):

    # Final ECO script generation (similar to diffRTL2ECO.py's main block)
    aprint("\nGenerating final ECO script...\n", self)

    
    final_script_messages = []
    final_script_messages.append("You are an expert in Verilog ECO (Engineering Change Order) automation. Generate a complete Perl ECO script based on the provided individual module ECO suggestions. The script should start with 'set_top(\"TOP_MODULE_NAME\");' and end with 'fix_design;'. Combine all 'push_top' and 'pop_top' blocks into a single script.")
    final_script_messages.append(f"Here are the ECO suggestions collected for various modules:\n{self.para['ecoscript_output']}\n\nGenerate the final complete ECO script for top module '{top_module_name}'.")

    final_genscript = callLLM(final_script_messages, self)

    # Replace placeholder top module name if necessary
    final_genscript = re.sub(r"set_top\(\"\w+\"\)", f"set_top(\"{top_module_name}\")", final_genscript)

    # Insert pre-processed ECO suggestions if any
    if self.para['ecoscript_output']:
        inserted_eco_blocks = "# These are pre-processed ECO suggestions for individual modules:\n"
        inserted_eco_blocks += self.para['ecoscript_output'] + "\n"
        # Attempt to insert before fix_design, or at the end if fix_design is not found
        if "fix_design" in final_genscript:
            final_genscript = re.sub(r"fix_design", inserted_eco_blocks + "fix_design", final_genscript)
        else:
            final_genscript += "\n" + inserted_eco_blocks # Append if fix_design not found

    aprint("\n\n--- FINAL ECO SCRIPT GENERATED ---\n", self)
    aprint(final_genscript + "\n", self)
    aprint("--- END OF ECO SCRIPT ---\n\n", self)

    aprint("\n\n--- FINAL CHANGE LIST ---\n", self)
    aprint(self.para['changelist_output'] + "\n", self)
    aprint("--- END OF CHANGE LIST ---\n\n", self)

    # Save outputs to files
    writeFile(os.path.join(output_dir, "ecoscript.pl"), final_genscript)
    writeFile(os.path.join(output_dir, "changelist.txt"), self.para['changelist_output'])
    aprint(f"ECO script saved to '{output_dir}/ecoscript.pl'\n", self)
    aprint(f"Change list saved to '{output_dir}/changelist.txt'\n", self)

    # Optional: Clean up temporary directories
    # shutil.rmtree(temp_dir1)
    # shutil.rmtree(temp_dir2)
    # aprint("Temporary directories cleaned up.\n", self)

    aprint("Verilog ECO analysis completed.\n", self)


def sysVAssertion(messages, self):
    answer = callLLM(messages, self)

def findLogic(command, selfiles, keywords, self):
    search_string = ""
    if len(keywords):
        search_string = getSearchString(keywords, self)
    if not search_string:
        for onefile in selfiles:
            search_string += readFile(onefile)
    messages = []
    messages.append("Base on the context answer the question about logic design\n")
    messages.append(search_string)
    messages.append(f"Query: {command}")
    answer = callLLM(messages, self)

    
def getSearchString(keywords, self):
    
    search_dirs = [os.getcwd()] + self.verilog_config.get('library_path', []) + self.vdirs
    search_keys_string = ""

    # Use a set to avoid processing the same file multiple times if directories overlap
    all_verilog_files = set()
    for dir in search_dirs:
        if not os.path.isdir(dir):
            continue

        for oneext in (".v", ".sv"):
            flist = glob.glob(dir+f"/*{oneext}")
            for thefile in flist:
                all_verilog_files.add(thefile)

    for file_path in all_verilog_files:
        try:
            content = readFile(file_path)
            if not content:
                continue
        except Exception as e:
            aprint(f"Warning: Could not read file {file_path}: {e}\n", self)
            continue

        positions_for_this_file = []
        for keyword in keywords:
            if not keyword: continue
            try:
                # Use re.finditer to find all non-overlapping matches of the keyword
                for match in re.finditer(keyword, content):
                    mat_min, mat_max = match.start(), match.end()
                    
                    # Adjust the position to get a context window of 500 chars on each side
                    pos_min = max(0, mat_min - 500)
                    pos_max = min(len(content), mat_max + 500)
                    
                    positions_for_this_file.append((pos_min, pos_max))
            except re.error as e:
                aprint(f"Warning: Invalid regex keyword '{keyword}': {e}\n", self)
                continue

        if not positions_for_this_file:
            continue

        # --- Merge overlapping/adjacent intervals ---
        # 1. Sort intervals by their start position
        positions_for_this_file.sort()

        # 2. Perform the merge
        merged_intervals = []
        if positions_for_this_file:
            # Start with the first interval
            current_merge_start, current_merge_end = positions_for_this_file[0]
            
            for next_start, next_end in positions_for_this_file[1:]:
                # If the next interval starts before the current merged one ends, they overlap
                if next_start < current_merge_end:
                    # Extend the current merged interval to encompass the next one
                    current_merge_end = max(current_merge_end, next_end)
                else:
                    # No overlap, finalize the previous interval and start a new one
                    merged_intervals.append((current_merge_start, current_merge_end))
                    current_merge_start, current_merge_end = next_start, next_end
            
            # Add the last merged interval to the list
            merged_intervals.append((current_merge_start, current_merge_end))

        # 3. Extract substrings from the merged intervals and concatenate
        for start, end in merged_intervals:
            # Add a header for context, useful for the LLM
            search_keys_string += f"\n// --- Context from file: {file_path} ---\n"
            search_keys_string += content[start:end]
            search_keys_string += f"\n// --- End Context from file: {file_path} ---\n"
    #print(f"Search these context : {search_keys_string}")
    return search_keys_string

def findScriptType(jobj):
    stype = 'auto_eco'
    if 'eco_operation' in jobj:
        stype = 'nl'
    elif 'noneq_pnt' in jobj:
        stype = 'debug_noneq'
    elif 'remap_item' in jobj:
        stype = 'remap_item'
    elif 'lec' in jobj and jobj['lec']:
        stype = 'lec'
    elif 'ana_eco' in jobj and jobj['ana_eco']:
        stype = 'ana_eco'
    elif 'llm_dfix' in jobj and jobj['llm_dfix']:
        stype = 'llm_dfix'
    return stype

def ecoPrompt(stype):
    system = """Analyze the script requirement of the query. 
    Return in JSON format enclosed in ```json```. 
    Extract related fields information if they present, including:
    """
  
    system += """
    'need_file_content': Only when a file or directory content is needed, but user doesn't provide the content. If user does provide file content, remove this field. This field should be the only field in the JSON string if it is needed. It's an array
    'modify_rtl': When user asks to modify the original RTL, and the modified RTL is not ready yet, set the modification method to this field. If the modifed RTL file is read, remove this field.
    'eco_name': the ECO name or project name, not space in the name. If not explicitly mentioned, extract one from the query
    'library': the libraries, it's an array, if not specified, set to ['default']
    'library_list': the library list, use supecifies a library list file
    'ref_netlist': the reference netlist or called re-synthesis new netlist
    'imp_netlist': the implementation netlist or called netlist under ECO
    'postlayout_netlist': the netlist from backend after layout
    'ref_rtl': the reference RTL Verilog file which is the new modifed RTL file
    'imp_rtl': the implementation RTL Verilog file which is the original RTL file
    'ref_rtl_list': the reference RTL Verillog list file which lists RTL files including the modified files
    'imp_rtl_list': the implementation RTL Verilog list file which list all original RTL files
    'inc_dir': the include directories user specifies
    'set_define': the define use specified
    'hier_instance': the hierarchical instance list for the sub-module in RTL guided ECO, it's array
    'rtl_guided': if both ref/imp RTL files are provided, set it to one
    'lec': when user asks to do lec, set it to one
    'run_script': apply the script
    'netlist_eco_module_name': the module name in ECO for the netlist, default to be the top module
    'rtl_sub_module_name': the sub-module name for RTL
    'ref_svf': the reference SVF file
    'imp_svf': the implementation SVF file
    'metal_only': if asking for metal only spare gate or gate array ECO, set it to 1, otherwise set it to 0
    'ana_eco': if user asks to analyze RTL files and give suggestion
    'llm_dfix': if user asks to use LLM directly fixing netlist, set to the number user wants the retrial numbers
    'dft_check_only': if user asks to do dft checking after eco
    'dft_check_fix': if user asks to do dft checking and fixing after eco
    'power_check_only': if user asks to do power check for level shifter or isolation cells
    'power_check_fix': if user asks to do power check for level shifter or isolation cells and fix if there is error
    'spare_gate_pattern': spare gate pattern
    'constraint': DFT constraint, an array, like ['set_pin_constant("SCAN_ENABLE", 0)', 'set_pin_constant("MBIST_EN", 0)']
    'ignore_output': Ignore output, an array, ['set_ignore_output("scan_out*")']
    'output_netlist': the ECO output netlist file name
    'output_perl': the ECO output to Perl file
    'output_tcl': the ECO output to TCL file
    'lef': the LEF file name
    'def': the DEF file name
    'upf': the UPF file name
    'gate_array_enable': if asking for gate array ECO, set it to 1, otherwise set it to 0
    'gate_array_pattern': the gate array pattern
    'gate_array_filler': the gate array filler naming
    'sch': if asking for schematic, set the instance name 'inst1/inst2/the_state_reg'
    'fix_list': if asking to use third party non-equivalent list file or use list file in fix_design
    'list_file': the list file for fix_list
    'noneq_pnt': if user specifies a non equivalent point for debug
    'remap_item': the item needs to be remapped and worked on
    'eco_operation': when user wants to manually change the netlist
    'dont_use': Those do not use listed by user
    Note: User can ask you to automatically find out the files and types in the specified directory
    For example:
    """

    system += """
    EXAMPLE INPUT:
    Do RTL guided ECO, the original RTL file is ori_rtl.sv, it should be modified to invert input port fifo_control_en. The netlist under eco is prelayout.v, do not use BUFT* and DCC* cell

    EXPECTED OUTPUT:
    
    ```json
    {
    "imp_rtl" : "ori_rtl.sv",
    "modifiy_rtl": "Modify ori_rtl.sv to invert input port fifo_control_en",
    "dont_use": ["BUFT*", "DCC*"]
    }
    ```
    
    EXAMPLE INPUT:
    
    Please run an ECO in the current directory, refer to eco_spec.md for more information
    
    EXPECTED OUTPUT:
    
    ```json
    {
    "need_file_content": [".", "eco_spec.md"]
    }
    ```

    EXAMPLE INPUT:
    
    Apply myeco.pl on netlist mynet.v and check the power for level shifter, upf file is myupf.upf
    
    EXPECTED OUTPUT:
    
    ```json
    {
    
    "library": ["default"], 
    "imp_netlist": "mynet.v",
    "run_script": "myeco.pl",
    "upf": "myupf.upf",
    "power_check_only":1
    }
    ```

    EXAMPLE INPUT:
    
    Run myeco.pl script to netlist mynetlist.v top module name is mytop, use library list file mylib.f
    
    EXPECTED OUTPUT:
    
    ```json
    {
    "eco_name": "run_script",
    "netlist_eco_module_name": "mytop",
    "library_list": "mylib.f", 
    "imp_netlist": "mynetlist.v",
    "run_script": "myeco.pl"
    }
    ```

    EXAMPLE INPUT:
    Query: analyze the original rtl rtl1.sv and modified rtl rtl2.sv, give suggestion on how to improve the ECO and minimize the patch size
    EXPECTED OUTPUT:
    ```json
    {'ana_eco':1,
    "ref_rtl": "rtl2.sv",
    "imp_rtl": "rtl1.sv"
    }
    ```

    EXAMPLE INPUT:
    Query: use LLM to directly fix the netlist mynet.v with two times by diffing the original rtl rtl1.sv and modified rtl rtl2.sv top level module name is mytop, run DFT checker in the end
    EXPECTED OUTPUT:
    ```json
    {
    "eco_name": "llm_direct_fix",
    "llm_dfix":2,
    "dft_check_only":1,
    "ref_rtl": "rtl2.sv",
    "imp_rtl": "rtl1.sv",
    "netlist_eco_module_name": "mytop",
    "library": ["default"], 
    "imp_netlist": "mynet.v"
    }
    ```

   
    EXAMPLE INPUT:
    
    Please write a RTL guided ECO script: the file list in the current directory are:
    original_rtl.sv
    modifed_rtl.sv
    prelayout_netlist.v
    syn.svf
    example_script.pl
    my_eco.report
    readme.md
    Other setting can take example from script example_script.pl
    
    
    EXPECTED OUTPUT:
    
    ```json
    {"need_file_content":["example_script.pl"]}
    ```
    
    
    EXAMPLE INPUT:
    Do automatic ECO the ref netlist is /some/path/reference.gv, the netlist under ECO is /some/path/implementation.gv, top level module name mytop
    
    EXPECTED OUTPUT:
    
    ```json
    {
    "eco_name": "mytop_eco",
    "netlist_eco_module_name": "mytop",
    "library": ["default"], 
    "ref_netlist": "/some/path/reference.gv", 
    "imp_netlist": "/some/path/implementation.gv"
    }
    ```

    EXAMPLE INPUT:
    Query: debug this error, reference instance u_con/u_def/U123 is not in IMP netlist my_imp.gv, the REF netlist is my_ref.gv
    EXPECTED OUTPUT:
    ```json
    "library": ["default"], 
    "ref_netlist": "my_ref.gv", 
    "imp_netlist": "my_imp.gv",
    "remap_item": "u_con/u_def/U123"
    ```


    
    EXAMPLE INPUT:

    Please write an ECO script for me. It's for postlayout metal only ECO. 
    The library files are /some/path/tsmc_typ_85c_078v_svt.lib /some/path/tsmc_typ_85c_078v_lvt.lib
    the ref netlist is /some/path/reference.gv, the netlist under ECO is /some/path/implementation.gv
    the ref svf is /some/path/reference.svf.txt, the imp svf is /some/path/implementation.svf.txt
    set ignore on "output*"
    use list file myfixlist.txt in fix_design
    DFT constraint: set ATPG_MODE to 0, set MBIST_EN to 0
    output ECO netlist to /some/path/eco_verilog.v, write an Innovus TCL script /some/path/eco_innovus.tcl

    EXPECTED OUTPUT:

    ```json
    {
    "eco_name": "postlayout_metal_only_eco",
    "netlist_eco_module_name": "",
    "library": ["/some/path/tsmc_typ_85c_078v_svt.lib", "/some/path/tsmc_typ_85c_078v_lvt.lib"], 
    "ref_netlist": "/some/path/reference.gv", 
    "imp_netlist": "/some/path/implementation.gv", 
    "ref_svf": "/some/path/reference.svf.txt", 
    "imp_svf": "/some/path/implementation.svf.txt", 
    "metal_only": 1,
    "fix_list": 1,
    "list_file": "myfixlist.txt",
    "rtl_guided" : 0,
    "constraint": ["set_pin_constant('ATPG_MODE', 0)", "set_pin_constant('MBIST_EN', 0)"],
    "ignore_output": ["set_ignore_output('output*')"],
    "output_netlist": "/some/path/eco_verilog.v"
    "output_tcl": "/some/path/eco_innovus.tcl"
    }
    ```

    EXAMPLE INPUT:

    Do a RTL guided Metal only ECO, here are the files provided:

    Library files: /some/path/tsmc_typ_85c_078v_svt.lib /some/path/tsmc_typ_85c_078v_lvt.lib
    Imp RTL file: /myproj/path/imp_rtl/chip_design.sv
    Modified RTL file: /myproj/path/new_rtl/new_rtl.sv
    Imp netlist under ECO: /myproj/path/implementation.gv
    svf file: /myproj/path/implementation.svf.txt
    Ignore outputs: "output*"
    DEF: /myproj/path/design.def
    LEF: myproj/path/design.lef
    DFT constraint: ATPG_MODE to 0, MBIST_EN to 0
    Backend netlist: /myproj/path/from_be.gv
    output ECO netlist: /some/path/eco_verilog.v
    ouput ECO PERL: /some/path/eco_perl.pl
    Spare gates: */*_SPARE*
    Top module name: my_chiptop

    EXPECTED OUTPUT:

    ```json
    {
    "eco_name": "rtl_guided_metal_eco",
    "netlist_eco_module_name": "my_chiptop",
    "library": ["/some/path/tsmc_typ_85c_078v_svt.lib", "/some/path/tsmc_typ_85c_078v_lvt.lib"], 
    "ref_rtl": "/myproj/path/new_rtl/new_rtl.sv",
    "imp_rtl": "/myproj/path/imp_rtl/chip_design.sv",
    "imp_netlist": "/some/path/implementation.gv", 
    "postlayout_netlist": " /myproj/path/from_be.gv"
    "imp_svf": "/some/path/implementation.svf.txt",
    "lef": "/myproj/path/design.lef",
    "def": "/myproj/path/design.def"
    "spare_gate_pattern": "*/*_SPARE*",
    "metal_only": 1,
    "rtl_guided" : 1,
    "constraint": ["set_pin_constant('ATPG_MODE', 0)", "set_pin_constant('MBIST_EN', 0)"],
    "ignore_output": ["set_ignore_output('output*')"],
    "output_netlist": "/some/path/eco_verilog.v"
    "output_perl": "/some/path/eco_perl.pl"
    }
    ```



    
    EXAMPLE INPUT:
    Do an ECO on netlist:
    Top module name: mytop
    Netlist under ECO: /the_ai_proj/postlayout.v
    In module toout2, invert chout_reg_2_'s D pin
    Save ECO Perl script to: eco_p.pl

    EXPECTED OUTPUT: (Note toout2 is assigned to netlist_eco_module_name, so 'module toout2' is removed from eco_operation) 
    ```json
    {
    "eco_name": "modify_chout_2_D",
    "netlist_eco_module_name": "toout2",
    "library": ["default"], 
    "imp_netlist": "/the_ai_proj/postlayout.v", 
    "metal_only": 0,
    "rtl_guided" : 0,
    "eco_operation": "invert chout_reg_2_'s D pin",
    "output_perl": "eco_p.pl"
    }
    ```
    

    
    EXAMPLE INPUT:

    Please write a RTL guided ECO script:
    the imp RTL file list is /myproj/path/imp_rtl/chip_design.l
    the modified RTL file list is /myproj/path/new_rtl/new_rtl.l
    define a variable MY_CONTROL to one
    set include directories to be /myproj/inc1 and /myproj/inc2
    the imp netlist under ECO is /myproj/path/implementation.gv
    the svf is /myproj/path/implementation.svf.txt
    the top design name is 'chip_top'
    set ignore on "output*"
    DFT constraint: set ATPG_MODE to 0, set MBIST_EN to 0
    output ECO Perl script to /myproj/path/eco_result/eco_script.gpl

    EXPECTED OUTPUT:

    ```json
    {
    "eco_name": "rtl_guided_eco",
    "netlist_eco_module_name": "chip_top",
    "rtl_sub_module_name" : "",
    "library": ["default"], 
    "ref_rtl_list": "/myproj/path/new_rtl/new_rtl.l",
    "imp_rtl_list": "/myproj/path/imp_rtl/chip_design.l",
    "set_define": ["MY_CONTROL=1"],
    "inc_dir": ["/myproj/inc1", "/myproj/inc2"],
    "imp_netlist": "/myproj/path/implementation.gv", 
    "imp_svf": "/myproj/path/implementation.svf.txt", 
    "metal_only": 0,
    "fix_list": 0,
    "rtl_guided": 1,
    "constraint": ["set_pin_constant('ATPG_MODE', 0)", "set_pin_constant('MBIST_EN', 0)"],
    "ignore_output": ["set_ignore_output('output*')"],
    "output_perl": "/myproj/path/eco_result/eco_script.gpl",
    }
    ```

    EXAMPLE INPUT:

    Please write a RTL guided ECO script:
    use extra liberty library file in /mylib/thepath/tsmc.2nm.lib
    the imp RTL file is /myproj/path/imp_rtl/sub_design.sv
    the modified RTL file is /myproj/path/new_rtl/sub_design.new.sv
    the RTL sub-module name is sub_mod_a
    the hierarchical instance paths are "u_sub_mod1/u_sub_mod_a", "u_sub_mod3/u_sub_mod_a_1"
    the imp netlist under ECO is /myproj/path/implementation.gv
    the svf is /myproj/path/implementation.svf.txt
    the top design name is 'chip_top'
    set ignore on "output*"
    DFT constraint: set ATPG_MODE to 0, set MBIST_EN to 0
    output ECO Perl script to /myproj/path/eco_result/eco_script.gpl

    EXPECTED OUTPUT:

    ```json
    {
    "eco_name": "rtl_guided_eco",
    "netlist_eco_module_name": "chip_top",
    "rtl_sub_module_name" : "sub_mod_a",
    "hier_instance": ["u_sub_mod1/u_sub_mod_a", "u_sub_mod3/u_sub_mod_a_1"],
    "library": ["/mylib/thepath/tsmc.2nm.lib","default"], 
    "ref_rtl": "/myproj/path/new_rtl/sub_design.new.sv",
    "imp_rtl": "/myproj/path/imp_rtl/sub_design.sv",
    "imp_netlist": "/myproj/path/implementation.gv", 
    "imp_svf": "/myproj/path/implementation.svf.txt", 
    "metal_only": 0,
    "fix_list": 0,
    "rtl_guided": 1,
    "constraint": ["set_pin_constant('ATPG_MODE', 0)", "set_pin_constant('MBIST_EN', 0)"],
    "ignore_output": ["set_ignore_output('output*')"],
    "output_perl": "/myproj/path/eco_result/eco_script.gpl",
    }
    ```

    EXAMPLE INPUT:

    Please write a RTL guided ECO script: the file list in the current directory are:
    original_rtl.sv
    modifed_rtl.sv
    prelayout_netlist.v
    syn.svf
    example_script.pl
    my_eco.report
    readme.md
    Other setting refer to the example script example_script.pl
    ```example_script.pl
    read_library("/lib/tsmc/art.m.lib");
    set_dont_use("G*");
    read_rtl('-ref', "/proj1/ref_verilog.sv");
    read_rtl('-imp', "/proj1/imp_verilog.sv");
    set_top("my_chip_top");
    rtl_compare;
    read_design("-imp", "/proj1/imp_netlist.v");
    set_top("my_chip_top");
    set_pin_constant("scan_en", 0);
    fix_design;
    report_eco();
    write_perl("myout.gpl");
    write_verilog("eco_out.v");
    ```

    EXPECTED OUTPUT:

    ```json
    {
    "eco_name": "rtl_guided_eco",
    "rtl_sub_module_name" : "",
    "netlist_eco_module_name": "chip_top",
    "library": ["/lib/tsmc/art.m.lib"], 
    "ref_rtl": "modifed_rtl.sv",
    "imp_rtl": "original_rtl.sv",
    "imp_netlist": "prelayout_netlist.v", 
    "imp_svf": "syn.svf",
    "dont_use": ["G*"],
    "metal_only": 0,
    "fix_list": 0,
    "rtl_guided": 1,
    "constraint": ["set_pin_constant('scan_en', 0)"],
    "ignore_output": [],
    "output_perl": "myout.gpl",
    "output_netlist": "eco_out.v"
    }
    ```

    EXAMPLE INPUT:
    Do LEC, logic equivalence checking on the ref netlist is /some/path/reference.gv, the netlist under ECO is /some/path/implementation.gv

    EXPECTED OUTPUT:
    
    ```json
    {
    "eco_name": "do_lec",
    "netlist_eco_module_name": "",
    "library": ["default"], 
    "ref_netlist": "/some/path/reference.gv", 
    "imp_netlist": "/some/path/implementation.gv", 
    "lec": 1
    }
    ```
  

    EXAMPLE INPUT:
    Bring up schematic of u_abc/u_con/U123 in netlist /some/path/implementation.gv, the top module name is mytop_d

    EXPECTED OUTPUT:
    
    ```json
    {
    "eco_name": "do_sch",
    "netlist_eco_module_name": "mytop_d",
    "library": ["default"], 
    "imp_netlist": "/some/path/implementation.gv", 
    "sch": "u_abc/u_con/U123"
    }
    ```
  
    EXAMPLE INPUT:
    Bring up schematic of u_abc/u_con/cnt_reg_0_ in netlist /some/path/implementation.gv and netlist /some/path/reference.gv, the top module name is mytop_d

    EXPECTED OUTPUT:
    
    ```json
    {
    "eco_name": "do_sch",
    "netlist_eco_module_name": "mytop_d",
    "library": ["default"], 
    "imp_netlist": "/some/path/implementation.gv", 
    "ref_netlist": "/some/path/reference.gv", 
    "sch": "u_abc/u_con/cnt_reg_0_"
    }
    ```
      
    """

    
    return system

def funGenScript(query, ask4set, self):
    pre_mask = 0
    post_mask = 0
    gate_array = 0
    manual = 0
    automatic = 0
    gui = 0
    debug_noneq = 0
    do_remap_item = 0
    do_llm_dfix = 0
    do_dft_check = 0
    change_pin = 0
    change_port = 0
    use_list = 0
    retarget = 0
    find_eq = 0
    dft_drc = 0
    dft_constraint = 0
    formality = 0
    multibit = 0
    new_module = 0
    repetitive = 0
    rtl_guided = 0
    no_exact = 0
    scan_chain = 0
    svf = 0
    ref_svf = ""
    imp_svf = ""
    syn_sub = 0
    deffile = ""
    run_script = ""
    innovus = 0
    lec = 0
    nl = 0
    sch = ""
    flatten = 0
    if ask4set.get('rtl_guided', 0) == 1:
        rtl_guided = 1
    if ask4set.get('llm_dfix', 0) >= 1:
        do_llm_dfix = ask4set.get('llm_dfix', 0)
    if ask4set.get('dft_check_only', 0) == 1 or ask4set.get('dft_check_fix', 0) == 1:
        do_dft_check = 1
    #if 'gate_array_enable' in ask4set:
    #    gate_array = ask4set['gate_array_enable']
    #    if gate_array:
    #        automatic = 1
    #        leffile = "/proj/ai_acc/lib/leaf_cells.lef"
    #        deffile = "/proj/ai_acc/post/implementation.def"

    if 'gui' in ask4set:
        gui = 1
    if re.search(r"\bflatten\b", query, flags=re.IGNORECASE):
        flatten = 1
    if 'run_script' in ask4set and ask4set['run_script']:
        run_script = ask4set['run_script']
    elif ask4set.get('script_type', "none") == "lec":
        lec = 1
    elif 'sch' in ask4set:
        sch = ask4set['sch']
    elif ask4set.get('script_type', "none") == 'nl':
        nl = 1
        automatic = 1
    elif ask4set.get('script_type', "none") == "auto_eco":
        automatic = 1
    elif ask4set.get('script_type', "none") == "manual_eco":
        manual = 1
    elif ask4set.get('script_type', "none") == "debug_noneq":
        debug_noneq = 1
    elif ask4set.get('script_type', "none") == "remap_item":
        do_remap_item = 1
    #elif ask4set.get('script_type', "none") == "llm_dfix":
    #    do_llm_dfix = 1
    elif ask4set.get('script_type', "none") == "dft_drc":
        dft_drc = 1
    
    if 'changepin' in ask4set and not nl:
        change_pin = ask4set['changepin']
        if change_pin:
            manual = 1
    if 'changeport' in ask4set and not nl:
        change_port = ask4set['changeport']
        if change_port:
            manual = 1
    if 'noneq_pnt' in ask4set:
        noneq_pnt = ask4set['noneq_pnt']
    else:
        noneq_pnt = "u_abc/reg_1_"
    if 'remap_item' in ask4set:
        remap_item = ask4set['remap_item']
    else:
        remap_item = "u_abc/reg_1_"
    if 'ref_svf' in ask4set:
        ref_svf = ask4set['ref_svf']
        if ref_svf:
            svf = 1
    
    if 'imp_svf' in ask4set:
        imp_svf = ask4set['imp_svf']
        if imp_svf:
            svf = 1
    if 'def' in ask4set:
        deffile = ask4set['def']
    if 'lef' in ask4set:
        leffile = ask4set['lef']
    if 'dft_drc' in ask4set:    
        dft_drc = ask4set['dft_drc']

    if 'fix_list' in ask4set:
        use_list = ask4set['fix_list']
    

   

    if automatic:
        basename = "auto"
    elif manual:
        basename = "manual"
    elif dft_drc:
        basename = 'dft_drc'
    else:
        basename = "debug_net"
    if gate_array:
        basename += "_gatearray"
    if post_mask:
        basename += "_metalonly"
    if svf:
        basename += "_svf"
    if multibit:
        basename += "_mbit"
    if rtl_guided:
        basename += "_rtl"
        
    scon = ""
    if not ask4set['foreco']:
       scon += "# ___AI___ GOF ECO script, the API sequence should be strictly followed\n"
    scon += "use strict;\n";  # To catch script syntax issue
    the_name = f"{basename}_example"
    if 'eco_name' in ask4set:
        the_name = ask4set['eco_name']
        
    scon += f"# ECO name '{the_name}'\n"
    scon += f"setup_eco(\"{the_name}\");\n"
    scon += f"set_log_file(\"{the_name}.log\");\n"
    #if self.set_ai_dfix:
    #    scon += "set_ai_dfix(1);\b"
        
    libs = []
    if 'library' in ask4set:
        libs = ask4set['library']
        #libs = libstr.split(" ")
    if 'library_list' in ask4set:
        listfile = ask4set['library_list']
        scon += f"read_library('-f', \"{listfile}\");\n"
    elif len(libs) == 0 and not ask4set['foreco']:
        libs = ["/lib/5nm/tsmc_typ_85c_078v_svt.lib", "/lib/5nm/tsmc_typ_85c_078v_lvt.lib"]
    for lib in libs:
        if re.search(r"\.lib$", lib, flags=re.IGNORECASE):
            scon += f"read_library(\"{lib}\"); # Read in liberty file\n"
        elif re.search(r"\.v\w*$", lib, flags=re.IGNORECASE):
            scon += f"read_library('-v', \"{lib}\"); # Read in verilog file\n"
        elif lib == 'default':
            liblist = self.gof_config['Library']
            for onelib in liblist:
                scon += f"read_library(\"{onelib}\");\n"
        if 'dont_use' in ask4set:
            if len(ask4set['dont_use']):
                dont = ",".join(f"'{item}'" for item in ask4set['dont_use'])
                scon += f"set_dont_use({dont});\n"
                
    if do_llm_dfix:
        scon += f"set_ai_dfix({do_llm_dfix});\n"
        scon += f"set_ai_remote_server('localhost', 1998, 0, '___ENDQQ___', '___ENDAA___', 'LLM_1_3_0', 0);\n" 
    #    scon += f"""read_rtl('-ref', '{ask4set['ref_rtl']}');\n"""
    #    scon += f"""read_rtl('-imp', '{ask4set['imp_rtl']}');\n"""
    #el
    if rtl_guided:
        if 'hier_instance' in ask4set and ask4set['hier_instance']:
            paths = ",".join(f"'{item}'" for item in ask4set['hier_instance'])
            scon += f"""set_path_prefix({paths});\n"""
        if not 'ref_rtl' in ask4set and not 'ref_rtl_list' in ask4set:
            ask4set['ref_rtl'] = "/proj/rtl/ref_rtl.sv"
            ask4set['imp_rtl'] = "/proj/rtl/imp_rtl.sv"
        if 'set_define' in ask4set:
            for one in ask4set['set_defin']:
                twoitems = one.split('=')
                scon += f"set_defin('{twoitems[0]}', '{twoitems[1]}');\n"
        if 'inc_dir' in ask4set:
            dirs = ",".join(f"'{item}'" for item in ask4set['inc_dir'])
            scon += f"set_inc_dirs({dirs});\n"
        if ask4set['ref_rtl']:
            scon += f"""read_rtl('-ref', '{ask4set['ref_rtl']}');\n"""
            scon += f"""read_rtl('-imp', '{ask4set['imp_rtl']}');\n"""
        else:
            scon += f"""read_rtl('-ref', '-f', '{ask4set['ref_rtl_list']}');\n"""
            scon += f"""read_rtl('-imp', '-f', '{ask4set['imp_rtl_list']}');\n"""
        rtl_top = ask4set.get('netlist_eco_module_name', 'unknown')
        if 'rtl_sub_module_name' in ask4set and ask4set['rtl_sub_module_name']:
            rtl_top = ask4set['rtl_sub_module_name']
        if not rtl_top:
            rtl_top = "chip_top"
        scon += f"""set_top('{rtl_top}');\n"""
        scon += "rtl_compare;\n"
    refnetlist = ""
    if 'ref_netlist' in ask4set:
        refnetlist = ask4set['ref_netlist']
    if (automatic or lec or debug_noneq or do_remap_item) and not refnetlist and not rtl_guided and not ask4set['foreco']:
        refnetlist = "/proj/ai_acc/newsyn/reference.gv"
    impnetlist = ""
    if 'imp_netlist' in ask4set:
        impnetlist = ask4set['imp_netlist']
    if not impnetlist:
        impnetlist = "/proj/ai_acc/post/implementation.gv"
    if svf:
        if multibit:
            scon += "# Multibit flop design is recommended to have SVF files loaded\n"
        if ref_svf:
            scon += f"read_svf(\"-ref\", \"{ref_svf}\");    # SVF file must be loaded before read_design, and must be in text format\n"
        scon += f"read_svf(\"-imp\", \"{imp_svf}\");  # SVF file must be loaded before read_design, and must be in text format\n"
    if refnetlist:
        scon += f"read_design(\"-ref\", \"{refnetlist}\");  # Read in Reference Netlist file after new synthesis\n"
    under_what = "ECO";
    if dft_drc:
        under_what = "DFT DRC"
        scon += f"set_multibit_blasting(0); # Disable multibit blasting in DFT DRC\n"
    scon += f"read_design(\"-imp\", \"{impnetlist}\");  # Read in Implementation Netlist file Which is under {under_what}\n"
    if 'upf' in ask4set:
        upf = ask4set['upf']
        scon += f"read_upf(\"{upf}\");\n"
    if 'netlist_eco_module_name' in ask4set:
        topmod = ask4set['netlist_eco_module_name']
        if not topmod:
            topmod = "chip_top"
        scon += f"set_top(\"{topmod}\");# Set the top module that ECO is working on\n"
    #else:
    #    scon += "set_top(\"topmod\");# Set the top module that ECO is working on\n"
    ignores = []
    if automatic or lec or debug_noneq or do_remap_item:
        # ignore list for DFT
        if "ignore_output" in ask4set:
            ignores = ask4set['ignore_output'] #.split(" ")
        if len(ignores) == 0 and not ask4set['foreco']:
            ignores = ['set_ignore_output("scan_out*")']
        for ignore in ignores:
            scon += f"{ignore};\n"
        # Constraint for DFT
        cons = []
        if 'constraint' in ask4set:
            cons = ask4set['constraint'] #.split(" ")
        if len(cons)==0 and not ask4set['foreco']:
            # Example constraint for user starting script
            cons = ['set_pin_constant("scan_enable", 0)', 'set_pin_constant("scan_mode", 0)',
                    'set_net_constant("u_dft/u_mbist_control/mbist_en", 0)',
                    'set_pin_constant("u_subtop/u_ttor1/Y", 0, "-imp")']
        for con in cons:
            scon += f"{con};\n"

    if deffile:
        scon += f"read_lef(\"{leffile}\");# Read LEF file\n"
        scon += f"read_def(\"{deffile}\");# Read Design Exchange Format (DEF) file. Loading DEF file before fix_design makes ECO physical aware\n"
    if manual:
        if repetitive:
            scon += "for($i=0;$i<8;$i++){\n"
            if change_pin == 1:
                scon += "   change_pin(\"u_con/u_cnt/state_reg_${i}_/D\", \"INVX1\", \"eco_inv_ins_$i\", \"-\");\n"
            elif change_pin == 2:
                scon += "   change_pin(\"u_con/u_cnt/constrl_sig[$i]\", \"INVX1\", \"eco_inv_ins_$i\", \"-\");\n"
            elif change_pin == 3:
                scon += "   change_pin(\"u_con/u_cnt/state_reg_${i}_/D\", \"1'b0\");\n"
            elif change_port == 1:
                scon += "   change_port(\"THE_OUT_PORT[$i]\", \"INVX1\", \"eco_inv_ins_$i\", \"-\");\n"
            elif change_port == 2:
                scon += "   change_port(\"THE_OUT_PORT[$i]\", \"u_con/u_cnt/state_reg_${i}_/Q\");\n"
            else:
                scon += "   change_pin(\"u_con/u_cnt/state_reg_${i}_/D\", \"THE_IN_PORT[$i]\");\n"
            scon += "}\n"
        else:
            if change_pin == 1:
                scon += "   change_pin(\"u_con/u_cnt/state_reg/D\", \"INVX1\", \"eco_inv_ins\", \"-\");\n"
            elif change_pin == 2:
                scon += "   change_pin(\"u_con/u_cnt/constrl_sig\", \"INVX1\", \"eco_inv_ins\", \"-\");\n"
            elif change_pin == 3:
                scon += "   change_pin(\"u_con/u_cnt/state_reg/D\", \"1'b0\");\n"
            if change_port == 1:
                scon += "   change_port(\"THE_OUT_PORT_ONE_BIT\", \"INVX1\", \"eco_inv_ins_0\", \"-\");\n"
            elif change_port == 2:
                scon += "   change_port(\"THE_OUT_PORT_ONE_BIT\", \"u_con/u_cnt/state_reg/Q\");\n"
            if not change_pin and not change_port:
                scon += "   change_pin(\"u_con/u_cnt/state_reg/D\", \"THE_IN_PORT_ONE_BIT\");\n"
    if automatic and ask4set['intent'] != 'gof_debug':
        if nl:
            ecoops = re.sub(r'"', '\\"', ask4set['eco_operation'])
            scon += f"set_ai_remote_server('localhost', 1998, 0, '___ENDQQ___', '___ENDAA___', 'LLM_1_3_0', 0);\n" 
            
            scon += f"run_nl(\"{ecoops}\");\n"
        elif use_list:
            list_file = ask4set['list_file']
            scon += f"fix_design(\"-list_file\", \"{list_file}\"); # Load non-equivalent points list file from formaliyt\n"
        elif flatten:
            scon += "fix_design(\"-flatten\");\n"
        else:
            scon += "fix_design();\n"
            if do_dft_check:
                scon += "stitch_scan_chain();\n"
                if ask4set.get('dft_check_fix', 0):
                    scon += "ai_check_dft(1);\n"
                else:
                    scon += "ai_check_dft(0);\n"
    elif debug_noneq:
        scon += f"set_ai_remote_server('localhost', 1998, 0, '___ENDQQ___', '___ENDAA___', 'LLM_1_3_0', 0);\n" 
        scon += f"ai_debug_noneq(\"{noneq_pnt}\");\n"
    elif ask4set['intent'] == 'gof_debug':
        scon += f"set_ai_remote_server('localhost', 1998, 0, '___ENDQQ___', '___ENDAA___', 'LLM_1_1_0', 0);\n" 
    elif do_remap_item:
        scon += f"set_ai_remote_server('localhost', 1998, 0, '___ENDQQ___', '___ENDAA___', 'LLM_1_3_0', 0);\n" 
        scon += f"ai_remap_item(\"{remap_item}\");\n"
    #elif do_llm_dfix:
    #    scon += f"set_ai_remote_server('localhost', 1998, 0, '___ENDQQ___', '___ENDAA___', 'LLM_1_3_0', 0);\n" 
    #    imp_rtl = ask4set['imp_rtl']
    #    ref_rtl = ask4set['ref_rtl']
    #    ecoscript = "sandbox/dfix_eco.gpl"
    #    scon += f"ai_llm_dfix('{imp_rtl}','{ref_rtl}','{ecoscript}');\n"
    if run_script:
        scon += f"run('{run_script}');\n"
    if 'metal_only' in ask4set and ask4set['metal_only']:
        if 'gate_array_enable' in ask4set and ask4set['gate_array_enable']:
            ga_pat = "G*"
            if 'gate_array_pattern' in ask4set:
                ga_pat = ask4set['gate_array_pattern']
            ga_filler = "GFILLER*"
            if 'gate_array_filler' in ask4set:
                ga_filler = ask4set['gate_array_filler']
            scon += f"get_spare_cells('-gate_array', '{ga_pat}', '-gate_array_filler', '{ga_filler}'); # Gate array cells extraction\n"
            scon += "map_spare_cells();\n"
        else:
            spare_pat = "*/*_SPARE*"
            if 'spare_gate_pattern' in ask4set:
                spare_pat = ask4set['spare_gate_pattern']
            scon += f"get_spare_cells('{spare_pat}'); # To get spare cells in '{spare_pat}' format in Metal only post mask ECO \n"
            scon += "map_spare_cells(); # To map the new added gates to the spare cells\n"
       
    econetlist = ""

    if 'upf' in ask4set:
         if ask4set.get('power_check_fix', 0):
             scon += "ai_check_power(1);\n"
         else:
             scon += "ai_check_power(0);\n"
    
    if (manual or automatic or run_script or do_llm_dfix) and ask4set['intent'] != 'gof_debug':
        if nl:
            scon += "set_top('~');\n"
        scon += "report_eco(); # ECO report\n"
        scon += "check_design(\"-eco\");# Check if the ECO causes any issue, like floating\n"
        if 'output_netlist' in ask4set:
            econetlist = ask4set['output_netlist']
        if not econetlist and not ask4set['foreco']:
            econetlist = "/proj/ai_acc/eco/eco_verilog.v"
        if econetlist:
            scon += f"write_verilog(\"{econetlist}\");# Write out ECO result in Verilog\n"
        if 'output_perl' in ask4set:
            perlout = ask4set['output_perl']
            scon += f"write_perl(\"{perlout}\");# Write out ECO result in Perl\n"
    else:
        econetlist = impnetlist # LEC only
    if 'output_tcl' in ask4set:
        tclout = ask4set['output_tcl']
        if tclout:
            scon += f"write_tcl(\"{tclout}\");# Write out ECO result in TCL\n" 
    else:
        if innovus:
             scon += "write_soce(\"/proj/ai_acc/eco/eco_innovus.soce\");# Write out ECO result in Innovus ECO script\n"
        
    if lec:
        
        scon += f"set_ai_remote_server('localhost', 1998, 0, '___ENDQQ___', '___ENDAA___', 'LLM_1_1_0', 0);\n" 
        scon += "run_lec(); # Run GOF LEC\n"
    if formality:
        scon += "write_compare_points(\"compare_points.report\");\n"
        scon += "write_formality_help_files(\"fm_dir/formality_help\"); # formality_help files are generated in fm_dir folder\n"
    
    if formality:
        scon += "\nIntegrate the file into Formality script\n"
        scon += "# Formality netlist vs netlist script\n"
        scon += "read_db -tech tsmc.db\n"
        scon += f"read_verilog -r {refnetlist}\n"
        scon += f"read_verilog -i {econetlist}\n"
        scon += "#Setup constraint\n"
        scon += "#Read in the help config file \n"
        scon += "source fm_dir/formality_help.config.tcl\n"
        scon += "match\n"
        scon += "verify\n"

    if sch:
        if refnetlist:
            scon += f"sch(\"{sch}\", '-both');\n"
        else:
            scon += f"sch(\"{sch}\");\n"

    if dft_drc:
        scon += "set_pin_constant(\"PIN_TEST_MODE\", 1);\n" 
        scon += "set_pin_constant(\"PIN_SCN_SHIFT\", 1);\n" 
        scon += "my @ports = get_ports(\"PIN_SCN_SO*\");\n"
        scon += "foreach my $port (@ports){\n"
        scon += "  my $in = $port;\n"
        scon += "  $in =~ s/PIN_SCN_SO/PIN_SCN_SI/;\n"
        scon += "  set_scan_pairs($in, $port);\n"
        scon += "}\n"
        scon += "create_clock(\"PIN_DFT_CLK1\", 8);\n"
        scon += "create_clock(\"PIN_DFT_CLK2\", 8);\n"
        scon += "create_reset(\"PIN_RESET\", 1);\n"
        scon += "my $err = dft_drc;\n"

    if not ask4set['foreco']:
        scon += "\n# You may need to change the file names, instance names and port names in the script\n"
    else:
        if not self.debug and ask4set['intent'] != 'gof_debug' or self.yes:
            scon += "gexit();\n" 
    return scon

def netlistECO(stype, command, self):
    aprint(f"==Do netlist processing type {stype}==\n", self)

    system = ecoPrompt(stype)
    prompt = f"Analyze the user query and return JSON string: {command}"
    messages = []
    messages.append(system)
    messages.append(prompt)
    
    self.use_model = "fast"
    answer = callLLM(messages, self) # JSON
    jobj = {}
    in_cnt = 5
    while in_cnt:
        in_cnt -= 1
        if re.search(r"```json", answer):
            jstring = re.search(r'```json(.*?)```', answer, flags=re.DOTALL).group(1)
            jstring = re.sub(r"\bnull\b", "0", jstring)
            jobj = expandEnvVars(eval(jstring))
            if "need_file_content" in jobj:
                for onefile in jobj['need_file_content']:
                    #print(f"try {onefile} in need_file_content ...\n")
                    if os.path.isdir(onefile):
                        files = glob.glob(onefile+"/*")
                        if len(files) > 20:
                            file_cnt = len(files)
                            messages.append(f"```content-of-the-directory-{onefile}\nWarning! The current directory has {file_cnt} files too many to display all\n```\n")
                        else:
                            cont = " ".join(files)
                            messages.append(f"```content-of-the-directory-{onefile}\n{cont}\n```\n")
                    elif os.path.exists(onefile):
                        cont = readFile(onefile)
                        messages.append(f"```content-of-the-file-{onefile}\n{cont}\n```\n")
                    else:
                        aprint(f"WARNING! the file/directory {onefile} doesn't exist\n", self)
                        messages.append(f"The file or directory {onefile} doesn't exist, move on ...\n")
                answer = callLLM(messages, self) # JSON
            elif "modify_rtl" in jobj:
                rtl = jobj['imp_rtl']
                change = jobj['modify_rtl']
                pyfile = sys.argv[0]
                cmds = [pyfile, '--prompt', change, '--files', rtl]
                cmdst = " ".join(cmds)
                aprint(f"Run RTL modification command {cmdst}\n", self)
                #subprocess.run(cmds, stdout=sys.stdout, stderr=sys.stderr)
                #result = subprocess.run(cmds, capture_output=True, text=True)
                modify_rtl_file = ""
                with subprocess.Popen(
                        cmds, 
                        stdout=subprocess.PIPE, 
                        stderr=subprocess.STDOUT, # Redirect errors to stdout so we catch everything
                        text=True,
                        bufsize=1  # Line buffered
                ) as process:
                    # Iterate over the output stream in real time
                    cnt = 0
                    for line in process.stdout:
                        cnt += 1
                        #print(line, end="")  # Print the line as it arrives
                        if re.search(r"# Set log file name '(\S+)'", line):
                            logfile = re.search(r"# Set log file name '(\S+)'", line).group(1)
                            aprint(f"Check GOF log file {logfile}\n", self)
                        if re.search(r"^INFO", line):
                            aprint(f"{line}\n", self)
                        if re.search(r"FINAL_VERILOG_OUTPUT_FILE (\S+)", line):
                            modify_rtl_file = re.search(r"FINAL_VERILOG_OUTPUT_FILE (\S+)", line).group(1)
                            
                # Check the exit code after the loop finishes
                return_code = process.wait()
                messages.append(f"The modified RTL file has been generated and saved to {modify_rtl_file}, remove JSON field 'modify_rtl'\n")
                answer = callLLM(messages, self)
               
                
            else:
                break
                    
    else:
        aprint("ERROR! LLM didn't return a JSON in ECO setting\n", self)
        return
    query = "\n".join(messages)


    error = checkECOSetting(jobj, self)
    if error:
        aprint(f"Please fix {error} errors in the ECO spec\n", self)
        return

    # gof ana
    difffile = "sandbox/analyze.diff"
    if 'imp_rtl' in jobj and 'ref_rtl' in jobj:
        imp_rtl = jobj['imp_rtl']
        ref_rtl = jobj['ref_rtl']
        fcont1 = readFile(imp_rtl)
        fcont2 = readFile(ref_rtl)
        lines1 = fcont1.strip().splitlines()
        lines2 = fcont2.strip().splitlines()
        alldiff = ""
        for line in difflib.unified_diff(lines1, lines2, fromfile=os.path.basename(imp_rtl), tofile=os.path.basename(ref_rtl), lineterm='', n=150):
            alldiff += line + "\n"
        writeFile(difffile, alldiff)
        aprint(f"Diff file {difffile} created\n", self)

    
    if "ana_eco" in jobj and jobj['ana_eco']:
        if 1: #not self.dfix: # dfix is direct fix in netlist by RTL diff
            #aprint("It's gof ana\n", self)
            anaRTL4ECO(jobj, command, self)
            return
    
    jspecs = []
    rtl_guided = 0
    metal_only = 0
    llm_dfix = 0
    if 'llm_dfix' in jobj and jobj['llm_dfix']:
        llm_dfix = 1
        rtl_guided = 1
        jobj['rtl_guided'] = 1
    elif 'rtl_guided' in jobj and jobj['rtl_guided']:
        rtl_guided = 1
    if 'metal_only' in jobj and jobj['metal_only']:
        metal_only = 1
        
    perl4next = ""
    if rtl_guided:
        jspec = {}
        jspec['foreco'] = 1
        fields = "eco_name library library_list imp_netlist power_check_only upf dft_check_only power_check_fix dft_check_fix llm_dfix dont_use ref_rtl imp_rtl ref_rtl_list imp_rtl_list set_define inc_dir hier_instance rtl_guided netlist_eco_module_name rtl_sub_module_name imp_svf constraint ignore_output output_perl".split()
        if not metal_only and not 'postlayout_netlist' in jobj:
            fields.append('output_netlist')
            fields.append('output_tcl')

        for one in fields:
            if one in jobj:
                jspec[one] = jobj[one]
                
        if metal_only:
            ctime = int(time.time())
            perl4next = f"sandbox/gof_output.{ctime}.pl"
            jspec['output_perl'] = perl4next
        elif 'postlayout_netlist' in jobj:
            ctime = int(time.time())
            perl4next = f"sandbox/gof_output.{ctime}.pl"
            jspec['output_perl'] = perl4next
            
        jspec['script_type'] = "auto_eco"
        jspecs.append(jspec)
    if not metal_only and 'postlayout_netlist' in jobj:
        jspec = {}
        jspec['foreco'] = 1
        fields = "eco_name library library_list dont_use imp_netlist netlist_eco_module_name imp_svf output_perl output_netlist output_tcl".split()
        for one in fields:
            if one in jobj:
                jspec[one] = jobj[one]
        jspec['run_script'] = perl4next
        jspec['imp_netlist'] = jobj['postlayout_netlist']
        jspec['eco_name'] += "_be_net"
        jspec['script_type'] = "auto_eco"
        jspecs.append(jspec)
    if metal_only and rtl_guided:
        jspec = {}
        jspec['foreco'] = 1
        fields = "eco_name dont_use library library_list imp_netlist metal_only netlist_eco_module_name imp_svf output_perl output_netlist output_tcl spare_gate_pattern gate_array_enable gate_array_pattern lef def".split()
        for one in fields:
            if one in jobj:
                jspec[one] = jobj[one]
        jspec['run_script'] = perl4next
        jspec['imp_netlist'] = jobj['postlayout_netlist']
        jspec['eco_name'] += "_metal_only"
        jspec['script_type'] = "auto_eco"
        jspecs.append(jspec)
    if len(jspecs)==0:
        jspec = jobj
        jspec['foreco'] = 0
        jspec['script_type'] = findScriptType(jobj)
        jspecs.append(jspec)
    scripts = []
    for onespec in jspecs:
        onespec['foreco'] = 1 # force to be accurate
        onespec['intent'] = stype
        sc_cont = funGenScript(query, onespec, self)
        aprint(sc_cont, self)
        ctime = int(time.time())
        script_file = f"sandbox/eco_script.{ctime}.pl"
        while os.path.exists(script_file):
            ctime = int(time.time())
            script_file = f"sandbox/eco_script.{ctime}.pl"
        writeFile(script_file, sc_cont)
        aprint(f"\n\n=== ECO script is saved to {script_file} ===\n\n", self)
        scripts.append(script_file)
    if self.yes:
        user_input = 'y'
    else:
        user_input = input(f"Do you want to proceed the ECO run? (y/n) ").strip()
    if user_input == 'y':
        gof_path = get_gof_path(self) #self.gof_config['gof']
        for onescript in scripts:
            cmds = [f'{gof_path}/gof', '-run', onescript]
            cmdst = " ".join(cmds)
            aprint(f"Run ECO command {cmdst}\n", self)
            subprocess.run(cmds, stdout=sys.stdout, stderr=sys.stderr)
            if not 'eco_name' in jobj:
                jobj['eco_name'] = 'default_eco'
            logfile = jobj['eco_name']+".log"
            aprint(f"INFO! ECO has done, check {logfile} for more information\n", self)


def checkNeededFiles(needed, jobj, self):
    error = 0
    for need in needed:
        des = needed[need]
        if not need in jobj:
            aprint(f"ERROR! {des} is not provided\n", self)
            error += 1
        else:
            thefile = jobj[need]
            if not os.path.exists(thefile):
                aprint(f"ERROR! {des} {thefile} does not exist\n", self)
                error += 1
    return error
def checkECOSetting(jobj, self):
    error = 0
    if 'rtl_guided' in jobj and jobj['rtl_guided']:
        needed = {'ref_rtl': "The reference RTL Verilog (modified RTL)",
                  'imp_rtl': "The implementation RTL Verilog (original RTL)",
                  'imp_netlist': "The implmentation netlist (Prelayout netlist)"
                  }
        error += checkNeededFiles(needed, jobj, self)
    if 'metal_only' in jobj and jobj['metal_only']:
        needed = {'postlayout_netlist': "The postlayout netlist",
                  'lef': "The LEF file",
                  'def': "The DEF file"
                  }
        error += checkNeededFiles(needed, jobj, self)
        if 'gate_array_enable' in jobj and jobj['gate_array_enable'] and (not 'gate_array_pattern' in jobj or not 'gate_array_filler' in jobj):
            aprint("ERROR! gate array pattern and filler pattern should be specified in the spec file\n", self)
            error += 1
        elif not 'spare_gate_pattern' in jobj:
            aprint("ERROR! spare gate pattern or gate array pattern should be speficied in metal only ECO\n", self)
            error += 1

    if 'svf' in jobj and jobj['svf']:
        svf = jobj['svf']
        if not os.path.exists(svf):
            error += 1
            aprint(f"ERROR! SVF file {svf} doesn't exist\n", self)
    if 'check_power' in jobj and jobj['check_power']:
        if not 'upf' in jobj:
            error += 1
            aprint(f"ERROR! UPF file doesn't specify\n", self)
        else:
            upf = jobj['upf']
            if not os.path.exists(upf):
                error += 1
                aprint(f"ERROR! UPF file {upf} doesn't exist\n", self)
    return error




if __name__ == "__main__":
    args = parse_args()
    pid = 'default'
    if args.id:
        pid = args.id
    prompt = ""
    if args.prompt:
        prompt = args.prompt
    elif args.instruction:
        prompt = " ".join(args.instruction)
    if prompt:
        app = NoGuiApp(pid)
        app.in_display = ""
        app.debug = args.debug
        app.query = prompt
        app.cli_mode = 1
        if args.noa:
            app.noa = 1
        if args.dfix:
            app.dfix = 1
        if args.yes:
            app.yes = 1
        do_llm_call(app, 1, app.query)

