完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
扫一扫,分享给好友
|
|
相关推荐
1个回答
|
|
时序约束可以很复杂,这里我们先介绍基本的时序路径约束,复杂的时序约束我们将在后面进行介绍。
在本节的主要内容如下所示: ·时序路径和关键路径的介绍 ·建立时间、保持时间简述 ·时钟的约束(寄存器-寄存器之间的路径约束) ·输入延时的约束 ·输出延时的约束 ·组合逻辑的约束 ·结合设计规格进行实战 RTL代码描述了电路的时序逻辑和组合逻辑,即RTL代码体现了电路的寄存器结构和数目、电路的拓扑结构、寄存器之间的组合逻辑功能以及寄存器与I/O端口之间的组合逻辑功能。但代码中并不包括电路的时间(路径的延时)和电路面积(门数)。综合工具现在不能很好地支持异步电路,甚至不支持异步电路,因此时序路径的约束主要是针对同步电路的,关于异步的电路的约束,后面也会进行相关的说明。 1、时序路径与关键路径 我们来看一下同步电路,常见的结构如下所示: 中间是我们设计的模块(芯片),对于同步电路,为了使电路能正常工作,即电路在我们规定的工作频率和工作环境下能功能正确地工作,我们需要对设计中的所有时序路径进行约束。那么时序路径是什么呢?下面进行解说: 时序路径是指信号可以继续穿过,不必等待其他触发条件的路径。沿着时序路径,信号仅在通过电路元器件时有延迟。 时序路径是一个点到点的数据通路, 数据沿着时序路径进行传递。每条时序路径有一个起点(Startpoint)和一个终点(Endpoint)。 起点定义为: · 输入端口; · 触发器或寄存器的时钟引脚。 终点定义为: · 输出端口; · 时序器件的除时钟引脚外的所有输人引脚。 这样, 时序路径可以是输入端口到寄存器、 寄存器到寄存器、 寄存器到输出端口、 输入端口到输出端口。如下面这个电路中: 就有4条路径: 1:从输入端口A到FF1的D端; 2:从FF1的CLK端到FF2的D端; 3:从FF2的CLK端到输出端口out1; 4:从输入端口A到输出端口out1。 好看一点的图如下: 路径的特性是存在延时,也就是说,路径1、2、3、4都存在有延时,延时最长的一条路径称为关键路径。一般情况下,路径1、2、3是最常见的,路径4比较少见。 2、常见的时序路径约束 ①建立时间、保存时间和亚稳态 在进行约束的时候,先了解触发器的三个概念:建立时间、保持时间以及亚稳态。这里只是简单地介绍一下,关于建立时间和保持时间的深入介绍,请查看我的博文:http://www.cnblogs.com/IClearner/p/6443539.html,关于亚稳态的深入介绍,请查看我的博文:http://www.cnblogs.com/IClearner/p/6475943.html 建立时间:时钟有效沿到来之前的某段时间内,数据必须稳定,否则触发器锁存不住数据,这段时间成为建立时间,用Tsetup或者Tsu表示。 保持时间:时钟有效沿到来之后的某段时间内,数据也必须稳定,否则触发器锁存不住数据,这段时间成为保持时间,用Thold或者Th表示。 如下图所示: 在第二个时钟上升沿的时候,要锁存住输入端D的高电平,D1是满足了建立时间和保持时间的情况;而D2则是建立时间没有满足,因此不能成功锁存住输入的高电平;D3保持时间不满足,也不能成功锁存输入的高电平。 亚稳态:每个触发器都有其规定的建立(setup)和保持(hold)时间参数,该参数存放在由半导体厂商所提供的工艺库中。假如触发器由时钟的上升沿触发,在这个时间参数内,输入信号是不允许发生变化的。否则在信号的建立或保持时间中对其采样,得到的结果是不可预知的,有可能是0或者1,即亚稳态。 有了这三个概念之后,我们可以对路径进行约束了。约束就是为了满足寄存器的建立时间(和保持时间),我们先对模块内的路径进行约束,也就是下面电路框图中的中间部分: 对于中间的部分路径,可以用前面的那个路径图来描述: 也就是主要约束这些类型的路径,本小节主要讲的就是这些路径的约束。 ②路径2(寄存器到寄存器之间的路径)的约束: 我们先从寄存器到寄存器之间的路径2开始;前面说到了,为什么要约束时序路径,是为了满足寄存器的建立时间和保持时间。对于路径2,数据从FF1的D端口传输到FF2的D端口,主要需要经历触发器的翻转时间/转换延时、寄存器与寄存器之间的组合逻辑延时、连线延时这些种延时。因为数据是随着时钟的节拍一拍一拍往后传的,因此这里的寄存器与寄存器之间的路径约束,就是对时钟的建模,或者是说对时钟的约束。下面进行说明: 为了满足FF2建立时间的要求,也就是数据经过上面的延时(触发器的翻转时间/转换延时、寄存器与寄存器之间的组合逻辑延时、连线延时)之后到达FF2的D端的时间再加上FF2的建立时间,需要要小于时钟周期;也就是说,你的那些延时不能过大,延时一旦过大,数据可能就不满足建立时间的关系,甚至还更新不了。举个例子说: 现在的节拍(0ns这一时刻)到来后,数据(比如说是一个高电平)从FF1的D端传来,经过组合逻辑,在下一个节拍(20ns这一时刻)的时候传到FF2的D端,更新FF2的数据(0ns时,FF2保存的是低电平),如红色箭头所示;但是由于延时太大,下一个节拍到来了(20ns到了),这个高电平还在还在组合逻辑那里,如绿色箭头所示,就导致了FF2的D端数据不能得到更新,或者不满足建立时间,由此可能引起锁存错误。当对时钟进行建模之后,拍长也就决定了,也就是那些延时(触发器的翻转时间/转换延时、寄存器与寄存器之间的组合逻辑延时、连线延时)最大是多少也就知道了,通过对时钟进行建模,也就是通过对寄存器与寄存之间的路径进行约束,DC就知道了这条路径运行的最大延时,就会选择合适的单元来满足这些延时的约束,如果DC选来选去,发现最牛逼的单元得到的电路延时还是很大,无法满足FF2的建立时间要求,DC就会报错。这个时候,你就要进行修改设计了(比如修改约束或者修改代码)。 为了满足FF2的保持时间,也就是数据经过上面的延时(触发器的翻转时间/转换延时、寄存器与寄存器之间的组合逻辑延时、连线延时)之后到达FF2的D端的时间,不能小于某个值。也就是说,这些延时也不能太小。举个极端的例子说,在0ns的时候,触发器有效沿到来,FF1和FF2都要更新下数据,FF1准备锁存高电平,FF2准备锁存低电平;由于FF1反应很快,电路延时很小,FF1寄存住的高电平很快就会传到FF2的D端口,高电平就会冲掉FF2要锁存的低电平(也就是FF2的D端口低电平还没有稳定,违反了保持时间),也就是说会导致低电平对FF2的保持时间不能得到满足,导致FF2更新数据失败,要锁存住的低电平可能就产生亚稳态。因此就有传输延时需要大于FF2的保持时间。此外,我们由此也可以知道,保持时间的分析比建立时间的分析提前一个时钟周期沿,也就是说在0ns时候传输数据,建立时间是在下一个时钟上升沿(20ns时刻)进行检查FF2的D端口数据是否稳定(若不稳定,就违反了建立时间),而保持时间是在发送数据的同一时刻(也就是0ns时刻)检查FF2的D端口数据是否稳定(如果不稳定,就违反了保持时间);关于保持时间的分析比建立时间的分析提前一个时钟周期沿这一点需要注意。 然而,保持时间一般是能够满足的,也就是传输延时一般是大于触发器的保持时间的,即使满足不了,在后端版图设计的时候,也可以有修改措施(比如路径加缓冲器增加延时)。因此我们在约束的时候,我们一般不关注保持时间,而是注重建立时间。 经过上面一大堆的废话,相信大家已经对这个约束过程有一定的了解了,下面进行概括一下,并进行时钟建模。 通过上面的讲解,我们知道,一般情况下,如果寄存器和寄存器之间组合电路的延迟大于Clock_cycle-Tsu(Clock_cycle是时钟周期,Tsu是触发器的建立时间),电路的功能会不正确,将不能正常工作。如果已知电路的时钟工作频率,就知道了寄存器和寄存器之间组合电路的最大时延,如下图所示: 图中路径X的最大时延应满足下列关系: Tclk-Q是FF2的从引脚CLK到引脚Q的延时,Tsetup是FF3的建立时间,这两个参数都由工艺库提供。总结完成之后,下面对时钟进行建模,也就是寄存器到寄存器之间的路径进行约束,时钟的建模是比较复杂的,因此先一步一步地讲解,最后给出约束脚本。 定义时钟时钟的命令为:create_clock。假设时钟周期为10ns,定义时钟的命令就是: create_clock -period 10 [get_ports clk] 对于的时钟波形为: 定义时钟时(虚拟时钟除外,虚拟时钟在后面说),我们必须定义时钟周期(也就是-period这个选项)和时钟源(端口或引脚)(也就是设计中的clk),也可以加上一些可选项(option)来定义时钟的占空因数(duty cycle),偏移(offset/skew)和时钟名( clock name),我们可以通过man create_clock 来查看命令的相关选项。 一旦定义了时钟,对于寄存器之间的路径,我们已经做了约束。我们可以用report_clock命令来查看所定义的时钟以及其属性。如果我们需要使用时钟的两个沿(上升沿和下降沿),时钟的占空因数将影响时序的约束。 然而单单定义一个时钟周期进行约束寄存器与寄存器之间的路径很显然是过于理想的,需要再添加其他的时钟属性,在添加之前,需要知道时钟的偏移(skew)、抖动(jitter)、转换时间(transition)、延时(latency)这几个概念或者这几个时钟的属性,这些属性请查看我的另一篇博文: http://www.cnblogs.com/IClearner/p/6440488.html 该博文详细介绍了时钟的建模,也就是路径2的约束。 ③路径1(输入端口到寄存器D端)的约束: 这里讨论的是模块前后使用的是同一个时钟CLK,如下图所示,至于使用不同的时钟(比如前面的模块是ClkA而不是Clk,那么约束就不一样了)放在后面说。 在上图中,在Clk时钟上升沿,通过外部电路的寄存器FF1发送数据经过输人端口A传输到要综合的电路,在下一个时钟的上升沿被内部寄存器FF2接收。它们之间的时序关系如下图所示: 对于我们要综合的模块,DC综合输入的组合逻辑,也就是上面的电路N,得到它的延时是Tn,但是这个Tn是否满足的要求(比如说满足触发器的建立时间)呢?在进行约束之前,DC是不知道的,因此我们通过约束这条路径,也就是告诉DC外部的延时(包括寄存器翻转延时和组合逻辑、线网的传输延时)是多少,比如说是Tclk-q+Tm,在约束了时钟之后,DC就会计算这条路径留给电路N的延时是多少,也就是Tclk-q+Tm。然后DC就拿Tclk -(Tclk-q+Tm)和Tn+Tsetup相比较,看Tclk -(Tclk-q+Tm)是否比Tn+Tsetup大,也就是看综合得到的电路N的延时Tn是不是过大,如果Tn太大,大于Tclk -(Tclk-q+Tm),那么DC就会进行优化,以减少延时。如果延时还是太大,DC就会报错。因此我们要进行输入端口的约束,告诉外部电路的延时是多少,以便DC约束输入的组合逻辑。 如果我们已知输入端口的外部电路的延迟(假设为4 ns,包括翻转延时和外部的逻辑延时),就可以很容易地计算出留给综合电路输入端到寄存器N的最大允许延迟: DC中,用get_input_delay命令约束输人路径的延迟: set_input_delay -max 4 -clock CLK [get_ports A] 我们指定外部逻辑用了多少时间,DC计算还有多少时间留给内部逻辑。在这条命令中,外部逻辑用了4 ns,对于时钟周期为10 ns的电路,内部逻辑的最大延迟为10 - 4 - Tsetup = 6 。 例如,对于下面的电路: 输入端口延时的约束如下所示: create_clock -period 20 [get-ports Clk] set_input_delay -max 7.4 -clock Clk [get-ports A] 对应的时序关系图如下所示: 如果触发器U1的建立时间为1ns,则N逻辑允许的最大延迟为: 20 - 7 .4 - 1 = 11 .6 ns 换言之:如果N逻辑允许的最大逻辑为11.6ns,那么可以得到外部输入最大的延时就是20-11.6-1=7.4ns. 上面是没有考虑不确定因素情况,当考虑不确定因素时,则有: 当有抖动和偏移的时候(假设不确定时间为U),如果触发器U1的建立时间为1ns,外部输入延时为D(包括前级寄存器翻转和组合逻辑的延时),则N逻辑允许的最大延迟S为: 20-D-U-1=S,同样可以得到外部输入的延时为:20-U-1-S=D 当输入的组合逻辑有多个输入端口时,如下图所示: 则可以用下面命令对除时钟以外的所有输人端口设置约束: set_input_delay 3.5 -clock Clk -max [remove_from_collection [all_ inputs ] [get_ports Clk] ] remove_from_collection [all_inputs] [get_ports Clk]”;#命令表示从所有的输入端口中除掉时钟Clk。 如果要移掉多个时钟,用下面的命令: Remove_from_collection [all_inputs] [get_ports “Clk1 Clk2”] ④路径3(寄存器到输出端口)的约束: 在了解了路径1的约束直接之后,路径3的约束就变得容易理解了,路径3与外部输出电路的的电路图如下所示: clk时钟上升沿通过内部电路的寄存器FF2发送数据经要综合的电路S,到达输出端口B,在下一个时钟的上升沿被到达外部寄存器的FF2接收。他们之间的时序关系如下图所示,我们要要约束的的组合路径电路S的延时,要DC计算它的延时是否能够满足时序关系,就要告诉DC外部输出的延时大概是多少: 当我们已知外部电路的延迟(假设为5.4 ns),就可以很容易地计算出留给要综合电路输出端口的最大延迟,如下图所示: DC中,用set_output_delay命令约束输出路径的延迟,对于上面的电路图,有: set_output_delay -max 5. 4 -clock Clk [get_ports B] 我们指定外部逻辑用了多少时间,DC将会计算还有多少时间留给内部逻辑。举个例子说,对于下面的这个电路模型: 寄存器到输出端口的时序路径约束为: create_clock -period 20 [get_ports Clk] set_output_delay -max 7.0 -clock Clk [get_ports B] 对应的时序关系图如下所示: 如果U3的Tclk-q = 1. 0ns,则S逻辑允许的最大延迟为: 20 - 7 .0 - 1=12 ns,也就是说如果S逻辑到最终的延时大于12ns,那么这条时序路径就会违规,DC就会报错。 上面是没有考虑抖动和偏移的,内部延时为S(包括clk-q和组合逻辑延时),外部输出延时为X(包括外部组合逻辑和后级寄存器的建立时间),时钟周期为T,那么就有: T-S=X,知道了最大的内部延时S,就可以算出输出外部允许的最大延时X 当考虑抖动偏移等组成的uncertainty因素时,假设不确定时间为Y,那么就有: T-Y-S=X,因此外部输出延时X,可以直接得到,也可以通过内部延时间S(和不确定时间Y)接计算得出X。 在这里说一下关于输入路径延时和输出路径延时的一些实际情况。 进行SOC设计时,由于电路比较大,需要对设计进行划分,在一个设计团队中,每个设计者负责一个或几个模块。设计者往往并不知道每个模块的外部输入延迟和/或外部输出的建立要求(这些要求或许在设计规格书里面写有,或许没有,当没有的时候设计者就不知道了),如下图所示: 这时,我们可以通过建立时间预算(Time Budget),为输入/输出端口设置时序的约束,也就是先预置这些延时,大家先商量好(或者设计规格书声明好)。但是预置多少才合适呢?就有下面的基本原则了: DC要求我们对所有的时间路径作约束,而不应该在综合时还留有未加约束的路径。我们可以假设输人和输出的内部电路仅仅用了时钟周期的40%。如果设计中所有的模块都按这种假定设置对输人/输出进行约束,将还有20%时钟周期的时间作为富余量( Margin),富余量中包括寄存器FF1的延迟和FF2的建立时间,即:富余量=20%时钟周期 - Tclk-q - Tsetup,如下图所示: 举个例子说,对于前面的电路,就要按照这么一个比例进行设置: 对应的约束为: create_clock -period 10 [get-ports CLK] set_input_delay -max 6 -clock CLK [all_inputs] remove_input_delay [get ports CLK] ;#时钟不需要输入延迟的约束 set_output_delay -max 6 -clock CLK [all-outputs] 如果设计中的模块以寄存器的输出进行划分,时间预算将变得较简单,如下图所示: 时间预算的约束为: create_clock -period 10 [get-ports CLK] set_input_delay -max KaTeX parse error: Expected 'EOF', got '#' at position 78: …t ports CLK] ;#̲时钟不需要输入延迟的约束 …Tclk-q] -clock CLK [all-outputs] ⑤路径4的约束 路径4是组合逻辑的路径,组合逻辑的约束可能需要虚拟时钟的概念。组合逻辑可能有两种中情况,一种是前面电路中的路径4: 模块里面有输入端口到输出端口的组合逻辑外,也有时序逻辑,也就是模块里面有时钟,那么就可以对于路径4,就下面的电路模型进行约束: 组合逻辑部分F的延时Tf就等于时钟周期T-Tinput_delay-Toutput_delay,时钟周期减去两端,就得到了中间的延时约束了,对于上面的模型,可以这样约束为: set_input_delay 0.4 -clock CLK -add_delay [get_ports B] set_output_delay 0.2 -clock CLK -add_delay [get_ports D] set_max_delay $CLK_PERIOD -from [get_ports B] -to [get_ports D] 当然,最后一句的约束可有可无。对于多时钟的同步约束,只需要修改相应的延时和时钟就可以了,可以参考前面的多时钟同步时序约束那里。 当考虑有不确定因素时,假设F的延时是F,外部输入延时为E(clk-q+组合逻辑延时),外部输出延时为G(组合逻辑延时+后级寄存器建立时间),不确定时间为U,时间周期为T,则有(最大频率下): T - F -E-U = G 另外一种是纯的组合逻辑,模块内部没有时钟: 这种时钟需要用到虚拟时钟的概念,后面介绍有虚拟时钟的约束时,再进行说明。 3、实战 首先设计的模块如下所示: 设计(约束)规格书如下所示: (时钟的定义) 寄存器建立时间定义) (输入输出端口的延时定义) (组合逻辑的定义) 上面的规格定义用来给我们进行时序约束使用,现在实践开始。 ·创建.synopsys_dc.setup文件,设置好DC的启动环境 –>common_setup.tcl文件: 由于这里有物理库,因此可以使用DC的拓扑模式进行启动。 –>dc_setup.tcl文件: >.synopsys_dc.setup文件: -------------------------------------这一步时间不够下可以忽略------------------ ·启动DC,查看target_library的信息 –>启动的时候,我们使用管道开关,把DC的启动信息保存到start_report.log里面(dc_shell -topo是DC的启动命令,启动时产生的信息,通过 | tee -i 流入start_report.log文件中): (我们也可以通过启动gui界面进行输入命令,也可以在shell中输入命令) –>由于我们仅仅是需要查看target_library库的信息,因此我们只需要读入库:read_db sc_max.db –>然后我们查看与这个库相关联的工艺库:list_libs,结果为: 我们可以看到,sc_max.db是target_library的的文件名称,而target_library的库名字是cb13fs120_tsmc_max –>接着我们查看库信息: 这里我们使用重定向的命令,将报告的结果保存到 lib.rpt这个文件中。redirect是重定向的命令,-file是将命令产生信息保存到文件中,lib.rpt是要保存信息到文件,后面的{}中存放的是要执行的命令。 然后在终端读取相应库的单位信息,时序单位为ns,电容单位为pf ·创建约束 在完成启动文件的书写之后,我就需要根据设计规格书,进行书写约束了 –>时钟的约束(寄存器和寄存器之间的路径约束): 1.时钟频率为333.33MHz,因此时钟周期就是3ns: create_clock -period 3.0 [get_ports clk] 2.时钟源到时钟端口的(最大)延时即source latency是0.7ns set_clock_latency -source -max 0.7 [get_clocks clk] 3.时钟端口到寄存器的时钟端口延时即network latency为0.3ns有0.03ns的时钟偏移: set_clock_latency -max 0.3 [get_clocks clk] 4.时钟周期有0.04ns的抖动 5.需要为时钟周期留0.05ns的建立时间余量 这里我们就要设置不确定因素了,由于设计规格声明是对建立时间留余量,因此我们主要考虑建立时间的不确定因素: 首先是时钟偏移为±30ps,则有可能是前级时钟往后移30ps,同时本级时钟往前移30ps,对于建立时间偏移的不确定因素为30+30 =60ps; 然后是时钟抖动,前级的时钟抖动影响不到本级,因此只需要考虑本级的时钟抖动,由于是考虑建立时间,因此考虑本级时钟往前抖40ps,即对于建立时间抖动的不确定因素为40ps; 最后是要留50ps的建立时间不确定余量; 因此对于建立时间,总的不确定时间为60+40+50=150ps=0.15ns: set_clock_uncertainty -setup 0.15 [get_clocks clk] 6.时钟转换时间为0.12ns: set_clock_transition 0.12 [get_clocks clk] –>输入延迟约束(输入路径的约束): 1.规定模块内data1和data2端口的逻辑S延时最大为2.2ns,并没有直接告诉外部逻辑的延时,因此我们需要计算: 外部最大的延时为:clock period - clock uncertainty - delay of S - register setup time = 3.0 - 0.15 - 2.2 - 0.2 = 0.45ns, 因此有: set_input_delay -max 0.45 -clock clk [get_ports data*] 2.对于sel端口,由于明显地、直接说了从外部数据发送端(指的是F3的clk)到sel端口的latest(最大)绝对延时是1.4ns,也就是说,这个绝对延时包括了时钟的latency延时,而input_delay是不包括的,input_delay是相对时钟的前级逻辑延时,是不包括时钟的latency,那么就需要减去时钟的latency(包括source 和 network): 1.4ns-(700ps + 300ps) = 0.4ns,那么就有: set_input_delay -max 0.4 -clock clk [get_ports sel] –>输出延时约束(输出路径的约束): 1.直接告诉了在out1的外部组合逻辑的最大延时为0.42ns,后级触发器的建立时间为0.08ns,也就是外部延时为0.42+0.08=0.5ns: set_output_delay -max 0.5 -clock clk [get_ports out1] 2.内部延时为810ns,应用前面的公式:时钟周期-内部延时(翻转与内部组合逻辑延时)-不确定时间=外部延时(外部组合逻辑+后级寄存器的建立时间),于是有:3-0.81-0.15=2.04ns,于是有: set_output_delay -max 2.04 -clock clk [get_ports out2] 3.意思是外部延时只有后级寄存器的建立时间要求: set_output_delay -max 0.4 -clock clk [get_ports out3] –>组合逻辑的约束: 根据前面的公式可以得到: 3-0.15-输入延时-2.45=输出延时,于是可以得到: 输入延时+输出延时 = 0.4ns 由于设计规格没有规定这个比例,因此只要满足输入输出延时的关系满足上面的式子都可以,如果综合后有违规,我们后面可以再适当调整一下,设置为: set_input_delay -max 0.3 -clock clk [get_ports Cin*] set_output_delay -max 0.1 -clock clk [get_ports Cout] –>检查语法: ·启动DC (·读入设计前的检查) ·读入设计(和查看设计) 这里和流程一样。主要是read、current_design 、link、check_design,这里就不具体演示了。 ·应用约束和查看约束 –>直接执行source scripts/MY_DESIGN.con进行应用约束 –>查看有没有缺失或者冲突的关键约束: check_timing,返回值为1,表示执行成功。 –>验证时钟是否约束正确: report_clock report_clock -skew report_port -verbose –>保存约束好的设计: write -format ddc -hier -out unmapped/MY_DESIGN.ddc ·综合 (简单的步骤跟流程一样) ·综合后检查(与优化) (简单的步骤跟流程一样) ·保存综合后的设计 (简单的步骤跟流程一样) |
|
|
|
只有小组成员才能发言,加入小组>>
4274个成员聚集在这个小组
加入小组3275 浏览 0 评论
航顺(HK)联合电子发烧友推出“近距离体验高性能Cortex-M3,免费申请价值288元评估板
4216 浏览 1 评论
4221 浏览 0 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-10 18:38 , Processed in 0.508291 second(s), Total 76, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号