
Welcome to my Blog!

x
Coding Style Notes:
parameter 代表的不是编码(encode),而是对寄存器相应 bit 的索引(index)。
(?)state & next_state
通常可以将输出逻辑移到下一级的输入逻辑。
?
?
- 异步 FIFO 不能使用计数方法,因为读写端口时钟域不同。
- 为了判断空/满状态,必须对读/写指针进行比较。
FIFO 指针常用格雷码计数,每个时钟边沿只会有 1 位变化,避免多 bit CDC 问题。
指针计数是否会在采样间隔中持续变化甚至上溢、下溢?答案是不会。
如果对二进制指针进行采样,并在两个时钟域之间使用握手信号来安全传递,则可以使用二进制指针设计 FIFO 。
优点:
缺点:
方法:
空 empty
满 full
almost empty:rptr+4 赶上 wptr
almost full:wptr+4 赶上 rptr
fifo1.v
:顶层模块
fifomem.v
:FIFO memory buffer,此处是一个双端口 RAM 。
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
n
fifo1.v
:顶层模块
fifomem.v
:FIFO memory buffer,此处是一个双端口 RAM 。
sync_cmp.v
:生成异步的 afull_n
和 aempty_n
信号。
wptr_full.v
:生成 write pointer 和 full flag 。
rptr_empty.v
:生成 read pointer 和 empty flag 。
afull_n
和 aempty_n
信号。rempty
和 wfull
信号,同步释放。wfull
,而不清除 rempty
。wptr
和 rptr
。direction
。aemtpy_n
,进而清除 rempty
。亚稳态:由于不满足建立/保持时间,触发器在时钟边沿采样到变化中的信号,触发器输出信号在一段时间内不处于稳定的 0/1 状态,经过一段稳定时间 tMET 后恢复正常(可能是 0 也可能是 1 )。
亚稳态不能完全避免。
亚稳态会穿越后续电路,导致非法信号值传播到整个设计的其余部分。
将 CDC 信号在接收时钟域同步,是否就不需要在发送时钟域同步寄存?
若快时钟的频率至少是慢时钟的 1.5 倍,则信号一定会被采样 1-2 次,不存在漏采样的问题。
当发送时钟域频率高于接收时钟域,且 CDC 信号脉宽仅为发送时钟域的一个周期:
当发送的脉冲宽度稍大于接收时钟周期:
在超过采样时钟周期的一段时间内断言 CDC 信号,最小脉冲周期是采样时钟周期的 1.5 倍。
将使能控制信号同步到接收时钟周期,再将确认信号同步回发送时钟周期。未收到确认信号之前,发送信号不能改变。
发送时钟域同步输出的多个信号会存在数据变化偏差(skew),可能导致接收时钟域不能同时采样到正确信号。
两个控制信号存在小偏差,导致在接收时钟域的不同时钟沿被采样。
将 en
load
合并成 lden
信号。
接收时钟沿可能恰好在两个相移控制信号之间,导致在接收时钟域的使能控制信号链中形成单周期间隙。
合并控制信号,在接收时钟域内生成第二相移流水线使能信号。
发送不同步的数据和同步的控制信号。
优点:
确认反馈信号 b_ack
生成确认脉冲 aack
, aack
用作小型 READY-BUSY、1 状态 FSM 的输入,该模块生成就绪信号 aready
以指示现在可以安全地再次更改 adatain
数据和 asend
控制信号。
接收时钟域有一个 WAIT-READY、1 状态 FSM,当数据寄存器的输入上的数据有效时,它会向接收逻辑发送有效信号 bvalid
。在接收逻辑通过断言 bload
信号确认应加载数据之前,不会实际加载数据。在数据加载之前,不会向发送时钟域提供反馈。加载后才会将 b_ack
信号发送回。
对于计数器而言,跨时钟域传输时,通常不需要采样计数的每一个值(允许漏采样)。
二进制计数器在计数时会发生多位同时变化,可能由于数据偏差导致错误采样。
格雷码每次计数仅有一位发生变化,消除了多 bit CDC 问题。
Guidelines:组合逻辑电路用阻塞赋值,时序逻辑电路用非阻塞赋值。
忽略上述准则仍然可以综合出正确的电路,但仿真可能与实际电路的行为不匹配。
在不被任何其他 Verilog 语句中断的情况下,计算 RHS 并更新 LHS 。
当一个过程块中的 RHS 也是另一个过程块中的 LHS 时,并在同一个仿真时间执行,就会出现 Race Condition 。
非阻塞赋值并行执行。
非阻塞赋值仅用于 reg 类型,即只用于过程赋值块内。
<=
配。<=
。=
。<=
。=
和非阻塞赋值 <=
。$strobe
来打印非阻塞赋值 <=
的变量。#0
。?
1 | module dff( |
Linear Feedback Shift-Register (LFSR)
使用 <=
1 | s |
(✗)在同一个 always 块中对同一个变量进行多次非阻塞赋值 <=
是未定义的行为
(✓)最后一个赋值语句胜出
(✗)$display()
对于非阻塞赋值的变量无效
(✓)非阻塞赋值在 $display()
之后生效,用 $strobe
(✗)#0
语句在每个 time step 的最后生效
(✓)#0
语句在每个 time step 的 Inactive 阶段生效
综合仍会推断出正确的电路(与门),但仿真行为与综合电路不符。
综合仍会推断出正确的电路(与或门),但仿真行为与综合电路不符。
函数会被综合为位组合逻辑,而如果函数中的代码对应锁存器,则综合前仿真视为锁存器,综合后仿真却视为组合逻辑,出现不匹配。
// synopsys full_case
表示所有可能的 case 都已经列出,未列出的都是“不关心”的。
en
信号被优化掉了。// synopsys parallel_case
告诉综合工具应并行检验所有条件,即使存在重叠的条件。
重叠的条件平常会推导出优先级编码器,但在 parallel case
中会推导为无优先级。
z
的 case 条件优先级高于 y
,所以 y
的条件中必须排除与 z
重叠的部分,推导出优先级。parallel case
中,各个条件无优先级,故各自 z
和 y
各自推导出 2 输入与门。casex
将 x
态视作不关心。当信号被初始化为未知状态时,可能会导致 x
态传播,但 casex
不能检测到这样的问题。
en
因为某种原因变为 x
态,casex
仍会根据 addr
进行匹配、执行,掩盖了 x
态的问题。addr
也是一样。casex
,最好使用 casez
。casez
将 z
态视作不关心,可能会导致 casex
一样的问题。
casez
于建模地址解码器和优先级编码器时非常有用,可以用 ?
去匹配。x
初始化x
对于verilog仿真器是未知,对于综合工具是不关心(利于综合优化)。
x
可能导致仿真综合不匹配,但是在 FSM 设计中可能有用,可以在仿真时检测错误的状态转换。s == 2'b11
时产生不匹配,但是若 2'b11
是不期望达到的状态,可以用于仿真调试。translate_on/translate_off
初始化?
translate_on/translate_off
的一般用法translate_on/translate_off
在打印信息的时候很有用,但用于建模功能却很危险。rstn
和 setn
都拉低后,rstn
再拉高,此时 q
却未被置位。需要使用不可综合的构造才能完全与综合后的电路行为 100% 匹配。在赋值左侧设置延时可能导致仿真与综合电路行为不匹配。
in
发生改变,经过 65 个时间单位才退出 always
块,期间 in
再次发生改变也不会再次进入 always
块,计时期间其他事件都被忽略。这显然与综合后电路的行为不符。don't care
优化,则需要复位。always
块中都应用非阻塞赋值 <=
。always
中混合建模同步复位触发器和跟随触发器,导致 rst_n
被推导为跟随触发器的使能信号 ld
。always
中描述。d
输入逻辑云的一部分。d
之间必须穿越的逻辑。sync_set_reset
可以帮助推导这种触发器,但是需要注意前后仿不一致的问题。(?)异步复位最大的问题在于复位释放。
Synopsys 要求,如果敏感列表中有任一个信号是边沿敏感的,则所有信号都必须是边沿敏感的。
复位缓冲树?
rst_n
和 set_n
都拉低后,rst_n
再拉高,此时 q
却未被置位:仿真与综合电路行为不匹配。synopsys translate_off/on
和不可综合的构造才能与综合后的电路行为 100% 匹配。$recovery
$removal
$recrem
由于复位信号到达不同触发器时间存在偏差,可能导致两个触发器在不同的时钟周期释放复位。
每个使用异步复位的 ASIC 都应该包括一个复位信号同步电路。
必须根据 clk-q 复位树的时序分析时钟树时序。
对于多时钟设计,每个时钟域都应该使用单独的复位同步电路和复位分配树,保证满足复位恢复时间。
(?)
对于一些设计,多个 ASIC 需要准确地在同一时间进行复位释放。可能受板布局、工艺、温度影响。
1 | gcc -E hello.c -o hello.i 预处理,生成.i文件(头文件、宏展开等) |
1 | all: target1 target2 target3 |
:=
:直接赋值=
:变量的值是整个Makefile中最后被指定的值?=
:如果该变量没有被赋值,则赋值予等号后面的值+=
:将符号后面的值添加到前面的变量上$(VAL)
$<
代表第一个依赖文件$^
表示所有的依赖文件$@
表示生成的目标文件1 | SRC = $(wildcard *.c) # 通配符 |
clean
)没有依赖文件,只有用make
来调用时才会执行。clean
同名的文件时,执行make clean
就会出现错误,此时需要使用伪目标1 | .PHONY: clean |
makefile里执行的命令都会打印在终端,若不需要打印则在命令前加@
1 | clean: |
。。。
要使用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 | 将当前程序执行流跳转到指定行或地址 |
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 | 查看设置的命令行参数 |
logic、reg、net(wire)、integer、real、time、realtime
bit
byte、shortint、int、longint、shortreal
byte、shortint、int、longint 默认有符号
unsigned 表示无符号
使用 $isunknown()
可检查二态值是否出现 X 或 Z
1 | // cast |
访问数组越界地址将返回缺省值(四值为 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];
1 | initial begin |
1 | initial begin |
定义
bit [3:0] d_array[];
分配大小、初始化
d_array = new[3];
d_array = '{0, 1, 2}
;
方法
1 | d_array.size() // return 0 if not created |
追加元素
1 | d_array = new [d_array.size() + N] (d_array); // 保留原有元素 |
定义
1 | enum {RED, YELLOW, GREEN} color_1; // 默认 int, RED = 0, YELLOW = 1, GREEN = 2 |
enum 命名不能以数字开头
方法
1 | typedef enum {RED, YELLOW, GREEN} colors; |
类型检查
1 | typedef enum bit[1:0] {RED, YELLOW, GREEN} colors; |
1 |
Definition
1 | // WrapCounter counts up to a max value based on a bit size |
Instance
1 | val x = new WrapCounter(2) |
1 | // These are normal functions. |
1 | def times2(x: Int): Int = 2 * x |
1 | /** Prints a triangle made of "X"s |
1 | val list1 = List(1, 2, 3) |
._1
takes the first element of tuple.
1 | (1, 2, 3)._2 // 2 |
1 | if (done) { |
1 | for (i <- 0 to 7) { print(i + " ") } // 0 - 7 |
package
1 | package mytools |
import
1 | import mytools.Tool1 |
- Variables are objects.
- Constants in the sense of Scala’s
val
declarative are also objects.- Even literal values are objects.
- Even functions themselves are objects. More on this later.
- Objects are instances of classes.
- In fact, in just about every way that matters in Scala, the object in Objected Oriented will be called an instance.
- In defining classes, the programmer specifies:
- The data (
val
,var
) associated with the class.- The operations, called methods or functions, that instances of the class can perform.
- Classes can extend other classes.
- The class being extended is the superclass; the extendee is the subclass.
- In this case, the subclass inherits the data and methods from the superclass.
- There are many useful but controlled ways in which a class may extend or override inherited properties.
- Classes may inherit from traits. Think of traits as lightweight classes that allow specific, limited ways of inheriting from more than one superclass.
- (Singleton) Objects are a special kind of Scala class.
- They are not objects as above. Remember, we’re calling those instances.