1.basics(基础)
5).或非门
nor的用法,不是out = nor(a,b), nor是一个定义好gate元件,通过例化使用.
![](https://img-blog.csdnimg.cn/img_convert/d2aa7beaa6dd441b874bc3c1d2679cc9.png)
6).同或门:
可写作xnor, 异或门为xor:
![](https://img-blog.csdnimg.cn/img_convert/9ab38f4b3b8f4bb1a240ab2fd6c1c205.png)
8).7458chip
![](https://img-blog.csdnimg.cn/img_convert/fb1bfcc466574d43b49c33d01d08b17f.png)
2.vectors(矢量)
1).vectors矢量
多位信号的assign表明位数:outv[2:0] = vec[2:0], 也可以是outv = vec.
![](https://img-blog.csdnimg.cn/img_convert/b9b38d49db5c4166888dc7f43b257297.png)
2)vectors in more detail 一些注意点
i). declaring vectors:
wire [0:7] a 和 wire [7:0] a的区别是前者最高位为a[0],后者为a[7],两种写法都可以,但同一个设计中最好用相同的形式, 后者常用.
![](https://img-blog.csdnimg.cn/img_convert/91984a3c570f4d61a1a965c8f0b8426e.png)
ii). 隐含的网络带来的错误:
未声明宽度的时候默认位宽为1,下图中a到b的连接截取了最低位,b到c的连接中位宽不匹配,则报错:`default_nettype none
![](https://img-blog.csdnimg.cn/img_convert/d74bfeb86eef42359026145d3fe4abc9.png)
iii). vector array 向量阵列.
下图为255个位宽为8bit的寄存器阵列和29个位宽为1bit的寄存器阵列
![](https://img-blog.csdnimg.cn/img_convert/dcfae829a62b42e380d9c79297f6f1f6.png)
4)bitwise operators:
& | ~ 为按位操作, && || !为逻辑操作:如3'b101:~结果是3'b010, !的结果是1'b0.
![](https://img-blog.csdnimg.cn/img_convert/fdbc33e17c3740b0ba4c0ff2019be5f0.png)
5)four input gates
![](https://img-blog.csdnimg.cn/img_convert/0b1a0cabe6094c9b93c73c1ead307752.png)
6)vector concatenation(串联) operator 连接操作, 示例如下:
![](https://img-blog.csdnimg.cn/img_convert/6f0375b8737b4da5bf34d460104ba230.png)
![](https://img-blog.csdnimg.cn/img_convert/1ff0c101354e41aebcc1e8d15ed8b828.png)
7)vector reversal 1 矢量逆序
![](https://img-blog.csdnimg.cn/img_convert/363bc3cbd6844dd0ae8c8b99e53305d8.png)
是否可以assign out[7:0] = in[0:7] ?? 答案是不可以: verilog不允许这样操作.
![](https://img-blog.csdnimg.cn/img_convert/ce13d4d4eef148daa005eed68634542d.png)
正解如下:(或者逐位拼接也可以, 但很麻烦). 注意:for循环仅在always和initial块中使用, 直接定义for循环会报错.
![](https://img-blog.csdnimg.cn/img_convert/74ca0d4870a04927883a2c9a65525c7d.png)
8)replication operator 复制操作
vector复制示例:
![](https://img-blog.csdnimg.cn/img_convert/5fa8226e82a542cc86c8a460fc02bc22.png)
常见的复制实例: 符号位扩展
sign extending:
4'b0101 --> 8'b0000_0101 (8'd5)
4'b1101 --> 8'b1111_1101 (-8'd3)
![](https://img-blog.csdnimg.cn/img_convert/6235e3a91abe4aeba97883c782339699.png)
9)more replication 其他复制的情况
注意:{5{a}, 5{b}, ... }这种拼接方式是错误的, 5个a连接应该表示为{5{a}}.
![](https://img-blog.csdnimg.cn/img_convert/2508c405eb984d549ae3a8b8c5ae9151.png)
解答如下:
![](https://img-blog.csdnimg.cn/img_convert/241f7eb5a2e54421b5ce9a33a69d4094.png)
3.modules: hierarchy(模块:层次化)
1)modules 例化一个模块
![](https://img-blog.csdnimg.cn/img_convert/81facb6c7f2b495598dc8d4827b0cb32.png)
2)例化模块(by pos)
![](https://img-blog.csdnimg.cn/img_convert/afcaed12cdd8480db7c1da5e1d5b24ff.png)
3)例化模块(by name)
![](https://img-blog.csdnimg.cn/img_convert/ad3c781663d74d3f84c416c672f8fcb3.png)
4)three module(3个D触发器模块例化连接)
![](https://img-blog.csdnimg.cn/img_convert/4a8c6d12e35d4311a8a8de9b197ca7d8.png)
5)modules and vectors
下面模块的作用是选择不同延迟周期数(从0到3个周期), dff用于延迟, 选择器用来选择.
![](https://img-blog.csdnimg.cn/img_convert/fa01b9642ec0488db369bc22704fa6f9.png)
解答:
module top_module (
input clk,
input [7:0] d,
input [1:0] sel,
output [7:0] q
);
//wire between 3 D-ff
wire d1_d2[7:0];
wire d2_d3[7:0];
//wire to selector, delay 0/1/2/3 cycle
wire [7:0] dly_0;
wire [7:0] dly_1;
wire [7:0] dly_2;
wire [7:0] dly_3;
assign dly_0 = d;
my_dff8 u1_my_diff8(
.clk(clk),
.d(d),
.q(dly_1)
);
my_dff8 u2_my_diff8(
.clk(clk),
.d(dly_1),
.q(dly_2)
);
my_dff8 u3_my_diff8(
.clk(clk),
.d(dly_2),
.q(dly_3)
);
always @(*)//一开始还加了{}, 都忘了用begin end了, 长点记性
begin
case(sel)// no " : "
2'b00: q = dly_0;
2'b01: q = dly_1;
2'b10: q = dly_2;
2'b11: q = dly_3;
default: q = 2'b0;
endcase
end
endmodule
6)adder1
用两个16bit 加法器做一个32bit加法器, 低位进位:0, 最高位进位不考虑
![](https://img-blog.csdnimg.cn/img_convert/57595481b3894d2fae974a9d9f2fb938.png)
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire carry; //1 bit
/*
一开始把[12:0]放在 add_out后面了,结果报错说例化的时候不能把array type赋值给非array type, 才发现的.. 长长记性
*/
wire [15:0] add_out1;
wire [15:0] add_out2;
add16 u1_add(.a(a[15:0]), .b(b[15:0]), .cin(1'b0), .sum(add_out1), .cout(carry));//cin = 0
add16 u2_add(.a(a[31:16]), .b(b[31:16]), .cin(carry), .sum(add_out2), .cout());// cout ignored
assign sum[31:0] = {add_out2, add_out1};
endmodule
7)adder2
这英文题目说啥没看懂, 看了别人的中文帖子懂了: 就是需要自己写一个1位的全加器模块, 上题说到的add16是这个add1的上层模块. add16不用自己写, 但可以在top_module中直接用(还是有些绕).算是上一题的补充.
module add1 ( input a, input b, input cin, output sum, output cout );
/*
//assign cout = (a^b)&cin + a&b
本来是用的上面这个逻辑表达式, 仿真不通过, 最后发现是或( | 写成了 + ), 不愧是我
*/
assign cout = a&b | a&cin | b&cin;
assign sum = a^b^cin;
endmodule
8)carry selected adder
因为纹波进位加法器(前面那种), 高位的运算需要等待低位的进位结果出来后才能开始进行. 在计算的位数过多时延迟会很大. 本题的进位选择加法器可以减少延迟. 3delay 减少到 1delay.
![](https://img-blog.csdnimg.cn/img_convert/0275bc35318041fabb458a3270d1ab44.png)
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire [15:0] sum1;
wire [15:0] sum2;
wire sel;
add16 u0_add(a[15:0], b[15:0], 1'b0, sum[15:0], sel);
add16 u1_add(a[31:16], b[31:16], 1'b0, sum1, );
add16 u2_add(a[31:16], b[31:16], 1'b1, sum2, );
assign sum[31:16] = sel ? sum2 : sum1;
endmodule
9 )adder-subtractor:加法减法器
![](https://img-blog.csdnimg.cn/img_convert/481fba39ec154463b6f1e92581fc387f.png)
通过一个输入的控制信号来决定是让两个数进行加法还是减法.
减法的原理是,把被减数b转换成补码形式, 即对b按位取反再+1. 所加的1在最低的cin处.
按位取反: 将b与1按位异或即可, 如10101与1按位异或得到01010
故要进行加法则sub = 0(不转补码), 进行减法则sub = 1(转补码)
module top_module(
input [31:0] a,
input [31:0] b,
input sub,
output [31:0] sum
);
wire [31:0] rvs_b;
wire carry;
assign rvs_b = b^{32{sub}};
add16 u1_add(a[15:0], rvs_b[15:0], sub, sum[15:0], carry);
add16 u2_add(a[31:16], rvs_b[31:16], carry, sum[31:16], );
endmodule
4.procedures(进程)
1)always块:组合逻辑
assign赋值的左侧必须是网络类型(如wire), 过程语句(always)中赋值的左侧必须是变量类型(如reg), 与综合得到的硬件无关, 仅为verilog中的语法规定.
练习:使用 assign 语句和组合逻辑的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 @(*) out_alwaysblock = a&b;
endmodule
2)always块:时序逻辑
带有时钟的always块可以创建组合逻辑块,同时也可以在输出端建立一套触发器(或寄存器). 输出的改变不是立即发生的, 要等到下一个时钟的上升沿.
连续赋值: assign x = y;
阻塞赋值: x = y; (组合逻辑always块中)
非阻塞赋值: x <= y; (时序逻辑always块中)
遵循以上规则很重要, 否则会在仿真/综合的时候引起不必要的麻烦.
还有一点:在sv中组合逻辑和时序逻辑块的表示方式为: always_comb和always_ff.
练习:使用 assign 语句、always块(组合逻辑、时序逻辑)三种方式构建异或门(xor gate)a
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 @(*) out_always_comb = a^b;
always @(posedge clk) out_always_ff <= a^b;//delay 1 cycle
endmodule
3)if语句
在always块中使用. if示例如下, 一个2输入数据选择器, 也给出了等效的assign语句:
![](https://img-blog.csdnimg.cn/img_convert/b3dd82f1b9f64e529be0e2f64e79a3e3.png)
练习: 用assign 和if 两种方式实现4输入的数据选择器, 其真值表如下:
![](https://img-blog.csdnimg.cn/img_convert/00806d2c850d4ae590f57338395e8ecb.png)
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&&sel_b2)begin
out_always = b;
end
else begin
out_always = a;
end
end
endmodule
4)if 语句latch
在进行if判断的时候要考虑到所有可能的情况,不然没未考虑到的情况出现时, 会导致输出不变, 这就是latch, 给出的示例如下:
![](https://img-blog.csdnimg.cn/img_convert/26693163de4746d3a4b3090d98a61b3a.png)
![](https://img-blog.csdnimg.cn/img_convert/f38693d501844fceb8f94cc8daeb6af4.png)
生成的电路存在latch(在指定的情况之外,output不变), 所以应该加上其他条件:
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)
shut_off_computer = 1;
else
shut_off_computer = 0; //if not overheated then working
end
always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
else
keep_driving = 0; //if arrived the stop
end
endmodule
5)case语句
将一条表达式与一堆其他的xx相比较的时候, case的功能与if-else if-else近乎相等.与c语言中的switch语句不同.
需要注意的一点是:用default避免latch
练习: 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'd0: out = data0;
3'd1: out = data1;
3'd2: out = data2;
3'd3: out = data3;
3'd4: out = data4;
3'd5: out = data5;
default: out = 3'd0;
endcase
end
endmodule
6)优先编码器
优先编码器是一个组合逻辑电路, 给定一个矢量输入, 输出为该矢量的第一个'1'出现的位置. 例如, 一个8位优先编码器, 给定输入为8'b1001_0000, 输出为8'd4, 因为4为第一个出现的'1'(这里应该是从0算起).
练习: 设计一个4位优先编码器, 如果没有输入bit为1, 则输出为0. 注意一个4bit数有16种可能的组合.
我的解决方法是casex.
casex: x和z出现的时候都不管它.
casez: z出现的时候不管它
module top_module (
input [3:0] in,
output reg [1:0] pos );
always @(*)begin
casex(in)
4'bxxx1: pos = 2'd0;
4'bxx10: pos = 2'd1;
4'bx100: pos = 2'd2;
4'b1000: pos = 2'd3;
default: pos = 2'd0;
endcase
end
endmodule
7)用casez的优先编码器
显式指定优先级, 这样不管case的顺序如何都能实现优先编码.
module top_module (
input [7:0] in,
output reg [2:0] pos );
always @(*)begin
casez(in)
8'bzzzz_zzz1: pos = 3'd0;
8'bzzzz_zz10: pos = 3'd1;
8'bzzzz_z100: pos = 3'd2; // try not use 4'bz1zz because it must be the 3rd case
8'bzzzz_1000: pos = 3'd3; // try not use 4'b1zzz because it must be the 4th case
8'bzzz1_0000: pos = 3'd4;
8'bzz10_0000: pos = 3'd5;
8'bz100_0000: pos = 3'd6;
8'b1000_0000: pos = 3'd7;
default: pos = 3'd0;
endcase
end
endmodule
8)avoiding latching
练习:根据下图实现键盘上下左右四个键位的映射
![](https://img-blog.csdnimg.cn/img_convert/977c731f00504a3384aaec094c66877b.png)
对于每个case项目, 不光要指定对应的键位为1, 还要指定其他键位为0, 以下是第一种方法.但这个方法其实不好, 如果键位特别多的话, 需要在每个case里面都给未映射到的键位赋值, 代码量很大.
module top_module (
input [15:0] scancode,
output reg left,
output reg down,
output reg right,
output reg up );
always @(*)begin
case(scancode)
16'he06b: begin left = 1'b1; right = 1'b0; up = 1'b0; down = 1'b0; end
16'he074: begin right = 1'b1; left = 1'b0; up = 1'b0; down = 1'b0; end
16'he072: begin down = 1'b1; left = 1'b0; right = 1'b0; up = 1'b0; end
16'he075: begin up = 1'b1; left = 1'b0; right = 1'b0; down = 1'b0; end
default: begin left = 1'b0; right = 1'b0; up = 1'b0; down = 1'b0; end
endcase
end
endmodule
于是可以在一开始就给所有键位赋默认值, 当其中某一个键位被映射到了后, 单独给其赋1. 有了如下的代码.
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'he074: right = 1'b1;
16'he072: down = 1'b1;
16'he075: up = 1'b1;
default: begin left = 1'b0; right = 1'b0; up = 1'b0; down = 1'b0; end
endcase
end
endmodule
5.more verilog features(更多特性)
1)conditional ternary operator: 三元运算符
练习: 给定4个无符号数, 找出最小值. 先用条件运算符分别比较两个数, 然后再将它们组合起来形成4路求最小值电路.(5行代码左右)
wire只能被assign连续赋值,reg只能在initial和always中赋值
module top_module (
input [7:0] a, b, c, d,
output [7:0] min);//
// assign intermediate_result1 = compare? true: false;
wire [7:0] min_2 [1:0];
assign min_2[1] = (a < b) ? a : b;
assign min_2[0] = (c < d) ? c : d;
assign min = (min_2[1] < min_2[0]) ? min_2[1] : min_2[0];
endmodule
2)reduction operators: 缩减运算符
有时候需要对一个长矢量所有的位进行操作, 比如a[0] & a[1] & a[2] & ... , 如果完全写出来会很麻烦. reduction操作可以对矢量的位进行and/or/xor操作, 最终得到一位的输出. 有如下示例:
![](https://img-blog.csdnimg.cn/img_convert/443abfc541b74f37b1ec97dfb22b12d9.png)
注: (a[3:0] == 4'hf)输出为1位, 即矢量a为1111时, 括号内为true, 输出为1. 相当于&a[3:0].
这些一元操作只有一个操作符(类似于not操作!和~). 还可以将输出进行反相来构建一个nand(与非)门, nor(或非门)和xnor(同或门): 比如~&d[7:0].
练习: 奇偶校验常用于在不完美信道上传输数据的错误检测, 建立一个可以计算8bit字节奇偶的电路. 使用偶校验(对所有位进行xor处理). 期望代码行数为1.
注:偶校验码指在数据发送前检查1的个数,一共有偶数个1则头部填充0,奇数个1则头部填充1,整体保持偶数个1,接收数据时,重新检查1的个数。数据所有位进行XOR处理, 若有偶数个1则XOR结果为0, 否则结果为1.
module top_module (
input [7:0] in,
output parity);
assign parity = ^in[7:0];
endmodule
3)reduction: even wider gates
搭建一个有100个输入的组合逻辑电路,in[99:0].实现and/or/xor.
module top_module(
input [99:0] in,
output out_and,
output out_or,
output out_xor
);
assign out_and = ∈
assign out_or = |in;
assign out_xor = ^in;
endmodule
4)combinational for-loop: vector reversal 2
练习:将一个100位的vector进行倒序处理.
方法为for循环, 先定义一个整数integer i. 不是genvar(只能在generate里面用)
module top_module(
input [99:0] in,
output [99:0] out
);
integer i;
always @(*)begin
for(i = 0; i < 100; i++)begin
out[i] = in[99-i];
end
end
endmodule
5)combinational for-loop: 255-bit population count
练习: 此电路对vector中的1的数量进行计数, 输入为255bit的vector.
module top_module(
input [254:0] in,
output [7:0] out );
integer i;
always @(*)begin
out = 8'b0;
for(i = 0; i < 255; i++)begin
out = in[i] ? (out+1'b1) : out;
end
end
endmodule
6)generate for-loop: 100-bit binary adder 2
练习:使用generate for 循环结构搭建一个100位的二进制加法器, 自己的解法如下:
module top_module(
input [99:0] a, b,
input cin,
output [99:0] cout,
output [99:0] sum );
fulladder fulader0(a[0], b[0], cin, sum[0], cout[0]);
genvar i;
generate
for(i = 1; i < 100; i = i + 1)
begin: fulladder
fulladder u_fulladder(a[i], b[i], cout[i-1], sum[i], cout[i]);
end
endgenerate
endmodule
module fulladder(
input a,
input b,
input cin,
output sum,
output cout);
assign sum = a^b^cin;
assign cout = ((a^b)&cin)|(a&b);
endmodule
generate for-loop: 100-digit CBD adder
练习:使用generate for-loop构建一个100位加法器, 已经提供一个一位的BCD加法器.
注:BCD的加法是以4bit位为一个BCD位, 实际输入400bit的数据相当于100位的BCD码, 关于BCD码的说明,见二进制转BCD码原理及verilog实现一文.
module top_module(
input [399:0] a, b,
input cin,
output cout,
output [399:0] sum );
wire [99:-1] carry;
assign carry[-1] = cin;
assign cout = carry[99];
genvar i;
generate
for(i = 0; i < 400; i=i+4)begin: bcd_fulladd100
bcd_fadd bcd_fadd100(
.a(a[i+3:i]),
.b(b[i+3:i]),
.cin(carry[i/4 - 1]),
.cout(carry[i/4]),
.sum(sum[i+3:i])
);
end
endgenerate
endmodule