Part 1. 命令模式代码示例



UVM方法学与设计模式_5:命令模式 & UVM Sequence


假设有如上一段第一代版本的代码,run函数用于执行对receiver模块的一系列操作/命令。


随着项目的进展,V1需要做一些改变,新增了命令得到V2的代码版本,如下所示:



UVM方法学与设计模式_5:命令模式 & UVM Sequence

随着操作和命令的继续增多,我们需要不断对代码进行修 改:


UVM方法学与设计模式_5:命令模式 & UVM Sequence

更加糟糕的是,如果我们希望对命令进行更精细的控制,比如我们希望有相关的使能信号可以开关相关的命令,则代码将会演化成如下的糟糕状态:

class Invoker{public: void run(Receiver *receiver, bool IsCrcEnable, bool IsYuvEnable, bool IsNewCmd0Enable, ..., bool IsNewCmdNEnable) { receiver->TurnOn; if (IsCrcEnable) { receiver->CrcOn; } if (IsYuvEnable) { receiver->YuvOn; } if (IsNewCmd0Enable) { receiver->NewCmd0On; } if (IsNewCmd1Enable) { receiver->NewCmd1On; } if (IsNewCmd2Enable) { receiver->NewCmd2On; } . . . if (IsNewCmdNEnable) { receiver->NewCmdNOn; }
...
if (IsNewCmdNEnable) { receiver->NewCmdNOff; } . . .
if (IsNewCmd2Enable) { receiver->NewCmd2Off; } if (IsNewCmd1Enable) { receiver->NewCmd1Off; } if (IsNewCmd0Enable) { receiver->NewCmd0Off; } if (IsYuvEnable) { receiver->YuvOff; } if (IsCrcEnable) { receiver->CrcOff; } receiver->TurnOff; }}


更不要说,当我们需要对这些操作/命令进行排队,制定优先级和取消操作了。可想而知我们的代码将会变得十分冗长,并且难以阅读和维护(阅读和维护代码往往占用了我们80%的编程时间)。

命令模式可以非常好的解决这个问题。让我们来试着发现这个模式。


上述代码最大的问题是什么呢?run函数知道的太多了(正如我们在之前几篇文章中所说的一样:))!


事实上run函数只需要进行执行操作,并不需要将具体执行的具体操作hard coding进我们的函数体内。


到这里,仔细的同学估计已经想到了,套用之前几篇文章的套路,还是使用多态来进行代码改造(所以说多态才是OOP的根本,只不过C++和SV的多态都借用了继承的方式而已)。


我们将每一个命令都封装一个个具体的对象,那么就非常容易对这些命令进行使能操作。具体可见如下代码(不要在意具体的细节,这里只是示意代码):

class Command{public: Command(Receiver *pReceiver): m_pReceiver(pReceiver) {} virtual void excute() = 0;protected: Receiver *m_pReceiver;};
class CrcOnCmd: public Command{public: CrcOnCmd(Receiver *pReceiver): Command(pReceiver) {} virtual void excute(){ m_pReceiver->CrcOn(); }};
class CrcOffCmd: public Command{public: CrcOffCmd(Receiver *pReceiver): Command(pReceiver) {} virtual void excute(){ m_pReceiver->CrcOff(); }};
class TurnOnCmd: public Command{public: TurnOnCmd(Receiver *pReceiver): Command(pReceiver) {} virtual void excute(){ m_pReceiver->TurnOn(); }}
class TurnOffCmd: public Command{public: TurnOffCmd(Receiver *pReceiver): Command(pReceiver) {} virtual void excute(){ m_pReceiver->TurnOff(); }}
class YuvOnCmd: public Command{public: YuvOnCmd(Receiver *pReceiver): Command(pReceiver) {} virtual void excute(){ m_pReceiver->YuvOn(); }}
class YuvOffCmd: public Command{public: YuvOffCmd(Receiver *pReceiver): Command(pReceiver) {} virtual void excute(){ m_pReceiver->YuvOff(); }}
class Receiver{public: virtual void TurnOn() {} virtual void TurnOff() {} virtual void CrcOn() {} virtual void CrcOff() {} virtual void YuvOn() {} virtual void YuvOff() {}}
class Invoker{public: void AddCmd(Command *cmd){ vector.push_back(cmd); } void RemoveCmd(Command *cmd){ auto it = find(m_CommandVector.begin(), m_CommandVector.end(), cmd); if (it != m_CommandVector.end()) m_CommandVector.erase(it); } void Run(){ for (auto &it : m_CommandVector) it->excute(); }private: std::vector<Command*> m_CommandVector;}
int main(){ Invoker *invoker = new invoker(); Receiver *receiver = new Receiver(); CrcOnCmd *crconcmd = new CrcOnCmd(receiver); CrcOffCmd *crcoffcmd = new CrcOffCmd(receiver); TurnOnCmd *turnoncmd = new TurnOnCmd(receiver); TurnOffCmd *turnoffcmd = new TurnOffCmd(receiver); YuvOnCmd *yuvoncmd = new YuvOnCmd(receiver); YuvOffCmd *yuvoncmd = new YuvOffCmd(receiver);
invoker->AddCmd(crconcmd); invoker->AddCmd(yuvoncmd); invoker->AddCmd(turnoncmd);
invoker->Run();
return 0;}


通过使用AddCmd和RemoveCmd方法我们就可以非常轻松的对命令进行增加和删除操作(示例代码中是逐一调用AddCmd方法,有没有更好的方法呢?)。


并且我们可以在此基础上非常方便的加入优先级功能,确保某些命令优先执行,只要对不同的命令赋予不同的优先级值即可,Run方法可以根据不同的优先级值对命令进行排序执行操作,对应的代码也不难实现。


Part 2. UVM Sequence





本质上发给DUT的激励就可以看成是一个个具体的命令,在UVM中,这些激励被称为transaction。


transaction往往包装在sequence中进行后续的发射操作,UVM中可以对sequence及其中的transaction进行管理,其中包括设置优先级(uvm_do_pri)、同步操作等。


试想,如果不是采用对象的方式对transaction和sequence进行管理,想要实现类似的功能框架代码和具体的业务代码其耦合性得多强。


Part3. 总结

命令模式定义:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。


授权转载于 知乎专栏《UVM方法学与设计模式


往期精彩