逐位逻辑运算符:对于 N 比特输入向量之间的逻辑比较,会在 N 比特上逐位进行,并产生一个 N 比特长的运算结果。
逻辑运算符:任何类型的输入都会被视作布尔值,零->假,非零->真,将布尔值进行逻辑比较后,输出一个 1 比特的结果。
// 模块有两个 3bit 宽的输入变量 a,b ,要求输出 a,b 逐位或的,a,b 逻辑或以及 a,b 按位取反的结果,其中 b 在高位。
module top_module(
input [2:0] a,
input [2:0] b,
output [2:0] out_or_bitwise,
output out_or_logical,
output [5:0] out_not
);
assign out_or_bitwise = a | b; // 按位或
assign out_or_logical = a || b; // 逻辑或
assign out_not[2:0] = {~b, ~a};
endmodule
这项操作常见于不同的大小端体系之间的数据交换,比如 x86 体系使用小端模式存储数据,而因特网协议中均使用大端模式,数据在本地和网络进行数据交换之前,均要进行大小端转换。
// 方法一
// 将输入向量的字节顺序颠倒,也就是字节序大小端转换。
// aaaaaaaabbbbbbbbccccccccdddddddd => ddddddddccccccccbbbbbbbbaaaaaaaa
module top_module (
input [31:0] in,
output [31:0] out
);
assign out[31:24] = in[ 7: 0];
assign out[23:16] = in[15: 8];
assign out[15: 8] = in[23:16];
assign out[ 7: 0] = in[31:24];
endmodule
// 方法二
// 将输入向量的字节顺序颠倒,也就是字节序大小端转换。
// aaaaaaaabbbbbbbbccccccccdddddddd => ddddddddccccccccbbbbbbbbaaaaaaaa
module top_module (
input [31:0] in,
output [31:0] out
);
assign out = {in[7-:8],in[15-:8],in[23-:8],in[31-:8]};
//[7-,8]表示从倒数第7个开始取,往后取8个数,取到倒数第0个数
endmodule
// 分别构建一个 4 输入与门,或门以及异或门
module top_module(
input [3:0] in,
output out_and,
output out_or,
output out_xor
);
assign out_and = ∈ // 等同于 in[3] & in[2] & in[1] & in[0];
assign out_or = |in; // 等同于 in[3] | in[2] | in[1] | in[0];
assign out_xor = ^in; // 等同于 in[3] ^ in[2] ^ in[1] ^ in[0];
endmodule
for 循环可以理解为代码循环的语法,减少编码量,但真正的硬件电路不存在循环。
integer i;
always @(*) begin
for (i=0; i<8; i++)
out[i] = in[8-i-1];
end
generate 生成块可以例化 assign 语句,模块,信号和变量的声明以及 always initial 这样的过程块。下例中,generate 块在综合的过程中,综合了 8 句 assign 赋值语句。
// 给定一个 8bit 输入向量,将其反向输出。
module top_module(
input [7:0] in,
output [7:0] out
);
//assign out = {in[0],in[1],in[2],in[3],in[4],in[5],in[6],in[7]};
generate
genvar i; // generate模块变量
for(i=0;i<8;i++)begin : my_block_name
assign out[i]=in[7-i];
end
endgenerate
endmodule
正数(符号位为0),负数(符号位为1),扩展位宽,都是用符号位补齐左边空位。
重复操作符,复制N次
{5{1'b1}} // 5'b11111
{2{a,b,c}} // {a,b,c,a,b,c}
{3'd5, {2{3'd6}}} // 9'b101_110_110.
// 将一个 8bit 有符号数扩展为 32bit 数。
module top_module (
input [7:0] in,
output [31:0] out );//需要补齐31-7=24位,用符号位填充左边空位
assign out = {{24{in[7]}},in} ; // 注意不要写成{24{in[7]},in}
endmodule
// 使用位连接符和重复操作符,拼接
module top_module (
input a, b, c, d, e,
output [24:0] out );//
assign out = ~{{5{a}}, {5{b}}, {5{c}}, {5{d}}, {5{e}}} ^ {{5{a,b,c,d,e}}};
endmodule
如图,将 mod_a 镶嵌进 top_module,即为例化。例化是模块间信号连接的方式,有同名例化,异名例化。
// 同名例化 根据 mod_a 本来的的定义,将例化端口列表,顺序依次连接 mod_a 端口列表
// eg mod_a 本来的的定义: mod_a(out,in1,in2); 而实例化中定义为:mod_a mod_a_instance1(wa,wb,wc),则 wa->out, wb->in1, wc->in2
mod_a mod_a_instance1(
wa,
wb,
wc
);
// 异名例化 根据端口名称指定外部信号的连接
mod_a mod_a_instance2(
.out(wc),
.in1(wa),
.in2(wb)
);
// 异名例化
module top_module ( input a, input b, output out );
mod_a mod_a(
.in1(a),
.in2(b),
.out(out)
);
endmodule
// 同名例化
// module mod_a ( output, output, input, input, input, input );
// mod_a 未给出output,input端口名(output1 output2 input1 input2...)无法区分,只能用同名例化,例化时按顺序连接top_module的输入/输出
module top_module (
input a,
input b,
input c,
input d,
output out1,
output out2
);
mod_a mod_a(out1,out2, a, b, c, d);
endmodule
多路复用器。 一种实现方法是在一个always块内使用case语句。
1、always语句有两种触发方式。第一种是电平触发,例如always @(a or b or c),a、b、c均为变量,当其中一个发生变化时,下方的语句将被执行。
2、第二种是沿触发,例如always @(posedge clk or negedge rstn),即当时钟处在上升沿或下降沿时,语句被执行。
3、而对于always@(*),意思是以上两种触发方式都包含在内,任意一种发生变化都会触发该语句。
// my_dff8 ( input clk, input [7:0] d, output [7:0] q );
module top_module (
input clk,
input [7:0] d,
input [1:0] sel,
output [7:0] q
);
wire[7:0] q1,q2,q3;
my_dff8 d1(
.clk(clk),
.d(d),
.q(q1)
);
my_dff8 d2(
.clk(clk),
.d(q1),
.q(q2)
);
my_dff8 d3(
.clk(clk),
.d(q2),
.q(q3)
);
always@(*) begin // always模块中的任何一个输入信号或电平发生变化时,该语句下方的模块将被执行。
case(sel)
0: begin q=d; end
1: begin q=q1; end
2: begin q=q2; end
3: begin q=q3; end
endcase
end
endmodule
二选一 always case 和 assign 写法
always@(*)
case(cout)
0: begin sum[31:16] = s1; end
1: begin sum[31:16] = s2; end
endcase
assign sum[31:16] = carry?sum1:sum0;
a+b,a-b,b:正数变负数(所有位按位取反再加1)
当sub为1时,执行减法,使用32位的异或门对B进行取反。(这也可以被视为b[31:0]与sub复制32次相异或),a+~b+1
当sub为0时,执行加法,(与0异或是原码) a+b
module top_module(
input [31:0] a,
input [31:0] b,
input sub,
output [31:0] sum
);
wire carry;
wire[31:0] b1;
assign b1=32{sub}^b; // 减去一个数等于加上这个数的补码(就是题中的按位取反再加1)。按位取反:32{sub}={1111,...,1}32位
add16 a1(.a(a[15:0]), .b(b1[15:0]), .cin(sub), .sum(sum[15:0]), .cout(carry) );
add16 a2(.a(a[31:16]), .b(b1[31:16]), .cin(carry), .sum(sum[31:16]) );
endmodule
被always块敏感变量影响的值,提前定义为reg型
用assign直接赋值得的结果用wire型
// 使用assign语句和组合always块来构建与门
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
两种 always 块
组合逻辑:always @(*) // 组合always模块中的任何一个输入信号或电平发生变化时,该语句下方的模块将被执行。
时序逻辑:always @(posedge clk) // 时序always块也会像组合always块一样生成一系列的组合电路,但同时在组合逻辑的输出生成了一组触发器(或寄存器)。该输出在下一个时钟上升沿(posedge clk)后可见,而不是之前的立即可见。
3种赋值
连续赋值(assign x=y;):不能在过程块内使用;
过程 阻塞性赋值(x=y;):只能在过程块中使用; // always @(*) 使用
过程 非阻塞性赋值(x<=y):只能在过程块内使用。 // always @(posedge clk) 使用
组合 always 用阻塞性x=y,结果同步输入显示
时序 always @(posedge clk) 用非阻塞性x<=y,结果在下一个时钟上升沿显示
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; // 结果在下一个时钟上升沿显示
endmodule
condition ? if_true : if_false
2选1
// 如果sel_b1和sel_b2都为真,输出b,其他情况输出a
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
Examples:
(0 ? 3 : 5) // This is 5 because the condition is false.
(sel ? b : a) // A 2-to-1 multiplexer between a and b selected by sel.
always @(posedge clk) // A T-flip-flop.
q <= toggle ? ~q : q;
连续赋值
always @(*) // State transition logic for a one-input FSM
case (state)
A: next = w ? B : A; // w=1:next=B; w=0:next=A
B: next = w ? A : B;
endcase
assign out = ena ? q : 1'bz; // A tri-state buffer
// (sel[1:0] == 2'h0) , 选 a
// (sel[1:0] != 2'h0) , 选 (sel[1:0] == 2'h1) ? b : c )
// (sel[1:0] == 2'h1) , 选 b
// (sel[1:0] != 2'h1) , 选 c
((sel[1:0] == 2'h0) ? a : // A 3-to-1 mux 3选1选择器
(sel[1:0] == 2'h1) ? b :
c )
case 项是按顺序检查的 (实际上,它更像是生成一个巨大的真值表然后生成超大的门)。注意有输入(例如,4’b0011)匹配多个case项。选择第一个匹配(因此4’b0011匹配第一个case项,out = 0)。
符号"?" 是z的同义词,所以2’bz0与2’b?0相同。
// 构建一个4输入的优先编码器。给定一个4位向量,输出输入向量中左数第一个1的位置。如果输入均为0,则输出零。例如,输入4'b1000应该输出2'd3,因为位[3]是第一个出现1的位置。
always @(*) begin
casez (in[3:0])
4'bzzz1: out = 0; // 不管[3][2][1]位,只看[0]位
4'bzz1z: out = 1; // 不管[3][2][0]位,只看[1]位
4'bz1zz: out = 2;
4'b1zzz: out = 3;
default: out = 0;
endcase
end
case语句只改变目标赋值,其他只按默认值不变,不用再写default
// 识别这四个按键的扫描码并输出
// 按键输出为1,没按键输出为0
module top_module (
input [15:0] scancode,
output reg left,
output reg down,
output reg right,
output reg up );
always@(*) begin
up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0; // 赋值,输出默认为0
case(scancode)
16'he06b: begin left=1'b1; end // 更改left赋值,down、right、up保持默认值
16'he072: begin down=1'b1; end
16'he074: begin right=1'b1; end
16'he075: begin up=1'b1; end
endcase
end
endmodule
& a[3:0] // AND: a[3]&a[2]&a[1]&a[0].相当于 (a[3:0] == 4'hf)
| b[3:0] // OR: b[3]|b[2]|b[1]|b[0].相当于 (b[3:0] != 4'h0)
^ c[2:0] // XOR: c[2]^c[1]^c[0]
// 奇偶校验,检验传输数据中1的个数,
// 通过检验位将传输1的个数变成奇数就是奇校验,变成偶数就是偶校验
/*
8'b01100100 //原数据
9'b01100100_0 //奇校验 (将传输1的个数变成奇数就是奇校验)
9'b01100100_1 //偶校验 (将传输1的个数变成偶数就是偶校验)
*/
// 偶校验:异或
// 奇校验:同或(异或取反)
module top_module (
input [7:0] in,
output parity);
assign parity = ^in[7:0];
endmodule
// 将输入反向输出
module top_module(
input [99:0] in,
output [99:0] out
);
always@(*)begin
for(int i=0; i<100; i++)begin
out[i] = in[99-i];
end
end
endmodule
for-loop 和 generate-loop 循环的缺点:浪费资源。
在RTL级编码中极少使用for循环,这是因为for循环会被综合器展开为所有变量情况的执行语句,每个变量独立占用寄存器资源,不能有效的复用硬件逻辑资源,造成巨大的浪费,一般常用case语句代替。
system verilog for循环
//100个1bit的全加器来实现100bit的带进位的加法器
module top_module(
input [99:0] a, b,
input cin,
output [99:0] cout,
output [99:0] sum );
always@(*)begin
{cout[0],sum[0]} = a[0] + b[0] + cin;
for(int i=1;i<100;i++)begin
{cout[i],sum[i]} = a[i] + b[i] + cout[i-1];
end
end
endmodule
verilog for循环
//100个1bit的全加器来实现100bit的带进位的加法器
module top_module(
input [99:0] a, b,
input cin,
output [99:0] cout,
output [99:0] sum );
integer i;
always@(*)begin
{cout[0],sum[0]} = a[0] + b[0] + cin;
for(i=1;i<100;i++)begin
{cout[i],sum[i]} = a[i] + b[i] + cout[i-1];
end
end
endmodule
verilog for循环
//100个1bit的全加器来实现100bit的带进位的加法器
module top_module(
input [99:0] a, b,
input cin,
output [99:0] cout,
output [99:0] sum );
integer i;
always@(*)begin
{cout[0],sum[0]} = a[0] + b[0] + cin;
for(i=1;i<100;i++)begin
{cout[i],sum[i]} = a[i] + b[i] + cout[i-1];
end
end
endmodule
generate-loop 循环
// 例化bcd加法器计算400bit输入的加法
// 实例化100个bcd_fadd来实现100位的BCD进位加法器。该加法器应包含两个100bit的BCD码(包含在400bit的矢量中)和一个cin, 输出产生sum 和 cout。
module top_module(
input [399:0] a, b,
input cin,
output cout,
output [399:0] sum );
wire [399:0] cout_temp; // 声明一个wire型的cout_temp来存放每次计算后cout的值。
// 先例化一个bcd加法器计算cout[0]
bcd_fadd bit_fadd(
.a(a[3:0]),
.b(b[3:0]),
.cin(cin),
.cout(cout_temp[0]),
.sum(sum[3:0])
);
// generate 块
genvar i;
generate
for(i=4; i<400; i=i+4)begin:bcd // begin:name 自定义名
// 例化bcd加法器
bcd_fadd inst_bcd_fadd(
.a(a[i+3:i]),
.b(b[i+3:i]),
.cin(cout_temp[i-4]), //上次计算输出的cout
.cout(cout_temp[i]), //本次计算输出的cout,在下次计算中变为cin
.sum(sum[i+3:i])
);
end
endgenerate
assign cout = cout_temp[396];
endmodule
接 0
module top_module (
output out);
assign out = 1'b0;
endmodule
assign out_and = a&b;
assign out_or = a|b;
assign out_xor = a^b; //异或
assign out_xnor = ~(a^b); //同或
assign out_nand = ~(a&b); //与非
assign out_nor = ~(a|b); //或非
assign out_anotb = a & (~b); // anotb: and ~b
最小项之和的方法来构建电路图,最小项表达式为真值表中每一个对应函数值为1的输入变量,将上图真值表中函数值为1的最小项取出相加,便是函数最小项表达式。
F = X 3 ‾ X 2 X 1 ‾ + X 3 ‾ X 2 X 1 + X 3 X 2 ‾ X 1 + X 3 X 2 X 1 F=\overline{X~3~}X~2~\overline{X~1~} + \overline{X~3~}X~2~X~1~ +X~3~\overline{X~2~}X~1~ +X~3~X~2~X~1~ F=X 3 X 2 X 1 +X 3 X 2 X 1 +X 3 X 2 X 1 +X 3 X 2 X 1
= X 3 ‾ ( X 2 X 1 ‾ + X 2 X 1 ) + X 3 ( X 2 ‾ X 1 + X 2 X 1 ) =\overline{X~3~}(X~2~\overline{X~1~} + X~2~X~1~ ) +X~3~(\overline{X~2~}X~1~ +X~2~X~1) =X 3 (X 2 X 1 +X 2 X 1 )+X 3 (X 2 X 1 +X 2 X 1)
= X 3 ‾ X 2 + X 3 X 1 =\overline{X~3~}X~2~ +X~3~X~1~ =X 3 X 2 +X 3 X 1
module top_module(
input x3,
input x2,
input x1, // three inputs
output f // one output
);
assign f = ~x3&x2 | x3&x1; // +是|或
endmodule
真值表
x | y | z |
---|---|---|
0 | 0 | 1 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
最小项之和的方法来构建电路图,最小项表达式为真值表中每一个对应函数值为1的输入变量,将上图真值表中函数值为1的最小项取出相加,便是函数最小项表达式。
由真值表,z = x ‾ \overline{x} x y ‾ \overline{y} y + xy
即同或 z = ~(x^y);
verilog片选不支持 in[sel* 4+3 : sel* 4]这种语法。如果把向量的位选取写成 vect[msb:lsb] 这种形式,下标 msb 和 lsb 中是不能出现变量的
// 实现一个 256 选 1 选择器,sel 信号作为选择信号,当 sel = 0 时选择 in[3:0],sel = 1 时选择 in[7:4],以此类推
module top_module(
input [1023:0] in,
input [7:0] sel,
output [3:0] out );
// verilog片选不支持 in[sel*4+3 : sel*4]这种语法。如果把向量的位选取写成 vect[msb:lsb] 这种形式,下标 msb 和 lsb 中是不能出现变量的
assign out = in[sel*4 +: 4]; // 从 sel*4 开始,选择比特序号大于sel*4 的 4 位比特,相当于[sel*4+3:sel*4]
// 等同于 assign out = in[sel*4+3 -: 4]; // 从 sel*4+3 开始,选择比特序号小于 sel*4+3 的 4 位比特,相当于[sel*4+3:sel*4]
endmodule
半加器
module top_module(
input a, b,
output cout, sum );
//assign cout = a&b ;
//assign sum = a^b;
assign {cout,sum} = a+b;
endmodule
全加器
// 1bit
module top_module(
input a, b, cin,
output cout, sum );
assign {cout, sum} = a+b+cin;
endmodule
// 100bit
module top_module(
input [99:0] a, b,
input cin,
output cout, // 最后的进位
output [99:0] sum );
assign {cout,sum} = a + b + cin;
endmodule
// 通过实例化 3 个全加器,并将它们级联起来实现一个位宽为 3 bit 的二进制加法器,加法器将输入的两个 3bit 数相加,产生相加之和以及进位。
// 全加器
module adder(
input a, b, cin,
output cout, sum );
assign{cout,sum} = a + b + cin;
endmodule
module top_module(
input [2:0] a, b,
input cin,
output [2:0] cout,
output [2:0] sum );
// 开始例化全加器
adder U1(
.a(a[0]),
.b(b[0]),
.cin(cin),
.cout(cout[0]),
.sum(sum[0])
);
adder U2(
.a(a[1]),
.b(b[1]),
.cin(cout[0]), //上一个全加器的进位输出
.cout(cout[1]),
.sum(sum[1])
);
adder U3(
.a(a[2]),
.b(b[2]),
.cin(cout[1]), //上一个全加器的进位输出
.cout(cout[2]),
.sum(sum[2])
);
endmodule
module top_module (
input [3:0] x,
input [3:0] y,
output [4:0] sum);
assign sum = x+y; // 不能使用位连接符 {x+y},那么结果就会被限制为 4 bit 数,进位被舍去
endmodule
加法器
判断溢出(判断数值部分是否向符号位进位,有则是溢出):
例一:
负数补码相加: 85h + 9ch = 10000101b(符号位1,数值位0000101) + 10011100b(符号位1,数值位001_1100)
两补码相加,数值部分相加得001_1100, 不会向符号位进位,但真的就没有溢出吗?
再看两个数的原码之和:
10000101b的原码 = 11111011b(-123)
10011100b的原码 = 11100100b(-100)
原码之和的数值部分将向符号位进位,溢出
例二:
负数补码相加: e7h + b3h = 11100111b + 10110011b
两补码相加,数值部分会向符号位进位,是溢出吗?
再看两个数的原码之和:
11100111b的原码 = 10011001b(-25)
10110011b的原码 = 11001101b(-77)
原码之和没有向符号位进位,即没有发生溢出
于是,补码的溢出判断规则:
同号数相加如果结果的符号位和两加数不同,就会溢出。
即:
⑴不是同号数相加,则不可能溢出;
⑵同号数相加有可能溢出;
⑶同号数相加如果结果的符号位和两加数不同,就会溢出
// 实现一个 2 进制 8bit 有符号数加法器,加法器将输入的两个 8bit数补码相加,产生相加之和以及进位
// 正数+正数(两数同号相加,符号位和两加数不同:001),可能溢出
// 负数+负数(两数同号相加,符号位和两加数不同:110),可能溢出
module top_module (
input [7:0] a,
input [7:0] b,
output [7:0] s,
output overflow
); //
assign s = a + b;
assign overflow = (a[7] & b[7] & ~s[7] ) | (~a[7] & ~b[7] & s[7]); // 判断补码溢出
endmodule
电路中 F = A+B+C 表示为 F = A | B | C
// 化简卡诺图,尝试最大项之积和最小项之和的形式来完成电路设计。
module top_module(
input a,
input b,
input c,
input d,
output out );
assign out = ~b&~c | ~a&~d | b&c&d | a&c&d;
endmodule
其中D为don’t care值。相当于X。可以与1圈一起,但不能圈中只含X(单独圈)。
module top_module (
input [4:1] x,
output f );
assign f = ~x[1]&x[3] | x[2]&x[4];
endmodule
// 8位 D触发器。
module top_module (
input clk,
input [7:0] d,
output [7:0] q
);
always@(posedge clk) begin // 同步复位系统,等到下一个时钟上升沿才会得到响应,
q<=d;
end
endmodule
0x34,即8’h34 0x开始的数据表示16进制
同步、异步复位
always@(posedge clk) // 同步复位系统,等到下一个时钟上升沿才会得到响应 // synchronous reset
always@(posedge clk or negedge res) // 异步复位系统,~res就复位,不用等clk // asynchronous reset(AR)
同 D触发器相比,这个元件没有 clk 端口,取而代之的是 ena 端口,所以这是一个锁存器。锁存器的特征在于,相较于 D触发器的触发事件发生于 clk 时钟的边沿,锁存器锁存的触发事件发生于使能端 ena 的电平。
// D锁存器
module top_module (
input d,
input ena,
output q);
always@(*)begin
if(ena)begin
q<=d;
end
end
endmodule
// 图中的三角形代表时钟,不再用 CLK 标出。
// AR 表示 asynchronous reset 异步复位 always@(posedge clk or ___)
module top_module (
input clk,
input d,
input ar, // asynchronous reset 异步复位
output q);
always@(posedge clk or posedge ar)begin // 异步复位
if(ar)begin
q<=0;
end
else begin
q<=d;
end
end
endmodule
// R 表示 synchronous reset 同步复位 always@(posedge clk)
module top_module (
input clk,
input d,
input r, // synchronous reset 同步复位 R
output q);
always@(posedge clk)begin // 同步复位
if(r)begin
q<=0;
end
else begin
q<=d;
end
end
endmodule
// 如下图所示的状态机,假设D触发器在状态机启动之前初始化为0,实现该电路
module top_module (
input clk,
input x,
output z
);
// always块中因变量 提前定义为reg
reg q1=0; //题目假设D触发器在状态机启动之前初始化为0,
reg q2=0;
reg q3=0;
always@(posedge clk)begin
q1 <= q1^x;
q2 <= ~q2&x;
q3 <= ~q3|x;
end
assign z = ~(q1|q2|q3);
endmodule
// JK触发器的真值表如下图所示,仅使用D触发器和门电路来实现该JK触发器。其中Qold是D触发器在时钟上升沿之前的输出。
module top_module (
input clk,
input j,
input k,
output Q);
always@(posedge clk)begin
case({j,k})
2'b00: begin Q <= Q; end
2'b01: begin Q <= 1'b0; end
2'b10: begin Q <= 1'b1; end
2'b11: begin Q <= ~Q; end
endcase
end
endmodule
边沿检测的特性就是两边电平发生了变化,检测0变1上升沿,1变0下降沿。与上一个clk的输入不同,即为变化了(上升或下降)
// 检测上升沿(检测01)
module top_module (
input clk,
input [7:0] in,
output [7:0] pedge
);
reg[7:0] temp; // always块中的因变量 reg
always@(posedge clk)begin
temp <= in; // 记录上一个clk的输入
pedge <= ~temp∈
end
endmodule
//检测上升沿和下降沿(检测变化,即检测01,10)
module top_module (
input clk,
input [7:0] in,
output [7:0] anyedge
);
reg[7:0] temp; // always块中的因变量 reg
always@(posedge clk)begin
temp <= in; // 记录上一个clk的输入
anyedge <= temp^in;
end
endmodule
// 检测输入信号的每一位的下降沿(10),并保持输出直到复位
module top_module (
input clk,
input reset,
input [31:0] in,
output [31:0] out
);
reg[31:0] in_temp, out_temp;
always@(posedge clk)begin // 检测10 下降沿
in_temp <= in;
end
assign out_temp = in_temp & ~in; // 10 检测完下降沿又清零(只冒尖)
always@(posedge clk)begin // 确定输出
if(reset)begin
out<=0;
end
else begin
for(int i=0;i<32;i++)begin
if(out_temp[i])begin
out[i] <= 1;
end
else begin
out[i] <= out[i]; // 保持输出不变,该时刻out[i](右边),在下一时刻赋值给下一时刻的out[i](左),即式子左右的out[i]中的i,其实是不一样的
end
end
end
end
endmodule
// 双边沿检测的触发器 延时半个时钟周期
// 分别使用上下时钟边沿触发器(没有posedge clk or negedge clk写法),记录上下时钟边沿时输入的情况,并按照上下时钟边沿,分别输出上下时钟边沿记录的输入信号
module top_module (
input clk,
input d,
output q
);
reg q1,q2;
always@(posedge clk)begin // 上边沿
q1 <= d;
end
always@(negedge clk)begin // 下边沿
q2 <= d;
end
assign q = clk?q1:q2;
endmodule
// 利用bdc模10计数器将1000HZ提取1H。
// a,b,c都是模10的计数器,a的输入时钟是1000Hz,每当a计到10的时候,给b一个使能,相,b是a的十分之一,故b的时钟是100Hz。同理c是b的十分之一为10Hz。级联计数1000
module top_module (
input clk,
input reset,
output OneHertz,
output [2:0] c_enable
); //
wire[3:0] q0,q1,q2; // 每个BCD码计数器都从0计数到9
bcdcount counter0 (
.clk(clk),
.reset(reset),
.enable(c_enable[0]), // a一直计数,一直处于使能状态, c_enable[0] = 1'b1
.Q(q0) // 每次q0 == 4'd9,a给b一个使能信号,然后q0 != 4'd9,b暂停工作
);
bcdcount counter1 (
.clk(clk),
.reset(reset),
.enable(c_enable[1]), // a计到10的时候,q0 == 4'd9,则b处于使能,计数一次
.Q(q1)
);
bcdcount counter2 (
.clk(clk),
.reset(reset),
.enable(c_enable[2]), // b计到10的时候(q1 == 4'd9),且b处于使能(q0 == 4'd9),才能给c一个使能信号
.Q(q2) // c计到10,且c处于使能(c_enable[2]=1),才能output,
);
assign c_enable = {q1 == 4'd9 && q0 == 4'd9, q0 == 4'd9, 1'b1}; // 逻辑与&&连接式子
// assign c_enable = {(q1 == 4'd9) & (q0 == 4'd9), q0 == 4'd9, 1'b1}; //按位与&判断最近左右
assign OneHertz = {q2 == 4'd9 && q1 == 4'd9 && q0 == 4'd9};
endmodule
// 设计一个4位BCD(二进制编码十进制)计数器
// (千位,百位,十位,个位), 每一位都是一个bcd模10计数器
module count(
input clk,
input reset,
input ena,
output reg[3:0] q // 这种端口列表写法,如果后面用到always,且影响到q, 需要直接定义output reg[3:0] q
);
always@(posedge clk)begin
if(reset)begin
q<=0;
end
else if(ena)begin
if(q==4'd9)begin
q<=0;
end
else begin
q<=q+1;
end
end
end
endmodule
module top_module (
input clk,
input reset, // Synchronous active-high reset
output [3:1] ena,
output [15:0] q);
// 每个位都是bcd码计数,都从0计数到9
//个位
count Inst1_count
(
.clk(clk),
.reset(reset),
.ena(1'b1),
.q(q[3:0])
);
//十位
count Inst2_count
(
.clk(clk),
.reset(reset),
.ena(ena[1]), // 个位计数10(q[3:0]==4'd9),进位
.q(q[7:4])
);
//百位
count Inst3_count
(
.clk(clk),
.reset(reset),
.ena(ena[2]), // 个位计数10,十位计数器才有使能,此时当十位计数器计数10(q[3:0]==4'd9 && q[7:4]==4'd9),才能进位
.q(q[11:8])
);
//千位
count Inst4_count
(
.clk(clk),
.reset(reset),
.ena(ena[3]), // 十/百位计数器有使能,且百位计数10(q[3:0]==4'd9 && q[7:4]==4'd9 && q[11:8]==0)
.q(q[15:12])
);
assign ena = {q[3:0]==4'd9 && q[7:4]==4'd9 && q[11:8]==4'd9, q[3:0]==4'd9 && q[7:4]==4'd9, q[3:0]==4'd9};
endmodule
// 用计数器设计一个带am/pm的12小时时钟。该计数器通过一个CLK进行计时,用ena使能信号来驱动时钟的递增。
// 11:59:59 AM to 12:00:00 PM
module counter_60 ( // 0-59
input clk,
input reset,
input en,
output reg[7:0] cout_out
);
always @(posedge clk) begin
if(reset) // 复位
cout_out <= 0;
else if(en) begin // 使能
if (cout_out == 8'h59) // 高位计数器5,低位计数器9 (8'h:4bit一位)
cout_out <= 0;
else begin
if(cout_out[3:0] == 9) begin // 低位bcd计数器计数9
cout_out[3:0] <= 0; // 低位bcd计数器计数复位
cout_out[7:4] <= cout_out[7:4] + 1; // 高位bcd计数器+1
end
else
cout_out[3:0] <= cout_out[3:0] + 1; // 低位bcd计数器+1
end
end
end
endmodule
module counter_12 ( // 1-12
input clk,
input en,
input reset,
output reg[7:0] cout_out
);
always @(posedge clk) begin
if(reset) // 复位
cout_out <= 8'h12;
else if(en) begin // 使能
if (cout_out == 8'h12) // 12时复位
cout_out <= 1;
else begin
if(cout_out[3:0] == 9) begin // 低位bcd计数器计数9
cout_out[3:0] <= 0; // 低位bcd计数器清零
cout_out[7:4] <= cout_out[7:4] + 1; // 高位bcd计数器计数+1
end
else
cout_out[3:0] <= cout_out[3:0] + 1; // 低位bcd计数器计数+1
end
end
end
endmodule
module top_module(
input clk,
input reset,
input ena,
output pm,
output [7:0] hh,
output [7:0] mm,
output [7:0] ss);
// 例化
counter_60 counter1( // ss
.clk(clk),
.reset(reset),
.en(ena),
.cout_out(ss)
);
counter_60 counter2( // mm
.clk(clk),
.reset(reset),
.en(ena&(ss == 8'h59)), //秒计数器counter1也必须在使能状态,且秒计数到59,才能给分计数使能信号
.cout_out(mm)
);
counter_12 counter3( // hh
.clk(clk),
.reset(reset),
.en(ena&(ss == 8'h59)&(mm == 8'h59)), //分计数使能(ena&(ss == 8'h59)),且分计数器到59,才能给小时计数使能信号
.cout_out(hh)
);
always@(posedge clk) begin
if(reset) pm <= 0;
else if(hh == 8'h11 && ss == 8'h59&& mm == 8'h59) pm <= ~pm; // 11:59:59之前 am,之后pm
end
endmodule
// 设计一个4bit异步复位,拥有同步置位和使能的右移移位寄存器。
// 如果ena和load同时为高,load有更高的优先级。
module top_module(
input clk,
input areset, // async active-high reset to zero
input load,
input ena,
input [3:0] data,
output reg [3:0] q);
always@(posedge clk or posedge areset)begin
if(areset)begin
q<=4'b0;
end
else if(load)begin
q<=data;
end
else if(ena)begin
q = {1'b0,q[3:1]}; // 原来的q[3:1]在下个时刻移到q[2:0](右移)
end
end
endmodule
算术移位和逻辑移位
// 设计一个64-bit带同步置位的算术移位寄存器。该寄存器可以由amount控制来移动方向和每次移动的次数。
// 一个5-bit值为11000的寄存器算术右移一位后为11100, 而逻辑右移后为01100。一个5-bit值为01000的寄存器算术右移一位后为00100,且该寄存器逻辑右移会产生同样的结果。逻辑移位寄存器和算术左移移位寄存器没有区别。
module top_module(
input clk,
input load,
input ena,
input [1:0] amount,
input [63:0] data,
output reg [63:0] q);
always@(posedge clk)begin
if(load)begin
q<=data;
end
else if(ena)begin
case(amount)
2'b00: q <= {q[62:0], 1'b0};
2'b01: q <= {q[55:0], 8'b0};
2'b10: q <= {q[63], q[63:1]};
2'b11: q <= {{8{q[63]}}, q[63:8]}; // [7-,8]表示从倒数第7个开始取,往后取8个数,取到倒数第0个数
endcase
end
end
endmodule
线性反馈移位寄存器(LFSR)
// 线性反馈移位寄存器(LFSR)是通常带有几个XOR门来产生下一状态的移位寄存器
module top_module(
input clk,
input reset, // Active-high synchronous reset to 5'h1
output [4:0] q
);
always@(posedge clk)begin
if(reset)begin
q<=1;
end
else begin
q[4] <= 1'b0 ^ q[0];
q[3] <= q[4];
q[2] <= q[3] ^ q[0];
q[1] <= q[2];
q[0] <= q[1];
end
end
endmodule
// 32bit LFSR,
// 抽头位置i,对应移位寄存器输出q[i-1] <= q[i]^q[0]
module top_module(
input clk,
input reset, // Active-high synchronous reset to 32'h1
output [31:0] q
);
always@(posedge clk)begin
if(reset)begin
q <= 32'h1;
end
else begin
q <= {1'b0 ^ q[0], q[31:23], q[22]^q[0], q[21:3], q[2]^q[0], q[1]^q[0]};
//抽头位置32,22,2,1. q[21] <= q[22]^q[0], q[1] <= q[2]^q[0],q[0] <= q[1]^q[0]
end
end
endmodule
// n bit 移位寄存器电路
// Connect the R inputs to the SW switches,clk to KEY[0],E to KEY[1],L to KEY[2], and w to KEY[3].Connect the outputs to the red lights LEDR[3:0].
module top_module (
input [3:0] SW,
input [3:0] KEY,
output [3:0] LEDR
); //
wire [3:0] w_input = {KEY[3],LEDR[3],LEDR[2],LEDR[1]};
generate
genvar i;
for(i=0;i<4;i++)begin:muxdff
MUXDFF (
.clk(KEY[0]),
.e(KEY[1]),
.w(w_input[i]),
.r(SW[i]),
.l(KEY[2]),
.q(LEDR[i])
);
end
endgenerate
endmodule
module MUXDFF (
input clk,
input e,
input w,
input r,
input l,
output q
);
always@(posedge clk)begin
q <= l?r:(e? w:q);
end
endmodule