HDLBits -Verilog实践笔记[1]

  • 1.入门指南

顶层的模块名和端口名top_module不得更改,否则会得到一个模拟错误。

最终状态

如果你的电路是正确的,你会看到状态:成功!。还有一些其他的可能性:

  • 编译错误—电路未编译。

  • 模拟误差—电路编译成功,但模拟未完成。

  • 错误的—电路已编译并仿真,但输出与基准不匹配。

  • 成功!—电路正确

您可以在上跟踪或分享您的进度我的统计页面。

  • 2001年标准更加简明

  • shiyue1702

// https://hdlbits.01xz.net/wiki/Wire

  • 创建一个具有一个输入和一个输出的模块,其行为就像一根线。

  • 与物理线路不同,Verilog中的线路(和其他信号)是方向的。这意味着信息只向一个方向流动,从(通常是一个)来源到下沉(源通常也称为驾驶员那驱动电线上的值)。在Verilog“连续分配”中(assign left_side = right_side;),右侧的信号值被驱动到左侧的线上。这种赋值是“连续的”,因为即使右边的值发生变化,赋值也一直在继续。连续的任务不是一次性的。

  • 模块上的端口也有方向(通常是输入或输出)。输入端口是受……操纵来自模块外部的东西,而一个输出端口驱动外面的东西。从模块内部看,输入端口是驱动器或源,而输出端口是接收器。

  • 下图说明了电路的每一部分如何对应Verilog代码的每一位。模块和端口声明创建了电路的黑色部分。您的任务是通过添加一个assign要连接的语句in到out。盒子外面的器件与您无关,但您应该知道,您的电路是通过将信号从我们的测试线束连接到上的端口来进行测试的top_module.

  • 除了连续分配之外,Verilog还有其他三种用于程序块的分配类型,其中两种是可合成的。在我们开始使用程序块之前,我们不会用到它们。

  • 模块声明

module top_module( input in, output out );

  • 提示:

  • A continuous assignment assigns the right side to the left side continuously, so any change to the RHS is immediately seen in the LHS.

2.基本语法

  • wire4

  • 在模块外部,有三个输入端口和四个输出端口。当你有多个分配声明命令它们出现在代码中的位置没关系。与编程语言不同,分配陈述(“连续任务”)描述连接事物之间,而不是行为将一个值从一个物体复制到另一个物体。

  • 现在应该澄清一个潜在的混淆来源:这里的绿色箭头代表连接在...之间电线,但本身不是电线。模块本身已经声明了7条焊线(名为a、b、c、w、x、y和z)。这是因为input和output除非另有说明,否则声明实际上声明了一个连接。写作input wire a与相同input a。因此assign语句不是在创建连线,而是在已经存在的7条连线之间创建连接。

module top_module( 
    input a,b,c,
    output w,x,y,z );
    assign w = a;
    assign x = b;
    assign y = b;
    assign z = c;
endmodule                //状态:成功!
  • 串联运算符{ signal1, signal2, signal3, ... }在这里会很有用。

  • 创建一个实现非门的模块。

该电路类似于电线,但略有不同。当从电线进行连接时in到终点线out我们将实现一个反相器(或“非门”)来代替普通的导线。

使用赋值语句。这assign声明将连续不断地驱动器的反义词in在线上out.

module top_module( input in, output out );
    assign out = ~in;
endmodule                //状态:成功!
  • 创建一个实现与门的模块。

这个电路现在有三条线(a, b,以及out).电线a和b已经具有由输入端口驱动到其上的值。但是电线out目前没有任何驱动力。写一个assign推动的陈述out随着信号的与a和b.

请注意,该电路非常类似于非门,只是多了一个输入。如果听起来不一样,那是因为我已经开始把信号描述为驾驶(有一个已知值,由附加在它上面的东西决定)或没有动力被什么东西。Input wires是由舱外的东西驱动的。assign语句将驱动一个逻辑电平到线路上。如您所料,一个连线不能有多个驱动程序(如果有,它的逻辑级别是什么?),而没有驱动程序的连线会有一个未定义的值(合成硬件时通常被视为0)。

  • Verilog有单独的按位与(&)和逻辑与(&&)运算符,比如c。因为我们在这里使用的是一位运算符,所以我们选择哪个并不重要。

module top_module( 
    input a, 
    input b, 
    output out );
    assign out = a && b;
endmodule                 //状态:成功!
  • 创建一个实现或非门的模块。或非门是输出反相的或门。用Verilog编写时,NOR函数需要两个运算符。

一;一个assign语句用一个值驱动一条线(或者更正式的叫法是“网”)。这个值可以是任意复杂的函数,只要它是一个组合的(即无记忆、无隐藏状态)功能。一;一个assign语句是一个连续分配因为每当它的任何输入发生变化时,输出都会被“重新计算”,就像一个简单的逻辑门一样。

  • Verilog有单独的按位或(|)和逻辑或(||)运算符,比如c。因为我们在这里使用的是一位运算符,所以我们选择哪个并不重要。

module top_module( 
    input a, 
    input b, 
    output out );
    assign out = ~(a||b);
endmodule                   //状态:成功!
  • 创建一个实现XNOR门的模块。

  • 按位异或运算符是^。没有逻辑异或运算符。

module top_module( 
    input a, 
    input b, 
    output out );
    assign out = ~(a^b);
endmodule                   //状态:成功!
  • 声明电线

到目前为止,电路已经足够简单,输出是输入的简单函数。随着电路变得越来越复杂,你将需要电线来连接内部元件。当你需要使用一个连接时,你应该在模块体中声明它,在第一次使用它之前。(将来,您会遇到更多类型的信号和变量,它们也是以同样的方式声明的,但是现在,我们将从类型的信号开始电线).

module top_module (
    input in,              // Declare an input wire named "in"
    output out             // Declare an output wire named "out"
);

    wire not_in;           // Declare a wire named "not_in"

    assign out = ~not_in;  // Assign a value to out (create a NOT gate).
    assign not_in = ~in;   // Assign a value to not_in (create another NOT gate).

endmodule   // End of module "top_module"

在上述模块中,有三根电线(在, 在外,以及不在),其中两个已经声明为模块的输入和输出端口的一部分(这就是为什么您在前面的练习中不需要声明任何连线)。电线不在需要在模块内部声明。从模块外部看不到它。然后,使用两个创建两个非门分配声明。请注意,你先创建哪个非门并不重要:你最终仍然会得到相同的电路。

实践

实现以下电路。创建两条中间线(可以随意命名)将AND和OR门连接在一起。注意,馈给非门的导线实际上是导线在外,所以您不一定需要在这里声明第三条线。请注意,导线仅由一个源(门的输出)驱动,但可以馈入多个输入。

如果你遵循图中的电路结构,你应该有四个赋值语句,因为有四个信号需要赋值。

(是的,可以创建一个没有中间电线但功能相同的电路。)

`default_nettype none
module top_module(
    input a,
    input b,
    input c,
    input d,
    output out,
    output out_n   ); 

    wire a1,a2,o1;       //声明内部连接 a1 a2 o1

    assign a1 = a && b;
    assign a2 = c && d;
    assign o1 = (a1 || a2);
    assign out = o1;
    assign out_n = ~(o1);
endmodule                      //状态:成功!
`default_nettype none
module top_module(
    input a,
    input b,
    input c,
    input d,
    output out,
    output out_n   ); 

    wire a1,a2;                  //声明两个内部走线
    
    assign a1 = a&b;
    assign a2 = c&d;
    assign out = a1|a2;
    assign out_n = !(a1|a2);
endmodule                          //状态:成功!
  • 7458是一个具有四个与门和两个或门的芯片。

创建一个功能与7458芯片相同的模块。它有10个输入和2个输出。您可以选择使用assign语句来驱动每条输出线,或者您可以选择声明(四条)线用作中间信号,其中每条内部线由一个与门的输出驱动。如果需要额外的练习,可以尝试两种方法。

module top_module ( 
    input p1a, p1b, p1c, p1d, p1e, p1f,
    output p1y,
    input p2a, p2b, p2c, p2d,
    output p2y );

    wire a11,a12,a21,a22;  //声明内部走线
    
    assign a11 = (p1a & p1b & p1c);
    assign a12 = (p1d & p1e & p1f);
    assign a21 = (p2a & p2b);
    assign a22 = (p2c & p2d);   
    assign p1y = (a11 | a12);
    assign p2y = (a21 | a22);
endmodule                       //状态:成功!
  • 2.向量0

向量用于使用一个名称对相关信号进行分组,以便于操作。举个例子,wire [7:0] w;声明一个名为w这在功能上相当于有8根单独的线。

请注意申报向量的位置决定了维度以前向量的名称,这与C语法不同。然而零件选择有这样的尺寸在...之后如你所料的向量名。

wire [99:0] my_vector; // Declare a 100-element vector

assign out = my_vector[10]; // Part-select one bit out of the vector

构建一个具有一个3位输入的电路,然后输出相同的向量,并将其分成三个独立的1位输出。连接输出o0到输入向量的位置0,o1到位置1等等。

在图表中,旁边带有数字的刻度线表示向量(或“总线”)的宽度,而不是为向量中的每个位单独绘制一条线。

module top_module ( 
    input wire [2:0] vec,
    output wire [2:0] outv,
    output wire o2,
    output wire o1,
    output wire o0  ); // Module body starts after module declaration

    assign outv = vec;
    assign o2 = vec[2];
    assign o1 = vec[1];
    assign o0 = vec[0];
    
endmodule                 //状态:成功!
  • 向量1

向量用于使用一个名称对相关信号进行分组,以便于操作。举个例子,wire [7:0] w;声明一个名为w这相当于有8根独立的电线。

  • 声明向量

  • 向量必须声明为:

type[upper:lower] vector _ name;

类型指定向量的数据类型。这通常是wire 或者 reg。如果您正在声明输入或输出端口,则该类型还可以包括端口类型(例如,投入或者输出)也是。一些例子:

wire [7:0] w;         // 8-bit wire
reg  [4:1] x;         // 4-bit reg
output reg [0:0] y;   //1位reg也是一个输出端口(这仍然是一个向量)
input wire [3:-2] z;  // 6位wire输入(允许负范围)
output [3:0] a;       //  4位输出wire。【除非另有指定,否则类型为'wire'】。
wire [0:7] b;         // 8位wire,其中b[0]是最高位。

这字节顺序(或者非正式地,“方向”)是最低有效位具有较低的索引(小端,例如[3:0])还是较高的索引(大端,例如[0:3])。在Verilog中,一旦一个向量被声明了一个特殊的字节序,它必须总是以同样的方式使用。例如,书写vec[0:3]当...的时候vec已声明wire [3:0] vec;是违法的。保持字符顺序的一致性是一个很好的实践,因为如果不同字符顺序的向量被分配或一起使用,就会出现奇怪的错误。

隐式网 Implicit nets

隐式网络通常是难以检测的错误的来源。在Verilog中,net-type网络类型的信号可以由assign语句或者将未声明的内容附加到模块端口。隐式网络总是一位线one-bit wires,如果您打算使用向量,会导致错误。禁用隐式网络的创建可以使用`default_nettype none指令。

wire [2:0] a, c;   // Two vectors
assign a = 3'b101;  // a = 101
assign b = a;       // b =   1  implicitly-created wire( b = 1隐式创建的连接)
assign c = b;       // c = 001  <-- bug
my_module i1 (d,e); // d and e are implicitly one-bit wide 
                    //d和e如果没有声明,则隐式地为1位宽。
                    // 这可能是一个错误,如果端口是一个向量。

加`default_nettype none会使第二行代码成为一个错误,这使得错误更加明显。

  • 未封装阵列与封装阵列 Unpacked vs. Packed Arrays

你可能已经注意到了声明,向量索引被写入以前向量名称。这声明了数组的“打包”维度,其中位被一起“打包”到一个blob中(这与模拟器相关,但与硬件无关)。这解除…的负担尺寸已声明在...之后名字。它们通常用于声明内存数组。由于ECE253没有涵盖内存阵列,因此我们在本课程中没有使用封装阵列。看见http://www.asic-world.com/systemverilog/data_types10.html了解更多详情。

reg [7:0] mem [255:0];   // 256 unpacked elements, each of which is a 8-bit packed vector of reg.
                          //256个解包元素,每个元素都是一个8位的reg打包向量。
reg mem2 [28:0];         // 29 unpacked elements, each of which is a 1-bit reg.
                         //29个未打包的元素,每个都是1位reg。

//数组是变量的集合,所有变量都是相同的类型,使用相同的名称加上一个或多个索引来访问。

  • 在C #中,数组从0开始按整数进行索引,或者转换为指针。尽管整个数组都可以初始化,但每个元素都必须在过程语句中单独读取或写入。在Verilog-2001中,数组从左到右进行索引。如果它们是向量,它们可以被指定为一个单元,但如果它们是数组,则不能。Verilog-2001允许多维度

  • 在Verilog-2001中,所有数据类型都可以声明为数组。reg、wire和所有其他网络类型也可以声明一个矢量宽度。在对象名之前声明的尺寸称为“矢量宽度”尺寸。在对象名之后声明的维度被称为“数组”维度。

  • SystemVerilog使用这个术语压缩阵列引用对象名之前声明的尺寸(Verilog-2001称为矢量宽度)。该术语未封装数组用于引用对象名称后声明的维度。

  • 压缩数组只能由单个位类型(bit、logic、reg、wire和其他网络类型)以及递归的其他压缩数组和压缩结构组成。压缩数组的最大大小是有限的,但至少应为65536 (216)位。

module packed_unpacked_data();

// packed array 打包数组
bit [7:0] packed_array = 8'hAA; 
// unpacked array 未打包数组
reg unpacked_array [7:0] = '{0,0,0,0,0,0,0,1}; 

initial begin
    $display ("packed array[0]   = %b", packed_array[0]);
    $display ("unpacked array[0] = %b", unpacked_array[0]);
    $display ("packed array      = %b", packed_array);

    // Below one is wrong syntax  下面一个语法错误
    //$display("unpacked array[0] = %b",unpacked_array);

    #1  $finish;
end
endmodule

//模拟输出
 packed array[0]   = 0
 unpacked array[0] = 1
 packed array      = 10101010

访问矢量元素:部分选择

使用向量名来访问整个向量。例如:

assign w = a;

取整个4位向量a并将其分配给整个8位向量w(声明摘自上文)。如果左右两边的长度不匹配,则根据情况进行零扩展或截断。

部分选择运算符可用于访问向量的一部分:

w[3:0]      // Only the lower 4 bits of w
x[1]        // The lowest bit of x
x[1:1]      // ...also the lowest bit of x
z[-1:-2]    // Two lowest bits of z
b[3:0]      // 不合法。向量部分选择必须匹配声明的方向。
b[0:3]      // The *upper* 4 bits of b.
assign w[3:0] = b[0:3];    //将b的上4位赋给w的下4位w[3]=b[0], w[2]=b[1],等等。

练习

构建一个组合电路,将输入半字(16位,[15:0])分成低位[7:0]和高位[15:8]字节。

`default_nettype none     // 禁用隐式网络。减少某些类型的bug。
module top_module( 
    input wire [15:0] in,
    output wire [7:0] out_hi,
    output wire [7:0] out_lo );

endmodule
`default_nettype none     // Disable implicit nets. Reduces some types of bugs.
module top_module( 
    input wire [15:0] in,
    output wire [7:0] out_hi,
    output wire [7:0] out_lo );
    assign out_hi = in[15:8];
    assign out_lo = in[7:0];
endmodule                         //状态:成功!
  • 向量部分选择

32位向量可视为包含4个字节(位[31:24]、[23:16]等)。).建立一个电路来逆转字节4字节字的排序。

AaaaaaaaBbbbbbbbCcccccccDddddddd => DdddddddCcccccccBbbbbbbbAaaaaaaa

此操作通常用于字节顺序例如,在许多互联网协议中使用的小字节x86系统和大字节格式之间。

  • 提示:Part-select可以用在赋值的左侧和右侧。

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                           //状态:成功!

  • 向量门

构建一个具有两个3位输入的电路,用于计算两个向量的按位或、两个向量的逻辑或以及两个向量的逆(非)。放置…的反义词b在...的上半部out_not(即位[5:3]),以及的倒数a在下半部分。

按位运算符与逻辑运算符

前面,我们提到了各种布尔运算符的按位和逻辑版本(例如,诺尔盖特).使用向量时,两种运算符类型之间的区别变得很重要。两个N位向量之间的按位运算&复制向量的每个位的运算,并产生N位输出

逻辑运算&&将整个向量视为布尔值(真=非零,假=零),并产生1位输出。

查看模拟波形,了解按位“或”和“逻辑“或”有何不同。

  • 提示:即使你不能assign多次连接到导线,您可以使用左侧的零件选择assign。你不需要在一个语句中给整个向量赋值。

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[5:3] = ~b;
    assign out_not[2:0] = ~a;
endmodule                        //状态:成功!
  • 四输入逻辑门

构建具有四个输入的组合电路,在[3:0].

有3个输出:

  • out _ AND:4输入与门的输出。

  • out _ or:4输入OR门的输出。

  • out _ XOR:4输入异或门的输出。

要查看AND、OR和XOR运算符,请参见gate, norgate, 以及xnorgate.

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[3]||in[2]||in[1]||in[0];
    assign out_xor = in[3]^in[2]^in[1]^in[0];
endmodule                                      //状态:成功!
  • 向量串联运算符

零件选择Part selection用来选择矢量的一部分。串联运算符{a,b,c}用于通过将向量的较小部分连接在一起来创建较大的向量。 示例如下:

{3'b111, 3'b000} => 6'b111000
{1'b1, 1'b0, 3'b101} => 5'b10101
{4'ha, 4'd10} => 8'b10101010     // 4'ha和4'd10都是二进制的4'b1010

串联需要知道每个组件的宽度(或者你怎么知道结果的长度?).因此,{1, 2, 3}是非法的,并导致错误消息:串联中不允许无大小的常数.

串联运算符可用于赋值的左侧和右侧。

input [15:0] in;
output [23:0] out;
assign {out[7:0], out[15:8]} = in;         // 交换两个字节。右边和左边都是16位向量。
assign out[15:0] = {in[7:0], in[15:8]};    // This is the same thing.
assign out = {in[7:0], in[15:8]}; // 这是不同的。右边的16位向量被扩展以匹配-
                                  //-左边的24位向量,因此输出[23:16]为零。
                                  //在前两个例子中,out[23:16]没有被赋值

一点点练习

给定几个输入向量,将它们连接在一起,然后将它们分成几个输出向量。有六个5位输入向量:a、b、c、d、e和f,总共30位输入。对于32位输出,有四个8位输出向量:w、x、y和z。输出应该是两个输入向量的串联1比特:

module top_module (
    input [4:0] , b, c, d, e, f,
    output [7:0] w, x, y, z );//

    assign {w,x,y,z} = {a,b,c,d,e,f,2'b11};

endmodule                                 //状态:成功!

  • 向量反转1

给定一个8位输入向量[7:0],反转其位顺序。 提示:

  • assign out[7:0] = in[0:7];不起作用,因为Verilog不允许反转向量位排序。

  • 串联操作符可以节省一些代码,允许使用1条赋值语句而不是8条。

module top_module( 
    input [7:0] in,
    output [7:0] out
);
    assign {out[0],out[1],out[2],out[3],out[4],out[5],out[6],out[7]} = in;
endmodule 
                         //状态:成功!
  • 向量4-复制运算符

并置算符concatenation operator允许将向量连接在一起形成一个更大的向量。但是有时候你想把同一件事情连接起来很多次,这样做还是很乏味的赋值a = {b,b,b,b,b,b };。复制运算符允许重复一个向量并将它们连接在一起:

{num {vector}}

这复制矢量经过数字时代周刊。数字必须是常数。两组大括号都是必需的。 示例:

{5{1'b1}}           // 5'b11111 (or 5'd31 or 5'h1f)
{2{a,b,c}}          // The same as {a,b,c,a,b,c}
{3'd5, {2{3'd6}}}   // 9'b101_110_110。它是101和的串联    3'd5 = 'b101
                    //第二个向量,是3'b110的两个副本。      3'd6 = 'b110

一点点练习

复制运算符的一个常见应用是将一个较小的数字符号扩展为一个较大的数字,同时保留其有符号的值。这是通过将较小数字的符号位(最高有效位)复制到左侧来实现的。例如,符号扩展4 'b0101(5)到8位的结果是8 'b00000101(5)符号扩展时4 'b1101(-3)到8位的结果是8 'b11111101 (-3).

构建一个电路,将8位数字符号扩展到32位。这需要串联符号位的24个副本(即复制位[7] 24次),然后是8位数本身。

module top_module (
    input [7:0] in,
    output [31:0] out );

    assign out = { {24{in[7]}}, in };

endmodule                 //状态:成功!
  • 向量5-更多复制

给定五个1位信号(a、b、c、d和e),计算25位输出向量中的所有25个成对1位比较。如果比较的两位相等,则输出应为1。

out[24] = ~a ^ a; // a == a, so out[24] is always 1.

out[23] = ~a ^ b;

out[22] = ~a ^ c;

...

out[ 1] = ~e ^ d;

out[ 0] = ~e ^ e;

如图所示,使用复制和串联运算符。

  • 顶部向量是每个输入的5个重复的串联

  • 底部向量是5个输入的串联的5次重复

module top_module (
    input a, b, c, d, e,
    output [24:0] out );//

    // The output is XNOR of two vectors created by  输出是由连接和复制五个输入。
    // concatenating and replicating the five inputs.
    assign out = ~{{5{a}},{5{b}},{5{c}},{5{d}},{5{e}}} ^ {{5{a,b,c,d,e}}};
       
endmodule              //状态:成功!
  • 3.模块Module

module,这是一个通过输入和输出端口与其外部交互的电路。更大、更复杂的电路是由组成由较小的模块和其他部分(如赋值语句和always块)连接在一起的较大的模块。这形成了一个层次结构,因为模块可以包含其他模块的实例。

下图显示了一个带有子模块的非常简单的电路。在本练习中,创建一个情况模块的mod_a,然后连接模块的三个引脚(in1, in2,以及out)连接到顶层模块的三个端口(电线a, b,以及out).该模块mod_a您必须实例化它。

连接模块时,只有模块上的端口是重要的。您不需要知道模块内部的代码。模块的代码mod_a看起来像这样:

modulemod_a(inputin1,inputin2,outputout);

// Module body

endmodule

只要使用的所有模块都属于同一个项目(因此编译器知道在哪里可以找到该模块),就可以通过在一个模块中实例化另一个模块来创建模块的层次结构。一个模块的代码没有编写里面的另一个模块的主体(不同模块的代码不嵌套)。

您可以通过端口名称或端口位置将信号连接到模块。如果需要额外的练习,请尝试这两种方法。

将信号连接到模块端口

将导线连接到端口有两种常用方法:按位置或按名称。

按位置

通过位置将线连接到端口的语法应该是熟悉的,因为它使用了类似C的语法。当实例化一个模块时,端口根据模块的声明从左到右连接。例如:

mod_a instance1 ( wa, wb, wc );

这将实例化一个类型为mod_a并给它一个实例名称“实例1”,然后连接信号wa(在新模块之外)到第一端口(in1)的新模块,wb到第二端口(in2),以及wc到第三端口(out).这种语法的一个缺点是,如果模块的端口列表发生变化,也需要找到并更改模块的所有实例,以匹配新的模块。

名称

将信号连接到模块端口名叫即使端口列表改变,也允许电线保持正确连接。然而,这种语法更加冗长。

mod_a instance2 ( .out(wc), .in1(wa), .in2(wb) );

上面一行实例化了一个类型的模块mod_a命名为“实例2”,然后连接信号wa(模块外)至端口命名的 in1, wb去港口命名的 in2,以及wc去港口命名的 out。请注意,端口的顺序在这里是不相关的,因为连接将连接到正确的名称,而不管它在子模块端口列表中的位置。另请注意,在此语法中,紧接在端口名称前面的句点。

// .out'内部端口' (wc'外部信号名') ???

module top_module ( input a, input b, output out );
    mod_a instance2 ( .out(out), .in1(a), .in2(b) );
endmodule
               //状态:成功!
  • 模块位置-按位置连接端口

这个问题和上一个类似(组件).您将获得一个名为mod_a它依次有2个输出和4个输入。您必须连接6个端口按职位到顶层模块的端口out1, out2, a, b, c,以及d,按此顺序。

您将学习以下模块:

module mod_a ( output, output, input, input, input, input );

module top_module ( 
    input a, 
    input b, 
    input c,
    input d,
    output out1,
    output out2
);
    mod_a instance1 ( out1,out2, a, b, c, d );
endmodule               //状态:成功!
  • 模块-按名称连接端口

这个问题类似于组件。您将获得一个名为mod_a它有2个输出和4个输入。您必须连接6个端口名叫到顶层模块的端口:

港口mod_a

港口top_module

output out1

out1

output out2

out2

input in1

a

input in2

b

input in3

c

input in4

d

您将学习以下模块:

module mod_a ( output out1, output out2, input in1, input in2, input in3, input in4);

module top_module ( 
    input a, 
    input b, 
    input c,
    input d,
    output out1,
    output out2
);
  mod_a instance1 ( .out1(out1), .out2(out2),.in1(a), .in2(b),.in3(c),.in4(d));
endmodule                   // 状态:成功!     
  • 模块移位

  • 题目要求:给你一个模块my_dff具有两个输入和一个输出(实现D触发器)。实例化它们中的三个,然后将它们链接在一起,形成一个长度为3的移位寄存器。这clk端口需要连接到所有实例。

为您提供的模块是:

module my_dff ( input clk, input d, output q );

注意,要进行内部连接,您需要声明一些连线。在命名连线和模块实例时要小心:名称必须是唯一的。

module top_module ( input clk, input d, output q );

    wire q0,q1;       //定义中间内部走线q0 q1

    my_dff  instance0 ( .clk(clk), .d(d), .q(q0));
    my_dff  instance1 ( .clk(clk), .d(q0), .q(q1));
    my_dff  instance2 ( .clk(clk), .d(q1), .q(q));
endmodule              //状态:成功!

  • 模块移位8-模块和向量

这个练习是模块_移位模块端口不再只是单个引脚,我们现在有了带向量的模块作为端口,您可以将线向量而不是普通线连接到这些端口。与Verilog中的其他地方一样,端口的向量长度不必与连接它的电线相匹配,但这会导致向量的零填充或截断。本练习不使用向量长度不匹配的连接。

给你一个模块my_dff8具有两个输入和一个输出(实现一组8 D触发器)。实例化其中的三个,然后将它们链接在一起,形成一个长度为3的8位宽移位寄存器。此外,创建一个4:1多路复用器(未提供),它根据选择输出内容sel[1:0]:在第一个、第二个或第三个D触发器之后,输入D的值。(本质上,sel选择输入延迟的周期数,从零到三个时钟周期。)

为您提供的模块是:

module my_dff8 ( input clk, input [7:0] d, output [7:0] q );

没有提供多路复用器。一种可能的编写方法是在always用一个case声明在里面。(另请参见:mux9to1v)

module top_module ( 
    input clk, 
    input [7:0] d, 
    input [1:0] sel, 
    output [7:0] q 
);
    wire[7:0] q0,q1,q2;       //定义中间8比特位宽的内部走线线向量q0 q1 q2

    my_dff8  instance0 ( .clk(clk), .d(d), .q(q0));
    my_dff8  instance1 ( .clk(clk), .d(q0), .q(q1));
    my_dff8  instance2 ( .clk(clk), .d(q1), .q(q2));
   
    always @ (*) begin
    case(sel)
        2'b00: q = d;
        2'b01: q = q0;
        2'b10: q = q1;     //时序逻辑设计通常使用非阻塞赋值'<='
        2'b11: q = q2;     //实现组合逻辑时always块结构必须采用阻塞赋值语句'='
                          //default: q = d;
    endcase
end
endmodule                      //状态:成功!
  • 使用阻塞赋值是因为在多位信号同时赋值时,阻塞赋值可以保证按顺序执行,并且不会出现竞争条件。由于Verilog中的非阻塞赋值会并发执行,如果同时有多个非阻塞赋值操作作用于同一信号,则可能导致不可预期的结果。因此,在这种情况下,使用阻塞赋值可以更好地确保代码的正确性和稳定性。当然,使用哪种类型的赋值取决于具体情况和设计需求,需要根据实际情况进行选择

-----chat3API所回答

  • 模块加法器

给你一个模块add16它执行16位加法。实例化其中两个来创建一个32位加法器。在接收到来自第一加法器的进位之后,一个add16模块计算加法结果的低16位,而第二个add16模块计算结果的高16位。您的32位加法器不需要处理进位输入(假设为0)或进位输出(忽略),但内部模块需要处理才能正常工作。(换句话说add16模块执行16位a + b + cin,而您的模块执行32位a + b)。

如下图所示,将模块连接在一起。提供的模块add16具有以下声明:

module add16 ( input[15:0] a, input[15:0] b, input cin, 
               output[15:0] sum, output cout );
module top_module(
    input [31:0] a,
    input [31:0] b,
    output [31:0] sum
);
    wire cout0;
    wire [15:0] sum0,sum1;    //定义内部走线
    
    add16  instance0 (.sum(sum0),.cout(cout0), .cin(1'b0),.a(a[15:0]), .b(b[15:0]));
    add16  instance1 (.sum(sum1),.cout(), .cin(cout0),.a(a[31:16]), .b(b[31:16]));
    
    assign sum = {sum1,sum0};

endmodule                         //状态:成功!
  • 模块fadd

在本练习中,您将创建一个具有两个层次的回路。你的top_module将实例化的两份副本add16(已提供),每个实例将实例化16个add1(必须写)。因此,你必须写模块:top_module和add1.

喜欢模块_添加,您将获得一个模块add16它执行16位加法。您必须实例化其中的两个来创建32位加法器。一个add16模块计算加法结果的低16位,而第二个add16模块计算结果的高16位。您的32位加法器不需要处理进位输入(假设为0)或进位输出(忽略)。

连接add16模块组合在一起如下图所示。提供的模块add16具有以下声明:

module add16 ( input[15:0] a, input[15:0] b, input cin, 
               output[15:0] sum, output cout );

在每个里面add16,16个全加器(模块add1未提供)被实例化以实际执行加法。

您必须编写具有以下声明的全加器模块:

module add1 ( input a, input b, input cin, 
              output sum, output cout );

回想一下,全加器计算a+b+cin的和与进位。

总之,本设计中有三个模块:

  • top_module—包含以下两项的顶级模块...

  • add16,提供—一个16位加法器模块,由16个组成...

  • add1—1位全加器模块。

如果您的提交缺少module add1,您将收到一条错误消息,指出Error (12006): Node instance "user_fadd[0].a1" instantiates undefined entity "add1".

提示:Full adder equations:

    sum = a ^ b ^ cin
    cout = a&b | a&cin | b&cin
module top_module (
    input [31:0] a,
    input [31:0] b,
    output [31:0] sum
);//
    wire cout0;
    add16  inst0 (.sum(sum[15:0]),.cout(cout0), .cin(1'b0),.a(a[15:0]), .b(b[15:0]));
    add16  inst1 (.sum(sum[31:16]),.cout(), .cin(cout0),.a(a[31:16]), .b(b[31:16]));
endmodule

module add1 ( input a, input b, input cin,   output sum, output cout );
// Full adder module here
    assign {cout,sum} = a+b+cin;
endmodule                         //状态:成功!
  • 模块-进位选择加法器

波纹进位加法器的一个缺点(参见之前的练习)是加法器计算进位(在最坏的情况下,从进位到进位)的延迟相当慢,并且第二级加法器不能开始计算它的执行,直到第一级加法器完成。这使得加法器变慢。一个改进是进位选择加法器,如下所示。第一级加法器与之前相同,但我们复制了第二级加法器,一个假设进位输入=0,一个假设进位输入=1,然后使用快速2:1多路复用器来选择哪个结果恰好是正确的。

在本练习中,我们为您提供了相同的模块add16如前面的练习,将两个16位数字相加,并产生一个进位和16位和。您必须实例化这些来建立进位选择加法器,使用自己的16位2到1多路复用器。

如下图所示,将模块连接在一起。提供的模块add16具有以下声明:

module add16 ( input[15:0] a, input[15:0] b, input cin, 
               output[15:0] sum, output cout );
module top_module(
    input [31:0] a,
    input [31:0] b,
    output [31:0] sum
);
    wire cout0;  //定义内部线cout0,将连到复用器的SEL端
    wire [15:0] sum0,sum1,sum2,mout;
    
    add16  inst0 (.sum(sum0),.cout(cout0), .cin(1'b0),.a(a[15:0]), .b(b[15:0]));
    add16  inst1 (.sum(sum1),.cout(), .cin(1'b0),.a(a[31:16]), .b(b[31:16]));
    add16  inst2 (.sum(sum2),.cout(), .cin(1'b1),.a(a[31:16]), .b(b[31:16]));
    
    always @ (*) 
        begin
        case(cout0)
        1'b0: mout = sum1;     //时序逻辑设计通常使用非阻塞赋值'<='
        1'b1: mout = sum2;     //实现组合逻辑时always块结构必须采用阻塞赋值语句'='
        endcase
    end
    assign sum = {mout,sum0};  //最终输出端口赋值
endmodule                             //状态:成功!

  • 模块 addsub

可以通过对其中一个输入取反,从加法器构建加减器,这相当于先将输入反相,然后再加1。最终结果是一个可以做两种运算的电路:(a + b + 0)和(a + ~b + 1)。看见维基百科(一个基于wiki技术的多语言的百科全书协作计划ˌ也是一部用不同语言写成的网络百科全书ˌ 其目标及宗旨是为全人类提供自由的百科全书)ˌ开放性的百科全书如果你想更详细地了解这个电路的工作原理。

建立下面的加减法器。

为您提供了一个16位加法器模块,您需要实例化该模块两次:

  • module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );

  • 使用32位宽的XOR门来反转b无论何时输入sub是1。(这也可以看作是b[31:0]与子复制32次进行xor运算。看见复制运算符。).还要连接sub加法器进位的输入。

  • 提示:XOR门也可以被视为可编程反相器,其中一个输入控制另一个输入是否应该反相。以下两个电路都是异或门:

module top_module(
    input [31:0] a,
    input [31:0] b,
    input sub,            //XOR异或门 ^ 按位异或操作符
    output [31:0] sum        
);
    wire cout0;  //定义内部线cout0
    wire [15:0] sum0,sum1;
    wire [31:0] B;
    assign B = (b ^ {32{sub}});
    
    add16  inst0 (.sum(sum0),.cout(cout0), .cin(sub),.a(a[15:0]), .b(B[15:0]));
    add16  inst1 (.sum(sum1),.cout(), .cin(cout0),.a(a[31:16]), .b(B[31:16]));
    
    assign sum = {sum1,sum0};
endmodule                             //状态:成功!
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值