插件

简介

对于某些设计,您可能希望通过使用某种插件来组合组件的硬件,而不是直接在组件中实现硬件。这可以提供一些关键特性:

  • 您可以通过在组件的参数中添加新的插件来扩展组件的功能。例如,在CPU中添加浮点支持。

  • 您可以通过使用另一组插件来轻松切换相同功能的各种实现。例如,某个CPU乘法器的实现可能在某些FPGA上表现良好,而其他实现可能在ASIC上表现良好。

  • 它避免了非常非常庞大的手写顶层结构,其中一切都必须手动连接的情况。相反,插件可以通过查看/使用其他插件的软件接口来发现它们的关联关系。

VexRiscv和NaxRiscv项目就是这方面的例子。它们是具有大部分是空白的顶层的CPU,其硬件部分通过插件注入。例如:

  • PcPlugin

  • FetchPlugin

  • DecoderPlugin

  • RegFilePlugin

  • IntAluPlugin

这些插件将通过他们的服务池进行协调/传递/互连。

虽然VexRiscv使用严格的同步二阶段系统(设置(setup)/构建(build)回调(callback)),但NaxRiscv采用了一种更灵活的方法,使用spinal.core.fiber 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

请注意,每次在“构建期间”分叉一个实例化线程时,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

显然,这些示例对于它们的功能来说有些过度,总体上的思路更多地是:

  • 协调/创建插件之间的接口(例如跳转(jump)/刷新(flush)端口)

  • 安排实例化(例如解码/调度规范)

  • 提供一个可扩展的分布式框架(最小硬编码)