数字IC知识点总结(2)-SOC·(中)
六.RTL代码编写
在编写RTL代码之前有很多设计问题必须解决,这些问题包括编码风格、可测性设计、设计复用等。同时,RTL阶段的优化还能直接影响后续设计的时序收敛。
- 与团队共同讨论设计中的问题:团队中每个成员必须清楚设计规则,如结构模块的命名规则和信号的命名规则、信号的有效状态,以及时钟和复位的控制形式。
- 根据芯片结构准备说明书:说明书包括描述I/O引脚数量及定义,封装形式,时序要求、模块接口信号定义和许多其他的重要细节。说明书包含以下内容:模块功能的简要说明;顶层模块的接口信号;所有控制寄存器地址及功能描述;顶层模块的主要结构图;子模块功能;子模块的接口信号;子模块的主要结构图;子模块的实现原理;时钟信号的连接;复位的信号连接。
- 对于总线,尽量使用单向总线,这是综合友好,DFT友好的设计的一部分。
- 模块的划分是将复杂的设计分割成小的模块,它的好处是区分不同的功能模块。每个模块的尺寸和大小都不能太复杂,便于数据文件的管理和模块的重复使用。划分模块的时候要注意以下几点:1.顶层模块要更加简单直观,保障只有顶层有I/O引脚。2.在对核心逻辑进行模块划分时,要避免子模块间出现连接用的粘附逻辑。(如图)尽可能把相关的组合逻辑集中到一个模块中处理。因为综合器在默认的工作模式下进行综合优化时,不能跨越模块边界对相关的组合逻辑做归并优化处理。3.把多周期路径或伪路径,尽可能多的把这些逻辑限制在一个模块中,并在代码编写的时候用注释明确指出。多周期路径是指从一个寄存器的输出到另一个寄存器的输入路径延时超过了一个时钟周期的延时。4.尽量根据时钟的相关性来区分模块,将时钟分频、门控单元和复位产生等电路尽量放在同一个单元方便在综合的时候设置时钟约束。
在设计是,对视中也要有特定的处理。首先IP使用要注意以下方面1.确定使用IP的模块,需要购买哪些IP。2.模块之间的接口协议要尽量简单,模块间的接口定义要尽可能的与国际上通用的接口协议完全一致。3.注意积累IP与IP集成的经验。
除此之外,设计时还要考虑对可测性,对芯片速度,对布线的安排。
遵循这样的规则,我们在编写可综合的RTL代码时就要有更多的考虑:
(一).命名
对文件进行命名
- 顶层模块应该以芯片的名称来命名。
- 在顶层模块中,除I/O引脚和不需要综合的模块外,其余的作为次级顶层模块,建议以xxx_core.v命名
- 对于多处理器的设计,共享模块以(模块名_处理器名)命名
- 如果实例化一个模块,则实例化名最好与模块名相同,如果要实例化多次,则用下标来区分。
- 命名应该与该模块的功能相结合
对信号进行命名命名
- 低有效的信号一律加下划线和字母n或者b结尾
- 总线由高位到低位命名
- 不需要在信号名中加信号的方向
- 命名应该具有全局一致性。一些全局的信号在各个模块中的名应相同,注释指出输入输出的方向。
- 信号列表中,将clk。reset等扇出比较大的信号列在最后
(二).编码风格
- 注意缩进
- 对于时序单元使用非阻塞赋值
- 组合逻辑采用阻塞赋值
- 不要把阻塞赋值和非阻塞赋值混合在一个模块
- 保证敏感列表的完整性
- 尽量不要使用循环结构
- 对代码加上适量的注释
(三).综合考虑
- 每个模块尽量只使用一个主时钟
- 复位信号以reset命名表示高有效,reset_n代表低有效,一般来说复位为异步信号。
- 模块的分割最好能够使得在模块内部的输入和输出端直接和触发器相连接。
- 不在数据通路上的触发器都要有复位信号。
- 数据通路上的寄存器的复位信号根据流水线的划分来设置。
- 如果电路中同时存在具备复位信号的触发器何不具备复位信号的触发器,不要将它们放入一个程序块中。
- 在case语句中,指明所有可能出现的情况,如果不需要所有情况,加上default语句。不要使用casex和casez
- 代码描述尽量简单,尽量保持代码的可读性。
- 内部逻辑尽量避免使用三态门。
- 避免触发器在综合时产生锁存器,在if-else语句中覆盖到所有情况。
- 尽量避免异步逻辑,带有反馈环的组合逻辑及同步逻辑。
七.同步电路设计及其异步信号交互的问题
(一)同步信号设计
所谓同步电路设计,就是电路中所有受时钟控制的单元,如触发器或者寄存器,全部都由统一的全局时钟控制。
时序电路的第一个首要问题就是触发器的时序收敛问题。触发器的时序收敛保证了触发器的输入端的数据在时钟信号的有效沿到来之前就达到稳定状态,既满足触发器的建立时间,同时也保证了触发器输入端时钟有效沿过后的一段时间内保持稳定,既满足触发器的保持时间。
同步电路有他受到EDA工具的广泛支持,EDA工具可以保证电路系统的时序收敛,有效地避免了电路设计中的竞争冒险现象。触发器只在时钟边缘才改变取值,很大程度上减小了整个电路的毛刺和噪声的影响。但是他也有因为版图上触发器时钟端口距离各个个时序单元距离不同导致的时钟倾斜。一般使用时钟树来解决这个问题。
(二)全异步电路设计
全异步设计与同步设计最大的不同就是他的电路中数据传输可以在任何时间发生,电路中没有一个全局的或者局部的控制时钟。没有了全局时钟控制下的定式关系,全异步电路会遇到毛刺,竞争冒险等问题。为了解决这些问题,我们使用自定时的方法,要求每个单元,都能通过一个开始信号来启动,从而开始对输入的数据进行计算,一旦计算完毕,再发出一个done信号。除此之外,还需要ack来表明他们是否准备好接受下一个输入字的准备,req来表明他们的输出端是否有一个做好准备的合法数据。自同步方法的实现离不开握手协议。一般使用四相位握手。
(三)异步信号与同步电路交互的问题及解决办法
在SoC设计中,必然会出现面临处理来自不同时钟域的数据的问题,这里称之为异步信号与同步电路的交互问题。
多个时钟驱动的电路设计不能像同步电路一样,简单的满足各个寄存器的建立保持时间就可以准确无误的运行下去。,异步时钟一般是频率不同的两个时钟,或上升沿不对齐的两个时钟,异步时钟域发生信号之间的交互的时候,会发生违反了触发器保持或建立时间且使触发器锁存到了一个无效电平状态叫做亚稳态。这个亚稳态x与逻辑上的x并不一样。为了解决亚稳态,我们认为的制造一个延时,这个延时由在采样异步信号的触发器后再加一级触发器,异步信号在经过目的时钟域两集触发器采样之后才会对后续电路起作用。这种逻辑叫做同步器。RTL代码如下
module synchronizer( bclk, rst_n, adat, bdat ); input bclk; input rat_n; input adat; output bdat; reg bdat1; reg bdat2; always@(posedge clk or negedge rat_n) if(rst_n) { bdat1, bdat2} <= 2'b0; else { bdat1, bdat2} <= { adat, bdat1}; assign bdat = bdat2; endmodule
其实完全解决亚稳态的方法并不存在,如果时钟频率过快,那么那么需要增加更多级触发器。
除过亚稳态之外,还有异步控制信号的同步。
首先是快时钟同步慢时钟域下的异步控制信号。这种情况下,有可能异步控制信号在自己的时钟域中只维持了一个时钟的有效时间,但是已经被快时钟域采样了几次。我们可以使用下面这种方法来解决:
module synchronizer( clk_fast, rst_n, rd_en, rd_en_s2f ); input clk_fast; input rst_n; input rd_en; output rd_en_s2f; reg rd_en_s2f1; reg rd_en_s2f2; reg rd_en_s2f3; always@(posedge clk or negedge rst_n) if(!rst_n) {rd_en_s2f3,rd_en_s2f2,rd_en_s2f1} <= 3'b111; else {rd_en_s2f3,rd_en_s2f2,rd_en_s2f1} <= {rd_en_s2f2,rd_en_s2f1,rd_en}; always@(rd_en_s2f2 or rd_en_s2f3) case({rd_en_s2f3,rd_en_s2f2}) 2'b01:rd_en_s2f <= 1'b1; default: rd_en_s2f <= 1'b0; endmodules
慢时钟域同步快时钟域下的异步控制信号,可能会导致慢时钟到来之前以及无效,从而丢失了控制信号。这里我们使用两种握手方法来解决
第一种握手方法,是在快速时钟域中加入反馈信号,到异步时钟接收方确认已经收到这个有效信号之后再使信号失效。但是这种并不能解决在一个时钟周期内的多次翻转。
第二种方法是,当发现控制信号有效之后,由组合逻辑发出停止时钟的stall_n信号,该信号使快时钟失效,快时钟驱动的输出的控制信号保持不变,这样会保证慢时钟能采到有效地控制信号。stall_n的生成设计如下
module stall_logic( rd_en, rd_en_ack_s2f, stall_n ); input rd_en; input rd_en_ack_s2f; output stall_n; always@(rd_en or rd_en_ack_s2f) if(rd_en_ack_s2f) stall_n <= 1'b1; else if(rd_en) stall_n <= 1'b0; else stall_n <= 1; endmodule
异步时钟数据同步同样也有握手机制和FIFO机制,这里不再赘述。