Verilog基础(一):基础元素

news/2025/2/5 15:51:45 标签: fpga开发, verilog

verilog_0">verilog基础

我先说,看了肯定会忘,但是重要的是这个过程,我们知道了概念,知道了以后在哪里查询。语法都是术,通用的概念是术。所以如果你有相关的软件编程经验,那么其实开启这个学习之旅,你会感受到熟悉,也会感受到别致。


入门

- 如何开始

欢迎来到二进制的世界,数字逻辑的入门一开始可能有难度,因为你需要学习新的概念,新的硬件描述语言(HDL – Hardware Description Language)例如 verilog,几个新的仿真软件和一块FPGA的板子.但是这能帮你更加深刻的理解计算机的运作原理.

设计电路需要如下几个步骤:

  1. 编写HDL(verilog)代码
  2. 编译代码生成电路
  3. 模拟电路并修复错误

下面,我们来个简单的例子,请把one的输出设为1:

  • Module Declaration
verilog">module top_module(output one);
  • Solution
verilog">module top_module( output one );

    assign one = 1;

endmodule
- 输出0

建立一个没有输入,输出为常数0的电路.

本系列题使用verilog-2001 ANSI-style 的端口声明语法,因为它更容易阅读并减少了拼写错误.如果愿意,可以使用旧的verilog-1995语法.例如,下面的两个模块声明是可接受的和等效的:

verilog">module top_moduel(zero);
	output zero;
	//verilog-1995
endmodule

module top_module(output zero);
//verilog-2001
endmodule
  • Module Declaration
verilog">module top_module(
    output zero
);
  • Solution
verilog">module top_module(
    output zero
);// Module body starts after semicolon
	assign zero=0;
endmodule

Verilog 语言

基础元素

- wire类型

创建一个具有一个输入和一个输出的模块,其行为想一条"线"(Wire)。

与物理线不同但十分相似,Verilog中的线(和其他信号)是定向的。

这意味着信息只在一个方向上流动,从(通常是一个)源流向汇点(该源通常也被称为驱动程序将值驱动到wire上)。

verilog"连续赋值"(assign left_side=right_side;)中,右侧的信号值被驱动到左侧的"线"上

请注意:赋值是"连续的"(Continuous Assignments),因为如果右侧的值发生更改,分配也会一直持续,因此左侧的值将随之改变。(这里与其他语言有很大区别)。 连续分配不是一次性事件,其产生的变化是永久的。

想要真正理解为啥会这样,你首先要明白,你并不是在编写程序,你其实是在用代码"画"电路!
因此输入端的电平高低的变化必然会影响到wire的另一端,你可以想像真的有一根电线连接两个变量。

模块(module)上的端口(port)也有一个方向(通常是输入 – input或输出 – output)。

输入端口由来自模块外部的东西驱动,而输出端口驱动外部的东西。从模块内部查看时,输入端口是驱动程序或源,而输出端口是接收器。

下图说明了电路的每个部分如何对应Verilog代码的每个部分。

  • 模块和端口声明创建电路的黑色部分。
  • 您的任务是通过添加一个assign语句来创建一条线(绿色)。
  • 盒子外的部件不是您的问题,但您应该知道,通过将测试激励连接到top_module上的端口来测试电路。

除了连续赋值之外,Verilog还有另外三种用于程序块(Procedural block)的赋值类型,其中两种是可综合的。在开始使用Procedural block之前,我们不会使用它们.

  • Module Declaraction
verilog">module top_module( input in, output out );
  • Solution
verilog">module top_module( input in, output out );
    assign out = in;
endmodule

这里的 Module Declaraction 和 Solution就像是对应C里面的声明和定义实现

- Four wires

创建一个具有3个输入和4个输出的模块,这些输入和输出的行为如下:

A ->W
B -> X
B -> Y
C -> Z

下图说明了电路的每个部分如何对应Verilog代码的每个部分.模块外部有三个输入端口和四个输出端口.

当您有多个assign语句时,它们在代码中的出现顺序并不重要.与编程语言不同,assign语句(“连续赋值”)描述事物之间的连接,而不是将值从一个事物复制到另一个事物的操作.

可能现在应该澄清的一个潜在的困惑来源是:这里的绿色箭头表示电线之间的连接,但不是wire本身.

模块本身已经声明了7条线(名为A、B、C、W、X、Y和Z).这是因为input与output被声明为了wire类型.因此,assign语句不会创建wire,而是描述了在已存在的7条线之间创建的连接.

  • Module Declaraction
verilog">module top_module(
	input a,b,c;
	output w,x,y,z
);
  • Solution
verilog">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
- 反转器 (Inveter)

创建实现非门的模块.

这个电路和电线相似,但有点不同.当把电线从进线连接到出线时,我们要实现一个反相器(非门),而不是一根普通的线.

使用assign语句.assign语句将连续地将in取反并输出.

  • Module
verilog">module top_module( input in, output out );
  • Solution
verilog">module top_module( input in, output out );
	assign out = !in;
endmodule
- 与门 (AND gate)

创建实现和门的模块.

这个电路现在有三条线(A、B和OUT).导线A和B已经具有由输入端口驱动的值.但目前的布线并不是由任何东西驱动的.写一个赋值语句,用信号A和B的和来驱动.

输入线由模块外部的东西驱动.assign语句将把一个逻辑级别驱动信号连接到一条线上.正如您可能期望的那样,一条线不能有多个驱动信号(如果有的话,它的逻辑级别是什么?),并且没有驱动信号的导线将具有未定义的值(在合成硬件时通常被视为0,但有时候会出现奇怪的错误).

  • Module Declaraction
verilog">module top_module( 
    input a, 
    input b, 
    output out );
  • Solution
verilog">module top_module( 
    input a, 
    input b, 
    output out );
assign out = a&b;
endmodule
- 或非门 (NOR gate)

创建实现或非门的模块.或非门是一个输出反转的或门.

assign语句用一个值来驱动(drive)一条线(或者更正式地称为"net").这个值可以是任意复杂的函数,只要它是一个组合逻辑(即,无内存(memory-less),无隐藏状态).

  • Module Declaraction
verilog">module top_module( 
    input a, 
    input b, 
    output out );
  • Solution
verilog">module top_module( 
    input a, 
    input b, 
    output out );
    assign out = !(a|b);
endmodule
- 异或非门 (XNOR gate)

实现异或非门模块.

  • Module Declaraction
verilog">module top_module( 
    input a, 
    input b, 
    output out );
  • Solution
verilog">module top_module( 
    input a, 
    input b, 
    output out );
    assign out = !(a^b);
endmodule
- 声明wires

到目前为止,电路都十分简单。随着电路变得越来越复杂,您将需要wire将内部组件连接在一起。当您需要使用导线时,您应该在模块体中在首次使用之前的某个地方声明它.

(将来,您将遇到更多类型的信号和变量,它们也以相同的方式声明,但现在,我们将从Wire类型的信号开始).

verilog">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"

实现以下电路.

创建两个wire(命名任意)以将and/or gate 连接在一起.

请注意,Not gate 是输出,因此您不必在这里声明第三条线.

wire可有多个输出,但只能有一个输入驱动. 下面这例子:

如果您遵循图中的电路结构,那么应该以四个赋值语句结束,因为有四个信号需要赋值.

  • Module Declaraction
verilog">`default_nettype none
module top_module(
    input a,
    input b,
    input c,
    input d,
    output out,
    output out_n   ); 
  • Solution
verilog">`default_nettype none
module top_module(
    input a,
    input b,
    input c,
    input d,
    output out,
    output out_n   ); 

	wire inside1,inside2;
    
	assign inside1 = a&b;
    
	assign inside2 = c&d; 
    
	assign out = inside1|inside2;
    
	assign out_n = !(out);

endmodule
- 7458模块

实现如下电路:

  • Module Declaraction
verilog">module top_module ( 
    input p1a, p1b, p1c, p1d, p1e, p1f,
    output p1y,
    input p2a, p2b, p2c, p2d,
    output p2y );
  • Solution
verilog">module top_module ( 
    input p1a, p1b, p1c, p1d, p1e, p1f,
    output p1y,
    input p2a, p2b, p2c, p2d,
    output p2y );
	wire inside1,inside2,inside3,inside4;
    
	assign inside1 = p1a&p1b&p1c;
    
	assign inside2 = p1d&p1e&p1f;
    
	assign inside3 = p2a&p2b;
    
	assign inside4 = p2c&p2d;
    
	assign p1y = inside1|inside2;
    
	assign p2y = inside3|inside4;

endmodule

容器(Vectors)

- 容器介绍

vector被用来对相关的信号进行分组,以便于操作.例如,Wire[7:0]w;

声明一个名为w 的 8 位数组,在功能上相当于具有8条独立的线.

请注意,vector的声明将维度(dimensions 即数组长度)放在容器名称之前,这与C语法相比不常见.

至于为什么会是如下声明,主要是采用了小端序.

verilog">Wire[99:0]my_vector;//声明一个长度为100容器
assign out=my_vector[10];//从数组中选择一位

构建一个具有一个3位vector输入的电路,并将其拆分为三个单独的1位输出.

  • Module Declaraction
verilog">module top_module ( 
    input wire [2:0] vec,
    output wire [2:0] outv,
    output wire o2,
    output wire o1,
    output wire o0  ); 
  • Solution
verilog">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 o0 = vec[0];
    
    assign o1 = vec[1];
    
    assign o2 = vec[2];
    
assign outv = vec;
endmodule
- 容器细节

vector声明如下:

type [upper:lower] vector_name;
type指定了vector的类型,通常是wire或者reg.

verilog">wire [2:0] a, c;   // Two vectors
assign a = 3'b101;  // a = 101
assign b = a;       // b =   1  implicitly-created wire
assign c = b;       // c = 001  <-- bug
my_module i1 (d,e); // d and e are implicitly one-bit wide if not declared.
                    // This could be a bug if the port was intended to be a vector.

这里有一个问题:变量b在之前并没有被声明。在Verilog中,如果在使用一个变量之前没有声明它,编译器可能会隐式地声明它,但具体行为依赖于编译器的实现和设置。对于assign语句中的未声明变量,Verilog标准并没有规定必须隐式声明为多少位宽。如果编译器隐式地将b声明为一个单一位宽的线网(这是某些编译器的默认行为),那么这里就会发生位宽不匹配的问题。正确的做法是显式声明b的位宽,例如wire [2:0] b;。

由于b可能没有被正确声明为3位宽(如上所述),这里将b的值赋给c可能不会产生预期的结果。如果b被隐式声明为1位宽,那么即使a是101,b也只能存储最低位(1),然后这个值会被扩展到c的3位宽(变成001),这不是原意。

这行代码实例化了一个名为my_module的模块,其实例名为i1。d和e是连接到my_module端口的信号。如果d和e在之前没有被声明,并且my_module的对应端口是向量而不是单一位,那么这里也可能存在位宽不匹配的问题。在Verilog中,如果端口连接时未声明的信号会被隐式声明为1位宽。为了避免潜在的错误,应该显式声明所有端口信号的位宽。

  • 关于"片选"(Part Selection)

访问整个数组只需要:

verilog">assign w = a;

而访问数组的一部分,若长度在赋值时不匹配,则用0补齐例如:

verilog">reg [7:0] c;
assign c = x[3:1];
//此时,长度不匹配,则用0补齐

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]      // Illegal. Vector part-select must match the direction of the declaration.
b[0:3]      // The *upper* 4 bits of b.
assign w[3:0] = b[0:3];    // Assign upper 4 bits of b to lower 4 bits of w. w[3]=b[0], w[2]=b[1], etc.

建立一个电路,将一个半字(16 bits,[15:0])分成高8位[15:8],与低8位[7:0]输出.

  • Module Declaraction
verilog">`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 );
  • Solution
verilog">`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_lo = in[7:0];
    
    assign out_hi = in[15:8];
endmodule

在Verilog中,default_nettype 指令用于指定当在代码中遇到未声明的信号时,这些信号应该被隐式地声明为什么类型的线网(net)。默认情况下,如果不指定 default_nettype,某些Verilog编译器可能会将未声明的信号隐式地声明为1位宽的 wire 类型。 然而,这种行为可能会导致难以追踪的bug,特别是当期望的信号宽度与实际隐式声明的宽度不匹配时。

指令 default_nettype none 的作用是禁用隐式线网的声明。这意味着,如果在代码中使用了未声明的信号,编译器将会报错,而不是隐式地为其创建一个线网。这有助于减少由于未声明信号而导致的某些类型的bug,因为它迫使设计师显式地声明所有使用的信号及其位宽。

- 容器的片选(Vector part select)

32位矢量可以被视为包含4个字节(位[31:24]、[23:16]等).建立一个电路,使4字节字颠倒顺序.

aaaaaaaabbbbbbcccccccccddddddd=>ddddddddccccccccccbbbbbbbaaaaaaaa

此操作通常在需要交换一段数据的结束地址时使用,例如在Little Endian(小端序) x86系统和许多Internet协议中使用的Big Endian(大端序格式之间.

  • Module Declaraction
verilog">module top_module( 
    input [31:0] in,
    output [31:0] out );
  • Solution
verilog">module top_module( 
    input [31:0] in,
    output [31:0] out );//

    // assign out[31:24] = ...;
    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
- 位级操作(Bitwise operators)

建立一个电路,该电路有两个3-bits输入,用于计算两个vector的"基于位"的或(bitwise-OR)、两个矢量的"逻辑或"(Logical-OR)和两个矢量的非(NOT).将b的非放在out-not的高位部分(即[5:3]),将a的非放在低位部分.

看看模拟波形,看看bitwise-OR与Logical-OR的区别.

  • Module Declaraction
verilog">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
);
  • Solution
verilog">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] = ~a;	// Part-select on left side is o.
	assign out_not[5:3] = ~b;	//Assigning to [5:3] does not conflict with [2:0]
	
endmodule
- 4位Vecotr

建立一个具有4为输入的组合电路,输出要求如下:

  • out_and: 输入经过 “与门” 后的结果

  • out_or: 输入经过 “或门” 后的结果

  • out_xor: 输入经过 “异或门” 后的结果

  • Module Declaraction

verilog">module top_module( 
    input [3:0] in,
    output out_and,
    output out_or,
    output out_xor
);
  • Solution
verilog">module top_module( 
    input [3:0] in,
    output out_and,
    output out_or,
    output out_xor
);
    assign out_and = in[0]&in[1]&in[2]&in[3];
    
    assign out_or = in[0]|in[1]|in[2]|in[3];
    
    assign out_xor = in[0]^in[1]^in[2]^in[3];
endmodule

- Vector连接操作符(Vector concatenation operator)

片选用于选择vector的部分。连接运算符{a,b,c}用于通过将vector的较小部分连接在一起来创建较大的vector.

verilog">{3'b111, 3'b000} => 6'b111000
{1'b1, 1'b0, 3'b101} => 5'b10101
{4'ha, 4'd10} => 8'b10101010     // 4'ha and 4'd10 are both 4'b1010 in binary

连接需要知道每个组件的宽度,因此,{1,2,3}是非法的,并导致错误消息:串联中不允许使用未经大小化的常量.

连接操作符可以在赋值的左侧和右侧使用.

verilog">input [15:0] in;
output [23:0] out;
assign {out[7:0], out[15:8]} = in;         // Swap two bytes. Right side and left side are both 16-bit vectors.
assign out[15:0] = {in[7:0], in[15:8]};    // This is the same thing.
assign out = {in[7:0], in[15:8]};       // This is different. The 16-bit vector on the right is extended to
                                        // match the 24-bit vector on the left, so out[23:16] are zero.
                                        // In the first two examples, out[23:16] are not assigned.

连接并重新分割给定输入:

  • Module Declaraction
verilog">module top_module (
    input [4:0] a, b, c, d, e, f,
    output [7:0] w, x, y, z );
  • Solution
verilog">module top_module (
    input [4:0] a, b, c, d, e, f,
    output [7:0] w, x, y, z );//

    // assign { ... } = { ... };
    assign {w[7:0],x[7:0],y[7:0],z[7:0]} = {a[4:0],b[4:0],c[4:0],d[4:0],e[4:0],f[4:0],2'b11};
endmodule
- 反转Vector

反转一个8位vector

  • Module Declaraction
verilog">module top_module( 
    input [7:0] in,
    output [7:0] out
);
  • Solution
verilog">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
- 拷贝操作符(Replication operator)

连接运算符允许将vector连接在一起以形成较大的vector.但是有时候你想把同一个东西连接在一起很多次,比如assign A = {B, B, B, B, B, B};这样的事情仍然很乏味.复制运算符允许复制vector并将它们连接在一起:

verilog">{num{vector}}

这会将vector复制num次.

例如:

verilog">{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. It's a concatenation of 101 with
                    // the second vector, which is two copies of 3'b110.

复制运算经常会用在"有符号数"的扩转运算中,假如将一个8位有符号数扩展到16位,我们需要将其符号位进行复制并填充到扩展位.即:

8'b10000001 => 16'b1111111110000001
//这就是有符号数的扩展
  • Module Declaraction
verilog">module top_module (
    input [7:0] in,
    output [31:0] out );
  • Solution
verilog">module top_module (
    input [7:0] in,
    output [31:0] out );//

    // assign out = { replicate-sign-bit , the-input };
    assign out[31:0] = {{24{in[7]}},in[7:0]};
endmodule
- 拷贝练习

给定5个1位的输入信号,并进行如下图的比较运算,相同的位记为1,并储存在out中.

verilog">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;

  • Module Declaraction
verilog">module top_module (
    input a, b, c, d, e,
    output [24:0] out );
  • Solution
verilog">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 = ~{ ... } ^ { ... };
    assign out =  ~{{5{a}},{5{b}},{5{c}},{5{d}},{5{e}}} ^ {5{a,b,c,d,e}};
endmodule


http://www.niftyadmin.cn/n/5842294.html

相关文章

CSS Display属性完全指南

CSS Display属性完全指南 引言核心概念常用display值详解1. block&#xff08;块级元素&#xff09;2. inline&#xff08;行内元素&#xff09;3. inline-block&#xff08;行内块级元素&#xff09;4. flex&#xff08;弹性布局&#xff09;5. grid&#xff08;网格布局&…

第五章-SUSE- Rancher-容器高可用与容灾测试-Rancher-(使用Rancher-backup-异地还原测试)

系列文章目录 第一章-SUSE- Rancher-容器高可用与容灾测试-RKE2-外置数据库&#xff08;Mysql主备集群搭建&#xff09; 第二章-SUSE- Rancher-容器高可用与容灾测试-RKE2-集群搭建&#xff08;使用Mysql&#xff09;-CSDN博客 第三章-SUSE- Rancher-容器高可用与容灾测试-Ra…

RabbitMQ深度探索:五种消息模式

RabbitMQ 工作队列&#xff1a; 默认的传统队列是为均摊消费&#xff0c;存在不公平性&#xff1b;如果每个消费者速度不一样的情况下&#xff0c;均摊消费是不公平的&#xff0c;应该是能者多劳采用工作队列模式&#xff1a; 在通道中只需要设置 baseicQos 的值即可 表示 MQ 服…

蓝桥杯python基础算法(2-2)——基础算法(D)——进制转换*

目录 五、进制转换 十进制转任意进制&#xff0c;任意进制转十进制 例题 P1230 进制转换 作业 P2095 进制转化 作业 P2489 进制 五、进制转换 十进制转任意进制&#xff0c;任意进制转十进制 int_to_char "0123456789ABCDEF" def Ten_to_K(k, x):answer "…

Vue整合Axios

目标 将 axios 请求方法&#xff0c;封装到 request 模块 我们会使用 axios 来请求后端接口, 一般都会对 axios 进行一些配置 (比如: 配置基础地址,请求响应拦截器等等) 一般项目开发中, 都会对 axios 进行基本的二次封装, 单独封装到一个模块中, 便于使用 安装 Axios npm …

笔试-业务逻辑5

应用 一公司组建团建活动&#xff0c;租用双人公共自行车&#xff0c;每辆最多乘坐2人&#xff0c;且最大载重为m。 该公司共n个人&#xff0c;请问需要多少双人公共自行车&#xff1f; 实现 m, n [int(i) for i in input("请输入双人公共自行车限重以及人数&#xff…

亚博microros小车-原生ubuntu支持系列:21 颜色追踪

背景知识 这个测试例子用到了很多opencv的函数&#xff0c;举个例子。 #cv2.findContours函数来找到二值图像中的轮廓。#参数&#xff1a;#参数1&#xff1a;输 入的二值图像。通常是经过阈值处理后的图像&#xff0c;例如在颜色过滤之后生成的掩码。#参数2(cv2.RETR_EXTERNA…

51单片机 05 矩阵键盘

嘻嘻&#xff0c;LCD在RC板子上可以勉强装上&#xff0c;会有一点歪。 一、矩阵键盘 在键盘中按键数量较多时&#xff0c;为了减少I/O口的占用&#xff0c;通常将按键排列成矩阵形式&#xff1b;采用逐行或逐列的“扫描”&#xff0c;就可以读出任何位置按键的状态。&#xf…