Integrating Clash kernels with Vitis

I’d thought it’d be interesting to run Clash code on one of AMD’s accelerator cards by linking and managing the kernel using Vitis.

For this I need an AXI-4 lite slave interface for control of the kernel and AXI4 m/stream interface(s) to transfer the data. (https://docs.amd.com/r/en-US/ug1393-vitis-application-acceleration/Requirements-of-an-RTL-Kernel). I thought I could use the AXI protocol from clash-protocols but I don’t really know how to approach it.

Has anyone done this before and has an example of the setup? Or otherwise, how can I create my top level module such that I have these required interfaces?

In clash-protocols, there is a Protocol instance for Axi4Stream, but I think we don’t have one for Axi4-Lite.

Regarding the Axi4Stream Protocol, if you have a Circuit (Axi4Stream dom conf userType) (Axi4Stream dom conf userType), you can use the toSignals function to convert that Circuit a a to a function with the forward and backward channels.

Currently the forward channel is implemented as Signal dom (Maybe (Axi4StreamM2S conf userType)).

The protocols themselves only define some interfaces and we can write test functions to verify whether they follow the specification. The course of action for you would probably be to make a component that implements your desires behavior with the interface you need for Vitis. Then you could make a top entity and generate the HDL.

Make sure the ports receive the correct names with a Synthesize annotation.
An example is given below:

import Protocols.Axi4.Stream
import Protocols
import Clash.Explicit.Prelude
import Clash.Prelude(exposeClockResetEnable)
import qualified Protocols.DfConv as DfConv

myCircuit ::
  Clock System -> Reset System -> Enable System ->
  Circuit
    (Axi4Stream System ('Axi4StreamConfig 4 8 8) Bool)
    (Axi4Stream System ('Axi4StreamConfig 4 8 8) Bool)
myCircuit = exposeClockResetEnable $ DfConv.fifo proxy proxy d16
 where
  proxy = Proxy @(Axi4Stream System ('Axi4StreamConfig 4 8 8) Bool)

top ::
  Clock System ->
  Reset System ->
  Enable System ->
  ( Signal System (Maybe (Axi4StreamM2S ('Axi4StreamConfig 4 8 8) Bool))
  , Signal System Axi4StreamS2M
  ) ->
  ( Signal System Axi4StreamS2M
  , Signal System (Maybe (Axi4StreamM2S ('Axi4StreamConfig 4 8 8) Bool))
  )
top clk rst ena = toSignals $ myCircuit clk rst ena

{-# OPAQUE top #-}
{-# ANN top (Synthesize
  { t_name   = "axi4StreamCircuit"
  , t_inputs =
    [ PortName "clk"
    , PortName "rst"
    , PortName "en"
    , PortProduct ""
      [ PortProduct "rx"
        [ PortName "valid"
        , PortProduct ""
          [ PortName "data"
          , PortName "keep"
          , PortName "strb"
          , PortName "last"
          , PortName "id"
          , PortName "dest"
          , PortName "user"
          ]
        ]
      , PortName "tx_ready"
      ]
    ]
  , t_output =
    PortProduct ""
      [ PortName "rx_ready"
      , PortProduct "tx"
        [ PortName "valid"
        , PortProduct ""
          [ PortName "data"
          , PortName "keep"
          , PortName "strb"
          , PortName "last"
          , PortName "id"
          , PortName "dest"
          , PortName "user"]
        ]
      ]
  }) #-}

The generated verilog now contains the following interface:

module axi4StreamCircuit
    ( // Inputs
      input wire  clk // clock
    , input wire  rst // reset
    , input wire  en // enable
    , input wire [0:0] rx_valid
    , input wire [31:0] rx_data
    , input wire [3:0] rx_keep
    , input wire [3:0] rx_strb
    , input wire  rx_last
    , input wire [7:0] rx_id
    , input wire [7:0] rx_dest
    , input wire  rx_user
    , input wire  tx_ready

      // Outputs
    , output wire  rx_ready
    , output wire [0:0] tx_valid
    , output wire [31:0] tx_data
    , output wire [3:0] tx_keep
    , output wire [3:0] tx_strb
    , output wire  tx_last
    , output wire [7:0] tx_id
    , output wire [7:0] tx_dest
    , output wire  tx_user
    );