├── .gitignore ├── AnalogCoder.png ├── AnalogCoder_label.png ├── README.md ├── environment.yml ├── execution_error.md ├── gpt_run.py ├── hkummlab.png ├── lib_info.tsv ├── problem_check ├── Adder.py ├── Amplifier.py ├── CurrentMirror.py ├── Differentiator.py ├── Integrator.py ├── Inverter.py ├── Opamp.py ├── Oscillator.py ├── PLL.py ├── Schmitt.py ├── Subtractor.py └── VCO.py ├── problem_set.tsv ├── prompt_template.md ├── prompt_template_complex.md ├── retrieval_prompt.md ├── sample_design ├── p1.py ├── p10.py ├── p11.py ├── p12.py ├── p13.py ├── p14.py ├── p15.py ├── p16.py ├── p17.py ├── p18.py ├── p19.py ├── p2.py ├── p20.py ├── p21.py ├── p22.py ├── p23.py ├── p24.py ├── p3.py ├── p4.py ├── p5.py ├── p6.py ├── p7.py ├── p8.py ├── p9.py ├── p_lib.py ├── subcircuits │ ├── __init__.py │ ├── charge_pump.py │ ├── circuit_imgs │ │ ├── RingVCO.png │ │ ├── cp_and_lf.png │ │ ├── divider.png │ │ ├── opamp.png │ │ └── pfd.png │ ├── dff.py │ ├── diffop.py │ ├── divider.py │ ├── logic_gates.py │ ├── loop_filter.py │ ├── opamp.py │ ├── pfd.py │ └── ring_vco.py └── test_all_sample_design.py ├── simulation_error.md ├── subcircuit_lib ├── p10_lib.py ├── p11_lib.py ├── p12_lib.py ├── p13_lib.py ├── p14_lib.py ├── p15_lib.py ├── p1_lib.py ├── p2_lib.py ├── p3_lib.py ├── p4_lib.py ├── p5_lib.py ├── p6_lib.py ├── p7_lib.py ├── p8_lib.py └── p9_lib.py ├── teaser.png └── utda.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | *.sh 2 | 3 | *requirements.txt 4 | 5 | *_log.txt 6 | 7 | gpt3p5/ 8 | 9 | p1_0_0_check.py 10 | p1_0_0.py 11 | *_test.yml 12 | 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | share/python-wheels/ 36 | *.egg-info/ 37 | .installed.cfg 38 | *.egg 39 | MANIFEST 40 | 41 | # PyInstaller 42 | # Usually these files are written by a python script from a template 43 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 44 | *.manifest 45 | *.spec 46 | 47 | # Installer logs 48 | pip-log.txt 49 | pip-delete-this-directory.txt 50 | 51 | # Unit test / coverage reports 52 | htmlcov/ 53 | .tox/ 54 | .nox/ 55 | .coverage 56 | .coverage.* 57 | .cache 58 | nosetests.xml 59 | coverage.xml 60 | *.cover 61 | *.py,cover 62 | .hypothesis/ 63 | .pytest_cache/ 64 | cover/ 65 | 66 | # Translations 67 | *.mo 68 | *.pot 69 | 70 | # Django stuff: 71 | *.log 72 | local_settings.py 73 | db.sqlite3 74 | db.sqlite3-journal 75 | 76 | # Flask stuff: 77 | instance/ 78 | .webassets-cache 79 | 80 | # Scrapy stuff: 81 | .scrapy 82 | 83 | # Sphinx documentation 84 | docs/_build/ 85 | 86 | # PyBuilder 87 | .pybuilder/ 88 | target/ 89 | 90 | # Jupyter Notebook 91 | .ipynb_checkpoints 92 | 93 | # IPython 94 | profile_default/ 95 | ipython_config.py 96 | 97 | # pyenv 98 | # For a library or package, you might want to ignore these files since the code is 99 | # intended to run in multiple environments; otherwise, check them in: 100 | # .python-version 101 | 102 | # pipenv 103 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 104 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 105 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 106 | # install all needed dependencies. 107 | #Pipfile.lock 108 | 109 | # poetry 110 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 111 | # This is especially recommended for binary packages to ensure reproducibility, and is more 112 | # commonly ignored for libraries. 113 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 114 | #poetry.lock 115 | 116 | # pdm 117 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 118 | #pdm.lock 119 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 120 | # in version control. 121 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 122 | .pdm.toml 123 | .pdm-python 124 | .pdm-build/ 125 | 126 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 127 | __pypackages__/ 128 | 129 | # Celery stuff 130 | celerybeat-schedule 131 | celerybeat.pid 132 | 133 | # SageMath parsed files 134 | *.sage.py 135 | 136 | # Environments 137 | .env 138 | .venv 139 | env/ 140 | venv/ 141 | ENV/ 142 | env.bak/ 143 | venv.bak/ 144 | 145 | # Spyder project settings 146 | .spyderproject 147 | .spyproject 148 | 149 | # Rope project settings 150 | .ropeproject 151 | 152 | # mkdocs documentation 153 | /site 154 | 155 | # mypy 156 | .mypy_cache/ 157 | .dmypy.json 158 | dmypy.json 159 | 160 | # Pyre type checker 161 | .pyre/ 162 | 163 | # pytype static type analyzer 164 | .pytype/ 165 | 166 | # Cython debug symbols 167 | cython_debug/ 168 | 169 | # PyCharm 170 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 171 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 172 | # and can be added to the global gitignore or merged into this file. For a more nuclear 173 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 174 | #.idea/ -------------------------------------------------------------------------------- /AnalogCoder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laiyao1/AnalogCoder/6225b4925fcced0d08d3bd9ee4010b8649144b31/AnalogCoder.png -------------------------------------------------------------------------------- /AnalogCoder_label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laiyao1/AnalogCoder/6225b4925fcced0d08d3bd9ee4010b8649144b31/AnalogCoder_label.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AnalogCoder: Analog Circuit Design via Training-Free Code Generation 2 | 3 | New: Our paper has been accepted by **AAAI'25**. 4 | 5 |

6 | alt text 7 |

8 | 9 | The code implemented for AAAI'25 paper **AnalogCoder: Analog Circuit Design via Training-Free Code Generation**. 10 | 11 | [Yao Lai](https://laiyao1.github.io/)1, [Sungyoung Lee](https://brianlsy98.github.io/)2, [Guojin Chen](https://gjchen.me/)3, [Souradip Poddar](https://www.linkedin.com/in/souradip-poddar-52376212a/)2, [Mengkang Hu](https://aaron617.github.io/)1, [David Z. Pan](https://users.ece.utexas.edu/~dpan/)2, [Ping Luo](http://luoping.me/)1. 12 | 13 | 1 The University of Hong Kong, 14 | 2 The University of Texas at Austin, 15 | 3 The Chinese University of Hong Kong. 16 | 17 | 18 | Image 1 19 | 20 | 21 | 22 | Image 2 23 | 24 | 25 | 26 | 27 | [[Paper](https://arxiv.org/pdf/2405.14918)] 28 | 29 | # Introduction 30 | 31 |

32 | alt text 33 |

34 | 35 | **Analog circuit design** is a significant task in modern chip technology, focusing on selecting component types, connectivity, and parameters to ensure proper circuit functionality. Despite advances made by Large Language Models (LLMs) in digital circuit design, the **complexity** and **scarcity of data** in analog circuitry pose significant challenges. To mitigate these issues, we introduce **AnalogCoder**, the *first* training-free LLM agent for designing analog circuits that converts tasks into **Python code generation**. 36 | 37 | Main advantages of AnalogCoder: 38 | - AnalogCoder features a feedback-enhanced flow with crafted domain-specific prompts, enabling effective and automated design of analog circuits with a high success rate. 39 | - It proposes a circuit skill library to archive successful designs as reusable modular sub-circuits, simplifying composite circuit creation. 40 | - Extensive testing on a custom-designed benchmark of 24 analog circuit design tasks of varying difficulty shows that AnalogCoder successfully designed 20 circuits, outperforming existing methods. 41 | 42 | 43 | In summary, AnalogCoder can significantly improve the labor-intensive chip design process, enabling non-experts to efficiently design analog circuits. 44 | 45 | # Evaluation of LLMs 46 | 47 | **Ranking method**: # of solved (number of successfully solved circuit design problems) takes priority. If tied, higher average Pass@1 takes priority. 48 | 49 | | LLM Model | Avg. Pass@1 | Avg. Pass@5 | # of Solved | 50 | |----------------------------------------|-----------------:|-----------------:|----------------:| 51 | | Llama2-7B | 0.0 | 0.0 | 0 | 52 | | Llama2-13B | 0.0 | 0.0 | 0 | 53 | | SemiKong-8B* | 0.1 | 0.7 | 1 | 54 | | Llama3-8B | 0.1 | 0.7 | 1 | 55 | | Phi3-14B | 0.3 | 1.3 | 1 | 56 | | Qwen-1.5-110B | 0.3 | 1.4 | 2 | 57 | | CodeLlama-13B | 0.6 | 2.5 | 2 | 58 | | Mistral-7B | 3.3 | 7.7 | 2 | 59 | | Llama 2-70B | 5.1 | 9.8 | 3 | 60 | | CodeQwen-1.5-7B | 1.1 | 5.6 | 4 | 61 | | CodeLlama-34B | 1.9 | 7.4 | 4 | 62 | | CodeLlama-7B | 2.4 | 9.0 | 4 | 63 | | DeepSeek-Coder-33B | 4.0 | 10.2 | 4 | 64 | | Llama3.1-8B | 4.9 | 12.9 | 4 | 65 | | Magicoder-7B | 3.8 | 8.6 | 5 | 66 | | Mixtral-8×7B | 5.6 | 12.4 | 5 | 67 | | StarCoder2-15B-Instuct | 5.6 | 12.4 | 5 | 68 | | CodeGeeX4-9B* | 10.6 | 20.3 | 6 | 69 | | CodeLlama-70B | 3.2 | 12.2 | 7 | 70 | | CodeGemma-7B | 6.9 | 17.0 | 7 | 71 | | WizardCoder-33B | 7.1 | 16.9 | 7 | 72 | | GPT-3.5 (w/o context) | 8.1 | 18.5 | 7 | 73 | | GPT-3.5 (w/o flow) | 12.8 | 25.3 | 8 | 74 | | Codestral-22B | 16.4 | 29.1 | 8 | 75 | | GPT-3.5 (w/o CoT) | 19.4 | 26.3 | 8 | 76 | | GLM-4 | 22.8 | 31.2 | 8 | 77 | | GPT-3.5 (SPICE) | 13.9 | 26.9 | 9 | 78 | | GPT-3.5 | 21.4 | 35.0 | 10 | 79 | | GPT-3.5 (fine-tune) | 28.1 | 39.6 | 10 | 80 | | Llama3-70B | 28.8 | 36.4 | 11 | 81 | | Gemini-1.0-Pro | 28.9 | 41.2 | 11 | 82 | | Gemini-1.5-Flash | 35.7 | 40.6 | 11 | 83 | | Qwen-2-72B | 9.3 | 26.6 | 12 | 84 | | GPT-4o-mini | 34.9 | 41.7 | 12 | 85 | | DeepSeek-V2-Chat | 38.6 | 44.3 | 13 | 86 | | GPT-4 (w/o tool) | 51.1 | 57.7 | 14 | 87 | | Llama3.1-70B | 25.4 | 42.6 | 14 | 88 | | GPT-4o (w/o tool) | 54.2 | 58.9 | 15 | 89 | | Claude-3.5-Sonnet (w/o tool) | 58.1 | 60.7 | 15 | 90 | | Mistral-Large-2 | 28.6 | 51.0 | 17 | 91 | | Gemini-1.5-Pro | 33.9 | 44.6 | 17 | 92 | | DeepSeek-V2-Coder | 56.5 | 69.2 | 19 | 93 | | Llama3.1-405B | 56.9 | 70.7 | 20 | 94 | | AnalogCoder (GPT 4o-based) | 66.1 | 75.9 | 20 | 95 | | AnalogCoder (Claude 3.5 Sonnet-based) | 76.1 | 86.3 | 22 | 96 | 97 | \* without CoT (prompt to directly generate codes rather than devices) due to token limitations or its primary design for code generation. 98 | 99 | 100 | Note: 101 | 1. All our results are reproducible. 102 | 2. The configuration of the environment does NOT require sudo privileges. 103 | 104 | 105 | 106 | 107 | # Installation 108 | AnalogCoder requires Python ≥ 3.10, PySpice ≥ 1.5, and openai >= 1.16.1. 109 | 110 | ## Python Install 111 | ``` 112 | git clone https://github.com/anonyanalog/AnalogCoder 113 | cd AnalogCoder 114 | conda env create -f environment.yml 115 | conda activate analog 116 | ``` 117 | 118 | ## Environment Check 119 | To ensure the current environment is functional, the following tests can be performed: 120 | 121 | ``` 122 | cd sample_design 123 | python test_all_sample_design.py 124 | ``` 125 | 126 | When the program finishes running, if `All tasks passed` is displayed, it indicates that the environment is normal. 127 | 128 | Otherwise, it will display `Please check your environment and try again`. It means you should check the configuration of the current Python environment, especially the settings related to PySpice. 129 | 130 | 131 | 132 | 133 | 134 | # Quick Start 135 | You can directly run the following code for quick start. 136 | ``` 137 | python gpt_run.py --task_id=1 --api_key="[OPENAI_API]" --num_per_task=1 138 | ``` 139 | which will generate one circuit based on task 1. 140 | 141 | 142 | # Benchmark 143 | - Task descriptions are in `problem_set.tsv`. 144 | - Sample circuits are in directory `sample_design`. 145 | - Test-benches are in directory `problem_check`. 146 | 147 | # Citation 148 | If you find our work beneficial, we would be grateful if you considered citing our paper. 149 | 150 | 151 | ``` 152 | @misc{lai2024analogcoder, 153 | title={AnalogCoder: Analog Circuit Design via Training-Free Code Generation}, 154 | author={Yao Lai and Sungyoung Lee and Guojin Chen and Souradip Poddar and Mengkang Hu and David Z. Pan and Ping Luo}, 155 | year={2024}, 156 | eprint={2405.14918}, 157 | archivePrefix={arXiv}, 158 | primaryClass={cs.LG} 159 | } 160 | ``` 161 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: analog 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - matplotlib 7 | - pandas 8 | - numpy 9 | - scipy 10 | - ollama 11 | - openai 12 | - pyspice==1.5 -------------------------------------------------------------------------------- /execution_error.md: -------------------------------------------------------------------------------- 1 | I am encountering an error when running the PySpice code. Below is the error message: 2 | [ERROR] 3 | Pleae help me identify the bug and write the complete corrected code. -------------------------------------------------------------------------------- /gpt_run.py: -------------------------------------------------------------------------------- 1 | from openai import OpenAI 2 | import openai 3 | import argparse 4 | import re 5 | import os 6 | import subprocess 7 | import time 8 | import pandas as pd 9 | import sys 10 | import shutil 11 | 12 | import signal 13 | import json 14 | 15 | class TimeoutException(Exception): 16 | pass 17 | 18 | def signal_handler(signum, frame): 19 | raise TimeoutException("timeout") 20 | 21 | parser = argparse.ArgumentParser() 22 | parser.add_argument('--model', type=str, default="gpt-3.5-turbo") 23 | parser.add_argument('--temperature', type=float, default=0.5) 24 | parser.add_argument('--num_per_task', type=int, default=15) 25 | parser.add_argument('--num_of_retry', type=int, default=3) 26 | parser.add_argument("--num_of_done", type=int, default=0) 27 | parser.add_argument("--task_id", type=int, default=1) 28 | parser.add_argument("--ngspice", action="store_true", default=False) 29 | parser.add_argument("--no_prompt", action="store_true", default=False) 30 | parser.add_argument("--skill", action="store_true", default=False) 31 | parser.add_argument("--no_context", action="store_true", default=False) 32 | parser.add_argument("--no_chain", action="store_true", default=False) 33 | parser.add_argument("--retrieval", action="store_true", default=True) 34 | parser.add_argument('--api_key', type=str) 35 | 36 | args = parser.parse_args() 37 | 38 | opensource_models = ["mistral", "wizardcoder", "deepseek-coder:33b-instruct", "codeqwen", "mixtral", "qwen"] 39 | 40 | if any([model in args.model for model in opensource_models]): 41 | import ollama 42 | if args.skill: 43 | args.num_of_retry = min(2, args.num_of_retry) 44 | 45 | complex_task_type = ['Oscillator', 'Integrator', 'Differentiator', 'Adder', 'Subtractor', 'Schmitt'] 46 | bias_usage = """Due to the operational range of the op-amp being 0 to 5V, please connect the nodes that were originally grounded to a 2.5V DC power source. 47 | Please increase the gain as much as possible to maintain oscillation. 48 | """ 49 | 50 | 51 | dc_sweep_template = """ 52 | import numpy as np 53 | analysis = simulator.dc(V[IN_NAME]=slice(0, 5, 0.01)) 54 | fopen = open("[DC_PATH]", "w") 55 | out_voltage = np.array(analysis.Vout) 56 | in_voltage = np.array(analysis.V[IN_NAME]) 57 | print("out_voltage: ", out_voltage) 58 | print("in_voltage: ", in_voltage) 59 | for item in in_voltage: 60 | fopen.write(f"{item:.4f} ") 61 | fopen.write("\\n") 62 | for item in out_voltage: 63 | fopen.write(f"{item:.4f} ") 64 | fopen.write("\\n") 65 | fopen.close() 66 | """ 67 | 68 | 69 | pyspice_template = """ 70 | try: 71 | analysis = simulator.operating_point() 72 | fopen = open("[OP_PATH]", "w") 73 | for node in analysis.nodes.values(): 74 | fopen.write(f"{str(node)}\\t{float(analysis[str(node)][0]):.6f}\\n") 75 | fopen.close() 76 | except Exception as e: 77 | print("Analysis failed due to an error:") 78 | print(str(e)) 79 | """ 80 | 81 | 82 | output_netlist_template = """ 83 | source = str(circuit) 84 | print(source) 85 | """ 86 | 87 | import_template = """ 88 | from PySpice.Spice.Netlist import Circuit 89 | from PySpice.Unit import * 90 | """ 91 | 92 | 93 | sin_voltage_source_template = """ 94 | circuit.SinusoidalVoltageSource('sin', 'Vin', circuit.gnd, 95 | ac_magnitude=1@u_nV, dc_offset={0}, amplitude=1@u_nV, offset={0}) 96 | """ 97 | 98 | 99 | global client 100 | 101 | 102 | if "gpt" in args.model: 103 | client = OpenAI(api_key=args.api_key) 104 | elif "deepseek-chat" in args.model: 105 | client = OpenAI(api_key=args.api_key, base_url="https://api.deepseek.com/v1") 106 | else: 107 | client = None 108 | 109 | 110 | # This function extracts the code from the generated content which in markdown format 111 | def extract_code(generated_content): 112 | empty_code_error = 0 113 | assert generated_content != "", "generated_content is empty" 114 | regex = r".*?```.*?\n(.*?)```" 115 | matches = re.finditer(regex, generated_content, re.DOTALL) 116 | first_match = next(matches, None) 117 | try: 118 | code = first_match.group(1) 119 | print("code", code) 120 | code = "\n".join([line for line in code.split("\n") if len(line.strip()) > 0]) 121 | except: 122 | code = "" 123 | empty_code_error = 1 124 | return empty_code_error, code 125 | # Add necessary libraries 126 | if not args.ngspice: 127 | if "from PySpice.Spice.Netlist import Circuit" not in code: 128 | code = "from PySpice.Spice.Netlist import Circuit\n" + code 129 | if "from PySpice.Unit import *" not in code: 130 | code = "from PySpice.Unit import *\n" + code 131 | new_code = "" 132 | for line in code.split("\n"): 133 | new_code += line + "\n" 134 | if "circuit.simulator()" in line: 135 | break 136 | 137 | return empty_code_error, new_code 138 | 139 | 140 | 141 | def run_code(file): 142 | print("IN RUN_CODE : {}".format(file)) 143 | simulation_error = 0 144 | execution_error = 0 145 | execution_error_info = "" 146 | floating_node = "" 147 | try: 148 | print("-----------------running code-----------------") 149 | print("file:", file) 150 | result = subprocess.run(["python", "-u", file], check=True, text=True, 151 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=60) 152 | print("num of lines", len(result.stdout.split("\n"))) 153 | print("num of error lines", len(result.stderr.split("\n"))) 154 | if len(result.stdout.split("\n")) >= 2 and ("failed" in result.stdout.split("\n")[-2] or "failed" in result.stdout.split("\n")[-1]): 155 | if len(result.stdout.split("\n")) >= 2: 156 | if "check node" in result.stdout.split("\n")[1]: 157 | simulation_error = 1 158 | floating_node = result.stdout.split("\n")[1].split()[-1] 159 | else: 160 | execution_error = 1 161 | if "ERROR" in result.stdout.split("\n")[1]: 162 | execution_error_info = "ERROR" + result.stdout.split("\n")[1].split("ERROR")[-1] 163 | elif "Error" in result.stdout.split("\n")[1]: 164 | execution_error_info = "Error" + result.stdout.split("\n")[1].split("Error")[-1] 165 | if len(result.stdout.split("\n"))>=3 and "ERROR" in result.stdout.split("\n")[2]: 166 | execution_error_info += "\nERROR" + result.stdout.split("\n")[2].split("ERROR")[-1] 167 | elif len(result.stdout.split("\n"))>=3 and "Error" in result.stdout.split("\n")[2]: 168 | execution_error_info += "\nError" + result.stdout.split("\n")[2].split("Error")[-1] 169 | if len(result.stdout.split("\n"))>=4 and "ERROR" in result.stdout.split("\n")[3]: 170 | execution_error_info += "\nERROR" + result.stdout.split("\n")[3].split("ERROR")[-1] 171 | elif len(result.stdout.split("\n"))>=4 and "Error" in result.stdout.split("\n")[3]: 172 | execution_error_info += "\nError" + result.stdout.split("\n")[3].split("Error")[-1] 173 | if len(result.stderr.split("\n")) >= 2: 174 | if "check node" in result.stderr.split("\n")[1]: 175 | simulation_error = 1 176 | floating_node = result.stderr.split("\n")[1].split()[-1] 177 | else: 178 | execution_error = 1 179 | if "ERROR" in result.stderr.split("\n")[1]: 180 | execution_error_info = "ERROR" + result.stderr.split("\n")[1].split("ERROR")[-1] 181 | elif "Error" in result.stderr.split("\n")[1]: 182 | execution_error_info = "Error" + result.stderr.split("\n")[1].split("Error")[-1] 183 | if len(result.stdout.split("\n"))>=3 and "ERROR" in result.stderr.split("\n")[2]: 184 | execution_error_info += "\nERROR" + result.stderr.split("\n")[2].split("ERROR")[-1] 185 | elif len(result.stdout.split("\n"))>=3 and "Error" in result.stdout.split("\n")[2]: 186 | execution_error_info += "\nError" + result.stdout.split("\n")[2].split("Error")[-1] 187 | if len(result.stdout.split("\n"))>=4 and "ERROR" in result.stderr.split("\n")[3]: 188 | execution_error_info += "\nERROR" + result.stderr.split("\n")[3].split("ERROR")[-1] 189 | elif len(result.stdout.split("\n"))>=4 and "Error" in result.stderr.split("\n")[3]: 190 | execution_error_info += "\nError" + result.stderr.split("\n")[3].split("Error")[-1] 191 | if simulation_error == 1: 192 | execution_error = 0 193 | if execution_error_info == "" and execution_error == 1: 194 | execution_error_info = "Simulation failed." 195 | code_content = open(file, "r").read() 196 | if "circuit.X" in code_content: 197 | execution_error_info += "\nPlease avoid using the subcircuit (X) in the code." 198 | if "error" in result.stdout.lower() and not "<= 2: 207 | if "check node" in e.stderr.split("\n")[1]: 208 | simulation_error = 1 209 | floating_node = e.stderr.split("\n")[1].split()[-1] 210 | execution_error = 1 211 | 212 | execution_error_info = e.stdout + e.stderr 213 | if simulation_error == 1: 214 | execution_error = 0 215 | execution_error_info = "Simulation failed." 216 | return execution_error, simulation_error, execution_error_info, floating_node 217 | except subprocess.TimeoutExpired: 218 | print(f"Time out error when running code.") 219 | execution_error = 1 220 | execution_error_info = "Time out error when running code.\n" 221 | execution_error_info = "Suggestion: Avoid letting users input in Python code.\n" 222 | return execution_error, simulation_error, execution_error_info, floating_node 223 | 224 | 225 | 226 | 227 | def check_netlist(netlist_path, operating_point_path, input, output, task_id, task_type): 228 | warning = 0 229 | warning_message = "" 230 | # Check all the input and output nodes are in the netlist 231 | if not os.path.exists(operating_point_path): 232 | return 0, "" 233 | fopen_op = open(operating_point_path, 'r').read() 234 | for input_node in input.split(", "): 235 | if input_node.lower() not in fopen_op.lower(): 236 | warning_message += "The given input node ({}) is not found in the netlist.\n".format(input_node) 237 | warning = 1 238 | for output_node in output.split(", "): 239 | if output_node.lower() not in fopen_op.lower(): 240 | warning_message += "The given output node ({}) is not found in the netlist.\n".format(output_node) 241 | warning = 1 242 | 243 | if warning == 1: 244 | warning_message += "Suggestion: You can replace the nodes actually used for input/output with the given names. Please rewrite the corrected complete code.\n" 245 | 246 | if task_type == "Inverter": 247 | return warning, warning_message 248 | vdd_voltage = 5.0 249 | vinn_voltage = 1.0 250 | vinp_voltage = 1.0 251 | for line in fopen_op.split("\n"): 252 | line = line.lower() 253 | if line.startswith("vdd"): 254 | vdd_voltage = float(line.split("\t")[-1]) 255 | if line.startswith("vinn"): 256 | vinn_voltage = float(line.split("\t")[-1]) 257 | if line.startswith("vinp"): 258 | vinp_voltage = float(line.split("\t")[-1]) 259 | 260 | if vinn_voltage != vinp_voltage: 261 | warning_message += "The given input voltages of Vinn and Vinp are not equal.\n" 262 | warning = 1 263 | warning_message += "Suggestion: Please make sure the input voltages are equal.\n" 264 | 265 | fopen_netlist = open(netlist_path, 'r') 266 | voltages = {} 267 | for line in fopen_op.split("\n"): 268 | if line.strip() == "": 269 | continue 270 | node, voltage = line.split() 271 | voltages[node] = float(voltage) 272 | voltages["0"] = 0 273 | voltages["gnd"] = 0 274 | 275 | vthn = 0.5 276 | vthp = 0.5 277 | miller_node_1 = None 278 | miller_node_2 = None 279 | resistance_exist = 0 280 | has_diodeload = 0 281 | first_stage_out = None 282 | for line in fopen_netlist.readlines(): 283 | if line.startswith('.'): 284 | continue 285 | if line.startswith("C"): 286 | if task_id == 9: 287 | miller_node_1 = line.split()[1].lower() 288 | miller_node_2 = line.split()[2].lower() 289 | if line.startswith("R"): 290 | resistance_exist = 1 291 | if line.startswith("M"): 292 | name, drain, gate, source, bulk, model = line.split()[:6] 293 | name = name[1:] 294 | drain = drain.lower() 295 | source = source.lower() 296 | bulk = bulk.lower() 297 | gate = gate.lower() 298 | mos_type = "NMOS" if "nmos" in model.lower() else "PMOS" 299 | ## Common-gate 300 | if task_id == 4: 301 | if drain == "vin" or gate == "vin": 302 | warning_message += (f"For a common-gate amplifier, the vin should be connected to source.\n") 303 | warning_message += (f"Suggestion: Please connect the vin to the source node.\n") 304 | warning = 1 305 | elif task_id == 3: 306 | if drain == "vout" or gate == "vout": 307 | warning_message += (f"For a common-drain amplifier, the vout should be connected to source.\n") 308 | warning_message += (f"Suggestion: Please connect the vout to the source node.\n") 309 | warning = 1 310 | elif task_id == 10: 311 | if gate == drain: 312 | has_diodeload = 1 313 | 314 | elif task_id == 9: 315 | if gate == "vin": 316 | first_stage_out = drain 317 | 318 | if mos_type == "NMOS": 319 | # VDS 320 | vds_error = 0 321 | if voltages[drain] == 0.0: 322 | if drain.lower() == "0" or drain.lower() == "gnd": 323 | warning_message += (f"Suggetions: Please avoid connect {mos_type} {name} drain to the ground.\n") 324 | else: 325 | vds_error = 1 326 | warning_message += (f"For {mos_type} {name}, the drain node ({drain}) voltage is 0.\n") 327 | # VDS 328 | elif voltages[drain] < voltages[source]: 329 | vds_error = 1 330 | warning_message += (f"For {mos_type} {name}, the drain node ({drain}) voltage is lower than the source node ({source}) voltage.\n") 331 | if vds_error == 1: 332 | warning_message += (f"Suggestion: Please set {mos_type} {name} with an activated state and make sure V_DS > V_GS - V_TH.\n") 333 | # VGS 334 | vgs_error = 0 335 | if voltages[gate] == voltages[source]: 336 | # vgs_error = 1 337 | if gate == source: 338 | warning_message += (f"For {mos_type} {name}, the gate node ({gate}) is connected to the source node ({source}).\n") 339 | warning_message += (f"Suggestion: Please {mos_type} {name}, please divide its gate ({gate}) and source ({source}) connection.\n") 340 | else: 341 | vgs_error = 1 342 | warning_message += (f"For {mos_type} {name}, the gate node ({gate}) voltage is equal to the source node ({source}) voltage.\n") 343 | elif voltages[gate] < voltages[source]: 344 | vgs_error = 1 345 | warning_message += (f"For {mos_type} {name}, the gate node ({gate}) voltage is lower than the source node ({source}) voltage.\n") 346 | elif voltages[gate] <= voltages[source] + vthn: 347 | vgs_error = 1 348 | warning_message += (f"For {mos_type} {name}, the gate node ({gate}) voltage is lower than the source node ({source}) voltage plus the threshold voltage.\n") 349 | if vgs_error == 1: 350 | warning_message += (f"Suggestion: Please set {mos_type} {name} with an activated state by increasing the gate voltage or decreasing the source voltage and make sure V_GS > V_TH.\n") 351 | if mos_type == "PMOS": 352 | # VDS 353 | vds_error = 0 354 | if voltages[drain] == vdd_voltage: 355 | if drain.lower() == "vdd": 356 | warning_message += (f"Suggestion: Please avoid connect {mos_type} {name} drain to the vdd.\n") 357 | else: 358 | vds_error = 1 359 | warning_message += (f"For {mos_type} {name}, the drain node ({drain}) voltage is V_dd.\n") 360 | # VDS 361 | elif voltages[drain] > voltages[source]: 362 | vds_error = 1 363 | warning_message += (f"For {mos_type} {name}, the drain node ({drain}) voltage is higher than the source node ({source}) voltage.\n") 364 | if vds_error == 1: 365 | warning_message += (f"Suggestion: Please set {mos_type} {name} with an activated state and make sure V_DS < V_GS - V_TH.\n") 366 | # VGS 367 | vgs_error = 0 368 | if voltages[gate] == voltages[source]: 369 | if gate == source: 370 | warning_message += (f"For {mos_type} {name}, the gate node ({gate}) is connected to the source node ({source}).\n") 371 | warning_message += f"Suggestion: Please {mos_type} {name}, please divide its gate ({gate}) and source ({source}) connection.\n" 372 | else: 373 | vgs_error = 1 374 | warning_message += (f"For {mos_type} {name}, the gate node ({gate}) voltage is equal to the source node ({source}) voltage.\n") 375 | elif voltages[gate] > voltages[source]: 376 | vgs_error = 1 377 | warning_message += (f"For {mos_type} {name}, the gate node ({gate}) voltage is higher than the source node ({source}) voltage.\n") 378 | elif voltages[gate] >= voltages[source] - vthp: 379 | vgs_error = 1 380 | warning_message += (f"For {mos_type} {name}, the gate node ({gate}) voltage is higher than the source node ({source}) voltage plus the threshold voltage.\n") 381 | if vgs_error == 1: 382 | warning_message += (f"Suggestion: Please set {mos_type} {name} with an activated state by decreasing the gate voltage or incresing the source voltage and make sure V_GS < V_TH.\n") 383 | 384 | if task_id in [1, 2, 3, 4, 5, 6, 8, 13]: 385 | if resistance_exist == 0: 386 | warning_message += "There is no resistance in the netlist.\n" 387 | warning_message += "Suggestion: Please add a resistance load in the netlist.\n" 388 | warning = 1 389 | if task_id == 9: 390 | if first_stage_out == None: 391 | warning_message += "There is no first stage output in the netlist.\n" 392 | warning_message += "Suggestion: Please add a first stage output in the netlist.\n" 393 | warning = 1 394 | elif (first_stage_out == miller_node_1 and miller_node_2 == "vout") or (first_stage_out == miller_node_2 and miller_node_1 == "vout"): 395 | pass 396 | elif miller_node_1 == None: 397 | warning_message += "There no Miller capacitor in the netlist.\n" 398 | warning_message += "Suggestion: Please correctly connect the Miller compensation capacitor." 399 | warning = 1 400 | else: 401 | warning_message += "The Miller compensation capacitor is not correctly connected.\n" 402 | warning_message += "Suggestion: Please correctly connect the Miller compensation capacitor." 403 | warning = 1 404 | if task_id == 10 and has_diodeload == 0: 405 | warning_message += "There is no diode-connected load in the netlist.\n" 406 | warning_message += "Suggestion: Please add a diode-connected load in the netlist.\n" 407 | warning = 1 408 | warning_message = warning_message.strip() 409 | if warning_message == "": 410 | warning = 0 411 | else: 412 | warning = 1 413 | warning_message = "According to the operating point check, there are some issues, which defy the general operating principles of MOSFET devices. \n" + warning_message + "\n" 414 | warning_message += "\nPlease help me fix the issues and rewrite the corrected complete code.\n" 415 | return warning, warning_message 416 | 417 | 418 | def check_function(task_id, code_path, task_type): 419 | fwrite_code_path = "{}_check.py".format(code_path.rsplit(".", 1)[0]) 420 | fwrite_code = open(fwrite_code_path, 'w') 421 | if task_type == "CurrentMirror": 422 | test_code = open("problem_check/CurrentMirror.py", "r").read() 423 | code = open(code_path, 'r').read() 424 | code = code + "\n" + test_code 425 | fwrite_code.write(code) 426 | fwrite_code.close() 427 | elif task_type == "Amplifier" or task_type == "Opamp": 428 | voltage = "1.0" 429 | test_code = open(f"problem_check/{task_type}.py", "r").read() 430 | for line in open(code_path, 'r').readlines(): 431 | if line.startswith("circuit.V") and "vin" in line.lower(): 432 | 433 | parts = line.split("#")[0].strip().rstrip(")").split(",") 434 | raw_voltage = parts[-1].strip() 435 | if raw_voltage[0] == "\"" or raw_voltage[0] == "'": 436 | raw_voltage = raw_voltage[1:-1] 437 | if "dc" in raw_voltage.lower(): 438 | voltage = raw_voltage.split(" ")[1] 439 | else: 440 | voltage = raw_voltage 441 | new_voltage = " \"dc {} ac 1u\"".format(voltage) 442 | parts[-1] = new_voltage 443 | line = ",".join(parts) + ")\n" 444 | 445 | fwrite_code.write(line) 446 | fwrite_code.write("\n") 447 | fwrite_code.write(test_code) 448 | fwrite_code.close() 449 | print("voltage", voltage) 450 | elif task_type == "Inverter": 451 | test_code = open("problem_check/Inverter.py", "r").read() 452 | code = open(code_path, 'r').read() 453 | code = code + "\n" + test_code 454 | fwrite_code.write(code) 455 | fwrite_code.close() 456 | else: 457 | return 0, "" 458 | try: 459 | result = subprocess.run(["python", "-u", fwrite_code_path], check=True, text=True, 460 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 461 | print(result.stdout) 462 | print("function correct.") 463 | func_error = 0 464 | return_message = "" 465 | except subprocess.CalledProcessError as e: 466 | print("function error.") 467 | print("e.stdout", e.stdout) 468 | print("e.stderr", e.stderr) 469 | func_error = 1 470 | return_message = "\n".join(e.stdout.split("\n")) 471 | 472 | return func_error, return_message 473 | 474 | import numpy as np 475 | def get_best_voltage(dc_file_path): 476 | fopen = open(dc_file_path, 'r') 477 | vin = np.array([float(x) for x in fopen.readline().strip().split(" ")]) 478 | vout = np.array([float(x) for x in fopen.readline().strip().split(" ")]) 479 | if np.max(vout) - np.min(vout) < 1e-3: 480 | return 1, 0 481 | min_margin = 10.0 482 | for i, v in enumerate(vout): 483 | if np.abs(v - 2.5) < min_margin: 484 | min_margin = np.abs(v - 2.5) 485 | best_voltage = vin[i] 486 | return 0, best_voltage 487 | 488 | 489 | def get_vin_name(netlist_content, task_type): 490 | vinn_name = "in" 491 | vinp_name = None 492 | for line in netlist_content.split("\n"): 493 | if not line.lower().startswith("v"): 494 | continue 495 | if len(line.lower().split()) < 2: 496 | continue 497 | if task_type == "Amplifier" and "vin" in line.lower().split()[1]: 498 | vinn_name = line.split()[0][1:] 499 | if task_type == "Opamp" and "vinp" in line.lower().split()[1]: 500 | vinp_name = line.split()[0][1:] 501 | if task_type == "Opamp" and "vinn" in line.lower().split()[1]: 502 | vinn_name = line.split()[0][1:] 503 | return vinn_name, vinp_name 504 | 505 | 506 | def replace_voltage(raw_code, best_voltage, vinn_name, vinp_name): 507 | new_code = "" 508 | for line in raw_code.split("\n"): 509 | if not line.lower().startswith("circuit.v"): 510 | new_code += line + "\n" 511 | continue 512 | if vinn_name is not None and (line.lower().startswith(f"circuit.v('{vinn_name.lower()}'") or line.lower().startswith(f"circuit.v(\"{vinn_name.lower()}\"")): 513 | parts = line.split("#")[0].strip().rstrip(")").split(",") 514 | new_voltage = " {}".format(best_voltage) 515 | parts[-1] = new_voltage 516 | line = ",".join(parts) + ")" 517 | elif vinp_name is not None and (line.lower().startswith(f"circuit.v('{vinp_name.lower()}'") or line.lower().startswith(f"circuit.v(\"{vinp_name.lower()}\"")): 518 | parts = line.split("#")[0].strip().rstrip(")").split(",") 519 | new_voltage = " {}".format(best_voltage) 520 | parts[-1] = new_voltage 521 | line = ",".join(parts) + ")" 522 | new_code += line + "\n" 523 | return new_code 524 | 525 | 526 | def connect_vinn_vinp(dc_sweep_code, vinn_name, vinp_name): 527 | new_code = "" 528 | for line in dc_sweep_code.split("\n"): 529 | if not line.lower().startswith("circuit.v"): 530 | new_code += line + "\n" 531 | continue 532 | if vinp_name is not None and (line.lower().startswith(f"circuit.v('{vinp_name.lower()}'") or line.lower().startswith(f"circuit.v(\"{vinp_name.lower()}\"")): 533 | new_line = "circuit.V('dc', 'Vinn', 'Vinp', 0.0)\n" 534 | new_code += new_line 535 | else: 536 | new_code += line + "\n" 537 | return new_code 538 | 539 | def get_subcircuits_info(subcircuits, 540 | lib_data_path = "lib_info.tsv", task_data_path = "problem_set.tsv"): 541 | lib_df = pd.read_csv(lib_data_path, delimiter='\t') 542 | task_df = pd.read_csv(task_data_path, delimiter='\t') 543 | # New data frame 544 | columns = ["Id", "Circuit Type", "Gain/Differential-mode gain (dB)", "Common-mode gain (dB)", "Input", "Output"] 545 | subcircuits_df = pd.DataFrame(columns=columns) 546 | # write all the subcircuits information 547 | for sub_id in subcircuits: 548 | print("sub_id", sub_id) 549 | lib_df_row = lib_df.loc[lib_df['Id'] == sub_id] 550 | task_df_row = task_df.loc[task_df['Id'] == sub_id] 551 | print("task_df_row", task_df_row) 552 | sub_type = task_df.loc[task_df['Id'] == sub_id, 'Type'].item() 553 | sub_gain = float(lib_df.loc[lib_df['Id'] == sub_id, 'Av (dB)'].item()) 554 | sub_com_gan = float(lib_df.loc[lib_df['Id'] == sub_id, 'Com Av (dB)'].item()) 555 | sub_gain = "{:.2f}".format(sub_gain) 556 | sub_com_gan = "{:.2f}".format(sub_com_gan) 557 | print("sub_gain", sub_gain) 558 | print("sub_com_gan", sub_com_gan) 559 | print("sub_id", sub_id) 560 | print("sub_type", sub_type) 561 | sub_input = task_df.loc[task_df['Id'] == sub_id, 'Input'].item() 562 | input_node_list = sub_input.split(", ") 563 | input_node_list = [node for node in input_node_list if "bias" not in node] 564 | sub_input = ", ".join(input_node_list) 565 | 566 | sub_output = task_df.loc[task_df['Id'] == sub_id, 'Output'].item() 567 | output_node_list = sub_output.split(", ") 568 | output_node_list = [node for node in output_node_list if "outn" not in node and "outp" not in node] 569 | sub_output = ",".join(output_node_list) 570 | 571 | new_row = {'Id': sub_id, "Circuit Type": sub_type, "Gain/Differential-mode gain (dB)": sub_gain, "Common-mode gain (dB)": sub_com_gan, "Input": sub_input, "Output": sub_output} 572 | subcircuits_df = pd.concat([subcircuits_df, pd.DataFrame([new_row])], ignore_index=True) 573 | print("subcircuits_df") 574 | print(subcircuits_df) 575 | subcircuits_info = subcircuits_df.to_csv(sep='\t', index=False) 576 | return subcircuits_info 577 | 578 | 579 | def get_note_info(subcircuits, 580 | lib_data_path = "lib_info.tsv", task_data_path = "problem_set.tsv"): 581 | lib_df = pd.read_csv(lib_data_path, delimiter='\t') 582 | task_df = pd.read_csv(task_data_path, delimiter='\t') 583 | note_info = "" 584 | 585 | for sub_id in subcircuits: 586 | sub_type = task_df.loc[task_df['Id'] == sub_id, 'Type'].item() 587 | sub_name = task_df.loc[task_df['Id'] == sub_id, 'Submodule Name'].item() 588 | sub_bias_voltage = lib_df.loc[lib_df['Id'] == sub_id, 'Voltage Bias'].item() 589 | if "Amplifier" not in sub_type and "Opamp" not in sub_type: 590 | continue 591 | sub_phase = lib_df.loc[lib_df['Id'] == sub_id, 'Vin(n) Phase'].item() 592 | if sub_type == "Amplifier": 593 | if sub_phase == "inverting": 594 | other_sub_phase = "non-inverting" 595 | else: 596 | other_sub_phase = "inverting" 597 | note_info += f"The Vin of {sub_name} is the {sub_phase} input.\n" 598 | note_info += f"There is NO in {other_sub_phase} input in {sub_name}.\n" 599 | note_info += f"The DC operating voltage for Vin is {sub_bias_voltage} V.\n" 600 | elif sub_type == "Opamp": 601 | if sub_phase == "inverting": 602 | other_sub_phase = "non-inverting" 603 | else: 604 | other_sub_phase = "inverting" 605 | note_info += f"The Vinn of {sub_name} is the {sub_phase} input.\n" 606 | note_info += f"The Vinp of {sub_name} is the {other_sub_phase} input.\n" 607 | note_info += f"The DC operating voltage for Vinn/Vinp is {sub_bias_voltage} V.\n" 608 | print("note_info", note_info) 609 | return note_info, sub_bias_voltage 610 | 611 | 612 | def get_call_info(subcircuits, 613 | lib_data_path = "lib_info.tsv", task_data_path = "problem_set.tsv"): 614 | template = '''```python 615 | from p[ID]_lib import * 616 | # declare the subcircuit 617 | circuit.subcircuit([SUBMODULE_NAME]()) 618 | # create a subcircuit instance 619 | circuit.X('1', '[SUBMODULE_NAME]', [INPUT_OUTPUT]) 620 | ``` 621 | ''' 622 | lib_df = pd.read_csv(lib_data_path, delimiter='\t') 623 | task_df = pd.read_csv(task_data_path, delimiter='\t') 624 | call_info = "" 625 | for it, subcircuit in enumerate(subcircuits): 626 | sub_id = subcircuit 627 | sub_name = task_df.loc[task_df['Id'] == sub_id, 'Submodule Name'].item() 628 | input_nodes = task_df.loc[task_df['Id'] == sub_id, 'Input'].item() 629 | output_nodes = task_df.loc[task_df['Id'] == sub_id, 'Output'].item() 630 | sub_info = template.replace('[SUBMODULE_NAME]', sub_name) 631 | input_node_list = input_nodes.split(", ") 632 | input_node_list = [node for node in input_node_list if "bias" not in node] 633 | 634 | # for input_node in input_nodes.split(", "): 635 | input_info = ", ".join([f"'{input_node}'" for input_node in input_node_list]) 636 | output_node_list = output_nodes.split(", ") 637 | output_node_list = [node for node in output_node_list if "outn" not in node and "outp" not in node] 638 | output_info = ", ".join([f"'{output_node}'" for output_node in output_node_list]) 639 | if input_info != "" and output_info != "": 640 | input_output = f"{input_info}, {output_info}" 641 | elif input_info == "": 642 | input_output = f"{output_info}" 643 | else: 644 | input_output = f"{input_info}" 645 | sub_info = sub_info.replace('[INPUT_OUTPUT]', input_output) 646 | sub_info = sub_info.replace('[ID]', str(sub_id)) 647 | call_info += sub_info 648 | return call_info 649 | 650 | global generator 651 | generator = None 652 | 653 | 654 | def write_pyspice_code(sp_code_path, code_path, op_path): 655 | sp_code = open(sp_code_path, 'r') 656 | code = open(code_path, 'w') 657 | code.write(import_template) 658 | code.write("circuit = Circuit('circuit')\n") 659 | for line in sp_code.readlines(): 660 | if line.startswith(".model"): 661 | parts = line.split() 662 | if len(parts) < 6: 663 | continue 664 | code.write(f"circuit.model('{parts[1]}', '{parts[2]}', {parts[3]}, {parts[4]}, {parts[5]})\n") 665 | elif line.startswith('R') or line.startswith('C') or line.startswith('V') or line.startswith('I'): 666 | type_name = line[0] 667 | parts = line.split() 668 | if len(parts) < 4: 669 | continue 670 | name = parts[0][1:] 671 | n1 = parts[1] 672 | n2 = parts[2] 673 | value = parts[3] 674 | code.write(f"circuit.{type_name}('{name}', '{n1}', '{n2}', '{value}')\n") 675 | elif line.startswith('M'): 676 | parts = line.split() 677 | if len(parts) < 8: 678 | continue 679 | name = parts[0][1:] 680 | drain = parts[1] 681 | gate = parts[2] 682 | source = parts[3] 683 | bulk = parts[4] 684 | model = parts[5] 685 | w = parts[6] 686 | l = parts[7] 687 | code.write(f"circuit.MOSFET('{name}', '{drain}', '{gate}', '{source}', '{bulk}', model='{model}', {w}, {l})\n") 688 | code.write("simulator = circuit.simulator()\n") 689 | code.write(pyspice_template.replace("[OP_PATH]", op_path)) 690 | code.close() 691 | 692 | 693 | def start_tmux_session(session_name, command): 694 | subprocess.run(['tmux', 'new-session', '-d', '-s', session_name]) 695 | subprocess.run(['tmux', 'send-keys', '-t', session_name, command, 'C-m']) 696 | print(f"tmux session '{session_name}' started, running command: {command}") 697 | 698 | 699 | def kill_tmux_session(session_name): 700 | try: 701 | subprocess.run(['tmux', 'kill-session', '-t', session_name], check=True) 702 | print(f"tmux session '{session_name}' has been killed successfully.") 703 | except subprocess.CalledProcessError: 704 | print(f"Failed to kill tmux session '{session_name}'. Session might not exist.") 705 | 706 | 707 | def work(task, input, output, task_id, it, background, task_type, flog, 708 | money_quota = 100, subcircuits = None): 709 | 710 | global generator 711 | 712 | total_tokens = 0 713 | total_prompt_tokens = 0 714 | total_completion_tokens = 0 715 | 716 | if task_type not in complex_task_type or args.skill == False: 717 | if "llama" in args.model: 718 | fopen = open('prompt_template.md','r') 719 | elif args.ngspice: 720 | fopen = open('prompt_template_ngspice.md','r') 721 | elif any(model in args.model for model in opensource_models): 722 | fopen = open('prompt_template.md','r') 723 | else: 724 | fopen = open('prompt_template.md','r') 725 | 726 | if args.no_prompt: 727 | fopen = open('prompt_template_wo_prompt.md', 'r') 728 | elif args.no_context: 729 | fopen = open('prompt_template_wo_context.md', 'r') 730 | elif args.no_chain: 731 | fopen = open('prompt_template_wo_chain_of_thought.md', 'r') 732 | prompt = fopen.read() 733 | 734 | prompt = prompt.replace('[TASK]', task) 735 | prompt = prompt.replace('[INPUT]', input) 736 | prompt = prompt.replace('[OUTPUT]', output) 737 | # Make the subcircuits by GPT possible 738 | if task_type in complex_task_type: 739 | prompt = prompt.replace('6. Avoid using subcircuits.', '').replace('7.', '6.').replace('8.', '7.') 740 | bias_voltage = 2.5 741 | 742 | else: 743 | fopen = open('prompt_template_complex.md','r') 744 | prompt = fopen.read() 745 | 746 | prompt = prompt.replace('[TASK]', task) 747 | prompt = prompt.replace('[INPUT]', input) 748 | prompt = prompt.replace('[OUTPUT]', output) 749 | subcircuits_info = get_subcircuits_info(subcircuits) 750 | note_info, bias_voltage = get_note_info(subcircuits) 751 | if task_type == "Oscillator": 752 | note_info += bias_usage 753 | call_info = get_call_info(subcircuits) 754 | prompt = prompt.replace('[SUBCIRCUITS_INFO]', subcircuits_info).replace('[NOTE_INFO]', note_info).replace('[CALL_INFO]', call_info) 755 | fwrite_prompt = open(f'prompt_template_complex_with_sub_{task_type}.md', 'w') 756 | fwrite_prompt.write(prompt) 757 | fwrite_prompt.close() 758 | 759 | # Background is not used now 760 | if background is not None: 761 | prompt += "\n\nHint Background: \n" + background + "\n## Answer \n" 762 | fopen.close() 763 | 764 | fopen_exe_error = open('execution_error.md', 'r') 765 | prompt_exe_error = fopen_exe_error.read() 766 | fopen_exe_error.close() 767 | 768 | fopen_sim_error = open('simulation_error.md', 'r') 769 | prompt_sim_error = fopen_sim_error.read() 770 | fopen_sim_error.close() 771 | 772 | messages = [ 773 | {"role": "system", "content": "You are an analog integrated circuits expert."}, 774 | {"role": "user", "content": prompt} 775 | ] 776 | retry = True 777 | 778 | if money_quota < 0: 779 | flog.write("Money quota is used up. Exceed quota: {}\n".format(money_quota)) 780 | return money_quota 781 | 782 | while retry: 783 | if any(model in args.model for model in opensource_models): 784 | print(f"start {args.model} completion") 785 | signal.signal(signal.SIGALRM, signal_handler) 786 | signal.alarm(360) 787 | try: 788 | completion = ollama.chat( 789 | model=args.model, 790 | messages=messages, 791 | options={ 792 | "temperature": args.temperature, 793 | "top_p": 1.0, 794 | # "num_predict": 16192, 795 | }) 796 | print(f"{args.model} completion finish") 797 | signal.alarm(0) 798 | break 799 | except TimeoutException as e: 800 | print(e) 801 | print("timeout") 802 | signal.alarm(0) 803 | print("restart ollama") 804 | kill_tmux_session("ollama") 805 | result = subprocess.run(['ollama', 'restart'], capture_output=True, text=True) 806 | start_tmux_session("ollama", "ollama serve") 807 | time.sleep(120) 808 | else: 809 | try: 810 | completion = client.chat.completions.create( 811 | model = args.model, 812 | messages = messages, 813 | temperature = args.temperature 814 | ) 815 | break 816 | except openai.APIStatusError as e: 817 | print("Encountered an APIStatusError. Details:") 818 | print(e) 819 | print("sleep 30 seconds") 820 | time.sleep(30) 821 | 822 | 823 | if "gpt" in args.model: 824 | total_tokens += completion.usage.total_tokens 825 | total_prompt_tokens += completion.usage.prompt_tokens 826 | total_completion_tokens += completion.usage.completion_tokens 827 | 828 | if "ft:gpt-3.5" in args.model: 829 | money_quota -= (completion.usage.prompt_tokens / 1e6 * 3) + (completion.usage.completion_tokens / 1e6 * 6) 830 | elif "gpt-3" in args.model: 831 | money_quota -= (completion.usage.prompt_tokens / 1e6 * 0.5) + (completion.usage.completion_tokens / 1e6 * 1.5) 832 | elif "gpt-4" in args.model: 833 | money_quota -= (completion.usage.prompt_tokens / 1e6 * 10) + (completion.usage.completion_tokens / 1e6 * 30) 834 | 835 | if "gpt" in args.model or "deepseek-chat" in args.model: 836 | answer = completion.choices[0].message.content 837 | else: 838 | answer = completion['message']['content'] 839 | 840 | if "ft:gpt-3.5" in args.model: 841 | if "a:9HyyBpNI" in args.model: 842 | model_dir = "gpt3p5-ft-A" 843 | elif "b:9Hzb5l4S" in args.model: 844 | model_dir = "gpt3p5-ft-B" 845 | elif "c:9I0X557K" in args.model: 846 | model_dir = "gpt3p5-ft-C" 847 | else: 848 | assert False 849 | elif "gpt-3" in args.model: 850 | model_dir = 'gpt3p5' 851 | elif "gpt-4" in args.model: 852 | model_dir = 'gpt4' 853 | elif "deepseek-chat" in args.model: 854 | model_dir = "deepseek2" 855 | elif any(model in args.model for model in opensource_models): 856 | model_dir = str(args.model).replace(":", "-") 857 | else: 858 | model_dir = 'unknown' 859 | 860 | 861 | if "ft-A" in model_dir: 862 | assert task_id in [0, 3, 6, 9, 14, 10, 11] 863 | elif "ft-B" in model_dir: 864 | assert task_id in [1, 4, 7, 12, 10, 11] 865 | elif "ft-C" in model_dir: 866 | assert task_id in [2, 5, 8, 13, 10, 11] 867 | if args.ngspice: 868 | model_dir += '_ngspice' 869 | 870 | if args.no_prompt: 871 | model_dir += "_no_prompt" 872 | elif args.no_context: 873 | model_dir += "_no_context" 874 | elif args.no_chain: 875 | model_dir += "_no_chain" 876 | 877 | if args.num_of_retry > 3: 878 | model_dir += "_retry_{}".format(args.num_of_retry) 879 | 880 | if task_type in complex_task_type and not args.skill: 881 | model_dir += "_no_skill" 882 | 883 | if not os.path.exists(model_dir): 884 | try: 885 | os.mkdir(model_dir) 886 | except: 887 | pass 888 | if not os.path.exists('{}/p{}'.format(model_dir, task_id)): 889 | try: 890 | os.mkdir('{}/p{}'.format(model_dir, task_id)) 891 | except: 892 | pass 893 | 894 | empty_code_error, raw_code = extract_code(answer) 895 | operating_point_path = "{}/p{}/{}/p{}_{}_{}_op.txt".format(model_dir, task_id, it, task_id, it, 0) 896 | if not args.ngspice and "simulator = circuit.simulator()" not in raw_code: 897 | raw_code += "\nsimulator = circuit.simulator()\n" 898 | if args.ngspice and ".end" in raw_code: 899 | raw_code = raw_code.replace(".end", "") 900 | 901 | code_id = 0 902 | if not args.ngspice: 903 | if task_type not in complex_task_type: 904 | code = raw_code + pyspice_template.replace("[OP_PATH]", operating_point_path) 905 | else: 906 | pyspice_template_complex = open(f"problem_check/{task_type}.py", "r").read() 907 | figure_path = "{}/p{}/{}/p{}_{}_{}_figure".format(model_dir, task_id, it, task_id, it, code_id) 908 | if task_type == "Oscillator": 909 | code = raw_code + pyspice_template_complex.replace("[FIGURE_PATH]", figure_path) 910 | else: 911 | if args.skill: 912 | code = raw_code + pyspice_template_complex.replace("[FIGURE_PATH]", figure_path).replace('[BIAS_VOLTAGE]', str(bias_voltage)) 913 | else: 914 | code = raw_code + pyspice_template_complex.replace("[FIGURE_PATH]", figure_path).replace('[BIAS_VOLTAGE]', "float(circuit.element(vin_name).dc_value)") 915 | if "import math" not in code: 916 | code = "import math\n" + code 917 | else: 918 | code = raw_code 919 | 920 | fwrite_input = open('{}/p{}/p{}_{}_input.txt'.format(model_dir, task_id, task_id, it), 'w') 921 | fwrite_input.write(prompt) 922 | fwrite_input.flush() 923 | fwrite_output = open('{}/p{}/p{}_{}_output.txt'.format(model_dir, task_id, task_id, it), 'w') 924 | fwrite_output.write(answer) 925 | fwrite_output.flush() 926 | 927 | # delete files 928 | existing_code_files = os.listdir("{}/p{}".format(model_dir, task_id)) 929 | for existing_code_file in existing_code_files: 930 | if existing_code_file.endswith(".sp"): 931 | os.remove("{}/p{}/{}".format(model_dir, task_id, existing_code_file)) 932 | print("remove file: ", existing_code_file) 933 | if existing_code_file.endswith("_op.txt"): 934 | os.remove("{}/p{}/{}".format(model_dir, task_id, existing_code_file)) 935 | print("remove file: ", existing_code_file) 936 | 937 | if os.path.exists("{}/p{}/{}".format(model_dir, task_id, it)): 938 | existing_code_files = os.listdir("{}/p{}/{}".format(model_dir, task_id, it)) 939 | for existing_code_file in existing_code_files: 940 | if os.path.isfile("{}/p{}/{}/{}".format(model_dir, task_id, it, existing_code_file)): 941 | try: 942 | os.remove("{}/p{}/{}/{}".format(model_dir, task_id, it, existing_code_file)) 943 | except: 944 | pass 945 | print("remove file: ", existing_code_file) 946 | 947 | 948 | while code_id < args.num_of_retry: 949 | messages.append({"role": "assistant", "content": answer}) 950 | 951 | if not os.path.exists("{}/p{}/{}".format(model_dir, task_id, it)): 952 | # try: 953 | os.mkdir("{}/p{}/{}".format(model_dir, task_id, it)) 954 | # except: 955 | # pass 956 | 957 | code_path = '{}/p{}/{}/p{}_{}_{}.py'.format(model_dir, task_id, it, task_id, it, code_id) 958 | if args.ngspice: 959 | code_path = code_path.replace(".py", ".sp") 960 | fwrite_code = open(code_path, 'w') 961 | fwrite_code.write(code) 962 | fwrite_code.close() 963 | 964 | if args.ngspice: 965 | sp_code_path = code_path 966 | code_path = code_path.replace(".sp", ".py") 967 | write_pyspice_code(sp_code_path, code_path, operating_point_path) 968 | answer_code = open(code_path, 'r').read() 969 | else: 970 | answer_code = code 971 | 972 | if task_type in complex_task_type and args.skill == True: 973 | for subcircuit in subcircuits: 974 | shutil.copy(f"subcircuit_lib/p{subcircuit}_lib.py", "/".join(code_path.split("/")[:-1])) 975 | execution_error, simulation_error, execution_error_info, floating_node = run_code(code_path) 976 | print("execution_error = {}, simulation_error = {}".format(execution_error, simulation_error)) 977 | 978 | 979 | dc_sweep_error = 0 980 | dc_sweep_success = 0 981 | 982 | if execution_error == 0 and simulation_error == 0: 983 | if task_type not in complex_task_type: 984 | _, code_netlist = None, answer_code 985 | code_netlist += output_netlist_template 986 | code_netlist_path = "{}/p{}/{}/p{}_{}_{}_netlist_gen.py".format(model_dir, task_id, it, task_id, it, code_id) 987 | fwrite_code_netlist = open(code_netlist_path, 'w') 988 | fwrite_code_netlist.write(code_netlist) 989 | fwrite_code_netlist.close() 990 | 991 | netlist_path = "{}/p{}/{}/p{}_{}_{}_netlist.sp".format(model_dir, task_id, it, task_id, it, code_id) 992 | result = subprocess.run(["python", "-u", code_netlist_path], check=True, text=True, 993 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 994 | netlist_file_path = "{}/p{}/{}/p{}_{}_{}_netlist.sp".format(model_dir, task_id, it, task_id, it, code_id) 995 | fwrite_netlist = open(netlist_file_path, 'w') 996 | fwrite_netlist.write("\n".join(result.stdout.split("\n")[1:])) 997 | fwrite_netlist.close() 998 | 999 | ## special for Opamp: dc sweep 1000 | 1001 | if "Opamp" in task_type or "Amplifier" in task_type: 1002 | vinn_name = "in" 1003 | vinp_name = "inp" 1004 | netlist_content = open(netlist_file_path, 'r').read() 1005 | vinn_name, vinp_name = get_vin_name(netlist_content, task_type) 1006 | dc_sweep_code_path = '{}/p{}/{}/p{}_{}_{}_dc_sweep.py'.format(model_dir, task_id, it, task_id, it, code_id) 1007 | dc_file_path = '{}/p{}/{}/p{}_{}_{}_dc.txt'.format(model_dir, task_id, it, task_id, it, code_id) 1008 | _, dc_sweep_code = None, answer_code 1009 | if "simulator = circuit.simulator()" not in dc_sweep_code: 1010 | dc_sweep_code += "\nsimulator = circuit.simulator()\n" 1011 | if task_type == "Opamp": 1012 | dc_sweep_code = connect_vinn_vinp(dc_sweep_code, vinn_name, vinp_name) 1013 | dc_sweep_code += dc_sweep_template.replace("[IN_NAME]", vinn_name).replace("[DC_PATH]", dc_file_path) 1014 | fwrite_dc_sweep_code = open(dc_sweep_code_path, 'w') 1015 | fwrite_dc_sweep_code.write(dc_sweep_code) 1016 | fwrite_dc_sweep_code.close() 1017 | try: 1018 | subprocess.run(["python", "-u", dc_sweep_code_path], check=True, text=True, 1019 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 1020 | dc_sweep_error, best_voltage = get_best_voltage(dc_file_path) 1021 | print("dc_sweep_error", dc_sweep_error) 1022 | print("best_voltage", best_voltage) 1023 | print("vinn_name", vinn_name) 1024 | print("vinp_name", vinp_name) 1025 | assert dc_sweep_error == 0 1026 | os.rename(code_path, code_path + ".bak") 1027 | print("code_path", code_path) 1028 | _, raw_code = None, answer_code 1029 | if "simulator = circuit.simulator()" not in raw_code: 1030 | raw_code += "\nsimulator = circuit.simulator()\n" 1031 | new_code = replace_voltage(raw_code, best_voltage, vinn_name, vinp_name) 1032 | # write new op analysis code 1033 | with open(f"{code_path}", "w") as f: 1034 | f.write(new_code) 1035 | # rerun the op test 1036 | execution_error_1, simulation_error_1, execution_error_info_1, floating_node_1 = run_code(code_path) 1037 | # make sure the op test passed 1038 | assert execution_error_1 == 0 1039 | assert simulation_error_1 == 0 1040 | # All dc sweep passed 1041 | # generate a new netlist with the best voltage, replace _gen.py and .sp 1042 | new_code_netlist = new_code + output_netlist_template 1043 | code_netlist_path = "{}/p{}/{}/p{}_{}_{}_netlist_gen.py".format(model_dir, task_id, it, task_id, it, code_id) 1044 | fwrite_code_netlist = open(code_netlist_path, 'w') 1045 | fwrite_code_netlist.write(new_code_netlist) 1046 | fwrite_code_netlist.close() 1047 | netlist_path = "{}/p{}/{}/p{}_{}_{}_netlist.sp".format(model_dir, task_id, it, task_id, it, code_id) 1048 | result = subprocess.run(["python", "-u", code_netlist_path], check=True, text=True, 1049 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 1050 | netlist_file_path = "{}/p{}/{}/p{}_{}_{}_netlist.sp".format(model_dir, task_id, it, task_id, it, code_id) 1051 | fwrite_netlist = open(netlist_file_path, 'w') 1052 | fwrite_netlist.write("\n".join(result.stdout.split("\n")[1:])) 1053 | fwrite_netlist.close() 1054 | dc_sweep_success = 1 1055 | except: 1056 | # recover the raw file 1057 | if os.path.exists(code_path + ".bak"): 1058 | if os.path.exists(code_path): 1059 | os.remove(code_path) 1060 | os.rename(code_path + ".bak", code_path) 1061 | # dc sweep finish 1062 | 1063 | warning, warning_message = check_netlist(netlist_path, operating_point_path, input, output, task_id, task_type) 1064 | if warning == 0: 1065 | func_error, func_error_message = check_function(task_id, code_path, task_type) 1066 | func_error_message = func_error_message.replace("Unsupported Ngspice version 38", "") 1067 | func_error_message = func_error_message.replace("Unsupported Ngspice version 36", "") 1068 | if func_error ==0: 1069 | # completion 1070 | print("CODE_PATH = {}".format(code_path)) 1071 | os.rename(code_path, code_path.rsplit(".", 1)[0] + "_success.py") 1072 | if any(model in args.model for model in opensource_models): 1073 | flog.write("task:{}\tit:{}\tcode_id:{}\tsuccess.\tcompletion_tokens:{}" 1074 | "\tprompt_tokens:{}\ttotal_tokens:{}\n".format(task_id, it, code_id, 1075 | completion.usage.completion_tokens, 1076 | completion.usage.prompt_tokens, 1077 | completion.usage.total_tokens)) 1078 | flog.write("total_tokens\t{}\ttotal_prompt_tokens\t{}\ttotal_completion_tokens\t{}\n".format(total_tokens, total_prompt_tokens, total_completion_tokens)) 1079 | ftmp_write = open(f'{model_dir}/p{task_id}/{it}/token_info_{total_tokens}_{total_prompt_tokens}_{total_completion_tokens}_{code_id}', 'w') 1080 | ftmp_write.close() 1081 | else: 1082 | flog.write("task:{}\tit:{}\tcode_id:{}\tsuccess.\n".format(task_id, it, code_id)) 1083 | flog.write("money_quota\t{:.10f}\n".format(money_quota)) 1084 | flog.flush() 1085 | break 1086 | else: 1087 | os.rename(code_path, code_path.rsplit(".", 1)[0]+"_success.py") 1088 | if "llama" not in args.model and "wizard" not in args.model and "deepseek" not in args.model and "mistral" not in args.model and "qwencode" not in args.model and "mixtral" not in args.model: 1089 | flog.write("task:{}\tit:{}\tcode_id:{}\tsuccess.\tcompletion_tokens:{}" 1090 | "\tprompt_tokens:{}\ttotal_tokens:{}\n".format(task_id, it, code_id, 1091 | completion.usage.completion_tokens, 1092 | completion.usage.prompt_tokens, 1093 | completion.usage.total_tokens)) 1094 | flog.write("total_tokens\t{}\ttotal_prompt_tokens\t{}\ttotal_completion_tokens\t{}\n".format(total_tokens, total_prompt_tokens, total_completion_tokens)) 1095 | ftmp_write = open(f'{model_dir}/p{task_id}/{it}/token_info_{total_tokens}_{total_prompt_tokens}_{total_completion_tokens}_{code_id}', 'w') 1096 | ftmp_write.close() 1097 | else: 1098 | flog.write("task:{}\tit:{}\tcode_id:{}\tsuccess.\n".format(task_id, it, code_id)) 1099 | flog.write("money_quota\t{:.10f}\n".format(money_quota)) 1100 | flog.flush() 1101 | break 1102 | 1103 | # Ignore the compatible error 1104 | execution_error_info = execution_error_info.replace("Unsupported Ngspice version 38", "") 1105 | execution_error_info = execution_error_info.replace("Unsupported Ngspice version 36", "") 1106 | 1107 | if dc_sweep_error == 1: 1108 | new_prompt = "According to dc sweep analysis, changing the input voltage does not change the output voltage. Please check the netlist and rewrite the complete code.\n" 1109 | new_prompt += "Reference operating point:\n" 1110 | op_content = open(operating_point_path, 'r').read() 1111 | new_prompt += op_content 1112 | 1113 | flog.write("task:{}\tit:{}\tcode_id\t{}\tdc sweep error\n".format(task_id, it, code_id)) 1114 | flog.flush() 1115 | ftmp = open("{}/p{}/{}/dc_sweep_error_{}".format(model_dir, task_id, it, code_id), "w") 1116 | ftmp.close() 1117 | else: 1118 | if dc_sweep_success == 1: 1119 | new_prompt = f"According to dc sweep analysis, the best input voltage is {best_voltage}. Please use this voltage.\n" 1120 | else: 1121 | new_prompt = "" 1122 | if empty_code_error == 1: 1123 | new_prompt += "There is no complete code in your reply. Please generate a complete code." 1124 | flog.write("task:{}\tit:{}\tcode_id\t{}\tempty code error\n".format(task_id, it, code_id)) 1125 | flog.flush() 1126 | ftmp = open("{}/p{}/{}/empty_error_{}".format(model_dir, task_id, it, code_id), "w") 1127 | ftmp.close() 1128 | elif simulation_error == 1: 1129 | new_prompt += prompt_sim_error.replace("[NODE]", floating_node) 1130 | flog.write("task:{}\tit:{}\tcode_id:{}\tsimulation error\n".format(task_id, it, code_id)) 1131 | flog.flush() 1132 | ftmp = open("{}/p{}/{}/simulation_error_{}".format(model_dir, task_id, it, code_id), "w") 1133 | ftmp.close() 1134 | elif execution_error == 1: 1135 | new_prompt += prompt_exe_error.replace("[ERROR]", execution_error_info) 1136 | flog.write("task:{}\tit:{}\tcode_id:{}\texecution error\n".format(task_id, it, code_id)) 1137 | flog.flush() 1138 | ftmp = open("{}/p{}/{}/execution_error_{}".format(model_dir, task_id, it, code_id), "w") 1139 | ftmp.close() 1140 | elif warning == 1: 1141 | new_prompt += warning_message 1142 | flog.write("task:{}\tit:{}\tcode_id:{}\tmosfet connection error\n".format(task_id, it, code_id)) 1143 | flog.flush() 1144 | ftmp = open("{}/p{}/{}/mosfet_connection_error_{}".format(model_dir, task_id, it, code_id), "w") 1145 | ftmp.close() 1146 | elif func_error == 1: 1147 | new_prompt += func_error_message 1148 | new_prompt += "\nPlease rewrite the corrected complete code." 1149 | flog.write("task:{}\tit:{}\tcode_id:{}\tfunction error\n".format(task_id, it, code_id)) 1150 | flog.flush() 1151 | ftmp = open("{}/p{}/{}/function_error_{}".format(model_dir, task_id, it, code_id), "w") 1152 | ftmp.close() 1153 | else: 1154 | assert False 1155 | flog.write("total_tokens\t{}\ttotal_prompt_tokens\t{}\ttotal_completion_tokens\t{}\n".format(total_tokens, total_prompt_tokens, total_completion_tokens)) 1156 | flog.write("money_quota\t{:.10f}\n".format(money_quota)) 1157 | ftmp_write = open(f'{model_dir}/p{task_id}/{it}/token_info_{total_tokens}_{total_prompt_tokens}_{total_completion_tokens}_{code_id}', 'w') 1158 | ftmp_write.close() 1159 | flog.flush() 1160 | code_id += 1 1161 | if code_id >= args.num_of_retry: 1162 | break 1163 | messages.append({"role": "user", "content": new_prompt}) 1164 | 1165 | if money_quota < 0: 1166 | flog.write("Money quota is used up. Exceed quota: {}\n".format(money_quota)) 1167 | return 1168 | 1169 | retry = True 1170 | while retry: 1171 | try: 1172 | if any(model in args.model for model in opensource_models): 1173 | print(f"start {args.model} completion") 1174 | signal.signal(signal.SIGALRM, signal_handler) 1175 | signal.alarm(360) 1176 | try: 1177 | completion = ollama.chat( 1178 | model=args.model, 1179 | messages=messages, 1180 | options={ 1181 | "temperature": args.temperature, 1182 | "top_p": 1.0, 1183 | }) 1184 | print(f"{args.model} completion finish") 1185 | signal.alarm(0) 1186 | break 1187 | except TimeoutException as e: 1188 | print(e) 1189 | print("timeout") 1190 | signal.alarm(0) 1191 | print("restart ollama") 1192 | kill_tmux_session("ollama") 1193 | result = subprocess.run(['ollama', 'restart'], capture_output=True, text=True) 1194 | start_tmux_session("ollama", "ollama serve") 1195 | time.sleep(120) 1196 | else: 1197 | completion = client.chat.completions.create( 1198 | model = args.model, 1199 | messages = messages, 1200 | temperature = args.temperature 1201 | ) 1202 | break 1203 | except openai.APIStatusError as e: 1204 | print("Encountered an APIStatusError. Details:") 1205 | print(e) 1206 | print("sleep 30 seconds") 1207 | time.sleep(30) 1208 | 1209 | if "gpt" in args.model: 1210 | total_tokens += completion.usage.total_tokens 1211 | total_prompt_tokens += completion.usage.prompt_tokens 1212 | total_completion_tokens += completion.usage.completion_tokens 1213 | 1214 | if "ft:gpt-3.5" in args.model: 1215 | money_quota -= (completion.usage.prompt_tokens / 1e6 * 3) + (completion.usage.completion_tokens / 1e6 * 6) 1216 | elif "gpt-3" in args.model: 1217 | money_quota -= (completion.usage.prompt_tokens / 1e6 * 0.5) + (completion.usage.completion_tokens / 1e6 * 1.5) 1218 | elif "gpt-4" in args.model: 1219 | money_quota -= (completion.usage.prompt_tokens / 1e6 * 10) + (completion.usage.completion_tokens / 1e6 * 30) 1220 | 1221 | fwrite_input.write("\n----------\n") 1222 | fwrite_input.write(new_prompt) 1223 | fwrite_input.flush() 1224 | 1225 | if "gpt" in args.model or "deepseek-chat" in args.model: 1226 | answer = completion.choices[0].message.content 1227 | else: 1228 | answer = completion['message']['content'] 1229 | 1230 | fwrite_output.write("\n----------\n") 1231 | fwrite_output.write(answer) 1232 | 1233 | empty_code_error, code = extract_code(answer) 1234 | 1235 | 1236 | operating_point_path = "{}/p{}/{}/p{}_{}_{}_op.txt".format(model_dir, task_id, it, task_id, it, code_id) 1237 | if "simulator = circuit.simulator()" not in code: 1238 | code += "\nsimulator = circuit.simulator()\n" 1239 | if task_type not in complex_task_type: 1240 | code += pyspice_template.replace("[OP_PATH]", operating_point_path) 1241 | else: 1242 | figure_path = "{}/p{}/{}/p{}_{}_{}_figure".format(model_dir, task_id, it, task_id, it, code_id) 1243 | if task_type == "Oscillator": 1244 | code += pyspice_template_complex.replace("[FIGURE_PATH]", figure_path) 1245 | else: 1246 | if args.skill: 1247 | code += pyspice_template_complex.replace("[FIGURE_PATH]", figure_path).replace('[BIAS_VOLTAGE]', str(bias_voltage)) 1248 | else: 1249 | code += pyspice_template_complex.replace("[FIGURE_PATH]", figure_path).replace('[BIAS_VOLTAGE]', "float(circuit.element(vin_name).dc_value)") 1250 | if "import math" not in code: 1251 | code = "import math\n" + code 1252 | 1253 | # save messages 1254 | fwrite = open('{}/p{}/{}/p{}_{}_messages.txt'.format(model_dir, task_id, it, task_id, it), 'w') 1255 | fwrite.write(str(messages)) 1256 | fwrite.close() 1257 | fwrite_input.close() 1258 | fwrite_output.close() 1259 | return money_quota 1260 | 1261 | 1262 | def get_retrieval(task, task_id): 1263 | prompt = open('retrieval_prompt.md', 'r').read() 1264 | prompt = prompt.replace('[TASK]', task) 1265 | messages = [ 1266 | {"role": "system", "content": "You are an analog integrated circuits expert."}, 1267 | {"role": "user", "content": prompt} 1268 | ] 1269 | if "gpt" in args.model and args.retrieval: 1270 | try: 1271 | completion = client.chat.completions.create( 1272 | model = args.model, 1273 | messages = messages, 1274 | temperature = args.temperature 1275 | ) 1276 | except openai.APIStatusError as e: 1277 | print("Encountered an APIStatusError. Details:") 1278 | print(e) 1279 | print("sleep 30 seconds") 1280 | time.sleep(30) 1281 | answer = completion.choices[0].message.content 1282 | print("answer", answer) 1283 | fretre_path = os.path.join(args.model.replace("-", ""), f"p{str(task_id)}", "retrieve.txt") 1284 | fretre = open(fretre_path, "w") 1285 | fretre.write(answer) 1286 | fretre.close() 1287 | regex = r".*?```.*?\n(.*?)```" 1288 | matches = re.finditer(regex, answer, re.DOTALL) 1289 | first_match = next(matches, None) 1290 | match_res = first_match.group(1) 1291 | print("match_res", match_res) 1292 | subcircuits = eval(match_res) 1293 | else: 1294 | # use default subcircuits 1295 | subcircuits = [11] 1296 | return subcircuits 1297 | 1298 | 1299 | 1300 | def main(): 1301 | data_path = 'problem_set.tsv' 1302 | df = pd.read_csv(data_path, delimiter='\t') 1303 | # print(df) 1304 | # set money cost to $2 1305 | remaining_money = 2 1306 | for index, row in df.iterrows(): 1307 | circuit_id = row['Id'] 1308 | if circuit_id != args.task_id: 1309 | continue 1310 | strftime = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime()) 1311 | if args.ngspice: 1312 | flog = open('{}_{}_{}_ngspice_log.txt'.format(strftime, args.model, circuit_id), 'w') 1313 | elif args.no_prompt: 1314 | flog = open('{}_{}_{}_no_prompt_log.txt'.format(strftime, args.model, circuit_id), 'w') 1315 | elif args.no_context: 1316 | flog = open('{}_{}_{}_no_context_log.txt'.format(strftime, args.model, circuit_id), 'w') 1317 | elif args.no_chain: 1318 | flog = open('{}_{}_{}_no_chain_log.txt'.format(strftime, args.model, circuit_id), 'w') 1319 | elif not args.skill and row['Type'] in complex_task_type: 1320 | flog = open('{}_{}_{}_log_no_skill.txt'.format(strftime, args.model, circuit_id), 'w') 1321 | else: 1322 | flog = open('{}_{}_{}_log.txt'.format(strftime, args.model, circuit_id), 'w') 1323 | for it in range(args.num_of_done, args.num_per_task): 1324 | flog.write("task: {}, it: {}\n".format(circuit_id, it)) 1325 | flog.flush() 1326 | if row['Type'] in complex_task_type: 1327 | subcircuits = get_retrieval(row['Circuit'], args.task_id) 1328 | else: 1329 | subcircuits = None 1330 | remaining_money = work(row['Circuit'], row['Input'].strip(), row['Output'].strip(), circuit_id, it, 1331 | None, row['Type'], flog, money_quota=remaining_money, 1332 | subcircuits = subcircuits) 1333 | if remaining_money < 0: 1334 | break 1335 | 1336 | if __name__ == "__main__": 1337 | main() 1338 | -------------------------------------------------------------------------------- /hkummlab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laiyao1/AnalogCoder/6225b4925fcced0d08d3bd9ee4010b8649144b31/hkummlab.png -------------------------------------------------------------------------------- /lib_info.tsv: -------------------------------------------------------------------------------- 1 | Id Type Av (dB) Com Av (dB) Vin(n) Phase Voltage Bias 2 | 0 Amplifier 17.025166965695615 NA inverting 1.21 3 | 1 CurrentMirror NA NA NA 0.0 4 | 2 Inverter NA NA inverting 1.0 5 | 3 Inverter NA NA inverting 0.0 6 | 4 Amplifier -1.1483743996327227 NA non-inverting 3.71 7 | 5 Amplifier 17.02516696691898 NA non-inverting 0.79 8 | 6 Amplifier 3.0102999372276096 NA inverting 1.97 9 | 7 Amplifier 24.082399647689847 NA inverting 0.82 10 | 8 Amplifier 75.94026898353593 NA -90 degree 1.0 11 | 9 Opamp 199.99956997487047 -40.12867535462482 inverting 2.49 12 | 10 CurrentMirror NA NA NA 0.0 13 | 11 Opamp 41.61900557997383 -135.66721120644925 inverting 1.79 14 | 12 Opamp 150.36128137602688 -58.71904858948874 inverting 1.65 15 | 13 Opamp 24.044031388496503 21.58664528028884 inverting 0.87 16 | 14 Amplifier 44.128945502128325 NA inverting 1.65 17 | -------------------------------------------------------------------------------- /problem_check/Adder.py: -------------------------------------------------------------------------------- 1 | bias_voltage = [BIAS_VOLTAGE] 2 | v1_amp = bias_voltage 3 | v2_amp = bias_voltage + 0.125 4 | 5 | vin1_name = "" 6 | for element in circuit.elements: 7 | if "vin1" in [str(pin.node).lower() for pin in element.pins] and element.name.lower().startswith("v"): 8 | vin1_name = element.name 9 | 10 | if not vin1_name == "": 11 | circuit.element(vin1_name).detach() 12 | circuit.V('in1', 'Vin1', circuit.gnd, v1_amp) 13 | vin1_name = "Vin1" 14 | else: 15 | circuit.V('in1', 'Vin1', circuit.gnd, v1_amp) 16 | vin1_name = "Vin1" 17 | 18 | 19 | vin2_name = "" 20 | for element in circuit.elements: 21 | if "vin2" in [str(pin.node).lower() for pin in element.pins] and element.name.lower().startswith("v"): 22 | vin2_name = element.name 23 | 24 | if not vin2_name == "": 25 | circuit.element(vin2_name).detach() 26 | circuit.V('in2', 'Vin2', circuit.gnd, v2_amp) 27 | vin2_name = "Vin2" 28 | else: 29 | circuit.V('in2', 'Vin2', circuit.gnd, v2_amp) 30 | vin2_name = "Vin2" 31 | 32 | 33 | for element in circuit.elements: 34 | if element.name.lower().startswith(vin1_name): 35 | circuit.element(element.name).dc_value = f"dc {v1_amp}" 36 | elif element.name.lower().startswith(vin2_name): 37 | circuit.element(element.name).dc_value = f"dc {v2_amp}" 38 | 39 | # print(str(circuit)) 40 | 41 | simulator = circuit.simulator() 42 | 43 | 44 | params = {vin1_name: slice(bias_voltage, bias_voltage + 0.5, 0.01)} 45 | # Run a DC analysis 46 | try: 47 | analysis = simulator.dc(**params) 48 | except: 49 | print("DC analysis failed.") 50 | sys.exit(2) 51 | 52 | import numpy as np 53 | out_voltage = np.array(analysis.Vout) 54 | in_voltage = np.array(analysis.Vin1) 55 | vin2_voltage = np.array(analysis.Vin2) 56 | 57 | 58 | import sys 59 | 60 | 61 | tolerance = 0.2 # 20% Tolerance 62 | for i, out_v in enumerate(out_voltage): 63 | in_v_1 = in_voltage[i] - bias_voltage 64 | in_v_2 = v2_amp - bias_voltage 65 | expected_vout = bias_voltage - (in_v_1 + in_v_2) 66 | actual_vout = out_v 67 | if not np.isclose(actual_vout, expected_vout, rtol=tolerance): 68 | print(f"The circuit does not function correctly as an adder.\n" 69 | f"Expected Vout: {expected_vout:.4f} V, Vin1 = {in_v_1+bias_voltage:.4f} V, Vin2 = {in_v_2+bias_voltage:.4f} V | Actual Vout: {actual_vout:.4f} V\n") 70 | sys.exit(2) 71 | 72 | 73 | print("The op-amp adder functions correctly.\n") 74 | sys.exit(0) -------------------------------------------------------------------------------- /problem_check/Amplifier.py: -------------------------------------------------------------------------------- 1 | simulator_id = circuit.simulator() 2 | mosfet_names = [] 3 | import PySpice.Spice.BasicElement 4 | for element in circuit.elements: 5 | if isinstance(element, PySpice.Spice.BasicElement.Mosfet): 6 | mosfet_names.append(element.name) 7 | 8 | mosfet_name_ids = [] 9 | for mosfet_name in mosfet_names: 10 | mosfet_name_ids.append(f"@{mosfet_name}[id]") 11 | 12 | simulator_id.save_internal_parameters(*mosfet_name_ids) 13 | analysis_id = simulator_id.operating_point() 14 | 15 | id_correct = 1 16 | for mosfet_name in mosfet_names: 17 | mosfet_id = float(analysis_id[f"@{mosfet_name}[id]"][0]) 18 | if mosfet_id < 1e-5: 19 | id_correct = 0 20 | print("The circuit does not function correctly. " 21 | "the current I_D for {} is 0. ".format(mosfet_name) 22 | .format(mosfet_name)) 23 | 24 | if id_correct == 0: 25 | print("Please fix the wrong operating point.\n") 26 | sys.exit(2) 27 | 28 | 29 | frequency = 100@u_Hz 30 | analysis = simulator.ac(start_frequency=frequency, stop_frequency=frequency*10, 31 | number_of_points=2, variation='dec') 32 | 33 | import numpy as np 34 | 35 | node = 'vout' 36 | output_voltage = analysis[node].as_ndarray()[0] 37 | gain = np.abs(output_voltage / (1e-6)) 38 | 39 | print(f"Voltage Gain (Av) at 100 Hz: {gain}") 40 | 41 | required_gain = 1e-5 42 | import sys 43 | if gain > required_gain: 44 | print("The circuit functions correctly at 100 Hz.\n") 45 | sys.exit(0) 46 | else: 47 | print("The circuit does not function correctly.\n" 48 | "the gain is less than 1e-5.\n" 49 | "Please fix the wrong operating point.\n") 50 | sys.exit(2) -------------------------------------------------------------------------------- /problem_check/CurrentMirror.py: -------------------------------------------------------------------------------- 1 | load_resistances = [100, 300, 500, 750, 1000] 2 | currents = [] 3 | 4 | import PySpice.Spice.BasicElement 5 | for element in circuit.elements: 6 | if isinstance(element, PySpice.Spice.BasicElement.Resistor): 7 | resistor_name = element.name 8 | node1, node2 = element.nodes 9 | break 10 | 11 | 12 | resistor = circuit[resistor_name] 13 | for r_load in load_resistances: 14 | resistor.resistance = r_load 15 | analysis = simulator.operating_point() 16 | if str(node2) == "0": 17 | current = float(analysis[str(node1)][0]) / r_load 18 | elif str(node1) == "0": 19 | current = - float(analysis[str(node2)][0]) / r_load 20 | else: 21 | current = - (float(analysis[str(node1)][0]) - float(analysis[str(node2)][0])) / r_load 22 | currents.append(current) 23 | 24 | for r_load, current in zip(load_resistances, currents): 25 | print(f"Load: {r_load}, Current: {current}") 26 | 27 | tolerance = 1e-6 28 | 29 | current_variations = [] 30 | for i in range(4): 31 | current_variations.append(abs(currents[i+1] - currents[i])) 32 | 33 | import sys 34 | if min(current_variations) < tolerance and min(currents) > 1e-5: 35 | pass 36 | # print("The circuit functions correctly as a constant current source within the given tolerance.") 37 | # sys.exit(0) 38 | else: 39 | print("The circuit does not function correctly as a current source.") 40 | sys.exit(2) 41 | 42 | iin_name = None 43 | for element in circuit.elements: 44 | if "ref" in element.name.lower(): # and element.name.lower().startswith("v"): 45 | iin_name = element.name 46 | 47 | # print("iin_name", iin_name) 48 | if iin_name is None: 49 | print("The circuit functions correctly as a current source within the given tolerance.") 50 | sys.exit(0) 51 | 52 | 53 | circuit.element(iin_name).dc_value = "0.00155" 54 | 55 | # print(str(circuit)) 56 | simulator = circuit.simulator() 57 | resistor.resistance = 500 58 | analysis = simulator.operating_point() 59 | if str(node2) == "0": 60 | current = float(analysis[str(node1)][0]) / r_load 61 | elif str(node1) == "0": 62 | current = - float(analysis[str(node2)][0]) / r_load 63 | else: 64 | current = - (float(analysis[str(node1)][0]) - float(analysis[str(node2)][0])) / r_load 65 | 66 | # print("current", current) 67 | # print("currents", currents) 68 | # print("abs(current - currents[2])", abs(current - currents[2])) 69 | if abs(current - currents[2]) < 1e-6: 70 | print("The circuit does not as a current source because it cannot replicate the Iref current.") 71 | sys.exit(2) 72 | else: 73 | print("The circuit functions correctly as a current source within the given tolerance.") 74 | sys.exit(0) 75 | -------------------------------------------------------------------------------- /problem_check/Differentiator.py: -------------------------------------------------------------------------------- 1 | vin_name = "" 2 | for element in circuit.elements: 3 | if "vin" in [str(pin.node).lower() for pin in element.pins] and element.name.lower().startswith("v"): 4 | vin_name = element.name 5 | 6 | bias_voltage = [BIAS_VOLTAGE] 7 | 8 | # Detach the previous Vin if it exists and attach a new triangular wave source 9 | if vin_name != "": 10 | circuit.element(vin_name).detach() 11 | circuit.V('tri', 'Vin', circuit.gnd, f"dc {bias_voltage} PULSE({bias_voltage-0.5} {bias_voltage+0.5} 0 50m 50m 1n 100m)") 12 | else: 13 | circuit.V('in', 'Vin', circuit.gnd, f"dc {bias_voltage} PULSE({bias_voltage-0.5} {bias_voltage+0.5} 0 50m 50m 1n 100m)") 14 | 15 | # Adjust R1 resistance if needed 16 | for element in circuit.elements: 17 | if element.name.lower().startswith("rf") or element.name.lower().startswith("rrf") or element.name.lower().startswith("r1"): 18 | r_name = element.name 19 | circuit.element(r_name).resistance = "10k" 20 | 21 | # Adjust C1 capacitance if needed 22 | for element in circuit.elements: 23 | if element.name.lower().startswith("c1") or element.name.lower().startswith("cc1"): 24 | c_name = element.name 25 | circuit.element(c_name).capacitance = "3u" 26 | 27 | # Initialize the simulator 28 | simulator = circuit.simulator() 29 | 30 | 31 | import sys 32 | # Perform transient analysis 33 | try: 34 | analysis = simulator.transient(step_time=1@u_us, end_time=200@u_ms) 35 | except: 36 | print("analysis failed.") 37 | sys.exit(2) 38 | 39 | 40 | import numpy as np 41 | # Extract data from the analysis 42 | time = np.array(analysis.time) 43 | vin = np.array(analysis['vin']) 44 | vout = np.array(analysis['vout']) 45 | 46 | import matplotlib.pyplot as plt 47 | # Plot the response 48 | plt.figure() 49 | plt.plot(time, vout) 50 | plt.title('Response of Op-amp Differentiator') 51 | plt.xlabel('Time [s]') 52 | plt.ylabel('Output Voltage [V]') 53 | plt.grid(True) 54 | plt.savefig("[FIGURE_PATH]") 55 | 56 | 57 | from scipy.signal import find_peaks 58 | # Check for square wave characteristics in the output 59 | # Calculate the mean voltage level of the peaks and troughs 60 | 61 | # print("vout", vout) 62 | # print("max(vout)", max(vout)) 63 | # print("min(vout)", min(vout)) 64 | min_height = (max(vout) + min(vout)) / 2 65 | # print("min_height", min_height) 66 | num_of_peaks = 2 67 | min_distance = len(vout) / (2 * num_of_peaks) / 1.5 68 | # print("min_distance", min_distance) 69 | 70 | peaks, _ = find_peaks(vout, height=min_height, distance=min_distance) 71 | 72 | 73 | troughs, _ = find_peaks(-vout, height=-min_height, distance=min_distance) 74 | 75 | 76 | average_peak_voltage = np.mean(vout[peaks]) 77 | average_trough_voltage = np.mean(vout[troughs]) 78 | 79 | 80 | 81 | if len(peaks) == 0 or len(troughs) == 0: 82 | print("No peaks or troughs found in output voltage. Please check the netlist.") 83 | sys.exit(2) 84 | 85 | peak_voltages = vout[peaks] 86 | trough_voltages = vout[troughs] 87 | mean_peak = np.mean(peak_voltages) 88 | mean_trough = np.mean(trough_voltages) 89 | 90 | 91 | def is_square_wave(waveform, mean_peak, mean_trough, rtol=0.1): 92 | high_level = np.mean([x for x in waveform if x > (mean_peak + mean_trough) / 2]) 93 | low_level = np.mean([x for x in waveform if x <= (mean_peak + mean_trough) / 2]) 94 | 95 | is_high_close = np.isclose(high_level, mean_peak, rtol=rtol) 96 | is_low_close = np.isclose(low_level, mean_trough, rtol=rtol) 97 | 98 | return is_high_close and is_low_close 99 | 100 | # print("mean_peak - bias_voltage", mean_peak - bias_voltage) 101 | # print("mean_trough - bias_voltage", mean_trough - bias_voltage) 102 | # Check if the output is approximately a square wave by comparing the mean of the peaks and troughs 103 | if np.isclose(mean_peak - bias_voltage, -mean_trough+ bias_voltage, rtol=0.2) and \ 104 | np.isclose(mean_peak - bias_voltage, 0.6, rtol=0.2) and \ 105 | is_square_wave(vout, mean_peak, mean_trough): # 20% tolerance 106 | # print("The op-amp differentiator functions correctly.\n") 107 | # sys.exit(0) 108 | pass 109 | elif not np.isclose(mean_peak - bias_voltage, -mean_trough+ bias_voltage, rtol=0.2): 110 | print(f"The circuit does not function correctly as a differentiator.\n" 111 | f"When the input is a triangle wave and the output is not a square wave.\n") 112 | sys.exit(2) 113 | elif not is_square_wave(vout, mean_peak, mean_trough): 114 | print(f"The circuit does not function correctly as a differentiator.\n" 115 | f"When the input is a triangle wave and the output is not a square wave.\n") 116 | sys.exit(2) 117 | else: 118 | print(f"The circuit does not function correctly as a differentiator.\n" 119 | f"Output voltage peak value is wrong. Mean peak voltage: {mean_peak} V | Mean trough voltage: {mean_trough} V\n") 120 | sys.exit(2) 121 | 122 | 123 | for element in circuit.elements: 124 | if element.name.lower().startswith("x"): 125 | x_name = element.name 126 | 127 | circuit.element(x_name).detach() 128 | simulator = circuit.simulator() 129 | try: 130 | analysis = simulator.transient(step_time=1@u_us, end_time=200@u_ms) 131 | except: 132 | print("The op-amp differentiator functions correctly.\n") 133 | sys.exit(0) 134 | 135 | time = np.array(analysis.time) 136 | vin = np.array(analysis['vin']) 137 | vout = np.array(analysis['vout']) 138 | 139 | 140 | 141 | 142 | min_height = (max(vout) + min(vout)) / 2 143 | num_of_peaks = 2 144 | min_distance = len(vout) / (2 * num_of_peaks) / 1.5 145 | 146 | peaks, _ = find_peaks(vout, height=min_height, distance=min_distance) 147 | 148 | 149 | troughs, _ = find_peaks(-vout, height=-min_height, distance=min_distance) 150 | 151 | 152 | average_peak_voltage = np.mean(vout[peaks]) 153 | average_trough_voltage = np.mean(vout[troughs]) 154 | 155 | 156 | 157 | if len(peaks) == 0 or len(troughs) == 0: 158 | print(f"The op-amp differentiator functions correctly.\n") 159 | sys.exit(2) 160 | 161 | peak_voltages = vout[peaks] 162 | trough_voltages = vout[troughs] 163 | mean_peak = np.mean(peak_voltages) 164 | mean_trough = np.mean(trough_voltages) 165 | 166 | 167 | if np.isclose(mean_peak - bias_voltage, -mean_trough+ bias_voltage, rtol=0.2) and np.isclose(mean_peak - bias_voltage, 0.6, rtol=0.2): # 20% tolerance 168 | print("The differentiator maybe a passive differentiator.\n") 169 | elif not np.isclose(mean_peak - bias_voltage, -mean_trough+ bias_voltage, rtol=0.2): 170 | print(f"The op-amp differentiator functions correctly.\n") 171 | sys.exit(2) 172 | else: 173 | print(f"The op-amp differentiator functions correctly.\n") 174 | sys.exit(2) 175 | -------------------------------------------------------------------------------- /problem_check/Integrator.py: -------------------------------------------------------------------------------- 1 | vin_name = "" 2 | for element in circuit.elements: 3 | if "vin" in [str(pin.node).lower() for pin in element.pins] and element.name.lower().startswith("v"): 4 | vin_name = element.name 5 | 6 | 7 | bias_voltage = [BIAS_VOLTAGE] 8 | 9 | if vin_name != "": 10 | circuit.element(vin_name).detach() 11 | circuit.V('pulse', 'Vin', circuit.gnd, f"dc {bias_voltage} PULSE({bias_voltage-0.5} {bias_voltage+0.5} 1u 1u 1u 10m 20m)") 12 | else: 13 | circuit.V('in', 'Vin', circuit.gnd, f"dc {bias_voltage} PULSE({bias_voltage-0.5} {bias_voltage+0.5} 1u 1u 1u 10m 20m)") 14 | 15 | for element in circuit.elements: 16 | if element.name.lower().startswith("r1") or element.name.lower().startswith("rr1"): 17 | r_name = element.name 18 | circuit.element(r_name).resistance = "10k" 19 | 20 | for element in circuit.elements: 21 | # print("element.name", element.name) 22 | if element.name.lower().startswith("cf") or element.name.lower().startswith("ccf") or element.name.lower().startswith("c1"): 23 | c_name = element.name 24 | circuit.element(c_name).capacitance = "3u" 25 | 26 | simulator = circuit.simulator() 27 | 28 | try: 29 | analysis = simulator.transient(step_time=1@u_us, end_time=200@u_ms) 30 | except: 31 | print("analysis failed.") 32 | sys.exit(2) 33 | 34 | 35 | import numpy as np 36 | import matplotlib.pyplot as plt 37 | # Plot the step response 38 | time = np.array(analysis.time) 39 | vin = np.array(analysis['vin']) 40 | vout = np.array(analysis['vout']) 41 | 42 | 43 | plt.figure() 44 | plt.plot(time, vout) 45 | plt.title('Step Response of Op-amp Integrator') 46 | plt.xlabel('Time [s]') 47 | plt.ylabel('Output Voltage [V]') 48 | plt.grid(True) 49 | plt.savefig("[FIGURE_PATH]") 50 | 51 | 52 | expected_slope = 0.5 / 0.03 53 | 54 | 55 | from scipy.signal import find_peaks 56 | 57 | peaks, _ = find_peaks(vout) 58 | 59 | troughs, _ = find_peaks(-vout) 60 | 61 | if len(peaks) < 2 or len(troughs) < 2: 62 | print("No peaks or troughs found in output voltage. Please check the netlist.") 63 | sys.exit(2) 64 | 65 | 66 | start = peaks[-2] 67 | end = troughs[troughs > start][0] 68 | 69 | slope, intercept = np.polyfit(time[start:end], vout[start:end], 1) 70 | slope = np.abs(slope) 71 | from scipy.stats import linregress 72 | _, _, r_value, p_value, std_err = linregress(time[start:end], vout[start:end]) 73 | 74 | 75 | 76 | import sys 77 | if not np.isclose(slope, expected_slope, rtol=0.3): # 30% tolerance 78 | print(f"The circuit does not function correctly as an integrator.\n" 79 | f"Expected slope: {expected_slope} V/s | Actual slope: {slope} V/s\n") 80 | sys.exit(2) 81 | 82 | if not r_value** 2 >= 0.9: 83 | print("The op-amp integrator does not have a linear response.\n") 84 | sys.exit(2) 85 | 86 | 87 | for element in circuit.elements: 88 | if element.name.lower().startswith("x"): 89 | x_name = element.name 90 | 91 | circuit.element(x_name).detach() 92 | simulator = circuit.simulator() 93 | try: 94 | analysis = simulator.transient(step_time=1@u_us, end_time=200@u_ms) 95 | except: 96 | print("The op-amp integrator functions correctly.\n") 97 | sys.exit(0) 98 | 99 | time = np.array(analysis.time) 100 | vin = np.array(analysis['vin']) 101 | vout = np.array(analysis['vout']) 102 | 103 | 104 | 105 | 106 | expected_slope = 0.5 / 0.03 107 | 108 | 109 | from scipy.signal import find_peaks 110 | 111 | peaks, _ = find_peaks(vout) 112 | 113 | troughs, _ = find_peaks(-vout) 114 | 115 | if len(peaks) < 2 or len(troughs) < 2: 116 | print("The op-amp integrator functions correctly.\n") 117 | sys.exit(0) 118 | 119 | 120 | start = peaks[-2] 121 | end = troughs[troughs > start][0] 122 | 123 | slope, intercept = np.polyfit(time[start:end], vout[start:end], 1) 124 | slope = np.abs(slope) 125 | from scipy.stats import linregress 126 | _, _, r_value, p_value, std_err = linregress(time[start:end], vout[start:end]) 127 | 128 | 129 | if np.isclose(slope, expected_slope, rtol=0.5): # 50% tolerance 130 | print("The integrator maybe a passive integrator.\n") 131 | sys.exit(2) 132 | 133 | print("The op-amp integrator functions correctly.\n") 134 | sys.exit(0) 135 | 136 | -------------------------------------------------------------------------------- /problem_check/Inverter.py: -------------------------------------------------------------------------------- 1 | analysis = simulator.operating_point() 2 | for node in analysis.nodes.values(): 3 | print(f"{str(node)}\t{float(analysis[str(node)][0]):.6f}") 4 | vin_name = "" 5 | for element in circuit.elements: 6 | if "vin" in [str(pin.node).lower() for pin in element.pins] and element.name.lower().startswith("v"): 7 | vin_name = element.name 8 | 9 | circuit.element(vin_name).dc_value = "5" 10 | 11 | simulator2 = circuit.simulator() 12 | analysis2 = simulator2.operating_point() 13 | 14 | vout2 = float(analysis2["vout"][0]) 15 | 16 | circuit.element(vin_name).dc_value = "0" 17 | 18 | simulator3 = circuit.simulator() 19 | analysis3 = simulator3.operating_point() 20 | 21 | vout3 = float(analysis3["vout"][0]) 22 | 23 | import sys 24 | if vout2 <= 2.5 and vout3 >= 2.5 and vout3 - vout2 >= 1.0: 25 | print("The circuit functions correctly.\n") 26 | sys.exit(0) 27 | 28 | print("The circuit does not function correctly.\n" 29 | "It can not invert the input voltage.\n" 30 | "Please fix the wrong operating point.\n") 31 | 32 | sys.exit(2) 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /problem_check/Opamp.py: -------------------------------------------------------------------------------- 1 | simulator_id = circuit.simulator() 2 | mosfet_names = [] 3 | import PySpice.Spice.BasicElement 4 | for element in circuit.elements: 5 | if isinstance(element, PySpice.Spice.BasicElement.Mosfet): 6 | mosfet_names.append(element.name) 7 | 8 | mosfet_name_ids = [] 9 | for mosfet_name in mosfet_names: 10 | mosfet_name_ids.append(f"@{mosfet_name}[id]") 11 | 12 | simulator_id.save_internal_parameters(*mosfet_name_ids) 13 | analysis_id = simulator_id.operating_point() 14 | 15 | id_correct = 1 16 | for mosfet_name in mosfet_names: 17 | mosfet_id = float(analysis_id[f"@{mosfet_name}[id]"][0]) 18 | if mosfet_id < 1e-5: 19 | id_correct = 0 20 | print("The circuit does not function correctly. " 21 | "the current I_D for {} is 0. ".format(mosfet_name) 22 | .format(mosfet_name)) 23 | 24 | if id_correct == 0: 25 | print("Please fix the wrong operating point.\n") 26 | sys.exit(2) 27 | 28 | 29 | frequency = 100@u_Hz 30 | analysis = simulator.ac(start_frequency=frequency, stop_frequency=frequency*10, 31 | number_of_points=2, variation='dec') 32 | 33 | import numpy as np 34 | 35 | node = 'vout' 36 | output_voltage = analysis[node].as_ndarray()[0] 37 | gain = np.abs(output_voltage / (1e-6)) 38 | 39 | print(f"Common-Mode Gain (Av) at 100 Hz: {gain}") 40 | 41 | vinn_name = "" 42 | for element in circuit.elements: 43 | # print("element name", element.name) 44 | # for pin in element.pins: 45 | # print("pin name", pin.node) 46 | if "vinn" in [str(pin.node).lower() for pin in element.pins] and element.name.lower().startswith("v"): 47 | vinn_name = element.name 48 | 49 | 50 | circuit.element(vinn_name).dc_value += " 180" 51 | 52 | simulator2 = circuit.simulator() 53 | analysis2 = simulator2.ac(start_frequency=frequency, stop_frequency=frequency, 54 | number_of_points=1, variation='dec') 55 | 56 | output_voltage2 = np.abs(analysis2[node].as_ndarray()[0]) 57 | gain2 = output_voltage2 / (1e-6) 58 | 59 | print(f"Differential-Mode Gain (Av) at 100 Hz: {gain2}") 60 | 61 | required_gain = 1e-5 62 | import sys 63 | 64 | if gain < gain2 - 1e-5 and gain2 > required_gain: 65 | print("The circuit functions correctly at 100 Hz.\n") 66 | sys.exit(0) 67 | 68 | if gain >= gain2 - 1e-5: 69 | print("Common-Mode gain is larger than Differential-Mode gain.\n") 70 | 71 | if gain2 < required_gain: 72 | print("Differential-Mode gain is smaller than 1e-5.\n") 73 | 74 | print("The circuit does not function correctly.\n" 75 | "Please fix the wrong operating point.\n") 76 | sys.exit(2) -------------------------------------------------------------------------------- /problem_check/Oscillator.py: -------------------------------------------------------------------------------- 1 | del_vname = [] 2 | for element in circuit.elements: 3 | v_name = element.name 4 | if element.name.lower().startswith("v"): 5 | del_vname.append(v_name) 6 | 7 | 8 | pin_name = "Vinp" 9 | pin_name_n = "Vinn" 10 | for element in circuit.elements: 11 | if element.name.lower().startswith("x"): 12 | opamp_element = element 13 | pin_name = str(opamp_element.pins[0].node) 14 | pin_name_n = str(opamp_element.pins[1].node) 15 | break 16 | 17 | 18 | # print("pin_name", pin_name) 19 | # print("pin_name_n", pin_name_n) 20 | 21 | params = {pin_name: 2.51, pin_name_n: 2.5} 22 | 23 | simulator = circuit.simulator() 24 | simulator.initial_condition(**params) 25 | 26 | try: 27 | analysis = simulator.transient(step_time=1@u_us, end_time=10@u_ms) 28 | except: 29 | print("analysis failed.") 30 | sys.exit(2) 31 | 32 | import numpy as np 33 | # Get the output node voltage 34 | vout = np.array(analysis['Vout']) 35 | vinp = np.array(analysis[pin_name]) 36 | vinn = np.array(analysis[pin_name_n]) 37 | time = np.array(analysis.time) 38 | 39 | from scipy.signal import find_peaks, firwin, lfilter 40 | 41 | numtaps = 51 42 | cutoff_hz = 10.0 43 | sample_rate = 1000 44 | fir_coeff = firwin(numtaps, cutoff_hz, fs=sample_rate, window="hamming") 45 | 46 | filtered_vout = lfilter(fir_coeff, 1.0, vout) 47 | peaks, _ = find_peaks(filtered_vout) 48 | 49 | 50 | error = 0 51 | import sys 52 | 53 | # Plot the results 54 | import matplotlib.pyplot as plt 55 | 56 | plt.figure() 57 | plt.plot(time, vout) 58 | # plt.plot(time, filtered_vout) 59 | plt.plot(time, vinp) 60 | plt.plot(time, vinn) 61 | plt.title('Wien Bridge Oscillator Output') 62 | plt.xlabel('Time [s]') 63 | plt.ylabel('Voltage [V]') 64 | plt.legend(['Vout', 'Vinp', 'Vinn']) 65 | plt.grid() 66 | plt.savefig('[FIGURE_PATH].png') 67 | 68 | # sys.exit(0) 69 | 70 | troughs, _ = find_peaks(-filtered_vout) 71 | if len(peaks) > 0 and len(troughs) > 0: 72 | amplitudes = [] 73 | 74 | for peak in peaks: 75 | trough_index = np.argmin(np.abs(troughs - peak)) 76 | trough = troughs[trough_index] 77 | amplitude = np.abs(vout[peak] - vout[trough]) 78 | amplitudes.append(amplitude) 79 | 80 | amplitudes = np.array(amplitudes) 81 | 82 | min_amplitude_threshold = 1e-6 83 | 84 | else: 85 | print("Not enough peaks were detected to determine amplitude.") 86 | sys.exit(2) 87 | 88 | # print("Amplitudes: ", amplitudes) 89 | amplitudes = np.sort(amplitudes) 90 | num_elements_to_keep = len(amplitudes) // 2 91 | amplitudes = amplitudes[-num_elements_to_keep:] 92 | 93 | 94 | if not all(amplitudes > min_amplitude_threshold): 95 | print("The peak amplitudes are too small. (<1uV)") 96 | error = 1 97 | 98 | 99 | if len(peaks) > 3: 100 | peak_times = time[peaks] 101 | periods = np.diff(peak_times) 102 | average_period = np.mean(periods) 103 | some_small_threshold = 0.2 * average_period 104 | period_variation = np.std(periods) 105 | if period_variation < some_small_threshold: 106 | if error == 0: 107 | print("The oscillator works correct and produces periodic oscillations.") 108 | print("The average period is: {} s".format(np.mean(periods))) 109 | else: 110 | print("Periodicity is inconsistent, oscillation may not be an ideal periodicity.") 111 | error = 1 112 | else: 113 | print("Not enough peaks were detected to determine periodicity.") 114 | error = 1 115 | 116 | 117 | 118 | if error == 1: 119 | sys.exit(2) 120 | else: 121 | sys.exit(0) -------------------------------------------------------------------------------- /problem_check/PLL.py: -------------------------------------------------------------------------------- 1 | in_frequency = 10e6 2 | period = 1/in_frequency 3 | circuit.PulseVoltageSource('1', 'clk_ref', circuit.gnd, initial_value=0@u_V, pulsed_value=1@u_V, 4 | pulse_width=(0.48*period)@u_s, period=(period)@u_s, delay_time=30@u_ns, rise_time=(0.02*period)@u_s, fall_time=(0.02*period)@u_s) 5 | simulator = circuit.simulator() 6 | 7 | simulator.initial_condition(vctrl=0.5@u_V, 8 | clk_p=0.5@u_V, clk_n=0.5@u_V, 9 | clk_p_45=0.5@u_V, clk_n_45=0.5@u_V, 10 | clk_p_90=0.5@u_V, clk_n_90=0.5@u_V, 11 | clk_p_135=0.5@u_V, clk_n_135=0.5@u_V) 12 | analysis = simulator.transient(step_time=10@u_ns, end_time=10@u_us) 13 | 14 | 15 | ### Find frequency 16 | import numpy as np 17 | time = np.array(analysis.time) # Time points array 18 | vout = np.array(analysis['clk_p']) # Output voltage array 19 | # Find zero-crossings 20 | zero_crossings = np.where(np.diff(np.sign(vout-0.5))[:])[0][-5:] 21 | # Calculate periods by subtracting consecutive zero-crossing times 22 | periods = np.diff(time[zero_crossings]) 23 | # Average period 24 | average_period = 2 * np.mean(periods) 25 | # Frequency is the inverse of the period 26 | out_frequency = 1 / average_period 27 | print() 28 | print(f"REF Frequency : {in_frequency*1e-6} MHz") 29 | print(f"OUT Frequency : {out_frequency*1e-6} MHz") 30 | print() 31 | 32 | 33 | 34 | if np.close(in_frequency, out_frequency, rtol=0.05): 35 | print("The Phase-Locked Loop functions correctly.\n") 36 | else: 37 | print("The Phase-Locked Loop does not function correctly.\n") 38 | print("When the clk_ref frequency is 10 MHz, the output frequency should be 10 MHz.\n") 39 | sys.exit(0) 40 | 41 | fig = plt.figure(figsize=(14, 9)) 42 | 43 | # plt.ylim((-0.2, 1.2)) 44 | 45 | plt.subplot(321) 46 | plt.xlim((0, 1e-6)) 47 | plt.plot(list(analysis.time), list(analysis["clk_ref"])) 48 | plt.plot(list(analysis.time), list(analysis["clk_p"])) 49 | plt.title('init clk') 50 | 51 | plt.subplot(323) 52 | plt.xlim((0, 1e-6)) 53 | plt.plot(list(analysis.time), list(analysis["UP"])) 54 | plt.plot(list(analysis.time), list(analysis["DN"])) 55 | plt.title('init UP/DN') 56 | 57 | plt.subplot(325) 58 | plt.plot(list(analysis.time), list(analysis["vctrl"])) 59 | plt.title('overall vctrl') 60 | 61 | plt.subplot(322) 62 | plt.xlim((9e-6, 10e-6)) 63 | plt.plot(list(analysis.time), list(analysis["clk_ref"])) 64 | plt.plot(list(analysis.time), list(analysis["clk_p"])) 65 | plt.title('converged clk') 66 | 67 | plt.subplot(324) 68 | plt.xlim((9e-6, 10e-6)) 69 | plt.plot(list(analysis.time), list(analysis["UP"])) 70 | plt.plot(list(analysis.time), list(analysis["DN"])) 71 | plt.title('converged UP/DN') 72 | 73 | plt.subplot(326) 74 | plt.xlim((9e-6, 10e-6)) 75 | plt.plot(list(analysis.time), list(analysis["vctrl"])) 76 | plt.title('converged vctrl') 77 | 78 | plt.show() 79 | 80 | fig.savefig("./outputs/pll.png") 81 | plt.close(fig) 82 | ###################### 83 | 84 | sys.exit(0) -------------------------------------------------------------------------------- /problem_check/Schmitt.py: -------------------------------------------------------------------------------- 1 | vin_name = "Vin" 2 | for element in circuit.elements: 3 | if "vin" in [str(pin.node).lower() for pin in element.pins] and element.name.lower().startswith("v"): 4 | vin_name = element.name 5 | 6 | params = {vin_name: slice(0, 5, 0.1)} 7 | 8 | try: 9 | analysis = simulator.dc(**params) 10 | except: 11 | print("DC analysis failed.") 12 | sys.exit(2) 13 | 14 | params2 = {vin_name: slice(5, 0, -0.1)} 15 | 16 | simulator2 = circuit.simulator() 17 | 18 | try: 19 | analysis2 = simulator2.dc(**params2) 20 | except: 21 | print("DC analysis failed.") 22 | sys.exit(2) 23 | 24 | import numpy as np 25 | import matplotlib.pyplot as plt 26 | 27 | 28 | vin = np.array(analysis[vin_name]) 29 | vout = np.array(analysis['Vout']) 30 | vin2 = np.flip(np.array(analysis2[vin_name])) 31 | vout2 = np.flip(np.array(analysis2['Vout'])) 32 | 33 | threshold = 2.5 34 | 35 | 36 | try: 37 | trigger_index = np.where(vout > threshold)[0][0] 38 | except: 39 | print("The circuit does not function correctly. The output voltage does not cross the Vdd/2.") 40 | sys.exit(2) 41 | 42 | trigger_vin = vin[trigger_index] 43 | 44 | try: 45 | trigger_index2 = np.where(vout2 > threshold)[0][0] 46 | except: 47 | print("The circuit does not function correctly. The output voltage does not cross the Vdd/2.") 48 | sys.exit(2) 49 | 50 | trigger_vin2 = vin2[trigger_index2] 51 | 52 | 53 | # Plot the input and output waveforms 54 | plt.figure() 55 | plt.plot(vin, vin, label='Vin') 56 | plt.plot(vin, vout, label='Vout') 57 | plt.plot(vin2, vout2, label='Vout2') 58 | plt.title('Schmitt Trigger Input and Output Waveforms') 59 | plt.xlabel('vin [V]') 60 | plt.ylabel('Voltage [V]') 61 | plt.legend() 62 | plt.grid(True) 63 | plt.savefig("[FIGURE_PATH].png") 64 | 65 | if abs(trigger_vin - trigger_vin2) <= 0.05: 66 | print("The circuit does not function correctly. Trigger points are too close.") 67 | print(f"Trigger points: {trigger_vin:.5f}V and {trigger_vin2:.5f}V are not sufficiently different. Please use the positive feedback which the Rf should connect to the non-inverting input of the op-amp.") 68 | sys.exit(2) 69 | elif vout[-1] - vout[0] < 2.5 or vout2[-1] - vout2[0] < 2.5: 70 | print("The circuit does not function correctly. The output voltage does not vary more than Vdd/2.") 71 | sys.exit(2) 72 | elif not np.all(np.diff(vout)>=0) or not np.all(np.diff(vout2)>=0): 73 | print("The circuit does not function correctly. The output voltage variation does not monotonically increase with increasing input voltage.") 74 | sys.exit(2) 75 | 76 | print("The circuit functions correctly with different trigger points.") 77 | sys.exit(0) -------------------------------------------------------------------------------- /problem_check/Subtractor.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import sys 3 | 4 | # Define the bias voltage and input voltage differences 5 | BIAS_VOLTAGE = [BIAS_VOLTAGE] 6 | v1_amp = BIAS_VOLTAGE*2 7 | v2_amp = BIAS_VOLTAGE*2 8 | 9 | 10 | vin1_name = "" 11 | for element in circuit.elements: 12 | if "vin1" in [str(pin.node).lower() for pin in element.pins] and element.name.lower().startswith("v"): 13 | vin1_name = element.name 14 | 15 | vin1_name = "" 16 | for element in circuit.elements: 17 | if "vin1" in [str(pin.node).lower() for pin in element.pins] and element.name.lower().startswith("v"): 18 | vin1_name = element.name 19 | 20 | if not vin1_name == "": 21 | circuit.element(vin1_name).detach() 22 | circuit.V('in1', 'Vin1', circuit.gnd, v1_amp) 23 | vin1_name = "Vin1" 24 | else: 25 | circuit.V('in1', 'Vin1', circuit.gnd, v1_amp) 26 | vin1_name = "Vin1" 27 | 28 | vin2_name = "" 29 | for element in circuit.elements: 30 | if "vin2" in [str(pin.node).lower() for pin in element.pins] and element.name.lower().startswith("v"): 31 | vin2_name = element.name 32 | 33 | if not vin2_name == "": 34 | circuit.element(vin2_name).detach() 35 | circuit.V('in2', 'Vin2', circuit.gnd, v2_amp) 36 | vin2_name = "Vin2" 37 | else: 38 | circuit.V('in2', 'Vin2', circuit.gnd, v2_amp) 39 | vin2_name = "Vin2" 40 | 41 | 42 | 43 | # print("vin1_name", vin1_name) 44 | # print("vin2_name", vin2_name) 45 | 46 | for element in circuit.elements: 47 | if element.name.lower().startswith(vin1_name.lower()): 48 | circuit.element(element.name).dc_value = f"dc {v1_amp}" 49 | elif element.name.lower().startswith(vin2_name.lower()): 50 | circuit.element(element.name).dc_value = f"dc {v2_amp}" 51 | 52 | # print(str(circuit)) 53 | 54 | simulator = circuit.simulator() 55 | 56 | 57 | params = {vin1_name: slice(BIAS_VOLTAGE*2 -2.25, BIAS_VOLTAGE*2 - 1.75, 0.05)} 58 | # Run a DC analysis 59 | try: 60 | analysis = simulator.dc(**params) 61 | except: 62 | print("DC analysis failed.") 63 | sys.exit(2) 64 | 65 | # Collect the simulation results 66 | out_voltage = np.array(analysis.Vout) 67 | vin1_voltage = np.array(analysis.Vin1) 68 | vin2_voltage = np.array(analysis.Vin2) 69 | 70 | # vinn_voltage = np.array(analysis.inv_input) 71 | # print("vinn_voltage", vinn_voltage) 72 | # print("vin1_voltage", vin1_voltage) 73 | # print("vin2_voltage", vin2_voltage) 74 | # vinp_voltage = np.array(analysis.non_inv_input) 75 | # print("vinp_voltage", vinp_voltage) 76 | # print("out_voltage", out_voltage) 77 | 78 | # Define a tolerance for verifying the subtractor's functionality 79 | tolerance = 0.2 # 20% Tolerance 80 | 81 | # Iterate over the simulation results to check if the output is Vin2 - Vin1 82 | for i, out_v in enumerate(out_voltage): 83 | in_v_1 = vin1_voltage[i] 84 | in_v_2 = v2_amp 85 | expected_vout = in_v_2 - in_v_1 86 | actual_vout = out_v 87 | if not np.isclose(actual_vout, expected_vout, rtol=tolerance): 88 | print(f"The circuit does not function correctly as a subtractor.\n" 89 | f"Expected Vout: {expected_vout:.2f} V, Vin1 = {in_v_1:.2f} V, Vin2 = {in_v_2:.2f} V | Actual Vout: {actual_vout:.2f} V\n") 90 | sys.exit(1) 91 | 92 | print("The op-amp subtractor functions correctly.\n") 93 | sys.exit(0) -------------------------------------------------------------------------------- /problem_check/VCO.py: -------------------------------------------------------------------------------- 1 | simulator.initial_condition(vout_1=0.3@u_V, vout=0.6@u_V) 2 | 3 | try: 4 | analysis = simulator.transient(step_time=1@u_ns, end_time=100@u_us) 5 | except: 6 | print("Transient analysis failed.") 7 | sys.exit(2) 8 | 9 | 10 | import numpy as np 11 | 12 | fig = plt.figure() 13 | plt.ylim((-2, 2)) 14 | plt.plot(list(analysis.time), list(analysis["vout"])) 15 | fig.savefig("[FIGURE_PATH].png") 16 | 17 | 18 | y = np.array(analysis["vout"]) 19 | # print("y", y) 20 | t = np.array(analysis.time) 21 | threshold = (y.max() + y.min())/2 22 | # print("threshold", threshold) 23 | # print(threshold) 24 | crossings = [] 25 | for i in range(1, len(y)): 26 | if y[i-1] < threshold and y[i] >= threshold: 27 | slope = (y[i] - y[i-1]) / (t[i] - t[i-1]) 28 | exact_time = t[i-1] + (threshold - y[i-1]) / slope 29 | crossings.append(exact_time) 30 | 31 | # print("crossings", crossings) 32 | # print("len(crossings)", len(crossings)) 33 | periods = np.diff(crossings) 34 | average_period = np.median(periods) 35 | # print("average_period", average_period) 36 | 37 | circuit.element("Vin").detach() 38 | circuit.V('in', 'vin', circuit.gnd, 0.65@u_V) 39 | 40 | simulator = circuit.simulator() 41 | simulator.initial_condition(vout_1=0.3@u_V, vout=0.7@u_V) 42 | # print("simulator2 start") 43 | 44 | try: 45 | analysis = simulator.transient(step_time=1@u_ns, end_time=100@u_us) 46 | except: 47 | print("Transient analysis failed.") 48 | sys.exit(2) 49 | # print("simulator2 end") 50 | 51 | plt.plot(list(analysis.time), list(analysis["vout"])) 52 | 53 | fig.savefig("./opamp_vco.png") 54 | 55 | y = np.array(analysis["vout"]) 56 | # print("y", y) 57 | t = np.array(analysis.time) 58 | threshold = (y.max() + y.min())/2 59 | # print("threshold", threshold) 60 | # print(threshold) 61 | crossings = [] 62 | for i in range(1, len(y)): 63 | if y[i-1] < threshold and y[i] >= threshold: 64 | slope = (y[i] - y[i-1]) / (t[i] - t[i-1]) 65 | exact_time = t[i-1] + (threshold - y[i-1]) / slope 66 | crossings.append(exact_time) 67 | 68 | # print("crossings", crossings) 69 | # print("len(crossings)", len(crossings)) 70 | periods = np.diff(crossings) 71 | average_period2 = np.median(periods) 72 | # print("average_period2", average_period2) 73 | 74 | 75 | circuit.element("Vin").detach() 76 | circuit.V('in', 'vin', circuit.gnd, 0.8@u_V) 77 | 78 | simulator = circuit.simulator() 79 | simulator.initial_condition(vout_1=0.3@u_V, vout=0.7@u_V) 80 | # print("simulator2 start") 81 | analysis = simulator.transient(step_time=1@u_ns, end_time=100@u_us) 82 | # print("simulator2 end") 83 | 84 | plt.plot(list(analysis.time), list(analysis["vout"])) 85 | 86 | # fig.savefig("./opamp_vco.png") 87 | 88 | y = np.array(analysis["vout"]) 89 | # print("y", y) 90 | t = np.array(analysis.time) 91 | threshold = (y.max() + y.min())/2 92 | # print("threshold", threshold) 93 | # print(threshold) 94 | crossings = [] 95 | for i in range(1, len(y)): 96 | if y[i-1] < threshold and y[i] >= threshold: 97 | slope = (y[i] - y[i-1]) / (t[i] - t[i-1]) 98 | exact_time = t[i-1] + (threshold - y[i-1]) / slope 99 | crossings.append(exact_time) 100 | 101 | # print("crossings", crossings) 102 | # print("len(crossings)", len(crossings)) 103 | periods = np.diff(crossings) 104 | average_period3 = np.median(periods) 105 | # print("average_period3", average_period3) 106 | 107 | 108 | if average_period - 1e-5 > average_period2 and average_period - 1e-5 > average_period3: 109 | print("The voltage-controlled oscillator functions correctly.") 110 | sys.exit(0) 111 | elif average_period + 1e-5 < average_period2 and average_period + 1e-5 < average_period3: 112 | print("The voltage-controlled oscillator functions correctly.") 113 | sys.exit(0) 114 | else: 115 | print("The voltage-controlled oscillator does not function correctly.") 116 | print("The average period is not changing as expected when adjusting the vin.") 117 | sys.exit(2) -------------------------------------------------------------------------------- /problem_set.tsv: -------------------------------------------------------------------------------- 1 | Id Level Circuit Input Output Type Submodule Name 2 | 1 Easy a single-stage common-source amplifier with resistive load R Vin Vout Amplifier SingleStageAmp 3 | 2 Easy A three-stage amplifier with single input and output (each stage is common-source with resistive load) Vin Vout Amplifier ThreeStageAmp 4 | 3 Easy A common-drain amplifier (a.k.a. a source follower) with resistive load R (output Vout at the source) Vin Vout Amplifier CommonDrainAmp 5 | 4 Easy A single-stage common-gate amplifier with resistive load R (input signal Vin must be applied at the source terminal) Vin, Vbias Vout Amplifier CommonGateAmp 6 | 5 Easy A single-stage cascode amplifier with two NMOS transistors provides a single-ended output through a resistive load R Vin, Vbias Vout Amplifier SingleStageCascodeAmp 7 | 6 Easy A NMOS inverter with resistive load R Vin Vout Inverter NMOSInverter 8 | 7 Easy A logical inverter with 1 NMOS and 1 PMOS Vin Vout Inverter LogicalInverter 9 | 8 Easy A simple NMOS constant current source with resistive load R Vbias Vout CurrentMirror NMOSConstantCurrentSource 10 | 9 Medium A two-stage amplifier with a Miller compensation capacitor Vin Vout Amplifier TwoStageOpampMiller 11 | 10 Medium A single-stage amplifier (common-source with PMOS diode-connected load (gate and drain are shorted)) Vin Vout Amplifier CommonSourceAmpDiodeLoad 12 | 11 Medium A differential opamp with an active PMOS current mirror load, a tail current source, and two outputs Vinp, Vinn, Vbias Voutp, Vout Opamp SingleStageOpamp 13 | 12 Medium A cascode current mirror with 4 mosfets (2 stacked at input side with diode-connected, 2 stacked at output side), reference current source input Iref (connected to Vdd) and resistive load R Iref Iout CurrentMirror CascodeCurrentMirror 14 | 13 Medium A single-stage differential common-source opamp with dual resistive loads, tail current, and a single output Vinp, Vinn Vout Opamp SingleStageDiffCommonSourceOpamp 15 | 14 Hard A two-stage differential opamp (first stage: common-source with an active load and a tail current, second stage: common-source with an active load) Vinp, Vinn, Vbias1, Vbias2, Vbias3 Voutp, Vout Opamp OpampResistanceLoad 16 | 15 Hard A single-stage telescopic cascode opamp with two outputs (4 nmos as cascode input pair, 4 pmos as cascode loads, and 1 tail current) Vinp, Vinn, Vbias1, Vbias2, Vbias3, Vbias4 Voutp, Vout Opamp SingleStageDiffOpamp 17 | 16 Hard An RC phase-shift oscillator - Vout Oscillator Unknown 18 | 17 Hard A Wien Bridge oscillator - Vout Oscillator Unknown 19 | 18 Hard An Opamp integrator with resistor R1 and capacitor Cf Vin Vout Integrator Unknown 20 | 19 Hard An Opamp differentiator with resistor Rf and capacitor C1 Vin Vout Differentiator Unknown 21 | 20 Hard An Opamp adder to make Vout=-(Vin1+Vin2) Vin1, Vin2 Vout Adder Unknown 22 | 21 Hard An Op-amp subtractor to make Vout=Vin2-Vin1 Vin1, Vin2 Vout Subtractor Unknown 23 | 22 Hard A non-inverting Schmitt trigger with positive feedback op-map Vin Vout Schmitt Unknown 24 | 23 Hard A voltage-controlled oscillator Vin Vout VCO Unknown 25 | 24 Hard A phase-locked loop clk_ref clk_p PLL Unknown -------------------------------------------------------------------------------- /prompt_template.md: -------------------------------------------------------------------------------- 1 | You aim to design a topology for a given circuit described in the text. 2 | Please ensure your designed circuit topology works properly and achieves the design requirements. 3 | 4 | Here is an example: 5 | 6 | ## Question 7 | Design a 2-stage amplifier (first stage: a common-source stage with current-source load, second stage: a common-source stage with resistor load). 8 | 9 | Input node name: Vin, Vbias. 10 | 11 | Output node name: Vout. 12 | 13 | 14 | ## Answer 15 | 16 | ### Task 1 17 | 18 | #### Components Needed 19 | - **NMOS Transistors**: M1 and M3 20 | - **PMOS Transistors**: M2 (used as the current source in the first stage) 21 | - **Resistors**: R1 for the second stage load 22 | - **Power Supply**: Vdd for DC supply 23 | - **Input Signal Source**: Vin, Vbias for biasing and signal input 24 | - **Capacitors**: Not specified but can be included for coupling and bypass applications if required 25 | 26 | #### Stage 1: Common-Source Amplifier with Current Source Load 27 | 1. **Transistor Setup**: 28 | - **M1** (NMOS) as the main amplifying transistor. 29 | - Gate of **M1** is connected to the input node **Vin**. 30 | - Source of **M1** connected to the ground. 31 | - Drain of **M1** connected to the drain of **M2**. 32 | 33 | 2. **Biasing**: 34 | - **Vin** provides the input signal. 35 | - **Vbias** is used to bias **M2** (PMOS), ensuring it operates as a current source. 36 | 37 | 3. **Current Source Load (M2)**: 38 | - **M2**, a PMOS transistor, is configured as a current source. 39 | - The source of **M2** is connected to **Vdd**, and its gate is connected to **Vbias**. 40 | - Drain of **M2** is connected to the drain of **M1**, providing a high-impedance load. 41 | 42 | #### Stage 2: Common-Source Amplifier with Resistor Load 43 | 1. **Transistor Setup**: 44 | - **M3** (NMOS) as the main amplifying transistor for the second stage. 45 | - Gate of **M3** connected to the drain of **M1**. 46 | - Source of **M3** connected to the ground. 47 | - Drain of **M3** connected to **Vout** through resistor **R1**. 48 | 49 | 2. **Load and Coupling**: 50 | - **R1** connects the drain of **M3** to **Vdd**. This resistor converts the current through **M3** into an output voltage. 51 | 52 | 53 | ### Task 2 54 | 55 | ```python 56 | from PySpice.Spice.Netlist import Circuit 57 | from PySpice.Unit import * 58 | 59 | circuit = Circuit('Two-Stage Amplifier') 60 | # Define the MOSFET models 61 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 62 | circuit.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 63 | 64 | # Power Supplies for the power and input signal 65 | 66 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 67 | circuit.V('in', 'Vin', circuit.gnd, 1.0) # 1V input for bias voltage (= V_th + 0.5 = 0.5 + 0.5 = 1.0) 68 | circuit.V('bias', 'Vbias', circuit.gnd, 4.0) # 4V input for bias voltage (= Vdd - |V_th| - 0.5 = 5.0 - 0.5 - 0.5 = 4.0) 69 | 70 | # First Stage: Common-Source with Active Load 71 | # parameters: name, drain, gate, source, bulk, model, w, l 72 | circuit.MOSFET('1', 'Drain1', 'Vin', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 73 | circuit.MOSFET('2', 'Drain1', 'Vbias', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 74 | 75 | # Second Stage: Common-Source with Resistor Load 76 | circuit.MOSFET('3', 'Vout', 'Drain1', circuit.gnd, circuit.gnd, model='nmos_model', w=100e-6, l=1e-6) 77 | circuit.R('1', 'Vout', 'Vdd', 1@u_kΩ) 78 | 79 | # Analysis Part 80 | simulator = circuit.simulator() 81 | ``` 82 | 83 | 84 | As you have seen, the output of your designed topology should consist of two tasks: 85 | 1. Give a detailed design plan about all devices and their interconnectivity nodes and properties. 86 | 2. Write a complete Python code, describing the topology of integrated analog circuits according to the design plan. 87 | 88 | Please make sure your Python code is compatible with PySpice. 89 | Please give the runnable code without any placeholders. 90 | 91 | 92 | Do not write other redundant codes after ```simulator = circuit.simulator()```. 93 | 94 | There are some tips you should remember all the time: 95 | 1. For the MOSFET definition circuit.MOSFET(name, drain, gate, source, bulk, model, w=w1,l=l1), be careful about the parameter sequence. 96 | 2. You should connect the bulk of a MOSFET to its source. 97 | 3. Please use the MOSFET threshold voltage, when setting the bias voltage. 98 | 4. Avoid giving any AC voltage in the sources, just consider the operating points. 99 | 5. Make sure the input and output node names appear in the circuit. 100 | 6. Avoid using subcircuits. 101 | 7. Use nominal transistor sizing. 102 | 8. Assume the Vdd = 5.0 V. 103 | 104 | ## Question 105 | 106 | Design [TASK]. 107 | 108 | Input node name: [INPUT]. 109 | 110 | Output node name: [OUTPUT]. 111 | 112 | 113 | ## Answer 114 | 115 | 116 | -------------------------------------------------------------------------------- /prompt_template_complex.md: -------------------------------------------------------------------------------- 1 | You aim to design a topology for a given circuit described in the text. 2 | Please ensure your designed circuit topology works properly and achieves the design requirements. 3 | To make the task easier, I provide you with some existing subcircuits you can directly use by calling functions in Python with the PySpice library. 4 | Now I would like you to help me design a complex analog circuit based on them. 5 | 6 | 7 | 8 | Here is an example: 9 | 10 | ## Question 11 | Design an opamp with 470 ohm resistance load. 12 | 13 | Input node name: in 14 | 15 | Output node name: out 16 | 17 | You can directly use the following subcircuits. 18 | 19 | ### Subcircuits Info 20 | 21 | Id Circuit Type Gain/Differential-mode gain (dB) Common-mode gain (dB) Input Output 22 | - Opamp 20 -10 Vin Vout 23 | 24 | 25 | ### Call Info 26 | 27 | To use them, please insert the following codes. 28 | 29 | ```python 30 | from example_lib import * 31 | # declare the subcircuit 32 | circuit.subcircuit(‘BasicOperationalAmplifier()) 33 | # create a subcircuit instance 34 | circuit.X('1', 'BasicOperationalAmplifier', 'Vin', 'Vout') 35 | ``` 36 | 37 | 38 | ## Answer 39 | 40 | 41 | ```python 42 | 43 | from PySpice.Spice.Netlist import Circuit 44 | from PySpice.Unit import * 45 | from example_lib import * 46 | 47 | circuit = Circuit('Operational Amplifier') 48 | 49 | # Define the MOSFET models 50 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 51 | circuit.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 52 | 53 | 54 | circuit.V('input', 'in', circuit.gnd, 2.5@u_V) 55 | circuit.subcircuit(BasicOperationalAmplifier()) 56 | circuit.X('op', 'BasicOperationalAmplifier', 'in', circuit.gnd, 'out') 57 | R = 470 58 | circuit.R('load', 'out', circuit.gnd, R) 59 | 60 | simulator = circuit.simulator() 61 | ``` 62 | 63 | As you have seen, the output of your designed topology should be in a complete Python code, describing the topology of integrated analog circuits according to the design plan. 64 | 65 | Please make sure your Python code is compatible with PySpice. 66 | Please give the runnable code without any placeholders. 67 | Do not write other redundant codes after ```simulator = circuit.simulator()```. 68 | 69 | There are some tips you should remember all the time: 70 | 1. For the MOSFET definition circuit.MOSFET(name, drain, gate, source, bulk, model, w=w1,l=l1), be careful about the parameter sequence. 71 | 2. You should connect the bulk of a MOSFET to its source. 72 | 3. Please use the MOSFET threshold voltage, when setting the bias voltage. 73 | 4. Avoid giving any AC voltage in the sources, just consider the operating points. 74 | 5. Make sure the input and output node names appear in the circuit. 75 | 6. Assume the Vdd = 5.0 V. Do not need to add the power supply for subcircuits. 76 | 77 | ## Question 78 | 79 | Design [TASK]. 80 | 81 | Input node name: [INPUT]. 82 | 83 | Output node name: [OUTPUT]. 84 | 85 | You can directly use the following subcircuits. 86 | 87 | ### Subcircuits Info 88 | 89 | 90 | [SUBCIRCUITS_INFO] 91 | 92 | #### Note 93 | 94 | [NOTE_INFO] 95 | 96 | ### Call Info 97 | 98 | To use them, please insert the following codes. 99 | 100 | [CALL_INFO] 101 | 102 | 103 | ## Answer 104 | -------------------------------------------------------------------------------- /retrieval_prompt.md: -------------------------------------------------------------------------------- 1 | I have the following implemented subcircuits you can directly call them by Python code with the Pyspice library, we list all basic information on them. 2 | 3 | | Id | Type | Circuit | Gain (dB) | Common Mode Gain (dB) | # of inputs | # of outputs | Input Phase | 4 | |---|---|---|---|---|---|---|---| 5 | | 1 | Amplifier | a single-stage common-source amplifier with resistive load R | 17.03 | NA | 1 | 1 | inverting | 6 | | 2 | Amplifier | A three-stage amplifier with single input and output (each stage is common-source with resistive load) | 44.13 | NA | 1 | 1 | inverting | 7 | | 3 | Amplifier | A common-drain amplifier (a.k.a. a source follower) with resistive load R (output Vout at the source) | -1.15 | NA | 1 | 1 | non-inverting | 8 | | 4 | Amplifier | A single-stage common-gate amplifier with resistive load R (input signal Vin must be applied at the source terminal) | 17.03 | NA | 1 | 1 | non-inverting | 9 | | 5 | Amplifier | A single-stage cascode amplifier with two NMOS transistors provides a single-ended output through a resistive load R | 24.08 | NA | 1 | 1 | inverting | 10 | | 6 | Inverter | A NMOS inverter with resistive load R | NA | NA | 1 | 1 | NA | 11 | | 7 | Inverter | A logical inverter with 1 NMOS and 1 PMOS | NA | NA | 1 | 1 | NA | 12 | | 8 | CurrentMirror | A simple NMOS constant current source with resistive load R | NA | NA | 1 | 1 | NA | 13 | | 9 | Amplifier | A two-stage amplifier with a Miller compensation capacitor | 75.94 | NA | 1 | 1 | -90 degree | 14 | | 10 | Amplifier | A single-stage amplifier (common-source with PMOS diode-connected load (gate and drain are shorted)) | 3.01 | NA | 1 | 1 | inverting | 15 | | 11 | Opamp | A differential opamp with an active PMOS current mirror load, a tail current source, and two outputs | 200 | -40.13 | 2 | 2 | non-inverting, inverting | 16 | | 12 | CurrentMirror | A cascode current mirror with 4 mosfets (2 stacked at input side with diode-connected, 2 stacked at output side), reference current source input Iref (connected to Vdd) and resistive load R | NA | NA | 1 | 1 | NA | 17 | | 13 | Opamp | A single-stage differential common-source opamp with dual resistive loads, tail current, and a single output | 24.04 | 21.59 | 2 | 1 | non-inverting, inverting | 18 | | 14 | Opamp | A two-stage differential opamp (first stage: common-source with an active load and a tail current, second stage: common-source with an active load) | 150.36 | -58.72 | 2 | 2 | non-inverting, inverting | 19 | | 15 | Opamp | A single-stage telescopic cascode opamp with two outputs (4 nmos as cascode input pair, 4 pmos as cascode loads, and 1 tail current) | 41.62 | -135.67 | 2 | 2 | non-inverting, inverting | 20 | 21 | 22 | Now, you need to design [TASK]. Please maximize the design success rate, thus the circuit with two inputs and the highest possible gain has greater flexibility. Please choose the subcircuits from the above table you will use and make the number of chosen subcircuits as few as possible. 23 | 24 | Please give out the IDs of the subcircuits that you choose, and enumerate them in a Python list like ```[0]```. -------------------------------------------------------------------------------- /sample_design/p1.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('Single-Stage Common-Source Amplifier') 4 | # Define the NMOS transistor model 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | # Power Supply for the power and input signal 7 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 8 | circuit.V('in', 'Vin', circuit.gnd, 1.5) 9 | # Single-Stage Common-Source Amplifier with Resistive Load 10 | circuit.MOSFET('1', 'Vout', 'Vin', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 11 | circuit.R('1', 'Vout', 'Vdd', 1@u_kΩ) 12 | # Analysis Part 13 | simulator = circuit.simulator() 14 | analysis = simulator.operating_point() 15 | -------------------------------------------------------------------------------- /sample_design/p10.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('Single-Stage Amplifier with PMOS Diode-Connected Load') 4 | # Define the MOSFET models 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | circuit.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 7 | # Power Supplies for the power and input signal 8 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 9 | circuit.V('in', 'Vin', circuit.gnd, 2.5) 10 | # Amplifier Stage: Common-Source with PMOS Diode-Connected Load 11 | # parameters: name, drain, gate, source, bulk, model, w, l 12 | circuit.MOSFET('1', 'Vout', 'Vin', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 13 | circuit.MOSFET('2', 'Vout', 'Vout', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 14 | # Analysis Part 15 | simulator = circuit.simulator() 16 | analysis = simulator.operating_point() 17 | -------------------------------------------------------------------------------- /sample_design/p11.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('Differential OpAmp with Active Load') 4 | # Define the MOSFET models 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | circuit.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 7 | # Power Supplies 8 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 9 | circuit.V('bias', 'Vbias', circuit.gnd, 1.5) # Bias voltage for the tail current source M3 10 | # Input Voltage Sources for Differential Inputs 11 | circuit.V('inp', 'Vinp', circuit.gnd, 2.5) 12 | circuit.V('inn', 'Vinn', circuit.gnd, 2.5) 13 | # Differential Pair and Tail Current Source 14 | circuit.MOSFET('1', 'Voutp', 'Vinp', 'Source3', 'Source3', model='nmos_model', w=50e-6, l=1e-6) 15 | circuit.MOSFET('2', 'Vout', 'Vinn', 'Source3', 'Source3', model='nmos_model', w=50e-6, l=1e-6) 16 | circuit.MOSFET('3', 'Source3', 'Vbias', circuit.gnd, circuit.gnd, model='nmos_model', w=100e-6, l=1e-6) 17 | # Active Current Mirror Load 18 | circuit.MOSFET('4', 'Voutp', 'Voutp', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 19 | circuit.MOSFET('5', 'Vout', 'Voutp', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 20 | # Analysis Part 21 | simulator = circuit.simulator() 22 | analysis = simulator.operating_point() 23 | -------------------------------------------------------------------------------- /sample_design/p12.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('Cascode Current Mirror') 4 | # Define the MOSFET models 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | # Power Supply 7 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 8 | # Reference Current Source 9 | circuit.I('ref', 'Vdd', 'Iref', 1@u_mA) # 1mA reference current 10 | # Input Side (Reference Side) 11 | circuit.MOSFET('1', 'Iref', 'Iref', 'Source2', 'Source2', model='nmos_model', w=50e-6, l=1e-6) # Diode-connected M1 12 | circuit.MOSFET('2', 'Source2', 'Iref', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) # M2 13 | # Output Side 14 | circuit.MOSFET('3', 'Iout', 'Iref', 'Source4', 'Source4', model='nmos_model', w=50e-6, l=1e-6) # M3 15 | circuit.MOSFET('4', 'Source4', 'Iref', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) # M4 16 | # Load Resistor 17 | circuit.R('1', 'Iout', 'Vdd', 1@u_kΩ) 18 | # Analysis Part 19 | simulator = circuit.simulator() 20 | analysis = simulator.operating_point() 21 | -------------------------------------------------------------------------------- /sample_design/p13.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('Single-Stage Differential Common-Source Opamp') 4 | # Define the MOSFET models 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | # Power Supplies for the power and input signal 7 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 8 | circuit.V('bias', 'Vbias', circuit.gnd, 1.0) # 1V input for bias voltage (= V_th + 0.5 = 0.5 + 0.5 = 1.0) 9 | circuit.V('inp', 'Vinp', circuit.gnd, 0.93) 10 | circuit.V('inn', 'Vinn', circuit.gnd, 0.93) 11 | # Differential Pair: M1 and M2 12 | circuit.MOSFET('1', 'Drain1', 'Vinp', 'SourceCommon', 'SourceCommon', model='nmos_model', w=50e-6, l=1e-6) 13 | circuit.MOSFET('2', 'Drain2', 'Vinn', 'SourceCommon', 'SourceCommon', model='nmos_model', w=50e-6, l=1e-6) 14 | # Tail Current Source: M3 15 | circuit.MOSFET('3', 'SourceCommon', 'Vbias', circuit.gnd, circuit.gnd, model='nmos_model', w=100e-6, l=1e-6) 16 | # Load Resistors 17 | circuit.R('1', 'Drain1', 'Vdd', 10@u_kΩ) # Load resistor for M1 18 | circuit.R('2', 'Drain2', 'Vdd', 10@u_kΩ) # Load resistor for M2 19 | # Output 20 | circuit.R('load', 'Vout', 'Drain1', 1@u_Ω) # Ideal wire for output node 21 | # Analysis Part 22 | simulator = circuit.simulator() 23 | analysis = simulator.operating_point() 24 | -------------------------------------------------------------------------------- /sample_design/p14.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('Two-Stage Differential Opamp') 4 | # Define the MOSFET models 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | circuit.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 7 | # Power Supply 8 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 9 | circuit.V('bias1', 'Vbias1', circuit.gnd, 1.0) # Bias voltage for tail current source 10 | circuit.V('bias2', 'Vbias2', circuit.gnd, 4.0) # Bias voltage for first active load 11 | circuit.V('bias3', 'Vbias3', circuit.gnd, 4.0) # Bias voltage for second active load 12 | # Differential Input 13 | circuit.V('inp', 'Vinp', circuit.gnd, 1.25) 14 | circuit.V('inn', 'Vinn', circuit.gnd, 1.25) 15 | # First Stage: Differential Pair with Active Load and Tail Current Source 16 | circuit.MOSFET('1', 'Drain1', 'Vinp', 'Source1', 'Source1', model='nmos_model', w=50e-6, l=1e-6) 17 | circuit.MOSFET('2', 'Drain2', 'Vinn', 'Source1', 'Source1', model='nmos_model', w=50e-6, l=1e-6) 18 | circuit.MOSFET('5', 'Source1', 'Vbias1', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 19 | circuit.MOSFET('6', 'Drain1', 'Drain1', 'Vdd', 'Vdd', model='pmos_model', w=50e-6, l=1e-6) 20 | circuit.MOSFET('7', 'Drain2', 'Drain1', 'Vdd', 'Vdd', model='pmos_model', w=50e-6, l=1e-6) 21 | # Second Stage: Common-Source Amplifier with Active Load 22 | circuit.MOSFET('3', 'Voutp', 'Drain1', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 23 | circuit.MOSFET('8', 'Voutp', 'Vbias2', 'Vdd', 'Vdd', model='pmos_model', w=50e-6, l=1e-6) 24 | circuit.MOSFET('4', 'Vout', 'Drain2', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 25 | circuit.MOSFET('9', 'Vout', 'Vbias3', 'Vdd', 'Vdd', model='pmos_model', w=50e-6, l=1e-6) 26 | # Analysis Part 27 | simulator = circuit.simulator() 28 | analysis = simulator.operating_point() 29 | -------------------------------------------------------------------------------- /sample_design/p15.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('Telescopic Cascode Opamp') 4 | # Define the MOSFET models 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | circuit.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 7 | # Power Supply 8 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 9 | # Bias Voltages 10 | circuit.V('bias1', 'Vbias1', circuit.gnd, 1.5) # Bias voltage for NMOS cascode 11 | circuit.V('bias2', 'Vbias2', circuit.gnd, 3.5) # Bias voltage for PMOS cascode load 12 | circuit.V('bias3', 'Vbias3', circuit.gnd, 4.0) # Additional bias for PMOS cascode load 13 | circuit.V('bias4', 'Vbias4', circuit.gnd, 1.0) # Bias voltage for tail current source 14 | # Input Signals 15 | circuit.V('inp', 'Vinp', circuit.gnd, 1.48) 16 | circuit.V('inn', 'Vinn', circuit.gnd, 1.48) 17 | # Input Differential Pair 18 | circuit.MOSFET('1', 'Drain1', 'Vinp', 'Source1', 'Source1', model='nmos_model', w=50e-6, l=1e-6) 19 | circuit.MOSFET('2', 'Drain2', 'Vinn', 'Source1', 'Source1', model='nmos_model', w=50e-6, l=1e-6) 20 | # Cascode Devices 21 | circuit.MOSFET('3', 'Voutp', 'Vbias1', 'Drain1', 'Drain1', model='nmos_model', w=50e-6, l=1e-6) 22 | circuit.MOSFET('4', 'Vout', 'Vbias1', 'Drain2', 'Drain2', model='nmos_model', w=50e-6, l=1e-6) 23 | # Cascode Loads 24 | circuit.MOSFET('5', 'Voutp', 'Vbias2', 'Source3', 'Source3', model='pmos_model', w=100e-6, l=1e-6) 25 | circuit.MOSFET('6', 'Vout', 'Vbias2', 'Source4', 'Source4', model='pmos_model', w=100e-6, l=1e-6) 26 | # Additional Cascode Loads 27 | circuit.MOSFET('7', 'Source3', 'Vbias3', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 28 | circuit.MOSFET('8', 'Source4', 'Vbias3', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 29 | # Tail Current Source 30 | circuit.MOSFET('9', 'Source1', 'Vbias4', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 31 | # Analysis Part 32 | simulator = circuit.simulator() 33 | analysis = simulator.operating_point() 34 | -------------------------------------------------------------------------------- /sample_design/p16.py: -------------------------------------------------------------------------------- 1 | import math 2 | from PySpice.Spice.Netlist import Circuit 3 | from PySpice.Unit import * 4 | from p_lib import * 5 | circuit = Circuit('RC Phase-Shift Oscillator') 6 | # Define the DC operating voltage 7 | Vdc = 2.5 @ u_V 8 | # Add a DC voltage source for the operating point 9 | circuit.V(1, 'Vdc', circuit.gnd, Vdc) 10 | # Declare the subcircuit for the op-amp 11 | circuit.subcircuit(SingleStageOpamp()) 12 | # Create the RC phase-shift network 13 | R1 = 10 @ u_kΩ 14 | C1 = 10 @ u_nF 15 | R2 = 10 @ u_kΩ 16 | C2 = 10 @ u_nF 17 | R3 = 10 @ u_kΩ 18 | C3 = 10 @ u_nF 19 | # Connect the RC network 20 | circuit.R(1, 'Vout', 'node1', R1) 21 | circuit.C(1, 'node1', 'node2', C1) 22 | circuit.R(2, 'node2', 'node3', R2) 23 | circuit.C(2, 'node3', 'node4', C2) 24 | circuit.R(3, 'node4', 'node5', R3) 25 | circuit.C(3, 'node5', 'Vdc', C3) 26 | # Connect node2 to ground through a resistor to avoid floating node 27 | circuit.R(5, 'node2', circuit.gnd, 10 @ u_kΩ) 28 | # Connect the op-amp 29 | circuit.X('op', 'SingleStageOpamp', 'node5', 'Vdc', 'Vout') 30 | # Connect the feedback from the output to the input of the RC network 31 | circuit.R(4, 'node1', 'Vdc', 10 @ u_kΩ) 32 | # Create a simulator instance 33 | simulator = circuit.simulator() 34 | -------------------------------------------------------------------------------- /sample_design/p17.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | from subcircuits.diffop import * 4 | 5 | 6 | circuit = Circuit('Wien Bridge Oscillator') 7 | 8 | # Declare and instantiate the opamp 9 | circuit.subcircuit(SingleStageOpamp()) 10 | circuit.X('op1', 'SingleStageOpamp', 'Vinp', 'Vinn', 'Vout') 11 | 12 | # Component values 13 | R1 = 10@u_kΩ 14 | R2 = 10@u_kΩ 15 | C1 = 10@u_nF 16 | C2 = 10@u_nF 17 | 18 | # Wien Bridge network elements 19 | circuit.R('1', 'Vout', 'n1', R1) # R1 from Vout to node n1 20 | circuit.C('1', 'n1', 'Vinp', C1) # C1 from n1 to inverting input of opamp 21 | 22 | circuit.R('2', 'Vinp', 'Vbias', R2) # R2 from node n1 back to Vout, creating a series feedback 23 | circuit.C('2', 'Vinp', 'Vbias', C2) # C2 from Vout to ground, parallel to R2 24 | 25 | circuit.R('f', 'Vout', 'Vinn', R1) 26 | circuit.R('b', 'Vinn', 'Vbias', R1/10.0) 27 | 28 | # Non-inverting input setup 29 | circuit.V('bias', 'Vbias', circuit.gnd, 2.5@u_V) # Bias voltage for the non-inverting input 30 | 31 | simulator = circuit.simulator() 32 | analysis = simulator.operating_point() 33 | -------------------------------------------------------------------------------- /sample_design/p18.py: -------------------------------------------------------------------------------- 1 | import math 2 | from PySpice.Spice.Netlist import Circuit 3 | from PySpice.Unit import * 4 | from subcircuits.diffop import * 5 | circuit = Circuit('Opamp Integrator') 6 | # Define the MOSFET models 7 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 8 | circuit.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 9 | # Declare the subcircuit for the opamp 10 | circuit.subcircuit(SingleStageOpamp()) 11 | # Create a subcircuit instance for the opamp 12 | circuit.X('opamp', 'SingleStageOpamp', 'Vinp', 'Vinn', 'Vout') 13 | # Define the input voltage source 14 | circuit.V('input', 'Vin', circuit.gnd, 2.5@u_V) 15 | # Define the resistor R1 and capacitor Cf 16 | R1 = 1@u_kΩ # 1k ohm resistor 17 | Cf = 1@u_uF # 1uF capacitor 18 | # Connect the resistor R1 between the input voltage source and the inverting input of the opamp 19 | circuit.R('R1', 'Vin', 'Vinn', R1) 20 | # Connect the capacitor Cf between the inverting input and the output of the opamp 21 | circuit.C('Cf', 'Vinn', 'Vout', Cf) 22 | # Connect the non-inverting input of the opamp to a reference voltage (2.5 V, same as the operating point) 23 | circuit.V('ref', 'Vinp', circuit.gnd, 2.5@u_V) 24 | simulator = circuit.simulator() 25 | analysis = simulator.operating_point() 26 | -------------------------------------------------------------------------------- /sample_design/p19.py: -------------------------------------------------------------------------------- 1 | import math 2 | from PySpice.Spice.Netlist import Circuit 3 | from PySpice.Unit import * 4 | from subcircuits.diffop import * 5 | circuit = Circuit('Opamp Differentiator') 6 | # Define the MOSFET models 7 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 8 | circuit.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 9 | # Input voltage source 10 | circuit.V('input', 'Vin', circuit.gnd, 2.5@u_V) 11 | # DC operating voltage for the non-inverting input 12 | circuit.V('dc_bias', 'Vdc', circuit.gnd, 2.5@u_V) 13 | # Declare and use the SingleStageOpamp subcircuit 14 | circuit.subcircuit(SingleStageOpamp()) 15 | circuit.X('op', 'SingleStageOpamp', 'Vdc', 'n1', 'Vout') 16 | # Components for differentiator 17 | Rf = 10@u_kΩ 18 | C1 = 10@u_nF 19 | # Connect the components 20 | circuit.R('f', 'Vout', 'n1', Rf) 21 | circuit.C('1', 'n1', 'Vin', C1) 22 | simulator = circuit.simulator() 23 | analysis = simulator.operating_point() 24 | -------------------------------------------------------------------------------- /sample_design/p2.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('Three-Stage Amplifier') 4 | # Define the MOSFET models 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | # Power Supplies for the power and input signal 7 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 8 | circuit.V('in', 'Vin', circuit.gnd, 1.65) 9 | # First Stage: Common-Source with Resistor Load 10 | circuit.MOSFET('1', 'Drain1', 'Vin', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 11 | circuit.R('1', 'Drain1', 'Vdd', 1@u_kΩ) 12 | # Second Stage: Common-Source with Resistor Load 13 | circuit.MOSFET('2', 'Drain2', 'Drain1', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 14 | circuit.R('2', 'Drain2', 'Vdd', 1@u_kΩ) 15 | # Third Stage: Common-Source with Resistor Load 16 | circuit.MOSFET('3', 'Vout', 'Drain2', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 17 | circuit.R('3', 'Vout', 'Vdd', 1@u_kΩ) 18 | # Analysis Part 19 | simulator = circuit.simulator() 20 | analysis = simulator.operating_point() 21 | -------------------------------------------------------------------------------- /sample_design/p20.py: -------------------------------------------------------------------------------- 1 | import math 2 | from PySpice.Spice.Netlist import Circuit 3 | from PySpice.Unit import * 4 | from subcircuits.diffop import * 5 | circuit = Circuit('Opamp Adder') 6 | # Define the input voltages 7 | circuit.V('input1', 'Vin1', circuit.gnd, 1@u_V) 8 | circuit.V('input2', 'Vin2', circuit.gnd, 1@u_V) 9 | # Declare the subcircuit 10 | circuit.subcircuit(SingleStageOpamp()) 11 | # Create a subcircuit instance 12 | circuit.X('opamp', 'SingleStageOpamp', 'Vinp', 'Vinn', 'Vout') 13 | # Resistors for the inverting summing amplifier configuration 14 | R1 = 10@u_kΩ 15 | R2 = 10@u_kΩ 16 | Rf = 10@u_kΩ 17 | # Connect the resistors 18 | circuit.R(1, 'Vin1', 'Vinn', R1) 19 | circuit.R(2, 'Vin2', 'Vinn', R2) 20 | circuit.R(3, 'Vinn', 'Vout', Rf) 21 | # Bias the non-inverting input to 2.5V 22 | circuit.V('bias', 'Vinp', circuit.gnd, 2.5@u_V) 23 | # Ground the inverting input through a resistor to set the operating point 24 | circuit.R('ground', 'Vinn', circuit.gnd, 1@u_MΩ) 25 | simulator = circuit.simulator() 26 | analysis = simulator.operating_point() 27 | -------------------------------------------------------------------------------- /sample_design/p21.py: -------------------------------------------------------------------------------- 1 | import math 2 | from PySpice.Spice.Netlist import Circuit 3 | from PySpice.Unit import * 4 | from subcircuits.diffop import * 5 | circuit = Circuit('Opamp Subtractor') 6 | # Define the MOSFET models 7 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 8 | circuit.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 9 | # Define input voltages 10 | circuit.V('input1', 'Vin1', circuit.gnd, 2.75@u_V) 11 | circuit.V('input2', 'Vin2', circuit.gnd, 5.00@u_V) 12 | # Declare the subcircuit 13 | circuit.subcircuit(SingleStageOpamp()) 14 | # Resistor values 15 | R1 = 10@u_kΩ 16 | R2 = 10@u_kΩ 17 | # Create the subtractor circuit using the opamp and resistors 18 | circuit.R('1', 'Vin1', 'Vinn', R1) 19 | circuit.R('2', 'Vin2', 'Vinp', R1) 20 | circuit.R('3', 'Vinn', 'Vout', R2) 21 | circuit.R('4', 'Vinp', circuit.gnd, R2) 22 | # Create a subcircuit instance 23 | circuit.X('opamp', 'SingleStageOpamp', 'Vinp', 'Vinn', 'Vout') 24 | simulator = circuit.simulator() 25 | analysis = simulator.operating_point() 26 | -------------------------------------------------------------------------------- /sample_design/p22.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | from subcircuits.diffop import * 4 | 5 | circuit = Circuit('Schmitt Trigger') 6 | 7 | # Define the MOSFET models 8 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 9 | circuit.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 10 | 11 | # Set the DC supply voltage 12 | Vdd = 5.0@u_V 13 | 14 | # Define the DC operating voltage for Vinp/Vinn 15 | Vop = 2.5@u_V 16 | 17 | # Declare the subcircuit 18 | circuit.subcircuit(SingleStageOpamp()) 19 | 20 | # Create a subcircuit instance 21 | circuit.X('opamp', 'SingleStageOpamp', 'non_inverting', 'inverting', 'Vout') 22 | 23 | # Define feedback resistors for hysteresis 24 | Rf = 20@u_kΩ # Feedback resistor 25 | R1 = 10@u_kΩ # Input resistor 26 | 27 | # Connect the non-inverting input to the output through the feedback resistor 28 | circuit.R('f', 'non_inverting', 'Vout', Rf) 29 | # Connect a resistor from the input to the non-inverting input 30 | circuit.R('1', 'Vin', 'non_inverting', R1) 31 | 32 | # Connect the inverting input to ground through a resistor to set the reference voltage 33 | circuit.R('2', 'inverting', circuit.gnd, R1) 34 | 35 | # Connect a resistor from Vdd to the inverting input to form a voltage divider with R2 36 | # This sets the inverting input to Vdd/2 when the output is at Vdd (positive feedback condition) 37 | circuit.R('3', 'Vdd', 'inverting', R1) 38 | circuit.V('dd', 'Vdd', circuit.gnd, Vdd) 39 | circuit.V('in', 'Vin', circuit.gnd, 1.0@u_V) 40 | 41 | # Finalize the Circuit 42 | simulator = circuit.simulator() 43 | analysis = simulator.operating_point() -------------------------------------------------------------------------------- /sample_design/p23.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit, SubCircuit, SubCircuitFactory 2 | from PySpice.Unit import * 3 | 4 | class OpAmp(SubCircuitFactory): 5 | __name__ = 'op_amp' 6 | __nodes__ = ('vdd', 'vss', 'in_p', 'in_n', 'vout') 7 | def __init__(self, kp, vto): 8 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 9 | # super.__init__() 10 | 11 | self.model('nmos_m', 'nmos', level=1, kp=kp, vto=vto) 12 | self.model('pmos_m', 'pmos', level=1, kp=kp, vto=-vto) 13 | 14 | self.M(1, 'v1', 'v2', 'vdd', 'vdd', model='pmos_m') 15 | self.M(2, 'v2', 'v2', 'vdd', 'vdd', model='pmos_m') 16 | self.M(3, 'vout', 'v2', 'vdd', 'vdd', model='pmos_m') 17 | self.M(4, 'v3', 'in_n', 'v1', 'vdd', model='pmos_m') 18 | self.M(5, 'v4', 'in_p', 'v1', 'vdd', model='pmos_m') 19 | 20 | self.M(6, 'v3', 'v3', 'vss', 'vss', model='nmos_m') 21 | self.M(7, 'v4', 'v3', 'vss', 'vss', model='nmos_m') 22 | self.M(8, 'v2', 'vdd', 'vss', 'vss', model='nmos_m') 23 | self.M(9, 'vout', 'v4', 'vss', 'vss', model='nmos_m') 24 | 25 | circuit = Circuit('Voltage Controlled Oscillator with two OP Amps') 26 | circuit.model('nmos_model', 'nmos', level=1, kp=1000e-6, vto=0.4) 27 | circuit.model('pmos_model', 'pmos', level=1, kp=400e-6, vto=-0.4) 28 | 29 | circuit.V('VDD', 'vdd', circuit.gnd, 1@u_V) 30 | circuit.V('in', 'vin', circuit.gnd, 0.7@u_V) 31 | 32 | R1 = 15@u_kΩ 33 | R2 = 30@u_kΩ 34 | R3 = 1@u_kΩ 35 | R4 = 30@u_kΩ 36 | R5 = 3@u_kΩ 37 | R6 = 30@u_kΩ 38 | R7 = 30@u_kΩ 39 | C1 = 5e-1@u_nF 40 | 41 | opamp_kp = 400e-6; opamp_vto = 0.4 42 | 43 | circuit.R(1, 'vin', 'op1_n', R1) 44 | circuit.R(2, 'vin', 'op1_p', R2) 45 | circuit.R(3, 'nmos_g', 'vout', R3) 46 | circuit.R(4, 'op1_p', circuit.gnd, R4) 47 | circuit.R(5, 'nmos_d', 'op1_n', R5) 48 | circuit.R(6, 'op2_p', circuit.gnd, R6) 49 | circuit.R(7, 'op2_p', 'vout', R7) 50 | 51 | circuit.C(1, 'op1_n', 'vout_1', C1) 52 | 53 | circuit.M(1, 'nmos_d', 'nmos_g', circuit.gnd, circuit.gnd, model='nmos_model') 54 | 55 | circuit.subcircuit(OpAmp(2*opamp_kp, opamp_vto)) 56 | circuit.X('1', 'op_amp', 'vdd', circuit.gnd, 'op1_p', 'op1_n', 'vout_1') 57 | 58 | circuit.subcircuit(OpAmp(0.7*opamp_kp, opamp_vto)) 59 | circuit.X('2', 'op_amp', 'vdd', circuit.gnd, 'op2_p', 'vout_1', 'vout') 60 | 61 | simulator = circuit.simulator() -------------------------------------------------------------------------------- /sample_design/p24.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit, SubCircuit, SubCircuitFactory 2 | from PySpice.Unit import * 3 | 4 | from subcircuits import charge_pump, divider, loop_filter, pfd, ring_vco 5 | 6 | ###### Netlist ####### 7 | circuit = Circuit('Phase Locked Loop') 8 | circuit.model('nmos_model', 'nmos', level=1, kp=400e-6, vto=0.4) 9 | circuit.model('pmos_model', 'pmos', level=1, kp=400e-6, vto=-0.4) 10 | 11 | class PLL(SubCircuitFactory): 12 | __name__ = 'pll' 13 | __nodes__ = ('vdd', 'vss', 'clk_ref', 'UP', 'DN', 'vctrl', 14 | 'clk_p', 'clk_n', 'clk_p_45', 'clk_n_45', 15 | 'clk_p_90', 'clk_n_90', 'clk_p_135', 'clk_n_135') 16 | def __init__(self, kp, vto): 17 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 18 | # super.__init__() 19 | self.model('nmos_m', 'nmos', level=1, kp=kp, vto=vto) 20 | self.model('pmos_m', 'pmos', level=1, kp=kp, vto=-vto) 21 | 22 | self.subcircuit(pfd.PFD(kp=5*kp, vto=vto)) 23 | self.X('0', 'pfd', 'vdd', 'vss', 'clk_ref', 'clk_p', 'UP', 'DN') 24 | 25 | self.subcircuit(charge_pump.ChargePump(kp=3*kp, vto=vto)) 26 | self.X('1', 'charge_pump', 'vdd', 'vss', 'UP', 'DN', 'vctrl') 27 | 28 | self.subcircuit(loop_filter.LF_t2o3(R1=0.1@u_kΩ, R2=3@u_kΩ, 29 | C1=50@u_pF, C2=0.5@u_pF)) 30 | self.X('2', 'loop_filter', 'vss', 'vctrl', 'vctrl_delay') 31 | 32 | self.subcircuit(ring_vco.RingVCO(kp=kp, vto=vto)) 33 | self.X('3', 'ring_vco', 'vdd', 'vss', 'vctrl_delay', 'clk_p', 'clk_n', 34 | 'clk_p_45', 'clk_n_45', 35 | 'clk_p_90', 'clk_n_90', 36 | 'clk_p_135', 'clk_n_135') 37 | 38 | 39 | 40 | circuit.V('VDD', 'vdd', circuit.gnd, 1@u_V) 41 | 42 | 43 | 44 | circuit.subcircuit(PLL(400e-6, 0.4)) 45 | circuit.X('0', 'pll', 'vdd', circuit.gnd, 'clk_ref', 'UP', 'DN', 'vctrl', 46 | 'clk_p', 'clk_n', 'clk_p_45', 'clk_n_45', 47 | 'clk_p_90', 'clk_n_90', 'clk_p_135', 'clk_n_135') 48 | 49 | ##### Simulation ##### 50 | simulator = circuit.simulator() 51 | -------------------------------------------------------------------------------- /sample_design/p3.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('Common-Drain Amplifier') 4 | # Define the MOSFET model 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | # Power Supply 7 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 8 | # Input Signal Source 9 | circuit.V('in', 'Vin', circuit.gnd, 4.0) 10 | # NMOS Transistor - Common Drain Configuration 11 | circuit.MOSFET('1', 'Vdd', 'Vin', 'Vout', circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 12 | # Load Resistor 13 | circuit.R('1', 'Vout', circuit.gnd, 1@u_kΩ) 14 | # Analysis Part 15 | simulator = circuit.simulator() 16 | analysis = simulator.operating_point() 17 | -------------------------------------------------------------------------------- /sample_design/p4.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('Single-Stage Common-Gate Amplifier') 4 | # Define the MOSFET model 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | # Power Supply and Bias Voltage 7 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 8 | circuit.V('bias', 'Vbias', circuit.gnd, 1.5) # Bias voltage for the gate of M1, ensuring it's above threshold 9 | # Input Signal 10 | circuit.V('in', 'Vin', circuit.gnd, 0.0) 11 | # NMOS Transistor 12 | circuit.MOSFET('1', 'Vout', 'Vbias', 'Vin', 'Vin', model='nmos_model', w=50e-6, l=1e-6) 13 | # Load Resistor 14 | circuit.R('1', 'Vout', 'Vdd', 1@u_kΩ) 15 | # Analysis Part 16 | simulator = circuit.simulator() 17 | analysis = simulator.operating_point() 18 | -------------------------------------------------------------------------------- /sample_design/p5.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('Single-Stage Cascode Amplifier') 4 | # Define the MOSFET models 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | # Power Supplies for the power and input signal 7 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 8 | circuit.V('in', 'Vin', circuit.gnd, 1.5) 9 | circuit.V('bias', 'Vbias', circuit.gnd, 2.5) # Bias voltage for M2, ensuring it's in saturation 10 | # First Stage: Common-Source with NMOS M1 11 | circuit.MOSFET('1', 'Drain1', 'Vin', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 12 | # Cascode Stage with NMOS M2 13 | circuit.MOSFET('2', 'Vout', 'Vbias', 'Drain1', circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 14 | # Load Resistor 15 | circuit.R('1', 'Vout', 'Vdd', 1@u_kΩ) 16 | # Analysis Part 17 | simulator = circuit.simulator() 18 | analysis = simulator.operating_point() 19 | -------------------------------------------------------------------------------- /sample_design/p6.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('NMOS Inverter') 4 | # Define the NMOS model 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | # Power Supply for the circuit 7 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 8 | # Input signal source 9 | circuit.V('in', 'Vin', circuit.gnd, 1.0) # 1V input for bias voltage 10 | # NMOS Inverter Configuration 11 | circuit.MOSFET('1', 'Vout', 'Vin', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 12 | # Load Resistor 13 | circuit.R('1', 'Vout', 'Vdd', 1@u_kΩ) 14 | # Analysis Part 15 | simulator = circuit.simulator() 16 | analysis = simulator.operating_point() 17 | -------------------------------------------------------------------------------- /sample_design/p7.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('CMOS Inverter') 4 | # Define the MOSFET models 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | circuit.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 7 | # Power Supply 8 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 9 | # Input Signal 10 | circuit.V('in', 'Vin', circuit.gnd, 2.5) # Midpoint biasing for switching 11 | # NMOS and PMOS for Inverter 12 | circuit.MOSFET('1', 'Vout', 'Vin', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 13 | circuit.MOSFET('2', 'Vout', 'Vin', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 14 | # Analysis Part 15 | simulator = circuit.simulator() 16 | analysis = simulator.operating_point() 17 | -------------------------------------------------------------------------------- /sample_design/p8.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('NMOS Constant Current Source with Resistive Load') 4 | # Define the MOSFET model 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | # Power Supplies for the power and bias voltage 7 | circuit.V('dd', 'Vdd', circuit.gnd, 5.0) # 5V power supply 8 | circuit.V('bias', 'Vbias', circuit.gnd, 1.5) # Bias voltage (greater than V_th to ensure saturation) 9 | # NMOS Constant Current Source Setup 10 | circuit.M('1', 'Vout', 'Vbias', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 11 | # Load Resistor 12 | circuit.R('1', 'Vout', 'Vdd', 1@u_kΩ) # Resistor value as 1kΩ 13 | # Analysis Part 14 | simulator = circuit.simulator() 15 | analysis = simulator.operating_point() 16 | -------------------------------------------------------------------------------- /sample_design/p9.py: -------------------------------------------------------------------------------- 1 | from PySpice.Spice.Netlist import Circuit 2 | from PySpice.Unit import * 3 | circuit = Circuit('Two-Stage Amplifier with Miller Compensation') 4 | # Define the MOSFET models 5 | circuit.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 6 | circuit.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 7 | # Power Supplies 8 | circuit.V('dd', 'Vdd', circuit.gnd, 5@u_V) # 5V power supply 9 | circuit.V('in', 'Vin', circuit.gnd, 1.0) 10 | circuit.V('bias1', 'Vbias1', circuit.gnd, 4@u_V) # Bias for M2 11 | circuit.V('bias2', 'Vbias2', circuit.gnd, 4@u_V) # Bias for M4 12 | # First Stage: Common-Source with Active Load 13 | circuit.MOSFET('1', 'Drain1', 'Vin', circuit.gnd, circuit.gnd, model='nmos_model', w=50e-6, l=1e-6) 14 | circuit.MOSFET('2', 'Drain1', 'Vbias1', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 15 | # Second Stage: Common-Source with Active Load 16 | circuit.MOSFET('3', 'Vout', 'Drain1', circuit.gnd, circuit.gnd, model='nmos_model', w=100e-6, l=1e-6) 17 | circuit.MOSFET('4', 'Vout', 'Vbias2', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 18 | # Miller Compensation Capacitor 19 | circuit.C('c', 'Drain1', 'Vout', 10@u_pF) 20 | # Analysis Part 21 | simulator = circuit.simulator() 22 | analysis = simulator.operating_point() 23 | -------------------------------------------------------------------------------- /sample_design/p_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class SingleStageOpamp(SubCircuitFactory): 5 | NAME = ('SingleStageOpamp') 6 | NODES = ('Vinp', 'Vinn', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET models 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | self.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 12 | # Power Supplies 13 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 14 | self.V('bias', 'Vbias', self.gnd, 1.5) # Bias voltage for the tail current source M3 15 | # Input Voltage Sources for Differential Inputs 16 | # Differential Pair and Tail Current Source 17 | self.MOSFET('1', 'Voutp', 'Vinp', 'Source3', 'Source3', model='nmos_model', w=50e-6, l=1e-6) 18 | self.MOSFET('2', 'Vout', 'Vinn', 'Source3', 'Source3', model='nmos_model', w=50e-6, l=1e-6) 19 | self.MOSFET('3', 'Source3', 'Vbias', self.gnd, self.gnd, model='nmos_model', w=100e-6, l=1e-6) 20 | # Active Current Mirror Load 21 | self.MOSFET('4', 'Voutp', 'Voutp', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 22 | self.MOSFET('5', 'Vout', 'Voutp', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 23 | -------------------------------------------------------------------------------- /sample_design/subcircuits/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laiyao1/AnalogCoder/6225b4925fcced0d08d3bd9ee4010b8649144b31/sample_design/subcircuits/__init__.py -------------------------------------------------------------------------------- /sample_design/subcircuits/charge_pump.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from PySpice.Spice.Netlist import Circuit, SubCircuit, SubCircuitFactory 3 | from PySpice.Unit import * 4 | 5 | from subcircuits import opamp, loop_filter, logic_gates 6 | 7 | 8 | ###### Netlist ####### 9 | circuit = Circuit('Charge Pump') 10 | circuit.model('nmos_model', 'nmos', level=1, kp=400e-6, vto=0.4) 11 | circuit.model('pmos_model', 'pmos', level=1, kp=400e-6, vto=-0.4) 12 | 13 | class ChargePump(SubCircuitFactory): 14 | __name__ = 'charge_pump' 15 | __nodes__ = ('vdd', 'vss', 'up', 'dn', 'vctrl') 16 | def __init__(self, kp, vto): 17 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 18 | # super.__init__() 19 | 20 | self.model('nmos_m', 'nmos', level=1, kp=kp, vto=vto) 21 | self.model('pmos_m', 'pmos', level=1, kp=kp, vto=-vto) 22 | self.model('nmos_m2', 'nmos', level=1, kp=kp*10, vto=vto) 23 | self.model('pmos_m2', 'pmos', level=1, kp=kp*10, vto=-vto) 24 | 25 | self.CurrentSource('1', 'vdd', 'vn1', 300@u_uA) 26 | 27 | self.subcircuit(logic_gates.INV(kp=kp, vto=vto)) 28 | self.X('0', 'inv', 'vdd', 'vss', 'dn', 'dn_inv') 29 | 30 | self.M(1, 'vp1', 'vp1', 'vdd', 'vdd', model='pmos_m2') 31 | # self.M(2, 'op1_p', 'vp2', 'vdd', 'vdd', model='pmos_m') 32 | # self.M(3, 'op2_p', 'vp1', 'vdd', 'vdd', model='pmos_m') 33 | self.M(4, 'm4_d', 'dn_inv', 'vdd', 'vdd', model='pmos_m') 34 | # self.M(5, 'm5_d', 'dn_inv', 'vdd', 'vdd', model='pmos_m') 35 | self.M(6, 'vctrl', 'vp1', 'm4_d', 'vdd', model='pmos_m') 36 | # self.M(7, 'vctrl', 'vp2', 'm5_d', 'vdd', model='pmos_m') 37 | 38 | # self.subcircuit(opamp.OpAmp(kp=kp, vto=0.4)) 39 | # self.X('1', 'op_amp', 'vdd', 'vss', 'op1_p', 'vctrl', 'vp2') 40 | 41 | # self.subcircuit(opamp.OpAmp(kp=kp, vto=0.4)) 42 | # self.X('2', 'op_amp', 'vdd', 'vss', 'op2_p', 'vctrl', 'vn2') 43 | 44 | self.M(8, 'vn1', 'vn1', 'vss', 'vss', model='nmos_m2') 45 | self.M(9, 'vp1', 'vn1', 'vss', 'vss', model='nmos_m2') 46 | # self.M(10, 'op1_p', 'vn1', 'vss', 'vss', model='nmos_m') 47 | # self.M(11, 'op2_p', 'vn2', 'vss', 'vss', model='nmos_m') 48 | self.M(12, 'm12_d', 'up', 'vss', 'vss', model='nmos_m') 49 | # self.M(13, 'm13_d', 'up', 'vss', 'vss', model='nmos_m') 50 | self.M(14, 'vctrl', 'vn1', 'm12_d', 'vss', model='nmos_m') 51 | # self.M(15, 'vctrl', 'vn2', 'm13_d', 'vss', model='nmos_m') 52 | 53 | 54 | 55 | circuit.V('VDD', 'vdd', circuit.gnd, 1@u_V) 56 | circuit.PulseVoltageSource('1', 'up', circuit.gnd, initial_value=0@u_V, pulsed_value=1@u_V, 57 | pulse_width=90@u_ns, period=200@u_ns, delay_time=10@u_ns, rise_time=0.2@u_ns, fall_time=0.2@u_ns) 58 | circuit.PulseVoltageSource('2', 'dn', circuit.gnd, initial_value=0@u_V, pulsed_value=1@u_V, 59 | pulse_width=30@u_ns, period=200@u_ns, delay_time=70@u_ns, rise_time=0.2@u_ns, fall_time=0.2@u_ns) 60 | 61 | circuit.subcircuit(ChargePump(400e-6, 0.4)) 62 | circuit.X('0', 'charge_pump', 'vdd', circuit.gnd, 'up', 'dn', 'vctrl') 63 | 64 | circuit.subcircuit(loop_filter.LF_t2o3(R1=1@u_kΩ, R2=1@u_kΩ, 65 | C1=30@u_pF, C2=5@u_pF)) 66 | circuit.X('1', 'loop_filter', circuit.gnd, 'vctrl') 67 | ###################### 68 | 69 | 70 | ##### Simulation ##### 71 | # simulator = circuit.simulator(temperature=25, nominal_temperature=25) 72 | # # simulator.initial_condition(vctrl=0.5@u_V, op1_p=0.5@u_V, op2_p=0.5@u_V) 73 | # analysis = simulator.transient(step_time=100@u_ns, end_time=1@u_us) 74 | 75 | # fig = plt.figure() 76 | # # plt.ylim((-0.02, 1.02)) 77 | # plt.plot(list(analysis.time), list(analysis["up"])) 78 | # plt.plot(list(analysis.time), list(analysis["dn"])) 79 | # plt.plot(list(analysis.time), list(analysis["vctrl"])) 80 | # # plt.plot(list(analysis.time), list(analysis["vn1"])) 81 | # # plt.plot(list(analysis.time), list(analysis["vp1"])) 82 | # plt.show() 83 | # fig.savefig("./outputs/charge_pump.png") 84 | # plt.close(fig) 85 | ###################### -------------------------------------------------------------------------------- /sample_design/subcircuits/circuit_imgs/RingVCO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laiyao1/AnalogCoder/6225b4925fcced0d08d3bd9ee4010b8649144b31/sample_design/subcircuits/circuit_imgs/RingVCO.png -------------------------------------------------------------------------------- /sample_design/subcircuits/circuit_imgs/cp_and_lf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laiyao1/AnalogCoder/6225b4925fcced0d08d3bd9ee4010b8649144b31/sample_design/subcircuits/circuit_imgs/cp_and_lf.png -------------------------------------------------------------------------------- /sample_design/subcircuits/circuit_imgs/divider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laiyao1/AnalogCoder/6225b4925fcced0d08d3bd9ee4010b8649144b31/sample_design/subcircuits/circuit_imgs/divider.png -------------------------------------------------------------------------------- /sample_design/subcircuits/circuit_imgs/opamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laiyao1/AnalogCoder/6225b4925fcced0d08d3bd9ee4010b8649144b31/sample_design/subcircuits/circuit_imgs/opamp.png -------------------------------------------------------------------------------- /sample_design/subcircuits/circuit_imgs/pfd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laiyao1/AnalogCoder/6225b4925fcced0d08d3bd9ee4010b8649144b31/sample_design/subcircuits/circuit_imgs/pfd.png -------------------------------------------------------------------------------- /sample_design/subcircuits/dff.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from PySpice.Spice.Netlist import Circuit, SubCircuit, SubCircuitFactory 3 | from PySpice.Unit import * 4 | 5 | from subcircuits import logic_gates 6 | 7 | 8 | ###### Netlist ####### 9 | circuit = Circuit('D Flip Flop') 10 | circuit.model('nmos_model', 'nmos', level=1, kp=400e-6, vto=0.4) 11 | circuit.model('pmos_model', 'pmos', level=1, kp=400e-6, vto=-0.4) 12 | 13 | class DFF(SubCircuitFactory): 14 | __name__ = 'd_flip_flop' 15 | __nodes__ = ('vdd', 'vss', 'D', 'CLK', 'Q') 16 | def __init__(self, kp, vto): 17 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 18 | # super.__init__() 19 | 20 | self.subcircuit(logic_gates.INV(kp=kp, vto=vto)) 21 | self.X('1', 'inv', 'vdd', 'vss', 'CLK', 'CLKB') 22 | 23 | self.subcircuit(logic_gates.TINV(kp=kp, vto=vto)) 24 | self.X('2', 'tinv', 'vdd', 'vss', 'D', 'CLKB', 'v1') 25 | 26 | self.subcircuit(logic_gates.INV(kp=kp, vto=vto)) 27 | self.X('3', 'inv', 'vdd', 'vss', 'v1', 'v1_b') 28 | 29 | self.subcircuit(logic_gates.TINV(kp=kp, vto=vto)) 30 | self.X('4', 'tinv', 'vdd', 'vss', 'v1_b', 'CLK', 'v1') 31 | 32 | self.subcircuit(logic_gates.TINV(kp=kp, vto=vto)) 33 | self.X('5', 'tinv', 'vdd', 'vss', 'v1_b', 'CLK', 'v2') 34 | 35 | self.subcircuit(logic_gates.INV(kp=kp, vto=vto)) 36 | self.X('6', 'inv', 'vdd', 'vss', 'v2', 'Q') 37 | 38 | self.subcircuit(logic_gates.TINV(kp=kp, vto=vto)) 39 | self.X('7', 'tinv', 'vdd', 'vss', 'Q', 'CLKB', 'v2') 40 | 41 | 42 | 43 | class DFF_RST(SubCircuitFactory): 44 | __name__ = 'd_flip_flop_with_rst' 45 | __nodes__ = ('vdd', 'vss', 'D', 'CLK', 'RSTB', 'Q') 46 | def __init__(self, kp, vto): 47 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 48 | # super.__init__() 49 | 50 | self.model('nmos_m', 'nmos', level=1, kp=kp, vto=vto) 51 | self.model('pmos_m', 'pmos', level=1, kp=kp, vto=-vto) 52 | 53 | self.subcircuit(logic_gates.INV(kp=kp, vto=vto)) 54 | self.X('0', 'inv', 'vdd', 'vss', 'CLK', 'CLKB') 55 | 56 | self.subcircuit(logic_gates.TINV(kp=kp, vto=vto)) 57 | self.X('1', 'tinv', 'vdd', 'vss', 'D', 'CLKB', 'v1') 58 | 59 | self.M(1, 'v1', 'RSTB', 'vdd', 'vdd', model='pmos_m') 60 | 61 | self.subcircuit(logic_gates.INV(kp=kp, vto=vto)) 62 | self.X('2', 'inv', 'vdd', 'vss', 'v1', 'v1_b') 63 | 64 | self.subcircuit(logic_gates.TINV(kp=kp, vto=vto)) 65 | self.X('3', 'tinv', 'vdd', 'vss', 'v1_b', 'CLK', 'v1') 66 | 67 | self.subcircuit(logic_gates.TINV(kp=kp, vto=vto)) 68 | self.X('4', 'tinv', 'vdd', 'vss', 'v1_b', 'CLK', 'v2') 69 | 70 | self.M(2, 'v2', 'RSTB', 'vdd', 'vdd', model='pmos_m') 71 | 72 | self.subcircuit(logic_gates.INV(kp=kp, vto=vto)) 73 | self.X('5', 'inv', 'vdd', 'vss', 'v2', 'Q') 74 | 75 | self.subcircuit(logic_gates.TINV(kp=kp, vto=vto)) 76 | self.X('6', 'tinv', 'vdd', 'vss', 'Q', 'CLKB', 'v2') 77 | 78 | 79 | 80 | 81 | circuit.V('VDD', 'vdd', circuit.gnd, 1@u_V) 82 | circuit.PulseVoltageSource('1', 'D', circuit.gnd, initial_value=0@u_V, pulsed_value=1@u_V, 83 | pulse_width=150@u_ns, period=300@u_ns, delay_time=30@u_ns, rise_time=0.2@u_ns, fall_time=0.2@u_ns) 84 | circuit.PulseVoltageSource('2', 'CLK', circuit.gnd, initial_value=0@u_V, pulsed_value=1@u_V, 85 | pulse_width=100@u_ns, period=200@u_ns, delay_time=100@u_ns, rise_time=0.2@u_ns, fall_time=0.2@u_ns) 86 | circuit.PulseVoltageSource('3', 'rstb', circuit.gnd, initial_value=0@u_V, pulsed_value=1@u_V, 87 | pulse_width=800@u_ns, period=850@u_ns, delay_time=10@u_ns, rise_time=0.2@u_ns, fall_time=0.2@u_ns) 88 | 89 | circuit.subcircuit(DFF_RST(400e-6, 0.4)) 90 | circuit.X('1', 'd_flip_flop_with_rst', 'vdd', circuit.gnd, 'D', 'CLK', 'rstb', 'Q') 91 | ###################### 92 | 93 | 94 | ##### Simulation ##### 95 | # simulator = circuit.simulator(temperature=25, nominal_temperature=25) 96 | # analysis = simulator.transient(step_time=10@u_ns, end_time=1500@u_ns) 97 | 98 | # fig = plt.figure() 99 | # plt.ylim((-0.2, 1.2)) 100 | # # plt.plot(list(analysis.time), list(analysis["D"])) 101 | # plt.plot(list(analysis.time), list(analysis["rstb"])) 102 | # # plt.plot(list(analysis.time), list(analysis["CLKB"])) 103 | # plt.plot(list(analysis.time), list(analysis["Q"])) 104 | # plt.show() 105 | # fig.savefig("./outputs/dff.png") 106 | # plt.close(fig) 107 | ###################### -------------------------------------------------------------------------------- /sample_design/subcircuits/diffop.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class SingleStageOpamp(SubCircuitFactory): 5 | NAME = ('SingleStageOpamp') 6 | NODES = ('Vinp', 'Vinn', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET models 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | self.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 12 | # Power Supplies 13 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 14 | self.V('bias', 'Vbias', self.gnd, 1.5) # Bias voltage for the tail current source M3 15 | # Input Voltage Sources for Differential Inputs 16 | # Differential Pair and Tail Current Source 17 | self.MOSFET('1', 'Voutp', 'Vinp', 'Source3', 'Source3', model='nmos_model', w=50e-6, l=1e-6) 18 | self.MOSFET('2', 'Vout', 'Vinn', 'Source3', 'Source3', model='nmos_model', w=50e-6, l=1e-6) 19 | self.MOSFET('3', 'Source3', 'Vbias', self.gnd, self.gnd, model='nmos_model', w=100e-6, l=1e-6) 20 | # Active Current Mirror Load 21 | self.MOSFET('4', 'Voutp', 'Voutp', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 22 | self.MOSFET('5', 'Vout', 'Voutp', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 23 | -------------------------------------------------------------------------------- /sample_design/subcircuits/divider.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from PySpice.Spice.Netlist import Circuit, SubCircuit, SubCircuitFactory 3 | from PySpice.Unit import * 4 | 5 | from subcircuits import dff 6 | 7 | ###### Netlist ####### 8 | circuit = Circuit('Frequency Divider') 9 | circuit.model('nmos_model', 'nmos', level=1, kp=400e-6, vto=0.4) 10 | circuit.model('pmos_model', 'pmos', level=1, kp=400e-6, vto=-0.4) 11 | 12 | class Divider_4(SubCircuitFactory): 13 | __name__ = 'divider_4' 14 | __nodes__ = ('vdd', 'vss', 'CLK', 'CLK_OUT') 15 | def __init__(self, kp, vto): 16 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 17 | # super.__init__() 18 | 19 | self.model('nmos_m', 'nmos', level=1, kp=kp, vto=vto) 20 | self.model('pmos_m', 'pmos', level=1, kp=kp, vto=-vto) 21 | 22 | self.subcircuit(dff.DFF(kp, vto)) 23 | self.X('1', 'd_flip_flop', 'vdd', 'vss', 'D1', 'CLK', 'Q1') 24 | 25 | self.M(1, 'D1', 'Q1', 'vdd', 'vdd', model='pmos_m') 26 | self.M(2, 'D1', 'Q1', 'vss', 'vss', model='nmos_m') 27 | 28 | self.subcircuit(dff.DFF(kp, vto)) 29 | self.X('2', 'd_flip_flop', 'vdd', 'vss', 'D2', 'Q1', 'Q2') 30 | 31 | self.M(3, 'D2', 'Q2', 'vdd', 'vdd', model='pmos_m') 32 | self.M(4, 'D2', 'Q2', 'vss', 'vss', model='nmos_m') 33 | 34 | self.M(5, 'CLK_OUT_B', 'Q2', 'vdd', 'vdd', model='pmos_m') 35 | self.M(6, 'CLK_OUT_B', 'Q2', 'vss', 'vss', model='nmos_m') 36 | self.M(7, 'CLK_OUT', 'CLK_OUT_B', 'vdd', 'vdd', model='pmos_m') 37 | self.M(8, 'CLK_OUT', 'CLK_OUT_B', 'vss', 'vss', model='nmos_m') 38 | 39 | 40 | 41 | circuit.V('VDD', 'vdd', circuit.gnd, 1@u_V) 42 | circuit.PulseVoltageSource('2', 'CLK', circuit.gnd, initial_value=0@u_V, pulsed_value=1@u_V, 43 | pulse_width=100@u_ns, period=200@u_ns, delay_time=100@u_ns, rise_time=0.2@u_ns, fall_time=0.2@u_ns) 44 | 45 | circuit.subcircuit(Divider_4(400e-6, 0.4)) 46 | circuit.X('1', 'divider_4', 'vdd', circuit.gnd, 'CLK', 'CLK_OUT') 47 | ###################### 48 | 49 | 50 | ##### Simulation ##### 51 | # simulator = circuit.simulator(temperature=25, nominal_temperature=25) 52 | # analysis = simulator.transient(step_time=10@u_ns, end_time=2500@u_ns) 53 | 54 | # fig = plt.figure() 55 | # plt.ylim((-0.2, 1.2)) 56 | # plt.plot(list(analysis.time), list(analysis["CLK"])) 57 | # plt.plot(list(analysis.time), list(analysis["CLK_OUT"])) 58 | # plt.show() 59 | # fig.savefig("./outputs/divider.png") 60 | # plt.close(fig) 61 | ###################### -------------------------------------------------------------------------------- /sample_design/subcircuits/logic_gates.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from PySpice.Spice.Netlist import Circuit, SubCircuit, SubCircuitFactory 3 | from PySpice.Unit import * 4 | 5 | ###### Netlist ####### 6 | circuit = Circuit('NAND Gate') 7 | circuit.model('nmos_model', 'nmos', level=1, kp=400e-6, vto=0.4) 8 | circuit.model('pmos_model', 'pmos', level=1, kp=400e-6, vto=-0.4) 9 | 10 | class INV(SubCircuitFactory): 11 | __name__ = 'inv' 12 | __nodes__ = ('vdd', 'vss', 'I', 'O_delay') 13 | def __init__(self, kp, vto): 14 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 15 | # super.__init__() 16 | 17 | self.model('nmos_m', 'nmos', level=1, kp=kp, vto=vto) 18 | self.model('pmos_m', 'pmos', level=1, kp=kp, vto=-vto) 19 | 20 | self.M(0, 'O', 'I', 'vdd', 'vdd', model='pmos_m') 21 | self.M(1, 'O', 'I', 'vss', 'vss', model='nmos_m') 22 | 23 | self.R(0, 'O', 'O_delay', 0.01@u_kΩ) 24 | self.C(0, 'O_delay', 'vss', 0.1@u_pF) 25 | 26 | 27 | class TINV(SubCircuitFactory): 28 | __name__ = 'tinv' 29 | __nodes__ = ('vdd', 'vss', 'I', 'EN', 'O_delay') 30 | def __init__(self, kp, vto): 31 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 32 | # super.__init__() 33 | 34 | self.model('nmos_m', 'nmos', level=1, kp=kp, vto=vto) 35 | self.model('pmos_m', 'pmos', level=1, kp=kp, vto=-vto) 36 | 37 | self.subcircuit(INV(kp, vto)) 38 | self.X('0', 'inv', 'vdd', 'vss', 'EN', 'ENB') 39 | 40 | self.M(0, 'vp', 'I', 'vdd', 'vdd', model='pmos_m') 41 | self.M(1, 'vn', 'I', 'vss', 'vss', model='nmos_m') 42 | self.M(2, 'O', 'ENB', 'vp', 'vdd', model='pmos_m') 43 | self.M(3, 'O', 'EN', 'vn', 'vss', model='nmos_m') 44 | 45 | self.R(0, 'O', 'O_delay', 0.02@u_kΩ) 46 | self.C(0, 'O_delay', 'vss', 0.2@u_pF) 47 | 48 | 49 | class NAND(SubCircuitFactory): 50 | __name__ = 'nand' 51 | __nodes__ = ('vdd', 'vss', 'A', 'B', 'O_delay') 52 | def __init__(self, kp, vto): 53 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 54 | # super.__init__() 55 | 56 | self.model('nmos_m', 'nmos', level=1, kp=kp, vto=vto) 57 | self.model('pmos_m', 'pmos', level=1, kp=kp, vto=-vto) 58 | 59 | self.M(0, 'O', 'A', 'vdd', 'vdd', model='pmos_m') 60 | self.M(1, 'O', 'B', 'vdd', 'vdd', model='pmos_m') 61 | self.M(2, 'O', 'A', 'v1', 'vss', model='nmos_m') 62 | self.M(3, 'v1', 'B', 'vss', 'vss', model='nmos_m') 63 | 64 | self.R(0, 'O', 'O_delay', 0.04@u_kΩ) 65 | self.C(0, 'O_delay', 'vss', 0.4@u_pF) 66 | 67 | 68 | circuit.V('VDD', 'vdd', circuit.gnd, 1@u_V) 69 | circuit.PulseVoltageSource('0', 'A', circuit.gnd, initial_value=0@u_V, pulsed_value=1@u_V, 70 | pulse_width=150@u_ns, period=300@u_ns, delay_time=30@u_ns, rise_time=0.2@u_ns, fall_time=0.2@u_ns) 71 | circuit.PulseVoltageSource('1', 'B', circuit.gnd, initial_value=0@u_V, pulsed_value=1@u_V, 72 | pulse_width=100@u_ns, period=200@u_ns, delay_time=100@u_ns, rise_time=0.2@u_ns, fall_time=0.2@u_ns) 73 | 74 | circuit.subcircuit(NAND(400e-6, 0.4)) 75 | circuit.X('0', 'nand', 'vdd', circuit.gnd, 'A', 'B', 'O') 76 | ###################### 77 | 78 | 79 | ##### Simulation ##### 80 | # simulator = circuit.simulator(temperature=25, nominal_temperature=25) 81 | # analysis = simulator.transient(step_time=10@u_ns, end_time=1500@u_ns) 82 | 83 | # fig = plt.figure() 84 | # plt.ylim((-0.2, 1.2)) 85 | # # plt.plot(list(analysis.time), list(analysis["A"])) 86 | # # plt.plot(list(analysis.time), list(analysis["B"])) 87 | # plt.plot(list(analysis.time), list(analysis["O"])) 88 | # plt.show() 89 | # fig.savefig("./outputs/nand.png") 90 | # plt.close(fig) 91 | ###################### -------------------------------------------------------------------------------- /sample_design/subcircuits/loop_filter.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from PySpice.Spice.Netlist import Circuit, SubCircuit, SubCircuitFactory 3 | from PySpice.Unit import * 4 | 5 | ###### Netlist ####### 6 | circuit = Circuit('Loop Filter') 7 | circuit.model('nmos_model', 'nmos', level=1, kp=400e-6, vto=0.4) 8 | circuit.model('pmos_model', 'pmos', level=1, kp=400e-6, vto=-0.4) 9 | 10 | class LF_t2o3(SubCircuitFactory): 11 | __name__ = 'loop_filter' 12 | __nodes__ = ('vss', 'i', 'o') 13 | def __init__(self, R1, R2, C1, C2): 14 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 15 | # super.__init__() 16 | 17 | self.R(0, 'i', 'o', R1) 18 | self.R(1, 'o', 'c1_up', R2) 19 | self.C(1, 'c1_up', 'vss', C1) 20 | self.C(2, 'o', 'vss', C2) 21 | ###################### -------------------------------------------------------------------------------- /sample_design/subcircuits/opamp.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from PySpice.Spice.Netlist import Circuit, SubCircuit, SubCircuitFactory 3 | from PySpice.Unit import * 4 | 5 | 6 | ###### Netlist ####### 7 | circuit = Circuit('OpAmp') 8 | circuit.model('nmos_model', 'nmos', level=1, kp=400e-6, vto=0.4) 9 | circuit.model('pmos_model', 'pmos', level=1, kp=400e-6, vto=-0.4) 10 | 11 | class OpAmp(SubCircuitFactory): 12 | __name__ = 'op_amp' 13 | __nodes__ = ('vdd', 'vss', 'in_p', 'in_n', 'vout_delay') 14 | def __init__(self, kp, vto): 15 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 16 | # super.__init__() 17 | 18 | self.model('nmos_m', 'nmos', level=1, kp=kp, vto=vto) 19 | self.model('pmos_m', 'pmos', level=1, kp=kp, vto=-vto) 20 | 21 | self.M(1, 'v1', 'v2', 'vdd', 'vdd', model='pmos_m') 22 | self.M(2, 'v2', 'v2', 'vdd', 'vdd', model='pmos_m') 23 | self.M(3, 'vout', 'v2', 'vdd', 'vdd', model='pmos_m') 24 | self.M(4, 'v3', 'in_n', 'v1', 'vdd', model='pmos_m') 25 | self.M(5, 'v4', 'in_p', 'v1', 'vdd', model='pmos_m') 26 | 27 | self.M(6, 'v3', 'v3', 'vss', 'vss', model='nmos_m') 28 | self.M(7, 'v4', 'v3', 'vss', 'vss', model='nmos_m') 29 | self.M(8, 'v2', 'vdd', 'vss', 'vss', model='nmos_m') 30 | self.M(9, 'vout', 'v4', 'vss', 'vss', model='nmos_m') 31 | 32 | self.R(1, 'vout', 'vout_delay', 1@u_kΩ) 33 | self.C(1, 'vout_delay', 'vss', 1@u_pF) 34 | 35 | 36 | circuit.V('VDD', 'vdd', circuit.gnd, 1@u_V) 37 | circuit.V('INP', 'vinp', circuit.gnd, 0.54@u_V) 38 | circuit.V('INN', 'vinn', circuit.gnd, 0.55@u_V) 39 | 40 | circuit.subcircuit(OpAmp(400e-6, 0.4)) 41 | circuit.X('1', 'op_amp', 'vdd', circuit.gnd, 'vinp', 'vinn', 'vout') 42 | ###################### 43 | 44 | 45 | ##### Simulation ##### 46 | # simulator = circuit.simulator(temperature=25, nominal_temperature=25) 47 | # analysis = simulator.transient(step_time=1@u_us, end_time=500@u_us) 48 | 49 | # fig = plt.figure() 50 | # plt.ylim((-0.2, 1.2)) 51 | # # plt.plot(list(analysis.time), list(analysis["vinp"])) 52 | # # plt.plot(list(analysis.time), list(analysis["vinn"])) 53 | # plt.plot(list(analysis.time), list(analysis["vout"])) 54 | # plt.show() 55 | # fig.savefig("./outputs/opamp.png") 56 | # plt.close(fig) 57 | ###################### -------------------------------------------------------------------------------- /sample_design/subcircuits/pfd.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from PySpice.Spice.Netlist import Circuit, SubCircuit, SubCircuitFactory 3 | from PySpice.Unit import * 4 | from subcircuits import logic_gates 5 | from subcircuits import dff 6 | 7 | ###### Netlist ####### 8 | circuit = Circuit('Phase Frequency Detector') 9 | circuit.model('nmos_model', 'nmos', level=1, kp=400e-6, vto=0.4) 10 | circuit.model('pmos_model', 'pmos', level=1, kp=400e-6, vto=-0.4) 11 | 12 | class PFD(SubCircuitFactory): 13 | __name__ = 'pfd' 14 | __nodes__ = ('vdd', 'vss', 'clk_ref', 'clk_fb', 'UP', 'DN') 15 | def __init__(self, kp, vto): 16 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 17 | # super.__init__() 18 | 19 | self.subcircuit(dff.DFF_RST(kp=kp, vto=vto)) 20 | self.X('0', 'd_flip_flop_with_rst', 'vdd', 'vss', 'vdd', 'clk_ref', 'rstb', 'UP') 21 | 22 | self.subcircuit(dff.DFF_RST(kp=kp, vto=vto)) 23 | self.X('1', 'd_flip_flop_with_rst', 'vdd', 'vss', 'vdd', 'clk_fb', 'rstb', 'DN') 24 | 25 | self.subcircuit(logic_gates.NAND(kp=kp, vto=vto)) 26 | self.X('2', 'nand', 'vdd', 'vss', 'UP', 'DN', 'rstb') 27 | 28 | # self.subcircuit(logic_gates.INV(kp=kp, vto=vto)) 29 | # self.X('5', 'inv', 'vdd', 'vss', 'rstbbb', 'rstbb') 30 | # self.subcircuit(logic_gates.INV(kp=kp, vto=vto)) 31 | # self.X('6', 'inv', 'vdd', 'vss', 'rstbb', 'rstb') 32 | 33 | 34 | circuit.V('VDD', 'vdd', circuit.gnd, 1@u_V) 35 | circuit.PulseVoltageSource('1', 'clk_ref', circuit.gnd, initial_value=0@u_V, pulsed_value=1@u_V, 36 | pulse_width=5@u_ns, period=10@u_ns, delay_time=30@u_ns, rise_time=0.2@u_ns, fall_time=0.2@u_ns) 37 | circuit.PulseVoltageSource('2', 'clk_fb', circuit.gnd, initial_value=0@u_V, pulsed_value=1@u_V, 38 | pulse_width=15@u_ns, period=30@u_ns, delay_time=100@u_ns, rise_time=0.2@u_ns, fall_time=0.2@u_ns) 39 | circuit.subcircuit(PFD(400e-6, 0.4)) 40 | circuit.X('0', 'pfd', 'vdd', circuit.gnd, 'clk_ref', 'clk_fb', 'UP', 'DN', 'rstb') 41 | ###################### 42 | 43 | 44 | ##### Simulation ##### 45 | # simulator = circuit.simulator(temperature=25, nominal_temperature=25) 46 | # simulator.initial_condition(rstb=0@u_V) 47 | # analysis = simulator.transient(step_time=1@u_us, end_time=1500@u_ns) 48 | 49 | # fig = plt.figure() 50 | # plt.ylim((-0.2, 1.2)) 51 | # # plt.plot(list(analysis.time), list(analysis["rstb"])) 52 | # plt.plot(list(analysis.time), list(analysis["UP"])) 53 | # plt.plot(list(analysis.time), list(analysis["DN"])) 54 | # plt.show() 55 | # fig.savefig("./outputs/pfd.png") 56 | # plt.close(fig) 57 | ###################### -------------------------------------------------------------------------------- /sample_design/subcircuits/ring_vco.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from PySpice.Spice.Netlist import Circuit, SubCircuit, SubCircuitFactory 3 | from PySpice.Unit import * 4 | 5 | from subcircuits import logic_gates 6 | 7 | 8 | ###### Netlist ####### 9 | circuit = Circuit('Ring Voltage Controlled Oscillator') 10 | circuit.model('nmos_model', 'nmos', level=1, kp=400e-6, vto=0.4) 11 | circuit.model('pmos_model', 'pmos', level=1, kp=400e-6, vto=-0.4) 12 | 13 | class StarvedInvDelayLine(SubCircuitFactory): 14 | __name__ = 'delay_line' 15 | __nodes__ = ('vdd', 'vss', 'in_p', 'in_n', 'vctrl', 'out_p', 'out_n') 16 | def __init__(self, kp, vto): 17 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 18 | # super.__init__() 19 | 20 | self.model('nmos_m', 'nmos', level=1, kp=kp, vto=vto) 21 | self.model('pmos_m', 'pmos', level=1, kp=kp, vto=-vto) 22 | 23 | self.subcircuit(logic_gates.INV(kp, vto)) 24 | self.X('0', 'inv', 'vp1', 'vss', 'in_p', 'out_n') 25 | self.M(0, 'vp1', 'vctrl', 'vdd', 'vdd', model='pmos_m') 26 | 27 | self.subcircuit(logic_gates.INV(kp, vto)) 28 | self.X('1', 'inv', 'vp2', 'vss', 'in_n', 'out_p') 29 | self.M(1, 'vp2', 'vctrl', 'vdd', 'vdd', model='pmos_m') 30 | 31 | self.subcircuit(logic_gates.INV(kp, vto)) 32 | self.X('2', 'inv', 'vdd', 'vss', 'out_p', 'out_n') 33 | self.subcircuit(logic_gates.INV(kp, vto)) 34 | self.X('3', 'inv', 'vdd', 'vss', 'out_n', 'out_p') 35 | 36 | 37 | class RingVCO(SubCircuitFactory): 38 | __name__ = 'ring_vco' 39 | __nodes__ = ('vdd', 'vss', 'vctrl', 'clk_p', 'clk_n', 40 | 'clk_p_45', 'clk_n_45', 41 | 'clk_p_90', 'clk_n_90', 42 | 'clk_p_135', 'clk_n_135') 43 | def __init__(self, kp, vto): 44 | SubCircuit.__init__(self, self.__name__, *self.__nodes__) 45 | # super.__init__() 46 | 47 | self.subcircuit(StarvedInvDelayLine(kp, vto)) 48 | self.X('1', 'delay_line', 'vdd', 'vss', 'clk_p', 'clk_n', 'vctrl', 'clk_p_45', 'clk_n_45') 49 | 50 | self.subcircuit(StarvedInvDelayLine(kp, vto)) 51 | self.X('2', 'delay_line', 'vdd', 'vss', 'clk_p_45', 'clk_n_45', 'vctrl', 'clk_p_90', 'clk_n_90') 52 | 53 | self.subcircuit(StarvedInvDelayLine(kp, vto)) 54 | self.X('3', 'delay_line', 'vdd', 'vss', 'clk_p_90', 'clk_n_90', 'vctrl', 'clk_p_135', 'clk_n_135') 55 | 56 | self.subcircuit(StarvedInvDelayLine(kp, vto)) 57 | self.X('4', 'delay_line', 'vdd', 'vss', 'clk_p_135', 'clk_n_135', 'vctrl', 'clk_n', 'clk_p') 58 | 59 | 60 | 61 | circuit.V('VDD', 'vdd', circuit.gnd, 1@u_V) 62 | circuit.V('ctrl', 'vctrl', circuit.gnd, 0@u_V) 63 | 64 | circuit.subcircuit(RingVCO(400e-6, 0.4)) 65 | circuit.X('1', 'ring_vco', 'vdd', circuit.gnd, 'vctrl', 'clk_p', 'clk_n', 66 | 'clk_p_45', 'clk_n_45', 67 | 'clk_p_90', 'clk_n_90', 68 | 'clk_p_135', 'clk_n_135') 69 | ###################### 70 | 71 | 72 | ##### Simulation ##### 73 | # simulator = circuit.simulator(temperature=25, nominal_temperature=25) 74 | # simulator.initial_condition(clk_p=0.5@u_V, clk_n=0.5@u_V, 75 | # clk_p_45=0.5@u_V, clk_n_45=0.5@u_V, 76 | # clk_p_90=0.5@u_V, clk_n_90=0.5@u_V, 77 | # clk_p_135=0.5@u_V, clk_n_135=0.5@u_V) 78 | # analysis = simulator.transient(step_time=10@u_ns, end_time=3@u_us) 79 | 80 | 81 | # # ### Find frequency 82 | # import numpy as np 83 | # time = np.array(analysis.time) # Time points array 84 | # vout = np.array(analysis['clk_p']) # Output voltage array 85 | # # Find zero-crossings 86 | # zero_crossings = np.where(np.diff(np.sign(vout-0.5))[5:])[0] 87 | # # Calculate periods by subtracting consecutive zero-crossing times 88 | # periods = np.diff(time[zero_crossings]) 89 | # # Average period 90 | # average_period = np.mean(periods) 91 | # # Frequency is the inverse of the period 92 | # frequency = 1 / average_period 93 | # print() 94 | # print(f"Frequency: {frequency*1e-6} MHz") 95 | # print() 96 | 97 | # fig = plt.figure() 98 | # plt.ylim((-0.2, 1.2)) 99 | # plt.plot(list(analysis.time), list(analysis["clk_p"])) 100 | # plt.show() 101 | # fig.savefig("./outputs/ring_vco.png") 102 | # plt.close(fig) 103 | ###################### -------------------------------------------------------------------------------- /sample_design/test_all_sample_design.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import sys 4 | 5 | def work(): 6 | failed_tasks = [] 7 | for task_id in range(1, 25): 8 | file_path = os.path.join(f"p{task_id}.py") 9 | result = subprocess.run(['python', file_path], capture_output=True) 10 | if result.returncode == 0: 11 | print(f"Task {task_id} passed.") 12 | else: 13 | print(f"Task {task_id} failed.") 14 | failed_tasks.append(task_id) 15 | 16 | if len(failed_tasks) > 0: 17 | print(f"Failed tasks: {failed_tasks}") 18 | print(f"Please check your environment and try again.") 19 | sys.exit(1) 20 | else: 21 | print("All tasks passed.") 22 | sys.exit(0) 23 | 24 | def main(): 25 | work() 26 | 27 | 28 | if __name__ == "__main__": 29 | main() -------------------------------------------------------------------------------- /simulation_error.md: -------------------------------------------------------------------------------- 1 | The circuit design has one floating node: [NODE], causing an error in simulation. 2 | Please consider connect it to a voltage or current source or another device. 3 | Please identify the cause of the floating node and rewrite the corrected complete code. -------------------------------------------------------------------------------- /subcircuit_lib/p10_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class CommonSourceAmpDiodeLoad(SubCircuitFactory): 5 | NAME = ('CommonSourceAmpDiodeLoad') 6 | NODES = ('Vin', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET models 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | self.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 12 | # Power Supply and Input Signal 13 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 14 | # Single-Stage Amplifier: Common-Source with PMOS Diode-Connected Load 15 | # parameters: name, drain, gate, source, bulk, model, w, l 16 | self.MOSFET('1', 'Vout', 'Vin', self.gnd, self.gnd, model='nmos_model', w=50e-6, l=1e-6) 17 | self.MOSFET('2', 'Vout', 'Vin', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 18 | # Include the PMOS diode-connected load 19 | self.MOSFET('3', 'Vout', 'Vout', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 20 | -------------------------------------------------------------------------------- /subcircuit_lib/p11_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class SingleStageOpamp(SubCircuitFactory): 5 | NAME = ('SingleStageOpamp') 6 | NODES = ('Vinp', 'Vinn', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET models 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | self.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 12 | # Power Supplies 13 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 14 | self.V('bias', 'Vbias', self.gnd, 1.5) # Bias voltage for the tail current source M3 15 | # Input Voltage Sources for Differential Inputs 16 | # Differential Pair and Tail Current Source 17 | self.MOSFET('1', 'Voutp', 'Vinp', 'Source3', 'Source3', model='nmos_model', w=50e-6, l=1e-6) 18 | self.MOSFET('2', 'Vout', 'Vinn', 'Source3', 'Source3', model='nmos_model', w=50e-6, l=1e-6) 19 | self.MOSFET('3', 'Source3', 'Vbias', self.gnd, self.gnd, model='nmos_model', w=100e-6, l=1e-6) 20 | # Active Current Mirror Load 21 | self.MOSFET('4', 'Voutp', 'Voutp', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 22 | self.MOSFET('5', 'Vout', 'Voutp', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 23 | -------------------------------------------------------------------------------- /subcircuit_lib/p12_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class CascodeCurrentMirror(SubCircuitFactory): 5 | NAME = ('CascodeCurrentMirror') 6 | NODES = ('Iref', 'Iout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET models 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | self.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 12 | # Power Supplies 13 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 14 | self.V('bias', 'Vbias', self.gnd, 1.5) # Bias voltage for the tail current source M3 15 | # Input Voltage Sources for Differential Inputs 16 | self.V('inp', 'Vinp', self.gnd, 2.5) 17 | self.V('inn', 'Vinn', self.gnd, 2.5) 18 | # Differential Pair and Tail Current Source 19 | self.MOSFET('1', 'Voutp', 'Vinp', 'Source3', 'Source3', model='nmos_model', w=50e-6, l=1e-6) 20 | self.MOSFET('2', 'Vout', 'Vinn', 'Source3', 'Source3', model='nmos_model', w=50e-6, l=1e-6) 21 | self.MOSFET('3', 'Source3', 'Vbias', self.gnd, self.gnd, model='nmos_model', w=100e-6, l=1e-6) 22 | # Active Current Mirror Load 23 | self.MOSFET('4', 'Voutp', 'Voutp', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 24 | self.MOSFET('5', 'Vout', 'Voutp', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 25 | -------------------------------------------------------------------------------- /subcircuit_lib/p13_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class SingleStageDiffCommonSourceOpamp(SubCircuitFactory): 5 | NAME = ('SingleStageDiffCommonSourceOpamp') 6 | NODES = ('Vinp', 'Vinn', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET models 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | # Power Supplies for the power and tail current source 12 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 13 | # Tail Current Source Biasing (Adjusted to ensure NMOS 3 is activated properly) 14 | self.V('bias', 'Vbias', self.gnd, 1.5) # Adjusted bias voltage for tail current source 15 | # Differential Input Voltage Sources (Adjusted to ensure NMOS 1 and NMOS 2 are activated) 16 | # Differential Pair with adjusted source voltage for activation 17 | self.MOSFET('1', 'Vout', 'Vinp', 'Source3', 'Source3', model='nmos_model', w=50e-6, l=1e-6) # Output taken from Drain1 18 | self.MOSFET('2', 'Drain2', 'Vinn', 'Source3', 'Source3', model='nmos_model', w=50e-6, l=1e-6) 19 | # Tail Current Source with adjusted parameters for proper activation 20 | self.MOSFET('3', 'Source3', 'Vbias', self.gnd, self.gnd, model='nmos_model', w=100e-6, l=1e-6) 21 | # Load Resistors 22 | self.R('1', 'Vout', 'Vdd', 1@u_kΩ) # Connected to Vout for correct output node identification 23 | self.R('2', 'Drain2', 'Vdd', 1@u_kΩ) 24 | -------------------------------------------------------------------------------- /subcircuit_lib/p14_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class OpampResistanceLoad(SubCircuitFactory): 5 | NAME = ('OpampResistanceLoad') 6 | NODES = ('Vinp', 'Vinn', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET models 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | self.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 12 | # Power Supplies 13 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 14 | # Bias voltages and input signals 15 | self.V('bias1', 'Vbias1', self.gnd, 2.5) # Bias for active loads 16 | self.V('bias2', 'Vbias2', self.gnd, 1.0) # Bias for tail current source 17 | self.V('bias3', 'Vbias3', self.gnd, 2.5) # Bias for second stage active load 18 | # First Stage: Differential Pair with Active Load and Tail Current Source 19 | self.MOSFET('1', 'Drain1', 'Vinp', 'Source5', self.gnd, model='nmos_model', w=50e-6, l=1e-6) 20 | self.MOSFET('2', 'Drain2', 'Vinn', 'Source5', self.gnd, model='nmos_model', w=50e-6, l=1e-6) 21 | self.MOSFET('3', 'Drain1', 'Vbias1', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 22 | self.MOSFET('4', 'Drain2', 'Vbias1', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 23 | self.MOSFET('5', 'Source5', 'Vbias2', self.gnd, self.gnd, model='nmos_model', w=100e-6, l=1e-6) 24 | # Second Stage: Common-Source with Active Load 25 | self.MOSFET('6', 'Vout', 'Drain1', self.gnd, self.gnd, model='nmos_model', w=100e-6, l=1e-6) 26 | self.MOSFET('7', 'Vout', 'Vbias3', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 27 | -------------------------------------------------------------------------------- /subcircuit_lib/p15_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class SingleStageDiffOpamp(SubCircuitFactory): 5 | NAME = ('SingleStageDiffOpamp') 6 | NODES = ('Vinp', 'Vinn', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET models 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | self.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 12 | # Power Supply 13 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 14 | # Input and Bias Voltages 15 | self.V('bias1', 'Vbias1', self.gnd, 1.5@u_V) # Bias for NMOS cascode 16 | self.V('bias2', 'Vbias2', self.gnd, 1.5@u_V) # Bias for NMOS cascode 17 | self.V('bias3', 'Vbias3', self.gnd, 3.5@u_V) # Bias for PMOS cascode 18 | self.V('bias4', 'Vbias4', self.gnd, 3.5@u_V) # Bias for PMOS cascode 19 | self.V('biasTail', 'VbiasTail', self.gnd, 1.0@u_V) # Bias for the tail current source 20 | # NMOS Transistors 21 | self.MOSFET('1', 'Drain1', 'Vinp', 'Source5', self.gnd, model='nmos_model', w=50e-6, l=1e-6) 22 | self.MOSFET('2', 'Drain2', 'Vinn', 'Source5', self.gnd, model='nmos_model', w=50e-6, l=1e-6) 23 | self.MOSFET('3', 'Voutp', 'Vbias1', 'Drain1', self.gnd, model='nmos_model', w=50e-6, l=1e-6) 24 | self.MOSFET('4', 'Vout', 'Vbias2', 'Drain2', self.gnd, model='nmos_model', w=50e-6, l=1e-6) 25 | self.MOSFET('5', 'Source5', 'VbiasTail', self.gnd, self.gnd, model='nmos_model', w=50e-6, l=1e-6) 26 | # PMOS Transistors 27 | self.MOSFET('6', 'Voutp', 'Vbias3', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 28 | self.MOSFET('7', 'Voutp', 'Vbias4', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 29 | self.MOSFET('8', 'Vout', 'Vbias3', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 30 | self.MOSFET('9', 'Vout', 'Vbias4', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 31 | -------------------------------------------------------------------------------- /subcircuit_lib/p1_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class SingleStageAmp(SubCircuitFactory): 5 | NAME = ('SingleStageAmp') 6 | NODES = ('Vin', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET model 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | # Power Supply for the power and input signal 12 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 13 | # Common-Source Amplifier with Resistor Load 14 | # parameters: name, drain, gate, source, bulk, model, w, l 15 | self.MOSFET('1', 'Vout', 'Vin', self.gnd, self.gnd, model='nmos_model', w=50e-6, l=1e-6) 16 | self.R('1', 'Vout', 'Vdd', 1@u_kΩ) 17 | -------------------------------------------------------------------------------- /subcircuit_lib/p2_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class ThreeStageAmp(SubCircuitFactory): 5 | NAME = ('ThreeStageAmp') 6 | NODES = ('Vin', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET models 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | # Power Supply for the power 12 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 13 | # Input Signal 14 | # First Stage: Common-Source with Resistor Load 15 | self.MOSFET('1', 'Drain1', 'Vin', self.gnd, self.gnd, model='nmos_model', w=50e-6, l=1e-6) 16 | self.R('1', 'Drain1', 'Vdd', 1@u_kΩ) 17 | # Second Stage: Common-Source with Resistor Load 18 | self.MOSFET('3', 'Drain2', 'Drain1', self.gnd, self.gnd, model='nmos_model', w=50e-6, l=1e-6) 19 | self.R('2', 'Drain2', 'Vdd', 1@u_kΩ) 20 | # Third Stage: Common-Source with Resistor Load 21 | self.MOSFET('5', 'Vout', 'Drain2', self.gnd, self.gnd, model='nmos_model', w=50e-6, l=1e-6) 22 | self.R('3', 'Vout', 'Vdd', 1@u_kΩ) 23 | -------------------------------------------------------------------------------- /subcircuit_lib/p3_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class CommonDrainAmp(SubCircuitFactory): 5 | NAME = ('CommonDrainAmp') 6 | NODES = ('Vin', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET model 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | # Power Supply for the power 12 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 13 | # Common-Drain Amplifier with Resistor Load 14 | self.MOSFET('1', 'Vdd', 'Vin', 'Vout', self.gnd, model='nmos_model', w=50e-6, l=1e-6) 15 | self.R('load', 'Vout', self.gnd, 1@u_kΩ) 16 | -------------------------------------------------------------------------------- /subcircuit_lib/p4_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class CommonGateAmp(SubCircuitFactory): 5 | NAME = ('CommonGateAmp') 6 | NODES = ('Vin', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET model 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | # Power Supply and Bias Voltage 12 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 13 | self.V('bias', 'Vbias', self.gnd, 1.5) # Bias voltage for the gate of M1, ensuring it's above threshold 14 | # Input Signal 15 | # NMOS Transistor 16 | self.MOSFET('1', 'Vout', 'Vbias', 'Vin', 'Vin', model='nmos_model', w=50e-6, l=1e-6) 17 | # Load Resistor 18 | self.R('1', 'Vout', 'Vdd', 1@u_kΩ) 19 | -------------------------------------------------------------------------------- /subcircuit_lib/p5_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class SingleStageCascodeAmp(SubCircuitFactory): 5 | NAME = ('SingleStageCascodeAmp') 6 | NODES = ('Vin', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the NMOS transistor model 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | # Power Supply for the power and input signal 12 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 13 | self.V('bias', 'Vbias', self.gnd, 3.0) # Bias voltage for the upper transistor 14 | # Cascode Amplifier Design 15 | # Lower NMOS transistor M1 16 | self.MOSFET('1', 'Drain1', 'Vin', self.gnd, self.gnd, model='nmos_model', w=50e-6, l=1e-6) 17 | # Upper NMOS transistor M2 (Cascode) 18 | self.MOSFET('2', 'Vout', 'Vbias', 'Drain1', self.gnd, model='nmos_model', w=50e-6, l=1e-6) 19 | # Resistive Load 20 | self.R('load', 'Vout', 'Vdd', 1@u_kΩ) 21 | -------------------------------------------------------------------------------- /subcircuit_lib/p6_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class NMOSInverter(SubCircuitFactory): 5 | NAME = ('NMOSInverter') 6 | NODES = ('Vin', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET model 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | # Power Supply for the power and input signal 12 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 13 | # Common-Source Amplifier with Resistor Load 14 | # parameters: name, drain, gate, source, bulk, model, w, l 15 | self.MOSFET('1', 'Vout', 'Vin', self.gnd, self.gnd, model='nmos_model', w=50e-6, l=1e-6) 16 | self.R('1', 'Vout', 'Vdd', 1@u_kΩ) 17 | -------------------------------------------------------------------------------- /subcircuit_lib/p7_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class LogicalInverter(SubCircuitFactory): 5 | NAME = ('LogicalInverter') 6 | NODES = ('Vin', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET model 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | # Power Supply for the power and input signal 12 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 13 | # Common-Source Amplifier with Resistor Load 14 | # parameters: name, drain, gate, source, bulk, model, w, l 15 | self.MOSFET('1', 'Vout', 'Vin', self.gnd, self.gnd, model='nmos_model', w=50e-6, l=1e-6) 16 | self.R('1', 'Vout', 'Vdd', 1@u_kΩ) 17 | -------------------------------------------------------------------------------- /subcircuit_lib/p8_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class NMOSConstantCurrentSource(SubCircuitFactory): 5 | NAME = ('NMOSConstantCurrentSource') 6 | NODES = ('Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET model 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | # Power Supply for the power and input signal 12 | self.V('dd', 'Vdd', self.gnd, 5.0) # 5V power supply 13 | self.V('in', 'Vin', self.gnd, 1.5) 14 | # Common-Source Amplifier with Resistor Load 15 | # parameters: name, drain, gate, source, bulk, model, w, l 16 | self.MOSFET('1', 'Vout', 'Vin', self.gnd, self.gnd, model='nmos_model', w=50e-6, l=1e-6) 17 | self.R('1', 'Vout', 'Vdd', 1@u_kΩ) 18 | -------------------------------------------------------------------------------- /subcircuit_lib/p9_lib.py: -------------------------------------------------------------------------------- 1 | from PySpice.Unit import * 2 | from PySpice.Spice.Netlist import SubCircuitFactory 3 | 4 | class TwoStageOpampMiller(SubCircuitFactory): 5 | NAME = ('TwoStageOpampMiller') 6 | NODES = ('Vin', 'Vout') 7 | def __init__(self): 8 | super().__init__() 9 | # Define the MOSFET models 10 | self.model('nmos_model', 'nmos', level=1, kp=100e-6, vto=0.5) 11 | self.model('pmos_model', 'pmos', level=1, kp=50e-6, vto=-0.5) 12 | # Power Supplies 13 | self.V('dd', 'Vdd', self.gnd, 5@u_V) # 5V power supply 14 | self.V('bias1', 'Vbias1', self.gnd, 4@u_V) # Bias for M2 15 | self.V('bias2', 'Vbias2', self.gnd, 4@u_V) # Bias for M4 16 | # First Stage: Common-Source with Active Load 17 | self.MOSFET('1', 'Drain1', 'Vin', self.gnd, self.gnd, model='nmos_model', w=50e-6, l=1e-6) 18 | self.MOSFET('2', 'Drain1', 'Vbias1', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 19 | # Second Stage: Common-Source with Active Load 20 | self.MOSFET('3', 'Vout', 'Drain1', self.gnd, self.gnd, model='nmos_model', w=100e-6, l=1e-6) 21 | self.MOSFET('4', 'Vout', 'Vbias2', 'Vdd', 'Vdd', model='pmos_model', w=100e-6, l=1e-6) 22 | # Miller Compensation Capacitor 23 | self.C('c', 'Drain1', 'Vout', 10@u_pF) 24 | -------------------------------------------------------------------------------- /teaser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laiyao1/AnalogCoder/6225b4925fcced0d08d3bd9ee4010b8649144b31/teaser.png -------------------------------------------------------------------------------- /utda.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laiyao1/AnalogCoder/6225b4925fcced0d08d3bd9ee4010b8649144b31/utda.jpg --------------------------------------------------------------------------------