A new, more hardware-friendly design for copilot-bluespec
#654
RyanGlScott
started this conversation in
Ideas
Replies: 2 comments 10 replies
-
|
@RyanGlScott What would it take to prototype this change to copilot-bluespec? |
Beta Was this translation helpful? Give feedback.
9 replies
-
|
I've pushed a proof-of-concept implementation to this branch on my Copilot fork. Please do give it a try! Some further work needs to be done in polishing the documentation to reflect the new design, but I'll wait until we reach a consensus that we want to adopt this approach before doing so. |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Currently, the code that
copilot-bluespecgenerates is suitable for simulation, but it is not as suitable for use directly in hardware. One of the more notable challenges is that the generated Bluespec interfaces are compiled to Verilog in the opposite way that one would intuitively expect. Currently, a Copilot external stream is compiled into a Verilog output, and a Copilot trigger is compiled into a series of Verilog inputs. This is counterintuitive, as one would expect the external streams to be inputs instead, and would expect the values associated with a Copilot trigger to be outputs instead.Fortunately, I think it is possible to fix this design flaw with some changes to how
copilot-bluespecgenerates interfaces. To explain what I mean, I will use the following Copilot program (inspired by a question that @sukhmankkahlon asked here) as a running example:This example has one external stream (
sw), one stream that is internal to the program (counter), and another stream that is associated with a trigger (led). Our goal is to directly compile this to Verilog such thatswis an input,ledcomprises the outputs (note that "outputs" is plural—more on this later), and there are no other inputs or outputs (aside from Verilog's default ports, i.e., theCLKandRSTsignals).My proposal is to change
copilot-bluespecto generate the following Bluespec interface for the program above:This looks a bit different than what
copilot-bluespeccurrently generates, so let's review the differences:The
{-# always_ready, always_enabled #-}pragmas indicate that we should not generate extra "ready" or "enabled" inputs/outputs for the interface methods, which cuts down on the number of inputs/outputs that you have to managed in the generated Verilog code.The
swexternal stream is compiled to an interface method of typeUInt 8 -> Action. The fact thatUInt 8occurs in an argument position to a function means that it will turn into an input when compiled to Verilog. (We will see how to define and call this function shortly.)Also note the
{-# prefix = "", arg_names = ["sw"] #-}pragma, which is used to ensure that the corresponding Verilog input is also named "sw". (Otherwise, Verilog would give it a name likesw_0or some such.)The
ledtrigger is compiled into two interface methods. One method,ledGuard, returns aBool, and the other method,ledArg0, returns aUInt 8. Because both of these types are return types, they are both turned into outputs when compiled to Verilog. (Note that the numeric suffix in "ledArg0" indicates that it is the first argument associated with the trigger. If there were additional trigger arguments, then they would be compiled to additional interface methods namedledArg1,ledArg2, and so on.)I have made a choice to turn each Copilot trigger into multiple interface methods, and thus multiple outputs in the generated Verilog code. This is mainly done for the sake of convenience, as it gives distinct names to the different properties of a trigger handling function. Alternatively, we could bundle all of these things into one type, e.g.,
But this has the downside that the generated Verilog would represent
ledas a single 9-bit value, and one would have to manually perform bit-arithmetic on the 9-bit value to extract the bits corresponding toledGuardandledArg0. This seemed a bit clunky, so I have opted not to do this.In addition to generating
LedIfc,copilot-bluespecwill also generate a function to create a module instantiatingLedIfc:A lot of this identical to what
copilot-bluespeccurrently does, but note the following differences:mkLedthe typeModule LedIfc -> Module Empty, we simply makemkLedreturnModule LedIfcdirectly. An important hardware-related use case is to be able to compilemkLedto Verilog in isolation, and the new type signature streamlines this process.{-# properties mkLed = { synthesize } #-}to ensure that themkLedfunction can be cleanly compiled to Verilog.Wire(swWire) for the purposes of definingswin theinterfaceblock. When theswis called, it simply writes its input toswWire, and the written value is then used elsewhere in the module (e.g., in the implementation ofledArg0).ledtrigger. Instead, we do the necessaryled-related computation in theinterfaceblock, where we defineledGuardandledArg0.Now, we can compile
mkLedto Verilog:Which yields the following Verilog code:
Aside from the default
CLKandRST_Nsignals, we see exactly one input (sw) and exactly two outputs (ledGuardandledArg0). Nice!Besides running the code on hardware, another important use case is to be able to simulate the code (e.g., using Bluesim, Verilator, or some other simulator). The generated Bluespec code above is designed to be straightforward to simulate as well. For instance, here is an example
Top.bspackage that simply outputs theledvalue on each clock cycle:Note that there is a small amount of boilerplate needed here, since we now have to define the rule for the
ledtrigger here instead of in the generated code formkLed. Previously, we defined a callback function in the generatedLedIfcinterface for performing the trigger handling function's action, but this would result in unwanted inputs in the Verilog code, which is why I deliberately avoided doing so in the new design. Thankfully, the amount of boilerplate required per trigger isn't that bad: you just write"<trigger name>": when <module>.<trigger guard> ==> action ...and then fill in...with whatever action you want to take when the trigger fires.Beta Was this translation helpful? Give feedback.
All reactions