Skip to content

Conversation

@RyanGlScott
Copy link
Collaborator

@RyanGlScott RyanGlScott commented Nov 3, 2025

The current Bluespec backend leads to Verilog code that requires manual manipulations in order to work correctly. Specifically, Copilot externs, which are inputs to the Copilot monitoring system, are treated as outputs in Verilog, and Copilot triggers, which can be considered outputs of the monitoring system, are treated as inputs. This order of inputs and outputs is the opposite what we would like users to work with, and of what other backends generate (e.g., C99).

This PR updates copilot-bluespec to flip the order in which generated Bluespec module interfaces declare their inputs and outputs. A module interface now represents each Copilot extern as a method of type <ty> -> Action (where <ty> is the type of the extern), as this type directly translates to a Verilog input. Moreover, a module interface now represents trigger arguments as interface methods of type <ty>_i, where each <ty>_i is the type of each trigger argument. Each <ty>_i directly translates to a Verilog output.

In addition to changing the module interface definitions, this PR also changes the generated modules that instantiate the new interfaces. One of the more notable changes is that all extern values are stored as a Bluespec Wire in the module internals, as this leads to compact Verilog code without adding any user-visible inputs or outputs. Every time a value is read from an extern, the corresponding Wire must be read from, which means that several parts of the module internals have been updated to reference these Wires.

This PR also updates the test suite and DESIGN.md document accordingly.

Fixes #677.

@RyanGlScott RyanGlScott force-pushed the develop-hardware-friendly-bluespec branch from ec63500 to b7573e4 Compare November 3, 2025 12:34
@RyanGlScott
Copy link
Collaborator Author

I'm not quite sure why Travis CI is timing out... any ideas?

Nothing
[]
(BS.CQType [] (tWire `BS.TAp` transType ty))
(BS.CVar (BS.mkId BS.NoPos "mkBypassWire"))
Copy link
Member

@ivanperez-keera ivanperez-keera Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change Manager: Does this work with multiple externs and multiple triggers?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! When compiling a Copilot specification with multiple externs, mkExtWireDecln is called on each extern, thereby producing a separate Wire value for each extern:

mkExample :: Module ExampleIfc;
mkExample =
    module {
      extern1_wire :: Wire (UInt 8) <- mkBypassWire;
      extern2_wire :: Wire (UInt 8) <- mkBypassWire;
      ...

Where the externs are named extern1, extern2, etc.

Multiple triggers are also supported, although the mkExtWireDecln function doesn't interact directly with the compilation of triggers.

[ BS.PIPrefixStr ""
, BS.PIArgNames [BS.mkId BS.NoPos $ fromString $ lowercaseName name]
]
(BS.tArrow `BS.TAp` transType ty `BS.TAp` BS.tAction)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change Manager: What is this?

(BS.tArrow BS.TAptransType tyBS.TAp BS.tAction)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the Bluespec abstract syntax for the type ty -> Action, which is the type of the generated interface method for an extern.

Like in Haskell, the function type (->) is represented as a type constructor that is applied (using BS.TAp, short for "type application") to two type arguments, the argument type <ty> and the result type Action. As a result, the AST for this type is:

BS.TAp BS.tArrow (BS.TAp (transType ty) BS.tAction)

It becomes a bit difficult to read these ASTs when there are a lot of applications (i.e., a lot of BS.TAps) involved, so I usually write the BS.TAp applications as infix to cut down on the amount of parentheses required. This is just a stylistic preference, however—I'm fine with writing using prefix applications (like in the code shown above) if you'd prefer that.

-- language-bluespec's pretty-printer will error if it
-- encounters a CSletrec with an empty list of definitions, so
-- avoid generating a CSletrec if there are no streams.
[ BS.CMStmt $ BS.CSletrec genFuns | not (null genFuns) ] ++
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change Manager: Is this (map (BS.CMStmt . BS.CSletRec) genFuns)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, they're not the same. Your suggested code wouldn't typecheck, as CSletrec expects [BS.CDefl] as an argument, whereas your code would only typecheck if BS.CSletrec took a single BS.CDefl as an argument.

@ivanperez-keera
Copy link
Member

ivanperez-keera commented Nov 6, 2025

Change Manager:

  • See change list above.
  • In the second commit message: Now that copilot-bluespec has been updated to invert the order in which
    Verilog inputs and outputs are declared" -> rephrase to read "A prior commit has . ."
  • Do not use this as a pronoun to refer to this commit. Best to always qualify what this is (e.g., this commit, this module, this ...).
  • Is the change to use input1 instead of input something that resulted from this PR, or something that was a problem before but only was detected now?

Copy link
Member

@ivanperez-keera ivanperez-keera left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change Manager: See individual comments.

…luespec. Refs Copilot-Language#677.

The current Bluespec backend leads to Verilog code that requires manual
manipulations in order to work correctly. Specifically, Copilot externs, which
are inputs to the Copilot monitoring system, are treated as _outputs_ in
Verilog, and Copilot triggers, which can be considered outputs of the
monitoring system, are treated as _inputs_. This order of inputs and outputs is
the opposite what we would like users to work with, and of what other backends
generate (e.g., C99).

This commit updates the internals of `copilot-bluespec` to flip the order in
which generated Bluespec module interfaces declare their inputs and outputs. A
module interface now represents each Copilot extern as a method of type `<ty>
-> Action` (where `<ty>` is the type of the extern), as this type directly
translates to a Verilog input. Moreover, a module interface now represents
trigger arguments as interface methods of type `<ty>_i`, where each `<ty>_i` is
the type of each trigger argument. Each `<ty>_i` directly translates to a
Verilog output.

In addition to changing the module interface definitions, this commit also
changes the generated modules that instantiate the new interfaces. One of the
more notable changes is that all extern values are stored as a Bluespec `Wire`
in the module internals, as using `Wire`s leads to compact Verilog code without
adding any user-visible inputs or outputs. Every time a value is read from an
extern, the corresponding `Wire` must be read from, which means that several
parts of the module internals have been updated to reference these `Wire`s.
…new input/output order. Refs Copilot-Language#677.

The current Bluespec backend leads to Verilog code that requires manual
manipulations in order to work correctly. Specifically, Copilot externs, which
are inputs to the Copilot monitoring system, are treated as _outputs_ in
Verilog, and Copilot triggers, which can be considered outputs of the
monitoring system, are treated as _inputs_. This order of inputs and outputs is
the opposite what we would like users to work with, and of what other backends
generate (e.g., C99).

A prior commit has updated the internals of `copilot-bluespec` to invert the
order in which Verilog inputs and outputs are declared. This commit adapts the
top-level code in `Copilot.Compile.Bluespec.Compile` accordingly. In
particular, the `compileWith` function now generates a `<Prefix>.bs` file that
defines two module interfaces: one interface that contains the core methods
that will comprise the generated Verilog code, and another interface that helps
automate the process of defining a `Module Empty` value that is more suitable
for software simulation purposes.

Previously, `compileWith` generated a separate `<Prefix>Ifc.bs` file that
contained nothing but the module interface in isolation so that it could be
used separately from the interface's `Module` definition. This distinction no
longer makes as much sense, as in the new design, as (1) there are now multiple
interfaces, and (2) it is unlikely that the interfaces are not particularly
useful without having the `Module` definitions to accompany them. As such, this
commit now omits generating a `<Prefix>Ifc.bs` file, instead opting to generate
the interfaces as part of `<Prefix>.bs`.
…Refs Copilot-Language#677.

The current Bluespec backend leads to Verilog code that requires manual
manipulations in order to work correctly. Specifically, Copilot externs, which
are inputs to the Copilot monitoring system, are treated as _outputs_ in
Verilog, and Copilot triggers, which can be considered outputs of the
monitoring system, are treated as _inputs_. This order of inputs and outputs is
the opposite what we would like users to work with, and of what other backends
generate (e.g., C99).

A prior commit has updated the internals of `copilot-bluespec` to invert the
order in which Verilog inputs and outputs are declared. This commit updates the
test suite to work with the new design. In addition to changing the code to
reflect the new order of inputs and outputs, this commit also updates the names
of some internal variables from "`input`" to "`input1`". This change is
necessary because "`input`" is a reserved keyword in Verilog, and giving a
Bluespec interface method the same name as a reserved Verilog keyword becomes
an error when the interface is used in the result type of a synthesized module.
… Refs Copilot-Language#677.

The current Bluespec backend leads to Verilog code that requires manual
manipulations in order to work correctly. Specifically, Copilot externs, which
are inputs to the Copilot monitoring system, are treated as _outputs_ in
Verilog, and Copilot triggers, which can be considered outputs of the
monitoring system, are treated as _inputs_. This order of inputs and outputs is
the opposite what we would like users to work with, and of what other backends
generate (e.g., C99).

A prior commit has updated the internals of `copilot-bluespec` to invert the
order in which Verilog inputs and outputs are declared. This commit updates the
prose in the `DESIGN.md` document to reflect the new design. The new design is
just different enough from the old one that it made sense to completely rewrite
certain portions of the document. One of the more notable changes is that there
are now separate sections about running the generated Verilog code on hardware
versus simulating the generated code in software, as these are distinct use
cases that require separate discussion.
@RyanGlScott RyanGlScott force-pushed the develop-hardware-friendly-bluespec branch from b7573e4 to 5577557 Compare November 7, 2025 00:49
@RyanGlScott
Copy link
Collaborator Author

Is the change to use input1 instead of input something that resulted from this PR, or something that was a problem before but only was detected now?

The former.

Whenever you run bsc -g <function>, Bluespec requires that <function> have the result type Module <Ifc>, and moreover, <Ifc> must not have any methods with names that clash with reserved Verilog keywords (e.g., input). Prior to this PR, we were always compiling <function>s with types that look like:

mkMod :: Module <Ifc> -> Module Empty

Here, the result type is Module Empty, and because Empty has no interface methods, it trivially meets the requirement that none of its interface methods clash with Verilog keywords. Note that it did not matter what the names of <Ifc>'s methods were, as the naming requirements only apply to the result type.

After this PR, however, we now compile <function>s with types that look like this:

mkMod :: Module <Ifc>

Now Bluespec does check if <Ifc>'s methods clash with reserved Verilog keywords, so it rejected the use of input as a method name in the copilot-blue spec test suite. Renaming input to input1 was the most straightforward way to fix this. Moreover, it parallels how we handle test cases with multiple externs, where we distinguish the externs by naming them input1 and input2.

@RyanGlScott
Copy link
Collaborator Author

Implementor: Fix implemented, review requested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

copilot-bluespec: Flip direction of interface in bluespec

2 participants