插件
简介
对于某些设计,您可能希望通过使用某种插件来组合组件的硬件,而不是直接在组件中实现硬件。这可以提供一些关键特性:
您可以通过在组件的参数中添加新的插件来扩展组件的功能。例如,在CPU中添加浮点支持。
您可以通过使用另一组插件来轻松切换相同功能的各种实现。例如,某个CPU乘法器的实现可能在某些FPGA上表现良好,而其他实现可能在ASIC上表现良好。
It avoid the very very very large hand written toplevel syndrome where everything has to be connected manually. Instead plugins can discover their neighborhood by looking/using the software interface of other plugins.
VexRiscv和NaxRiscv项目就是这方面的例子。它们是具有大部分是空白的顶层的CPU,其硬件部分通过插件注入。例如:
PcPlugin
FetchPlugin
DecoderPlugin
RegFilePlugin
IntAluPlugin
…
And those plugins will then negotiate/propagate/interconnect to each others via their pool of services.
虽然VexRiscv使用严格的同步二阶段系统(设置(setup)/构建(build)回调(callback)),但NaxRiscv采用了一种更灵活的方法,使用spinal.core.fiber API来分叉实例化线程,这些线程可以联锁,以确保可行的实例化顺序。
插件API(Plugin API)提供了一个类似NaxRiscv的系统来定义使用插件的可组合组件。
执行顺序
主要思想是您有多个2执行环节:
Setup phase, in which plugins can lock/retain each others. The idea is not to start negotiation / elaboration yet.
Build phase, in which plugins can negotiation / elaboration hardware.
构建环节将不会在所有FiberPlugin完成其设置环节前启动。
class MyPlugin extends FiberPlugin {
val logic = during setup new Area {
// Here we are executing code in the setup phase
awaitBuild()
// Here we are executing code in the build phase
}
}
class MyPlugin2 extends FiberPlugin {
val logic = during build new Area {
// Here we are executing code in the build phase
}
}
简单示例
这是一个简单的虚设示例,其中包含一个将使用两个插件组合的SubComponent:
import spinal.core._
import spinal.lib.misc.plugin._
// Let's define a Component with a PluginHost instance
class SubComponent extends Component {
val host = new PluginHost()
}
// Let's define a plugin which create a register
class StatePlugin extends FiberPlugin {
// during build new Area { body } will run the body of code in the Fiber build phase, in the context of the PluginHost
val logic = during build new Area {
val signal = Reg(UInt(32 bits))
}
}
// Let's define a plugin which will make the StatePlugin's register increment
class DriverPlugin extends FiberPlugin {
// We define how to get the instance of StatePlugin.logic from the PluginHost. It is a lazy val, because we can't evaluate it until the plugin is bound to its host.
lazy val sp = host[StatePlugin].logic.get
val logic = during build new Area {
// Generate the increment hardware
sp.signal := sp.signal + 1
}
}
class TopLevel extends Component {
val sub = new SubComponent()
// Here we create plugins and embed them in sub.host
new DriverPlugin().setHost(sub.host)
new StatePlugin().setHost(sub.host)
}
该TopLevel会生成以下Verilog代码:
module TopLevel (
input wire clk,
input wire reset
);
SubComponent sub (
.clk (clk ), // i
.reset (reset) // i
);
endmodule
module SubComponent (
input wire clk,
input wire reset
);
reg [31:0] StatePlugin_logic_signal; // Created by StatePlugin
always @(posedge clk) begin
StatePlugin_logic_signal <= (StatePlugin_logic_signal + 32'h00000001); // incremented by DriverPlugin
end
endmodule
请注意,每次在“构建期间”分叉一个实例化线程时,DriverPlugin.logic线程的执行将在“sp”评估上被阻塞,直到StatePlugin.logic执行完成。
联锁/排序
插件可以通过Retainer实例相互联锁。每个插件实例都有一个内置锁,可以通过retain/release函数进行控制。
这是一个基于上面的 简单示例 的例子,但这次,DriverPlugin将通过由其他插件(在我们的例子中是SetupPlugin)设置的数量对StatePlugin.logic.signal递增。为了确保DriverPlugin不会过早生成硬件,SetupPlugin使用DriverPlugin.retain/release函数。
import spinal.core._
import spinal.lib.misc.plugin._
import spinal.core.fiber._
class SubComponent extends Component {
val host = new PluginHost()
}
class StatePlugin extends FiberPlugin {
val logic = during build new Area {
val signal = Reg(UInt(32 bits))
}
}
class DriverPlugin extends FiberPlugin {
// incrementBy will be set by others plugin at elaboration time
var incrementBy = 0
// retainer allows other plugins to create locks, on which this plugin will wait before using incrementBy
val retainer = Retainer()
val logic = during build new Area {
val sp = host[StatePlugin].logic.get
retainer.await()
// Generate the incrementer hardware
sp.signal := sp.signal + incrementBy
}
}
// Let's define a plugin which will modify the DriverPlugin.incrementBy variable because letting it elaborate its hardware
class SetupPlugin extends FiberPlugin {
// during setup { body } will spawn the body of code in the Fiber setup phase (it is before the Fiber build phase)
val logic = during setup new Area {
// *** Setup phase code ***
val dp = host[DriverPlugin]
// Prevent the DriverPlugin from executing its build's body (until release() is called)
val lock = dp.retainer()
// Wait until the fiber phase reached build phase
awaitBuild()
// *** Build phase code ***
// Let's mutate DriverPlugin.incrementBy
dp.incrementBy += 1
// Allows the DriverPlugin to execute its build's body
lock.release()
}
}
class TopLevel extends Component {
val sub = new SubComponent()
sub.host.asHostOf(
new DriverPlugin(),
new StatePlugin(),
new SetupPlugin(),
new SetupPlugin() // Let's add a second SetupPlugin, because we can
)
}
这是生成的verilog
module TopLevel (
input wire clk,
input wire reset
);
SubComponent sub (
.clk (clk ), // i
.reset (reset) // i
);
endmodule
module SubComponent (
input wire clk,
input wire reset
);
reg [31:0] StatePlugin_logic_signal;
always @(posedge clk) begin
StatePlugin_logic_signal <= (StatePlugin_logic_signal + 32'h00000002); // + 2 as we have two SetupPlugin
end
endmodule
显然,这些示例对于它们的功能来说有些过度,总体上的思路更多地是:
Negotiate / create interfaces between plugins (ex jump / flush ports)
安排实例化(例如解码/调度规范)
提供一个可扩展的分布式框架(最小硬编码)