0%

FSM

Moore & Mealy FSM

image-20230703104808208

整体风格

1-always (X)

x

2-always

  1. sequential state register
  2. combinational next-state & combinational output logic

Coding Style Notes:

  1. Parameters instead of the Verilog `define macro definition are used to define state encodings.
  2. Combinational always block sensitivity list is sensitive to changes on the state variable and all of the inputs.
  3. Combinational always block has a default next state assignment at the top of the always block (X to debug, or IDLE, or just set to the value of the state register).
  4. Default output assignments are made before coding the case statement (this eliminates latches ,reduces the amount of code required, highlights in the case statement exactly in which states the individual output change).
  5. There is an if-statement, an else-if-statement or an else statement for each transition arc in the FSM state diagram.

状态编码

二进制编码

  • 需要的 FF 少,但是组合逻辑多。
image-20230728090441455

onehot 编码

  • 需要的 FF 多(无所谓),但是组合逻辑可以得到优化(速度快)。
image-20230728090452696

简化 onehot 编码

image-20230728090511008

parameter 代表的不是编码(encode),而是对寄存器相应 bit 的索引(index)。

零空闲 onehot 编码

  • 将 ERROR 或 IDLE 编码设为 0 。
  • 重置时将 state 置为全 0 ,再设置相应位。
image-20230728090641145 image-20230728092837148

输出生成

输出为组合逻辑(2-always & assign)

  • 输出可能有毛刺。
  • 输出逻辑云消耗了下一级模块的可用时钟周期。
  • 时序约束复杂。
image-20230718150330200

Moore 机同步输出

设计分区(cloud-register)

  • 通过 FF 同步输出。
  • 简化了综合时的时序约束任务。
image-20230718145941724

3-always

(?)state & next_state

image-20230718150710432

状态编码输出

  • 输出即为状态编码的某些位,需要根据输出对状态编码进行设计。
  1. 将状态与输出列成表格。
  2. 发现有多个状态对应同一种输出,则需要添加 n 位状态编码,以消除状态编码重叠。
image-20230728092053223 image-20230718151924825

Mealy 机异步输出

通常可以将输出逻辑移到下一级的输入逻辑。

image-20230718152241337 image-20230718152300409

Synopsys FSM Tool

Full_case/ Parallel_case

?

state encode

对比

image-20230703103918442

FIFO1

  • FIFO 常用于数据的跨时钟域传输
  • FIFO 设计难点在于读/写指针的生成和空/满状态的判断。

同步 FIFO

  • 对写入和读取进行计数,增加(写入但不读取)、减少(读取但不写入)、保持(不读也不写/同时读写)。
  • 当计数为 0 ,为空状态;当计数为满,为满状态。

异步 FIFO

  • 异步 FIFO 不能使用计数方法,因为读写端口时钟域不同。
  • 为了判断空/满状态,必须对读/写指针进行比较。
  • 读指针指向要读取的地址,写指针指向要写入的地址。在读取/写入之后,读/写指针递增。
  • 当 FIFO 为空,读指针指向无效数据地址。进行写操作之后,空标志清零,读指针变为有效,立即将有效数据驱动到输出口,供接收端读取。

读/写指针的生成(计数器)

#1 寄存器输出是格雷码

  • 格雷码和二进制码转换、递增,得到 n 位格雷码计数器。
  • 将最高两位异或,与剩余位拼接,得到 n-1 位格雷码计数器。

#2 寄存器输出分别是二进制码和格雷码

  • n-1位二进制码用于寻址 FIFO地址。
  • n 位格雷码用于同步到另一个时钟域。
  • 若不使用 extra bit ,则都是 n-1 位。

指针计数信号的跨时钟域同步

  • FIFO 指针常用格雷码计数,每个时钟边沿只会有 1 位变化,避免多 bit CDC 问题。

  • 指针计数是否会在采样间隔中持续变化甚至上溢、下溢?答案是不会。

    • FIFO 满:写指针追上同步采样的读指针。同步采样的读指针可能不是读指针的实时值,但写指针不会超过同步采样的读指针,所以上溢不会发生。
    • FIFO 空:读指针追上同步采样的写指针。同步采样的写指针可能不是写指针的实时值,但读指针不会超过同步采样的写指针,所以下溢不会发生。

二进制计数器

如果对二进制指针进行采样,并在两个时钟域之间使用握手信号来安全传递,则可以使用二进制指针设计 FIFO 。

优点

  1. 采样+握手信号,可以用于进行任意比特信号的跨时钟域传递。
  2. 触发器数量:格雷码需要 2n 个触发器,同步模块需要 2n+4 个触发器(每位 1 个触发器,同步 ready 信号 2 个触发器,同步 acknowledge 信号 2 个触发器)
  3. 格雷码只能递增,二进制码则可以任意改变。
  4. 二进制指针允许任意深度的 FIFO ,而格雷码只能是 2 的 n 次方。
  5. 二进制指针容易计算 almost full & empty 。

缺点

  1. 采样并握手,会延迟新样本的采样,至少从接收时钟域延迟两个时钟,从发送时钟域延迟两个时钟。

方法

  • 使用寄存器采样指针计数信号,并向接收时钟域输出 ready 信号。
  • 当 ready 信号被接收时,接收时钟域向发送时钟域发回一个同步的 acknowledge 信号。
  • 在从接收时钟域接收到 acknowledge 信号之前,采样的指针计数信号不得改变。
  • 接收到 acknowledge 信号后,发送时钟域有权清除 ready 信号并重新采样指针计数信号。

空/满标志的生成

#1 增加额外位 extra bit

  • 空 empty

    • rptr next 和 synchronized wptr:
      • extra bit 相同,其余位也相同
    image-20230709161731837
  • 满 full

    • wptr next 和 synchronized rptr:
      • MSB 不同
      • 2nd MSB 也不同(格雷码特性)
      • 其余位相同
    image-20230709161707621 image-20230710164058628
    • almost empty:rptr+4 赶上 wptr

    • almost full:wptr+4 赶上 rptr

#2 趋势信号 direction

  • 将地址空间分为四个象限,通过两个 MSB 生成趋势信号 direction 。
image-20230714152857811 image-20230714152311605 image-20230714152835265
  • 空满:
image-20230714152931113

FIFO #1 同步比较

  • fifo1.v :顶层模块

  • fifomem.v :FIFO memory buffer,此处是一个双端口 RAM 。

    • 注:双端口 RAM 面积比单端口 RAM 大一倍,所以要注意控制 FIFO 深度。
  • sync_r2w.v :将 read pointer 同步到 write pointer 时钟域,仅包含触发器。

  • sync_w2r.v :将 write pointer 同步到 read pointer 时钟域,仅包含触发器。

  • wptr_full.v :生成 write pointer 和 full flag 。

  • rptr_empty.v :生成 read pointer 和 empty flag 。

问题

时钟不同速的问题

n

保守的 full & empty flag 清除策略

异步复位时多位同时跳变的问题

n

FIFO #2 异步比较

image-20230714151224529
  • fifo1.v :顶层模块

  • fifomem.v :FIFO memory buffer,此处是一个双端口 RAM 。

    • 注:双端口 RAM 面积比单端口 RAM 大一倍,所以要注意控制 FIFO 深度。
  • sync_cmp.v :生成异步的 afull_naempty_n 信号。

  • wptr_full.v :生成 write pointer 和 full flag 。

  • rptr_empty.v :生成 read pointer 和 empty flag 。

空/满

  • 异步生成 afull_naempty_n 信号。
  • 异步设置 remptywfull 信号,同步释放。
image-20230714154212501

复位

  1. 清除 wfull ,而不清除 rempty
  2. 复位 wptrrptr
  3. 清除 direction
  4. 异步产生 aemtpy_n ,进而清除 rempty

仅用于测试的 FIFO 行为模型

image-20230709085504647

CDC

亚稳态 metastability

  • 亚稳态:由于不满足建立/保持时间,触发器在时钟边沿采样到变化中的信号,触发器输出信号在一段时间内不处于稳定的 0/1 状态,经过一段稳定时间 tMET 后恢复正常(可能是 0 也可能是 1 )。

    image-20230712104219852
  • 亚稳态不能完全避免。

  • 亚稳态会穿越后续电路,导致非法信号值传播到整个设计的其余部分。

    image-20230712104243496

同步器 Synchronizer

两种同步场景

  • 不需要采样每个值(允许漏采样),但是需要采样准确。(如 FIFO )
  • CDC 信号必须被正确地接收,然后 CDC 信号才能变化。

2 个触发器的同步器

  • 第一级触发器将异步输入信号采样到新的时钟域,并等待一个时钟周期,使第一级输出信号上的亚稳态衰减。
  • 第二级触发器采样,得到稳定有效的信号。
  • 对于更高速的设计,亚稳态经过一个时钟周期仍然无法恢复,需要增加触发器数量,在后续几级内应恢复正常。
image-20230712104308950

同步器的 MTBF

  • 理论上,当第二级触发器采样时,第一级触发器输出可能仍处于亚稳态,导致第二级输出信号仍为亚稳态。
  • MTBF 表示发生潜在故障之间的间隔时间。
  • MTBF 随时钟频率和数据变化频率增加而减少,即故障发生的频率更高。
image-20230712103600309

问题:在接收时钟域同步而不在发送时钟域同步?

将 CDC 信号在接收时钟域同步,是否就不需要在发送时钟域同步寄存?

  • 答案是否定的。这会导致更容易采样到变化中的信号,导致亚稳态。
image-20230712110632012

快/慢时钟域之间的信号传输

慢时钟 -> 快时钟

若快时钟的频率至少是慢时钟的 1.5 倍,则信号一定会被采样 1-2 次,不存在漏采样的问题。

快数据变化频率 -> 慢时钟域

  • 来自发送时钟域的信号在被采样之前可能会变化两次,或者可能太接近较慢时钟域的采样边缘。
  • 若不允许漏采样:
    • 开环方法:不需要 acknowledge 信号
    • 闭环方法:需要接收时钟域的 acknowledge 信号

三个边沿要求

  • 对于 2 触发器同步器,CDC 信号保持稳定的时间至少是接收时钟域的 3 个时钟沿。(CDC 信号必须宽于接收域时钟的周期宽度的1-1/2倍)

问题

1 传输窄脉冲

当发送时钟域频率高于接收时钟域,且 CDC 信号脉宽仅为发送时钟域的一个周期:

  • 可能漏采样。
image-20230713164052708

2 采样宽脉冲

当发送的脉冲宽度稍大于接收时钟周期:

  • 可能脉冲上升沿和下降沿与接收时钟边沿太接近,不满足第一个时钟沿的建立时间、第二个时钟沿的保持时间,导致不能形成预期的输出。
image-20230713164752423

解决方法

开环法

在超过采样时钟周期的一段时间内断言 CDC 信号,最小脉冲周期是采样时钟周期的 1.5 倍。

  • 优点:不需要 acknowledge 信号,快。
  • 缺点:设计要求变化时,需要重新分析。可用 SystemVerilog 断言检测输入脉冲是否未能超过“三个边沿”设计要求。
image-20230713172325392

闭环法

将使能控制信号同步到接收时钟周期,再将确认信号同步回发送时钟周期。未收到确认信号之前,发送信号不能改变。

  • 优点:安全。
  • 缺点:在允许控制信号改变之前,在两个方向上同步信号可能导致相当大的延时。
image-20230713172941684

多 bit CDC 信号传输

发送时钟域同步输出的多个信号会存在数据变化偏差(skew),可能导致接收时钟域不能同时采样到正确信号。

解决方法

  • 多位信号整合
  • 多周期路径
  • 用格雷码传输

多位信号整合

同时传递两个控制信号

两个控制信号存在小偏差,导致在接收时钟域的不同时钟沿被采样。

解决方法

en load 合并成 lden 信号。

image-20230714085740888

两个相移控制信号

接收时钟沿可能恰好在两个相移控制信号之间,导致在接收时钟域的使能控制信号链中形成单周期间隙。

image-20230714085939523

解决方法

合并控制信号,在接收时钟域内生成第二相移流水线使能信号。

image-20230714090427108

当必须要传输多 bit CDC 信号

1 多周期路径 MCP

发送不同步的数据同步的控制信号

  • 数据信号异步发送并保持稳定,在目标寄存器的输入上建立。
  • 控制信号翻转,经过两个周期的同步,再通过一个脉冲发生器,产生 1 个周期的控制脉冲。

优点:

  • 不需要计算发送信号最低要求的脉宽。
  • 控制信号只需要翻转,不需要返回初始值。
image-20230714101743531 image-20230714104222263 image-20230714102953630

1 带反馈的 MCP

确认反馈信号 b_ack 生成确认脉冲 aackaack 用作小型 READY-BUSY、1 状态 FSM 的输入,该模块生成就绪信号 aready 以指示现在可以安全地再次更改 adatain 数据和 asend 控制信号。

image-20230714111049773

2 带 acknowledge 反馈的 MCP

接收时钟域有一个 WAIT-READY、1 状态 FSM,当数据寄存器的输入上的数据有效时,它会向接收逻辑发送有效信号 bvalid 。在接收逻辑通过断言 bload 信号确认应加载数据之前,不会实际加载数据。在数据加载之前,不会向发送时钟域提供反馈。加载后才会将 b_ack 信号发送回。

image-20230714111018035

2 FIFO

计数器

对于计数器而言,跨时钟域传输时,通常不需要采样计数的每一个值(允许漏采样)。

格雷码 -> 二进制码
  • 原理:
image-20230714113840300
  • 简单赋值:
image-20230714113437356
  • for 循环:无法编译
image-20230714113548321
  • for 循环:可以编译
image-20230714114043250 image-20230714114123315
二进制码 -> 格雷码
  • 原理:
image-20230714114354144
  • 右移异或:
image-20230714114335407
二进制计数器

二进制计数器在计数时会发生多位同时变化,可能由于数据偏差导致错误采样。

格雷码计数器

格雷码每次计数仅有一位发生变化,消除了多 bit CDC 问题。

#1
image-20230714114610999 image-20230714114634272
#2
image-20230714114808238 image-20230714114822855

使用 2 个寄存器的 1 位深异步 FIFO

  • 计数器:1 位深的格雷码/二进制码计数器只不过是一个翻转寄存器。
  • 空/满标志:用翻转的 empty/full 表示 rrdy/wrdy 。
  • 写入 1 个数据/控制信号后,FIFO 变满。此时 wptr 指向 RAM 的第二个地址,即 wrdy 有效后 wptr 已经指向可以写的地址;读端也是一样。
  • 使用这种方法,我们能够从发送 MCP 中删除一个时钟周期,并从确认反馈路径中删除另一个时钟周期。(?)
image-20230714144447097

命名约定和设计分区

命名约定

  • 对于时钟信号及由其产生的信号,使用前缀字符来标识信号所处的时钟域。

设计分区

  • 每个模块仅允许一个时钟。
  • 设计时,将各模块划分为单时钟模块,便于 STA 分析。
  • 使用同步器模块进行跨时钟域信号传输。

静态时序分析 STA

NBA

竞争 Race Condition

  • 根据 IEEE Verilog 标准,一些语句具有规定的执行顺序,另一些语句则没有规定的执行顺序。
  • 当多个语句在相同的仿真时间点执行时,语句的执行顺序未知,可能产生不同的结果,即 Race Condition 。

Blocking or Nonblocking: Simulation

Guidelines:组合逻辑电路用阻塞赋值,时序逻辑电路用非阻塞赋值。

忽略上述准则仍然可以综合出正确的电路,但仿真可能与实际电路的行为不匹配。

阻塞赋值 Blocking Assignment

在不被任何其他 Verilog 语句中断的情况下,计算 RHS 并更新 LHS 。

当一个过程块中的 RHS 也是另一个过程块中的 LHS 时,并在同一个仿真时间执行,就会出现 Race Condition 。

非阻塞赋值 Nonblocking Assignment

  1. 计算 RHS
  2. 更新 LHS

非阻塞赋值并行执行。

非阻塞赋值仅用于 reg 类型,即只用于过程赋值块内。

Coding Guideline

  1. 对时序逻辑电路建模时,使用非阻塞赋值 <= 配。
  2. 对锁存器建模时,使用非阻塞赋值 <=
  3. 使用 always 块对组合逻辑电路建模时,使用阻塞赋值 =
  4. 在同一个 always 块中混合建模组合逻辑和时序逻辑电路时,使用非阻塞赋值 <=
  5. 不要在同一个 always 块中混合使用阻塞 = 和非阻塞赋值 <=
  6. 不要在多个 always 块中对同一个变量赋值。
  7. 使用 $strobe 来打印非阻塞赋值 <= 的变量。
  8. 不要使用 #0

Verilog 仿真的分层事件队列(Stratified Event Queue)

  1. Active
  2. Inactive
  3. NBA
  4. Monitor/Postponed

情况

自触发的 always 块(Self-triggering Always Block)

流水线建模(Pipeling Modeling)

  • 1 个 always 块,阻塞赋值(需要仔细安排赋值顺序)
  • 多个 always 块,阻塞赋值(错误)
  • 1 个/多个 always 块,任意顺序的非阻塞赋值

触发器

1
2
3
4
5
6
7
8
9
10
module dff(
input clk, rst_n, d;
output q;
)
reg q;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) q <= 1'b0; // 使用阻塞赋值 = 有缺陷
else q <= d;
end
endmodule

带反馈的时序逻辑电路建模(Sequential Feedback Modeling)

Linear Feedback Shift-Register (LFSR)

使用 <=

1
s

Combinational Logic: Blocking Assignment

Mixed Sequential & Combinational Logic: Nonblocking Assignment

(X) Mixed Blocking & Nonblocking Assignment

(X) Multiple Assignment to Same Variable

对于非阻塞赋值的一些错误认知(Common Nonblocking Myths)

  • (✗)在同一个 always 块中对同一个变量进行多次非阻塞赋值 <= 是未定义的行为

    (✓)最后一个赋值语句胜出

  • (✗)$display() 对于非阻塞赋值的变量无效

    (✓)非阻塞赋值在 $display() 之后生效,用 $strobe

  • (✗)#0 语句在每个 time step 的最后生效

    (✓)#0 语句在每个 time step 的 Inactive 阶段生效

仿真与综合电路行为的不匹配

always 敏感列表

不完整的敏感列表

综合仍会推断出正确的电路(与门),但仿真行为与综合电路不符。

image-20230720143819953

完整的敏感列表,但赋值顺序错误

综合仍会推断出正确的电路(与或门),但仿真行为与综合电路不符。

image-20230720144229002

函数

函数会被综合为位组合逻辑,而如果函数中的代码对应锁存器,则综合前仿真视为锁存器,综合后仿真却视为组合逻辑,出现不匹配。

image-20230720144650001

Case

Full Case

// synopsys full_case 表示所有可能的 case 都已经列出,未列出的都是“不关心”的。

  • 推导出 4 个 3 输入与门。
image-20230720145600525
  • 推导出 4 个 2 输入与门,en 信号被优化掉了。
image-20230720145622717

Parallel Case

// synopsys parallel_case 告诉综合工具应并行检验所有条件,即使存在重叠的条件。

重叠的条件平常会推导出优先级编码器,但在 parallel case 中会推导为无优先级。

  • z 的 case 条件优先级高于 y ,所以 y 的条件中必须排除与 z 重叠的部分,推导出优先级。
image-20230720151307235
  • parallel case 中,各个条件无优先级,故各自 zy 各自推导出 2 输入与门。
image-20230720151449582

casex

casexx 态视作不关心。当信号被初始化为未知状态时,可能会导致 x 态传播,但 casex 不能检测到这样的问题。

  • en 因为某种原因变为 x 态,casex 仍会根据 addr 进行匹配、执行,掩盖了 x 态的问题。addr 也是一样。
image-20230720152943467
  • 不要使用 casex ,最好使用 casez

casez

casezz 态视作不关心,可能会导致 casex 一样的问题。

  • 但是用casez 于建模地址解码器和优先级编码器时非常有用,可以用 ? 去匹配。
image-20230720155035661

初始化

赋值 x 初始化

x 对于verilog仿真器是未知,对于综合工具是不关心(利于综合优化)。

  • 赋值 x 可能导致仿真综合不匹配,但是在 FSM 设计中可能有用,可以在仿真时检测错误的状态转换。
  • s == 2'b11 时产生不匹配,但是若 2'b11 是不期望达到的状态,可以用于仿真调试。
image-20230720155817399
  • 这种方法可以综合成更小、更快的设计。
image-20230720160434574

使用 translate_on/translate_off 初始化

image-20230720162603383

DFF:translate_on/translate_off 的一般用法

  • translate_on/translate_off 在打印信息的时候很有用,但用于建模功能却很危险。
  • 一个例外是带异步复位和置位功能的 DFF ,典型的编码风格可以综合出正确的电路,并在 99% 的时间中仿真正确。
image-20230720163244781
  • 但是,当 rstnsetn 都拉低后,rstn 再拉高,此时 q 却未被置位。需要使用不可综合的构造才能完全与综合后的电路行为 100% 匹配。
image-20230720163442624

延时

在赋值左侧设置延时可能导致仿真与综合电路行为不匹配。

  • in 发生改变,经过 65 个时间单位才退出 always 块,期间 in 再次发生改变也不会再次进入 always 块,计时期间其他事件都被忽略。这显然与综合后电路的行为不符。
image-20230720164319176

复位

  • 对于单个 ASIC 而言,复位的主要目的是强制 ASIC 设计(RTL 等)进入已知状态,以进行仿真。
  • ASIC 制作完后,是否需要复位由一些因素决定。如果设计得当,使所有未知状态都指向合法初始状态,则不需要复位
  • 如果 ASIC 中的状态机在综合时利用了 don't care 优化,则需要复位。

带复位触发器的建模

  • 一个给定功能的所有触发器都应该在一个过程块中建模,例外是下述的同步触发器 & 无复位跟随器。
  • 触发器的 always 块中都应用非阻塞赋值 <=

同一个 always 中建模不同类型的触发器

  • 错误:在一个 always 中混合建模同步复位触发器跟随触发器,导致 rst_n 被推导为跟随触发器的使能信号 ld
image-20230721090540867
  • 正确:分别在两个 always 中描述。
image-20230721090818529
  • 不过,如果是异步复位,则会推导出相同的电路。

同步复位

编码风格

image-20230721093843824

综合电路

  • (#1)触发器无同步复位端口,复位信号为 d 输入逻辑云的一部分。
    • 复位树扇出高,相对于时钟而言可能是一个晚到信号,所以尽量限制复位信号到 d 之间必须穿越的逻辑。
    • sync_set_reset 可以帮助推导这种触发器,但是需要注意前后仿不一致的问题。(?)
image-20230721092304457 image-20230721092656483
  • (#2)触发器有同步复位端口,复位信号直接接入。

优点

  • 可以过滤复位毛刺。但如果毛刺发生在时钟边沿附近,会导致亚稳态。
  • 确保电路 100% 同步
  • 面积更小,但可能不明显。
  • 对于基于周期的仿真器,效率更高。
  • 在某些设计中,复位必须由一组内部条件产生,此时建议用同步复位,可以过滤时钟之间的逻辑等式故障。(?)
  • 通过使用同步复位和多个时钟作为复位过程的一部分,可以在复位缓冲树内使用触发器,帮助缓冲树的时序保持在一个周期内。(?)

缺点

  • 可能需要脉冲拉伸器,以保证复位脉冲足够宽,保证被时钟边沿采样。
  • 仿真器阻塞。(?)
  • 为了防止上电时三态总线上的总线争用,必须要有异步复位功能。

异步复位

异步复位最大的问题在于复位释放。

编码风格

Synopsys 要求,如果敏感列表中有任一个信号是边沿敏感的,则所有信号都必须是边沿敏感的。

image-20230721103038347

时序约束

复位缓冲树?

同时具有异步复位和异步置位功能的触发器

  • rst_nset_n 都拉低后,rst_n 再拉高,此时 q 却未被置位:仿真综合电路行为不匹配。
  • 需要使用 synopsys translate_off/on 和不可综合的构造才能与综合后的电路行为 100% 匹配。
image-20230721103636255

优点

  • 只要器件库有异步复位的触发器,就能保证数据通路是干净的,不会综合出额外的逻辑。
    • 那些对数据通路时序要求极高的设计,无法接受因同步复位(#1)而在数据通路中插入额外的逻辑、增加额外的延时
    • 不过如果器件库也有同步复位的触发器,并且设计人员让 Synopsys 能实际使用这些端口,则不成立。
image-20230721104642459
  • 电路可以在有时钟和没时钟的情况下复位。

缺点

  • 异步复位如果在有效时钟沿释放,会导致亚稳态问题。
  • 电路板上的噪声或故障或系统复位可能造成假复位。(?)
  • 使用异步复位的设计很难进行静态时序分析 STA ,必须对同步和异步复位的复位树进行时序分析,以确保在一个时钟周期内释放复位。复位树的时序分析必须在布局后进行,以确保这一时序要求。(?)
  • 对于 DFT ,如果异步复位不是由 I/O 引脚直接驱动,则在 DFT 扫描和测试时必须禁用来自复位驱动的复位网。(?)

异步复位问题

image-20230721111556348

复位恢复时间 trec

  • 复位恢复时间指从复位释放到下一个时钟有效边沿之间的时间,需要满足建立时间tsu,否则会导致亚稳态或信号完整性的问题。
  • Verilog 2001 有三个内指命令用于模拟和测试恢复时间和信号移除时序检查:$recovery $removal $recrem

复位去除时间

跨越不同时钟周期的复位释放

由于复位信号到达不同触发器时间存在偏差,可能导致两个触发器在不同的时钟周期释放复位。

复位信号同步电路

每个使用异步复位的 ASIC 都应该包括一个复位信号同步电路。

  • 第二个触发器用于消除复位释放靠近时钟边沿导致的亚稳态。
image-20230721113130845 image-20230721112157458
  • 同步复位信号经过 clk-q 延时、复位分配树延时到达,还需要满足目标触发器的复位恢复时间。(T < tclk-q + tpd + trec + tsu?)
image-20230721112835008

复位分配树

  • 复位树通常具有和时钟树一样多的负载。

复位信号的偏移(skew)

  • 复位树与时钟树的区别是偏移(skew)的平衡。
    • 与时钟信号不同,只要复位信号的延时足够短,能够在一个时钟周期内传播到所有复位负载,并仍满足目标触发器的复位恢复时间要求,那么复位信号之间的偏移(skew)就无关紧要。
image-20230721113330546

复位树和时钟树的时序(复位同步电路的时钟来源)

必须根据 clk-q 复位树的时序分析时钟树时序。

  • 为复位树提供时钟的最安全方法是:时钟树的叶时钟为复位同步触发器提供时钟。
    • 但是大多情况下,一个时钟周期不够时钟脉冲穿过时钟树、复位信号穿过 clk-q 、再穿越复位树。
image-20230721115003924
  • 为了加快复位到达所有目标触发器的速度,复位同步触发器采用时钟树中更早的时钟
    • 在完成布局并获得两棵树的实际时序之前,无法进行详细时序调整。
image-20230721115025576

复位毛刺的过滤

  • 这种方法可以过滤复位毛刺,但它是 ugly 的。(?)
    • 需要一个数字延迟(随温度、电压等变化)
    • 复位输入 pad 应该是施密特触发 pad 。
image-20230721141328815
  • 一些供应商还提供了可实例化的延迟硬件宏。

多时钟域复位

对于多时钟设计,每个时钟域都应该使用单独的复位同步电路和复位分配树,保证满足复位恢复时间。

无协调复位释放

  • 对于许多多时钟设计,各时钟域的复位时间之间没有联系。
  • 只需各自创建独立的复位同步电路就行。
image-20230721150742013

有序复位释放

  • 对于某些多时钟设计,必须按照正确的顺序释放复位。
  • 将各个复位同步电路进行 d-q 级连,实现顺序释放。
image-20230721150725033

异步复位与 DFT

DFT

image-20230721144747499
  • 扫描链插入:将触发器换成扫描触发器,并将它们拼接成扫描链。
  • ATPG:程序生成扫描向量。
  • 测试:
    • 扫描移位模式,将测试向量扫描到所有触发器中。
    • 从扫描移位模式切换到功能数据输入模式。
    • 经过一个功能时钟。
    • 切换回扫描移位模式,扫描出结果,同时扫描入下一个测试向量。
  • DFT 通常需要两个控制引脚:
    • 模式切换引脚
    • 移位使能引脚

(?)

多 ASIC 复位同步

对于一些设计,多个 ASIC 需要准确地在同一时间进行复位释放。可能受板布局、工艺、温度影响。

image-20230721153533909

GCC

1
2
3
4
5
gcc -E hello.c -o hello.i   预处理,生成.i文件(头文件、宏展开等)
gcc -S hello.i -o hello.s 编译,生成.s汇编文件
gcc -c hello.s -o hello.o 编译,生成.o可重定向目标文件
gcc hello.o -o hello 链接,生成可执行文件(将目标代码和所需要的库链成一个完整的应用程序)
gcc hello.c -o hello 直接编译链接成可执行目标文件

GCC

Makefile

语法规则

1
2
3
4
5
all: target1 target2 target3
target1:
# 编译规则1
target2:
# 编译规则2

变量

  • 变量赋值
    • := :直接赋值
    • = :变量的值是整个Makefile中最后被指定的值
    • ?= :如果该变量没有被赋值,则赋值予等号后面的值
    • += :将符号后面的值添加到前面的变量上
  • 变量调用 $(VAL)
    • $< 代表第一个依赖文件
    • $^ 表示所有的依赖文件
    • $@ 表示生成的目标文件
1
2
SRC = $(wildcard *.c) # 通配符
OBJ = $(patsubst %.c, %.o, $(SRC)) # 取出SRC中的所有值,将.c 替换为.o,最后赋值给OBJ变量

伪目标

  • 伪目标(如clean)没有依赖文件,只有用make来调用时才会执行。
  • 当目录下有与clean同名的文件时,执行make clean就会出现错误,此时需要使用伪目标
1
.PHONY: clean

回显

makefile里执行的命令都会打印在终端,若不需要打印则在命令前加@

1
2
clean:
@rm -rf ./*.o

嵌套Makefile

。。。

Makefile

GDB

要使用GDB调试某个程序,编译时必须加上编译选项 **-g**,否则程序不包含调试信息。

  • run(r)
  • list(l)
  • break(b)
    • b FuncName or b FileName:FuncName :在(指定文件)函数入口添加断点
    • b Line or b FileName:Line:在(指定文件)行号处添加断点
    • b -/+offset :在当前程序暂停位置的前/后 offset 行处下断点
    • info break or i b :显示当前所有断点信息
    • delete(d)/disable/enable BreakPointNum :禁用/启用/删除断点
  • until(u)
  • continue(c) next(n) step(s) jump(j)
  • print(p) watch
  • backtrace(bt) frame(f)
  • return finish(fi)
  • help(h)
命令名称 命令缩写 命令说明
run r 运行程序
list l 显示源码
break b 添加断点
delete d 删除某个断点
enable enable 启用某个断点
disable disable 禁用某个断点
until u 运行到指定行停下来
continue c 让暂停的程序继续运行
next n 运行到下一行
step s 单步执行,遇到函数会进入
jump j 将当前程序执行流跳转到指定行或地址
print p 打印变量或寄存器值
watch watch 监视某一个变量或内存地址的值是否发生变化,发生变化时GDB中断
backtrace bt 查看当前线程的调用堆栈
frame f 切换到当前调用线程的指定堆栈
finish fi 执行到当前函数返回后停止,回到上一层调用函数处
return return 立即中断当前函数并返回,到上一层函数调用处
thread thread 切换到指定线程
tbreak tb 添加临时断点
info i 查看断点 / 线程等信息
ptype ptype 查看变量类型
disassemble dis 查看汇编代码
set args set args 设置程序启动命令行参数
show args show args 查看设置的命令行参数

GDB

数据类型

SystemVerilog data types

四态值:0 1 X Z

logic、reg、net(wire)、integer、real、time、realtime

二态值:0 1

  • bit

    • bit[7:0] 无符号
  • byte、shortint、int、longint、shortreal

    • byte、shortint、int、longint 默认有符号

    • unsigned 表示无符号

  • 使用 $isunknown() 可检查二态值是否出现 X 或 Z

转换

1
2
3
4
5
6
// cast
int'(2.0*3.0)
shortint'({8'hab, 8'hef})
// system task
integer $rtoi(real_val)
real $itor(int_val )
  • 当四态值转换为二态值时,X 和 Z 将转换为 0

数组

  • 访问数组越界地址将返回缺省值(四值为 X,二值为 0)。

  • 很多仿真器采用 32 比特的字边界存放数组。

  • 当需要用@等待数组的变化,则必须使用合并数组。

非合并数组

  • 维度声明

    int array [0:3][0:7]; or int array [4][8];

  • 存放格式

    bit[7:0] b_unpack[3];

  • 初始化

    int array [4] = '{0,1,2,3}

    array = '{4{1}}

    array = '{0,1,default:2}

    array [0:2] = '{0,1,2}

合并数组

对某些数据类型,有时候希望作为整体访问,有时候则希望分解为更小单元访问,可使用合并数组。

  • 数组大小声明

    bit [3:0] [7:0] bytes;bit [size] 不可以)

  • 存放格式

    bit [3:0] [7:0] bytes;

    bit [3:0] [7:0] barray[3];

遍历:for & foreach

1
2
3
4
5
6
7
8
9
10
11
12
initial begin
bit [31:0] src[5], dst[4][5];
for(int i = 0; i < $size(src); i++) // for 遍历
...
foreach(dst[i,j]) // foreach 遍历
...
foreach(dst[i]) begin // foreach 遍历第一个维度
foreach(dst[,j]) begin // foreach 遍历第二个维度
...
end
end
end

数组聚合操作

1
2
3
4
5
6
7
8
9
10
initial begin
bit [31:0] src[3] = '{0,1,2},
dst[3] = '{3,4,5};
// 聚合比较(仅支持等于和不等于比较)
if(src == dst)
...
if(src != dst)
...
// 复制
dst = src;

动态数组

  • 定义

    bit [3:0] d_array[];

  • 分配大小、初始化

    d_array = new[3]; d_array = '{0, 1, 2};

  • 方法

    1
    2
    d_array.size()		// return 0 if not created
    d_array.delete() // empties the array resulting in a zero-sized array
  • 追加元素

    1
    d_array = new [d_array.size() + N] (d_array);	// 保留原有元素

关联数组

字符串 string

SystemVerilog String

枚举 enum

  • 定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    enum {RED, YELLOW, GREEN} 				 color_1;	// 默认 int, RED = 0, YELLOW = 1, GREEN = 2
    enum bit[1:0] {RED, YELLOW, GREEN} color_2; // 声明 bit, RED = 0, YELLOW = 1, GREEN = 2
    enum {RED = 3, YELLOW = 5, GREEN} color_3; // 根据前者递增, RED = 3, YELLOW = 5, GREEN = 6
    enum {BLACK[3]} color_3; // BLACK0 = 0, BLACK1 = 1, BLACK2 = 2
    enum {BLACK[2] = 3} color_4; // BLACK0 = 3, BLACK1 = 4
    enum {BLACK[3:4]} color_5; // BLACK3 = 0, BLACK4 = 1
    enum {BLACK[4:5] = 4} color_6; // BLACK4 = 4, BLACK5 = 5
    enum {RED = 0, YELLOW, GREEN = 1} color_7; // 错误:值冲突
    enum bit[0:0] {RED, YELLOW, GREEN} color_8; // 错误:位宽不够

    typedef enum {TRUE, FALSE} bool_t; // 自定义数据类型

    enum 命名不能以数字开头

  • 方法

    1
    2
    3
    4
    5
    6
    7
    8
    typedef enum {RED, YELLOW, GREEN} colors;
    colors color = YELLOW;
    $display("%0d", color.first); // 0 (RED)
    $display("%0d", color.last ); // 2 (GREEN)
    $display("%0d", color.next ); // 2 (GREEN)
    $display("%0d", color.prev ); // 0 (RED)
    $display("%0d", color.num ); // 3
    $display("%0d", color.name ); // YELLOW
  • 类型检查

    1
    2
    3
    4
    5
    6
    7
    typedef enum bit[1:0] {RED, YELLOW, GREEN} colors;
    colors color;
    color = RED; // valid
    color = 0; // invalid
    color = colors'(0); // valid
    if(color == RED | color == 0) // valid, autocast
    ...

结构体 structure

1

1 Scala Introduction

Class

  • Definition

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // WrapCounter counts up to a max value based on a bit size
    class WrapCounter(counterBits: Int) {

    val max: Long = (1 << counterBits) - 1
    var counter = 0L

    def inc(): Long = {
    counter = counter + 1
    if (counter > max) {
    counter = 0
    }
    counter
    }
    println(s"counter created with max value $max")
    }
  • Instance

    1
    2
    val x = new WrapCounter(2)
    x.inc()

Fields

  • val: immutable
  • var: mutable

Methods

  • Declaration
1
2
3
4
5
6
// These are normal functions.
def plus1funct(x: Int): Int = x + 1
// These are functions as vals.
// The first one explicitly specifies the return type.
val plus1val: Int => Int = x => x + 1 // val plus1val: (Int => Int) = { x => x + 1 }
val times2val = (x: Int) => x * 2
  • Overloading Functions
1
2
3
4
5
def times2(x: Int): Int = 2 * x
def times2(x: String): Int = 2 * x.toInt

times2(5)
times2("7")
  • Recursive and Nested Functions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** Prints a triangle made of "X"s

* This is another style of comment
*/
def asciiTriangle(rows: Int) {

// This is cute: multiplying "X" makes a string with many copies of "X"
def printRow(columns: Int): Unit = println("X" * columns)

if(rows > 0) {
printRow(rows)
asciiTriangle(rows - 1) // Here is the recursive call
}
}

// printRow(1) // This would not work, since we're calling printRow outside its scope
asciiTriangle(6)

Collection

List

1
2
3
4
5
6
7
8
9
val list1 = List(1, 2, 3)
val list2 = 1 :: 2 :: 3 :: Nil

val third = list1(2)
val list3 = list1 ++ list2 // Appends
val m = list2.length
val s = list2.size
val headOfList = list1.head // Gets the first element
val restOfList = list1.tail // Get a new list with first element removed

More Collection Methods

Seq, Map…

tuple

._1 takes the first element of tuple.

1
(1, 2, 3)._2 // 2

Conditionals

1
2
3
4
5
6
7
8
9
10
11
12
if (done) {
// The braces are not required when **all** branches are one liners
println("we are done")
}
else if (numberOfKittens < kittensPerHouse) {
println("more kittens!")
numberOfKittens += 1
}
else {
done = true
}
// "if" conditional returns a value, given by the last line of the selected branch

for statement

1
2
3
4
for (i <- 0 to 7) 			{ print(i + " ") }  // 0 - 7
for (i <- 0 until 7) { print(i + " ") } // 0 - 6
for (i <- 0 to 10 by 2) { print(i + " ") } // 0 - 10 by 2
for (value <- randomList) { print(i + " ") } // from list

Packages

  • package

    1
    2
    package mytools
    class Tool1 { ... }
  • import

    1
    2
    3
    import mytools.Tool1
    import chisel3._ // all classes an methods from chisel3
    import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester} // specific classes

Object Oriented

  1. Variables are objects.
  2. Constants in the sense of Scala’s val declarative are also objects.
  3. Even literal values are objects.
  4. Even functions themselves are objects. More on this later.
  5. Objects are instances of classes.
  6. In fact, in just about every way that matters in Scala, the object in Objected Oriented will be called an instance.
  7. In defining classes, the programmer specifies:
  8. The data (valvar) associated with the class.
  9. The operations, called methods or functions, that instances of the class can perform.
  10. Classes can extend other classes.
  11. The class being extended is the superclass; the extendee is the subclass.
  12. In this case, the subclass inherits the data and methods from the superclass.
  13. There are many useful but controlled ways in which a class may extend or override inherited properties.
  14. Classes may inherit from traits. Think of traits as lightweight classes that allow specific, limited ways of inheriting from more than one superclass.
  15. (Singleton) Objects are a special kind of Scala class.
  16. They are not objects as above. Remember, we’re calling those instances.