Description

A blackbox allows the user to integrate an existing VHDL/Verilog component into the design by just specifying the interfaces. It’s up to the simulator or synthesizer to do the elaboration correctly.

Defining an blackbox

The example show how to define an blackbox.

// Define a Ram as a BlackBox
class Ram_1w_1r(wordWidth: Int, wordCount: Int) extends BlackBox {

  // SpinalHDL will look at Generic classes to get attributes which
  // should be used ad VHDL gererics / Verilog parameter
  // You can use String Int Double Boolean and all SpinalHDL base types
  // as generic value
  val generic = new Generic {
    val wordCount = Ram_1w_1r.this.wordCount
    val wordWidth = Ram_1w_1r.this.wordWidth
  }

  // Define io of the VHDL entiry / Verilog module
  val io = new Bundle {
    val clk = in Bool
    val wr = new Bundle {
      val en   = in Bool
      val addr = in UInt (log2Up(wordCount) bit)
      val data = in Bits (wordWidth bit)
    }
    val rd = new Bundle {
      val en   = in Bool
      val addr = in UInt (log2Up(wordCount) bit)
      val data = out Bits (wordWidth bit)
    }
  }

  //Map the current clock domain to the io.clk pin
  mapClockDomain(clock=io.clk)
}

In VHDL, Bool type will be translated into std_logic and Bits into std_logic_vector. If you want to get std_ulogic, you have to use a BlackBoxULogic instead of BlackBox.
In Verilog, BlackBoxUlogic has no effect.

class Ram_1w_1r(wordWidth: Int, wordCount: Int) extends BlackBoxULogic {
  ...
}

Generics

There are two different ways to declare generic : 

class Ram(wordWidth: Int, wordCount: Int) extends BlackBox {

    val generic = new Generic {
      val wordCount = Ram.this.wordCount
      val wordWidth = Ram.this.wordWidth
    }

    // OR 

    addGeneric("wordCount", wordWidth)
    addGeneric("wordWidth", wordWidth)
}

Instantiating a blackbox

To instantiate an blackbox, it’s the same than for 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) bit)
      val data = in Bits (8 bit)
    }
    val rd = new Bundle {
      val en   = in Bool
      val addr = in UInt (log2Up(16) bit)
      val data = out Bits (8 bit)
    }
  }

  //Instantiate the blackbox
  val ram = new Ram_1w_1r(8,16)

  //Interconnect all that stuff
  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)
  }
}

Clock and reset mapping

In your blackbox definition you have to explicitly define clock and reset wires. 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 :

name type default description
clockDomain ClockDomain ClockDomain.current Specify the clockDomain which provide signals
clock Bool Nothing Blackbox input which should be connected to the clockDomain clock
reset Bool Nothing Blackbox input which should be connected to the clockDomain reset
enable Bool Nothing Blackbox input which should be connected to the clockDomain enable

mapCurrentClockDomain has almost the same parameters than the mapClockDomain but without the clockDomain.

For example :

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)
}

io prefix

In order to avoid the prefix “io_” on each IOs of the blackbox, you can use the function noIoPrefix() as shown below :

// 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) bit)
      val data = in Bits (_wordWidth bit)
    }
    val rd = new Bundle {
      val en   = in Bool
      val addr = in UInt (log2Up(_wordCount) bit)
      val data = out Bits (_wordWidth bit)
    }
  }

  noIoPrefix()

  mapCurrentClockDomain(clock=io.clk)
}

Rename all io of a blackbox

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().repalce("portA_", "") + "_A") 
      if(bt.getName().contains("portB")) bt.setName(bt.getName().repalce("portB_", "") + "_B") 
    })
  }

  // Execute the function renameIO after the creation of the component 
  addPrePopTask(() => renameIO())
}

// This code generate those names :
//    clk 
//    cs_A, rwn_A, dIn_A, dOut_A
//    cs_B, rwn_B, dIn_B, dOut_B

Add RTL source

With the function addRTLPath() you can associate your RTL sources with the blackbox. After the generation of your Spinal code you can call the fonction mergeRTLSource for merging all sources together.

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 environement variable MY_PROJECT (System.getenv("MY_PROJECT"))
  
}

...

val report = SpinalVhdl(new MyBlackBox)
report.mergeRTLSource("mergeRTL") // merge all rtl sources into mergeRTL.vhd and mergeRTL.v file 


VHDL - No numeric type

If you want to get only std_logic_vector on your blackbox component, you can add the tag noNumericType to the blackbox.

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
}

// Code generated

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;

Tags: