ASIC-WORLD Verilog(11)过程时序控制

2023-07-31 09:14:22

写在前面

        在自己准备写一些简单的verilog教程之前,参考了许多资料----Asic-World网站的这套verilog教程即是其一。这套教程写得极好,奈何没有中文,在下只好斗胆翻译过来(加了自己的理解)分享给大家。

        这是网站原文:Verilog Tutorial

        这是系列导航:Verilog教程系列文章导航


过程块和时序控制(Procedural blocks and timing controls

  • 延时控制(Delay controls)
  • 边沿敏感的事件控制(Edge-Sensitive Event controls)
  • 电平敏感的事件控制(Level-Sensitive Event controls-Wait statements)
  • 特定事件控制(Named Events)

延时控制

        通过指定特定的仿真时间来达到延时的目的,一般语法是这样的:

#< time > < statement >;

        比如2个仿真时间单位后给复位信号赋值1;5个仿真时间单位后在给复位信号赋值0:

#2 reset = 1; //2个时间单位后赋值为1

#5 reset = 0; //5个时间单位后赋值为0

        下面是一个完整的例子,用来模拟一个复位,并通过 $monitor 来监控各个寄存器的值:

module clk_gen ();

reg clk, reset; 

initial begin
  $monitor ("TIME = %g RESET = %b CLOCK = %b", $time, reset, clk); //监控各个寄存器的值
  clk = 0; 
  reset = 0; 
  #2  reset = 1;  //2个单位后复位赋值为1
  #5  reset = 0;  //5个单位后复位赋值为0
  #10  $finish;
end 

always #1  clk =  ! clk; //每一个时间单位翻转一次时钟,即生成时钟信号,周期为2个时间单位

endmodule

        这是窗口的仿真结果:

 TIME = 0  RESET = 0 CLOCK = 0
 TIME = 1  RESET = 0 CLOCK = 1
 TIME = 2  RESET = 1 CLOCK = 0
 TIME = 3  RESET = 1 CLOCK = 1
 TIME = 4  RESET = 1 CLOCK = 0
 TIME = 5  RESET = 1 CLOCK = 1
 TIME = 6  RESET = 1 CLOCK = 0
 TIME = 7  RESET = 0 CLOCK = 1
 TIME = 8  RESET = 0 CLOCK = 0
 TIME = 9  RESET = 0 CLOCK = 1
 TIME = 10 RESET = 0 CLOCK = 0
 TIME = 11 RESET = 0 CLOCK = 1
 TIME = 12 RESET = 0 CLOCK = 0
 TIME = 13 RESET = 0 CLOCK = 1
 TIME = 14 RESET = 0 CLOCK = 0
 TIME = 15 RESET = 0 CLOCK = 1
 TIME = 16 RESET = 0 CLOCK = 0

        这是仿真结果的波形图:


边沿敏感的事件控制

        通过指定特定事件的边沿变化来控制时间(语句)的执行。一般语法是这样的:

@ (< posedge >|< negedge > signal) < statement >;

        通过时钟信号的 上升沿/下降沿 来控制某个事件的执行就是很经典的边沿敏感型事件控制语句。比如在enable信号的上升沿后的5个时钟单位后触发trigger信号为1:

always @ (posedge enable)begin 
   trigger = 0;
   repeat (5) begin    //重复5次
     @ (posedge clk) ; //在上升沿被触发
  end
   trigger = 1;         //触发其值为1
end

        这个代码可以拓展一下,并加上相应的测试脚本:


module edge_wait_example();

reg enable, clk, trigger;

//在每个enable上升沿的5个时钟后把trigger赋值为1
always @ (posedge enable)	
begin 
  trigger = 0;
  repeat (5) begin
    @ (posedge clk) ;
  end
  trigger = 1; 
end


initial begin
  $monitor ("TIME : %g CLK : %b ENABLE : %b TRIGGER : %b",
    $time, clk,enable,trigger);
  clk = 0;
  enable = 0;
  //通过延时语句分别对enable赋值
   #5   enable = 1;
   #1   enable = 0;
   #10  enable = 1;
   #1   enable = 0;
   #10  $finish;
end

always #1  clk = ~clk;

endmodule

        这是仿真结果:

 TIME : 0 CLK : 0 ENABLE : 0 TRIGGER : x
 TIME : 1 CLK : 1 ENABLE : 0 TRIGGER : x
 TIME : 2 CLK : 0 ENABLE : 0 TRIGGER : x
 TIME : 3 CLK : 1 ENABLE : 0 TRIGGER : x
 TIME : 4 CLK : 0 ENABLE : 0 TRIGGER : x
 TIME : 5 CLK : 1 ENABLE : 1 TRIGGER : 0
 TIME : 6 CLK : 0 ENABLE : 0 TRIGGER : 0
 TIME : 7 CLK : 1 ENABLE : 0 TRIGGER : 0
 TIME : 8 CLK : 0 ENABLE : 0 TRIGGER : 0
 TIME : 9 CLK : 1 ENABLE : 0 TRIGGER : 0
 TIME : 10 CLK : 0 ENABLE : 0 TRIGGER : 0
 TIME : 11 CLK : 1 ENABLE : 0 TRIGGER : 0
 TIME : 12 CLK : 0 ENABLE : 0 TRIGGER : 0
 TIME : 13 CLK : 1 ENABLE : 0 TRIGGER : 0
 TIME : 14 CLK : 0 ENABLE : 0 TRIGGER : 0
 TIME : 15 CLK : 1 ENABLE : 0 TRIGGER : 1
 TIME : 16 CLK : 0 ENABLE : 1 TRIGGER : 0
 TIME : 17 CLK : 1 ENABLE : 0 TRIGGER : 0
 TIME : 18 CLK : 0 ENABLE : 0 TRIGGER : 0
 TIME : 19 CLK : 1 ENABLE : 0 TRIGGER : 0
 TIME : 20 CLK : 0 ENABLE : 0 TRIGGER : 0
 TIME : 21 CLK : 1 ENABLE : 0 TRIGGER : 0
 TIME : 22 CLK : 0 ENABLE : 0 TRIGGER : 0
 TIME : 23 CLK : 1 ENABLE : 0 TRIGGER : 0
 TIME : 24 CLK : 0 ENABLE : 0 TRIGGER : 0
 TIME : 25 CLK : 1 ENABLE : 0 TRIGGER : 1
 TIME : 26 CLK : 0 ENABLE : 0 TRIGGER : 1

电平敏感的事件控制

        当前条件为真时才执行接下来的语句,有点类似if语句。它的一般语法是这样的:

wait (< expression >) < statement >; 

        比如当data_ready为真时,才把data_bus的值赋给data:

wait (data_ready == 1)  data = data_bus; 

        这个代码可以拓展一下,并加上相应的测试脚本:

module wait_example();

reg mem_read, data_ready;
reg [7:0] data_bus, data;

always @ (mem_read or data_bus or data_ready) begin
  data = 0;
  while (mem_read == 1'b1) begin
    wait (data_ready == 1) #1 data = data_bus;
  end
end

// Testbench Code here
initial begin
 $monitor ("TIME = %g READ = %b READY = %b DATA = %b", 
   $time, mem_read, data_ready, data);
 data_bus = 0;
 mem_read = 0;
 data_ready = 0;
 #10 data_bus = 8'hDE;
 #10 mem_read = 1;
 #20 data_ready = 1;
 #1  mem_read = 1;
 #1  data_ready = 0;
 #10 data_bus = 8'hAD;
 #10 mem_read = 1;
 #20 data_ready = 1;
 #1  mem_read = 1;
 #1  data_ready = 0;
 #10 $finish;
end

endmodule

        这是仿真结果:

 TIME = 0  READ = 0 READY = 0 DATA = 00000000
 TIME = 20 READ = 1 READY = 0 DATA = 00000000
 TIME = 40 READ = 1 READY = 1 DATA = 00000000
 TIME = 41 READ = 1 READY = 1 DATA = 11011110
 TIME = 42 READ = 1 READY = 0 DATA = 11011110
 TIME = 82 READ = 1 READY = 1 DATA = 11011110
 TIME = 83 READ = 1 READY = 1 DATA = 10101101
 TIME = 84 READ = 1 READY = 0 DATA = 10101101 


赋值内延迟语句(Intra-Assignment Timing Controls) 

        这是相对于 赋值间延迟语句(Inter-Assignment Timing Controls) 的概念,赋值间延迟语句就是我们平常最常用的延迟语句,也就是这种:

#10 rega = regb;

        这种情况下,赋值语句需要等待一定时间,然后将计算结果(右侧值)赋值给目标信号(左侧值)。 

        而赋值内延迟语句的用法则是这样的:

rega = #10 regb;

        它是先计算出右侧值,延时完成后再将结果赋给左侧。看看下面的例子:

  1 module intra_assign();
  2 
  3 reg a, b;
  4 
  5 initial begin
  6   $monitor("TIME = %g  A = %b  B = %b",$time, a , b);
  7   a = 1; 
  8   b = 0; 
  9   a = #10 0; 
 10   b = a;
 11    #20  $display("TIME = %g  A = %b  B = %b",$time, a , b);
 12   $finish;
 13 end 
 14 
 15 endmodule

        这是仿真结果:

 TIME = 0   A = 1  B = 0
 TIME = 10  A = 0  B = 0
 TIME = 30  A = 0  B = 0 


使用连续赋值语句对组合逻辑建模

        组合逻辑就是无论右侧的结果何时发生了变化,左侧的值都会同样立即发生改变。

例1 三态缓冲器

module tri_buf_using_assign();
reg data_in, enable;
wire pad;

assign pad = (enable) ? data_in : 1'bz;

initial begin
  $monitor ("TIME = %g ENABLE = %b DATA : %b PAD %b", 
    $time, enable, data_in, pad);
  #1 enable = 0;
  #1 data_in = 1;
  #1 enable = 1;
  #1 data_in = 0;
  #1 enable = 0;
  #1 $finish;
end

endmodule

        这个三态缓冲器也是经典的控制I2C、1-Wire等总线的一种方法。当enable为1时,就往总线上输出数据;当enable为0时,此时总线为高组态,就可以从总线上读取数据了。

        仿真结果:

 TIME = 0 ENABLE = x DATA : x PAD x
 TIME = 1 ENABLE = 0 DATA : x PAD z
 TIME = 2 ENABLE = 0 DATA : 1 PAD z
 TIME = 3 ENABLE = 1 DATA : 1 PAD 1
 TIME = 4 ENABLE = 1 DATA : 0 PAD 0
 TIME = 5 ENABLE = 0 DATA : 0 PAD z

例2 多路选择器

        同样的,这样还可以实现多路选择器:

module mux_using_assign();
reg data_in_0, data_in_1;
wire data_out;
reg  sel;

assign data_out = (sel) ? data_in_1 : data_in_0; 

// Testbench code here
initial begin
  $monitor("TIME = %g SEL = %b DATA0 = %b DATA1 = %b OUT = %b",
    $time,sel,data_in_0,data_in_1,data_out);
  data_in_0 = 0;
  data_in_1 = 0;
  sel = 0;
  #10 sel = 1;
  #10 $finish;
end

// Toggel data_in_0 at #1
always #1 data_in_0 = ~data_in_0;

// Toggel data_in_1 at #2
always #2 data_in_1 = ~data_in_1;

endmodule

        仿真结果很简单直观,看看就好:

 TIME = 0 SEL = 0 DATA0 = 0 DATA1 = 0 OUT = 0
 TIME = 1 SEL = 0 DATA0 = 1 DATA1 = 0 OUT = 1
 TIME = 2 SEL = 0 DATA0 = 0 DATA1 = 1 OUT = 0
 TIME = 3 SEL = 0 DATA0 = 1 DATA1 = 1 OUT = 1
 TIME = 4 SEL = 0 DATA0 = 0 DATA1 = 0 OUT = 0
 TIME = 5 SEL = 0 DATA0 = 1 DATA1 = 0 OUT = 1
 TIME = 6 SEL = 0 DATA0 = 0 DATA1 = 1 OUT = 0
 TIME = 7 SEL = 0 DATA0 = 1 DATA1 = 1 OUT = 1
 TIME = 8 SEL = 0 DATA0 = 0 DATA1 = 0 OUT = 0
 TIME = 9 SEL = 0 DATA0 = 1 DATA1 = 0 OUT = 1
 TIME = 10 SEL = 1 DATA0 = 0 DATA1 = 1 OUT = 1
 TIME = 11 SEL = 1 DATA0 = 1 DATA1 = 1 OUT = 1
 TIME = 12 SEL = 1 DATA0 = 0 DATA1 = 0 OUT = 0
 TIME = 13 SEL = 1 DATA0 = 1 DATA1 = 0 OUT = 0
 TIME = 14 SEL = 1 DATA0 = 0 DATA1 = 1 OUT = 1
 TIME = 15 SEL = 1 DATA0 = 1 DATA1 = 1 OUT = 1
 TIME = 16 SEL = 1 DATA0 = 0 DATA1 = 0 OUT = 0
 TIME = 17 SEL = 1 DATA0 = 1 DATA1 = 0 OUT = 0
 TIME = 18 SEL = 1 DATA0 = 0 DATA1 = 1 OUT = 1
 TIME = 19 SEL = 1 DATA0 = 1 DATA1 = 1 OUT = 1 


  •  📣您有任何问题,都可以在评论区和我交流📃!
  • 📣本文由 孤独的单刀 原创,首发于CSDN平台🐵,博客主页:wuzhikai.blog.csdn.net
  • 📣您的支持是我持续创作的最大动力!如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!

更多推荐

设计模式:备忘录模式

目录组件代码示例源码中使用优缺点总结备忘录模式(MementoPattern)是一种行为型设计模式,用于在不破坏封装性的前提下,捕获和恢复对象的内部状态。备忘录模式可以将对象的状态保存到备忘录对象中,并在需要时从备忘录对象中恢复状态,实现对象状态的保存和回滚。组件在备忘录模式中,通常包含以下角色:发起人(Origina

[X3m]ros交叉编译

ros需要安装以下包PYTHON_PACKAGE_LIST="larklark-parsernetifacespyyamlifcfgpyunicodedata"TogetheROS.Bot|TogetheROS.Bot用户手册编译tros.b​1使用docker文件​该部分操作均在开发机的docker内完成。##创建目

数据驱动 vs 关键字驱动:对搭建UI自动化测试框架的探索

UI自动化测试用例剖析让我们先从分析一端自动化测试案例的代码开始我们的旅程。以下是我之前写的一个自动化测试的小Demo。这个Demo基于Selenium与Java。由于现在Selenium在自动化测试的统治地位,并且随着Selenium4的即将发布,在未来很长的一段时间里这种统治地位应该还会持续,所以我的这篇文章还都是

我的创作纪念日

机缘第一次写博客我记得是写了个原生ajax的文章,因为突然用这个确实写不出来我写博客纯属为了记录项目经验有的bug可能这个项目解决了下个项目又噶了哈哈,我觉得跟博友们好好交流一下还是可以的,互相进步收获获得了88粉丝的关注有些文章的阅读量还是很高的,嘿嘿,收获最大的就是在工作中遇到的bug解决的更快了哈哈认识十几个志同

算法分享三个方面学习方法(做题经验,代码编写经验,比赛经验)

目录0.前言:(遇到OI不要慌)(只要道路对了,就不怕遥远)1.做题经验谈1.1做题的目的1.2我对于算法比赛的题目的看法1.2.1类似题1.2.2套模型:1.3在训练过程中如何做题1.4一些建议:提高算法能力1.5一些建议:提高代码能力1.6选一个好的OJ1.7分析问题的方法:我的一些经验2.代码编写经验谈2.1你5

UML活动图

在UML中,活动图本质上就是流程图,它描述系统的活动、判定点和分支等,因此它对开发人员来说是一种重要工具。活动图活动是某件事情正在进行的状态,既可以是现实生活中正在进行的某一项工作,也可以是软件系统中某个类对象的一个操作。活动图和流程图的区别1、流程图着重描述处理过程,他的主要控制结构是顺序、分支和循环,各个处理过程之

java学习--day11(抽象类、接口)

文章目录day10作业今天的内容1.super关键字2.抽象类【重要】2.1abstract关键字3.final关键字4.接口【重要】4.1生活中的接口4.2Java中接口4.3案例4.4案例day10作业1.成员变量和局部变量的区别成员变量:定义在类体中,整个类可以使用局部变量:定义在方法中,只能在方法使用2.继承的

系统架构设计师(第二版)学习笔记----信息安全系统及信息安全技术

【原文链接】系统架构设计师(第二版)学习笔记----信息加解密技术文章目录一、信息安全系统的组成框架1.1信息安全系统组成框架1.2信息安全系统技术内容1.3常用的基础安全设备1.4网络安全技术内容1.5操作系统安全内容1.6操作系统安全机制1.7数据库安全技术1.8信息安全系统的组织体系1.9信息安全系统的管理体系二

SocketTool V4.0 使用说明

TCP/UDPSocket调试工具提供了TCPServer,TCPClient,UDPServer,UDPClient,UDPGroup五种Socket调试方案。下面是一份简要的使用流程:TCP通信测试:1)创建TCPServer选中左方的TCPServer,然后点击”创建”按钮,软件弹出监听端口输入框。输入监听端口后

playwright: 通过Route对象处理请求

Route对象可以通过page.route()或者browser_context.route()来设置路由Route对象的方法有:abort,continue_,fallback,fetch,fulfillabort终止路由请求,并且可以设置error_code,默认是failed,其他值有aborted,access

经管博士科研基础【25】概率论中的相关基础概念

1.Support在概率论中,"support"(支撑集)是指随机变量可能取值的集合。对于离散型随机变量,支撑集包含了所有可能的取值;而对于连续型随机变量,支撑集是指其密度函数或概率质量函数非零的区域。举个例子来说,对于一个离散型随机变量,比如抛硬币的结果(正面或反面),其支撑集就是{正面,反面},因为这两个是唯一可能

热文推荐