插件
简介
对于某些设计,您可能希望通过使用某种插件来组合组件的硬件,而不是直接在组件中实现硬件。这可以提供一些关键特性:
您可以通过在组件的参数中添加新的插件来扩展组件的功能。例如,在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.
The build phase will not start before all FiberPlugin are done with their setup phase.
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
// incremented by DriverPlugin
StatePlugin_logic_signal <= (StatePlugin_logic_signal + 32'h00000001);
end
endmodule
Note each during build fork an elaboration thread, the DriverPlugin.logic thread execution
will be blocked on the “sp” evaluation until the StatePlugin.logic execution is done.
联锁/排序
Plugins can interlock each others using Retainer instances.
Each plugin instance has a built in lock which can be controlled using retain/release functions.
Here is an example based on the above Simple example but that time, the DriverPlugin will increment the StatePlugin.logic.signal
by an amount set by other plugins (SetupPlugin in our case). And to ensure that the DriverPlugin doesn’t generate the hardware too early,
the SetupPlugin uses the DriverPlugin.retain/release functions.
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
// + 2 as we have two SetupPlugin
StatePlugin_logic_signal <= (StatePlugin_logic_signal + 32'h00000002);
end
endmodule
显然,这些示例对于它们的功能来说有些过度,总体上的思路更多地是:
Negotiate / create interfaces between plugins (ex jump / flush ports)
安排实例化(例如解码/调度规范)
提供一个可扩展的分布式框架(最小硬编码)