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

2020-06-12 16:26:47 浏览数 (2)

Part 1. 命令模式代码示例

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

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

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

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

代码语言:javascript复制
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;
    }
}
代码语言:javascript复制

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

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

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

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

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

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

代码语言:javascript复制
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;
}
代码语言:javascript复制

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

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

Part 2. UVM Sequence

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

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

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

Part3. 总结

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

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

0 人点赞