(1)使用编写时序逻辑代码用非阻塞赋值
- 所有变量均需等待触发沿到来才进行赋值操作
- 计算赋值号右边信号时,所有变量均是触发沿到来前的值
- 过程块呢的语句是并行执行的
(2) 用always块编写组合逻辑块代码要用阻塞赋值
- always块中敏感列表要用电平触发的方式
(3) 同一个always块中不能既用阻塞赋值又用非阻塞赋值
(4) 一个always块中只能对一个变量进行赋值
- always块是并行执行的
- 严禁在多个always块中对同一 变量赋值
- 不推荐在一个always块中对多个变量赋值
(5) 在异步复位程序中,若always语句中出现if语句,则时钟信号以外敏感控制信号(一般为复位信号)必须在第一条if语句中判断,若放在后面的else if语句中就会编译出错
- 常见错误:cannot match operand(s) in the condition to the corresponding edges in the enclosing event control of the always construct。其对应的代码块为:
always @(posedge sys_clk or negedge sys_rst_n) begin
if (count == COUNT_MAX) //
led_out <= ~led_out; // 时序逻辑块中用非阻塞赋值
else if(sys_rst_n == 1'b0)
led_out <= 1'b0;
else
led_out <= led_out;
end
显然,复位信号没有在第一条if语句中进行判断,故出错!
- 修改办法:把除时钟信号外的敏感控制信号放在第一条if语句中
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0) //
led_out <= 1'b0;
else if(count == COUNT_MAX)
led_out <= ~led_out; // 时序逻辑块中用非阻塞赋值
else
led_out <= led_out;
end
(6) parameter参数的名称为大写,以区分其他变量
parameter COUNT_MAX = 25'd24_999_999
(7) 写较长的数时中间用_(下划线)隔开以增强可读性
parameter MIN = 25'd10_999_999,
parameter MAX = 25'd24_999_999
(8) 连接模块端口的中间变量使用wire类型
由于wire型变量才是表示电路中的物理连接,因此模块间连接的中间变量要使用wire型,而不是reg!否则会报错:Error (10663): Verilog HDL Port Connection error at capKey_ctrl_Led.v(41): output or inout port "key_out" must be connected to a structural net expression
常用的场景为在一个模块中实例化另一个模块时,他们之间连接的中间变量用wire型!
module A(
port
);
// 连接两个模块的中间变量要用wire型变量
wire moduleB_out;
// 实例化模块B
B B_int1(
.in(in),
.out(moduleB_out)
);
endmodule
// 另一个文件中定义的模块
module B(
input wire in,
output reg out
);
endmodule
注意:与原模块中输出信号的类型无关,模块间连接的中间信号都用wire型!
(9) 状态机的描述
1. 设计状态转移图时状态时避免状态冗余,即尽量选取状态数较少的类型的状态机进行描述,以便于代码编写
状态机(FSM),主要用于在时序电路中描述具有先后顺序的事物。主要分为两类:
- Moore型:输出只与当前状态有关
- Mealy型:输出不仅与当前状态有关,还与输入有关
如上图所示,显然Mealy型状态机的状态数较少,故选择该状态机进行进一步的设计!
注意:输入有多少种情况,每个状态的跳转就有多少种情况!(应避免画状态图时跳转状态遗漏)
2. 对状态的编码方式
2.1 独热码
- 编码方式:每个状态只有一位为1,因此有多少个状态就需要多少位宽的寄存器进行编码
如上述三个状态的编码方式为:
// 定义状态(3个状态),使用独热编码(效率高,占用寄存器资源多)
parameter IDLE = 3'b001;
parameter ONE = 3'b010;
parameter TWO = 3'b100;
- 优势: 节省逻辑资源,应用与高速系统中
- 劣势: 消耗寄存器资源较多
2.2 二进制编码
- 编码方式:对状态直接进行二进制编码,2^寄存器位宽 >= 状态数
如上述三个状态的编码方式为:
// 二进制编码(效率低,占用寄存器资源少)
parameter IDLE = 2'b00;
parameter ONE = 2'b01;
parameter TWO = 2'b10;
- 优势: 节省寄存器资源
- 劣势: 消耗逻辑资源较多
2.3 格雷码
编码方式: 相邻两个状态仅有一位不同,消耗位宽与二进制编码相同
如上述三个状态的编码方式为:
// 格雷码(相邻两个状态只有一位不同),折中的编码方式
parameter IDLE = 2'b00;
parameter ONE = 2'b01;
parameter TWO = 2'b11;
- 优势:是独热码和二进制编码的折中
3 RTL代码中状态机的描述
把状态转移和数据输出分别用不同的always块描述
状态转移用一个always块
当有多个输出时,每个输出用一个always块进行描述
// 状态转换的描述
always @(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
state <= IDLE;
else begin
case(state)
IDLE: if (in== 1'b1)
state <= ONE;
else
state <= IDLE;
ONE: if (in== 1'b1)
state <= TWO;
else
state <= ONE;
TWO: if (in== 1'b1)
state <= IDLE;
else
state <= TWO;
default: state <= IDLE;
endcase
end
end
// 输出1设置
always @(posedge sys_clk or negedge sys_rst_n) begin
out1
end
// 输出2设置
always @(posedge sys_clk or negedge sys_rst_n) begin
out2
end
(10) 变量名、模块名首字母在只能是字母或下划线
- 标识符用于定义常数、变量、信号、端口、参数名、模块名等
- 组成: 字母、数字、$、下划线任意组合而成
- 首位只能是下划线或字母!
(11) 时钟信号选取原则
FPGA中基于系统时钟可以产生任意分频/倍频的时钟信号。若锁相环(PLL)资源充足,可优先选用锁相环产生分频、倍频、任意占空比、任意相位的信号。
当需要多路分频信号且PLL资源不够用时可选择降频的方式产生系统时钟分频后的时钟信号:
如图所示,用标志信号的方式产生降频时钟,捕捉标志信号的下降沿即可实现5分频!
PLL和降频方式的优势在于输出的时钟信号仍通过系统时钟树到达相应的寄存器,使得每路时钟信号到达每个寄存器时间几乎保持一致,避免了时序错误!
(12) 跨时钟域处理的方案
- 慢速 -> 高速 :打两拍 (消除亚稳态 )
- 高速 -> 慢速 :脉冲同步、握手信号、FIFO
(13) 单bit信号边沿检测
- sig_reg1 为把输入信号 sig 同步到系统时钟下
- sig_reg2 为延迟一拍
- ~sig_reg 为同步后的输入信号取反
assign posedge = ~( (~sig_reg1) | sig_reg2 );
assign negedge = (~sig_reg1) & sig_reg2 ;