首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >凿子:如何从命令行更改模块参数?

凿子:如何从命令行更改模块参数?
EN

Stack Overflow用户
提问于 2021-01-27 15:04:37
回答 2查看 498关注 0票数 2

我有许多具有多个参数的模块。以模板中GCD的修改版本为例:

代码语言:javascript
复制
class GCD (len: Int = 16, validHigh: Boolean = true) extends Module {
  val io = IO(new Bundle {
    val value1        = Input(UInt(len.W))
    val value2        = Input(UInt(len.W))
    val loadingValues = Input(Bool())
    val outputGCD     = Output(UInt(len.W))
    val outputValid   = Output(Bool())
  })

  val x  = Reg(UInt())
  val y  = Reg(UInt())

  when(x > y) { x := x - y }
    .otherwise { y := y - x }

  when(io.loadingValues) {
    x := io.value1
    y := io.value2
  }

  io.outputGCD := x
  if (validHigh) {
    io.outputValid := (y === 0.U)
  } else {
    io.outputValid := (y =/= 0.U)
  }
}

为了测试或合成许多不同的设计,我想在调用测试人员或生成器应用程序时从命令行更改值。最好是这样:

代码语言:javascript
复制
[generation or test command] --len 12 --validHigh false

但是这个或类似的东西也可以

代码语言:javascript
复制
[generation or test command] --param "len=12" --param "validHigh=false"

经过一些试验和错误之后,我想出了一个解决方案如下所示:

gcd.scala

代码语言:javascript
复制
package gcd

import firrtl._
import chisel3._

case class GCDConfig(
  len: Int = 16,
  validHigh: Boolean = true
)

class GCD (val conf: GCDConfig = GCDConfig()) extends Module {
  val io = IO(new Bundle {
    val value1        = Input(UInt(conf.len.W))
    val value2        = Input(UInt(conf.len.W))
    val loadingValues = Input(Bool())
    val outputGCD     = Output(UInt(conf.len.W))
    val outputValid   = Output(Bool())
  })

  val x  = Reg(UInt())
  val y  = Reg(UInt())

  when(x > y) { x := x - y }
    .otherwise { y := y - x }

  when(io.loadingValues) {
    x := io.value1
    y := io.value2
  }

  io.outputGCD := x
  if (conf.validHigh) {
    io.outputValid := y === 0.U
  } else {
    io.outputValid := y =/= 0.U
  }
}

trait HasParams {
  self: ExecutionOptionsManager =>

  var params: Map[String, String] = Map()

  parser.note("Design Parameters")

  parser.opt[Map[String, String]]('p', "params")
    .valueName("k1=v1,k2=v2")
    .foreach { v => params = v }
    .text("Parameters of Design")
}

object GCD {
  def apply(params: Map[String, String]): GCD = {
    new GCD(params2conf(params))
  }

  def params2conf(params: Map[String, String]): GCDConfig = {
    var conf = new GCDConfig
    for ((k, v) <- params) {
      (k, v) match {
        case ("len", _) => conf = conf.copy(len = v.toInt)
        case ("validHigh", _) => conf = conf.copy(validHigh = v.toBoolean)
        case _ =>
      }
    }
    conf
  }
}

object GCDGen extends App {
  val optionsManager = new ExecutionOptionsManager("gcdgen")
  with HasChiselExecutionOptions with HasFirrtlOptions with HasParams
  optionsManager.parse(args) match {
    case true => 
      chisel3.Driver.execute(optionsManager, () => GCD(optionsManager.params))
    case _ =>
      ChiselExecutionFailure("could not parse results")
  }
}

以及测试

GCDSpec.scala

代码语言:javascript
复制
package gcd

import chisel3._
import firrtl._
import chisel3.tester._
import org.scalatest.FreeSpec
import chisel3.experimental.BundleLiterals._
import chiseltest.internal._
import chiseltest.experimental.TestOptionBuilder._

object GCDTest extends App {
  val optionsManager = new ExecutionOptionsManager("gcdtest") with HasParams
  optionsManager.parse(args) match {
    case true => 
      //println(optionsManager.commonOptions.programArgs)
      (new GCDSpec(optionsManager.params)).execute()
    case _ =>
      ChiselExecutionFailure("could not parse results")
  }
}

class GCDSpec(params: Map[String, String] = Map()) extends FreeSpec with ChiselScalatestTester {

  "Gcd should calculate proper greatest common denominator" in {
    test(GCD(params)) { dut =>
      dut.io.value1.poke(95.U)
      dut.io.value2.poke(10.U)
      dut.io.loadingValues.poke(true.B)
      dut.clock.step(1)
      dut.io.loadingValues.poke(false.B)
      while (dut.io.outputValid.peek().litToBoolean != dut.conf.validHigh) {
        dut.clock.step(1)
      }
      dut.io.outputGCD.expect(5.U)
    }
  }
}

这样,我就可以生成不同的设计并用

代码语言:javascript
复制
sbt 'runMain gcd.GCDGen --params "len=12,validHigh=false"'
sbt 'test:runMain gcd.GCDTest --params "len=12,validHigh=false"'

但是这个解决方案有几个问题或烦恼:

  1. 它使用不推荐的特性(ExecutionOptionsManager和HasFirrtlOptions)。我不确定这个解决方案是否可以移植到新的FirrtlStage基础设施中。
  2. 这里面有很多样板。为每个模块编写新的case类和params2conf函数,并在添加或删除参数时都重写这两种情况,这将变得非常繁琐。
  3. 一直使用conf.x而不是x。但我想,这是不可避免的,因为Scala中没有什么比得上python的kwargs

有没有更好的方法或者至少没有被反对的方法?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-02-02 13:29:42

基于http://blog.echo.sh/2013/11/04/exploring-scala-macros-map-to-case-class-conversion.html,我找到了另一种使用scala宏删除params2conf样板的方法。我还用verilog一代扩展了Chick的回答,因为这也是最初问题的一部分。我的解决方案的完整存储库可以在github上找到。

基本上有三个文件:

将映射转换为case类的宏:

代码语言:javascript
复制
package mappable

import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context

trait Mappable[T] {
  def toMap(t: T): Map[String, String]
  def fromMap(map: Map[String, String]): T
}

object Mappable {
  implicit def materializeMappable[T]: Mappable[T] = macro materializeMappableImpl[T]

  def materializeMappableImpl[T: c.WeakTypeTag](c: Context): c.Expr[Mappable[T]] = {
    import c.universe._
    val tpe = weakTypeOf[T]
    val companion = tpe.typeSymbol.companion

    val fields = tpe.decls.collectFirst {
      case m: MethodSymbol if m.isPrimaryConstructor => m
    }.get.paramLists.head

    val (toMapParams, fromMapParams) = fields.map { field =>
      val name = field.name.toTermName
      val decoded = name.decodedName.toString
      val returnType = tpe.decl(name).typeSignature

      val fromMapLine = returnType match {
        case NullaryMethodType(res) if res =:= typeOf[Int] => q"map($decoded).toInt"
        case NullaryMethodType(res) if res =:= typeOf[String] => q"map($decoded)"
        case NullaryMethodType(res) if res =:= typeOf[Boolean] => q"map($decoded).toBoolean"
        case _ => q""
      }

      (q"$decoded -> t.$name.toString", fromMapLine)
    }.unzip

    c.Expr[Mappable[T]] { q"""
      new Mappable[$tpe] {
        def toMap(t: $tpe): Map[String, String] = Map(..$toMapParams)
        def fromMap(map: Map[String, String]): $tpe = $companion(..$fromMapParams)
      }
    """ }
  }
}

类库工具:

代码语言:javascript
复制
package cliparams

import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation, ChiselCli}
import firrtl.AnnotationSeq
import firrtl.annotations.{Annotation, NoTargetAnnotation}
import firrtl.options.{HasShellOptions, Shell, ShellOption, Stage, Unserializable, StageMain}
import firrtl.stage.FirrtlCli

import mappable._

trait SomeAnnotaion {
  this: Annotation =>
}

case class ParameterAnnotation(map: Map[String, String])
    extends SomeAnnotaion
    with NoTargetAnnotation
    with Unserializable

object ParameterAnnotation extends HasShellOptions {
  val options = Seq(
    new ShellOption[Map[String, String]](
      longOption = "params",
      toAnnotationSeq = (a: Map[String, String]) => Seq(ParameterAnnotation(a)),
      helpText = """a comma separated, space free list of additional paramters, e.g. --param-string "k1=7,k2=dog" """
    )
  )
}

trait ParameterCli {
  this: Shell =>

  Seq(ParameterAnnotation).foreach(_.addOptions(parser))
}

class GenericParameterCliStage[P: Mappable](thunk: (P, AnnotationSeq) => Unit, default: P) extends Stage {

  def mapify(p: P) = implicitly[Mappable[P]].toMap(p)
  def materialize(map: Map[String, String]) = implicitly[Mappable[P]].fromMap(map)

  val shell: Shell = new Shell("chiseltest") with ParameterCli with ChiselCli with FirrtlCli

  def run(annotations: AnnotationSeq): AnnotationSeq = {
    val params = annotations
      .collectFirst {case ParameterAnnotation(map) => materialize(mapify(default) ++ map.toSeq)}
      .getOrElse(default)

    thunk(params, annotations)
    annotations
  }
}

GCD源文件

代码语言:javascript
复制
// See README.md for license details.

package gcd

import firrtl._
import chisel3._
import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation}
import firrtl.options.{StageMain}

// Both have to be imported
import mappable._
import cliparams._

case class GCDConfig(
  len: Int = 16,
  validHigh: Boolean = true
)

/**
  * Compute GCD using subtraction method.
  * Subtracts the smaller from the larger until register y is zero.
  * value in register x is then the GCD
  */
class GCD (val conf: GCDConfig = GCDConfig()) extends Module {
  val io = IO(new Bundle {
    val value1        = Input(UInt(conf.len.W))
    val value2        = Input(UInt(conf.len.W))
    val loadingValues = Input(Bool())
    val outputGCD     = Output(UInt(conf.len.W))
    val outputValid   = Output(Bool())
  })

  val x  = Reg(UInt())
  val y  = Reg(UInt())

  when(x > y) { x := x - y }
    .otherwise { y := y - x }

  when(io.loadingValues) {
    x := io.value1
    y := io.value2
  }

  io.outputGCD := x
  if (conf.validHigh) {
    io.outputValid := y === 0.U
  } else {
    io.outputValid := y =/= 0.U
  }
}

class GCDGenStage extends GenericParameterCliStage[GCDConfig]((params, annotations) => {
  (new chisel3.stage.ChiselStage).execute(
    Array("-X", "verilog"),
    Seq(ChiselGeneratorAnnotation(() => new GCD(params))))}, GCDConfig())

object GCDGen extends StageMain(new GCDGenStage)

和测试

代码语言:javascript
复制
// See README.md for license details.

package gcd

import chisel3._
import firrtl._
import chisel3.tester._
import org.scalatest.FreeSpec
import chisel3.experimental.BundleLiterals._
import chiseltest.internal._
import chiseltest.experimental.TestOptionBuilder._
import firrtl.options.{StageMain}

import mappable._
import cliparams._

class GCDSpec(params: GCDConfig, annotations: AnnotationSeq = Seq()) extends FreeSpec with ChiselScalatestTester {

  "Gcd should calculate proper greatest common denominator" in {
    test(new GCD(params)) { dut =>
      dut.io.value1.poke(95.U)
      dut.io.value2.poke(10.U)
      dut.io.loadingValues.poke(true.B)
      dut.clock.step(1)
      dut.io.loadingValues.poke(false.B)
      while (dut.io.outputValid.peek().litToBoolean != dut.conf.validHigh) {
        dut.clock.step(1)
      }
      dut.io.outputGCD.expect(5.U)
    }
  }
}

class GCDTestStage extends GenericParameterCliStage[GCDConfig]((params, annotations) => {
  (new GCDSpec(params, annotations)).execute()}, GCDConfig())

object GCDTest extends StageMain(new GCDTestStage)

生成和测试都可以通过CLI参数化,如OQ中所示:

代码语言:javascript
复制
sbt 'runMain gcd.GCDGen --params "len=12,validHigh=false"'
sbt 'test:runMain gcd.GCDTest --params "len=12,validHigh=false"'
票数 0
EN

Stack Overflow用户

发布于 2021-01-27 23:25:03

问得好。我认为你是,你几乎所有的东西都是对的。我通常不需要命令行来改变我的测试,我的开发周期通常只是直接运行测试参数中的值。我使用intelliJ,这似乎使这很容易(但可能只适用于我的习惯和规模的项目,我的工作)。

但我想给你一个建议,这将使你摆脱ExecutionOptions风格,因为这种方式正在迅速消失。

在下面的示例代码中,我基本上提供了两个文件,首先,这里有一些库,比如使用现代注释成语的工具,我相信,可以最小化锅炉板。它们依赖于严格的匹配,但这是可以修复的。在第二个,是您的GCD,GCDSpec,稍微修改了一下,以提取出稍微不同的帕拉姆。在第二个底部是一些非常小的锅炉板,允许您获得命令行访问您想要的。

祝你好运,我希望这大多是不言自明的。

第一个文件:

代码语言:javascript
复制
import chisel3.stage.ChiselCli
import firrtl.AnnotationSeq
import firrtl.annotations.{Annotation, NoTargetAnnotation}
import firrtl.options.{HasShellOptions, Shell, ShellOption, Stage, Unserializable}
import firrtl.stage.FirrtlCli

trait TesterAnnotation {
  this: Annotation =>
}

case class TestParams(params: Map[String, String] = Map.empty) {
  val defaults: collection.mutable.HashMap[String, String] = new collection.mutable.HashMap()

  def getInt(key:     String): Int = params.getOrElse(key, defaults(key)).toInt
  def getBoolean(key: String): Boolean = params.getOrElse(key, defaults(key)).toBoolean
  def getString(key:  String): String = params.getOrElse(key, defaults(key))
}
case class TesterParameterAnnotation(paramString: TestParams)
    extends TesterAnnotation
    with NoTargetAnnotation
    with Unserializable

object TesterParameterAnnotation extends HasShellOptions {
  val options = Seq(
    new ShellOption[Map[String, String]](
      longOption = "param-string",
      toAnnotationSeq = (a: Map[String, String]) => Seq(TesterParameterAnnotation(TestParams(a))),
      helpText = """a comma separated, space free list of additional paramters, e.g. --param-string "k1=7,k2=dog" """
    )
  )
}

trait TesterCli {
  this: Shell =>

  Seq(TesterParameterAnnotation).foreach(_.addOptions(parser))
}

class GenericTesterStage(thunk: (TestParams, AnnotationSeq) => Unit) extends Stage {
  val shell: Shell = new Shell("chiseltest") with TesterCli with ChiselCli with FirrtlCli

  def run(annotations: AnnotationSeq): AnnotationSeq = {
    val params = annotations.collectFirst { case TesterParameterAnnotation(p) => p }.getOrElse(TestParams())

    thunk(params, annotations)
    annotations
  }
}

第二份档案:

代码语言:javascript
复制
import chisel3._
import chisel3.tester._
import chiseltest.experimental.TestOptionBuilder._
import chiseltest.{ChiselScalatestTester, GenericTesterStage, TestParams}
import firrtl._
import firrtl.options.StageMain
import org.scalatest.freespec.AnyFreeSpec

case class GCD(testParams: TestParams) extends Module {
  val bitWidth = testParams.getInt("len")
  val validHigh = testParams.getBoolean("validHigh")

  val io = IO(new Bundle {
    val value1 = Input(UInt(bitWidth.W))
    val value2 = Input(UInt(bitWidth.W))
    val loadingValues = Input(Bool())
    val outputGCD = Output(UInt(bitWidth.W))
    val outputValid = Output(Bool())
  })

  val x = Reg(UInt())
  val y = Reg(UInt())

  when(x > y) { x := x - y }.otherwise { y := y - x }

  when(io.loadingValues) {
    x := io.value1
    y := io.value2
  }

  io.outputGCD := x
  if (validHigh) {
    io.outputValid := y === 0.U
  } else {
    io.outputValid := y =/= 0.U
  }
}

class GCDSpec(params: TestParams, annotations: AnnotationSeq = Seq()) extends AnyFreeSpec with ChiselScalatestTester {

  "Gcd should calculate proper greatest common denominator" in {
    test(GCD(params)).withAnnotations(annotations) { dut =>
      dut.io.value1.poke(95.U)
      dut.io.value2.poke(10.U)
      dut.io.loadingValues.poke(true.B)
      dut.clock.step(1)
      dut.io.loadingValues.poke(false.B)
      while (dut.io.outputValid.peek().litToBoolean != dut.validHigh) {
        dut.clock.step(1)
      }
      dut.io.outputGCD.expect(5.U)
    }
  }
}

class GcdTesterStage
    extends GenericTesterStage((params, annotations) => {
      params.defaults ++= Seq("len" -> "16", "validHigh" -> "false")
      (new GCDSpec(params, annotations)).execute()
    })

object GcdTesterStage extends StageMain(new GcdTesterStage)
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65921833

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档