HDBits刷题1: Verilog Language

1.basics(基础)

5).或非门

nor的用法,不是out = nor(a,b), nor是一个定义好gate元件,通过例化使用.

6).同或门:

可写作xnor, 异或门为xor:

8).7458chip

2.vectors(矢量)

1).vectors矢量

多位信号的assign表明位数:outv[2:0] = vec[2:0], 也可以是outv = vec.

2)vectors in more detail 一些注意点

i). declaring vectors:

wire [0:7] a 和 wire [7:0] a的区别是前者最高位为a[0],后者为a[7],两种写法都可以,但同一个设计中最好用相同的形式, 后者常用.

ii). 隐含的网络带来的错误:

未声明宽度的时候默认位宽为1,下图中a到b的连接截取了最低位,b到c的连接中位宽不匹配,则报错:`default_nettype none

iii). vector array 向量阵列.

下图为255个位宽为8bit的寄存器阵列和29个位宽为1bit的寄存器阵列

4)bitwise operators:

& | ~ 为按位操作, && || !为逻辑操作:如3'b101:~结果是3'b010, !的结果是1'b0.

5)four input gates

6)vector concatenation(串联) operator 连接操作, 示例如下:

7)vector reversal 1 矢量逆序

是否可以assign out[7:0] = in[0:7] ?? 答案是不可以: verilog不允许这样操作.

正解如下:(或者逐位拼接也可以, 但很麻烦). 注意:for循环仅在always和initial块中使用, 直接定义for循环会报错.

8)replication operator 复制操作

vector复制示例:

常见的复制实例: 符号位扩展

sign extending:

4'b0101 --> 8'b0000_0101 (8'd5)

4'b1101 --> 8'b1111_1101 (-8'd3)

9)more replication 其他复制的情况

注意:{5{a}, 5{b}, ... }这种拼接方式是错误的, 5个a连接应该表示为{5{a}}.

解答如下:

3.modules: hierarchy(模块:层次化)

1)modules 例化一个模块

2)例化模块(by pos)

3)例化模块(by name)

4)three module(3个D触发器模块例化连接)
5)modules and vectors

下面模块的作用是选择不同延迟周期数(从0到3个周期), dff用于延迟, 选择器用来选择.

解答:

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, 最高位进位不考虑

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.

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:加法减法器

通过一个输入的控制信号来决定是让两个数进行加法还是减法.

减法的原理是,把被减数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语句:

练习: 用assign 和if 两种方式实现4输入的数据选择器, 其真值表如下:

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, 给出的示例如下:

生成的电路存在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

练习:根据下图实现键盘上下左右四个键位的映射

对于每个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操作, 最终得到一位的输出. 有如下示例:

注: (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 = &in;
    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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bluebub

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值