本文作为SpinalHDL学习笔记第二十八篇,介绍SpinalHDL 使用小组件。
目录:
1.PLIC 映射器
2.插件
1.PLIC 映射器
PLIC 映射器定义了 PLIC(平台级中断控制器)的寄存器生成和访问。
PlicMapper.apply(bus: BusSlaveFactory, mapping: PlicMapping)(gateways : Seq[PlicGateway],targets : Seq[PlicTarget])
PlicMapper 的参数:
• bus:连接此控制器的总线
• mapping:一个映射配置(见上文)
• gateways:用于生成总线访问控制的 PlicGateway(中断源)序列
• targets:生成总线访问控制的 PlicTarget 序列(如:多核)
它遵循 riscv 提供的接口: https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc
截至目前,有两种内存映射可用:
PlicMapping.sifive
遵循 SiFive 的 PLIC 映射(例如 E31 核心复合手册 ),基本上是一个成熟的 PLIC
PlicMapping.light
此映射生成更轻量级的 PLIC,但代价是缺少一些可选特性:
• 不读取中断优先级
• 不读取中断的挂起位(必须使用声明 (claim)/完成 (complete) 机制)
• 不读取目标的阈值
剩下的寄存器 & 逻辑会被生成。
2.插件
对于某些设计,您可能希望通过使用某种插件来组合组件的硬件,而不是直接在组件中实现硬件。这可以提供一些关键特性:
• 可以通过在组件的参数中添加新的插件来扩展组件的功能。例如,在 CPU 中添加浮点支持。
• 可以通过使用另一组插件来轻松切换相同功能的各种实现。例如,某个 CPU 乘法器的实现可能在某些 FPGA 上表现良好,而其他实现可能在 ASIC 上表现良好。
• 它避免了非常非常庞大的手写顶层结构,其中一切都必须手动连接的情况。相反,插件可以通过查看/使用其他插件的软件接口来发现它们的关联关系。
VexRiscv 和 NaxRiscv 项目就是这方面的例子。它们是具有大部分是空白的顶层的 CPU,其硬件部分通过插件注入。例如:
• PcPlugin
• FetchPlugin
• DecoderPlugin
• RegFilePlugin
• IntAluPlugin
• …
这些插件将通过他们的服务池进行协调/传递/互连。
虽然 VexRiscv 使用严格的同步二阶段系统(设置 (setup)/构建 (build) 回调 (callback)),但 NaxRiscv 采用了一种更灵活的方法,使用 spinal.core.fber API 来分叉实例化线程,这些线程可以联锁,以确保可行的实例化顺序。
插件 API(Plugin API) 提供了一个类似 NaxRiscv 的系统来定义使用插件的可组合组件。
执行顺序
主要思想是您有多个 2 执行环节:
• 设置 (Setup) 环节,在此环节插件可以联锁/保留。其目的并非开始协调/实例化。
• 构建 (Build) 环节,在此环节插件可以协调/实例化硬件。
构建环节将不会在所有 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 binded 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
联锁/排序
插件可以通过 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
显然,这些示例对于它们的功能来说有些过度,总体上的思路更多地是:
• 协调/创建插件之间的接口(例如跳转 (jump)/刷新 (flush) 端口)
• 安排实例化(例如解码/调度规范)
• 提供一个可扩展的分布式框架(最小硬编码)