系列文章目录
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、时序逻辑
组合逻辑最大的缺点就是会存在竞争和冒险问题,这种竞争与冒险问题是非常危险的,常常会引起电路的不稳定性和工作时的不确定性,而我们使用时序逻辑就可以极大的避免这种问题,从而使系统更加稳定。
时序逻辑最基本的单元就是寄存器,寄存器具有存储功能,一般是由D触发器构成,由时钟脉冲控制,每个D触发器(D Flip Flop ,DFF)能够存储一位二进制码。
寄存器---->一般是由DFF构成(D触发器)
D触发器的功能为:在一个脉冲信号(一般为晶振产生的时钟脉冲)上升沿或下降沿的作用下,将信号从输入端D送到输出端Q,如果时钟脉冲的边沿信号未出现,即使输入信号改变,输出信号仍然保持原值,且寄存器拥有复位清零功能,其复位又分为同步复位和异步复位。
区分一个设计是组合逻辑电路还是时序逻辑电路主要是看数据工作是不是在时钟沿下进行的,
在FPGA的设计中,复杂的电路设计都要用到时序逻辑电路,往往都是以时序逻辑电路为主,组合逻辑为辅的混合逻辑电路。
二、同步复位的D触发器和异步复位的D触发器的区别
同步复位:时钟上升沿(或者下降沿)复位,时钟上升沿(或者下降沿)释放
异步复位:立刻复位,时钟上升沿(或者下降沿)释放
三、时序逻辑电路的特点
1.过滤毛刺
2.延期一拍
因为我们所画的波形图都是基于前仿真的,没有加入门延时的信息,所以很多时候数据的变化都是和时钟直接对齐的(在现实中实际上不可能完全对其的)。
当表达时序逻辑时如果时钟和数据是对齐的,则默认当前时钟沿采集到的数据为在该时钟上升沿前一时刻的值;
当表达组合逻辑时如果时钟和数据是对齐的,则默认当前时钟沿采集到的数据为在该时钟上升沿同一时刻的值。
而仿真工具在进行RTL代码的仿真时也遵循这个规则,我们也可以理解为仿真寄存器是按照以下的理想环境下进行的:
①建立时间Tsu(指触发器的时钟信号上升沿到来以前,数据稳定不变的最小时间)最大(一个时钟周期)
②保持时间Th(指触发器的时钟信号上升沿到来以后,数据稳定不变的最小时间)最小(为0)
而在仿真组合逻辑时因为没有时钟也就没有建立时间和保持时间的概念,所以数据只要有变化就立刻有效。这里我们在画波形图的时候一定要记住这个“延一拍”的效果,否则我们绘制的波形图就会和最后的仿真结果不符,也可能会导致最后的逻辑混乱
四、同步复位的D触发器和异步复位的D触发器RTL综合出来的不同
同步复位D触发器:只有一个寄存器构成
异步复位D触发器:由一个选择器和一个寄存器构成yiwe
疑问:寄存器和D触发器,到底是谁构成谁啊???
五、有几点要注意的
1.always块既可以是组合逻辑也可以是时序逻辑,而且变量都要定义成reg
①组合逻辑:
always@(*)或者always(n1, n2, n3…)
使用"="赋值
②时序逻辑
always@(posedge sys_clk)
使用"<="赋值
注意:在一个always块一般只有一个变量赋值
2.assign是只能用来表示组合逻辑
3.仿真代码原理图
4.仿真代码编程规范
`timescale 1ns/1ns //时间尺度、精度单位定义,决定”#“后面的数字表示的时间尺度和精度,具体表达的含义为:”时间尺度/时间精度“,
//(不可被综合,但在可综合代码中也可以写,只是会在仿真时表达结果,而在综合会自动被综合器优化掉)
//为了以后编写方便,我们将该语句放在所有”.v“文件的开头,后面的代码示例将不再显示该句
module tb_flip_flop(); //testbench的格式和待测试的RTL模块的格式相同,也是以“module”开始,以“endmodule”结束,所有的代码都要在他们中间编写
//不同的是在testbench中端口列表为空,因为testbench不对外进行信号的输入输出,只是自己产生激励信号提供给内部实例化待测RTL模块使用
//所以端口列表中没有内容,只是列出”()“,当然可以将”()“省略,括号后面有个”;“不要忘记!!!
//要在initial块和always块中被赋值的变量一定是reg型
//在testbench中待测试RTL模块的输入永远是reg型变量
reg sys_clk;
reg sys_rst_n;
reg key_in;
//输出信号,我们直接观察,也不用再任何地方进行赋值,所以是wire型变量(在testbench中待测试RTL模块的输出永远是wire型变量)
wire led_out;
//initial语句是不可以被综合的,一般只在testbench中表达而不在RTL代码中表达,
//initial块中的语句上电后只执行一次,主要用于初始化仿真中要输入的信号,初始化值在没有特殊要求的情况下给0或者1都可以。
//如果不赋初值,仿真时的信号会显示为不定的状态(ModelSim中的波形显示红色)
//初始化系统时钟、全局复位和输入信号
initial
begin //在仿真中begin...end块中的内容都是顺序执行的
//在没有延迟的情况下几乎没有差别,看上去是同时执行的
//如果有延迟时才能表达的比较明了;
//而在RTL代码中begin...end相当于括号的作用
//在同一个always块中给多个变量赋值的时候要加上
sys_clk = 1'b1; //时钟信号的初始化为1,且用“=”赋值,其他信号的赋值都是用“<=”!!!(为什么!!!)
sys_rst_n <= 1'b0; //因为低电平复位,所以复位信号的初始化为0
key_in <= 1'b0; //输入信号按键的初始化,为0和1均可
#20
sys_rst_n <= 1'b1; //初始化20ns后,复位释放,因为是低电平复位,所以释放时,把信号拉高后系统才开始工作
#210
sys_rst_n <= 1'b0; //为了观察同步复位和异步复位的区别,在复位释放后电路工作210ns后再让复位有效,之所以选择延迟
//210ns,而不是200ns或220ns,是因为能够使复位信号在时钟下降沿时复位,能够清晰的看出同步复位和异步复位的差别!!!!!
#40
sys_rst_n <= 1'b1; //复位40ns后再次让复位释放掉
end
//sys_clk:产生输入随机数,模拟系统时钟的输入情况,每隔10ns电平翻转一次,周期为20ns,频率为50MHz
always #10 sys_clk = ~sys_clk;//取模求余数,产生随机数1'b0、1'b1,每隔10ns产生一次随机数
//key_in:产生输入随机数,模拟按键的输入情况
always #20 key_in <= {$random} % 2; //取模求余数,产生非负随机数0、1,每隔20ns产生一次随机数(之所以每20ns产生一次随机数而不是之前的每10ns产生一次随机数,是
//为了在时序逻辑中能够保证key_in信号的变化的时间小于等于时钟周期,这样就不会产生类似毛刺的变化信号,虽然产生的毛刺在时序
//电路中也能被滤除掉,但是不便于我们观察波形)
//下面的语句是为了在ModelSim仿真中直接打印出来信息便于观察信号变化的状态
//也可以不使用下面的语句而直接观察仿真出的波形
//-------------------------------------------------------------------
initial begin
$timeformat(-9, 0, "ns", 6);//设置显示的时间格式,此处表示的是(打印时间单位为纳秒,小数点后打印的小数位为0位,时间值后打印的字符串为”ns“,打印的最小数量字符为6个)
//只要监测的变量(时间、in1、in2、cin、sum、cout)发生变化,就会打印出相应的信息
$monitor("@time %t:key_in=%b led_out=%b", $time,key_in,led_out);
end
//-------------------------------------------------------------------
//待测试RTL模块的实例化,相当于将待测试模块放到测试模块中,并将输入输出对应连接上,测试模块中产生激励信号给待测试模块的输入,以观察待测试模块的输出信号是否正确
//------------------------flip_flop_inst--------------------------------
flip_flop flip_flop_inst
//第一个是被实例化模块的名字,第二个是我们自己定义的在另一个模块中实例化后的名字。同一个模块可以在另一个模块中或不同的另外模块中被多次实例化,第一个名字相同,第二个名字不同
(
//前面的”in1“表示被实例化模块中的信号,后面的”in1“表示实例化该模块并要和这个模块的该信号相连接的信号(可以取名不同,一般取名相同,方便连接和观察)
//"."可以理解为将这两个信号连接在一起
.sys_clk(sys_clk), //input sys_clk
.sys_rst_n(sys_rst_n), //input sys_rst_n
.key_in(key_in), //input key_in
.led_out(led_out) //output led_out
);
endmodule
//备注:上面用到了2个initial和4个always块,上电后这6个模块同时执行,也就是所谓的”并行“执行,在rtl代码中也是同样的
5.仿真代码中的几个疑问:
①两个initial说是执行一次,为啥后面的那个initial一致在打印,难道后面那个initial只是在初始化一次后台的某个模块,之后他自动运行,不是运行initial中的语句
②时钟信号sys_clk为什么用“=”赋值,而不是用的"<="赋值,难道是因为sys_clk表示的组合逻辑,其他的都是时序逻辑???
6.编程规范
//always中的if-else实现方法
module mux2_1 //模块开头以“module”开始,然后是模块名“mux2_1”
( //模块名后面就是端口列表“();”------端口列表里例举了该模块对外输入、输出信号的方式、类型、位宽、名字,
// 该写法采用verilog-2011标准,这样更直观且实例化更加方便,
// 之前的verilog-1995标准是将模块对外输入、输出信号的方式、类型、位宽都放在外面
input wire in1, //输入端1
input wire in2, //输入端2,当数据只有1位宽时位宽表示可以省略,且输入只能是wire型变量
input wire sel, //选择端,每行信号以“,”结束,最后一个后面不加“,”
output reg out //结果输出,输出可以是wire型变量也可以是reg型变量,
//如果输出在always块中被赋值(即在“<=”的左边),就要用reg型变量,
//如果输出在assign语句中被赋值(即在“=”的左边),就要用wire型变量
); //端口列表括号后有个“;”不要忘记
//out:组合逻辑输出sel选择的结果
always@(*) //“*”为通配符,表示只要if括号中的条件或者赋值号右边的变量发生变化,则立即执行下面的代码,“(*)”在此always中等价“(sel, in1, in2)”写法
if(sel == 1'b1) //当“if...else...”中只有一个变量时不需要加“begin...end”,也显得代码更加的简洁
out = in1; //always块中如果表达的是组合逻辑关系时使用“=”进行赋值,每句赋值以“;”结束
else
out = in2;
endmodule //模块的结尾以“endmodule”结束,每个模块只能有一组“module”和“endmodule”,所有的代码都要在他们中间编写
7.RTL
我们发现这并不是最基本的门电路,而是一个多路器的符号,之前不是说数字电路不都是由最基本的门电路构成的吗,这个为什么不是呀?因为我们描述的角度不同,我们是从寄存器传输级(RTL)这个层次来描述的,最基本的单元可能就是这些寄存器、多路器、译码器、比较器、加法器等等,当然在会有一些门电路(在以上RTL基本单元没有的功能上会体现),这些基本的单元再往底层划分还是可以由其他的门电路构成的,所以在描述这些电路功能时我们也可以用最基本的门电路来描述,那我们最后看到的RTL视图就是由门电路构成的了,其缺点就是效率太低。既然我们可以从更高的层次描述实现的功能来提高效率,为什么还要用低层次的描述方式呢,所以基于门级的描述我们很少用,大家在看其他资料的时候有很多都是将这两者混在一起讲的,这样是让初学者感觉迷惑的地方。那就有人问了还有没有更高层次的描述方法?当然有,比寄存器传输级还高的描述方式有算法级和系统级,将会使用到更高级的语言,如System Verilog和Ssytem C,也可以使用C和C++再通过高层次综合(High-level Synthesis,HLS)的方式来实现。