Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set names for ILAs and VIOs by default #2655

Open
kleinreact opened this issue Feb 2, 2024 · 16 comments
Open

Set names for ILAs and VIOs by default #2655

kleinreact opened this issue Feb 2, 2024 · 16 comments

Comments

@kleinreact
Copy link
Contributor

The name of an ILA or VIO can be set using Clash's setName, which turns out to be useful if data needs to be passed or read from these components in an automated setup. However, the user has to be careful in correctly using setName, as for example

setName @"name" $ vioProbe a b ...

and

setName @"name" vioProbe a b ...

sets the name of two different components, the latter being the desired use case for most applications.

Hence, to make the user experience less fragile to this, we shall consider adding a required type level name argument to the ILA and VIO interfaces by default.

@DigitalBrains1
Copy link
Member

I think that in the common case of only a single ILA or VIO, there is no need for explicit names. Vivado's get_hw_ilas will just return one unique instance.

I think we should improve our documentation of the naming process rather than change the API.

@kleinreact
Copy link
Contributor Author

kleinreact commented Feb 2, 2024

That is a general problem with naming. If instances are unique, then names turn obsolete, but as soon as multiple of them are around, having some way to properly name them is the best way to go.

I still think that an API change is the better way to go here, because it turns Clash magic into some controllable and well-defined argument. As ILAs and VIOs are blackboxes anyway, it is not much effort to handle the names as part of the blackbox, in the same fashion as we do already for naming the ports. If names are not needed, just offer the user to pass the empty string, which the blackbox then maps to the auto-generated name by default.

@DigitalBrains1
Copy link
Member

There are many cases where you might want to give something a name to be able to find it again in the HDL and stages beyond the HDL. That is why we have setName. I'd rather make the general case work than special-case it for things where we feel people might want to name it more often.

@DigitalBrains1
Copy link
Member

DigitalBrains1 commented Feb 3, 2024

You could argue that I'm going off-topic with this post, since most of it is not about ILA's or VIO's. However, you are suggesting we stop using setName because of problems with it. So I dive into the workings and problems of setName. I think that's in fact on-topic.

I think the difference between

f = setName @"name" $ g . h

and

f = setName @"name" g . h

is that the former sets the name of both component g and component h, whereas the latter only sets the name of component g (I know this is different from what I said before, I think I was mistaken then). Note that if duplicate names arise, Clash will have to add suffixes to make them all unique, so in the former, there is much more chance that this will occur. It all depends a bit on what the components look like; whether they emit names at all in the HDL. Also, signals have a tendency to be named for the component they originate from, which will also add more of these names.

To get back to ILAs, if I try the following, which seems to be a reasonable method to write one's ILAs:

module TestIlaNames where

import Clash.Explicit.Prelude
import Clash.Cores.Xilinx.Ila

topEntity ::
  Clock System ->
  Signal System (Unsigned 8) ->
  Signal System (Unsigned 8) ->
  Signal System ()
topEntity clk in1 in2 = ila1 clk in1 `hwSeqX` ila2 clk in2 `hwSeqX` pure ()
{-# OPAQUE topEntity #-}

ila1 ::
  Clock System ->
  Signal System (Unsigned 8) ->
  Signal System ()
ila1 clk inp = setName @"foo" ila (ilaConfig $ "a" :> Nil) clk inp

ila2 ::
  Clock System ->
  Signal System (Unsigned 8) ->
  Signal System ()
ila2 clk inp = setName @"bar" $ ila (ilaConfig $ "b" :> Nil) clk inp

both ILAs end up with the plain names foo and bar. In fact, both the ILA itself and the instance of the entity containing the ILA end up being called foo and bar respectively, but since they are in different files and thus different namespaces, they do not get a disambiguating suffix.

I agree that the user experience for setName can be fragile because the difference between incantations is rather subtle and it all happily type checks. However, I don't actually observe any fragility here. Do you have an example where it did work out wrong? That could make the discussion more focussed. I tried to break it, but I couldn't with an ILA because it will always end up in its own file, and thus the name is always unique. I unsuccesfully tried to break it with this:

module TestIlaNames where

import Clash.Explicit.Prelude
import Clash.Cores.Xilinx.Ila

topEntity ::
  Clock System ->
  Signal System (Unsigned 8) ->
  Signal System ()
topEntity clk inp = setName @"foo" $ ila ilaCfg clk $ delay clk enableGen 0 inp
 where
  ilaCfg = ilaConfig $ "a" :> Nil
{-# OPAQUE topEntity #-}

More broadly considering setName in general, since we just switched the PLL's in Clash Prelude from explicit names to "please use setName", I'm quite invested in getting setName to work, and I do run into issues in practice. It seems to me the tuple unpacking when using the safe variants of the PLL's is currently preventing them from being correctly named. This is surprising to me, because I'm sure I tested whether it worked when I wrote the code; I did not add a test to the test suite, though. So I'd like to get setName working better; maybe that will also assuage your complaints?

I did notice another unexpected thing, which corresponds to what Hidde reported on the ILAs in the Bittide project. Once Clash begins disambiguating names, it prefixes the names with prefixes based on the name of the entity. But if the name is already unique, it does not prefix the name.

With the following code:

module SetName where

import Clash.Explicit.Prelude

topEntity ::
  Clock System ->
  Enable System ->
  Signal System Int ->
  Signal System Int
topEntity clk en = f clk en . g clk en
{-# OPAQUE topEntity #-}

f ::
  Clock System ->
  Enable System ->
  Signal System Int ->
  Signal System Int
f clk en = setName @"foo" delay clk en 0 . delay clk en 0
{-# OPAQUE f #-}

g ::
  Clock System ->
  Enable System ->
  Signal System Int ->
  Signal System Int
g clk en = setName @"bar" $ delay clk en 0 . delay clk en 0
{-# OPAQUE g #-}

The flip-flops in f are named (VHDL):

  • foo_delay
  • capp_arg_delay

And the flip-flops in g are named:

  • g_g1_bar_delay
  • g_g1_bar_0_delay

I think we should take a look whether we want that!

If you're wondering why Clash came up with g1, it's in the Core for g before normalization. After normalization, we see ticks with <prefixName>:

g before normalization:

λ(clk :: Clock "System") ->
λ(en :: Enable "System") ->
let
  g1 :: Signal "System" Int -> Signal "System" Int
  = delay# @"System" @Int $fKnownDomain"System"[GlobalId] $fNFDataXInt[GlobalId] clk[LocalId] en[LocalId] (I# 0#)
in <setName>"bar"
λ(x :: Signal "System" Int) ->
g1[LocalId] (g1[LocalId] x[LocalId])

g after normalization:

λ(clk :: Clock "System") ->
λ(en :: Enable "System") ->
λ(c$arg :: Signal "System" Int) ->
<setName>"bar"
letrec
  c$app_arg :: Signal "System" Int
  = <prefixName>"g_g1"delay# @"System" @Int (removedArg @(KnownDomain "System")) (removedArg @(NFDataX Int)) clk[LocalId] en[LocalId] (I# 0#)
      c$arg[LocalId]
  result :: Signal "System" Int
  = <prefixName>"g_g1"delay# @"System" @Int (removedArg @(KnownDomain "System")) (removedArg @(NFDataX Int)) clk[LocalId] en[LocalId] (I# 0#)
      c$app_arg[LocalId]
in result[LocalId]

@DigitalBrains1
Copy link
Member

Ah perhaps this is what irks you? I managed to at least foul up the names of the ILAs a bit. Normalisation introduced <prefixName> ticks, I think because it duplicated ilaInst.

module Test where

import Clash.Explicit.Prelude
import Clash.Cores.Xilinx.Ila

topEntity :: Clock System -> Signal System ()
topEntity clk =
    setName @"foo" ilaInst (ilaConfig ("a" :> Nil))
  `hwSeqX`
    setName @"bar" ilaInst (ilaConfig ("b" :> Nil))
  `hwSeqX`
    pure ()
 where
  ilaInst :: IlaConfig 1 -> Signal System ()
  ilaInst cfg = ila cfg clk (pure 0 :: Signal System (Unsigned 1))

gives the names topEntity_ilaInst_foo and topEntity_ilaInst_bar.

The normalisation of the core introduced <prefixName> ticks:

topEntity before normalization:

λ(clk :: Clock "System") ->
let
  ilaInst :: IlaConfig 1 -> Signal "System" ()
  = λ(cfg :: IlaConfig 1) ->
  ila[GlobalId] @"System" @(Signal "System" (Unsigned 1) -> Signal "System" ()) @1 $fKnownDomain"System"[GlobalId] topEntity2[GlobalId] (%%)
    cfg[LocalId]
    clk[LocalId]
    (fromInteger# @1 topEntity1[GlobalId] (IS 0#))
in hwSeqX @(Signal "System" ()) @(Signal "System" ())
     (<setName>"foo"
        ilaInst[LocalId]
        (IlaConfig @1
           (Cons @(+ 0 1) @(List Char) @0 (_CO_ @(~# Natural Natural (+ 0 1) (+ 0 1))) (unpackCString# "a")
              ((Λb -> Nil @0 @b (_CO_ @(~# Natural Natural 0 0))) @(List Char)))
           D4096
           True
           ilaConfig4[GlobalId]
           (ilaConfig2[GlobalId] @1)
           (ilaConfig1[GlobalId] @1)
           False))
     (hwSeqX @(Signal "System" ()) @(Signal "System" ())
        (<setName>"bar"
           ilaInst[LocalId]
           (IlaConfig @1
              (Cons @(+ 0 1) @(List Char) @0 (_CO_ @(~# Natural Natural (+ 0 1) (+ 0 1))) (unpackCString# "b")
                 ((Λb -> Nil @0 @b (_CO_ @(~# Natural Natural 0 0))) @(List Char)))
              D4096
              True
              ilaConfig4[GlobalId]
              (ilaConfig2[GlobalId] @1)
              (ilaConfig1[GlobalId] @1)
              False))
        ())   

topEntity after normalization:

λ(clk :: Clock "System") ->
letrec
  c$app_arg :: Signal "System" ()
  = <setName>"bar"
  <prefixName>"topEntity_ilaInst"
  topEntity2[GlobalId] clk[LocalId]
  c$app_arg :: Signal "System" ()
  = <setName>"foo"
  <prefixName>"topEntity_ilaInst"
  topEntity2[GlobalId] clk[LocalId]
  result :: Signal "System" ()
  = hwSeqX @(Signal "System" ()) @(Signal "System" ()) c$app_arg[LocalId]
      (hwSeqX @(Signal "System" ()) @(Signal "System" ()) c$app_arg[LocalId] ())
in result[LocalId]

It is rather misleading that it has a duplicate c$app_arg in that normalised Core... that's what you get for having CLASH_PPR_UNIQUES=False :-).

@DigitalBrains1
Copy link
Member

DigitalBrains1 commented Feb 4, 2024

Right now, I'm thinking that once a user uses setName, that should override the prefix. The name will still be made unique by Clash, but if the user picks a unique enough name it will match exactly and otherwise it'll just have a short numeric suffix instead of a long prefix.

And I think that instead of bbCtxName :: Maybe IdentifierText we should have:

data CtxName =
    NoCtxName
  | DefaultCtxName IdentifierText
  | ExplicitCtxName IdentifierText

bbCtxName :: CtxName

and then this code

getIlaName :: Maybe T.Text -> T.Text
getIlaName Nothing = "ila_inst"
getIlaName (Just "result") = getIlaName Nothing
getIlaName (Just "__VOID_TDECL_NOOP__") = getIlaName Nothing
getIlaName (Just s) = s

can become

getIlaName :: CtxName -> T.Text
getIlaName (ExplicitCtxName name) = name
getIlaName _                      = "ila_inst"

In fact, that should be a library function. And it shouldn't be called T.Text but IdentifierText, but okay.

@christiaanb
Copy link
Member

christiaanb commented Feb 4, 2024

Normalization adds prefixName X when it inlines the function X. The idea was that you could track the provenance of a name, even after inlining.

That’s why the naming mechanism does not ignore these prefixName X ticks. It allows you to distinguish two separate setName Y originating from two different inlined functions.

@DigitalBrains1
Copy link
Member

DigitalBrains1 commented Feb 4, 2024

Yes, that is a good thing, but maybe not for setName! That's my thinking.

[edit]
You snuck in an edit after I replied :-). My full reply is below.
[/edit]

@christiaanb
Copy link
Member

The current behavior is explicitly documented. Perhaps we want a clearFixes to remove pre- and postfixes from the naming context.

@DigitalBrains1
Copy link
Member

DigitalBrains1 commented Feb 4, 2024

Maybe if the user comes at the point where they cannot tell apart their setName X from their setName X elsewhere, that is the point where they should refine their X. Right now, they have to deal with potentially really long prefixes, which might make their output ugly. In the case of Bittide, the automated script will not output files named ilaPlot.{csv,zip} but Bittide_Instances_Hitl_FullMeshSwCc_fullMeshSwCcTest_fullMeshSwCcTest197_ilaPlot.{csv,zip}

Also, explicitly documented is not so explicit? It says

Name the instance or register with the given Symbol, instead of using an auto-generated name. Pre- and suffixes annotated with prefixName and suffixName will be added to both instances and registers named with setName and instances and registers that are auto-named.

It says anything you annotate with prefixName and suffixName will be added, but it does not say that this also goes for prefixes and suffixes cooked up by Clash itself.

I think a solution with clearFixes makes the code less... clear.

@christiaanb
Copy link
Member

You can also add a NOINLINE to the functions where you use setName

@DigitalBrains1
Copy link
Member

DigitalBrains1 commented Feb 4, 2024

I also think it is confusing that sometimes the prefix is not there, i.e., when Clash didn't have to inline any definitions. Compare f and g in this comment. g is OPAQUE, and it only contains one setName, yet it has prefixes. Same for ILAs, compare:

topEntity :: Clock System -> Signal System Bit -> Signal System ()
topEntity clk inp =
    setName @"foo" ilaInst (ilaConfig ("a" :> Nil))
  `hwSeqX`
    setName @"bar" ilaInst (ilaConfig ("b" :> Nil))
  `hwSeqX`
    pure ()
 where
  ilaInst :: IlaConfig 1 -> Signal System ()
  ilaInst cfg = ila cfg clk inp

which names the ILAs topEntity_ilaInst_foo and topEntity_ilaInst_bar, versus

topEntity :: Clock System -> Signal System Bit -> Signal System ()
topEntity clk inp = ila1 `hwSeqX` ila2 `hwSeqX` pure ()
 where
  ila1 :: Signal System ()
  ila1 = setName @"foo" ila (ilaConfig ("a" :> Nil)) clk inp
  ila2 :: Signal System ()
  ila2 = setName @"bar" ila (ilaConfig ("b" :> Nil)) clk inp

which names the ILAs foo and bar.

[edit]
Ahh... I think I understand your

You can also add a NOINLINE to the functions where you use setName

correctly now. I think that, when properly documented, might be the correct solution (I still like my CtxName though, fix that half-working ad-hoc pattern matching in getIlaName). But I'll respond at a later time, during working hours ;-)
[/edit]

@kleinreact
Copy link
Contributor Author

The reason I consider setName to be non-user friendly is that it requires to have explicit access to the references of the respective ILAs. Consider the following example.

module Test where

import Clash.Explicit.Prelude
import Clash.Cores.Xilinx.Ila

topEntity :: Vec 3 (Clock System) -> Signal System ()
topEntity clks =
    hwSeqX (zipWith ilaInst cfgs clks)
  $ pure ()
 where
  cfgs = map (ilaConfig . singleton) ("a" :> "b" :> "c" :> Nil)

  ilaInst :: IlaConfig 1 -> Clock System -> Signal System ()
  ilaInst cfg clk = setName @"ila" ila cfg clk (pure 0 :: Signal System (Unsigned 1))

which produces a topEntity.v with

...
topEntity2_1 ila (.c$pTS0 (c$clks_1));
topEntity2_0 ila_0 (.c$pTS0 (c$clks_0));
topEntity2 ila_1 (.c$pTS0 (c$clks));
...

topEntity2.v:

topEntity_ila_E981CE816A1232C3 ila_1 (.clk (c$pTS0), .probe0 (a));

topEntity2_0.v:

topEntity_ila_E981CE816A1232C3 ila_0 (.clk (c$pTS0), .probe0 (b));

topEntity2_1.v:

topEntity_ila_E981CE816A1232C3 ila (.clk (c$pTS0), .probe0 (c));

The only way a user can provide some names with setName here is to explicitly deconstruct the vector, assign names, and then to reassemble it, which is just very cumbersome. If the name would be passed as an argument to the blackbox instead, then the user would have the same flexibility as with cfgs above.

Maybe this wouldn't be necessary if Clash would produce very natural names, but as you can see in the example above, it definitely does not right now. Hence, the user has the desire to use an deviating naming scheme instead.

To be added, handling vectors of ILAs was the original setting, where I stumbled into these issues the first time, trying to monitor all of the transceivers of the FPGA. While this is still a manageable amount, imagine a use case where you have a hundred or thousands of equivalent component instances, which you like to monitor all in parallel. Then the way setName currently works is just no suitable option any more.

@martijnbastiaan
Copy link
Member

Agree Felix. What about a setNameVec?

@kleinreact
Copy link
Contributor Author

setNameVec would indeed be a solution for this particular problem and I currently cannot imagine where it could break or still be insufficient. setNameVec would get a vector of names then, I guess? Type or term or level?

Can you elaborate a bit more on the interface/usage of setNameVec?

@DigitalBrains1
Copy link
Member

Wow, yeah, that use case is nasty. I generated HDL for that code, and in my case, the first ila was called ila_1, the second ila_0 and the third ila.

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

No branches or pull requests

4 participants