实例化 VHDL 和 Verilog IP
描述
黑盒允许用户通过指定其接口将现有的 VHDL/Verilog 组件集成到设计中。正确地进行实力细化取决于仿真器或综合器。
定义一个黑盒
下面示例显示了定义黑盒的方法:
// Define a Ram as a BlackBox
class Ram_1w_1r(wordWidth: Int, wordCount: Int) extends BlackBox {
  // Add VHDL Generics / Verilog parameters to the blackbox
  // You can use String, Int, Double, Boolean, and all SpinalHDL base
  // types as generic values
  addGeneric("wordCount", wordCount)
  addGeneric("wordWidth", wordWidth)
  // Define IO of the VHDL entity / Verilog module
  val io = new Bundle {
    val clk = in Bool()
    val wr = new Bundle {
      val en   = in Bool()
      val addr = in UInt (log2Up(wordCount) bits)
      val data = in Bits (wordWidth bits)
    }
    val rd = new Bundle {
      val en   = in Bool()
      val addr = in UInt (log2Up(wordCount) bits)
      val data = out Bits (wordWidth bits)
    }
  }
  // Map the current clock domain to the io.clk pin
  mapClockDomain(clock=io.clk)
}
Bool 类型的信号将被转换为 std_logic , Bits 将被转换为 std_logic_vector。如果你想获得 std_ulogic,你必须使用 BlackBoxULogic 而不是 BlackBox。BlackBoxUlogic 不会更改生成的 Verilog。class Ram_1w_1r(wordWidth: Int, wordCount: Int) extends BlackBoxULogic {
  ...
}
泛型
有两种不同的方式来声明泛型:
class Ram(wordWidth: Int, wordCount: Int) extends BlackBox {
    addGeneric("wordCount", wordCount)
    addGeneric("wordWidth", wordWidth)
    // OR
    val generic = new Generic {
      val wordCount = Ram.this.wordCount
      val wordWidth = Ram.this.wordWidth
    }
}
实例化黑盒
实例化一个 BlackBox 就像实例化一个 Component 一样:
// Create the top level and instantiate the Ram
class TopLevel extends Component {
  val io = new Bundle {
    val wr = new Bundle {
      val en   = in Bool()
      val addr = in UInt (log2Up(16) bits)
      val data = in Bits (8 bits)
    }
    val rd = new Bundle {
      val en   = in Bool()
      val addr = in UInt (log2Up(16) bits)
      val data = out Bits (8 bits)
    }
  }
  // Instantiate the blackbox
  val ram = new Ram_1w_1r(8,16)
  // Connect all the signals
  io.wr.en   <> ram.io.wr.en
  io.wr.addr <> ram.io.wr.addr
  io.wr.data <> ram.io.wr.data
  io.rd.en   <> ram.io.rd.en
  io.rd.addr <> ram.io.rd.addr
  io.rd.data <> ram.io.rd.data
}
object Main {
  def main(args: Array[String]): Unit = {
    SpinalVhdl(new TopLevel)
  }
}
时钟和复位信号的映射
In your blackbox definition you have to explicitly define clock and reset signals. To map signals of a ClockDomain to corresponding inputs of the blackbox you can use the mapClockDomain or mapCurrentClockDomain function. mapClockDomain has the following parameters:
| 名称 | 类型 | 缺省值 | 描述 | 
|---|---|---|---|
| clockDomain | ClockDomain | ClockDomain.current | 指定提供信号的clockDomain | 
| 时钟 | Bool | Nothing | 应连接到clockDomain时钟的黑盒输入 | 
| reset | Bool | Nothing | 黑盒输入应连接到时钟域的复位信号 | 
| enable | Bool | Nothing | 黑盒输入应连接到时钟域的使能信号 | 
mapCurrentClockDomain 具有与 mapClockDomain 几乎相同的参数,但没有时钟域。
例如:
class MyRam(clkDomain: ClockDomain) extends BlackBox {
  val io = new Bundle {
    val clkA = in Bool()
    ...
    val clkB = in Bool()
    ...
  }
  // Clock A is map on a specific clock Domain
  mapClockDomain(clkDomain, io.clkA)
  // Clock B is map on the current clock domain
  mapCurrentClockDomain(io.clkB)
}
默认情况下,黑盒模块的端口是不绑定时钟域的,这意味着在使用这些端口时不会进行时钟交叉检查。您可以使用 ClockDomainTag 指定端口的时钟域:
class DemoBlackbox extends BlackBox {
  val io = new Bundle {
    val clk, rst = in Bool()
    val a = in Bool()
    val b = out Bool()
  }
  mapCurrentClockDomain(io.clk, io.rst)
  ClockDomainTag(this.clockDomain)(
    io.a,
    io.b
  )
}
您也可以将标记应用于整个线束:
val io = new Bundle {
  val clk, rst = in Bool()
  val a = in Bool()
  val b = out Bool()
}
ClockDomainTag(this.clockDomain)(io)
从SpinalHDL 1.10.2开始,您还可以将当前时钟域应用到所有端口:
val io = new Bundle {
  val clk, rst = in Bool()
  val a = in Bool()
  val b = out Bool()
}
setIoCd()
io前缀
为了避免黑盒的每个 IO 上都有前缀 “io_” ,可以使用函数 noIoPrefix() ,如下所示:
// Define the Ram as a BlackBox
class Ram_1w_1r(wordWidth: Int, wordCount: Int) extends BlackBox {
  val generic = new Generic {
    val wordCount = Ram_1w_1r.this.wordCount
    val wordWidth = Ram_1w_1r.this.wordWidth
  }
  val io = new Bundle {
    val clk = in Bool()
    val wr = new Bundle {
      val en   = in Bool()
      val addr = in UInt (log2Up(_wordCount) bits)
      val data = in Bits (_wordWidth bits)
    }
    val rd = new Bundle {
      val en   = in Bool()
      val addr = in UInt (log2Up(_wordCount) bits)
      val data = out Bits (_wordWidth bits)
    }
  }
  noIoPrefix()
  mapCurrentClockDomain(clock=io.clk)
}
重命名黑盒中的所有io
BlackBox 或 Component 的 IO 可以在编译时使用 addPrePopTask 函数重命名。此函数在编译期间调用一个无参数函数,对于添加重命名通道非常有用,如下所示:
class MyRam() extends Blackbox {
  val io = new Bundle {
    val clk = in Bool()
    val portA = new Bundle {
      val cs   = in Bool()
      val rwn  = in Bool()
      val dIn  = in Bits(32 bits)
      val dOut = out Bits(32 bits)
    }
    val portB = new Bundle {
      val cs   = in Bool()
      val rwn  = in Bool()
      val dIn  = in Bits(32 bits)
      val dOut = out Bits(32 bits)
    }
  }
  // Map the clk
  mapCurrentClockDomain(io.clk)
  // Remove io_ prefix
  noIoPrefix()
  // Function used to rename all signals of the blackbox
  private def renameIO(): Unit = {
    io.flatten.foreach(bt => {
      if(bt.getName().contains("portA")) bt.setName(bt.getName().replace("portA_", "") + "_A")
      if(bt.getName().contains("portB")) bt.setName(bt.getName().replace("portB_", "") + "_B")
    })
  }
  // Execute the function renameIO after the creation of the component
  addPrePopTask(() => renameIO())
}
// This code generate these names:
//    clk
//    cs_A, rwn_A, dIn_A, dOut_A
//    cs_B, rwn_B, dIn_B, dOut_B
添加 RTL 源
使用函数 addRTLPath() ,您可以将 RTL 源与黑盒关联起来。生成 SpinalHDL 代码后,您可以调用函数 mergeRTLSource 将所有源合并在一起。
class MyBlackBox() extends Blackbox {
  val io = new Bundle {
    val clk   = in  Bool()
    val start = in Bool()
    val dIn   = in  Bits(32 bits)
    val dOut  = out Bits(32 bits)
    val ready = out Bool()
  }
  // Map the clk
  mapCurrentClockDomain(io.clk)
  // Remove io_ prefix
  noIoPrefix()
  // Add all rtl dependencies
  addRTLPath("./rtl/RegisterBank.v")                         // Add a verilog file
  addRTLPath(s"./rtl/myDesign.vhd")                          // Add a vhdl file
  addRTLPath(s"${sys.env("MY_PROJECT")}/myTopLevel.vhd")     // Use an environment variable MY_PROJECT (System.getenv("MY_PROJECT"))
}
...
class TopLevel() extends Component {
  // ...
  val bb = new MyBlackBox()
  // ...
}
val report = SpinalVhdl(new TopLevel)
report.mergeRTLSource("mergeRTL") // Merge all rtl sources into mergeRTL.vhd and mergeRTL.v files
VHDL - 无数值类型
如果您只想在黑盒组件中使用 std_logic_vector ,则可以将标签 noNumericType 添加到黑盒中。
class MyBlackBox() extends BlackBox {
  val io = new Bundle {
    val clk       = in  Bool()
    val increment = in  Bool()
    val initValue = in  UInt(8 bits)
    val counter   = out UInt(8 bits)
  }
  mapCurrentClockDomain(io.clk)
  noIoPrefix()
  addTag(noNumericType)  // Only std_logic_vector
}
上面的代码将生成以下 VHDL:
component MyBlackBox is
  port(
    clk       : in  std_logic;
    increment : in  std_logic;
    initValue : in  std_logic_vector(7 downto 0);
    counter   : out std_logic_vector(7 downto 0)
  );
end component;