天津黑曼巴网站建设,上海电商设计招聘网站,福建高端建设网站,wordpress百度主动写在前面 在自己准备写一些简单的verilog教程之前#xff0c;参考了许多资料----asic-world网站的这套verilog教程即是其一。这套教程写得极好#xff0c;奈何没有中文#xff0c;在下只好斗胆翻译过来#xff08;加了自己的理解#xff09;分享给大家。 这是网站原文参考了许多资料----asic-world网站的这套verilog教程即是其一。这套教程写得极好奈何没有中文在下只好斗胆翻译过来加了自己的理解分享给大家。         这是网站原文Verilog Tutorial  介绍         Verilog 是一种硬件描述语言 (HARDWARE DESCRIPTION LANGUAGE ,HDL)。硬件描述语言是一种用于描述数字系统例如网络交换机、微处理器或存储器或简单的触发器的语言。这意味着你可以通过使用 HDL来描述任何级别的任何数字硬件。    //D触发器
module d_ff ( d, clk, q, q_bar);input d ,clk;
output q, q_bar;
wire d ,clk;
reg q, q_bar;always (posedge clk) beginq  d;q_bar  ! d;
endendmodule          通过硬件描述语言你可以描述一个如上图所示的简单D触发器也可以描述一个超过100 万个逻辑门的复杂设计。Verilog 是业界可用于硬件设计的 HDL 语言之一。它允许我们在行为级Behavior Level、寄存器传输级 (Register Transfer Level,RTL)、门级Gate level和开关级switch level进行数字设计。Verilog 允许硬件设计人员使用行为结构来表达他们的设计将实现细节推迟到最终设计的后期阶段。         很多想学这门语言的工程师经常会问这样一个问题学Verilog需要多少时间 对此我的答案是“如果你碰巧知道至少一种编程语言的话比如C、Java等那可能只需要一周的时间。”  设计风格         Verilog 与任何其他硬件描述语言一样允许采用自下而上Bottom-up或自上而下Top-down的方法进行设计。  自下而上的设计         传统的电子设计方法是自下而上的每个设计都是使用标准门电路在门级实现的。随着新设计变得越来越复杂这种方法几乎无法维护。新系统由 ASIC 或微处理器组成复杂性达到了数千个晶体管级别。这种传统的自下而上的方法必须让位于新的结构化、层次化的设计方法。没有这些新方法就无法解决新设计的复杂性。  自上而下的设计         所有设计师都期望的设计风格是自上而下的。真正的自上向下设计允许早期测试、不同技术的轻松更改、结构化系统设计并能提供许多其他优势。但是要实现纯粹的自上而下的设计是非常困难的基于这个事实大多数设计都是这两种方法自下而上和自上而下的混合以结合两种方法的优点。         下图展示了自上而下的设计方法。   Verilog的抽象层级         Verilog 支持在不同的抽象层级上进行设计。其中三个非常重要 行为级Behavioral level寄存器转换级Register-Transfer Level门级Gate Level 行为级         通过并行算法行为来描述系统。每个算法本身都是有顺序的这意味着它由一组依次执行的指令组成。函数Functions、任务Tasks和 Always 块Always blocks是主要元素。行为级不考虑设计的结构实现。  寄存器转换级RTL         使用寄存器传输级别的设计通过操作和寄存器之间的数据传输来指定电路的特性。这需要使用显式的时钟信号。RTL 设计包含精确的时序边界操作被安排在特定的时间发生。现代 RTL 代码定义是“任何可综合的代码都称为 RTL 代码”。  门级         在逻辑层内系统的特性由逻辑链路及其时序属性描述。所有的信号都是离散信号它们只能是明确的逻辑值0、1、X、Z。可用的操作是预定义的逻辑原语AND、OR、NOT 等门。对于任何级别的逻辑设计来说使用门级建模可能都不是一个好主意。门级代码由综合工具等工具生成此网表一般用于门级仿真和后端。  再介绍         每一个初学者的梦想都是能在一天之内掌握Verilog至少也要能使用它。为了帮助初学者实现这个梦想我接下来将讲解一些理论、示例和练习。         本教程不会教您如何编程软件编程因为它是为那些有一定编程软件编程经验的人设计的。Verilog 并发执行不同的代码块----尽管这与大多数软件编程语言的顺序执行所相反但二者之间仍有许多相似之处。         此外一些数字电路的设计背景也对学习Verilog很有帮助。         Verilog 出现之前的设计主要依赖原理图。每个设计无论复杂程度如何都是通过原理图设计的。它们难以验证且容易出错这就很容易形成设计、验证、设计、验证……的漫长且乏味的开发迭代过程。         当 Verilog 出现后我们突然有了一种新的思考方式来认识逻辑电路。Verilog 设计周期更像是一个传统的编程周期本教程将引导您完成它。          使用Verilog进行设计的流程是这样的 规格 (Specifications,specs)高层级设计High level design低层级设计Low level (micro) designRTL编码RTL coding验证Verification综合Synthesis         列表中的第一个是规格----我们将对设计施加何种限制和要求我们要构建什么         在本教程中我们将构建一个2路仲裁器2 agent arbiter一种在2个源之间进行选择以争夺控制权mastership的设备。以下是我们可能会编写的一些规格。 2路仲裁器高电平有效的异步复位源 0 优先于源1的固定优先级只要请求被断言授权就会被断言意思就是只要输入有效输出就有效         一旦我们有了规格就可以绘制框图了它基本上算是系统内数据流的一种抽象什么进入黑盒子或什么从黑盒子出来。我们举的例子很简单对应的框图如下所示。现在我们还不用担心神奇的黑盒子里有什么。  仲裁器框图           如果现在我们不用Verilog来设计这个仲裁器标准程序将要求我们绘制一个状态机然后我们将为每个触发器制作一个包含状态转换的真值表跟着我们会绘制卡诺图根据卡诺图我们可以得到优化后的电路。此方法适用于小型设计但对于大型设计此流程将变得复杂且容易出错。这就是 Verilog 的用武之地它向我们展示了另一种方式。  低层级设计         要了解 Verilog 是如何帮助我们设计仲裁器的话让我们继续聚焦在状态机----现在我们进入低级设计并剥开上一个框图中黑盒子的封面来看看输入是如何影响仲裁器的。           圆圈代表仲裁器可能处于的状态每个状态都对应一个输出。状态之间的箭头是状态转换由导致转换的事件标记。例如最左边的橙色箭头表示如果仲裁器处于状态 GNT0输出对应于 GNT0 的信号并接收到输入 !req_0时就会移动到状态 IDLE 并输出对应于该状态的信号。这个状态机描述了你所需要的系统的所有逻辑状态。下一步是将其全部放入 Verilog 中。  模块Modules         我们需要回溯一下才能做到这一点。如果你观察第一张图片中的仲裁器就会发现它有一个名称“仲裁器”和输入/输出端口req_0、req_1、gnt_0 和 gnt_1。         由于Verilog是一种HDL硬件描述语言一种用于集成电路概念设计的语言它也需要具备这些东西名称和输入/输出端口。在 Verilog 中我们把“黑盒子”称为模块Modules。这是Verilog中的保留字用于指代具有输入、输出和内部逻辑的事物。它们是其他编程语言中具有返回值的函数Functions的类似概念。  “仲裁器”模块的代码         如果你仔细观察仲裁块就会看到有许多的箭头标记进入模块的是输入从模块出去的则是输出。在Verilog中我们声明了模块名和端口名之后就可以定义每个端口的方向了。其RTL代码如下所示。 module arbiter (
clock      , // clock
reset      , // Active high, syn reset
req_0      , // Request 0
req_1      , // Request 1
gnt_0      , // Grant 0
gnt_1        // Grant 1
);//-------------Input Ports-----------------------------
input           clock               ;
input           reset               ;
input           req_0               ;
input           req_1               ;//-------------Output Ports----------------------------
output        gnt_0                 ;
output        gnt_1                 ;  双向端口示例         上面我们只使用了两种类型的端口输入input和输出output此外还有一种双向端口也可以使用----inout。 inout read_enable;     // 名为 read_enable 的端口是双向的  向量vector 信号示例         应该如何定义向量信号由多于一位的信号组成的序列呢Verilog 提供了一种简单的方法。 inout [7:0] address;    //端口“address”是双向的          请注意 [7:0] 意味着我们使用的是little-endian低字节序约定----从最右边的 0 位开始依次向左递增。         如果我们使用的是 [0:7]意味着我们使用的是big-endian高字节序约定----从最左边的 0 位开始依次向右递增。         Endianness 字节序是一种决定数据“读取”方式的排序方式不同的系统之间确实存在差异因此始终使用正确的 endianness 很重要。作为类比想想一些从左到右高字节序书写的语言英语与从右到左低字节序书写的其他语言阿拉伯语。了解语言的书写方向对于能够阅读它至关重要但方向本身是在几年前任意设置的。  概括 我们了解了如何在 Verilog 中定义块/模块block/module我们学习了如何定义端口和端口方向ports and port directions我们学习了如何声明向量/标量端口vector/scalar 数据类型         数据类型与硬件有什么关系实际上没什么关系。人们只是想再写一种包含数据类型的语言。但是等等……硬件确实有两种驱动Drivers。驱动那是什么         驱动是一种可以驱动负载的数据类型。在物理电路中驱动器基本上可以是电子可以穿过/进入的任何东西。 可以存储数值的驱动例如触发器flip-flop不能存储值但能连接两点的驱动例如线wire。         第一种类型的驱动在 Verilog 中称为 reg“register”的缩写。第二种数据类型称为线wire。此外还有很多其他的数据类型例如寄存器可以是有符号的、无符号的、浮点数……作为新手你现在暂时不要管这些。  示例 wire and_gate_output // and_gate_output 是只输出的线
reg d_flip_flop_output// d_flip_flop_output 是一个寄存器它存储并输出数值
reg [7:0] address_bus// address_bus 是一个低字节序的 8 位寄存器  概括 Wire 数据类型用于连接两点Reg 数据类型用于存储数值 运算符         值得庆幸的是verilog中的运算符与其他编程语言中的相同。他们取两个值并比较或其他的运算方式它们以产生第三个结果----常见的例子是加法、等于、逻辑与……为了让我们的生活更轻松几乎所有的运算符至少下面列表中的那些与它们在 C 语言中的对应部分完全相同。  类型  符号  功能  算术运算符  *  乘法    /  除法      加法    -  减法    %  取余      一元加法Unary plus    -  一元减法Unary minus  逻辑运算符  !  逻辑非      逻辑与    ||  逻辑或  关系运算符    大于      小于      大于等于      小于等于  相等运算符    等于    !  不等于  归约操作符  ~  按位取反    ~  与非    |  或    ~|  活飞    ^  异或    ^~  同或    ~^  同或  移位运算符    向右移位      向左移位  拼接运算符  { }  拼接  条件运算符  ?  条件  示例 a  b  c ;     // 这很容易
a  1  5;     // 嗯让我想想好吧将 1 左移 5 个位置。
a  !b ;        // 它会反转 b 吗
a  ~b ;        // 你还想给a赋值多少次这可能会导致多驱动multiple-drivers问题。  控制语句         等等这是什么if, else, repeat, while, for, case----Verilog 看起来和 C 语言一模一样或者其他你会使用的编程语言尽管功能上verilog看起来与 C语言相同但 Verilog 是一种 HDL因此描述应被转化为硬件。这意味着您在使用控制语句时必须小心否则您的设计可能无法在硬件中实现。  If-else语句 If-else 语句通过检查条件来决定执行哪部分代码。如果满足条件则执行该条件对应的代码。否则它会运行代码的其他部分。 if (enable  1b1) begindata  10;             //10进制赋值address  16hDEAD;     //16进制赋值wr_enable  1b1;     // 2进制赋值
end 
else begindata  32b0;wr_enable  1b0;address  address  1;  
end          你可以在条件检查中使用任何运算符就像 C 语言的用法一样。如果需要我们也可以嵌套 if-else 语句。没有 else 的语句也是合法的但是它们可能会有其他问题----在实现组合逻辑时容易产生锁存器Latch。  Case语句         当我们有一个变量需要检查多个值时可以使用 Case 语句。就像地址解码器一样输入是一个地址且需要检查它的所有可能值。我们没有使用多个嵌套的 if-else 语句一个对应于我们要查找的单个值而是使用单个 case 语句----这类似于 C/C 等语言中的 Switch 语句。         Case 语句以case开头以endcase结束。在这两个分隔符之间列出你希望执行的所有语句后跟冒号。         写default语句是一个好主意就像有限状态机 (FSM) 一样如果进入了非定义状态状态机就被挂起“死机”。带有返回功能的default语句可以保证我们的安全。 case(address)0 : $display (It is 11:40PM);1 : $display (I am feeling sleepy);2 : $display (Let me skip this tutorial);default : $display  (Need to complete);
endcase  注意 if-else 和 case 语句有一个共同点----如果您没有涵盖到所有可能的情况If-else 中没有“else”或 Case 中没有“default”并且你正在写一个组合逻辑语句的话那么综合工具就会推断出锁存器Latch。  While语句         如果它检查的条件为真那么 while 语句就会重复执行其中的代码。While循环通常不用于现实电路中的模型一般都用于测试脚本testbench。与其他语句块一样它们由begin和end分隔。 while (free_time) begin$display (Continue with webpage development);
end          只要 free_time 变量为真就会重复执行 begin 和 end 内的代码----即打印“Continue with webpage development”。         让我们来看一个更奇怪的例子它使用了大部分的 Verilog 结构。 module counter (clk,rst,enable,count);
input clk, rst, enable;
output [3:0] count;
reg [3:0] count;always  (posedge clk or posedge rst)if (rst) begincount  0;end else begin : COUNTwhile (enable) begincount  count  1;disable COUNT;end
endendmodule          您会注意到一个名为 always 的新块----这说明了 Verilog 的一个关键特性。正如我们之前提到的大多数软件语言都是按顺序执行的。相反Verilog 程序通常有许多并行执行的语句。当满足其中列出的一个或多个条件时所有标记为always 的块都将同时运行。         在上面的示例中always 块将在 rst 或 clk 达到上升沿时运行----当它们的值从 0 上升到 1 时。您可以在程序中同时运行两个或多个always块此处未显示但常用。         我们可以通过无效化保留字的方式来无效化代码块。在上面的示例中每次计数器递增后COUNT 代码块此处未显示都会被无效化。  For语句         Verilog 中的 for 循环几乎与 C/C 中的 for 循环完全相同。唯一的区别是 Verilog 不支持  和 -- 运算符所以和在 C 中那样编写 i 不同你需要写出完整的 i  i  1。  for (i  0; i  16; i  i 1) begin$display (Current value of i is %d, i);
end         此代码将按顺序打印从 0 到 15 的数字。将 for 循环用于RTL时要小心需要确保你的代码实际上可以在硬件中正常实现……并且循环不是无限的。  Repeat语句         repeat 语句类似于我们刚刚介绍的 for 循环。与我们声明 for 循环时显式地指定一个变量并递增它不同repeat语句是告诉程序应该运行代码多少次且没有变量递增除非我们希望它们如此就像在这个例子中。 repeat (16) begin$display (Current value of i is %d, i);i  i  1;
end          输出与前面的 for 循环程序示例完全相同。在实际硬件实现中我们一般很少使用repeat语句或 for 循环。  概括 while、if-else、case(switch)语句和C语言中的一样If-else 和 case 语句要求涵盖组合逻辑的所有情况For 循环与 C 中的相同但没有  和 -- 运算符Repeat 与 for 循环相同但没有递增变量 变量赋值         在数字电路中有两种类型的电路组合逻辑电路和时序逻辑电路。我们当然知道这一点但问题是“我们应该如何在 Verilog 中对此建模”。Verilog 提供了两种对组合逻辑进行建模的方法和一种对时序逻辑进行建模的方法。 可以使用 assign 和 always 语句对组合逻辑电路建模只能使用 always 语句对时序逻辑电路建模还有第三个块仅在testbench中实际上也可以在RTL中进行初始化但不常见使用称为Initial语句。 Initial块         顾名思义Initial块出师快仅在仿真开始时执行一次。这在编写testbench时很有用。如果我们有多个Initial块那么它们都会在仿真开始时执行。  示例 initial beginclk  0;reset  0;req_0  0;req_1  0;
end          在上面的示例中在仿真开始时即当仿真时间为0 时begin-end内的所有变量都会被赋值为零。 Always块         顾名思义always 块总是在执行这与仅执行一次在仿真开始时的Initial块不同。第二个区别是 always 块应该有一个敏感列表或与之关联的延迟。         敏感列表告诉 always 块何时执行代码块如下图所示。保留字alway后的符号表示代码块将在符号后括号中的条件“触发”。         关于 always 块的一个重要说明它不能驱动 wire 数据类型但可以驱动 reg 和 integer 数据类型。 always   (a or b or sel)beginy  0;if (sel  0) beginy  a;end else beginy  b;end
end          上面的例子是一个 2选1的多路选择器 a 和 b是输入sel 是选择y 是输出。         在任何组合逻辑电路中只要输入发生变化输出就会立即发生变化。当应用于 always 块时该理论意味着只要输入变量或输出控制变量发生变化就需要执行 always 块中的代码。这些变量是包含在敏感列表中的变量即 a、b 和 sel。         有两种类型的敏感列表电平敏感用于组合电路和边沿敏感用于触发器。下面的代码是相同的 2:1 Mux但输出 y 现在是触发器输出。 always   (posedge clk )beginif (reset  0) beginy  0;end else if (sel  0) beginy  a;end else beginy  b;end
end              通常情况下我们不得不复位触发器因此每次时钟从 0 转换到 1 (posedge) 时都要检查复位是否有效同步复位然后继续执行正常逻辑。         如果我们仔细观察就会发现在组合逻辑的情况下我们使用了“”进行赋值而对于时序逻辑我们则使用了“”运算符。“”是阻塞赋值“”是非阻塞赋值。“”在begin-end内顺序执行代码而非阻塞“”则并行执行代码。         我们可以写一个没有敏感列表的 always 块在这种情况下需要有一个延迟如下面的代码所示。 always  begin#5  clk  ~clk;
end          语句前面的 #5 将其延迟 5 个时间单位。  Assign块         assign 语句仅用于建模组合逻辑并且会连续执行。所以赋值语句被称为“连续赋值语句continuous assignment statement”因为其没有敏感列表。   assign out  (enable) ? data : 1bz;          上面的例子是一个三态buffer。当使能为 1 时数据被驱动到 out否则 out 被拉到高阻抗。我们可以使用嵌套的条件运算符来构造多路选择器、解码器和编码器。    assign out  data;          这个例子是一个简单的buffer。  任务和函数         当一次又一次重复相同的旧事物时Verilog 与任何其他编程语言一样提供了解决重复使用代码的方法----任务和函数。         下面的代码可用于计算奇偶校验。 function parity;input [31:0] data;integer i;beginparity  0;for (i 0; i  32; i  i  1) beginparity  parity ^ data[i];endend
endfunction          函数和任务具有相同的语法一个区别是任务可以有时间延迟但函数不能。这意味着函数可用于建模组合逻辑。第二个区别是函数可以返回一个值而任务不能。 Testbench测试脚本         好的我们已经根据设计文档编写了代码接着呢         我们需要对其进行测试看看它是否符合规格。大多数时候这与我们在大学时代在数字实验室中所做的相同----驱动输入将输出与预期值相匹配。         这是仲裁器的RTL代码 module arbiter (
clock, 
reset, 
req_0,
req_1, 
gnt_0,
gnt_1
);input clock, reset, req_0, req_1;
output gnt_0, gnt_1;reg gnt_0, gnt_1;always  (posedge clock or posedge reset)
if (reset) begingnt_0  0;gnt_1  0;
end else if (req_0) begingnt_0  1;gnt_1  0;
end else if (req_1) begingnt_0  0;gnt_1  1;
endendmodule          这是仲裁器的Testbench module arbiter_tb;reg clock, reset, req0,req1;
wire gnt0,gnt1;initial begin$monitor (req0%b,req1%b,gnt0%b,gnt1%b, req0,req1,gnt0,gnt1);clock  0;reset  0;req0  0;req1  0;#5  reset  1;#15  reset  0;#10  req0  1;#10  req0  0;#10  req1  1;#10  req1  0;#10  {req0,req1}  2b11;#10  {req0,req1}  2b00;#10  $finish;
endalways begin#5  clock   ! clock;
endarbiter U0 (
.clock (clock),
.reset (reset),
.req_0 (req0),
.req_1 (req1),
.gnt_0 (gnt0),
.gnt_1 (gnt1)
);endmodule          看起来我们已经将所有仲裁器的输入声明为 reg将输出声明为 wire我们这样做是因为测试平台需要驱动输入并需要监控输出。         在声明了所有需要的变量之后我们将所有输入初始化为已知状态----这是在initial块中实现的。初始化后我们按照要测试仲裁器的顺序断言/取消断言复位、req0、req1。时钟是用 always 块生成的。完成测试后我们需要停止仿真----使用了 $finish 来终止仿真。$monitor 则用于监空信号列表的变化并以我们想要的格式打印出来。         这是仿真运行后的结果 req00,req10,gnt0x,gnt1x req00,req10,gnt00,gnt10 req01,req10,gnt00,gnt10 req01,req10,gnt01,gnt10 req00,req10,gnt01,gnt10 req00,req11,gnt01,gnt10 req00,req11,gnt00,gnt11 req00,req10,gnt00,gnt11 req01,req11,gnt00,gnt11 req01,req11,gnt01,gnt10 req00,req10,gnt01,gnt10 您有任何问题都可以在评论区和我交流本文由 孤独的单刀 原创首发于CSDN平台博客主页wuzhikai.blog.csdn.net您的支持是我持续创作的最大动力如果本文对您有帮助还请多多点赞、评论和收藏⭐