前言
学习OPNET也快一个多月了,就这一段时间的OPNET学习进行一下总结,主要整理了一些在仿真过程中比较重要的概念和知识点。
OPNET关键概念
1. Opnet 事件表与仿真核心
事件是在一个特定时刻的一个特定动作的请求。OPNET仿真由事件驱动,在仿真中,仿真时钟仅在处理时间表头事件的时间更晚时随之变化。
事件表
- OPNET仿真维护一个全局事件表。
- 所有对象共享同一个仿真时钟。
- 事件依照时间先后顺序排列,第一个事件位于表头。
- 事件完成后,从表中删除。
仿真核心
- 仿真核心(Simulation Kernel,简称SK)是管理事件表的实体。
- SK向对应的模块依次递交事件。
- SK接收来自模块的请求,并将新事件插入事件表。
并发事件的处理
仿真核心用两种方法确定事件先后次序:
- 先预约先服务,也即按照事件推入事件表的顺序来判断先处理谁
- 优先级
- 模块和事件被设置优先级
- 高优先级的先服务
注意:事件仍然顺序处理,尽管看起来是在同一个时刻发生的。
2. opnet非强制性状态
非强制性状态与强制性状态的差别就在于多了对中断的等待,注意它的执行顺序是,入口代码->等待中断->中断到达->出口代码->执行转移条件的判断,初学的时候,尤其对于如下图的状态转换,可能会受转换线箭头的影响,对这样一个执行顺序掌握的不太明了。(我就是这样(@_@;))
3. opnet变量类型
-
全局变量
在HB区域定义,作用范围是整个项目,同一项目中的不同进程中的HB定义的变量不可重复初始化。如要使用已经定义好的全局变量,在变量声明前加上extern。 -
状态变量
在状态变量区定义,作用范围是整个进程,它的值在进程范围内保留,当模块将程序控制权交还给仿真核心时,状态变量的值受保护,状态变量为每个进程保持个性化提供机制。 -
临时变量
在临时变量区定义,它的值在两次中断触发之间不保留,也就是说,当模块将程序控制权交还给仿真核心时,临时变量的值不受保护。定义变量:
HB:头区域,定义常量,头文件,宏,全局变量,数据结构,数据类型和函数声明。
SV:状态变量区,定义状态变量,状态变量在不同的进程切换之间,保持值不变
TV:状态变量区,定义临时变量,临时变量在不同的进程切换之间,无法保持值不变。
FB:函数区域,定义和进程相关函数
DB:诊断区域,定义C/C++语句,将诊断信息输出到标准出设备中。
TB:终止区域,定义C/C++语句,这些语句将在进程销毁时执行,一般为释放内存等语句。
4. opnet中断类型
中断在opnet仿真中非常重要,OPNET是事件驱动型仿真,因此要有相应的事件产生仿真才能推进,预设中断就是产生事件的一种方式。下面归纳了一下opnet中的中断类型(不全):
-
仿真核心中断
begsim intrpt、endsim intrpt、failure intrpt、regular intrpt等等
begsim intrpt影响init状态的enter代码何时执行,如果是enabled,那么仿真一开始,即仿真0时刻,可以对process进行初始化,process被触发后,即执行init的enter代码。endsim intrpt如果仿真结束时,需要进行一些工作(如变量的收集,内存的释放等),则需要enable endsim intrpt。regular intrpt可用来做定时器,设定好intrpt interval之后,仿真核心每隔该时间量触发一次regular中断。 -
状态中断
stat_intrpt函数可以用来提取统计信息作为反馈控制变量,将该信息反馈回模型中进行控制。
对statictic wire可以这样理解:statistic wire将A进程的一个变量反映给B进程,该变量一般由op_stat_write()改变值,在B进程中:(1)检测到OPC_INTRPT_STAT中断以后,由op_stat_local_read()读入。(2)通过op_stat_local_read()查询stat值,一般是在收到OPC_INTRPT_STAT中断时去查询。而进行stat触发,当intrpt method选择为forced方式,将直接进行触发。当intrpt method为schedule方式时,有多种触发方式,比如rising edge、failing edge trigger、repeated value trigger等,按照触发方式在接收端引发OPC_INTRPT_STAT。 -
流中断
数据包流中断,op_intrpt_strm()返回接收的流索引号(stream index)。
-
自中断
代码:op_intrpt_schedule_self (op_sim_time () + dest_time, 0);
为了方便介绍,改成这样:op_intrpt_schedule_self (a, b);
a为时间,是double变量,表示我要在什么时间执行这个中断。其中op_sim_time()代表当前仿真时间,常在这出现,以方便我们能够决定自中断在这之后多久执行。b表示自中断的序号。比如你写了多个自中断,如何区分这些自中断,这样就需要给不同的自中断编号。具体使用如下:
opnet无线链路管道阶段
OPENT无线仿真中用14个首尾相接的管道阶段(pipeline stage)来尽量接近真实的模仿数据帧在信道中的传输。首先在整个传输过程还没有进行之前,把肯定不能被接收的物件圈定出来;在计算传输延时后接着复制封包,对每一个接收主询中的物件都复制一份;然后计算接收闭锁,检查信道是否完全吻合,如果完全匹配当作有效信号,如果部分匹配当作噪声处理;对于接收器来说,在经历传播延时后,内部产生一个中断,对每一个可能接受的信道,进行6-13阶段,由于包的每段有可能存在不同程度的干扰,因此对每一段都需要单独计算,如果是有效包则计算误码率,如果是噪声则考虑对有效包的影响;之后得到包的总误码数多少,最后决定是否丢包。
整个过程中计算数据保存在包的TDA(transmission Data Attribute)里,TDA预设了一些值,如某个发信机和发信机信道的Objid等。这些值一共有OPC_TDA_RA_MAX_INDEX,它是opnet定义的象征性名字,代表TDA属性的最大索引号,如果需要自定义TDA属性,则需要将新属性定义为OPC_TDA_RA_MAX_INDEX加1。
这里重点提一下接收主询阶段
该阶段用来确定候选的收信机对象,排除明显不符合的对象。移除不合要求的接收信道有利于减少包的复制和后续仿真阶段的运行,从而提高仿真效率。
TD类核心函数可以用来改变默认的收信机组属性,以及针对仿真时间动态的改变和重新计算收信机组。在help文档中的Programmers Reference中Transmission Data Package中可以查到相关的函数。而Radio Package函数包中可以查询到无线仿真相关函数的详细信息。应用举例:
将接收主询设置为单个接收信道: op_radio_txch_rxgroup_set(txch_objid,1,rxgroup);
将不必要的接收信道排除: op_radio_txch_rxch_remove(txch_objid,rxch_objid);
如何设置定向天线,并使天线指向某一结点,以及如何将自己从接收结点中排除掉,可以参考这篇博客——opnet无线管道模型
对于其他某些管道阶段,这篇也有一定参考意义。
个人觉得, 如果要进行无线仿真,有必要找一本opnet的教材仔细研究一下14个无线管道阶段,同时结合官方文档了解具体的函数使用方法。
后记
opnet仿真的内容很多,目前只是学了皮毛,不过有一个很明显的心得就是,清晰的思路和思维的全面特别重要。在建模过程中,要用什么函数以及如何使用这些函数等问题,基本上都可以通过搜索引擎,查找官方文档或他人经验分享得到解决。随着对opnet的逐渐熟练,“工具”的问题越来越不成为问题。关键是你要实现一个仿真目标,如何构建合适的进程模型,这个就需要你建立全面的状态,设置好状态和状态之间的逻辑关系,考虑到各种可能出现的事件以及事件发生的时间,有些微的差错就可能得不到理想的仿真结果,只能通过多多练习和思考来逐渐摸索opnet进程建模的好方法了。