实例化 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)
}
}
时钟和复位信号的映射
在黑盒定义中,您必须明确定义时钟和复位线。要将 ClockDomain
的信号映射到黑盒的相应输入,您可以使用 mapClockDomain
或 mapCurrentClockDomain
函数。 mapClockDomain
具有以下参数:
名称 |
类型 |
缺省值 |
描述 |
---|---|---|---|
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;