保留名称的方法

本页将描述 SpinalHDL 如何将名称从 scala 代码传播到生成的硬件 RTL。您应该了解它们,从而尽可能保留这些名称,以生成可理解的网表。

Nameable 基类

所有可以在 SpinalHDL 中命名的事物都扩展了 Nameable 基类。

因此在实践中,以下类扩展了 Nameable 类:

  • Component

  • Area

  • Data (UInt, SInt, Bundle, …)

有一些 Nameable 类型 API 的示例

class MyComponent extends Component {
  val a, b, c, d = Bool()
  b.setName("rawrr") // Force name
  c.setName("rawrr", weak = true) // Propose a name, will not be applied if a stronger name is already applied
  d.setCompositeName(b, postfix = "wuff") // Force toto to be named as b.getName() + _wuff"
}

会生成:

module MyComponent (
);
  wire                a;
  wire                rawrr;
  wire                c;
  wire                rawrr_wuff;
endmodule

In general, you don’t really need to access that API, unless you want to do tricky stuff for debug reasons or for elaboration purposes.

从 Scala 中提取名称

首先,从 1.4.0 版本开始,SpinalHDL 使用 scala 编译器插件,该插件可以在类构造期间在每次定义新 val 时,实现函数回调。

这个示例或多或少地展示了 SpinalHDL 本身是如何实现的:

// spinal.idslplugin.ValCallback is the Scala compiler plugin feature which will provide the callbacks
class Component extends spinal.idslplugin.ValCallback {
  override def valCallback[T](ref: T, name: String) : T = {
    println(s"Got $ref named $name") // Here we just print what we got as a demo.
    ref
  }
}

class UInt
class Bits
class MyComponent extends Component {
  val two = 2
  val wuff = "miaou"
  val toto = new UInt
  val rawrr = new Bits
}

object Debug3 extends App {
  new MyComponent()
  // ^ This will print :
  // Got 2 named two
  // Got miaou named wuff
  // Got spinal.tester.code.sandbox.UInt@691a7f8f named toto
  // Got spinal.tester.code.sandbox.Bits@161b062a named rawrr
}

使用 ValCallback“自省”功能,SpinalHDL 的组件类能够了解其内容和内容的名称。

但这也意味着,如果您希望某些东西获得名称,并且仅依赖于此自动命名功能,则对 Data (UInt、SInt、…) 实例的引用应存储在组件的某个 val 对象定义中。

例如 :

class MyComponent extends Component {
  val a,b = in UInt(8 bits) // Will be properly named
  val toto = out UInt(8 bits)   // same

  def doStuff(): Unit = {
    val tmp = UInt(8 bits) // This will not be named, as it isn't stored anywhere in a
                           // component val (but there is a solution explained later)
    tmp := 0x20
    toto := tmp
  }
  doStuff()
}

将生成:

module MyComponent (
  input      [7:0]    a,
  input      [7:0]    b,
  output     [7:0]    toto
);
  // Note that the tmp signal defined in scala was "shortcuted" by SpinalHDL,
  //  as it was unnamed and technically "shortcutable"
  assign toto = 8'h20;
endmodule

组件中的区域

命名系统的一个重要方面是您可以在组件内定义新的名称空间并进行操作

例如通过 Area :

class MyComponent extends Component {
  val logicA = new Area {    // This define a new namespace named "logicA
    val toggle = Reg(Bool()) // This register will be named "logicA_toggle"
    toggle := !toggle
  }
}

会生成

module MyComponent (
  input               clk,
  input               reset
);
  reg                 logicA_toggle;
  always @ (posedge clk) begin
    logicA_toggle <= (! logicA_toggle);
  end
endmodule

函数中的逻辑区

您还可以定义将创建新逻辑区的函数,该逻辑区将为其所有内容提供命名空间:

class MyComponent extends Component {
  def isZero(value: UInt) = new Area {
    val comparator = value === 0
  }

  val value = in UInt (8 bits)
  val someLogic = isZero(value)

  val result = out Bool()
  result := someLogic.comparator
}

这将生成:

module MyComponent (
  input      [7:0]    value,
  output              result
);
  wire                someLogic_comparator;

  assign someLogic_comparator = (value == 8'h0);
  assign result = someLogic_comparator;

endmodule

函数中的复合区(Composite)

SpinalHDL 1.5.0 中添加了复合区,它允许您创建一个范围,该范围将用作另一个 Nameable 的前缀:

class MyComponent extends Component {
  // Basically, a Composite is an Area that use its construction parameter as namespace prefix
  def isZero(value: UInt) = new Composite(value) {
    val comparator = value === 0
  }.comparator  // Note we don't return the Composite,
                //  but the element of the composite that we are interested in

  val value = in UInt (8 bits)
  val result = out Bool()
  result := isZero(value)
}

将生成:

module MyComponent (
  input      [7:0]    value,
  output              result
);
  wire                value_comparator;

  assign value_comparator = (value == 8'h0);
  assign result = value_comparator;

endmodule

复合区级联链

您还可以级联复合区:

class MyComponent extends Component {
  def isZero(value: UInt) = new Composite(value) {
    val comparator = value === 0
  }.comparator


  def inverted(value: Bool) = new Composite(value) {
    val inverter = !value
  }.inverter

  val value = in UInt(8 bits)
  val result = out Bool()
  result := inverted(isZero(value))
}

将生成:

module MyComponent (
  input      [7:0]    value,
  output              result
);
  wire                value_comparator;
  wire                value_comparator_inverter;

  assign value_comparator = (value == 8'h0);
  assign value_comparator_inverter = (! value_comparator);
  assign result = value_comparator_inverter;

endmodule

在一个线束(Bundle)的函数中的复合区

This behavior can be very useful when implementing Bundle utilities. For instance in the spinal.lib.Stream class is defined the following :

class Stream[T <: Data](val payloadType :  HardType[T]) extends Bundle {
  val valid   = Bool()
  val ready   = Bool()
  val payload = payloadType()

  def queue(size: Int): Stream[T] = new Composite(this) {
    val fifo = new StreamFifo(payloadType, size)
    fifo.io.push << self    // 'self' refers to the Composite construction argument ('this' in
                            //  the example). It avoids having to do a boring 'Stream.this'
  }.fifo.io.pop

  def m2sPipe(): Stream[T] = new Composite(this) {
    val m2sPipe = Stream(payloadType)

    val rValid = RegInit(False)
    val rData = Reg(payloadType)

    self.ready := (!m2sPipe.valid) || m2sPipe.ready

    when(self.ready) {
      rValid := self.valid
      rData := self.payload
    }

    m2sPipe.valid := rValid
    m2sPipe.payload := rData
  }.m2sPipe
}

这将允许嵌套调用,同时保留名称:

class MyComponent extends Component {
  val source = slave(Stream(UInt(8 bits)))
  val sink = master(Stream(UInt(8 bits)))
  sink << source.queue(size = 16).m2sPipe()
}

会生成

module MyComponent (
  input               source_valid,
  output              source_ready,
  input      [7:0]    source_payload,
  output              sink_valid,
  input               sink_ready,
  output     [7:0]    sink_payload,
  input               clk,
  input               reset
);
  wire                source_fifo_io_pop_ready;
  wire                source_fifo_io_push_ready;
  wire                source_fifo_io_pop_valid;
  wire       [7:0]    source_fifo_io_pop_payload;
  wire       [4:0]    source_fifo_io_occupancy;
  wire       [4:0]    source_fifo_io_availability;
  wire                source_fifo_io_pop_m2sPipe_valid;
  wire                source_fifo_io_pop_m2sPipe_ready;
  wire       [7:0]    source_fifo_io_pop_m2sPipe_payload;
  reg                 source_fifo_io_pop_rValid;
  reg        [7:0]    source_fifo_io_pop_rData;

  StreamFifo source_fifo (
    .io_push_valid      (source_valid                 ), //i
    .io_push_ready      (source_fifo_io_push_ready    ), //o
    .io_push_payload    (source_payload               ), //i
    .io_pop_valid       (source_fifo_io_pop_valid     ), //o
    .io_pop_ready       (source_fifo_io_pop_ready     ), //i
    .io_pop_payload     (source_fifo_io_pop_payload   ), //o
    .io_flush           (1'b0                         ), //i
    .io_occupancy       (source_fifo_io_occupancy     ), //o
    .io_availability    (source_fifo_io_availability  ), //o
    .clk                (clk                          ), //i
    .reset              (reset                        )  //i
  );
  assign source_ready = source_fifo_io_push_ready;
  assign source_fifo_io_pop_ready = ((1'b1 && (! source_fifo_io_pop_m2sPipe_valid)) || source_fifo_io_pop_m2sPipe_ready);
  assign source_fifo_io_pop_m2sPipe_valid = source_fifo_io_pop_rValid;
  assign source_fifo_io_pop_m2sPipe_payload = source_fifo_io_pop_rData;
  assign sink_valid = source_fifo_io_pop_m2sPipe_valid;
  assign source_fifo_io_pop_m2sPipe_ready = sink_ready;
  assign sink_payload = source_fifo_io_pop_m2sPipe_payload;
  always @ (posedge clk or posedge reset) begin
    if (reset) begin
      source_fifo_io_pop_rValid <= 1'b0;
    end else begin
      if(source_fifo_io_pop_ready)begin
        source_fifo_io_pop_rValid <= source_fifo_io_pop_valid;
      end
    end
  end

  always @ (posedge clk) begin
    if(source_fifo_io_pop_ready)begin
      source_fifo_io_pop_rData <= source_fifo_io_pop_payload;
    end
  end
endmodule

Unnamed signal handling

Since 1.5.0, for signal which end up without name, SpinalHDL will find a signal which is driven by that unnamed signal and propagate its name. This can produce useful results as long you don’t have too large island of unnamed stuff.

The name attributed to such unnamed signal is : _zz_ + drivenSignal.getName()

请注意,当生成后端需要将某些特定表达式或表达式链分解为多个信号时,也会使用此命名模式。

Verilog 表达式分割

There is an instance of expressions (ex : the + operator) that SpinalHDL need to express in dedicated signals to match the behavior with the Scala API :

class MyComponent extends Component {
  val a,b,c,d = in UInt(8 bits)
  val result = a + b + c + d
}

会生成

module MyComponent (
  input      [7:0]    a,
  input      [7:0]    b,
  input      [7:0]    c,
  input      [7:0]    d
);
  wire       [7:0]    _zz_result;
  wire       [7:0]    _zz_result_1;
  wire       [7:0]    result;

  assign _zz_result = (_zz_result_1 + c);
  assign _zz_result_1 = (a + b);
  assign result = (_zz_result + d);

endmodule

Verilog 长表达式分割

There is a instance of how a very long expression chain will be split up by SpinalHDL :

class MyComponent extends Component {
  val conditions = in Vec(Bool(), 64)
  // Perform a logical OR between all the condition elements
  val result = conditions.reduce(_ || _)

  // For Bits/UInt/SInt signals the 'orR' methods implements this reduction operation
}

会生成

module MyComponent (
  input               conditions_0,
  input               conditions_1,
  input               conditions_2,
  input               conditions_3,
  ...
  input               conditions_58,
  input               conditions_59,
  input               conditions_60,
  input               conditions_61,
  input               conditions_62,
  input               conditions_63
);
  wire                _zz_result;
  wire                _zz_result_1;
  wire                _zz_result_2;
  wire                result;

  assign _zz_result = ((((((((((((((((_zz_result_1 || conditions_32) || conditions_33) || conditions_34) || conditions_35) || conditions_36) || conditions_37) || conditions_38) || conditions_39) || conditions_40) || conditions_41) || conditions_42) || conditions_43) || conditions_44) || conditions_45) || conditions_46) || conditions_47);
  assign _zz_result_1 = ((((((((((((((((_zz_result_2 || conditions_16) || conditions_17) || conditions_18) || conditions_19) || conditions_20) || conditions_21) || conditions_22) || conditions_23) || conditions_24) || conditions_25) || conditions_26) || conditions_27) || conditions_28) || conditions_29) || conditions_30) || conditions_31);
  assign _zz_result_2 = (((((((((((((((conditions_0 || conditions_1) || conditions_2) || conditions_3) || conditions_4) || conditions_5) || conditions_6) || conditions_7) || conditions_8) || conditions_9) || conditions_10) || conditions_11) || conditions_12) || conditions_13) || conditions_14) || conditions_15);
  assign result = ((((((((((((((((_zz_result || conditions_48) || conditions_49) || conditions_50) || conditions_51) || conditions_52) || conditions_53) || conditions_54) || conditions_55) || conditions_56) || conditions_57) || conditions_58) || conditions_59) || conditions_60) || conditions_61) || conditions_62) || conditions_63);

endmodule

When 语句条件

when(cond) { } 语句条件生成为名为 when_ + fileName + line 的单独信号。 对 switch 语句也会做类似的事情。

// In file Test.scala
class MyComponent extends Component {
  val value = in UInt(8 bits)
  val isZero = out(Bool())
  val counter = out(Reg(UInt(8 bits)))

  isZero := False
  when(value === 0) { // At line 117
    isZero := True
    counter := counter + 1
  }
}

会生成

module MyComponent (
  input      [7:0]    value,
  output reg          isZero,
  output reg [7:0]    counter,
  input               clk,
  input               reset
);
  wire                when_Test_l117;

  always @ (*) begin
    isZero = 1'b0;
    if(when_Test_l117)begin
      isZero = 1'b1;
    end
  end

  assign when_Test_l117 = (value == 8'h0);
  always @ (posedge clk) begin
    if(when_Test_l117)begin
      counter <= (counter + 8'h01);
    end
  end
endmodule

最后一招

最后,如果信号没有名称(匿名信号),SpinalHDL 将寻找由匿名信号驱动的命名信号,并将其用作名称后缀:

class MyComponent extends Component {
  val enable = in Bool()
  val value = out UInt(8 bits)

  def count(cond : Bool): UInt = {
    val ret = Reg(UInt(8 bits)) // This register is not named (on purpose for the example)
    when(cond) {
      ret := ret + 1
    }
    return ret
  }

  value := count(enable)
}

会生成

module MyComponent (
  input               enable,
  output     [7:0]    value,
  input               clk,
  input               reset
);
  // Name given to the register in last resort by looking what was driven by it
  reg        [7:0]    _zz_value;

  assign value = _zz_value;
  always @ (posedge clk) begin
    if(enable)begin
      _zz_value <= (_zz_value + 8'h01);
    end
  end
endmodule

最后的命名方法并不适合所有情况,但可以提供帮助。

请注意,以下划线开头的信号不会存储在 Verilator 波形中(这是故意的)