1、流水线设计
利用一块组合逻辑去做8位的加法,速度比做2位的加法慢,所以可以采用4级流水线设计,每一级只做两位的加法操作,因此时钟频率可以大大提高。 数字电路里面的流水线设计是由时钟信号来控制的,时钟信号上升沿一来,就将本级的运算结果和需要送给下一级电路处理的数据存入到寄存器并送给下一级电路处理。
2、如何处理双向信号(verilog中用关键词inout定义的信号都是双向信号)
四线操作中IO0,IO1,IO2和IO3全部都可以用来发送数据以及接收数据,所以在编写代码的时候要把它们全部定义成双向类型的信号,即inout类型。所以在编写四线操作的代码之前有必要提前熟悉一下inout信号的处理方法。
总的来说,inout信号大致有以下4个特征:
(1)、inout端口不能被复制为reg型,因此不能被用在always语句中。
(2)、对于inout端口的逻辑判断,要用到?:条件表达式,来控制高阻的赋值。
(3)、inout信号需要用一个中转的寄存器,在always语句中才可以才可以将输入的信号赋值给输出(用inout代替纯output)。
(4)、高阻态不要用于芯片内部,应该用逻辑引到引脚处,然后用高阻来实现。
实际上,在FPGA内部的双向信号是通过一个三态门来实现的,一个典型的三态门结构如下:
module Test_inout
(
input I_clk,
input I_rst_n,
.
.
.
inout IO_data,
.
.
.
)
reg R_data_out ;
wire I_data_in ;
assign IO_data = Control ? R_data_out : 1'bz ;
assign I_data_in = IO_data ;
always @(posedge I_clk or negedge I_rst_n)
begin
.
.
.
;
end
endmodule
上面的代码表达的意思是:当Control为1是,IO_data信号作为输出,输出R_data_out的值,当Control为0时,IO_data信号作为输入,输入的值赋给I_data_in变量。
值得注意的是,inout作为输出的时候不能直接给他赋值,而需要通过一个中间变量R_data_out来间接赋值,同时inout变量的输入输出属性必须通过一个控制信号Control来控制,控制性号为高时为inout信号作为输出,输出R_data_out的值,为低时inout处于高阻状态,这时才作为输入接收外部的数据。
module inout_top
(
input I_data_in ,
inout IO_data ,
output O_data_out ,
input Control
);
assign IO_data = Control ? I_data_in : 1'bz ;
assign O_data_out = IO_data ;
endmodule
当Control为1时,IO_data为输出,输出I_data_in的值
当Control为0时,IO_data为输入,把输入的信号赋值给O_data_out
这段代码在Vivado2015.4.2编译环境下的RTL图如下图所示
在ISE14.7的编译环境下的RTL图如下图所示
可以发现在Vivado2015.4.2环境的Control信号的IBUF后面居然还综合出了一个LUT,在ISE14.7环境下Control信号后面综合出了一个反向器,出现这个LUT和反向器的原因是Control为1才把IO_data设置成输出,而在Xilinx中一个IOBUF资源默认T端为0时IO端才为输出,T端为1时,IO端为输入,所以把
assign IO_data = Control ? I_data_in : 1’bz ;
改为
assign IO_data = (Control == 1’b0) ? I_data_in : 1’bz ;
在Vivado2015.4.2环境下综合出的RTL图为下图
在ISE14.7的环境下综合出的RTL图如下图所示
Vivado环境中LUT和ISE环境中的反相器不见了,节省了1个Cell资源。
module inout_top
(
input I_data_in,
inout IO_data ,
output O_data_out ,
input Control
);
IOBUF #(
.DRIVE(12), // Specify the output drive strength
.IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE"
.IOSTANDARD("DEFAULT"), // Specify the I/O standard
.SLEW("SLOW") // Specify the output slew rate
) IOBUF_inst (
.O(O_data_out), // Buffer output
.IO(IO_data), // Buffer inout port (connect directly to top-level port)
.I(I_data_in), // Buffer input
.T(Control) // 3-state enable input, high=input, low=output
);
endmodule
总结,利用Verilog处理双向信号有两种方式:
1、写代码
assign IO_data = (Control == 1’b0)? I_data_in : 1’bz ;
assign O_data_out = IO_data ;
2、例化IOBUF原语
IOBUF #(
.DRIVE(12), // Specify the output drive strength
.IBUF_LOW_PWR(“TRUE”), // Low Power - “TRUE”, High Performance = “FALSE”
.IOSTANDARD(“DEFAULT”), // Specify the I/O standard
.SLEW(“SLOW”) // Specify the output slew rate
) IOBUF_inst (
.O(O_data_out), // Buffer output
.IO(IO_data), // Buffer inout port (connect directly to top-level port)
.I(I_data_in), // Buffer input
.T(Control) // 3-state enable input, high=input, low=output
3***当三段式状态机的输出基于nextstate描述时,无法用同一个输入信号即触发当前状态跳转,又控制当前状态输出正确逻辑***
三段式状态机的第三段并没有规定一定要基于nextstate输出,只是主流资料在介绍三段式状态机时,多用moor型为例,moor型的特点是输出仅由状态决定,当状态变化时,输出立刻变化,如要实现输出紧跟着状态变化,第三段中就必须要基于nextstate输出才可以
第三段使用nextstate和state的区别在于,当状态跳转时,基于nextstate的输出是立刻变化的,而基于state输出会延迟一个周期,其他情况都一样,应该根据自己的时序要求,选择用nextstate还是state。
4、MOS管
NMOS管在GS端输入为0时,DS端断开;在GS端输入为1时,DS端导通。
PMOS管在GS端输入为0时,DS端导通;在GS端输入为1时,DS端断开。
1个NMOS管+1个PMOS管可以实现非门电路。
2个NMOS管+2个PMOS管可以实现与非门电路
2个NMOS管+2个PMOS管可以实现或非门电路
3个NMOS管+3个PMOS管可以实现与门电路
3个NMOS管+3个PMOS管可以实现或门电路
·FPGA的配置方式有:
(1)、主串配置方式
(2)、从串配置方式
(3)、主并配置方式(8位或16位)
(4)、从并配置方式(8位、16位或32位)
(5)、JTAG/边界扫描配置
通过JTAG模式去固化FPGA外部接的Memory,时钟是FPGA产生的就称为主模式,如果是FPGA外部器件产生时钟就是从模式,
串是指SPI或者QSPI Flash有一个或者4个bit的数据位,并是指8、16、32位的。
主串通常由于SPI Flash或者 QSPI Flash
从串用于模拟外部处理器模拟实现SPI或者QSPI的接口实现对FPGA的配置。
5、跨时钟域处理
(1)、单bit打两拍(慢到快)
(2)、多bit异步FIFO、异步双口RAM
(3)、加握手信号
(4)、格雷码转换
针对单bit打两拍由快到慢会出现的问题:
如果单bit信号从时钟域A到时钟域B,那么存在两种不同的情况,传输脉冲信号pulse_a或传输电平信号level_a。实际上,在一般情况下只有电平信号level_a的宽度能被clk_b采集到才可以保证系统正常工作。那么对于脉冲信号pulse_a采取怎样的处理方法呢?可以用一个展宽信号来替代pulse_a实现垮时钟域的握手。
主要原理就是先把脉冲信号在clk_a下展宽,变成电平信号signal_a,再向clk_b传递,当确认clk_b已经“看见”信号同步过去之后,再清掉signal_a。代码通用框架如下:
module sync_pulse(
input clk_a,
input clk_b,
input rst_n,
input pulse_a_in,
output pulse_b_out,
output b_out
);
reg signal_a;
reg signal_b;
reg signal_b_r1;
reg signal_b_r2;
reg signal_b_a1,
reg signal_b_a2;
//在时钟域clk_a下,生成展宽信号signal_a
always @ (posedge clka or negedge rst_n) begin
if(!rst_n) begin
signal_a <= 1'b0;
end
else if(pulse_a_in) begin
signal_a <= 1'b1;
end
else if(signal_b_a2) begin //检测到signal_b1_a2被拉高,则拉低signal_a
signal_a <= 1'b0;
end
end
//在时钟域clk_b下,采集signal_a,生成signal_b
always @ (posedge clk_b or negedge rst_n) begin
if(rst_n) begin
signal_b <= 1'b0;
end
else begin
signal_b <= signal_a;
end
end
//打两拍
always @ (posedge clk_b or negedge rst_n) begin
if(!rst_n) begin
signal_b_r1 <= 1'b0;
signal_b_r2 <= 1'b0;
end
else begin
signal_b_r1 <= signal_b;
signal_b_r2 <= signal_b_r1;
end
end
//在时钟域clk_a下,采集signal_b_r1,用于反馈来拉低展宽信号signal_a
always @ (posedge clk_a or negedge rst_n) begin
if(!rst_n) begin
signal_b_a1 <= 1'b0;
signal_b_a2 <= 1'b0;
end
else begin
signal_b_a1 <= signal_b_r1;
signal_b_a2 <= signal_b_a1;
end
end
assign pulse_b_out = signal_b_r1 & (~signal_b_r2)
assign b_out = signal_b_r1;
endmodule
来自异步时钟域的输入需要寄存一次以同步化,再寄存一次以减少亚稳态带来的影响。
需要用到跳变沿的来自不同时钟域的输入,需要用到3个触发器,前两个用以同步,第3个触发器的输出和第2个的输出经过逻辑门来判断跳变沿。
6、什么是冒险竞争?
下面这个电路,使用了两个逻辑门,一个非门和一个与门,本来在理想情况下F的输出应该是一直稳定的0输出,但是实际上每个门电路从输入到输出是一定会有时间延迟的,这个时间通常叫做电路的开关延迟。而且制作工艺、门的种类甚至制造时微小的工艺偏差,都会引起这个开关延迟时间的变化。
实际上如果算上逻辑门的延迟的话,那么F最后就会产生毛刺。信号由于经由不同路径传输达到某一汇合点的时间有先有后的现象,就称之为竞争,由于竞争现象所引起的电路输出发生瞬间错误的现象,就称之为冒险,FPGA设计中最简单的避免方法是尽量使用时序逻辑同步输入输出。
(1)、加滤波电容,消除毛刺的影响
(2)、加选通信号,避开毛刺
(3)、增加冗余项,消除逻辑冒险。
7、MOS逻辑门
与非门:上并下串(上为PMOS,下为NMOS)
或非门:上串下并(上为PMOS,下为NMOS)
反相器(上为PMOS,下为NMOS)
8、Glitch Free时钟切换技术
介绍两种时钟切换方法,分别对应两种情况,第一种时两个时钟源的频率呈倍数关系,第二种是两个时钟源完全没有关系,异步时钟。
如果采用直接切换的方法:
assign outclk = sel? clk0: clk1;
肯定会产生毛刺,这对整个系统是很危险的,因为有些寄存器可能会捕获到时钟沿其他没捕获到,造成系统的不稳定。
下面是使用AND-OR型多路复用器逻辑进行简单的时钟切换
可以看出,当SELECT信号改变时,如果当前时钟源正好处于高电平,这样切换则引起了毛刺。
assign outclk = (clk1 & select) | (~select & clk0);
避免毛刺的方法如下:
下图针对的是两个时钟源频率成倍数关系
在每个时钟源的选择路径中插入一个下降沿触发的D触发器,这样可以保证上面的情况被避免,确保在切换时钟源时,即使任意时钟处于高电平,也不会引起输出的变换,时钟源切换时,这个反馈能保证一个时钟被完全取消选择后,输出传播另一个时钟,从而避免产生任何毛刺。
这个电路有三个时序路径需要考虑,SELECT到两个触发器的任何一个,DFF0到DFF1,DFF1到DFF0,这三条路径上的输入信号与时钟边沿同时发生变化,都可能会引起亚稳态,所以需要将触发器的触发边沿和SELECT信号的变换边沿分开,这可以通过时序约束来实现,因为这两个时钟是呈倍数的关系。
reg out1;
reg out0;
always @(negedge clk1 or negedge rst_n)begin
if(rst_n == 1'b0)begin
out1 <= 0;
end
else begin
out1 <= ~out0 & select;
end
end
always @(negedge clk0 or negedge rst_n)begin
if(rst_n == 1'b0)begin
out0 <= 0;
end
else begin
out0 <= ~select & ~out1;
end
end
assign outclk = (out1 & clk1) | (out0 & clk0);
第二种方法是针对两个异步时钟源的切换,这个方法是在第一种方法的基础上,在选择路径上再插入一个上升沿触发D触发器,这是为了针对对两个异步时钟源产生的反馈信号以及异步信号SELECT,对选择信号进行同步处理,这样即使是两个异步的时钟源进行切换,也可以避免亚稳态的产生。
reg out_r1;
reg out1;
reg out_r0;
reg out0;
always @(posedge clk1 or negedge rst_n)begin
if(rst_n == 1'b0)begin
out_r1 <= 0;
end
else begin
out_r1 <= ~out0 & select;
end
end
always @(negedge clk1 or negedge rst_n)begin
if(rst_n == 1'b0)begin
out1 <= 0;
end
else begin
out1 <= out_r1;
end
end
always @(posedge clk0 or negedge rst_n)begin
if(rst_n == 1'b0)begin
out_r0 <= 0;
end
else begin
out_r0 <= ~select & ~out1;
end
end
always @(negedge clk0 or negedge rst_n)begin
if(rst_n == 1'b0)begin
out0 <= 0;
end
else begin
out0 <= out_r0;
end
end
assign outclk = (out1 & clk1) | (out0 & clk0);