Add a BlackBox
primitive for blackboxes that trivially forward to an RTL module
#248
Labels
BlackBox
primitive for blackboxes that trivially forward to an RTL module
#248
I've ended up having this kind of design pop up several times where, essentially, I want to structure my overall project into two components:
An important invariant is that all BSP shells expose the same interface to the RTL core, so the core logic is independent of device characteristics (as much as possible, anyway).
This kind of layout is used traditionally in partial reconfiguration designs for Altera/Xilinx boards -- for example, the AWS F1 uses this kind of design to support "hot reload" of FPGA cores, and many people use partial reconfiguration for other things. However, in general it's also just good engineering and makes ports and things like incremental recompilation, and device-independent verification, much easier.
Doing this in Clash today is workable, but a bit of a chore.
To do this today, first you define a TopEntity that takes signals from the board, and trivially passes them to a
NOINLINE
blackbox:So the
shell
is the top module, and it only callsapp#
. Shell takes pins and signals from the board and morphs them into the signals expected byapp#
, however it needs to for the current device.app#
then has a blackbox JSON definition that might look like this:Then,
app#
is appropriately forwarded to an instantiation of theapp_module
Verilog module.But this is tedious! So this ticket is about having the clash compiler support this use case automatically. While it's relatively "simple" I think it would also be very useful, and it helps encourage people to structure their core logic independent of device signals. This really helps when trying to test things, etc. (Clash is already pretty good at it of course!)
Here's how this might look, I think:
This looks like a
topEntity
but rather, the Blackbox says, "Forward calls to this blackbox toapp_module
using the naming scheme for ports defined bybb_inputs
andbb_outputs
."This dovetails nicely as an "inverse" to
TopEntity
, I think. For example, you could define another Haskell module using aTopEntity
annotation that looks near identical to this Blackbox annotation, compile them both, and viola, you have separate generation of the shell from the blackbox, with no needed.json
files or custom Verilog/VHDL/SystemVerilog code.Now, the thing is, with the new multiple topentity support -- this is already pretty close to how the compiler works anyway internally! For example, I could have just given
app#
a real, but fake definition, and instead of BlackBoxing it, specify it as its ownTopEntity
. When I compile the code, Clash will then generate two HDL modules, where theshell
module callsapp
just as I expected. Then I could just ignore theapp#
RTL code and only use theshell
RTL code during synthesis. In a sense, the compiler already has to maintain this kind of mapping between dependentTopEntities
. (And the "shape" of a dependent topentity (its port names, type, etc) are defined in the manifest Clash spits out, so it must be tracked!)So in a way, this is just a variation of what
TopEntity
does already, I feel. It would essentially "inject" a manifest and the compiler generates some boilerplate. The nice part is A) I don't have to write meaningless definitions and pointless TopEntities, B) it's more explicit about what is being accomplished, and C) I don't have to write RTL by hand (3 times!) for these use cases.There are other reasons this would be good. For example, in a build system, I may want to compile the Haskell code for
Shell
once, and compile that into an "object file" that I can use later. Altera tools support this, allowing you to save synthesis time by re-using an already compiled shell. However, without BlackBox, I have to rewrite the RTL shell code in Verilog/SystemVerilog/VHDL, each time I change the shell/app interface. That sucks, although the topentity hack would get around it.Thus, I think this is a pretty useful addition on its own.
Question: Why the
blackbox#
call? Because there's no meaningful way to simulate the application, when the entire point of the shell is to forward physical board signals. So the definition almost never matters; thus, I'd assumeblackbox#
is really justblackbox# = errorX "bad! blackbox primitives can not be simulated!"
or something instead, and it's merely to make it clear "this is a blackbox definition" for aesthetics.However, in the past for large circuits, I tried this using
error
-- and I had many problems with GHC floating outerror
calls when there were more than one, breaking this approach. That means I instead had to use a bullshit definition that did not useerror
at all, but simply type checked, and gave back bullshit values. This caused compilation to succeed "as expected".I haven't investigated this exactly, but perhaps Clash can handle this easier if there's explicit support for it. And if not,
blackbox#
is not crucial to the whole idea, merely a helpful tool for you to easily "stub out" a definition, thanks to its polymorphic typeforall a. a
The text was updated successfully, but these errors were encountered: