verilog HDLBits Verilog语法

逐位逻辑运算符(&,|)和逻辑运算符(&&,||)之间的差别:

在这里插入图片描述

逐位逻辑运算符:对于 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


generate 模块循环:

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


中间变量 中间连线wire

在这里插入图片描述

多路复用器。 一种实现方法是在一个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


赋值语句 assign 和 组合always块

被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 )

casez

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 循环 generate 循环

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

由波形画电路

在这里插入图片描述

真值表

xyz
001
010
100
111

最小项之和的方法来构建电路图,最小项表达式为真值表中每一个对应函数值为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

D flip-flop (Dff) 触发器

在这里插入图片描述
在 clk 的每个上升沿将 d 复制到 q

// 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锁存器 D latch

在这里插入图片描述
同 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 触发器

在这里插入图片描述

// 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&in;
    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

在这里插入图片描述
verilog中各个always块都是并行执行的

// 检测输入信号的每一位的下降沿(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

移位寄存器 shift

// 设计一个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

在这里插入图片描述
使用 generate 语法例化多个模块

//  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


  • 0
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值