Showing posts with label UVM. Show all posts
Showing posts with label UVM. Show all posts

Friday, March 27, 2020

Modeling Random Stimulus and Functional Coverage in Python



If you've been following the blog over the last year, you've probably noticed that I've spent quite a bit of time over the last year learning and using Python. For several reasons, it's become my new favorite programming language. Until recently, I've mostly used Python as an implementation language. However, I've been curious as to how well Python works for implementing an embedded domain-specific language (eDSL). Most of my experience in this space has been with C++, so I was looking for an excuse to experiment.

In addition to using Python as a general programming language, I've also been using it as a testbench language for functional verification. I've spent time learning about the cocotb library that interfaces Python to a simulation engine, and in building an efficient task-based interface between Python and BFMs in simulation. With a decade or so developing OVM and UVM testbench environments, one thing I found myself missing in Python were the constraint and functional coverage modeling features that SystemVerilog provides. Now, to be clear, there is at least one existing Python library that provides random stimulus in Python, but after evaluating the approach and supported features against commonly-used SystemVerilog features, I decided to proceed in a different direction.

Functional Verification and Constrained Random Stimulus
Constrained-random stimulus and functional coverage have become well-ingrained in functional verification practice over the last decade or more. SystemVerilog, of course, embeds this functionality in the language, SystemC offers two libraries (SCV and CRAVE) for randomization. The Accellera Portable Test and Stimulus (PSS) language also specifies constrained-randomization and functional coverage features. These existing specifications all overlap on a few key features, though they also each have some unique features.

From a requirements perspective, I wanted to support a super-set of the constraint and functional coverage features from these existing sources to the extent possible. In addition to wanting to support as many useful features as possible, reuse was a key consideration. We're also beginning to see some open-source UVM-based libraries, such as the riscv-dv project from Google for generating RISC-V instruction streams, that include constraints and functional coverage. This library and others are implemented in SystemVerilog, of course, but could be translated to Python. The porting task is definitely eased if the constraints and coverage can simply be mechanically translated instead of being reworked and remodeled to target a different set of supported constructs.

Key Requirements
After a bit of investigation, I settled on the following requirements for my library, and decided to name it Python Verification Stimulus and Coverage (PyVSC).

  • Keep the user-visible modeling constructs as syntactically similar to SystemVerilog as possible and practical
  • Provide an underlying data model that can be programmatically processed to support static analysis, checking, and visualization of the user-specified constraints and coverage.
  • Allow users to capture simple features in natural syntax, while allowing them to programmatically build up the model for more-complex applications. 
  • Be able to take advantage of the availability and high performance of existing SMT solvers 

PyVSC Basics
The code below shows a simple example of capturing a class with random fields using the PyVSC library.
@vsc.randobj
class my_item_c(object):
    def __init__(self):
        self.a = vsc.rand_bit_t(8)
        self.b = vsc.rand_bit_t(8)

     @vsc.constraint
     def ab_c(self):
         self.a != 0
         self.a <= self.b
         self.b in vsc.rangelist(1,2,4,8)
Note that the class is identified as a randomizable class via the vsc.randobj decorator. Decorators, which Python supports as a first-class construct, enable classes and methods to be tagged as having special significance. They also allow additional functionality to be layered on. In this case, functions for randomization will be added to the class. The class inheritance hierarchy will also be altered slightly, to allow constraints and random fields to be configured after construction of a class instance. However, 

Class fields (both random and non-random) that will participate in randomization are declared using VSC types. This enables information on bit-width and randomizable status to be associated with class fields. 

Now, let's have a look at functional coverage.
  @vsc.covergroup
  class my_cg(object):

      def __init__(self):
          # Define the parameters accepted by the sample function
          self.with_sample(dict(
              it=my_item_c()
           ))

           self.a_cp = vsc.coverpoint( self.it.a, bins=dict(
              # Create 4 bins across the space 0..255
              a_bins = bin_array([4], [0,255])
           )
           self.b_cp = vsc.coverpoint(self.it.b, bins=dict(
              # Create one bin for each value (1,2,4,8)
              b_bins = bin_array([], 1, 2, 4, 8)
           )
           self.ab_cross = vsc.cross([self.a_cp, self.b_cp])
A covergroup is a class decorated with the vsc.covergroup decorator. As with a randomizable class, the decorator implements proper construction order, and adds methods to the target class. 

There are several ways that coverage data can be provided to a covergroup class. In the example above, coverage data will be passed as parameters to the 'sample' method. In order to do this, we must specify the sample-method parameter names and types. This is done by calling the with_sample method and passing a Python dictionary with parameter name and type. 

Coverpoints and crosses are defined using the coverpoint and cross methods. Coverpoint bins are declared as shown above, and support the set of value bins supported by SystemVerilog -- individual bins containing individual values and ranges of values, and bin arrays containing values partitioned across the bins.

Okay, let's put it all together. The code below creates an instance of the covergroup, an instance of the randomizable class, randomizes the class, and samples the data in the covergroup.
 # Create an instance of the covergroup
  my_cg_i = my_cg()

  # Create an instance of the item class
  my_item_i = my_item_c()

  # Randomize and sample coverage
  for i in range(16):
      my_item_i.randomize()
      my_cg_i.sample(my_item_i)

  # Now, randomize keeping b in the range [1,2]
  for i in range(16):
      with my_item_i.randomize_with() as it:
          it.b in vsc.rangelist(1,2)
      my_cg_i.sample(my_item_i)

  print("Coverage: %f \%" % (my_cg_i.get_coverage()))

The first randomization loop randomizes the class using constraints declared in the class. The second randomization loop adds an additional inline constraint.

Looking Forward
Over the next couple of posts, I'll go through the stimulus-generation and functional coverage features of PyVSC in more detail. Future posts will also tackle what to we can do in terms of static analysis of constraint models, as well as what to do with functional coverage data once we've collected it. Until then, feel free to have a look at the early documentation on readthedocs.io, and have a look at the PyVSC project on GitHub.


Disclaimer
The views and opinions expressed above are solely those of the author and do not represent those of my employer or any other party.

Saturday, July 27, 2019

Embedded Languages: The Space Between Language and API


We're all familiar with general-purpose programming language for capturing general algorithms, but there are also a sizeable group of domain-specific languages that exist to efficiently capture reasoning in a specific domain -- whether that's hardware design (Verilog, VHDL), database manipulations (SQL), or models at a high level of abstraction (UML/xtUML). These languages exist because the overhead is enormous for a domain expert to capture a problem in their given domain using a general-purpose programming language and APIs.

One of my favorite examples showing the motivation for domains-specific languages is spreadsheets. A spreadsheet is a language based around a namespace (table) where elements (cells) in the namespace are addressable by their coordinates, and whose values are represented by equations that may include references to other elements in the namespace. Just think how easy it is to setup a simple spreadsheet to do some what-if analysis, and how difficult it would be if you had to write a program to perform those calculations instead!

Simplistic though it may be, the spreadsheet perfectly captures the motivation behind domain-specific languages: focus on capturing the what of a given domain -- the key attributes, key relationships, and key operations -- and not on the how of the mechanics of how these elements would be represented in a general-purpose programming language. In short, a domain-specific language provides a user interface to complex algorithms phrased in familiar terms -- at least to someone knowledgeable in a that specific domain.

Taking the step of capturing domain knowledge in a new domain-specific language is a big step, though. There are a variety of reasons to defer taking that step or, perhaps, to not take that step at all.  Sometimes an entire language isn't required to implement the desired user interface. Sometimes it's desirable to have some benefits of a general-purpose language without the overhead of designing an entirely new all-in-one domain-specific and general-purpose language. The embedded domain-specific language is one approach that has been used to bring some benefits of a domain-specific language into an existing general-purpose programming language. The general approach is to use existing general-purpose language constructs, such as pre-processor macros and operator overloading, to build constructs with a domain-specific language feel within an existing language.

Within the set of embedded domain-specific languages that I'm aware of, I'm actually aware of three key styles of embedded a domain-specific language inside an existing general-purpose programming language.

Decorations and Annotations
One of the simplest domain-specific language integration techniques that I'm aware of is the decorator/annotation pattern. This style of domain-specific language is used to statically register classes or functions with a library framework.
class slave_address_map_info extends uvm_object;
  protected int min_addr;
  protected int max_addr;
  function new(string name = "slave_address_map_info");
    super.new(name);
  endfunction
  `uvm_object_utils_begin(slave_address_map_info)
    `uvm_field_int(min_addr, UVM_DEFAULT)
    `uvm_field_int(max_addr, UVM_DEFAULT)
  `uvm_object_utils_end

  // ...
endclass

While there are many examples of a decorator/annotation eDSLs, the example that came to mind first for me was the Universal Verification Methodology (UVM). UVM is a class library for functional verification built on top of the SystemVerilog domain-specific language. Two common operations that users of the UVM need to perform is registration of key user-defined types with the class library, and writing functions to clone, compare, and print class instances. Performing these operations in plain old code is time-consuming and error-prone. UVM provides a set of macros that allow the user to declare the existence of their user-defined class type and the fields within it (shown above highlighted in blue). 
The macros (SystemVerilog's key feature supporting embedded domain-specific languages) above cause the class type to be registered with the UVM class library, and implement functions for comparing, displaying, and cloning an object of this type. All from a high-level specification.


Enmeshed eDSL
Our next level of eDSL integration starts to look a bit more like a language. An Enmeshed eDSL provides the user statements that look a bit like a programming language, but are really driving algorithms behind the scenes. I call this style of integration Enmeshed because the user's general-purpose programming language code interacts closely with the algorithms driven by the eDSL as program runs.
class item : public rand_obj {
public:
item(rand_obj* parent = 0) : rand_obj(parent), src_addr(this), dest_addr(this) {
src_addr.addRange(0, 9);
src_addr.addRange(90, 99);
constraint(dest_addr() % 4 == 0);
constraint(dest_addr() <= reference(src_addr) + 3); 

}
   
randv<uint> src_addr;
randv<uint> dest_addr;
};
Our example of an Enmeshed eDSL comes courtesy of CRAVE, a constrained-random data generation package for the C++-based SystemC library. As you can see, the highlighted sections above look a bit more like a language. In this case, these are constraint expressions that control a constraint solver such that the values of src_addr and dst_addr obey the relationships established by the expressions.
When the user's program runs, it creates instances of classes like the one shown above, calls an API to create new random values for the random fields, and uses the values from those fields directly. In short, I consider the eDSL enmeshed with the host language because execution of the host language is interleaved with (effective) execution of the eDSL. The host language takes a primary role, and calls the eDSL code to provide specific services to the primary application.

Encapsulated eDSL
Our final level of eDSL integration is an embedded DSL that defines a new domain within the host language. There are several hardware-description languages embedded in general-purpose programming languages that fit this definition.

import chisel3._

class GCD extends Module {
  val io = IO(new Bundle {
    val a  = Input(UInt(32.W))
    val b  = Input(UInt(32.W))
    val e  = Input(Bool())
    val z  = Output(UInt(32.W))
    val v  = Output(Bool())
  })
  val x = Reg(UInt(32.W))
  val y = Reg(UInt(32.W))
  when (x > y)   { x := x -% y }
  .otherwise     { y := y -% x }
  when (io.e) { x := io.a; y := io.b }
  io.z := x
  io.v := y === 0.U
}

I've selected CHISEL (Constructing Hardware in a Scala-Embedded Language) as the example. What makes an encapsulated eDSL different is that the description made using the eDSL is monolithic and executed to create a single model -- in this case, Verilog. The GCD design show above might be used within a larger CHISEL-based design, but would never be used within a user's program to provide a useful service to the program. In a sense, an encapsulated eDSL description takes on a primary role within the host application. 


Embedding a DSL in Python
As we've seen, an embedded domain-specific language can provide a domain-specific interface to complex algorithms inside the confines of an existing general-purpose programming language. We've looked at several styles in which an embedded domain-specific language can be integrated into its host language -- all with different tradeoffs in terms of benefits and usability.
I've personally worked with embedded domain-specific languages in nearly every programming language I've used -- from C/C++ to TCL to Java. Most recently, though, I've been learning Python and (naturally) exploring the capabilities that Python offers for supporting an eDSL. Over the next few posts I'll look at Python's features that enable eDSL integration using a small eDSL I've been working on as an example.
In the meantime, what has your experience been with embedded domain-specific languages? Helpful or frustrating? Any notable examples -- either good or bad?


Disclaimer
The views and opinions expressed above are solely those of the author and do not represent those of my employer or any other party.

Wednesday, January 17, 2018

DVKit: Setting up SystemVerilog Development



In my last post on DVKit, I described how Eclipse uses projects to group source files, and uses workspaces to organize the projects and settings for a given development session. In this post, I'll start to dig into the support that DVKit provides for developing SystemVerilog.

DVKit includes the SVEditor plugin (http://sveditor.org), an open source Eclipse plug-in for developing SystemVerilog files. SystemVerilog is an object-oriented language that has similarities to C++ and Java. Unlike C++ and Java, though, SystemVerilog has strong ordering dependencies between files. C++ files can be independently analyzed because each C++ source file must include its dependencies and specify the namespace its content is in (if any). Java is even a bit more structured, requiring the class name and file name to match, and (effectively) requiring the directory structure to match the package namespace structure.

In contrast, all content in a SystemVerilog package must effectively be included in a single file. The pre-processor provides a level of workaround to enable classes to be stored in a separate file from the package file that includes them. However, this all adds up to make setting up source analysis for a SystemVerilog file a bit more detail-oriented than for other source languages.

In this post, I'll walk through setting up an Eclipse (DVKit) project for the UBus example from the UVM-1.2 library. I downloaded UVM here.

Creating a SystemVerilog Project

As mentioned in the last post, Eclipse makes it easy to create a project around existing source code. In this case, we know we will be working with SystemVerilog source, so we start by creating a new SVE Project. The wizard is found inside the SVEditor category, as shown below.


After selecting the proper wizard for project creation, we need to specify the particulars of the project:



Specifically, in this case:
  • ubus -- The name of the project
  • c:\usr1\fun\dvkit\uvm-1.2\examples\integrated\ubus -- The location of the ubus project within the UVM tree

Specifying Root Files

Since the ubus project contains existing sources, we next want to specify the root files so they can be indexed. SystemVerilog files need to be parsed in a very specific order, so it's important to only process the top-level files.

To do this, we first create a New Filelist on the Filelists page of the new SVE Project wizard. The filelist can be named anything, but the default (sve.f) is fine.


The next page is where the magic happens. 
  • Select the check box next to the 'ubus' project. This will cause all files with a SystemVerilog suffix (.sv, .svh, etc) to be selected
  • Click on the 'Compute Filelist' button. This pre-processes all source files and eliminates any files that are included by another. 
  • The resulting root files are displayed in the Filelist Contents box

Checking out the new Project

At this point, we've specified the root files in our project in such a way that the SVEditor plug-in can locate and parse them. We can now click Finish on the wizard and see the completed project.

Hmm... Okay, so we have a problem. Seems some macros from the UVM library can't be located. Of course, this isn't really surprising given than we haven't told the SVEditor plug-in to parse the UVM files.

Adding External Sources

Eclipse provides several ways to reference project-external source files. The easiest in this case is to just add the UVM library to the filelist that we already created.

We edit the sve.f file that we created during the project-creation process, and add two absolute paths to where we unpacked the UVM bundle:
  • +incdir+<uvm_install>/src
  • <uvm_install>/src/uvm_pkg.sv
After saving the file, we now have a project without errors.

Results


The payoff for properly configuring our SystemVerilog source project is that we can more-productively work with our SystemVerilog sources. In the screenshot above, the hover pop-up is displaying the documentation for the uvm_driver class. 

In future blog posts I'll dig into more features of Eclipse, DVKit, and plug-ins like SVEditor. For now, just a reminder that you can always download the completely open source DVKit here.

Tuesday, January 2, 2018

DVKit: More-productive Code Development for DV Engineers



If you're a design verification (DV) engineer, how many different languages do you code in every day? If you're like me, the number is significant. On any given day, I might find myself working on SystemVerilog, PERL, shell script, Makefile, even C++. Text editors like Vim and a host of others can edit any text file, and even provide some basic syntax coloring.

How Do Integrated Development Environments Help?

Integrated Developmene Environments (IDEs) provide features far beyond those provided by simple text editors. While a text editor is typically aware of the current file being edited, an IDE is aware of the files related to the current file being edited. An IDE typically provides syntax and semantic checking for code as it is developed, which results in more accurate code creation and fewer mistakes being discovered during compilation. An IDE also provides features for navigating across the content of the code being developed -- for example, navigating to the declaration of a class from its usage. IDEs also provide on-the-fly content assist based on the declarations in the code under development -- for example, prompting the user with the methods available in a specific class.

There are a number of IDEs available, but many tend to focus on a handful of languages. Visual Studio, for example, focuses on C, C++, and C#. NetBeans focuses on Java and web languages. With the large variety of languages used for design verification, we need an IDE with support for the same large variety of languages.

Eclipse

If you're looking for a true integrated development environment (IDE) for multiple languages, the Eclipse platform is a natural choice. Originally developed by IBM as an integrated development environment for Smalltalk and Java, Eclipse has evolved into an open source platform that supports development of a dizzying array of languages -- from Java to C++ to ANTLR and beyond. As an integrated development environment, Eclipse not only provides text editing and syntax highlighting, it provides support for easily navigating code structure. For example, navigating from where a field is used to where it was declared.

DVKit

I've used Eclipse as a primary development tool for the past 12 years or so. Eclipse follows a plug-in-based architecture, and makes it easy to install new plug-ins for developing various languages. So, it's not terribly difficult to "roll your own" installation of Eclipse that contains your favorite plug-ins. About four years ago, I realized that there were two challenges with continuing to roll my own. First, it was time consuming. Remembering the web sites for all my favorite plug-ins and installing them every time a new version of Eclipse was released, on every development machine I use, was a hassle. Second, I didn't have a good way to share my favorite collection of Eclipse plug-ins with others.

So, about four years ago I created DVKit. DVKit is based on Eclipse and packages all my favorite plug-ins for developing languages such as:
  • SystemVerilog
  • PERL
  • Python
  • Shell
  • C/C++
  • Java
  • Javascript
  • Scala
  • Makefile
  • TCL
  • XML
  • YAML
DVKit is available for Linux, Windows, and Mac OS-X. On Windows, an installer is available to make it extra simple.

You can find DVKit at http://dvkit.org, and the downloads page here.

Over the next few blog posts, I'll introduce you to the fundamentals of developing code with DVKit and Eclipse, and some of the language-specific features provided with DVKit. In the meantime, I would encourage you to download DVKit and begin to explore how an IDE can help boost your development productivity.

Saturday, August 19, 2017

Chisel Sharpening: If it's not tested, it's broken

"If it's not tested, it's broken."

-- Bruce Eckel

I'm a big believer in the quote above, and cite it somewhat frequently -- perhaps to the tedium of my colleagues. In my last post, I showed a Chisel3-based description of a Wishbone interconnect. While it might have looked cool, without tests I had no idea whether it worked correctly or not. After adding a simple UVM testbench around my interconnect I can, yet again, confirm the truth of the quote above. But, enough pontificating, let's dig into the specifics.

Error Types

Especially when implementing something while learning a new language or technique, I find that I make four types of errors:
  • Implementation errors and oversights - There are your run of the mill bugs. For example, I neglected to implement an intended feature, or I implemented the logic incorrectly. Good planning helps to minimize these errors, but they are why we place such value on good verification.
  •  Errors in description - These are learning mistakes related to the new language or technique. I structured a description around my understanding of the language/technique, only to find that it resulted in unexpected behavior. The ease or difficulty in avoiding and/or diagnosing errors in description is a key determinant for me in deciding how easy a new technique is to adopt. 
  • Errors in reuse - This type of error occurs when reusing existing IP, only to find that it functions differently from my understanding. 
  • Tool or library issues - These are errors that you hope not to encounter, but do crop up from time to time. 
Not surprisingly, I encountered the first three categories of errors while verifying my Wishbone interconnect.  I did encounter one tool/library issue, but I'll get to that later...

UVM Testbench

I decided to verify a 2x4 configuration of the Wishbone interconnect, and created a very (very) basic UVM testbench around the interconnect and instantiated two Wishbone master agents and four memory target devices - mapped at 0x00000000, 0x00001000, 0x00002000, and 0x00003000, respectively.


And, a very basic write/read test:
























I ran this in Questa (the Altera Modelsim Starter Edition, to be precise). On the first test run, nothing worked and quite a few signals were driven to X. In order to accelerate progress, I turned on register randomization (something supported by the Verilog code generated by Chisel).

Reuse Error: Chisel Arbiter

My simple write/read test causes both masters to perform a write to the same target device as the first operation. I had assumed that the Chisel-provided arbiter would grant the output to the selected master until that master dropped its request. However, this turned out to not be the case. It took browsing the source code (for me at least) to understand the mechanism the library developer had provided for controlling the arbiter locking behavior. Once I understood that mechanism, it was very simple to customize the locking behavior.
The arbiter is such a useful and reusable construct that I'll devote a future post to it, rather than delve into the details here.

Description Error: Multiple Assignments

My original interconnect description used a nested loop structure across the masters and slaves, with a conditional assignment to propagate back the slave response. 

  for (i <- 0 until p.N_SLAVES) {
    for (j <- 0 until p.N_MASTERS) {
      // ...
      
      // Propagate slave response back to active master
      when (out_arb(i).io.in(j).ready) {
          in_rsp(j) := out_rsp(i);
      } .otherwise {
          in_rsp(j).park_rsp();
      }
    }
    out_arb(i).io.out.bits.assign_req2p(io.s(i));
  }
  
As it turns out, this code trips over one of the corner cases of Chisel: when procedural code (eg for loops) make multiple assignments, the last assignment is taken. In this case, that means that both masters were being fed the response from the last slave device.
Investigating description errors like these unfortunately involve digging into the generated Verilog code. This is both tedious and not quite as bad as it sounds. Chisel picks sensible names for module I/O signals, so these are easy to track. However, Chisel also generates lots of anonymously-named internal signals (eg _T_51) that are used to implement the logic within the module.
I don't have a concrete proposal for the Chisel authors on how to improve this situation, but I would like to think a graphical view, such as a schematic, might be helpful in relating the input Scala code to the resulting Verilog.

Reuse Error: Mux Arguments

After better-understanding Chisel's behavior with respect to multiple assignments, I decided that using Chisel's 1-hot Mux primitive would be the best way to handle the response data. Here I bumped into a limitation of the Mux that the Arbiter primitive allowed me to ignore: multiplexing bundles with signals of different I/O directions is not supported (and, sadly, only uncovered very late in the transformation process from Chisel to Verilog). It all makes a lot of sense once you think it through.
Understanding this limitation drove me to redefine the I/O bundles I used to describe the Wishbone interface. It was a fairly straight-forward process, and one that I'll describe in more depth in a future post on structuring I/Os for standard interfaces with Chisel.

Understanding Data Manipulation Techniques

 Chisel encourages descriptions that involve collections of data. In some ways, this isn't so different from other hardware-description languages. What's different is the set of operators Chisel provides for manipulating these data collections. One early example I ran across was implementing address decode for the masters. I had arrays of target base/limit addresses, and wanted to determine which target device each master was selecting. This was very easy (and compactly) described with the following code:

    val slave_req = io.addr_base.zip(io.addr_limit).map(e => (
        io.m(i).ADR >= e._1 && io.m(i).ADR <= e._2) &&
        io.m(i).CYC && io.m(i).STB)

This code describes the following:
  • Combine the addr_base and addr_limit arrays into an array of (addr_base,addr_limit) tuples using the 'zip' operation
  • Convert this array of tuples into an array of Bool where the entry is 'true' if the target is selected
These techniques also apply nicely to selecting fields from composite data structures, as shown below. In the case below, we want to determine whether a given target device is actively selected by any master.

      when (out_arb(j).io.in.map((f) => f.valid).reduceLeft(_|_)) {
        out_arb(j).io.out.bits.assign_b2(io.s(j));
      } .otherwise {
        // If no master is requesting, deactivate the slave requests
        io.s(j).park_req()
      }

In this case, the code does the following:
  • out_arb(j).io.in is an array of composite data going into the per-target arbiters. The map() operation selects just the 'valid' field from each array element
  • Then, the reduceLeft() operation performs a reduction across the array
Now, both of these operations can be described in other hardware description languages. But both likely would involve several layers of temporary data fields. It's actually really nice to be able to describe the high-level view of the manipulation to be performed, and be confident that a sensible implementation of this implementation will be inferred (and, after having to dig into the implementation for other reasons, I can state that the implementation is sensible).

Tool Issue: Register without Reset

I mentioned earlier that I had turned on register initial-value randomization when I first started simulations. After getting my test running correctly, I had hoped this would not be needed. However, it turns out that Chisel's Arbiter primitive contains a register without a reset value. Perhaps this hasn't created an issue for many Chisel users because the Verilator 2-state simulator is often used. However, with a 4-state simulator like Questa/Modelsim, an uninitialized register is a fatal issue that results in X propagation and a non-functioning design.

I strongly recommend using registers that are reset, and will provide this feedback to the Chisel team. 

Conclusions

I'd class many of the issues I faced as all part of the learning curve for a new tool or technique. Challenging (and sometimes time-consuming) to surmount, perhaps, but issues I'd likely not face in the future. I've also gained an new-found appreciation for the descriptive power that Chisel's support for Scala's collection-manipulation operators bring to the description of hardware. 

For now, I have a working Wishbone interconnect described with Chisel. And, despite a few hiccups along the way, I'm still feeling pretty good about the expressive power that Chisel brings to hardware description.


Next, I'm curious to see how synthesis results compare for a hand-coded Wishbone interconnect and the Chisel-generated one. 
As always, you can find the source for my experiments here: