Is there an upper limit to the number of items in tuple?

Clash fails to compile when using tuples larger than size 12.

This seems somewhat arbitrary. My guess is that Clash defines bundle and unbundle functions only for tuples up to a certain size—possibly 12—and lacks support beyond that.

Does anyone have a clear idea regarding this?
And more importantly, are there any recommended workarounds or solutions?

For example:

module TupleBug where

import Clash.Prelude

circuit :: HiddenClockResetEnable dom => Signal dom (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)
circuit = outs
    where
        outs = bundle (out0, out1, out2, out3, out4, out5, out6, out7, out8, out9, out10, out11, out12)
        out0 = register 0 out0
        out1 = register 0 out1
        out2 = register 0 out2
        out3 = register 0 out3
        out4 = register 0 out4
        out5 = register 0 out5
        out6 = register 0 out6
        out7 = register 0 out7
        out8 = register 0 out8
        out9 = register 0 out9
        out10 = register 0 out10
        out11 = register 0 out11
        out12 = register 0 out12

topEntity :: Clock System -> Reset System -> Enable System -> Signal System (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)
topEntity clk rst en = exposeClockResetEnable circuit clk rst en

Fails to compile with following error.

Clashi, version 1.8.2 (using clash-lib, version 1.8.2):
https://clash-lang.org/  :? for help
clashi> :l tuple.hs
[1 of 1] Compiling TupleBug         ( tuple.hs, interpreted )

tuple.hs:8:23: error: [GHC-83865]
    • Couldn't match expected type: Unbundled
                                      dom
                                      (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
                                       Int)
                  with actual type: (Signal dom a12, Signal dom a11, Signal dom a10,
                                     Signal dom a9, Signal dom a8, Signal dom a7, Signal dom a6,
                                     Signal dom a5, Signal dom a4, Signal dom a3, Signal dom a2,
                                     Signal dom a1, Signal dom a0)
    • In the first argument of ‘bundle’, namely
        ‘(out0, out1, out2, out3, out4, out5, out6, out7, out8, out9,
          out10, out11, out12)’
      In the expression:
        bundle
          (out0, out1, out2, out3, out4, out5, out6, out7, out8, out9,
           out10, out11, out12)
      In an equation for ‘outs’:
          outs
            = bundle
                (out0, out1, out2, out3, out4, out5, out6, out7, out8, out9,
                 out10, out11, out12)
    • Relevant bindings include
        outs :: Signal
                  dom
                  (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)
          (bound at tuple.hs:8:9)
        out0 :: Signal dom a12 (bound at tuple.hs:9:9)
        out1 :: Signal dom a11 (bound at tuple.hs:10:9)
        out2 :: Signal dom a10 (bound at tuple.hs:11:9)
        out3 :: Signal dom a9 (bound at tuple.hs:12:9)
        out4 :: Signal dom a8 (bound at tuple.hs:13:9)
        out5 :: Signal dom a7 (bound at tuple.hs:14:9)
        out6 :: Signal dom a6 (bound at tuple.hs:15:9)
        out7 :: Signal dom a5 (bound at tuple.hs:16:9)
        out8 :: Signal dom a4 (bound at tuple.hs:17:9)
        out9 :: Signal dom a3 (bound at tuple.hs:18:9)
        out10 :: Signal dom a2 (bound at tuple.hs:19:9)
        out11 :: Signal dom a1 (bound at tuple.hs:20:9)
        out12 :: Signal dom a0 (bound at tuple.hs:21:9)
        (Some bindings suppressed; use -fmax-relevant-binds=N or -fno-max-relevant-binds)
  |
8 |         outs = bundle (out0, out1, out2, out3, out4, out5, out6, out7, out8, out9, out10, out11, out12)
  |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Failed, no modules loaded.
clashi>```

This is done for compile speeds and compile memory usage of clash-prelude. See Increase default tuple size (measurements) · Issue #1872 · clash-lang/clash-compiler · GitHub. You can enable -flarge-tuples to make clash-prelude have the maximum size tuple instances. You can do this by adding the following to cabal.project:

package clash-prelude
  flags: +large-tuples

We should probably increase the default of 12 though.

I see. Thanks for the info.
Is there also a way to enable the flag for clash interpreter?

I previously asked this question on the Clash Slack, and barbies was suggested as a possible solution. I’m commenting that here so others who come across this thread might find it helpful.

For future reference:
Here’s the working version of the code using records instead of tuples. I chose not to use the barbies library to keep dependencies minimal, since I’m primarily running Clash through the interpreter.

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedRecordDot #-}

module TupleBug where

import Clash.Prelude

data Ckt1Output = Ckt1Output
    { out0  :: Int
    , out1  :: Int
    , out2  :: Int
    , out3  :: Int
    , out4  :: Int
    , out5  :: Int
    , out6  :: Int
    , out7  :: Int
    , out8  :: Int
    , out9  :: Int
    , out10 :: Int
    , out11 :: Int
    , out12 :: Int
    , out13 :: Int
    , out14 :: Int
    , out15 :: Int
    , out16 :: Int
    } deriving (Generic, NFDataX)


data Ckt2Output = Ckt2Output
    { out0 :: Int
    , out1 :: Int
    , out16 :: Int
    } deriving (Generic, NFDataX)


circuit2 :: HiddenClockResetEnable dom => Signal dom Ckt2Output
circuit2 = outs
    where 
        outs = Ckt2Output <$> out0 <*> out1 <*> out16
        out0 = register 0 out0
        out1 = register 0 out1
        out16 = register 0 out16

circuit1 :: HiddenClockResetEnable dom => Signal dom Ckt1Output
circuit1 = outs
    where
        outs = Ckt1Output <$> out0 <*> out1 <*> out2 <*> out3 <*> out4 <*> out5 <*> out6 <*> out7 <*> out8 <*> out9 <*> out10 <*> out11 <*> out12 <*> out13 <*> out14 <*> out15 <*> out16

        ckt2 = circuit2

        out0 = (.out0) <$> ckt2
        out1 = (.out1) <$> ckt2
        out16 = (.out16) <$> ckt2

        out2 = register 0 out2
        out3 = register 0 out3
        out4 = register 0 out4
        out5 = register 0 out5
        out6 = register 0 out6
        out7 = register 0 out7
        out8 = register 0 out8
        out9 = register 0 out9
        out10 = register 0 out10
        out11 = register 0 out11
        out12 = register 0 out12
        out13 = register 0 out13
        out14 = register 0 out14
        out15 = register 0 out15


topEntity :: Clock System -> Reset System -> Enable System -> Signal System Ckt1Output
topEntity clk rst en = exposeClockResetEnable circuit1 clk rst en

You mean if you use Clash through stack exec? I don’t think there is.

If you create a starter project and set it there, the clashi interpreter you can invoke in the starter project will have the larger tuples available, though.

As an alternative to stack exec, you could create a starter project, configure that as you want, and then put the following shell script on your path:

#!/bin/sh

unset DIR
DIR="$(pwd)"
cd ~/where/your/starter/project/is
stack run --cwd "$DIR" clashi -- "$@"

We recommend you use a starter project to use Clash, and use them the way they are intended (which is not this). So we don’t recommend either stack exec or this shell script. But since you’re already using stack exec, heck, let’s throw some more stuff into the mix :wink: .

(Why unset? Because that is also the way to clear the export flag on an environment variable. It’s just a standard thing I do in every script, to prevent passing unintended env vars to programs.)