A new, more hardware-friendly design for copilot-bluespec
#654
RyanGlScott
started this conversation in
Ideas
Replies: 2 comments 9 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.
0 replies
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-bluespec
generates 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-bluespec
generates 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 thatsw
is an input,led
comprises 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., theCLK
andRST
signals).My proposal is to change
copilot-bluespec
to generate the following Bluespec interface for the program above:This looks a bit different than what
copilot-bluespec
currently 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
sw
external stream is compiled to an interface method of typeUInt 8 -> Action
. The fact thatUInt 8
occurs 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_0
or some such.)The
led
trigger 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
led
as a single 9-bit value, and one would have to manually perform bit-arithmetic on the 9-bit value to extract the bits corresponding toledGuard
andledArg0
. This seemed a bit clunky, so I have opted not to do this.In addition to generating
LedIfc
,copilot-bluespec
will also generate a function to create a module instantiatingLedIfc
:A lot of this identical to what
copilot-bluespec
currently does, but note the following differences:mkLed
the typeModule LedIfc -> Module Empty
, we simply makemkLed
returnModule LedIfc
directly. An important hardware-related use case is to be able to compilemkLed
to Verilog in isolation, and the new type signature streamlines this process.{-# properties mkLed = { synthesize } #-}
to ensure that themkLed
function can be cleanly compiled to Verilog.Wire
(swWire
) for the purposes of definingsw
in theinterface
block. When thesw
is called, it simply writes its input toswWire
, and the written value is then used elsewhere in the module (e.g., in the implementation ofledArg0
).led
trigger. Instead, we do the necessaryled
-related computation in theinterface
block, where we defineledGuard
andledArg0
.Now, we can compile
mkLed
to Verilog:Which yields the following Verilog code:
Aside from the default
CLK
andRST_N
signals, we see exactly one input (sw
) and exactly two outputs (ledGuard
andledArg0
). 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.bs
package that simply outputs theled
value 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
led
trigger here instead of in the generated code formkLed
. Previously, we defined a callback function in the generatedLedIfc
interface 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