How to avoid flattening of fields in Record?

Hi, for the code below:

module Spec where

import Clash.Prelude
import Clash.Annotations.TopEntity

data ValidInt = ValidInt {
    value :: Int, 
    valid :: Bool
} deriving (Generic, NFDataX)

data Inputs = Inputs {
    input0 :: ValidInt
} deriving (Generic, NFDataX)


machine :: HiddenClockResetEnable dom => Signal dom Inputs  -> Signal dom Int
machine inputs = pure 42

topEntity :: Clock System -> Reset System -> Enable System -> 
    Signal System Inputs -> Signal System Int
topEntity clk rst en inputs = exposeClockResetEnable (machine inputs) clk rst en

Clash flattens the inputs in the generated verilog like this:

/* AUTOMATICALLY GENERATED VERILOG-2001 SOURCE CODE.
** GENERATED BY CLASH 1.8.2. DO NOT MODIFY.
*/
`default_nettype none
`timescale 100fs/100fs
module topEntity
    ( // Inputs
      input wire  clk // clock
    , input wire  rst // reset
    , input wire  en // enable
    , input wire [64:0] inputs

      // Outputs
    , output wire signed [63:0] result
    );

  assign result = 64'sd42;
endmodule

I want two different wires instead. Something like:

input wire [63:0] input0_0; -- value
input wire input0_1; -- valid

Is there a way to do this?

As a potential solution, I tried annotating TopEntity like:

{-# ANN topEntity
  (Synthesize
    { 
        t_name = "myModule",
        t_inputs = [ 
            PortName "clk",
            PortName "rst",
            PortName "en",
            PortProduct "inputs" [
                PortProduct "input0" [
                    PortName "value",
                    PortName "valid"
                ]
            ]
        ],
        ...
    }) #-}

However, it gives me the error:

*** Exception: Saw a PortProduct in a Synthesize annotation:

  PortProduct "inputs" [PortProduct "input0" [PortName "value",PortName "valid"]]

but the port type:

  Product "Spec.ValidInt" (Just ["value","valid"]) [Signed 64,Bool]

is not a product!
CallStack (from HasCallStack):
  error, called at src/Clash/Netlist/Util.hs:1946:8 in clash-lib-1.8.2-8ZihzcIDdiCEsBBuhDMByd:Clash.Netlist.Util
  expandTopEntityOrErrM, called at src/Clash/Netlist/Util.hs:790:8 in clash-lib-1.8.2-8ZihzcIDdiCEsBBuhDMByd:Clash.Netlist.Util
  mkUniqueNormalized, called at src/Clash/Netlist.hs:267:9 in clash-lib-1.8.2-8ZihzcIDdiCEsBBuhDMByd:Clash.Netlist
  genComponentT, called at src/Clash/Netlist.hs:241:41 in clash-lib-1.8.2-8ZihzcIDdiCEsBBuhDMByd:Clash.Netlist
  genComponent, called at src/Clash/Netlist.hs:119:9 in clash-lib-1.8.2-8ZihzcIDdiCEsBBuhDMByd:Clash.Netlist

I also tried annotating the ValidInt with InlinePrimitive but I couldn’t make that work and it felt a bit clunky.

I know if I change Inputs into a tuple, Clash doesn’t flatten like this, but I prefer using Record due to the 12-item limit of tuple size in Clash. Also, when I have multiple input fields in Inputs Clash doesn’t flatten like this.

Is there a clean way to deal with this?

I’d suggest just making your top entity map between your Clash data types and the way you want them presented on the ports of the logical entity. So just something like:

topEntity ::
  Clock System ->
  Reset System ->
  Enable System ->
  Signal System Int ->
  Signal System Bool ->
  Signal System Int
topEntity clk rst en value0 valid0 =
  exposeClockResetEnable (machine inputs) clk rst en
 where
  inputs = Inputs <$> (ValidInt <$> value0 <*> valid0)
{-# OPAQUE topEntity #-}

or in a more record-y style:

topEntity clk rst en value0 valid0 =
  exposeClockResetEnable (machine inputs) clk rst en
 where
  inputs = construct <$> value0 <*> valid0
  construct value1 valid1 =
    Inputs
      { input0 =
          ValidInt
            { value = value1
            , valid = valid1
            }
      }

You can also use lenses or barbies to make it prettier.

You can use PortProduct to deconstruct types which have only one field; this is mostly intended for deconstructing Maybe into a valid flag and a data value, but you can do it for any sum-of-products type with just one field in total, splitting it into constructor bits and field bits:

module PortProduct where

import Clash.Prelude

data T a = A | B a | C

sop ::
  BitVector 2 ->
  Unsigned 4 ->
  T (Unsigned 4)
sop 0 _ = A
sop 1 x = B x
sop _ _ = C
{-# ANN sop (
  Synthesize
    { t_name = "sop"
    , t_inputs = [ PortName "si", PortName "di"]
    , t_output = PortProduct "o" [ PortName "s", PortName "d"]
    }) #-}

This has the same shape for inputs and output even though the output is a custom data type.

A primitive is a function, and of course, a data constructor is a function, but I’ve never seen anyone make a data constructor a black box… also, I’m fairly sure a black box has no influence on the ports of a logical entity, even if you create a black box for the data constructor, the ports would still look the same.

I do wonder if that is perhaps a subtle bug related to your Inputs type. It feels like this should work, both for newtype and data. I’m going to create an issue for that; if it turns out I was mistaken and this is not supposed to work, we can always close the issue as invalid :slight_smile: .

Right, I should just try that. It works with newtype, so yeah, it’s a bug with data. You shouldn’t annotate the newtype itself with PortProduct though, just act like it isn’t there when writing the ports. This works:

module Spec where

import Clash.Prelude
import Clash.Annotations.TopEntity

data ValidInt = ValidInt {
    value :: Int,
    valid :: Bool
} deriving (Generic, NFDataX)

newtype Inputs = Inputs {
    input0 :: ValidInt
} deriving (Generic, NFDataX)


machine :: HiddenClockResetEnable dom => Signal dom Inputs  -> Signal dom Int
machine inputs = pure 42

topEntity :: Clock System -> Reset System -> Enable System ->
    Signal System Inputs -> Signal System Int
topEntity clk rst en inputs = exposeClockResetEnable (machine inputs) clk rst en
{-# ANN topEntity
  (Synthesize
    {
        t_name = "myModule",
        t_inputs = [
            PortName "clk",
            PortName "rst",
            PortName "en",
            PortProduct "input0" [
                PortName "value",
                PortName "valid"
            ]
        ],
        t_output = PortName "out"
    }) #-}
1 Like

Perfect! This works like a charm.

Thank you very much for your regular help @DigitalBrains :folded_hands:

1 Like