Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bundle Literals Framework #820

Merged
merged 25 commits into from
Jul 4, 2018
Merged

Bundle Literals Framework #820

merged 25 commits into from
Jul 4, 2018

Conversation

ducky64
Copy link
Contributor

@ducky64 ducky64 commented May 11, 2018

Related issue: #805

Type of change: other enhancement

Impact: API addition, API deprecation

Development Phase: proposed implementation

Release Notes

  • Adds litToBigInt[Option], litToBoolean[Option], and litToDouble[Option] APIs. Deprecates previous isLit, litArg, litValue APIs, since those expose implementation details (LitArg), do not have Option variants, and may not return the type desired (BigInt/Boolean/Double).
  • [internal] literals now specified in Bindings, instead of in Data subtypes.
  • [internal] adds support for Bundle literals by introducing a Bundle literal binding that contains a map of leaf subelements to their literal values.
  • [internal] ids are now persistent through runs, necessary to allow Bundle literals outside a Builder context (otherwise, a new IdGen is created for each element, and the ids alias).
  • [internal] HasId hashcode and equals reverted to the Java Object defaults, which allows Data created outside a Builder context to be Map-indexable
  • [internal] Created a ChiselContext (like DynamicContext) for global operations (like _id creation) that can be done outside a Builder context.
  • [internal] lref/ref checks are more strict, but Bindings should ensure they don't trip, and cases where they do trip are likely to be FIRRTL errors.
  • [internal] Made accesses to DynamicContext more strict, and will throw an error in most cases. The exceptions are error logging (errors are discarded) and getting the current Module (which will return None if outside a Builder context).

Other notes:

  • Support for bundle literal extractors is not yet implemented (the TODO is intentional), proposal is to have litToBigInt[Option] do the right thing. Note that bundle subelement literals can be extracted.
  • Support for generating Bundle literal constructors by macro annotation will be in a future PR. This only provides the internal framework to support Bundle literals at all. A mockup of what the generated code could look like is in the Bundle literal test case.
  • litArg, litValue, isLit are deprecated globally, even in Chisel context. If anyone really cares (as in, this causes problems for someone), I can move those into the compatibility layer without the deprecation warning through implicit conversions.

@ducky64 ducky64 requested a review from jackkoenig May 11, 2018 19:45
@ducky64
Copy link
Contributor Author

ducky64 commented May 11, 2018

Changes:

  • HasId hashcode and equals reverted to the Java Object defaults, which allows Data created outside a Builder context to be Map-indexable. I can't seem to remove HasId.hashCode or HasId.equals completely without triggering a gnarly scalac error, so it just calls the superclass method. Funnnnnn...
  • Created a ChiselContext (like DynamicContext) for global operations (like _id creation) that can be done outside a Builder context.
  • Made accesses to DynamicContext more strict, and will throw an error in most cases. The exceptions are error logging (errors are discarded) and getting the current Module (which will return None if outside a Builder context).

@chick chick self-requested a review May 18, 2018 20:28
@chick
Copy link
Contributor

chick commented May 23, 2018

Added a test
"literals declared outside a builder context" should "compare with those inside builder context"
that shows errors with seemingly reasonable use cases.
Please investigate. Answer may that they are not reasonable use cases in which case better errors would be nice, but best would be if this test worked

@ducky64
Copy link
Contributor Author

ducky64 commented May 24, 2018

It appears that we current don't support === on anything but UInt/etc. The assert appears to be routed to chisel3.assert.apply(cond: Boolean), which was a workaround / hack to allow Scala asserts to coexist with Chisel, and something (possibly ScalaTest) is turning the Bundle === into a Boolean.

I've fixed the chisel3.core.BundleLitBinding cannot be cast to chisel3.core.ElementLitBinding issues, Element.ref wasn't handling BundleLitBindings correctly. Partial Bundle literals may be broken.

@ducky64
Copy link
Contributor Author

ducky64 commented May 24, 2018

Also, those tests probably don't belong in LiteralExtractorSpec, since they deal more with Bundle literals than literal extractors.

@jackkoenig
Copy link
Contributor

The aliasing between Chisel === and ScalaTest === is annoying. I usually work around it by defining test modules that need === outside of the ScalaTest class. Maybe there's a better way though

@ducky64
Copy link
Contributor Author

ducky64 commented May 24, 2018

It might also not be a bad idea to discuss renaming assert because of the pain it's causing with ScalaTest.

case Some(litArg) => Some(litArg.num)
case _ => None
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaner as

override def litToBigIntOption: Option[BigInt] = litArgOption.map(_.num)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks!

Copy link
Contributor

@chick chick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks ok to me. There is some reasonable name changing like DynamicContext to ChiselContext that creates a lot of diff's but overall looks ok. This PR REQUIRES documentation in the wiki and probably some more tests after that doc is written. I'd suggest at least one more approval before merging

Copy link
Contributor

@jackkoenig jackkoenig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments, questions, suggestions

private[chisel3] def litIsForcedWidth: Option[Boolean] = litArgOption match {
case Some(litArg) => Some(litArg.forcedWidth)
case _ => None
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

case Some(intVal) if intVal == 0 => Some(false)
case Some(intVal) => throwException(s"Boolean with unexpected literal value $intVal")
case None => None
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think better as something like:

def litToBooleanOption: Option[Boolean] = litToBigIntOption.map { intVal =>
  if (intVal == 1) true
  else if (intVal == 0) false
  else throwException(s"Boolean with unexpected literal value $intVal")
}

or

def litToBooleanOption: Option[Boolean] = litToBigIntOption.map {
  case intVal if intVal == 1 => true
  case intVal if intVal == 0 => false
  case intVal => throwException(s"Boolean with unexpected literal value $intVal")
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like option 2 for the (grammatical) parallelism; fixed.

val multiplier = math.pow(2, binaryPoint.get)
Some(intVal.toDouble / multiplier)
case _ => None
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess across the board I think it's better to leave off the case _ => None by just using .map on Options

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

def isLit(): Boolean = litArg.isDefined

// If this is an element literal, returns the LitArg bound to it.
// INTERNAL API, but this isn't protected to allow bundle literal constructors.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a ScalaDoc indicating that this is really an internal API. Does it need to be public or could it be protected if it's accessed by Bundle literal constructors which are methods on children of Bundle right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking into more creative solutions to this.

The basic issue is this:

[error]  Access to protected method elementLitArg not permitted because
[error]  prefix type chisel3.Bool does not conform to
[error]  class MyBundle where the access take place
[error]           clone.b -> bVal.elementLitArg.get

though MyBundle is a type of Bundle which is a type of Data, accessing the override elementLitArg defined in Element seems to cause problems.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, added a protected Aggregate.litArgOfBits for this purpose, which depends on private[core] Bits.litArgOption

case _ => None
}
@chiselRuntimeDeprecated
@deprecated("litValue deprecated, use litToBigInt or litTo*", "chisel3.2")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be deprecated. litToBigInt does the same thing just with a different name so I see no reason to deprecate this for a slight name change. I agree with deprecating litArg since we generally don't want people accessing the Chisel IR though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think litValue is ambiguous (for example, on FixedPoint you would not get the original value out) and inconsistent with the new API style (litTo[Type]), and we should encourage one way of doing things, instead of having two equivalent representations (yes, I know Scala does this, and I think they're wrong).

We could also standardize on litValue instead of litToBigInt, though I think the latter is more clear. And the former is not consistent with the other literal extractors we would want (especially for the new testers), like litToBoolean or litToDouble.

@@ -28,8 +28,8 @@ class SwitchContext[T <: Bits](cond: T, whenContext: Option[WhenContext], lits:
def is(v: Iterable[T])(block: => Unit): SwitchContext[T] = {
if (!v.isEmpty) {
val newLits = v.map { w =>
require(w.isLit, "is conditions must be literals!")
val value = w.litValue
require(w.litToBigIntOption != None, "is condition must be literal")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

w.litToBigIntOption != None => w.litToBigIntOption.isDefined

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@edwardcwang
Copy link
Contributor

Agree with Chick on documentation. From a user perspective that actually might be more interesting to review.

@ducky64
Copy link
Contributor Author

ducky64 commented Jun 29, 2018

Resolution from today's meeting: don't deprecate litValue, because modifying public APIs is bad. Instead, litToBigInt will be removed to avoid duplication of features.

Alternatives explored:

  • Keep both litValue and litToBigInt, marking one as "legacy" in documentation. I think that marking things as legacy in documentation is near-worthless, since it's far from guaranteed that people read the documentation. Especially if we do our jobs right and method names are descriptive.
  • Introduce another level of deprecation, below the current Scala deprecation level, and use this to mark litValue as a legacy function (so there will be no expectation that it will be removed in the future and existing code needs to be changed). Having some visible warning / informational message automatically generated reduces issues with people not reading documentation. I personally like this one, but there were concerns about additional complexity.
  • Have both APIs. I think this just unnecessarily adds complexity, now you have to learn two names for equivalent functionality, and there will be the inevitable question of "what do these do that are different" followed by "wtf, why".

The downside is that litValue will be named inconsistently from litToDouble or litToBoolean, which I think are useful for extracting FixedPoint literals or using Bools in Scala conditionals.

@ducky64
Copy link
Contributor Author

ducky64 commented Jul 1, 2018

It seems like any literal naming stability issues would be caused by code in UserModule.scala:

    // All suggestions are in, force names to every node.
    for (id <- getIds) {
      id match {
        case id: BaseModule => id.forceName(default=id.desiredName, _namespace)
        case id => id.forceName(default="_T", _namespace)
      }
      id._onModuleClose

Having that filter by binding type probably names sense (in particular, ignore LitBinding, maybe also PortBinding since Ports need to be user-specified anyways) and should fix this.

@jackkoenig
Copy link
Contributor

It's my fault for not providing a better test, but this currently does not preserve the non-lit naming behavior of #826. I'll provide a better test

@@ -62,6 +62,12 @@ sealed abstract class Aggregate extends Data {
}
}

override def litOption = ??? // TODO implement me
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this TODO intended to be resolved in this PR or later?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is meant to be implemented later. Literal extractors on Bundles would be a new feature.


requireIsHardware(this, "bits to be indexed")
pushOp(DefPrim(sourceInfo, Bool(), BitsExtractOp, this.ref, ILit(x), ILit(x)))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for the historical record:

These are minor constant propagations done on literals. I did some archaeology to understand why they were there in the first place. They date to very early chisel3 7ada20e and appear to be largely to work around bugs in order to get riscv-mini compiling. Most of them have already been removed but a couple seem to have stuck around. I agree they should be removed.

case Some(ElementLitBinding(litArg)) => litArg
case Some(BundleLitBinding(litMap)) => litMap.get(this) match {
case Some(litArg) => litArg
case _ => throwException(s"internal error: DontCare should be caught before connect")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get that this is an internal (and thus unexpected) error, but is it wise to specifically mention "connect" here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, changed it to say ref instead.

@jackkoenig
Copy link
Contributor

It seems like any literal naming stability issues would be caused by code in UserModule.scala:
{code}
Having that filter by binding type probably names sense (in particular, ignore LitBinding, maybe also PortBinding since Ports need to be user-specified anyways) and should fix this.

Yes I agree. And in fact then we can do much more than #826 did because we could possibly prevent Bundle types from impacting temporary name suffixes as well! I'm okay with not doing that whole hog in this PR, just so long as we preserve the behavior from #826

Copy link
Contributor Author

@ducky64 ducky64 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserModule forceName is now filtered by binding type. BlackBoxes will not forceName at all (it probably doesn't make sense to have temporaries on BlackBoxes anyways).

@@ -62,6 +62,12 @@ sealed abstract class Aggregate extends Data {
}
}

override def litOption = ??? // TODO implement me
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is meant to be implemented later. Literal extractors on Bundles would be a new feature.

case Some(ElementLitBinding(litArg)) => litArg
case Some(BundleLitBinding(litMap)) => litMap.get(this) match {
case Some(litArg) => litArg
case _ => throwException(s"internal error: DontCare should be caught before connect")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, changed it to say ref instead.

Copy link
Contributor

@jackkoenig jackkoenig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! Thank you for all of your hard work on this

@ducky64 ducky64 merged commit 5c32cd8 into master Jul 4, 2018
@ducky64 ducky64 deleted the bundlelits branch July 4, 2018 23:39
@edwardcwang edwardcwang mentioned this pull request Jul 8, 2018
@ucbjrl
Copy link
Contributor

ucbjrl commented Jul 9, 2018

Unfortunately, commit 46f0aa3 / 93eedef breaks SimpleTBSpec tests in dsptools:

STARTING test_run_dir/SimpleTB.SimpleTBSpec1077962831/VSimpleLitModule
[info] [0.002] SEED 1531158343599
java.util.NoSuchElementException: None.get
	at scala.None$.get(Option.scala:347)
	at scala.None$.get(Option.scala:345)
	at chisel3.internal.HasId$class.getRef(Builder.scala:114)
	at chisel3.core.Data.getRef(Data.scala:201)
	at chisel3.internal.firrtl.Node.fullName(IR.scala:56)
	at chisel3.internal.firrtl.Slot.fullName(IR.scala:105)
	at chisel3.internal.HasId$class.instanceName(Builder.scala:119)
	at chisel3.core.Data.instanceName(Data.scala:201)
	at dsptools.DspTesterUtilities$.getName(DspTesterUtilities.scala:57)
	at dsptools.DspTester.expect(DspTester.scala:301)
	at dsptools.DspTester.expect(DspTester.scala:248)
	at dsptools.DspTester.expect(DspTester.scala:246)
	at SimpleTB.EasyPeekPoke$class.check(SimpleTBwGenTypeOption.scala:54)
	at SimpleTB.PassLitTester.check(SimpleTBwGenTypeOption.scala:259)
	at SimpleTB.EasyPeekPoke$class.checkP(SimpleTBwGenTypeOption.scala:34)
	at SimpleTB.PassLitTester.checkP(SimpleTBwGenTypeOption.scala:259)
	at SimpleTB.PassLitTester$$anonfun$8.apply(SimpleTBwGenTypeOption.scala:271)
	at SimpleTB.PassLitTester$$anonfun$8.apply(SimpleTBwGenTypeOption.scala:271)
	at scala.collection.Iterator$class.foreach(Iterator.scala:891)
	at scala.collection.AbstractIterator.foreach(Iterator.scala:1334)
	at scala.collection.IterableLike$class.foreach(IterableLike.scala:72)
	at scala.collection.AbstractIterable.foreach(Iterable.scala:54)
	at SimpleTB.PassLitTester.<init>(SimpleTBwGenTypeOption.scala:271)
	at SimpleTB.SimpleTBSpec$$anonfun$17$$anonfun$apply$6.apply(SimpleTBwGenTypeOption.scala:375)
	at SimpleTB.SimpleTBSpec$$anonfun$17$$anonfun$apply$6.apply(SimpleTBwGenTypeOption.scala:374)
	at chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1$$anonfun$apply$mcZ$sp$2.apply$mcZ$sp(Driver.scala:65)
	at chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1$$anonfun$apply$mcZ$sp$2.apply(Driver.scala:64)
	at chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1$$anonfun$apply$mcZ$sp$2.apply(Driver.scala:64)
	at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
	at chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1.apply$mcZ$sp(Driver.scala:63)
	at chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1.apply(Driver.scala:39)
	at chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1.apply(Driver.scala:39)
	at logger.Logger$$anonfun$makeScope$1.apply(Logger.scala:129)
	at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
	at logger.Logger$.makeScope(Logger.scala:127)
	at chisel3.iotesters.Driver$$anonfun$execute$1.apply$mcZ$sp(Driver.scala:39)
	at chisel3.iotesters.Driver$$anonfun$execute$1.apply(Driver.scala:39)
	at chisel3.iotesters.Driver$$anonfun$execute$1.apply(Driver.scala:39)
	at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
	at chisel3.iotesters.Driver$.execute(Driver.scala:38)
	at dsptools.Driver$$anonfun$execute$1.apply$mcZ$sp(Driver.scala:29)
	at dsptools.Driver$$anonfun$execute$1.apply(Driver.scala:26)
	at dsptools.Driver$$anonfun$execute$1.apply(Driver.scala:26)
	at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
	at dsptools.Driver$.execute(Driver.scala:26)
	at SimpleTB.SimpleTBSpec$$anonfun$17.apply(SimpleTBwGenTypeOption.scala:374)
	at SimpleTB.SimpleTBSpec$$anonfun$17.apply(SimpleTBwGenTypeOption.scala:368)
 ...

due to the fact that _ref is None.

@jackkoenig
Copy link
Contributor

I also found that 95f1e17 breaks connecting the result of Mux on aggregates in compatibility mode, I have a fix for that coming.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants