├── .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 |
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 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | [[Paper](https://arxiv.org/pdf/2405.14918)]
28 |
29 | # Introduction
30 |
31 |
32 |
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
--------------------------------------------------------------------------------