思路分析:
led.v
`timescale 1ns/1ns
module led
(
input wire key_in , //输入按键
output wire led_out //输出控制led灯
);
//led_out:led灯输出的结果为key_in按键的输入值
assign led_out = key_in;
endmodule
仿真代码:tb_led.v
`timescale 1ns/1ns
module tb_led();
//wire define
wire led_out ;
//reg define
reg key_in ;
//初始化输入信号
initial key_in <= 1'b0;
//key_in:产生输入随机数,模拟按键的输入情况
always #10 key_in <= {$random} % 2; /*取模求余数,产生非负随机数0、1
每隔10ns产生一次随机数*/
//------------- led_inst -------------
led led_inst
(
.key_in (key_in ), //input key_in
.led_out(led_out) //output led_out
);
endmodule
组合逻辑:是Verilog HDL设计中的一个重要组成部分。从电路本质上讲,组合逻辑电路的特点是输出信号只是当前时刻输入信号的函数,与其他时刻的输入状态无关,无存储电路,也没有反馈电路。
多路选择器:即数据选择器,在多路数据传送过程中,能够根据需要将其中任意一路选出来的电路
思路分析:
sel高电平选择in_1,低电平选择in_1
mux2_1.v
`timescale 1ns/1ns
module mux2_1 //模块的开头以“module”开始,然后是模块名“mux2_1”
(
input wire in1, //输入端1,信号名后就是端口列表“();”(端口列表里面列举了该模块对外输入、输出信号的方式、类型、位宽、名字),该写法采用了Verilog-2001标准,这样更直观且实例化更方便,之前的Verilog-1995标准是将模块对外输入、输出信号的方式、类型、位宽都放到外面
input wire in2, //输入端2,当数据只有一位宽时位宽表示可以省略,且输入只能是wire型变量
input wire sel, //选择端,每行信号以“,”结束,最后一个后面不加“,”
output reg out //结果输出,输出可以是wire型变量也可以是reg型变量,如果输出在always块中被赋值(即在“<=”的左边)就要用reg型变量,如果输出在assign语句中被赋值(即在“=”的左边)就要用wire型变量
); //端口列表括号后有个“;”不要忘记
//***************************** Main Code ****************************//
//out:组合逻辑输出sel选择的结果
always@(*) //“*”为通配符,表示只要if括号中的条件或赋值号右边的变量发生变化则立即执行下面的代码,“(*)”在此always中等价于“(sel, in1, in2)”写法
if(sel == 1'b1) //当“if...else...”中只有一个变量时不需要加“begin...end”,也显得整个代码更加简洁
out = in1; //always块中如果表达的是组合逻辑关系时使用“=”进行赋值,每句赋值以“;”结束
else
out = in2;
/*
//out:组合逻辑输出选择结果
always@(*)
case(sel)
1'b1 : out = in1;
1'b0 : out = in2;
default : out = in1; //如果sel不能列举出所有的情况一定要加default。此处sel只有两种情况,并且完全列举了,所以default可以省略
endcase
*/
/*
out:组合逻辑输出选择结果
assign out = (sel == 1'b1) ? in1 : in2; //此处使用的是条件运算符(三元运算符),当括号里面的条件成立时,执行"?”后面的结果;如果括号里面的条件不成立时,执行“:”后面的结果
*/
endmodule //模块的结尾以“endmodule”结束(每个模块只能有一组“module”和“endmodule”,所有的代码都要在它们中间编写)
tb_mux2_1.v
`timescale 1ns/1ns //时间尺度、精度单位定义,决定“#(不可被综合,但在可综合代码中也可以写,只是会在仿真时表达效果,而综合时会自动被综合器优化掉)”后面的数字表示的时间尺度和精度,具体表达含义为:“时间尺度/时间精度”。为了以后编写方便我们将该句放在所有“.v”文件的开头,后面的代码示例将不再显示该句
module tb_mux2_1(); //testbench的格式和待测试RTL模块的格式相同,也是以“module”开始以“endmodule”结束,所有的代码都要在它们中间编写。不同的是在testbench中端口列表为空,因为testbench不对外进行信号的输入输出,只是自己产生激励信号提供给内部实例化待测RTL模块使用,所以端口列表中没有内容,只是列出“()”,当然也可以将“()”省略,括号后有个“;”不要忘记
//reg define
//要在initial块和always块中被赋值的变量一定要是reg型(在testbench中待测试RTL模块的输入永远是reg型变量)
reg in1;
reg in2;
reg sel;
//wire define
//输出信号,我们直接观察,也不用在任何地方进行赋值,所以是wire型变量(在testbench中待测试RTL模块的输出永远是wire型变量)
wire out;
//***************************** Main Code ****************************//
//initial语句是不可以被综合的,一般只在testbench中表达而不在RTL代码中表达。,initial块中的语句上电后只执行一次,主要用于初始化仿真中要输入的信号,初始化值在没有特殊要求的情况下给0或1都可以。如果不赋初值,仿真时信号会显示为不定态(ModelSim中的波形显示红色)
initial
begin //在仿真中begin...end块中的内容都是顺序执行的,在没有延时的情况下几乎没有差别,看上去是同时执行的,如果有延时才能表达的比较明了;而在rtl代码中begin...end相当于括号的作用,在同一个always块中给多个变量赋值的时候要加上
in1 <= 1'b0;
in2 <= 1'b0;
sel <= 1'b0;
end
//in1:产生输入随机数,模拟输入端1的输入情况
always #10 in1 <= {$random} % 2; //取模求余数,产生非负随机数0、1,每隔10ns产生一次随机数
//in2:产生输入随机数,模拟输入端2的输入情况
always #10 in2 <= {$random} % 2;
//sel:产生输入随机数,模拟选择端的输入情况
always #10 sel <= {$random} % 2;
//下面的语句是为了在ModelSim仿真中直接打印出来信息便于观察信号变化的状态,也可以不使用下面的语句而直接观察仿真出的波形
//------------------------------------------------------------
initial begin
$timeformat(-9, 0, "ns", 6); //设置显示的时间格式,此处表示的是(打印时间单位为纳秒,小数点后打印的小数位为0位,时间值后打印的字符串为“ns”,打印的最小数量字符为6个)
$monitor("@time %t: in1=%b in2=%b sel=%b out=%b", $time, in1, in2, sel, out); //只要监测的变量(时间、in1, in2, sel, out)发生变化,就会打印出相应的信息
end
//------------------------------------------------------------
//**************************** Instantiate ***************************//
//待测试RTL模块的实例化,相当于将待测试模块放到测试模块中,并将输入输出对应连接上,测试模块中产生激励信号给待测试模块的输入,以观察待测试模块的输出信号是否正确
//------------- mux2_1_inst -------------
mux2_1 mux2_1_inst
( //第一个是被实例化模块的名子,第二个是我们自己定义的在另一个模块中实例化后的名字。同一个模块可以在另一个模块中或不同的另外模块中被多次实例化,第一个名字相同,第二个名字不同
.in1(in1), //input in1,前面的“in1”表示被实例化模块中的信号,后面的“in1”表示实例化该模块并要和这个模块的该信号相连接的信号(可以取名不同,一般取名相同,方便连接和观察),“.”可以理解为将这两个信号连接在一起
.in2(in2), //input in2
.sel(sel), //input sel
.out(out) //output out
);
endmodule
译码器:译码是编码的逆过程,在编码时,每一种二进制代码,都赋予了特定的含义,即都表示了一个确定的信号或者对象。把代码状态的特定含义翻译出来的过程叫做译码,实现译码操作的电路称为译码器。或者说,译码器是可以将输入二进制代码的状态翻译成输出信号,以表示其原来含义的电路。
译码器是一类多输入多输出组合逻辑电路器件,其可以分为:变量译码和显示译码两类。
模块设计:
波形绘制:
decoder3_8.v
`timescale 1ns/1ns
module decoder3_8
(
input wire in1 , //输入信号in1
input wire in2 , //输入信号in2
input wire in3 , //输入信号in2
output reg [7:0] out //输出信号out
);
//***************************** Main Code ****************************//
/*
//out:根据3个输入信号选择输出对应的8bit out信号
always@(*)
if({in1, in2, in3} == 3'b000) //使用"{}"位拼接符将3个1bit数据按照顺序拼成一个3bit数据
out = 8'b0000_0001;
else if({in1, in2, in3} == 3'b001)
out = 8'b0000_0010;
else if({in1, in2, in3} == 3'b010)
out = 8'b0000_0100;
else if({in1, in2, in3} == 3'b011)
out = 8'b0000_1000;
else if({in1, in2, in3} == 3'b100)
out = 8'b0001_0000;
else if({in1, in2, in3} == 3'b101)
out = 8'b0010_0000;
else if({in1, in2, in3} == 3'b110)
out = 8'b0100_0000;
else if({in1, in2, in3} == 3'b111)
out = 8'b1000_0000;
else //最一个else对应的if中的条件只有一种情况,还可能产生以上另外的7种情况,如果不加这个else综合器会把不符合该if中条件的上面另外7种情况都考虑进去,会产生大量的冗余逻辑并产生latch(锁存器),所以在组合逻辑中最后一个if后一定要加上else,并任意指定一种确定的输出情况
out = 8'b0000_0001;
*/
//out:根据3个输入信号选择输出对应的8bit out信号
always@(*)
case({in1, in2, in3})
3'b000 : out = 8'b0000_0001; //输入与输出的8种译码对应关系
3'b001 : out = 8'b0000_0010;
3'b010 : out = 8'b0000_0100;
3'b011 : out = 8'b0000_1000;
3'b100 : out = 8'b0001_0000;
3'b101 : out = 8'b0010_0000;
3'b110 : out = 8'b0100_0000;
3'b111 : out = 8'b1000_0000;
default: out = 8'b0000_0001; //因为case中列举了in所有可能输入的8种情况,且每种情况都有对应确定的输出,所以此处default可以省略,但是为了以后因不能够完全列举而产生latch,所以我们默认一定要加上default,并任意指定一种确定的输出情况
endcase
endmodule
tb_decoder3_8.v
`timescale 1ns/1ns
module tb_decoder3_8();
//****************** Parameter and Internal Signal *******************//
//wire define
wire [7:0] out;
//reg define
reg in1;
reg in2;
reg in3;
//***************************** Main Code ****************************//
//初始化输入信号
initial begin
in1 <= 1'b0;
in2 <= 1'b0;
in3 <= 1'b0;
end
//in1:产生输入随机数,模拟输入端1的输入情况
always #10 in1 <= {$random} % 2;
//in2:产生输入随机数,模拟输入端2的输入情况
always #10 in2 <= {$random} % 2;
//in3:产生输入随机数,模拟输入端3的输入情况
always #10 in3 <= {$random} % 2;
initial begin
$timeformat(-9, 0, "ns", 6);
$monitor("@time %t: in1=%b in2=%b in3=%b out=%b", $time, in1, in2, in3, out);
end
//------------- decoder3_8_inst -------------
decoder3_8 decoder3_8_inst(
.in1 (in1), //input in1
.in2 (in2), //input in2
.in3 (in3), //input in3
.out (out) //output [7:0] out
);
endmodule
加法器是数字电路中经常用到的一种基本器件,主要用于两个数或者多个数的加和,加法器又分为半加器和全加器。
半加器电路是指对两个输入数据位相加,输出一个结果位和进位,没有进位输入的加法器电路。是实现两个一位二进制数的加法运算电路。
全加器是在半加器的基础上的升级版,除了加数和被加数加和外还要加上上一级传进来的进位信号。
模块设计:
波形图绘制
half_adder.v
`timescale 1ns/1ns
module half_adder
(
input wire in1 , //加数1
input wire in2 , //加数2
output wire sum , //两个数的加和
output wire cout //加和后的进位
);
//***************************** Main Code ****************************//
//sum:两个数加和的输出
//cout:两个数进位的输出
assign {cout, sum} = in1 + in2; //assign的输出用wire,always用reg
endmodule
tb_half_adder.v
`timescale 1ns/1ns
module tb_half_adder();
//wire define
wire sum;
wire cout;
//reg define
reg in1;
reg in2;
//初始化输入信号
initial
begin
in1 <= 1'b0;
in2 <= 1'b0;
end
//in1:产生输入随机数,模拟加数1的输入情况
always #10 in1 <= {$random} % 2; //取模求余数,产生随机数1'b0、1'b1,每隔10ns产生一次随机数
//in2:产生输入随机数,模拟加数2的输入情况
always #10 in2 <= {$random} % 2;
initial begin
$timeformat(-9, 0, "ns", 6);
$monitor("@time %t: in1=%b in2=%b sum=%b cout=%b", $time, in1, in2, sum, cout);
end
//------------- half_adder_inst --------------
half_adder half_adder_inst
(
.in1 (in1 ), //input in1
.in2 (in2 ), //input in2
.sum (sum ), //output sum
.cout (cout ) //output cout
);
endmodule
自底向上设计
自上而下设计
混合使用
模块设计
full_adder.v
`timescale 1ns/1ns
module full_adder
(
input wire in1 , //加数1
input wire in2 , //加数2
input wire cin , //上一级的进位
output wire sum , //两个数的加和
output wire cout //加和后的进位
);
//wire define
wire h0_sum; //在顶层中作为half_adder_inst0的sum信号和half_adder_inst1的in1信号的中间连线
wire h0_cout; //在顶层中作为half_adder_inst0的cout信号和或门的中间连线
wire h1_cout; //在顶层中作为half_adder_inst1的cout信号和或门的中间连线
//------------- half_adder_inst0 --------------
half_adder half_adder_inst0
( //前面是实例化(调用)的模块的名字相当于是告诉顶层我要使用来自half_adder这个模块的功能,后面的是在顶层中重新起的在本模块中的名字相当于是这个模块具体到顶层中
.in1 (in1 ), //input in1 前面in1是相当于half_adder模块中的信号,(in1)顶层中的信号,然后最前面加上“.”,可以形象的理解为把这两个信号线连接到一起(rtl中的实例化过程和Testbench中的实例化过程是一样的,可以对比理解学习)
.in2 (in2 ), //input in2
.sum (h0_sum ), //ouptut sum
.cout (h0_cout) //output cout
);
//------------- half_adder_inst0 --------------
half_adder half_adder_inst1
( //同一个模块可以被实例化多次(所以相同功能只设计一个通用模块即可),但是在顶层的名字一定要区别开,这样子才能表达出是实例化的两个相同功能的模块
.in1 (h0_sum ), //input in1
.in2 (cin ), //input in2
.sum (sum ), //ouptut sum
.cout (h1_cout) //output cout
);
//cout:总的进位信号
assign cout = h0_cout | h1_cout;
endmodule
tb_full_adder.v
`timescale 1ns/1ns
module tb_full_adder();
//wire define
wire sum;
wire cout;
//reg define
reg in1;
reg in2;
reg cin;
//初始化输入信号
initial begin
in1 <= 1'b0;
in2 <= 1'b0;
cin <= 1'b0;
end
//in1:产生输入随机数,模拟加数1的输入情况
always #10 in1 <= {$random} % 2; //取模求余数,产生随机数1'b0、1'b1,每隔10ns产生一次随机数
//in2:产生输入随机数,模拟加数2的输入情况
always #10 in2 <= {$random} % 2;
//cin:产生输入随机数,模拟前级进位的输入情况
always #10 cin <= {$random} % 2;
initial begin
$timeformat(-9, 0, "ns", 6);
$monitor("@time %t: in1=%b in2=%b cin=%b sum=%b cout=%b", $time, in1, in2, cin, sum, cout);
end
//------------- full_adder_inst --------------
full_adder full_adder_inst
(
.in1 (in1 ), //input in1
.in2 (in2 ), //input in2
.cin (cin ), //input cin
.sum (sum ), //output sum
.cout (cout ) //output cout
);
endmodule
latch其实就是锁存器,是一种在异步电路系统中,对输入信号电平敏感的单元,用来存储信息。
锁存器在数据未锁存时,输出端的信号随输入信号变化,就像信号通过一个缓冲器,一旦锁存信号有效,则数据被锁存,输入信号不起作用。因此,锁存器也被称为透明锁存器,指的是不锁存时输出对于输入时透明的。
异步电路:主要是组合逻辑电路,用于产生FIFO或RAM的读写控制信号脉冲,但它同时也用于在时序电路中,此时它没有统一的时钟,状态变化的时刻是不稳定的,通常输入信号只在电路处于稳定状态时才发生变化。
同步电路:是由时序电路(寄存器和各种触发器)和组合逻辑电路构成的电路,其所有操作都是在严格的时钟控制下完成的。这些时序电路共享同一个时钟CLK,而所有的状态变化都是在时钟的上升沿(或下降沿)完成的。
latch的危害:
产生latch的几种情况:
例1:38译码器if-else的latch
例2:38译码器case没有default的latch
例3:组合逻辑中输出变量赋值给自己的latch