I would like to generate a Module IO interface from a spec I have stored in a scala variable.
I would like to produce this class definition:
class AddIfc extends Module {
val io = IO(new Bundle {
val a = Input(UInt(8.W))
val b = Input(UInt(8.W))
val o = Output(UInt(8.W))
})
}
from something like a list of tuples:
List( ("a", "in", 8), ("b", "in", 8), ("o", "out", 8))
I can imagine building an AST and evaluating it using some of the reflection capabilities in scala. Has anyone done this and have an example to show?
Take a look at Record (the parent class of Bundle). They're a bit more advanced because you have to implement elements (rather than reflection doing it for you in Bundle), and you have to implement cloneType.
There is an example in the Chisel tests.
Related
In Chisel3, I want to create a generic Bundle ParamsBus with parameterized type.
Then I follow the example on the Chisel3 website:
class ParamBus[T <: Data](gen: T) extends Bundle {
val dat1 = gen
val dat2 = gen
override def cloneType = (new ParamBus(gen)).asInstanceOf[this.type]
}
class TestMod[T <: Data](gen: T) extends Module {
val io = IO(new Bundle {
val o_out = Output(gen)
})
val reg_d = Reg(new ParamBus(gen))
io.o_out := 0.U
//io.o_out := reg_d.dat1 + reg_d.dat2
dontTouch(reg_d)
}
However, during code generation, I have the following error:
chisel3.AliasedAggregateFieldException: Aggregate ParamBus(Reg in TestMod) contains aliased fields List(UInt<8>)...
at fpga.examples.TestMod.<init>(test.scala:20)
Moreover, if I exchange the two lines to connect io.o_out, another error appears:
/home/escou64/Projects/fpga-io/src/main/scala/examples/test.scala:23:34: type mismatch;
found : T
required: String
io.o_out := reg_d.dat1 + reg_d.dat2
^
Any idea of the issue ?
Thanks for the help!
The issue you're running into is that the argument gen to ParamBus is a single object that is used for both dat1 and dat2. Scala (and thus Chisel) has reference semantics (like Java and Python), and thus dat1 and dat2 are both referring to the exact same object. Chisel needs the fields of Bundles to be different objects, thus the aliasing error you are seeing.
The easiest way to deal with this is to call .cloneType on gen when using it multiple times within a Bundle:
class ParamBus[T <: Data](gen: T) extends Bundle {
val dat1 = gen.cloneType
val dat2 = gen.cloneType
// Also note that you shouldn't need to implement cloneType yourself anymore
}
(Scastie link: https://scastie.scala-lang.org/mJmSdq8xSqayOceSjxHkRQ)
This is definitely a bit of a wart in the Chisel3 API because we try to hide the need to call .cloneType yourself, but least as of v3.4.3, this remains the case.
Alternatively, you could wrap the uses of gen in Output. It may seem weird to use a direction here but if all directions are Output, it's essentially the same as having no directions:
class ParamBus[T <: Data](gen: T) extends Bundle {
val dat1 = Output(gen)
val dat2 = Output(gen)
}
(Scastie link: https://scastie.scala-lang.org/TWajPNItRX6qOKDGDPnMmw)
A third (and slightly more advanced) technique is to make gen a 0-arity function (ie. a function that takes no arguments). Instead of gen being an object to use as a type template, it's instead a function that will create fresh types for you when called. Scala is a functional programming language so functions can be passed around as values just like objects can:
class ParamBus[T <: Data](gen: () => T) extends Bundle {
val dat1 = gen()
val dat2 = gen()
}
// You can call it like so:
// new ParamBus(() => UInt(8.W))
(Scastie link: https://scastie.scala-lang.org/JQ7D8VZsSCWP2i6DWJ4cLA)
I tend to prefer this final version, but I understand it can be more daunting for new users. Eventually I'd like to fix the issue you're seeing with a more direct use of gen, but these are ways to deal with the issue for the time being.
I'm new to Chisel, and I was wondering if it's possible to calculate constants in software before Chisel begins designing any circuitry. For instance, I have a module which takes one parameter, myParameter, but from this parameter I'd like to derive more variables (constant1 and constant2) that would be later used to initialize registers.
class MyModule(myParameter: Int) extends Module {
val io = IO(new Bundle{
val in = Input(SInt(8.W))
val out = Output(SInt(8.W))
})
val constant1 = 2 * myParameter
val constant2 = 17 * myParameter
val register1 = RegInit((constant1).U(8.W))
val register2 = RegInit((constant2).U(8.W))
//...
//...
}
Is there a way to configure Chisel's functionality so that an instance of MyModule(2) will first evaluate all Scala vals in software: constant1 = 2 * 2 = 4 and constant2 = 17 * 2 = 34. Then proceed to instantiate and initialize registers register1 = RegInit(4.U(8.W)) and register2 = RegInit(34.U(8.W))?
I was wondering if it's possible to calculate constants in software before Chisel begins designing any circuitry
Unless I'm misunderstanding your question, this is, in fact, how Chisel works.
Fundamentally, Chisel is a Scala library where the execution of your compiled Scala code creates hardware. This means that any pure-Scala code in your Chisel only exists at elaboration time, that is, during execution of this Scala program (which we call a generator).
Now, values in your program are created in sequential order as defined by Scala (and more-or-less the same as any general purpose programming language). For example, io is defined before constant1 and constant2 so the Chisel object for io will be created before either constants are calculated, but this shouldn't really matter for the purposes of your question.
A common practice in Chisel is to create custom classes to hold parameters when you have a lot of them. In this case, you could do something similar like this:
// Note this doesn't extend anything, it's just a Scala class
// Also note myParameter is a val now, this makes it accessible outside the class
class MyParameters(val myParameter: Int) {
val constant1 = 2 * myParameter
val constant2 = 17 * myParameter
}
class MyModule(params: MyParameters) extends Module {
val io = IO(new Bundle{
val in = Input(SInt(8.W))
val out = Output(SInt(8.W))
})
val register1 = RegInit((params.constant1).U(8.W))
val register2 = RegInit((params.constant2).U(8.W))
//...
//...
}
Hello I have a question regarding how to connect and map the ports between two modules, I will describe only the inputs and ouputs while excluding the control logic for each. The first module is a simple register whose output is the input to second module which is a demultiplexer
class simpleRegister extends Module {
val io = IO( new Bundle {
val enable = Input(UInt(1.W))
val in = Input(UInt(8.W))
val out = Output(UInt(8.W))
})
}
class demultiplexer extends Module {
val io = IO(new Bundle {
val datain = Input(UInt(8.W))
val dataout1 = Output(UInt(8.W))
val dataout2 = Output(UInt(8.W))
})
I am not sure if i should what method to use when I read " Interfaces & Bulk Connections" on github
You need to construct a top module that calls both the modules and perform the construction. This is pretty straight forward.
Your top module will look like this
class Top extends Module{
//You can expose a top level IO bundle if you will
val smplReg = Module(new simpleRegister)
val dmux = Module(new demultiplexer)
//connection of interest here is
dmux.io.datain := smplReg.io.out
//Make other connections as necessary
}
Interfaces and Bulk connections are when two IO bundles have similar fields instead of connecting each input/ output to the corresponding input/output you connect the bundle as a whole. This is just a way of optimizing your code.
Just noticed you can do something like:
class MyBundle1 extends Bundle {
val a = Bool()
val x = UInt(2.W)
val y = Bool()
}
class MyBundle2 extends Bundle {
val x = Bool()
val y = Bool()
}
class Foo extends Module {
val io = IO(new Bundle {
val in = Input(new MyBundle1)
val out = Output(new MyBundle2)
})
io.out := io.in
}
not get an error and actually get the correct or, at least, expected verilog. One would think that Chisel was type-safe wrt to Bundles in this sort of bulk connection. Is this intentional? If so, is there any particular reasons for it?
The semantics of := on Bundle allow the RHS to have fields that are not present on the LHS, but not vice-versa. The high-level description of x := y is "drive all fields of x with corresponding fields of y."
The bi-directional <> operator is stricter.
I can't speak precisely to the reasoning, but connection operators are often a matter of taste with respect to "do what I mean." The idea of including alternate forms of connection operators for future releases is an ongoing discussion, with the threat of "operator glut" being weighed against the ability of users to specify exactly what they'd like to do.
I have met some troubles while simulating design that contains comb-loop. Firrtl throws exception like
"No valid linearization for cyclic graph"
while verilator backend goes normal with warnings.
Is it possible to simulate such design with firrtl backend?
And can we apply --no-check-comb-loops not for all design but for some part of it while elaborating?
Example code here:
import chisel3._
import chisel3.iotesters.PeekPokeTester
import org.scalatest.{FlatSpec, Matchers}
class Xor extends core.ImplicitModule {
val io = IO(new Bundle {
val a = Input(UInt(4.W))
val b = Input(UInt(4.W))
val out = Output(UInt(4.W))
})
io.out <> (io.a ^ io.b)
}
class Reverse extends core.ImplicitModule {
val io = IO(new Bundle {
val in = Input(UInt(4.W))
val out = Output(UInt(4.W))
})
io.out <> util.Reverse(io.in)
}
class Loop extends core.ImplicitModule {
val io = IO(new Bundle {
val a = Input(UInt(4.W))
val b = Input(UInt(4.W))
val mux = Input(Bool())
val out = Output(UInt(4.W))
})
val x = Module(new Xor)
val r = Module(new Reverse)
r.io.in <> Mux(io.mux, io.a, x.io.out)
x.io.a <> Mux(io.mux, r.io.out, io.a)
x.io.b <> io.b
io.out <> Mux(io.mux, x.io.out, r.io.out)
}
class LoopBackExampleTester(cc: Loop) extends PeekPokeTester(cc) {
poke(cc.io.mux, false)
poke(cc.io.a, 0)
poke(cc.io.b, 1)
step(1)
expect(cc.io.out, 8)
}
class LoopBackExample extends FlatSpec with Matchers {
behavior of "Loop"
it should "work" in {
chisel3.iotesters.Driver.execute(Array("--no-check-comb-loops", "--fr-allow-cycles"), () => new Loop) { cc =>
new LoopBackExampleTester(cc)
} should be(true)
}
}
I will start by noting that Chisel is intended to make synchronous, flop-based, digital design easier and more flexible. It is not intended to represent all possible digital circuits. Fundamentally, Chisel exists to make the majority of stuff easier while leaving things that tend to be more closely coupled to implementation technology (like Analog) to Verilog or other languages.
Chisel (well FIRRTL) does not support such apparent combinational loops even if it possible to show that the loop can't occur due to the actual values on the mux selects. Such loops break timing analysis in synthesis and can make it difficult to create a sensible circuit. Furthermore, it isn't really true that the loop "can't occur". Unless there is careful physical design done here, there will likely be brief moments (a tiny fraction of a clock cycle) where there will be shorts which can cause substantial problems in your ASIC. Unless you are building something like a ring oscillator, most physical design teams will ask you not to do this anyway. For the cases where it is necessary, these designs typically are closely tied to the implementation technology (hand designed with standard cells) and as such are not really within the domain of Chisel.
If you need such a loop, you can express it in Verilog and instantiate the design as a BlackBox in your Chisel.