1.知识点-Verilog
1.知识点-Verilog
侧重verilog具体代码和具体的知识点
1.(Clock Domain Conversion)跨时钟域处理
参考:
https://blog.csdn.net/u013668469/article/details/99480694
https://blog.csdn.net/carrotbanana/article/details/126696748
1.1 电平同步器(慢到快)

为使得同步器工作正常,从原时钟传来的信号应先通过原时钟上的一个触发器,以消除所带的毛刺,而后不经过任何组合逻辑,进行打两拍。
局限性
- 从慢时钟域到快时钟域,信号肯定被采到,故最为适用。此时输出信号一般为电平信号,如果要求获得与新周期等宽的脉冲信号,则不可用。
- 快时钟域传递到慢时钟域,传递的信号必须为较宽的电平信号,要求保持高电平或低点评一个同步时钟周期以上。和输入信号关系较大,不可传递原时钟周期的脉冲信号,故不适用。
- 同步器有效的条件:第一级触发器进入亚稳态后的恢复时间 + 第二级触发器的建立时间 < = 时钟周期
//电平同步器--慢到快可行
`timescale 1ns/1ps
module level_syc(
input wire clk_1,
input wire clk_2,
input wire din,
input wire rst_n,
output wire dout
);
reg src_state;
reg src_state_d0,src_state_d1;
//原时钟信号寄存器输出,消除毛刺
always @(posedge clk_1 or negedge rst_n)begin
if(rst_n == 1'b0)begin
src_state <= 1'b0;
end
else begin
src_state <= din;
end
end
//同步到新时钟域
always @(posedge clk_2 or negedge rst_n)begin
if(rst_n == 1'b0)begin
src_state_d0 <= 1'b0;
src_state_d1 <= 1'b0;
end
else begin
src_state_d0 <= src_state;
src_state_d1 <= src_state_d0;
end
end
assign dout = src_state_d1;
endmodule
测试脚本:
在 Verilog 中,fork
和 join
是并行执行块的语法结构。
module level_syc_tb();
reg clk_1, clk_2, rst_n;
reg din;
// 慢时钟域到快时钟域
always
begin
#30 clk_1 = ~clk_1;
end
always
begin
#10 clk_2 = ~clk_2;
end
// 快时钟域到慢时钟域
/* always
begin
#10 clk_1 = ~clk_1;
end
always
begin
#30 clk_2 = ~clk_2;
end
*/
initial
fork
clk_1 = 1'b1;
din = 1'b0;
#5 clk_2 = 1'b1;
#10 rst_n = 1'b0;
#50 rst_n = 1'b1;
//慢时钟域到快时钟域
#200 din = 1'b1;
#260 din = 1'b0;
//快时钟域到慢时钟域,高电平持续两个同步时钟周期
/* #320 din = 1'b1;
#380 din = 1'b0;
//快时钟域到慢时钟域,高电平持续小于两个同步时钟周期
#800 din = 1'b1;
#820 din = 1'b0;
*/
join
level_syc u1(.clk_1(clk_1), .clk_2(clk_2), .rst_n(rst_n),
.din(din), .dout(dout));
endmodule
1.2 边沿同步(检测)器(慢到快)

信号在新时钟域打两拍完成同步之后,再外接一个触发器,相当于将信号再向后延迟一个周期。之后,通过非门和与门,对标准同步信号以及延迟信号进行逻辑组合,从而完成边沿的提取,最终得到一个与新时钟周期等宽,高电平有效的脉冲信号。
设上述三个触发器从左到右的输出分别为 Q0, Q1, Q2,则有:
提取上边沿 pules = Q1 & (~Q2);
提取下边沿 pulse = (~Q1) & Q2;
提取双边沿 pulse = Q1 ^ Q2;
moudule edge_syc(
input wire clk_1,
input wire clk_2,
input wire din,
input wire rst_n,
output wire dout_r,
output wire dout_f,
output wire dout_e
);
reg src_state;
reg src_state_d0, src_state_d1, src_state_d2;
//原时钟域下脉冲信号转变为电平信号
always @(posedge clk_1 or negedge rst_n)
begin
if(rst_n == 1'b0)
src_state <= 1'b0;
else
src_state <= din;
end
//同步至新时钟域
always @(posedge clk_2 or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
src_state_d0 <= 1'b0;
src_state_d1 <= 1'b0;
src_state_d2 <= 1'b0;
end
else
begin
src_state_d0 <= src_state;
src_state_d1 <= src_state_d0;
src_state_d2 <= src_state_d1;
end
end
//边沿检测产生新的脉冲
assign dout_r = src_state_d1 & ~src_state_d2;
assign dout_f = !src_state_d1 & src_state_d2;
assign dout_e = src_state_d1 ^ src_state_d2;
endmodule
1.3 脉冲同步器(快到慢)
思想:先将原时钟域下的脉冲信号,转化为电平信号,再进行同步,同步完成之后再把新时钟域下的电平信号转化为脉冲信号(边沿检测器的功能)。结合之前所了解的两种同步器,实际上在这里只需要考虑完成脉冲到电平的转化便可以了。
1.3.1 二选一选择器进行转化

假设初始时Q为0,Q非为1,在DATA为低电平时,Q值保持不变;在DATA脉冲的高电平到来时,Q完成翻转,变为1,此时Q非为0;而后DATA脉冲结束,变为低电平,Q值保持高电平不变,一直为高,直到下一次脉冲到来完成翻转。
1.3.2 异或门进行转化

假设初始Q为0。在in_pulse为低电平时,异或门输入相同,输出为0,Q值保持为0不变;在in_pulse脉冲的高电平到来时,异或门输入不同,输出为1,Q值变为1;此后in_pulse脉冲的低电平到来,异或门输入仍旧不同,Q值保持为1不变,直到下一次脉冲带来才发生翻转。
**限制:即输入脉冲的需要有两个同步时钟周期的间隔。**输入脉冲如果相距过近,则新时钟域中的输出脉冲也会紧密相邻,结果输出的脉冲宽度高于一个同步时钟周期,这是不期望的。
// 脉冲同步器
module pulse_sys(
input wire clk_1,
input wire clk_2,
input wire din,
input wire rst_n
);
reg src_state;
reg src_state_d0,src_state_d1,src_state_d2;
//原时钟域下脉冲信号转变为电平信号
always @(posedge clk_1 or negedge rst_n)
begin
if(rst_n == 1'b0)
src_state <= 1'b0;
// else if(din == 1'b1)
// src_state <= ~src_state;
else
src_state <= din ^ src_state; // 通过异或门做处理
end
// 同步至新时钟域
always@(posedge clk_2 or negedge rst_n)begin
if(rst_n == 1'b0)begin
src_state_d0 <= 1'b0;
src_state_d1 <= 1'b0;
src_state_d2 <= 1'b0;
end
else begin
src_state_d0 <= src_state;
src_state_d1 <= src_state_d0;
src_state_d2 <= src_state_d1;
end
end
//边沿检测产生新的脉冲
assign dout = src_state_d1 ^ src_state_d2;
endmodule
//测试代码
// 高频到低频 //
module pulse_syc_tb();
reg clk_1, clk_2, rst_n;
reg din;
always
begin
#10 clk_1 = ~clk_1;
end
always
begin
#30 clk_2 = ~clk_2;
end
initial
fork
clk_1 = 1'b1;
din = 1'b0;
#5 clk_2 = 1'b1;
#10 rst_n = 1'b0;
#50 rst_n = 1'b1;
#100 din = 1'b0;
#200 din = 1'b1; //间隔两个同步周期的脉冲信号
#220 din = 1'b0;
#320 din = 1'b1;
#340 din = 1'b0;
#600 din = 1'b1; //间隔一个同步周期的脉冲信号
#620 din = 1'b0;
#680 din = 1'b1;
#700 din = 1'b0;
#900 din = 1'b1; //等于两个原时钟周期的脉冲信号
#1020 din = 1'b0;
join
pulse_syc u1(.clk_1(clk_1), .clk_2(clk_2), .rst_n(rst_n),
.din(din), .dout(dout));
endmodule
// 低频到高频 //
/*module pulse_syc_tb();
reg clk_1, clk_2, rst_n;
reg din;
always
begin
#30 clk_1 = ~clk_1;
end
always
begin
#10 clk_2 = ~clk_2;
end
initial
fork
clk_1 = 1'b1;
din = 1'b0;
#5 clk_2 = 1'b1;
#10 rst_n = 1'b0;
#50 rst_n = 1'b1;
#100 din = 1'b0;
#200 din = 1'b1; //间隔两个同步周期的脉冲信号
#260 din = 1'b0;
#300 din = 1'b1;
#360 din = 1'b0;
#600 din = 1'b1; //间隔一个同步周期的脉冲信号
#660 din = 1'b0;
#680 din = 1'b1;
#740 din = 1'b0;
#900 din = 1'b1; //等于两个原时钟周期的脉冲信号
#1020 din = 1'b0;
join
pulse_syc u1(.clk_1(clk_1), .clk_2(clk_2), .rst_n(rst_n),
.din(din), .dout(dout));
endmodule
*/
1.4 握手机制(普遍适用)
采用握手机制的跨时钟传输相比于普通的跨时钟传输多了两个信号:请求信号、应答信号。握手机制实际上是带有反馈信号的同步器。
握手过程为:
源时钟下产生请求信号。
将请求信号同步到目标时钟下,目标时钟产生应答信号。
应答信号同步到源时钟下,源时钟清除请求信号。
目标时钟检测到请求信号撤销后,清除应答信号。

module Sync_Pulse (
input aclk,
input bclk,
input rst_n,
input adat,
output pulse_b_out,
output data_out
);
/****************************************************/
reg adat1,bq1_dat,bq2_dat,bq3_dat;
reg aq1_dat,aq2_dat;
//在时钟域aclk下,生成展宽信号adat1
always @ (posedge aclk or negedge rst_n)
begin
if (rst_n == 1'b0)
adta1 <= 1'b0;
else if (adat) //检测到到输入信号adat被拉高,则拉高adat1,使信号展宽
adat1 <= 1'b1;
else if (aq2_dat) //检测到反馈信号aq2_dat被拉高,则拉低adat1
adat1 <= 1'b0;
else;
end
//将aclk时钟下的信号adat1同步到bclk时钟
always @ (posedge bclk or negedge rst_n)
begin
if (rst_n == 1'b0)
bq1_dat <= 1'b0;
bq2_dat <= 1'b0;
bq3_dat <= 1'b0;
else
bq1_dat <= adat1;
bq2_dat <= bq1_dat;
bq3_dat <= bq2_dat;
end
//使用bq2_dat作为反馈信号,同步到aclk时钟下。用于反馈来拉低展宽信号adat1
always @ (posedge aclk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
aq1_dat <= 1'b0;
aq2_dat <= 1'b0;
end
else
begin
aq1_dat <= bq2_dat;
aq2_dat <= aq1_dat;
end
end
//输出模块,使用bq3_dat来组成输出模块,用来生成边沿信号或者输出信号
assign pulse_b_out = bq2_dat & (~bq3_dat);
assign data_out = bq3_dat;
endmodule
2.FIFO相关
**同步FIFO详解:**https://www.nowcoder.com/discuss/354655702533541888
**同步FIFO和异步FIFO总结:**https://mp.weixin.qq.com/s/Cr5UYJOSW2VC0cJzHL3yLQ
位宽计算的系统函数$clog2:https://blog.csdn.net/weixin_43698385/article/details/123303105
使用场景:
- 数据缓冲
- 时钟域的隔离:主要是异步FIFO,对于不同时钟域的数据传输,可以通过FIFO进行隔离,避免跨时钟域的数据传输带来设计和约束上的复杂。
- 不同宽度的数据接口
常见参数:
- 宽度
- 深度
- 满,空标志
0.例化双口RAM
// 返回以2为底的n的对数
function integer clog2 (input integer n); begin
n = n - 1;
for (clog2 = 0; n > 0; clog2 = clog2 + 1)
n = n >> 1;
end
endfunction
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
1.同步FIFO

Cummings, Clifford E. "Simulation and synthesis techniques for asynchronous FIFO design." SNUG 2002 (Synopsys Users Group Conference, San Jose, CA, 2002) User Papers. Vol. 281. 2002.
`timescale 1ns/1ns
/****************************/
// 作者:FPGA探索者
/****************************/
module sfifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input clk ,
input rst_n ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output reg wfull ,
output reg rempty ,
output wire [WIDTH-1:0] rdata
);
// 用localparam定义一个参数,可以在文件内使用
localparam ADDR_WIDTH = $clog2(DEPTH);
reg [ADDR_WIDTH:0] waddr;
reg [ADDR_WIDTH:0] raddr;
always @ (posedge clk or negedge rst_n) begin
if(~rst_n) begin
waddr <= 'b0;
end
else begin
if( winc && ~wfull ) begin
waddr <= waddr + 1'b1;
end
else begin
waddr <= waddr;
end
end
end
always @ (posedge clk or negedge rst_n) begin
if(~rst_n) begin
raddr <= 'b0;
end
else begin
if( rinc && ~rempty ) begin
raddr <= raddr + 1'b1;
end
else begin
raddr <= raddr;
end
end
end
always @ (posedge clk or negedge rst_n) begin
if(~rst_n) begin
wfull <= 'b0;
rempty <= 'b0;
end
else begin
wfull <= (raddr == {~waddr[ADDR_WIDTH], waddr[ADDR_WIDTH-1:0]});
rempty <= (raddr == waddr);
end
end
// 带有 parameter 参数的例化格式
dual_port_RAM
#(
.DEPTH(DEPTH),
.WIDTH(WIDTH)
)
dual_port_RAM_U0
(
.wclk(clk),
.wenc(winc),
.waddr(waddr[ADDR_WIDTH-1:0]),
.wdata(wdata),
.rclk(clk),
.renc(rinc),
.raddr(raddr[ADDR_WIDTH-1:0]),
.rdata(rdata)
);
endmodule
/**************RAM 子模块*************/
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
2. 格雷码
格雷码转二进制码的基本思路:格雷码转二进制是从左边第二位起,将每位与左边一位二进制码的值异或,作为该位二进制码后的值(最左边一位依然不变)。
//格雷码转自然二进制数
module gray2bin #(
parameter width = 4 //定义数据的位宽参数为4
)(
input [width - 1 : 0] gray,
output [width - 1 : 0] bin
);
//利用generate...for逐位循环输出最低位至次高位二进制数
genvar i;
generate
for(i = width - 2; i >= 0; i = i - 1) begin: gray_2_bin
assign bin[i] = bin[i + 1] ^ gray[i]; //格雷码与二进制数前一位进行异或逻辑运算
end
endgenerate
//二进制数最高位为格雷码最高位
assign bin[width - 1] = gray[width - 1];
endmodule
二进制码转格雷码的基本思路:从最右边一位起,依次将每一位与左边一位异或(XOR),作为对应格雷码该位的值,最左边一位不变。
assign waddr_gray= waddr ^ (waddr>>1);
assign raddr_gray= raddr ^ (raddr>>1);
3.异步FIFO
同步FIFO
格雷码跨时钟域传输:
使用格雷码的意义:
避免伪稳态:
当两种时钟(读和写时钟)是异步的时候,地址是由多个位组成的。格雷码的特性是相邻的值只有一位不同,这样可以大幅减少状态转换中的不确定性。
避免竞争条件:
不必担心在多个位变化时可能出现的竞争条件,从而简化了状态验证和同步的过程。
简化设计:
- 电路复杂性:如果使用简单的二进制编码,比较逻辑会相对复杂。因为需要对多个比特位进行判断,这增加了电路设计和调试的难度。
- 逻辑和时序控制:设计更复杂的时序逻辑以检测和管理读写操作,会要求更多的逻辑门和更复杂的布线,这样就增加了功耗和面积的消耗。
注意事项:
- 当二进制地址转格雷码时采用组合逻辑,需要进行延迟打拍
关于空满判断:
读地址到写时钟域,用以判断是否写满;
写地址到读时钟域,用以判断是否读空;

assign wfull = (waddr_gray_reg == {~addr_r2w[ADDR_WIDTH:ADDR_WIDTH-1],addr_r2w[ADDR_WIDTH-2:0]});
assign rempty = (raddr_gray_reg == addr_w2r);
module asyn_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wclk ,
input rclk ,
input wrstn ,
input rrstn ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output wire wfull ,
output wire rempty ,
output wire [WIDTH-1:0] rdata
);
localparam ADDR_WIDTH = $clog2(DEPTH);
reg [ADDR_WIDTH:0] waddr;
reg [ADDR_WIDTH:0] raddr;
always @ (posedge wclk or negedge wrstn) begin
if(~wrstn) begin
waddr <= 'b0;
end
else begin
if( winc && ~wfull ) begin
waddr <= waddr + 1'b1;
end
else begin
waddr <= waddr;
end
end
end
always @ (posedge rclk or negedge rrstn) begin
if(~rrstn) begin
raddr <= 'b0;
end
else begin
if( rinc && ~rempty ) begin
raddr <= raddr + 1'b1;
end
else begin
raddr <= raddr;
end
end
end
wire [ADDR_WIDTH:0] waddr_gray;
wire [ADDR_WIDTH:0] raddr_gray;
assign waddr_gray = waddr ^ (waddr>>1);
assign raddr_gray = raddr ^ (raddr>>1);
reg [ADDR_WIDTH:0] waddr_gray_reg;
always @ (posedge wclk or negedge wrstn) begin
if(~wrstn) begin
waddr_gray_reg <= 'd0;
end
else begin
waddr_gray_reg <= waddr_gray;
end
end
// 组合逻辑,进行打拍,避免时序问题
reg [ADDR_WIDTH:0] raddr_gray_reg;
always @ (posedge rclk or negedge rrstn) begin
if(~rrstn) begin
raddr_gray_reg <= 'd0;
end
else begin
raddr_gray_reg <= raddr_gray;
end
end
reg [ADDR_WIDTH:0] addr_r2w_t;
reg [ADDR_WIDTH:0] addr_r2w;
always @(posedge wclk or negedge wrstn) begin
if(~wrstn) begin
addr_r2w_t <='d0;
addr_r2w <= 'd0;
end
else begin
addr_r2w_t <= raddr_gray_reg;
addr_r2w <= addr_r2w_t;
end
end
reg [ADDR_WIDTH:0] addr_w2r_t;
reg [ADDR_WIDTH:0] addr_w2r;
always @(posedge rclk or negedge rrstn)begin
if(~rrstn)begin
addr_w2r_t <= 'd0;
addr_w2r <= 'd0;
end
else begin
addr_w2r_t <= waddr_gray_reg;
addr_w2r <= addr_w2r_t;
end
end
assign wfull = (waddr_gray_reg == {~addr_r2w[ADDR_WIDTH:ADDR_WIDTH-1], addr_r2w[ADDR_WIDTH-2:0]});
assign rempty = (raddr_gray_reg == addr_w2r);
dual_port_RAM #(
.DEPTH(DEPTH),
.WIDTH(WIDTH)
)
dual_port_RAM_UO
(
.wclk(wclk),
.wenc(winc && ~wfull),
.waddr(waddr[ADDR_WIDTH-1:0]),
.wdata(wdata),
.rclk(rclk),
.renc(rinc && ~rempty),
.raddr(raddr[ADDR_WIDTH-1:0]),
.rdata(rdata)
);
endmodule
4. FIFO深度计算
https://blog.csdn.net/m0_56222647/article/details/136830060
读写无空闲周期
FIFO_Depth=Burst_length-Burst_length*(rd_clk/wr_clk)*rd_rate; // 上式中 FIFO_Depth 表示 FIFO 深度 // Burst_length 表示突发数据长度 // rd_clk 读时钟频率 // wr_clk 写时钟频率 // rd_rate 读倍率
读写都有空闲周期
假设写数据空闲周期位为k(写使能占空比为1/(k+1)),读数据空闲周期为j(读使能占空比为1/(j+1))
FIFO_Depth=Burst_length-Burst_length*(rd_clk'='/wr_clk')*(k+1)/(j+1)*rd_rate; rd_clk'=rd_clk/(j+1) wr_clk'=wr_clk/(k+1)
具体分析: 写一个数据需要: (k+1)/wr_clk 读一个数据需要: (j+1)/rd_clk 写一个突发数据长度需要时间:(k+1)/wr_clk*Burst_length/ 可读的数据:(k+1)/wr_clk*Burst_length/ [(j+1)/rd_clk] 最小深度=Burst_length-可读的数据 FIFO_Depth=Burst_length-Burst_length*(rd_clk/wr_clk)*(k+1)/(j+1)*rd_rate;
背靠背,读写速率相等
每 100 个时钟写入 80 个数据,每 10 个时钟读取 8 个数据,突发长度为 160
背靠背: 每 100 个时钟写入 80 个数据,那剩下 20 个时钟周期去哪了?每 10 个时钟读取 8 个数据,那剩下 2 个时钟周期去哪了?剩下的周期在哪我们不管,**只考虑最差的情况,即前 20 个时钟周期空闲,后80 个周期写完 80 个数据,立马又是写请求,这次是前 80 个时钟周期写完 80 个数据,后 20 个时钟周期空闲。**即两次连续的突发写入,又称为背靠背。
- 写一个突发最少需要160时钟周期
- 160时钟周期读出160*8/10 = 128个数据
- 最小深度为160 - 128 = 32
Burst_length=2*突发写数据长度 FIFO_Depth=Burst_length=Burst_length*(rd_clk/wr_clk)*rd_rate;
背靠背,读写速率不相等
eg: wclk = 20MHz rclk = 40MHz 每 1000 个时钟周期写入500 个数据,每 4 个时钟周期读出 1 个数据
Burst_length=2*突发写数据长度 rd_clk'=rd_clk/4 wr_clk'=wr_clk FIFO_Depth=Burst_length-Burst_length*(rd_clk'/wr_clk')*rd_rate;
3. FSM有限状态机
https://www.runoob.com/w3cnote/verilog-fsm.html
状态图是以几何图形的方式来描述时序逻辑电路的状态转移规律以及输出与输入的关系。
3.1 基本概念
定义:
状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号依照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。
分类
Moore型:输出仅和状态有关而与输入无关
Mealy型:输出和状态和输入都有关
状态机写法(Mealy型)
- 三段式:
- 第一段:时序逻辑,非阻塞赋值,传递寄存器状态
- 第二段:组合逻辑,阻塞赋值,根据当前状态及当前输入,确定下一个一个状态机的状态
- 第三段:时序逻辑,非阻塞赋值,因是Mealy型,根据当前的状态及当前输入,确定输出信号
- 二段式:
- 第一段:时序逻辑,非阻塞赋值,传递寄存器状态
- 第二段:三段式的第二、三段合并(这里用阻塞还是非阻塞?)
- 一段式:
- 第一段:合并,非阻塞赋值
- 三段式:
状态机写法(Moore型)
- 三段式:区别于Mealy型,还需要增加额外的状态,用以描述Mealy 状态机输出时的输入信号和状态机状态
两种状态机的区别(转移图上看)
Mealy(上)Moore(下)
3.2 代码框架
// vending-machine
// 2 yuan for a bottle of drink
// only 2 coins supported: 5 jiao and 1 yuan
// finish the function of selling and changing
module vending_machine_p3 (
input clk ,
input rstn ,
input [1:0] coin , //01 for 0.5 jiao, 10 for 1 yuan
output [1:0] change ,
output sell //output the drink
);
//machine state decode
//定义状态,FPGA中推荐用独热码和格雷码(Gray)
//状态较少时(4-24个状态)用独热码效果好,状态多时格雷码(状态数大于24)效果好
parameter IDLE = 3'd0 ;
parameter GET05 = 3'd1 ;
parameter GET10 = 3'd2 ;
parameter GET15 = 3'd3 ;
//machine variable
reg [2:0] st_next ;//次态
reg [2:0] st_cur ;//现态
//(1) state transfer
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
st_cur <= 'b0 ;
end
else begin
st_cur <= st_next ;
end
end
//(2) state switch, using block assignment for combination-logic
//all case items need to be displayed completely
//这里面用"="赋值和用"<="没区别
always @(*) begin
//st_next = st_cur ;//如果条件选项考虑不全,可以赋初值消除latch
case(st_cur)
IDLE:
case (coin)
2'b01: st_next = GET05 ;
2'b10: st_next = GET10 ;
default: st_next = IDLE ;
endcase
GET05:
case (coin)
2'b01: st_next = GET10 ;
2'b10: st_next = GET15 ;
default: st_next = GET05 ;
endcase
GET10:
case (coin)
2'b01: st_next = GET15 ;
2'b10: st_next = IDLE ;
default: st_next = GET10 ;
endcase
GET15:
case (coin)
2'b01,2'b10:
st_next = IDLE ;
default: st_next = GET15 ;
endcase
default: st_next = IDLE ;
endcase
end
//(3) output logic, using non-block assignment
reg [1:0] change_r ;
reg sell_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
change_r <= 2'b0 ;
sell_r <= 1'b0 ;
end
else if ((st_cur == GET15 && coin ==2'h1)
|| (st_cur == GET10 && coin ==2'd2)) begin
change_r <= 2'b0 ;
sell_r <= 1'b1 ;
end
else if (st_cur == GET15 && coin == 2'h2) begin
change_r <= 2'b1 ;
sell_r <= 1'b1 ;
end
else begin
change_r <= 2'b0 ;
sell_r <= 1'b0 ;
end
end
assign sell = sell_r ;
assign change = change_r ;
endmodule
4. 时序电路基础&亚稳态
4.1 时序电路基础
建立时间Tsu(setup):触发器在时钟上升沿到来之前,其数据输入端的数据必须保持不变的最小时间。
保持时间Th(hold):触发器在时钟上升沿到来之后,其数据输入端的数据必须保持不变的最小时间。
同步电路和异步电路
同步逻辑是时钟之间有固定的因果关系。异步逻辑是各时钟之间没有固定的因果关系。
在电路中同一个时钟源的时钟分频出来的不同频率的时钟作用于两部分电路,这两部分电路也是同步的。反之,不同时钟源的电路就是异步电路。
异步电路无法作真正意义上的综合及STA,如果在同步电路里夹杂有异步电路,就set_false_path。所以异步电路只有 靠仿真来检查电路正确与否。
在同步电路设计中一般采用D 触发器,异步电路设计中一般采用Latch。
同步设计的优势及规则
优势
- 同步电路比较容易使用寄存器的异步复位/置位端,以使整个电路有一个确定的初始状态
- 在可编程逻辑器件中,使用同步电路可以避免器件受温度,电压,工艺的影响,易于消除电路的毛刺,使设计更可靠,单板更稳定
- 同步电路可以很容易地组织流水线,提高芯片的运行速度,设计容易实现
- 同步电路可以很好地利用先进的设计工具,如静态时序分析工具等,为设计者提供最大便利条件,便于电路错误分析,加快设计进度
规则
- 尽可能在整个设计中只使用一个主时钟和同一个时钟沿,主时钟走全局时钟网络。
- 在FPGA设计中,推荐所有输入、输出信号均应通过寄存器寄存,寄存器接口当作异步接口考虑。
- 当全部电路不能用同步电路思想设计时,即需要多个时钟来实现,则可以将全部电路分成若干局部同步电路(尽量以同一个时钟为一个模块),局部同步电路之间接口当作异步接口考虑
- 当必须采用多个时钟设计时,每个时钟信号的时钟偏差(△T)要严格控制
- 电路的实际最高工作频率不应大于理论最高工作频率,留有设计余量,保证芯片可靠工作
- 电路中所有寄存器、状态机在单板上电复位时应处在一个已知的状态
异步设计中的常见问题及解决方法
尽量转化为同步设计
最小周期计算

Tco:寄存器更新延迟。clock output delay,时钟触发到数据输出的最大延迟时间
最小时钟周期:Tmin = Tco + Tdata + Tsu - Tskew。
系统最快时钟频率(系统最高速度):Fmax = 1/Tmin
时钟偏斜:Tskew = Tclkd – Tclks
时钟抖动与偏斜
时钟抖动(Clock Jitter):指芯片的某一个给定点上时钟周期发生暂时性变化,使得时钟周期在不同的周期上可能加长或缩短。
时钟偏移(Clock Skew):是由于布线长度及负载不同引起的,导致同一个时钟信号到达相邻两个时序单元的时间不一致。
区别:Jitter是在时钟发生器内部产生的,和晶振或者PLL内部电路有关,布线对其没有影响。Skew是由不同布线长度导致的不同路径的时钟上升沿到来的延时不同。
FPGA设计中对时钟的使用:
FPGA芯片有固定的时钟路由,这些路由能有减少时钟抖动和偏差。需要对时钟进行相位移动或变频的时候,一般不允许对时钟进行逻辑操作,这样不仅会增加时钟的偏差和抖动,还会使时钟带上毛刺。一般的处理方法是采用FPGA芯片自带的时钟管理器如PLL,DLL或DCM,或者把逻辑转换到触发器的D输入(这些也是对时钟逻辑操作的替代方案)
4.2 亚稳态
亚稳态:是指触发器无法在某个规定时间段内达到一个确定的状态。
原因:由于触发器的Tsu和Th不满足,当触发器进入亚稳态,使得无法预测该单元的输出,这种不稳定是会沿信号通道的各个触发器级联传播。
消除:两级或多级寄存器同步。理论上亚稳态不能完全消除,只能降低,一般采用两级触发器同步就可以大大降低亚稳态发生的概率,再加多级触发器改善不大。
- 降低系统时钟
- 用更快的FF
- 引入同步机制
- 防止亚稳态传播改善时钟质量
4.3 复位问题
https://www.nowcoder.com/discuss/353159376590544896
同步复位在时钟沿采复位信号,完成复位动作。异步复位不管时钟,只要复位信号满足条件,就完成复位动作。异步复位对复位信号要求比较高,不能有毛刺,如果其与时钟关系不确定,也可能出现亚稳态。
1)尽量少使用复位,特别是少用全局复位,能不用复位就不用,一定要用复位的使用局部复位;
2)如果必须要复位,在同步和异步复位上,则尽量使用同步复位,一定要用异步复位的地方,采用“异步复位、同步释放”;
具体操作:
如果想采用异步复位,又想避免复位结束时,有些触发器处于复位状态,有些触发器处于工作状态的情况(由于skew造成),可以在复位输入的起始路径上加入一级D触发器。并限制同步后复位信号的max_delay。
5. FPGA底层资源
https://wuzhikai.blog.csdn.net/article/details/124973925
6. 静态时序分析STA
静态时序分析:不需要输入向量就能穷尽所有的路径,且运行速度很快、占用内存较少,不仅可以对芯片设计进行全面的时序功能检查,而且还可利用时序分析的结果来优化设计。
动态时序分析:动态时序模拟就是通常的仿真,因为不可能产生完备的测试向量,覆盖门级网表中的每一条路径。因此在动态时序分析中,无法暴露一些路径上可能存在的时序问题
6.1 概念
https://www.nowcoder.com/discuss/353159344550256640
前提:同步逻辑设计
目的:找到隐藏的时序问题,使设计达到时序闭合
能能够识别的故障:建立时间、保持时间、恢复时间、移除时间检查;最小跳变和最大跳变;时钟脉冲宽度、时钟畸变(Skew、Jitter)、总线竞争‘不受约束的逻辑通道、关键路径、约束冲突等。
基础时序:
transition time, propagation delay等参数的定义
Transition Time(转换时间):
对于输入和输出信号,
上升时间:从10%Vdd上升到90%Vdd的时间,
下降时间:从90%Vdd下降到10%Vdd的时间。
上升时间和下降时间统称为Transition Time。
Propagation Delay(传播延时):
在输入信号变化到超过50%Vdd到输出信号变化到超过50%Vdd之间的时间。
Timing constraints include:
setup time, hold time, recovery time, and minimum pulse width.
在时钟沿来临前,输入信号的变化超过50%Vdd的时间到时钟变化超过50%Vdd的时间中,输入信号保持稳定的最小时间。
在时钟沿来临后,输入信号的变化超过50%Vdd的时间到时钟变化超过50%Vdd的时间中,输入信号保持稳定的最小时间。
复位或者置位信号变化超过50%Vdd的时间到时钟变化超过50%Vdd的时间中,时钟沿来临的前最小时间,保证复位或置位完成。
复位或者置位信号变化超过50%Vdd的时间到时钟变化超过50%Vdd的时间中,时钟沿来临的后最小时间,保证置位或复位完成。
最小脉冲宽度就是信号上升沿变化超过50%Vdd到下降沿变化低于50%Vdd时,测量高电平的最小脉冲宽度,低电平最小宽度同理。
时序路径:
- 起点只能是设计的基本输入端口或内部寄存器的时钟输入端,终点只能是内部寄存器的数据输入端或设计的基本输出端口。
- 每条路径仅包含一个起点,一个终点
6.2 关键路径分析及解决方法
关键路径通常是指同步逻辑电路中,组合逻辑时延最大的路径(及布线的延迟),也就是说关键路径是对设计性能起决定性影响的时序路径
https://www.nowcoder.com/discuss/353159376452132864
所谓流水线思想
缩短触发器间组合逻辑的延时时间是提高同步电路速度的关键所在
可以将较大的组合逻辑分解为较小的N块,通过适当的方法平均分配组合逻辑,然后在中间插入触发器,并和原触发器使用相同的时钟,就可以避免在两个触发器之间出现过大的延时,消除速度瓶颈,这样可以提高电路的工作频率。
注意,流水线设计会在原数据通路上加入延时,另外硬件面积也会稍有增加。
在Vivado工具中,可以通过report_timing_summary 等来查看WNS(Worst Nagative Slack)对应最大延迟分析的所有时序路径的最差裕量(Setup)
具体方法
组合逻辑中插入寄存器(插入流水线)
在插入寄存器时,要在组合逻辑中选择合适的位置进行插入,使得插入寄存器后被分割出的几块小的组合逻辑延时基本一致
寄存器平衡(重定时Retiming)
在不增加寄存器个数的前提下,通过改变寄存器的位置来优化关键路径
操作符平衡(加法树、乘法树)
消除代码优先级(case代替if...else)
本身确实不需要优先级的地方,可以使用case代替if…else,使得顺序执行的语句编程并行执行
逻辑复制
高扇出的危害是大大增加了布局布线的难度,这样其扇出的节点也就无法被布局得彼此靠近,所以就导致了布线长度过大的问题。
关键信号后移
关键输入应该在逻辑最后一级提供,其中关键输入为芯片、Slice、或者LUT提供的时延最大的输入,比如在if…else if…链中,将关键信号放在第一级。
6.3 时钟约束的概念及基本策略
分类:
- 周期约束
- 偏移约束
- 静态时序路径约束
一般策略:
附加时序约束的一般策略是先附加全局约束,然后对快速和慢速例外路径附加专门约束。
附加全局约束时,首先定义设计的所有时钟,对各时钟域内的同步元件进行分组,
对分组附加周期约束,然后对FPGA/CPLD输入输出PAD附加偏移约束、对全组合逻辑的PAD TO PAD路径附加约束。
附加专门约束时,首先约束分组之间的路径,然后约束快、慢速例外路径和多周期路径,以及其他特殊路径。
附加约束的作用?
1:提高设计的工作频率(减少了逻辑和布线延时);
2:获得正确的时序分析报告;(静态时序分析工具以约束作为判断时序是否满足设计要求的标准,因此要求设计者正确输入约束,以便静态时序分析工具可以正确的输出时序报告)
3:指定FPGA/CPLD的电气标准和引脚位置。
7.低/高速总线
SPI、IIC、UART
SRIO、PCIE、8B/10B编码
8.AXI总线(需深入)
AXI4-Lite 例子:https://mp.weixin.qq.com/s/3CwXMUWIY-CtZwetYBZY8A
9. 覆盖率
覆盖率 2 个大方面:代码覆盖率,功能覆盖率。
代码覆盖率包括:
语句覆盖率
语句覆盖率上不去时,可以查看未覆盖处的代码是测试用例的疏忽、冗余代码或是保护用途的代码,比如case的default
条件覆盖率
分支覆盖率
针对 if(条件1),只要条件 1 取 true 和 false 都执行过,则这个分支就完全覆盖了
状态机覆盖率
功能覆盖率
又称黑盒测试覆盖率,只关心功能,不关心具体的代码是如何实现的。如果想要统计功能覆盖率,需要在 SystemVerilog 编写的测试用例中添加覆盖组,仿真器基于它来统计功能覆盖率
功能覆盖率高但是代码覆盖率低
分析未覆盖到的代码,推断仿真是否有遗漏的功能点,代码是否为冗余或不可达代码;
功能覆盖率低但是代码覆盖率高
仿真用例没有关注到一些功能点,需要修改测试用例。
10.芯片优化策略
https://www.nowcoder.com/discuss/353159536343195648
(1)面积优化,提高资源利用率以降低功耗要求:串行化,资源共享,逻辑优化;
(2)速度优化,提高运行速度:流水线设计,寄存器配平,关键路径优化,迟置信号后移。
杂项
FPGA中,同步时序电路的延时
- 高速时钟产生计数器以控制延时
- 比较小的延时,触发器打拍
4-LUT:可以看成一个有4位地址线的16x1的RAM
当用户通过原理图或HDL语言描述了一个逻辑电路以后,软件会自动计算逻辑电路的所有可能的结果,并把结果事先写入RAM,这样,每输入一个信号进行逻辑运算就等于输入一个地址进行查表,找出地址对应的内容,然后输出即可
竞争和冒险:在组合电路中,某一输入变量经过不同途径传输后,到达电路中某一汇合点的时间有先有后,这种现象称竞争;由于竞争而使电路输出发生瞬时错误的现象(毛刺)叫做冒险。
判断方法:
- 代数法(如果布尔式中有相反的信号则可能产生竞争和冒险现象)
- 卡诺图:有两个相切的卡诺圈并且相切处没有被其他卡诺圈包围,就有可能出现竞争冒险
- 实验法:示波器观测;
解决方法:
- 加滤波电路,消除毛刺的影响
- 加选通信号,避开毛刺
- 增加冗余项消除逻辑冒险
乒乓操作
缓冲周期①:
1.1输入数据流-->“数据缓冲 模块1”;
缓冲周期②:
2.1通过“输入数据选择单元”的切换,输入数据流-->“数据缓冲模块2”,
2.2将“数据缓冲模块1”缓存的第1个周期数据通过“输入数据选择单元”的选择,送到“数据流运算处理模块”进行运算处理;
缓冲周期③:
3.1 通过“输入数据选择单元”的再次切换,将输入的数据流缓存到“数据缓冲模块1”,
3.2 将 “数据缓冲模块2”缓存的第2个周期的数据通过“输入数据选择单元”切换,送到“数据流运算处理模块”进行运算处理。
...
如此循环。
I.O接口
- 按数据传送格式 并行 / 串行
- 按时序控制方式 同步 / 异步
- 按传送控制方式 直接程序传送 / 中断 / DMA
FPGA悬浮的总线会增加系统内的噪声,增加功率的损耗,并且具有潜在的产生不稳定性的问题,解决方案是加上拉电阻。
单片机上电后没有运转,首先要检查什么
首先应该确认电源电压是否正常;接下来就是检查复位引脚电压是否正常;然后再检查晶振是否起振了。
如果系统不稳定的话,有时是因为电源滤波不好导致的。在单片机的电源引脚跟地引脚之间接上一个0.1uF的电容会有所改善。如果电源没有滤波电容的话,则需要再接一个更大滤波电容,例如220uF的。遇到系统不稳定时,就可以并上电容试试(越靠近芯片越好)。
Others.暂未了解
- 时钟无毛刺切换技术:https://www.nowcoder.com/discuss/353159425357717504
- System Verilog基础,class类、logic等新增类型(四态)、动态数组、UVM组件;
- 时序约束,多时钟周期约束、伪路径、OCV、CPPR等;
- **DFT(Design for Test)**可测性设计,概念;