# circuit_simulation **Repository Path**: erode/circuit_simulation ## Basic Information - **Project Name**: circuit_simulation - **Description**: 本仓库用来保存熟悉电路仿真过程时所写的代码 - **Primary Language**: C++ - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-07-29 - **Last Updated**: 2023-08-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: EDA入门学习 ## README # circuit_simulation 本项目是我用来学习SPICE电路仿真流程时用来保存代码的仓库。 ### 新增加批量测试功能 在项目的根目录下执行batch_run_script.sh文件可以批量测试testcase文件夹下的文件,并将输出结果放在out文件夹下 注:在首次clone本项目后,您需要在该脚本所在的目录下,给予脚本执行权限 `chmod +x batch_run_script.sh` 然后执行该文件 即: `./batch_run_script.sh ` ### 代码演示地址 https://www.bilibili.com/video/BV1c8411o7T5/?pop_share=1&vd_source=f4bc1addd58ccd3c73dac4c86bac440e ### 项目各文件、文件夹介绍 1. include 文件夹中包含了程序所需要的一些数据结构。 包括器件Component类、节点Node类、网表Netlist类、器件所连接节点的实例Connectors结构体 节点所连接的器件实例Connections结构体。 2. testcase 本文件夹用保存进行测试的电路网表,电路网表遵循SPICE语法 3. out 本文件夹用来保存testcase中测试网表的DEBUG信息。 包括网表的各节点所连接的器件、接地节点datum(选择连接器件数目最多的节点作为接地节点,目的是为了让方程简便)、lastnode最大节点编号、total方程的维数(total=lastnode+电压源的数量) 4. eigen-3.4.0 此文件夹是从eigen官网(http://eigen.tuxfamily.org/index.php?title=Main_Page#Download)下载的,包含了eigen库所需要的一些文件。 下载解压好文件,只需将它放在本项目的根目录,然后中根目录下的CMakeLists.txt中添加你下载的eigen库的路径即可。 例如我本地的clion添加Eigen库的路径 include_directories(/Users/jiqing/CLionProjects/circuit_simulation/eigen-3.4.0) 5. main.cpp 此文件是我的项目的启动文件,你需要在cmake-build-debug文件夹下运行代码。 例如 (base) jiqing@jiqingdeMacBook-Pro cmake-build-debug % ./circuit_simulation ../testcase/netlist1 ../out/netlist1.out ### 仿真流程 1. 创建合适的数据结构 2. parse电路网表 3. 遍历数据结构,创建电路节点,创建KCL、KVL方程 4. 填充雅可比矩阵,使用eigen库进行牛顿迭代求解方程 ### 当前项目运行的结果 注:打印部分我写的比较简陋,一律打印了value的值,并没有根据器件的类型打印model,还是value等对应的值。 目前还停留在创建雅可比矩阵的地方,这里还没有完全实现出来,之后会慢慢实现。 牛顿迭代预计会使用eigen库的库函数来求解,目前只是配好了eigen库,还没有实现迭代的部分。 #### case1 ```angular2html Title: * Schmitt Trigger circuit no. 1 datum = 0 6 节点1 所连器件数为:3 编号:1 类型:Q 名称:q1 value:-1 编号:1 类型:R 名称:rc1 value:2000 编号:3 类型:R 名称:r3 value:10000 节点5 所连器件数为:2 编号:1 类型:Q 名称:q1 value:-1 编号:2 类型:V 名称:vin value:1.5 节点2 所连器件数为:3 编号:1 类型:Q 名称:q1 value:-1 编号:2 类型:Q 名称:q2 value:-1 编号:4 类型:R 名称:re value:100 节点3 所连器件数为:2 编号:2 类型:Q 名称:q2 value:-1 编号:2 类型:R 名称:rc2 value:1000 节点4 所连器件数为:2 编号:2 类型:Q 名称:q2 value:-1 编号:3 类型:R 名称:r3 value:10000 节点6 所连器件数为:3 编号:1 类型:R 名称:rc1 value:2000 编号:2 类型:R 名称:rc2 value:1000 编号:1 类型:V 名称:vcc value:10 节点0 所连器件数为:3 编号:4 类型:R 名称:re value:100 编号:1 类型:V 名称:vcc value:10 编号:2 类型:V 名称:vin value:1.5 functions: F(0) = x0/re + x0 - vcc + x0 - vin F(1) = Ic1 + (x1 - x6)/rc1 + (x1 - x4)/r3 F(2) = Ie1 + Ie2 + x2/re F(3) = Ic2 + (x3 - x6)/rc2 F(4) = - Ic2 - Ie2 + (x4 - x1)/r3 F(5) = x5 - vin F(6) = x6 - vcc F(7) = x7 + (x6 - x1)/rc1 + (x6 - x3)/rc2 F(8) = x8 - Ic1 - Ie1 ``` #### case2 ```angular2html Title: * Schmitt Trigger circuit no. 2 datum = 0 6 节点2 所连器件数为:3 编号:1 类型:Q 名称:q1 value:-1 编号:1 类型:R 名称:rc1 value:1500 编号:3 类型:R 名称:r1 value:10000 节点5 所连器件数为:3 编号:1 类型:Q 名称:q1 value:-1 编号:5 类型:R 名称:r2 value:5000 编号:6 类型:R 名称:r3 value:1250 节点1 所连器件数为:3 编号:1 类型:Q 名称:q1 value:-1 编号:2 类型:Q 名称:q2 value:-1 编号:4 类型:R 名称:re value:100 节点3 所连器件数为:2 编号:2 类型:Q 名称:q2 value:-1 编号:2 类型:R 名称:rc2 value:1000 节点4 所连器件数为:3 编号:2 类型:Q 名称:q2 value:-1 编号:3 类型:R 名称:r1 value:10000 编号:7 类型:R 名称:r4 value:1e+06 节点6 所连器件数为:4 编号:1 类型:R 名称:rc1 value:1500 编号:2 类型:R 名称:rc2 value:1000 编号:5 类型:R 名称:r2 value:5000 编号:1 类型:V 名称:vcc value:10 节点0 所连器件数为:4 编号:4 类型:R 名称:re value:100 编号:6 类型:R 名称:r3 value:1250 编号:7 类型:R 名称:r4 value:1e+06 编号:1 类型:V 名称:vcc value:10 functions: F(0) = x0/re + x0/r3 + x0/r4 + x0 - vcc F(1) = Ie1 + Ie2 + x1/re F(2) = Ic1 + (x2 - x6)/rc1 + (x2 - x4)/r1 F(3) = Ic2 + (x3 - x6)/rc2 F(4) = - Ic2 - Ie2 + (x4 - x2)/r1 + x4/r4 F(5) = - Ic1 - Ie1 + (x5 - x6)/r2 + x5/r3 F(6) = x6 - vcc F(7) = x7 + (x6 - x2)/rc1 + (x6 - x3)/rc2 + (x6 - x5)/r2 ``` #### case3 ```angular2html Title: * Chua Circuit datum = 0 14 节点13 所连器件数为:3 编号:1 类型:V 名称:VCC1 value:12 编号:2 类型:R 名称:R2 value:4000 编号:11 类型:R 名称:R11 value:4000 节点0 所连器件数为:7 编号:1 类型:V 名称:VCC1 value:12 编号:2 类型:V 名称:VCC2 value:12 编号:4 类型:R 名称:R4 value:5000 编号:6 类型:R 名称:R6 value:500 编号:7 类型:R 名称:R7 value:500 编号:9 类型:R 名称:R9 value:10100 编号:10 类型:R 名称:R10 value:10100 节点14 所连器件数为:3 编号:2 类型:V 名称:VCC2 value:12 编号:3 类型:R 名称:R3 value:4000 编号:12 类型:R 名称:R12 value:4000 节点1 所连器件数为:4 编号:3 类型:V 名称:V1 value:10 编号:4 类型:R 名称:R4 value:5000 编号:1 类型:Q 名称:Q1 value:-1 编号:3 类型:Q 名称:Q3 value:-1 节点2 所连器件数为:2 编号:3 类型:V 名称:V1 value:10 编号:1 类型:R 名称:R1 value:10000 节点11 所连器件数为:2 编号:4 类型:V 名称:V2 value:2 编号:14 类型:R 名称:R14 value:30000 节点3 所连器件数为:3 编号:4 类型:V 名称:V2 value:2 编号:1 类型:R 名称:R1 value:10000 编号:13 类型:R 名称:R13 value:30000 节点4 所连器件数为:3 编号:2 类型:R 名称:R2 value:4000 编号:5 类型:R 名称:R5 value:30000 编号:1 类型:Q 名称:Q1 value:-1 节点5 所连器件数为:3 编号:3 类型:R 名称:R3 value:4000 编号:8 类型:R 名称:R8 value:30000 编号:3 类型:Q 名称:Q3 value:-1 节点6 所连器件数为:3 编号:5 类型:R 名称:R5 value:30000 编号:9 类型:R 名称:R9 value:10100 编号:2 类型:Q 名称:Q2 value:-1 节点8 所连器件数为:3 编号:6 类型:R 名称:R6 value:500 编号:1 类型:Q 名称:Q1 value:-1 编号:2 类型:Q 名称:Q2 value:-1 节点9 所连器件数为:3 编号:7 类型:R 名称:R7 value:500 编号:3 类型:Q 名称:Q3 value:-1 编号:4 类型:Q 名称:Q4 value:-1 节点7 所连器件数为:3 编号:8 类型:R 名称:R8 value:30000 编号:10 类型:R 名称:R10 value:10100 编号:4 类型:Q 名称:Q4 value:-1 节点10 所连器件数为:3 编号:11 类型:R 名称:R11 value:4000 编号:13 类型:R 名称:R13 value:30000 编号:2 类型:Q 名称:Q2 value:-1 节点12 所连器件数为:3 编号:12 类型:R 名称:R12 value:4000 编号:14 类型:R 名称:R14 value:30000 编号:4 类型:Q 名称:Q4 value:-1 functions: F(0) = x0 - VCC1 + x0 - VCC2 + x0/R4 + x0/R6 + x0/R7 + x0/R9 + x0/R10 F(1) = - x1- x1 - V1 F(2) = - x17 + (x2 - x3)/R1 F(3) = - x18 + (x3 - x2)/R1 + (x3 - x10)/R13 F(4) = (x4 - x13)/R2 + (x4 - x6)/R5 + Ic1 F(5) = (x5 - x14)/R3 + (x5 - x7)/R8 + Ic3 F(6) = (x6 - x4)/R5 + x6/R9 - Ic2 - Ie2 F(7) = (x7 - x5)/R8 + x7/R10 - Ic4 - Ie4 F(8) = x8/R6 + Ie1 + Ie2 F(9) = x9/R7 + Ie3 + Ie4 F(10) = (x10 - x13)/R11 + (x10 - x3)/R13 + Ic2 F(11) = - x11- x11 - V2 F(12) = (x12 - x14)/R12 + (x12 - x11)/R14 + Ic4 F(13) = x13 - VCC1 F(14) = x14 - VCC2 F(15) = x15 + (x13 - x4)/R2 + (x13 - x10)/R11 F(16) = x16 + (x14 - x5)/R3 + (x14 - x12)/R12 F(17) = x17 + x1/R4 - Ic1 - Ie1 - Ic3 - Ie3 F(18) = x18 + (x11 - x12)/R14 ``` #### case4 ```angular2html Title: * 测试tran的电路 datum = 2 2 节点1 所连器件数为:2 编号:1 类型:V 名称:VCC value:3 编号:1 类型:R 名称:R1 value:1000 节点0 所连器件数为:2 编号:1 类型:V 名称:VCC value:3 编号:1 类型:C 名称:C1 value:1e-05 节点2 所连器件数为:2 编号:1 类型:R 名称:R1 value:1000 编号:1 类型:C 名称:C1 value:1e-05 functions: F(0) = x0 - VCC F(1) = x1 - VCC F(2) = (x2 - x1)/R1 F(3) = x3 + (x1 - x2)/R1 ``` ### 总结 建立KCL、KVL方程的过程 1. 扫描器件,若有电压源。则列KVL方程 列方程的时候用掉了该电压源某一端的节点的编号 2. 扫描节点链表,列各节点的KCL方程 如果当前节点连接了电压源,则当前KCL方程的编号会变成电压源编号+lastnode,同时在该方程中会将编号为电压源+lastnode的待求解的X也列入方程中,此时的X代表的是流过电压源的电流值 3. 处理含电压源的KVL方程的求导(填充雅可比矩阵) 先找到电压源,然后用掉其连接的某一个端口的节点编号 4. 处理KCL方程的求导(填充雅可比矩阵) 如果当前节点连接了电压源,则需要特殊处理 ### 个人的反思与收获 本次的仿真流程代码的实现过程中我发现了自己存在的许许多多的问题,我明明是最早开始写的,我确实做的最慢,进度最差,个人认为也是写的最烂的,曾很多次写到怀疑自己,一点儿也不想写这个破玩意了。 但也正是这个痛苦的过程,让我找到了自身存在的各种问题,亡羊补牢,为时不晚。很感谢在这个过程中在多次帮助我的金老师、晨曦师兄、嘉泰师兄以及晓宇,谢谢你们的帮助,让我收获了许多,也让我发现了 自身的漏洞。 1. 先完成基础功能,再完善内容,莫要追求完美 我写代码的这个过程艰难的一个原因是我总想着完美,一次性写出一个包含了各种功能, 相对比较有用的一个仿真器。为了完善,我轻而易举的被其中的任何一步卡住。首先卡住我的是数据结构, 为了写一个好的项目,我看了许多存电路器件的数据结构,我模仿,或者直接搬过来,然后继续往后写。 结果因为只是看懂大概,用这些数据结构的时候不清楚这些数据结构的定义,混淆不同的数据结构的同名的函数,变量,含义等。 还有就算我的数据结构没有什么问题,但是一次性不可能完成一个很好的创作,而一个完善的最终版的数据结构中势必包含了各种各样功能的“地基”,很复杂, 想全部搞明白会花费大量的时间和精力,而这,可能你只是知道了它们大致在干什么,当你把它拿过来用的时候,又会出现各种问题,会出现不会用的情况。 其次,在电路方程的建立的时候,我又参考了师兄,同学的代码,然后就是套,模仿,我感觉我懂了,我感觉我看懂了他们的代码,也知道书上的理论,感觉不难。 但是在写的时候总是出各种问题,各种被卡住。事后很久很久我回顾的时候才发现我出错是因为我根本不知道师兄,同学他们代码中的各种细节,而完成一个程序, 不能出现一丁点的错误,否则都会影响最终结果的错误。我主要是因为对雅可比矩阵以及方程的编号错位的问题以及代码中的某些比较个人的,为了方便的定义的 地方吃了许多亏,没有看懂,没有理解到他们的想法,而一直按照自己的想法套他们的代码。 2. 逐阶段测试的重要性 我在写完parse的时候懒了一下,对于.model语句,BJT管器件的模型参数没有进行打印测试,只是打印了器件的名称,因为其他器件都没有模型参数,BJT有,又要 特殊处理一下打印的语句,所以偷懒了。我没想到因为这个原因,我后来程序出问题的时候,为了这个运行结果不正确的错位,翻完了整个代码。打个比方,就好比 你建了一个100层的楼,结果修好了之后发现100楼没有水,而我从100楼开始往下,把这一栋100层的楼的水的管道重新拆了重建了一遍,结果在拆了重建到第一层地基的时候 发现原来我的整栋楼的进水的地方与外面的通水管道连接不正常。当然现实中我们肯定会从这个开始检测,从最底层检测。但是写代码的时候,我根本不知道我的模型参数解析不正常。 我从牛顿迭代结果一直显示是nan,然后到填充牛顿迭代的方程,到填充雅可比矩阵与右端向量,再到数据结构解析,再到其中处理BF=100语句,把bf的值存到模型中,最后才发现我的 atof(buf2)函数中将字符串buf转换为double型数据的时候出问题了,因为要将“BF=100”中的“100”传到buf2然后变成double,但是我传入到buf2的字符串一直是“=100”多了一个 等号,导致atof函数返回值一直是0,这个血的代价让我debug要疯了。简直是自己挖了坑,把自己给活埋了。我这才意识到分阶段测试代码是多么有意义而重要的事情啊。 3. 不要过多参考别人的代码,不懂要多请教他们思路,了解整个流程,然后仔细请教整个过程的细节,然后自己顺着思路,注意这些细节亲自想着实现。 我有好多次写代码到怀疑自己的能力,我为何如此不堪,从编程能力,算法竞赛来看,我岁也没有多好,但不至于到这种地步,差的离谱到家了。在我回顾的时候我知道了 问题出现的原因。每个人都是一个独立的个体,所以写的代码也是各种不同,而我就算陷入了这个问题中,过多的参考他们的代码,甚至让我忘了自己的逻辑是什么。没有 自己的逻辑,编写的代码,不出问题才怪。所以还是要请教思路,以及过程中的细节,然后自己形成一个思维逻辑来亲自实现。 我算是各种坑都挨着踩,挖坑把自己活埋,埋了之后挣扎逃出来再继续挖,然后继续埋。 这些难忘的经历以及自己走的弯路我觉得值的把它们写下来,这些痛苦教会了我许多。 2023.07~08