val pageReg: Page = RegInit({ val page = Wire(new Page()) page.initialize() page })
Showing posts with label Chisel. Show all posts
Showing posts with label Chisel. Show all posts
Thursday, September 6, 2018
Register a bundle in Chisel3
A quick update to a previous post that I made: http://blog.edmondcote.com/2017/03/register-bundle-in-chisel.html. Here is how I now create a register (sequential storage element) of a bundle in Chisel3. The benefit is the elimination of the previously-necessary wire declaration.
Friday, January 12, 2018
Address Masking for RAM Devices
I found the problem of generating the correct address for a memory operation confusing and decided that it would be of value to me (and others) to take notes.
Assume we have an implementation of a slave device on a TileLink2 bus. It can receive either read (Get) or write (PutFullData) messages on channel A and correctly return the response on channel D. More information about this in the spec.
The following covers the implementation of the module, specifically on the mask generation, nothing more.
Assume we have an implementation of a slave device on a TileLink2 bus. It can receive either read (Get) or write (PutFullData) messages on channel A and correctly return the response on channel D. More information about this in the spec.
The following covers the implementation of the module, specifically on the mask generation, nothing more.
Assumptions. The slave device covers address range 0x2000000, size 0x1FFF (or 8KB). The system bus uses 8 bytes per beat (matching the 64 bit bus). The memory width matches the bus width. Number of memory words is 1024 and address width is 10 bits.
val address = in.a.bits.address // receive the full 64-bit physical address
val size = 0x1FFF // 8KB size, get using addressSet.mask
val size = in.a.size // log2(bytes) of total amount of the data requested, e.g., 8B cache line == 1
Problem. To compute the word-aligned address, you need a mask. Here is an example worked out by hand.
2000_1000 ^ (0x2000_1000 & ~mask-1) = 0x1000 (byte aligned) // where mask = 0x2000
1000 / 8 = 0x200 (8B word aligned)
addr[9:0] = 0x200
That was the easy part. I got confused by the operation of the circuit generator. Isn't the following great ;)
val a_address = Cat((mask zip (in.haddr >> log2Ceil(beatBytes)).toBools).filter(_._1).map(_._2).reverse)
This is definitely one the least appealing parts of functional programming. It would take me a few hours to reverse engineer this. How this should work is any non-trivial function should be placed in its own container and unit tested. The unit tests are commented and serve as a description of the functional algorithm. But, I digress ...
There is a helper object in the util package. Moral of the story. Use it.
// This gets used everywhere, so make the smallest circuit possible ...
// Given an address and size, create a mask of beatBytes size
// eg: (0x3, 0, 4) => 0001, (0x3, 1, 4) => 0011, (0x3, 2, 4) => 1111
// groupBy applies an interleaved OR reduction; groupBy=2 take 0010 => 01
object MaskGen {
def apply(addr_lo: UInt, lgSize: UInt, beatBytes: Int, groupBy: Int = 1): UInt = {
[..]Saturday, October 21, 2017
Script to generate Verilog ports from Chisel source
Here's a script I wrote that will (naively) parse a Verilog file and output it's corresponding ports in Chisel format. This is useful to import a Verilog module as a blackbox in a Chisel design.
The chosen regular expression was testing using regex101.com, a populate online regular expression tester.
#!/bin/sh exec scala "$0" "$@" !# import scala.util.{Success, Try} import scala.util.matching.Regex val port = raw"\s*(input|output|inout)\s+(wire\s*|reg\s*)?\s*(\[(\d+):(\d+)\])?\s*(\w+)?\s*;?".r val p = scala.io.Source.fromFile(args(0)).getLines.foreach { f: String => val b = f match { case port(d, tzpe, _, msb, lsb, name) => val dir = d match { case "input" => "Input" case "output" => "Output" case "inout" => "Analog" case _ => } val w = Try({msb.toInt - lsb.toInt + 1}) match { case Success(s) => s"%d".W.format(s) case _ => "1.W" } s"val $name = $dir(UInt($w))" case _ => "" } if (b.nonEmpty) println(b) }
The chosen regular expression was testing using regex101.com, a populate online regular expression tester.
Monday, October 16, 2017
My Clocking and Reset Strategy with RawModule
This post is originally a reply to: https://groups.google.com/forum/#!topic/chisel-users/LTujWW6DtI4
The following snippet of code illustrates my top level clocking and reset strategy. I opt to use LazyRawModuleImp and explicitly specify the clocks and reset.
Module GSR is a Chisel BlackBox for my FPGA's GSR (global set reset) library cell.
The following snippet of code illustrates my top level clocking and reset strategy. I opt to use LazyRawModuleImp and explicitly specify the clocks and reset.
class LazyTop(implicit p: Parameters) extends LazyModule { lazy val module = new LazyRawModuleImp(this) { val io = IO(new Bundle { val ref_clk_p = Input(Clock()) val rst_sw_n = Input(Bool()) // async reset, debounced on board? val led_n = Output(Bits(8.W)) }) val GSR_INST = Module(new GSR()) // this is blackbox module GSR_INST.io.GSR := io.rst_sw_n val global_reset = Wire(Bool()) val global_reset_n = Wire(Bool()) global_reset_n := GSR_INST.io.GSR // appears GSR is active low global_reset := !GSR_INST.io.GSR // use active high clock withClockAndReset(ref_clk_p, global_reset) { io.led_n := RegNext("h5A".asUInt(8.W), init = 0.U) } } }
Module GSR is a Chisel BlackBox for my FPGA's GSR (global set reset) library cell.
class GSR extends BlackBox { val io = IO(new Bundle { val GSR = Input(Bool()) }) }
Wednesday, October 4, 2017
To link Chisel annotations and circuit info
Here's another snippet of Chisel code. It was well worth the (ashamed to admit) 4+ hrs to write. Admittedly, I am still getting a hand of functional programming. I am particularly proud of the recursive function call to populate an immutable val. The function's use case is to return Chisel circuit level information from an Annotation object. It is necessary because Annotation does not contain (do best of my knowledge) a reference to a Data object.
private def getDirection(circuit: Circuit, a: Annotation): ActualDirection = { // get net info from annotation object val thisModName = a.target.asInstanceOf[ComponentName].module.name val thisPortName = a.target.name.replace("io.", "") // FIXME: this is a HACK .. // find matching module in Chisel circuit val module = circuit.components.flatMap { case m: DefModule => Some(m) case b: DefBlackBox => None }.find { _.name == thisModName }.head // recursively return Element objects def elemsFromData(d: Data): Seq[Element] = d match { case e: Element => Seq(e) case r: Record => r.elements.flatMap((e: (String, Data)) => { elemsFromData(e._2) }).toSeq } // all elements for all module port val elems: Seq[Element] = module.ports.flatMap((p: Port) => { elemsFromData(p.id) }) // use DataMirror to find direction, have yet to test whether fully accurate // FIXME: obviously not optimal, should filter above val dir = DataMirror.directionOf(elems.find(_.name == thisPortName).head) dir }
Tuesday, September 26, 2017
Generate custom Chisel bundle using meta programming
I found the example to extend a Bundle's functionality limiting. You need to pass a varargs of (String,Data) tuple to the function. Here's a snippet:
I could have found a solution using the above (not sure, is it possible to convert ListMap to varargs?), but I dug into Scala meta programming for the purpose of ramp. Behold! Here's an alternative (though incomplete) method of generating a bundle.
The code above returns an instance of a typeless bundle. The result isn't equivalent to above, but serves my purpose. Have yet to try, but suspect the technique can be extended to other cases. There's likely implications wrt. statically typed nature of Scala. Use at your own risk. I am only posting this because it's a neat hack.
final class CustomBundle(elts: (String, Data)*) extends Record { val elements = ListMap(elts map { case (field, elt) => field -> elt.chiselCloneType }: _*) def apply(elt: String): Data = elements(elt) override def cloneType = (new CustomBundle(elements.toList: _*)).asInstanceOf[this.type] }
I could have found a solution using the above (not sure, is it possible to convert ListMap to varargs?), but I dug into Scala meta programming for the purpose of ramp. Behold! Here's an alternative (though incomplete) method of generating a bundle.
object MetaBundle { import reflect.runtime.currentMirror import tools.reflect.ToolBox val toolbox = currentMirror.mkToolBox() import toolbox.u._ def apply(): Bundle = { val tree = q""" import chisel3.core._ val b = new Bundle { // placeholder for your interface val i = Input(UInt(1.W)) val o = Output(UInt(1.W)) } b """
toolbox.eval(tree).asInstanceOf[Bundle] } }
The code above returns an instance of a typeless bundle. The result isn't equivalent to above, but serves my purpose. Have yet to try, but suspect the technique can be extended to other cases. There's likely implications wrt. statically typed nature of Scala. Use at your own risk. I am only posting this because it's a neat hack.
Monday, September 25, 2017
Driving random values on all elements of a bundle
Quick addition to the previous post. This technique is useful when prototyping hardware. Use LFSRs to drive pins/ports such that no logic pruning occurs its cone of logic during synthesis.
final def prng(bundle: Bundle): Unit = { bundle.elements.filter(_._2.dir == OUTPUT).foreach { case (n, d) => { val r = Module(new LFSR(d.getWidth)) when(true.B) { d := r.io.y } } } }
Example function to enhance Chisel bundle connection
Here's a real world example that demonstrates the flexibility of Chisel (or DSLs in general) compared to traditional languages for describing hardware.
It is my opinion that SystemVerilog's promise of making module connection more user (and verification) friendly has not been realized. The interface construct was a good first step, so were interface modports. The .* connection operator or parameterizable interfaces, not so much. Traditional teams send their best (aka. quickest) Perl monkey to the rescue and, just like that, a new target for feature creep and "I'm busy, fix the script yourself" is created.
You have access to an API when using an internal DSL approach (such as Chisel). If you wanted to write your own function to connect to arbitrary hardware interfaces - you do it. This doesn't directly eliminate feature creep, but provides a better framework for development than Perl/RegEx can offer. Consider this tradeoff. How many lines of parsing, data structure definition, testing and software regression strategies are needed compared to a few lines of Scala.
Here's an example from my private repo. It takes two Bundles as input and connects fields with matching names.
PS. Slowly, but surely, I'm becoming functional.
Its difficult for the time being, especially without a IDE. IntelliJ IDEA is terrific. Still can't properly explain a Monad. The ability to highlight a val, then hit "Alt-Enter", select "Add Type annotation to value definition", provides immeasurable value. Here's an example. By breaking up different parts of the above function, I can better determine (before compile time) the type of data structure that will be output.
It is my opinion that SystemVerilog's promise of making module connection more user (and verification) friendly has not been realized. The interface construct was a good first step, so were interface modports. The .* connection operator or parameterizable interfaces, not so much. Traditional teams send their best (aka. quickest) Perl monkey to the rescue and, just like that, a new target for feature creep and "I'm busy, fix the script yourself" is created.
You have access to an API when using an internal DSL approach (such as Chisel). If you wanted to write your own function to connect to arbitrary hardware interfaces - you do it. This doesn't directly eliminate feature creep, but provides a better framework for development than Perl/RegEx can offer. Consider this tradeoff. How many lines of parsing, data structure definition, testing and software regression strategies are needed compared to a few lines of Scala.
Here's an example from my private repo. It takes two Bundles as input and connects fields with matching names.
final def connect(left: Bundle, right: Bundle)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Unit = { (left.elements.toSeq ++ right.elements.toSeq) // Map to Seq, then combine lhs and rhs .groupBy(_._1) // group by name of .filter(_._2.length == 2) // filter out non matches .foreach { case (k, v) => { (lhs.dir, rhs.dir) match { case (NODIR, NODIR) => attach(lhs.asInstanceOf[Analog], rhs.asInstanceOf[Analog]) // to support INOUT ports case (INPUT, OUTPUT) => rhs := lhs case (OUTPUT, INPUT) => lhs := rhs case (INPUT, INPUT) => rhs := lhs // TODO: verify more case (OUTPUT, OUTPUT) => lhs := rhs // TODO: verify mode } } }
PS. Slowly, but surely, I'm becoming functional.
Its difficult for the time being, especially without a IDE. IntelliJ IDEA is terrific. Still can't properly explain a Monad. The ability to highlight a val, then hit "Alt-Enter", select "Add Type annotation to value definition", provides immeasurable value. Here's an example. By breaking up different parts of the above function, I can better determine (before compile time) the type of data structure that will be output.
val a: Map[String, Data] = left.elements val b: Map[String, Data] = right.elements val c: Seq[(String, Data)] = a.toSeq val d: Seq[(String, Data)] = a.toSeq ++ b.toSeq val e: Map[String, Seq[(String, Data)]] = d.groupBy(_._1)
Friday, August 25, 2017
Decomposing TileLink2 in RocketChip: A Barebones System
Quick post. I spent some time yesterday/today hacking at the TileLink2 implementation in RocketChip.
(As a matter of coincidence, SiFive recently released a draft spec of the protocol.)
My preferred method of learning is first to understand the engineer's original intent before enhancing or leveraging code. This means taking things apart.
The most difficult aspect in getting something up was understanding the inner workings of SystemBus class. I kept hitting run time assertions and it was helpful to manually sketch out the connections between the TL objects:
Here is a summary of my findings.
I identified the minimal set of configuration knobs for this experiment.
This is the system itself. It extends BaseComplex. Not RocketComplex.
This should be familiar. It is your top level Chisel3 module.
Here are the important parts of the system generator. This will generate your firrtl and verilog output.
Finally, one should never forget unit testing!
(As a matter of coincidence, SiFive recently released a draft spec of the protocol.)
My preferred method of learning is first to understand the engineer's original intent before enhancing or leveraging code. This means taking things apart.
The most difficult aspect in getting something up was understanding the inner workings of SystemBus class. I kept hitting run time assertions and it was helpful to manually sketch out the connections between the TL objects:
Here is a summary of my findings.
I identified the minimal set of configuration knobs for this experiment.
// Barebones system configuration class BarebonesSystemConfig extends Config((site, here, up) => { case XLen => 64 case BankedL2Params => BankedL2Params(nMemoryChannels = 0, nBanksPerChannel = 1, coherenceManager = { // FIXME: not really a coherence manager case (q, _) => implicit val p = q val cork = LazyModule(new TLCacheCork(unsafe = true)) (cork.node, cork.node) }) case SystemBusParams => SystemBusParams(beatBytes = site(XLen) / 8, blockBytes = site(CacheBlockBytes)) case PeripheryBusParams => PeripheryBusParams(beatBytes = site(XLen) / 8, blockBytes = site(CacheBlockBytes)) case MemoryBusParams => MemoryBusParams(beatBytes = 8, blockBytes = site(CacheBlockBytes)) case CacheBlockBytes => 64 case DTSTimebase => BigInt(1000000) // 1 MHz case DTSModel => "bboneschip" case DTSCompat => Nil case TLMonitorBuilder => (args: TLMonitorArgs) => Some(LazyModule(new TLMonitor(args))) case TLCombinationalCheck => false case TLBusDelayProbability => 0.0 })
This is the system itself. It extends BaseComplex. Not RocketComplex.
class BarebonesSystem(implicit p: Parameters) extends BaseCoreplex { // connect single master device (fuzzer) to system bus (sbus) sbus.fromSyncPorts() :=* LazyModule(new TLFuzzer(1)).node // connect single slave device (memory) to the periphery bus (pbus) LazyModule(new TLTestRAM(AddressSet(0x0, 0xfff))).node :=* pbus.toFixedWidthSingleBeatSlave(4) override lazy val module = new BarebonesSystemModule(this) } class BarebonesSystemModule[T <: BarebonesSystem](_outer: T) extends BaseCoreplexModule(_outer) {}
This should be familiar. It is your top level Chisel3 module.
// Chisel top module class Top(implicit val p: Parameters) extends Module { val io = new Bundle { val success = Bool(OUTPUT) } val dut = Module(LazyModule(new BarebonesSystem).module) dut.reset := reset }
Here are the important parts of the system generator. This will generate your firrtl and verilog output.
// System generator class System extends HasGeneratorUtilities { val names = new ParsedInputNames( targetDir = ".", topModuleProject = "com.bbones", topModuleClass = "Top", configProject = "", configs = "system" ) val config = new BarebonesSystemConfig val params = Parameters.root(config.toInstance) val circuit = elaborate(names, params) def generateArtefacts: Unit = ElaborationArtefacts.files.foreach { case (extension, contents) => writeOutputFile(names.targetDir, s"${names.configs}.${extension}", contents()) } def generateFirrtl: Unit = Driver.dumpFirrtl(circuit, Some(new File(names.targetDir, s"${names.configs}.fir"))) def generateVerilog: Unit = { import sys.process._ val firrtl = "/home/edc/gitrepos/rocket-chip/firrtl/utils/bin/firrtl" val path = s"${names.targetDir}/${names.configs}" val bin = s"$firrtl -i $path.fir -o $path.v -X verilog" bin.! } generateArtefacts generateFirrtl generateVerilog }
Finally, one should never forget unit testing!
// Unit test class SystemSpec extends FlatSpec with Matchers { "An instance of the system generator " should "compile" in { val s = new System } }
Monday, May 8, 2017
How to test a parameterized module in Chisel
Quick update today. Busy with real work. This quick post is written because how to test these modules in not adequately covered in the documentation for chisel-testers.
Consider the case for a parameterizable LSFR module.
Below I show a test containing two examples. First using PokeTester and the second using PeekPokeTester. See if you can spot the difference ...
In the PokeTester case, the first argument to test is new LSFR(new DDRCommand). For PeekPokeTester, you pass a function to generate the module () => new LSFR(new DDRCommand). Not immediately obvious. Now, look at the function prototypes for PokeTester and PeekPokeTester respectively ...
The definition of dutGen is different.
Wise to keep in mind that PokeTester is under iotesters.experimental.
Consider the case for a parameterizable LSFR module.
class LSFR[T <: Bundle](gen: T = UInt(4.W)) extends Module { val io = IO(new Bundle { val rand = Output(gen) }) val width = gen.getWidth val regLFSR = Reg(UInt(width.W), init = UInt(0)) when(true.B) { regLFSR := Cat(regLFSR(0) ^ regLFSR(1), regLFSR(width - 1, 1)) } io.rand := gen.fromBits(regLFSR) }
Below I show a test containing two examples. First using PokeTester and the second using PeekPokeTester. See if you can spot the difference ...
class LSFRPeekPokeTester[T <: Bundle](gen: T, c: LSFR[T]) extends PeekPokeTester(c) { println("peekpoke test") } class LSFRSpec extends ChiselFlatSpec with PokeTester { behavior of "LSFR" it should "elaborate with peek test" in { val options = new TesterOptionsManager options.setTargetDirName("test_run_dir/LSFRSpec") test(new LSFR(new DDRCommand), testerBackend = FirrtlInterpreterBackend, options = options) { (t, c) => { println("poke test") } } } it should "elaborate with peekpoke test" in { Driver(() => new LSFR(new DDRCommand)) { c => new LSFRPeekPokeTester(new DDRCommand, c) } } }
In the PokeTester case, the first argument to test is new LSFR(new DDRCommand). For PeekPokeTester, you pass a function to generate the module () => new LSFR(new DDRCommand). Not immediately obvious. Now, look at the function prototypes for PokeTester and PeekPokeTester respectively ...
// PokeTester source def test[T <: Module](dutGen: => T, testerBackend: TesterBackend, options: TesterOptionsManager)(block: (InnerTester, T) => Unit) { //[..] // PeekPokeTester source def apply[T <: Module]( dutGen: () => T, backendType: String = "firrtl", verbose: Boolean = false, testerSeed: Long = System.currentTimeMillis())( testerGen: T => PeekPokeTester[T]): Boolean = { //[..]
The definition of dutGen is different.
Wise to keep in mind that PokeTester is under iotesters.experimental.
Saturday, May 6, 2017
Use of HellaQueue in Chisel, secondary constructor
This post is an expanded update to an entry in my personal "NOTES" file. At first glance, I found the following code perplxing, therefore I decided to break it down.
What does the :< operator do in [T <: Data>]?
It is obvious (to me at least) that this is for type parameterization. The <: operator indicates that the type supplied to the class instance must have chisel3.core.Data as an ancestor.
Why does (data: => T) do?
No suprise that (val entries: Int) is the primary constructor. The fat arrow (=>) implies a function. The first argument to the constructor lets you specific the number of entries. The second argument data helps to parameterize the class.
Why is a class definion and a object definition of the same name?
The object is known as a companion class, consult stackoverflow.com for more info
What does the apply function do in the object?
The apply function acts as an auxillary constructor. Allows alternative syntax for creating HellaQueue.
class HellaQueue[T <: Data](val entries: Int)(data: => T) extends Module { val io = new QueueIO(data, entries) val fq = Module(new HellaFlowQueue(entries)(data)) fq.io.enq <> io.enq io.deq <> Queue(fq.io.deq, 1, pipe = true) } object HellaQueue { def apply[T <: Data](enq: DecoupledIO[T], entries: Int) = { val q = Module((new HellaQueue(entries)) { enq.bits }) q.io.enq.valid := enq.valid // not using <> so that override is allowed q.io.enq.bits := enq.bits enq.ready := q.io.enq.ready q.io.deq } }
What does the :< operator do in [T <: Data>]?
It is obvious (to me at least) that this is for type parameterization. The <: operator indicates that the type supplied to the class instance must have chisel3.core.Data as an ancestor.
Why does (data: => T) do?
No suprise that (val entries: Int) is the primary constructor. The fat arrow (=>) implies a function. The first argument to the constructor lets you specific the number of entries. The second argument data helps to parameterize the class.
// example usage val xy: Data val hq1 = (new HellaQueue(entries = 90)){xy} // equivalent to above, without (data: => T) in the class constructor val hq2 = (new HellaQueue[SInt](entries = 90))
Why is a class definion and a object definition of the same name?
The object is known as a companion class, consult stackoverflow.com for more info
What does the apply function do in the object?
The apply function acts as an auxillary constructor. Allows alternative syntax for creating HellaQueue.
Wednesday, May 3, 2017
Using IDEA Scala debugger for Chisel
This a quick follow up on an older post where I set up IntelliJ IDEA and Scala. I wrote this to make sure my steps for getting the debugger up and running were reproducable. The steps assume you have a working Chisel project that compiles using sbt.
Check if sbt compile works from the command line for your Chisel project ...
Check if sbt compile works from the command line for your Chisel project ...
[plus]:~/gitrepos/plus/arbiterdemo$ sbt compile [info] Loading global plugins from /home/edc/.sbt/0.13/plugins [info] Loading project definition from /home/edc/gitrepos/plus/arbiterdemo/project [info] Set current project to memctrldemo (in build file:/home/edc/gitrepos/plus/arbiterdemo/) [info] Updating {file:/home/edc/gitrepos/plus/arbiterdemo/}arbiterdemo... [..] [info] Done updating. [info] Compiling 3 Scala sources to /home/edc/gitrepos/plus/arbiterdemo/target/scala-2.11/classes... [success] Total time: 4 s, completed May 3, 2017 8:26:36 PM
Use the default project settings ...
Right click on your class that contains call to chisel3.Driver.execute, hit Run ...
Right click on your class that contains call to chisel3.Driver.execute, hit Run ...
Follow up by checking that that Verilog code is generated as expected (yes)
Finally, try setting a breakpoint by right clicking to the left of the source window and hit shift-F9 to begin debug. I was 100% successfull following only these steps on IDEA 2017.1 with the project-specific IDEA settings (i.e. .idea/ diretory) wiped out prior.
Finally, try setting a breakpoint by right clicking to the left of the source window and hit shift-F9 to begin debug. I was 100% successfull following only these steps on IDEA 2017.1 with the project-specific IDEA settings (i.e. .idea/ diretory) wiped out prior.
Subscribe to:
Posts (Atom)