1. 策略模式
策略模式是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。
在RTL设计中可能包含了复杂的多个访问仲裁逻辑,使用了多种算法来确定访问内存优先级顺序,包括规定优先级、轮询仲裁等等。仲裁器的输入是多个请求者信号,以及选择要使用的仲裁算法的配置。根据选择的类型和请求者信号的值,仲裁器确定具有最高优先级的请求源,并授予它访问内存的权利。如下图所示,仲裁类型可以动态配置,这就是为什么该特性适合使用策略设计模式进行建模。在该模式中,可以在testcase运行中从提供的一系列算法中选择要应用的特定算法。此外,还可以直接为仲裁添加新算法,而无需修改之前代码。值得注意的是,之前讲到的装饰器设计模式也可用于动态更改行为,关键的区别在于,装饰器模式在原功能基础上添加额外的功能,而策略者模式直接更改原先功能。总得来说,策略模式可以让你改变对象的内部结构,装饰器模式允许你更改对象的皮肤。
策略设计模式主要包括以下几个组件:
策略(Strategy):定义了所有具体策略(Concrete Strategies)的通用接口,它声明了一个上下文(Context)用于执行策略的方法。在这个例子中,策略定义了仲裁的接口函数arb_winner()。
具体策略(Concrete Strategies):实现了上下文所用算法的各种不同变体。在这个例子中,对于每个必要的算法,定义了一个具体策略类,提供特定算法的实现。将每个算法包装到一个单独的类中可以提高代码的可读性和可扩展性。
上下文(Context):维护指向具体策略的引用,且仅通过策略接口与该对象进行交流。比如UVM scoreboard组件依赖于Strategy,检查RTL内的仲裁逻辑是否正确实现,那么UVM scoreboard可以被认为是Context。
客户端(Client):会创建一个特定策略对象并将其传递给上下文。上下文则会提供一个设置函数以便客户端在运行时替换相关联的策略。当上下文需要运行算法时,它会在其已连接的策略对象上调用执行方法。上下文并不清楚其所涉及的策略类型与算法的执行方式。本例的客户端是example_application。
下图为策略设计模式在仲裁中应用的UML类图。
策略模式让你能将不同行为抽取到一个独立类层次结构中, 并将原始类组合成同一个, 从而减少重复代码。而且让你能将各种算法的代码、 内部数据和依赖关系与其他代码隔离开来。 不同客户端可通过一个简单接口执行算法, 并能在运行时进行切换。
2. 参考代码
仲裁处理的策略设计模式参考代码如下:
virtual class strategy;
pure virtual function int arb_winner(ref bit req_arr[3]);
endclass : strategy
class strategy_low_priority extends strategy;
virtual function int arb_winner(ref bit req_arr[3]);
for (int i=0; i<3; i++) begin
if (req_arr[i] == 1) begin
return i;
end
end
return -1;
endfunction : arb_winner
endclass : strategy_low_priority
class strategy_high_priority extends strategy;
virtual function int arb_winner(ref bit req_arr[3]);
for (int i=2; i>=0; i++) begin
if (req_arr[i] == 1) begin
return i;
end
end
return -1;
endfunction : arb_winner
endclass : strategy_high_priority
class my_context;
local strategy m_strategy;
function void set_strategy(strategy _m);
m_strategy = _m;
endfunction : set_strategy
function int execute_strategy(ref bit req_arr[3]);
return m_strategy.arb_winner(req_arr);
endfunction : execute_strategy
endclass : my_context
模拟测试代码如下:
class example_application;
rand bit low_priority;
rand bit high_priority;
constraint p_cons { low_priority + high_priority >= 1; }
function void main();
int result;
bit req_arrary[3] = '{1'b0, 1'b1, 1'b1};
strategy stg;
my_context m_ctx = new();
`uvm_info("strategy", $psprintf("low_priority:%b, high_priority:%b", low_priority, high_priority), UVM_LOW)
$display("The input req0:%b, req1:%b, req2:%b", req_arrary[0], req_arrary[1], req_arrary[2]);
if ( low_priority ) begin
stg = strategy_low_priority::new();
m_ctx.set_strategy(stg);
result = m_ctx.execute_strategy(req_arrary);
$display("For low priority, the result is: %0d", result);
end
if ( high_priority ) begin
stg = strategy_high_priority::new();
m_ctx.set_strategy(stg);
result = m_ctx.execute_strategy(req_arrary);
$display("For high priority, the result is: %0d", result);
end
endfunction : main
endclass : example_application
输出仿真日志如下:
| # [strategy] low_priority:1, high_priority:1
| # The input req0:0, req1:1, req2:1
| # For low priority, the result is: 1
| # For high priority, the result is: 2
从仿真结果可知,low_priority为1,high_priority为1,因此example_application类选取了strategy_low_priority类和strategy_high_priority类两个算法。
在strategy_low_priority类中,输入信号中,req0=0,req1=1,req2=1,req1输入口被选中,因此输出的结果是1。
在strategy_high_priority类中,输入信号中,req0=0,req1=1,req2=1,req2输入口被选中,因此输出的结果是2。