赞
踩
There are some HDLBits practices.
由于数字电路由导线连接的逻辑门组成,任何电路都可以表示为一些模块的组合和赋值语句。然而,有时这并不是描述电路最方便的方式。过程(always块就是一个例子)提供了描述电路的另一种语法。
对于硬件综合,两种类型的always块是相关的:
组合always块等价于赋值语句,因此总有一种方法可以用这两种方式来表示组合电路。选择使用哪一种主要是哪一种语法更方便的问题。注意:过程块内部的代码语法与外部的代码不同 。过程块有更丰富的语句集(例如,if-then, case),不能包含连续赋值,但也引入了许多新的非直观的出错方式。(程序性连续作业确实存在,但与连续作业有所不同)。
例如,赋值和组合always块描述同一电路。两者都创造了相同的组合逻辑。当任何一个输入(右边)改变值时,两者都会重新计算输出。
assgin out1 = a & b | c^ d;
always @(*)
out2 = a & b | c ^ d;
对于组合的always块,始终使用(*)的敏感信号列表。显式地列出信号很容易出错(如果漏掉一个),并且在硬件综合时被忽略。如果你显式地指定了灵敏度列表,但漏了一个信号,综合硬件仍然会像(*)被指定一样工作,但是仿真模拟将不会也不匹配硬件的行为。(在SystemVerilog中,使用always_comb)。
关于wire和reg的注释: assign语句的左侧必须是net类型(例如wire),而过程赋值语句(在always块中)的左侧必须是变量类型(例如reg)。这些类型(wire vs. reg)与所综合的硬件没有任何关系,只是Verilog作为硬件模拟语言使用时留下的语法。
使用赋值语句和组合的always块构建AND门。(由于赋值语句和组合块总是相同的功能,没有办法强制你使用这两个方法。但你是来练习的,对吧?(让你都是实现一遍))。
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
output wire out_assign,
output reg out_alwaysblock
);
assign out_assign = a & b;
always @(*)
begin
out_alwaysblock = a & b;
end
endmodule
assign,连续赋值,就是无条件全等,对象为wire类型;alyways 就是敏感赋值,有条件相等,对象为reg,敏感信号列表,电平触发时,综合为组合电路,边沿触发时,才综合为时序电路。
对于综合电路,两种类型always模块总是相关联的:
时序always块创建一团组合逻辑,就像组合always块一样,但也在一团组合逻辑的输出上创建一组触发器(或“寄存器”)。一团逻辑块的输出不能立即可见,而是只在下一个(posedge clk)之后的输出可见。(a blob of :一系列,团,块;不能详细定义形容的整体)。
Verilog中存在的三种赋值语句:
注意
在一个组合always块中,使用阻塞赋值。在时序always块中,使用非阻塞赋值。充分理解Verilog模拟器如何跟踪事件对硬件设计是特别有用的。如果不遵循这一规则,将很难找到仿真和合成硬件之间的不确定性和不同的错误。
用三种方法构建异或门,使用赋值语句、组合always块和时序always块。请注意,时钟always块产生一个不同于其他两个的电路:有一个触发器,所以输出延迟。
// synthesis verilog_input_version verilog_2001 module top_module( input clk, input a, input b, output wire out_assign, output reg out_always_comb, output reg out_always_ff ); assign out_assign = a ^ b; always @(*) begin out_always_comb = a ^ b; end always @(posedge clk ) begin out_always_ff <= a ^ b; end endmodule
if条件语句相当于一个二选一选择器,eg:
always @(*) begin
if (condition) begin
out = x;
end
else begin
out = y;
end
end
如果条件为真输出if下的,为假输出其他的;这相当于条件预算符下的连续赋值:
assign out = (condition) ? x : y;
然而,过程赋值if条件语句中有新的一种错误,只有输出有赋值的时候才能综合成组合电路。
构建一个2-to-1多路选择器,在a和b之间进行选择。如果sel_b1和sel_b2都为true,则选择b。否则,选择a。重复同样的操作两次,一次使用assign语句,一次使用过程性if语句。
module top_module( input a, input b, input sel_b1, input sel_b2, output wire out_assign, output reg out_always ); assign out_assign = (sel_b1 & sel_b2)? b : a; always @(*) begin if (sel_b1 == 1'b1 & sel_b2 == 1'b1) begin out_always = b; end else begin out_always = a; end end endmodule
在设计电路时,你必须首先从电路的角度考虑:
你不能做的是先写代码,然后希望它生成一个合适的电路。
语法正确的代码不一定会产生合理的电路(组合逻辑+触发器)。通常的理由是:“在你指定的那些情况之外会发生什么?”Verilog的回答是:保持输出不变。(???问号脸)这种“保持输出不变”的行为意味着需要记住当前的状态,从而产生一个锁存器。组合逻辑(例如,逻辑门)不能记住任何状态。小心警告(10240):除非锁存器是故意的,否则它几乎总是表明有故障。组合电路必须为所有条件下的所有输出分配一个值。这通常意味着您总是需要为输出分配可选择子句或默认值。
以下代码包含创建锁存器的不正确行为。修复bug,只有当电脑过热时你才会关掉它,当你到达目的地或需要加油时就停止开车。
module top_module ( input cpu_overheated, output reg shut_off_computer, input arrived, input gas_tank_empty, output reg keep_driving ); // always @(*) begin if (cpu_overheated) begin shut_off_computer = 1; end //give every selection the begin ~ end ,that nice coding style else begin shut_off_computer = 0; shut_off_computer = shut_off_computer; end end always @(*) begin if (~arrived) begin keep_driving = ~gas_tank_empty; end else begin keep_driving = ~arrived; end end endmodule
Verilog中的Case语句几乎等价于一个if-elseif-else序列,它将一个表达式与其他表达式的列表进行比较。它的语法和功能与C语言中的switch语句不同。
如果选择项多,case语句比if语句更方便。因此,在这个练习中,创建一个6:1的多路选择器。当sel在0到5之间时,选择相应的数据输入。否则,输出0。数据输入和输出都是4位宽。
module top_module ( input [2:0] sel, input [3:0] data0, input [3:0] data1, input [3:0] data2, input [3:0] data3, input [3:0] data4, input [3:0] data5, output reg [3:0] out );// always@(*) begin // This is a combinational circuit case(sel) 3'b000: out = data0; 3'b001: out = data1; 3'b010: out = data2; 3'b011: out = data3; 3'b100: out = data4; 3'b101: out = data5; default: out = 4'b0000; endcase end endmodule
优先编码器是一种组合电路,当给定一个输入位向量时,输出该位向量的前1位的位置。例如,给定输入8’b10010000的8位优先级编码器将输出3’d4,因为位[4]是第一个高的位。
构建一个4位优先编码器。对于这个问题,如果没有一个输入位是高的(即,输入为零),输出为零。注意,4位数字有16种可能的组合。
module top_module ( input [3:0] in, output reg [1:0] pos ); always @(*) begin case (in) 4'b0011, //3 4'b0101, //6 4'b0111, //7 4'b1001, //9 4'b1011, //11 4'b1101, //13 4'b1111, //15 4'b0001: //1 pos = 2'd0; // pos = 2'd1; 4'b0010, //2 4'b0110, //6 4'b1010, //10 4'b1110: //14 pos = 2'd1; 4'b1100, //12 4'b0100: //4 pos = 2'd2; 4'b1000: //8 pos = 2'd3; default: pos = 2'd0; endcase end endmodule
对于八位的case,将会有256种可能,但如果case语句支持忽略位,将能减少到九种。什么是casez:字节比对时,对于Z的位忽略:
always @(*) begin
casez (in[3:0])
4'bzzz1: out = 0; // in[3:1] can be anything
4'bzz1z: out = 1;
4'bz1zz: out = 2;
4'b1zzz: out = 3;
default: out = 0;
endcase
end
case语句时逐项循序检查,但存在一项对应多个输出,但第一项case项会有输出,后面项目不执行
module top_module ( input [7:0] in, output reg [2:0] pos ); always @(*) begin casez(in) 8'bzzzzzzz1: pos = 0; 8'bzzzzzz1z: pos = 1; 8'bzzzzz1zz: pos = 2; 8'bzzzz1zzz: pos = 3; 8'bzzz1zzzz: pos = 4; 8'bzz1zzzzz: pos = 5; 8'bz1zzzzzz: pos = 6; 8'b1zzzzzzz: pos = 7; default: pos = 0; endcase end endmodule
假设你正在为一款游戏构建一个处理PS/2键盘扫描代码的电路。给定接收到的扫描码的最后两个字节,您需要指出是否按了键盘上的一个箭头键。这涉及到一个相当简单的映射,可以用一个case语句(或if-elseif)实现,有四种情况。
Scancode[16:0] | arrow key |
---|---|
16’he06b | left arrow |
16’he072 | down arrow |
16’he074 | right arrow |
16’he075 | up arrow |
anything else | none |
你的电路有一个16位输入和四个输出。建立这个电路,识别这四个扫描码,并断言正确的输出。
为了避免创建锁存,必须在所有可能的条件下为所有输出赋值(参见always_if2)。仅仅有一个默认情况是不够的。必须为所有四种情况下的所有四种输出以及默认情况下的所有输出分配一个值。这可能涉及许多不必要的输入。一个简单的解决方法是在case语句之前给输出赋一个“默认值”:
always @(*) begin
up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
case (scancode)
... // Set to 1 as necessary.
endcase
end
这种代码风格确保输出在所有可能的情况下都被赋值(0),除非case语句覆盖了赋值。这也意味着默认的:case项变得不必需了。
module top_module ( input [15:0] scancode, output reg left, output reg down, output reg right, output reg up ); always @(*) begin left = 1'b0;right = 1'b0;up = 1'b0;down = 1'b0; case(scancode) 16'he06b: left = 1'b1; 16'he072: down = 1'b1; 16'he074: right = 1'b1; 16'he075: up = 1'b1; default: ;//can without case ouput satement endcase end endmodule
verilog也有和C语言类似的条件操作符:?
给定四个无符号数,求最小值。无符号数可以与标准的比较运算符(a < b)进行比较。使用条件运算符构造两路最小电路,然后再组合成四路最小电路。你可能需要一些线向量来得到中间结果。
module top_module (
input [7:0] a, b, c, d,
output [7:0] min);//
// assign intermediate_result1 = compare? true: false;
assign min = (((a<b)?a:b)<((c<d)?c:d))?((a<b)?a:b):((c<d)?c:d);
endmodule
你已经熟悉了两个值之间的位运算,例如,a & b或a ^ b。有时,你想创建一个宽的门来操作一个向量的所有位,比如(a[0] & a[1] & a[2] & a[3]…),如果向量很长,这就很乏味了。
归约运算符可以对向量的位进行与运算、或运算和异或运算,产生1位输出:
& a[3:0] // AND: a[3]&a[2]&a[1]&a[0]. Equivalent to (a[3:0] == 4'hf)
| b[3:0] // OR: b[3]|b[2]|b[1]|b[0]. Equivalent to (b[3:0] != 4'h0)
^ c[2:0] // XOR: c[2]^c[1]^c[0]
这些是只有一个操作数的一元操作符(类似于NOT操作符!和~)。你也可以反转这些输出来创建NAND, NOR和XNOR门,例如(~& d[7:0])。
奇偶校验经常被用来作为一种简单的检测错误的方法,当通过一个不完美的通道传输数据。创建一个电路,将计算一个8位字节的奇偶校验位(这将为字节添加第9位)。我们将使用“偶”奇偶校验,其中奇偶校验位就是所有8个数据位的异或
module top_module (
input [7:0] in,
output parity);
assign parity = ^ in;
endmodule
整一个有以下三种输出的,100种输入的组合逻辑:
归约操作符包括:归约与(&),归约与非(&),归约或(|),归约或非(|),归约异或(),归约同或(~)。归约操作符是单目操作符号,它对这个向量操作数逐位操作,最终产生一个 1bit 结果。逻辑操作符、按位操作符和归约操作符都使用相同的符号表示,因此有时候容易混淆。区分这些操作符的关键是分清操作数的数目,和计算结果的规则。
module top_module(
input [99:0] in,
output out_and,
output out_or,
output out_xor
);
assign out_and = & in;
assign out_or = | in;
assign out_xor = ^ in;
endmodule
给出一个100bit的向量,然后翻转以下(用for循环解决)
module top_module(
input [99:0] in,
output [99:0] out
);
always @(*)
begin
for(integer i = 0 ; i < 100 ; i = i + 1)
begin
out [i] = in [99 - i];
end
end
endmodule
搞一个输入信号向量中为1的计数器,就是在循环中带条件判断
module top_module( input [254:0] in, output [7:0] out ); always @(*) begin out = 0; for(integer i = 0 ; i < 255 ; i = i + 1) begin if(in[i] == 1'b1) begin out = out + in[i]; end else begin out = out + in[i]; end end end endmodule
通过实例化100个全加法器来创建一个100位二进制循环进位加法器。加法器将两个100位的数和一个进位数相加,得到一个100位的和并进位。为了鼓励您实际实例化全加法器,还输出循环进位加法器中每个全加法器的输出。Cout[99]是最后一个完整的加法器的最后一个执行量,也是你经常看到的执行量。
module top_module( input [99:0] a, b, input cin, output [99:0] cout, output [99:0] sum ); assign cout[0] = a[0] & b[0] | a[0] & cin | b[0] & cin; //luo ji dai shu shi zui jian assign sum[0] = a[0] ^ b[0] ^ cin; integer i; always @ (*) begin for (i=1; i<100; i = i + 1) begin cout[i] = a[i] & b[i] | a[i] & cout[i-1] | b[i] & cout[i-1]; sum[i] = a[i] ^ b[i] ^ cout[i-1]; end end endmodule
提供一个bcd_fadd 的一位BCD码加法器,做两个BCD码和进位的加法,最后输出总和以及进位:
module bcd_fadd (
input [3:0] a,
input [3:0] b,
input cin,
output cout,
output [3:0] sum );
实例化100个bcd_fadd,创建100位BCD循环进位加法器。你的加法器应该将两个100位的BCD数字被压缩成400位的向量)和一个进位相加,得到一个100位的和并进位。
module top_module( input [399:0] a, b, input cin, output cout, output [399:0] sum ); wire [399: 0] cout_t; bcd_fadd fadd(.a(a[3:0]),.b(b[3:0]),.cin(cin),.cout(cout_t[0]),.sum(sum[3:0])); //all cout_t assign to cout,but inimadtied first,so have 396 assign cout = cout_t[396]; //generate one time, Instantiate one generate genvar i; for(i = 4; i < 400; i=i+4) begin : add bcd_fadd fadd(.a(a[i+3:i]), .b(b[i+3:i]), .cin(cout_t[i-4]), .cout(cout_t[i]),.sum(sum[i+3:i])); end endgenerate endmodule
实现以下电路:
in ———— out
module top_module (
input in,
output out);
assign out = in; //combinational logic use blocking assignment
endmodule
实现以下电路:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tdbeq21Z-1651335732619)(https://hdlbits.01xz.net/mw/images/5/54/Exams_m2014q4i.png)]
module top_module (
output out);
assign out = 1'b0;
endmodule
实现下图电路:
就是实现一个或非门
module top_module (
input in1,
input in2,
output out);
assign out = !(in1 || in2); //output is 1bit
assign out = ~(in1 | in2);//output is same as input width
endmodule
注意逻辑运算符号与按位运算符号的区别
实现下图电路:
对输入信号in2取反后的与门
module top_module (
input in1,
input in2,
output out);
assign out = in1 && (!in2);
//assign out = in1 & (~in2);
endmodule
实现下图电路:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QLV7s8Hs-1651335732622)(https://hdlbits.01xz.net/mw/images/e/e6/Exams_m2014q4g.png)]
in1/2信号先同或,结果再和in3异或,^ 和 ~^,是互补运算,Verilog中只有按位的模式
module top_module (
input in1,
input in2,
input in3,
output out);
assign out = in3 ~^ (in1 ^ in2);
endmodule
试着同时构建几个逻辑门。建立一个有两个输入a和b的组合电路。
有7个输出,每个输出都有一个逻辑门驱动:
module top_module( input a, b, output out_and, output out_or, output out_xor, output out_nand, output out_nor, output out_xnor, output out_anotb ); assign out_and = a && b;//a & b; assign out_or = a || b;//a | b; assign out_xor = a ^ b; assign out_nand = !(a && b); //~(a & b); assign out_nor = !(a || b);//(~(a | b)); assign out_xnor = a ~^ b;//~(a ^ b); assign out_anotb = a && !b;//a & (~b); endmodule
7400系列集成电路是一系列数字芯片,每个芯片有几个门。7420是一个带有两个4输入NAND门的芯片。
创建一个功能与7420芯片相同的模块。它有8个输入和2个输出。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0AjK3a3a-1651335732622)(https://hdlbits.01xz.net/mw/images/4/48/7420.png)]
module top_module (
input p1a, p1b, p1c, p1d,
output p1y,
input p2a, p2b, p2c, p2d,
output p2y );
assign p1y = ~(p1a & p1b & p1c & p1d);
assign p2y = !(p2a & p2b & p2c & p2d);
endmodule
在前面的练习中,我们使用了简单的逻辑门和几个逻辑门的组合。这些电路是组合电路的例子。组合意味着电路的输出是其输入的函数(在数学意义上)。这意味着对于任何给定的输入值,只有一个可能的输出值。因此,描述组合函数行为的一种方法是显式列出输入的每个可能值的输出。这是真值表。
对于一个有N个输入的布尔函数,有2n 种可能的输入组合。真值表的每一行列出一个输入组合,所以总有2n行。输出列显示每个输入值的输出应该是什么。
row | inputs | outputs | ||
---|---|---|---|---|
number | x3 | x2 | x1 | f |
0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 1 | 0 |
2 | 0 | 1 | 0 | 1 |
3 | 0 | 1 | 1 | 1 |
4 | 1 | 0 | 0 | 0 |
5 | 1 | 0 | 1 | 2 |
6 | 1 | 1 | 0 | 0 |
7 | 1 | 1 | 1 | 1 |
上面的真值表适用于一个三输入一输出函数。对于8种可能的输入组合,每一种都有8行,还有一个输出列。有四种输出为1的输入组合,以及四种输出为0的输入组合。
根据真实表综合电路
假设我们想要构建上面的电路,但是我们被限制只能使用一组标准逻辑门。如何构建任意逻辑函数(表示为真值表)?
实现真值表功能的电路的一个简单方法是将真值表的功能表示为积和形式。乘积的和(意为或)意味着在真值表的每一行使用一个n输入与门(用来检测输入是否匹配每一行),然后是一个或门,它只选择那些输出为“1”的行。
对于上面的例子,如果输入匹配第2行或第3行或第5行或第7行,则输出为’1’(这是一个4输入or门)。如果x3=0, x2=1, x1=0,则输入匹配第2行(这是一个3输入and门)。因此,这个真值表可以通过使用4个或的与门来实现。(数字电路部分内容,不会的,单独补课吧的
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。