Declare and Connect Ports Parametrically (Chipyard Automatic ILA Core Generation)

45 views
Skip to first unread message

Oğuzhan Canpolat

unread,
May 11, 2023, 11:22:33 AM5/11/23
to chisel-users
TL;DR: Given a signal array A of length N, how can I declare and connect IO ports with names (clk, probe0, probe1... probeN-1). Where probe#i gets connected to A#i (i.e., probe3 := A(3)).

Hi All,

I want to create a helper object that automatically generates ILA IP cores and assigns signals to it for FPGA debugging. My plan was as follows:

- Use ElaborationArtefacts (chipyard) to create ILA IP with preferred number of probes and widths.
- Have a HasBlackBoxInline to generate a verilog wrapper and instantiate the IP core in it.
- Instantiate the BlackBox and connect the array of signals to ILA's ports.

However, I got stuck when declaring ports and naming them parametrically. Given a number N, my BlackBox should have the ports (clk, probe0, probe1, ... probeN-1).

What is the best way for me to create IO and connect them to the given array of signals? I believe I need to work with Scala Macros to generate such functionality but wanted to ask if there is any other easier way of doing it (i.e. create and access ports as a dictionary).

My proof of concept code is below. I added comments beginning with ISSUE to the sections I couldn't resolve for your convenience.

I appreciate any ideas and comments.

object MarkDebug {
private var counter = 0

def apply(clock: Clock, signals: Seq[UInt],
widths: Seq[Int]): ILACore = {
val ila = Module(new ILACore(s"hub${counter}", widths))
ila.io.clk := clock
// ISSUE: Connect probes with corresponding signals
(signals zip ila.io.drop(1)) foreach { case (s, p) p := s }
counter += 1
ila
}
}

class ILACore(name: String, probeWidths: Seq[Int])
extends BlackBox with HasBlackBoxInline {
// ISSUE: Generate ports with matching names and widths
// using verilog inline source
// probeWidths contains the width of each signal
// (i.e., probeWidths(3) => width of probe3)
val io = IO(new Bundle{
val clk = Input(Clock())
(0 until probeWidths.length) map/foreach { i => declare probe#i }
})

setInline(s"BlackBox${name}.v",
s"""`timescale 1ns/1ps
|
|module BlackBox${name} (
| input clk,
${(probeWidths.zip(0 until probeWidths.length) map {
case (w, idx) =>
s"| input [${w-1}:0] probe${idx}"}).mkString(",\n")}
|);
|
|ila_debug_${name} debug_${name} (
| .clk(clk)
${((0 until probeWidths.length) map {
i => s"| .probe${i}(probe${i})"}).mkString(",\n")}
|);
|
|endmodule""".stripMargin)

ILAGenerator(name, probeWidths)
}

object ILAGenerator {
def apply(name: String, probeWidths: Seq[Int]) = {
var builder = s"""
create_ip -vendor xilinx.com -library ip -name ila \
-version 6.2 -module_name ila_debug_${name} \
-dir $$ipdir -force
set_property -dict [list \
CONFIG.ALL_PROBE_SAME_MU {TRUE} \
CONFIG.ALL_PROBE_SAME_MU_CNT {1} \
CONFIG.C_ADV_TRIGGER {FALSE} \
CONFIG.C_CLKFBOUT_MULT_F {10} \
CONFIG.C_CLKOUT0_DIVIDE_F {10} \
CONFIG.C_CLK_FREQ {200} \
CONFIG.C_CLK_PERIOD {5} \
CONFIG.C_DATA_DEPTH {1024} \
CONFIG.C_DDR_CLK_GEN {FALSE} \
CONFIG.C_DIVCLK_DIVIDE {3} \
CONFIG.C_ENABLE_ILA_AXI_MON {false} \
CONFIG.C_EN_DDR_ILA {FALSE} \
CONFIG.C_EN_STRG_QUAL {0} \
CONFIG.C_EN_TIME_TAG {0} \
CONFIG.C_ILA_CLK_FREQ {2000000} \
CONFIG.C_INPUT_PIPE_STAGES {0} \
CONFIG.C_MONITOR_TYPE {Native} \
CONFIG.C_NUM_MONITOR_SLOTS {1} \"""
builder += s"\nCONFIG.C_NUM_OF_PROBES
{${probeWidths.length}} \\"
for (i <- 0 until probeWidths.length) {
builder += s"\nCONFIG.C_PROBE${i}_MU_CNT {1} \\"
builder += s"\nCONFIG.C_PROBE${i}_TYPE {0} \\"
builder += s"\nCONFIG.C_PROBE${i}_WIDTH
{${probeWidths(i)}} \\"
}
builder += s"""
CONFIG.C_SLOT_0_AXIS_TDATA_WIDTH {32} \
CONFIG.C_SLOT_0_AXIS_TDEST_WIDTH {1} \
CONFIG.C_SLOT_0_AXIS_TID_WIDTH {1} \
CONFIG.C_SLOT_0_AXIS_TUSER_WIDTH {1} \
CONFIG.C_SLOT_0_AXI_ADDR_WIDTH {32} \
CONFIG.C_SLOT_0_AXI_ARUSER_WIDTH {1} \
CONFIG.C_SLOT_0_AXI_AWUSER_WIDTH {1} \
CONFIG.C_SLOT_0_AXI_BUSER_WIDTH {1} \
CONFIG.C_SLOT_0_AXI_DATA_WIDTH {32} \
CONFIG.C_SLOT_0_AXI_ID_WIDTH {1} \
CONFIG.C_SLOT_0_AXI_PROTOCOL {AXI4} \
CONFIG.C_SLOT_0_AXI_RUSER_WIDTH {1} \
CONFIG.C_SLOT_0_AXI_WUSER_WIDTH {1} \
CONFIG.C_TIME_TAG_WIDTH {32} \
CONFIG.C_TRIGIN_EN {false} \
CONFIG.C_TRIGOUT_EN {false} \
CONFIG.C_XLNX_HW_PROBE_INFO {DEFAULT} \
CONFIG.EN_BRAM_DRC {TRUE} \
CONFIG.SIGNAL_CLOCK.FREQ_HZ {100000000} \
CONFIG.SIGNAL_CLOCK.INSERT_VIP {0} \
CONFIG.SLOT_0_AXI.INSERT_VIP {0} \
CONFIG.SLOT_0_AXIS.INSERT_VIP {0} \
] [get_ips ila_debug_${name}]
"""

ElaborationArtefacts.add(s"iladebug${name}.vivado.tcl",
builder)
}
}

Oğuzhan Canpolat

unread,
May 20, 2023, 3:34:27 PM5/20/23
to chisel-users
Hi,

I got around the problem by having a predeclared Sequence of probes and allowing debugging of signals that don't exceed this length (at once, you may use multiple ILAs or increase #probes). 

I'm leaving a working version of the code below if anyone needs a Chipyard package with similar functionality. Feel free to use it to your liking.

import chisel3._
import chisel3.util._
import chisel3.experimental.{IO, Analog, BaseModule}
import freechips.rocketchip.util.{ElaborationArtefacts}

object MarkDebug {
    val NUM_PROBES = 20
    private var counter = 0

    def apply(clock: Clock, signals: Seq[UInt]): ILACore = {
        assert(signals.length <= MarkDebug.NUM_PROBES, s"MarkDebug accepts at most ${MarkDebug.NUM_PROBES} signals")
        val probeWidths = signals map { s => if(s.widthKnown) s.getWidth else 32 }
        MarkDebug(clock, signals, probeWidths)
    }

    def apply(clock: Clock, signals: Seq[UInt], widths: Seq[Int]): ILACore = {
        assert(signals.length <= MarkDebug.NUM_PROBES, s"MarkDebug accepts at most ${MarkDebug.NUM_PROBES} signals")
        val ila = Module(new ILACore(s"ila${counter}", widths ++ Seq.fill(MarkDebug.NUM_PROBES - widths.length + 1)(1)))
        counter += 1
        ila.io.clk := clock
        val probes = Seq(
            ila.io.probe0,
            ila.io.probe1,
            ila.io.probe2,
            ila.io.probe3,
            ila.io.probe4,
            ila.io.probe5,
            ila.io.probe6,
            ila.io.probe7,
            ila.io.probe8,
            ila.io.probe9,
            ila.io.probe10,
            ila.io.probe11,
            ila.io.probe12,
            ila.io.probe13,
            ila.io.probe14,
            ila.io.probe15,
            ila.io.probe16,
            ila.io.probe17,
            ila.io.probe18,
            ila.io.probe19
        )
        signals zip probes foreach { case (s, p) => p := s }
        ila
    }
}

class ILACoreIO(probeWidths: Seq[Int]) extends Bundle {
    val clk     = Input(Clock())
    val probe0  = Input(UInt(probeWidths(0).W))
    val probe1  = Input(UInt(probeWidths(1).W))
    val probe2  = Input(UInt(probeWidths(2).W))
    val probe3  = Input(UInt(probeWidths(3).W))
    val probe4  = Input(UInt(probeWidths(4).W))
    val probe5  = Input(UInt(probeWidths(5).W))
    val probe6  = Input(UInt(probeWidths(6).W))
    val probe7  = Input(UInt(probeWidths(7).W))
    val probe8  = Input(UInt(probeWidths(8).W))
    val probe9  = Input(UInt(probeWidths(9).W))
    val probe10 = Input(UInt(probeWidths(10).W))
    val probe11 = Input(UInt(probeWidths(11).W))
    val probe12 = Input(UInt(probeWidths(12).W))
    val probe13 = Input(UInt(probeWidths(13).W))
    val probe14 = Input(UInt(probeWidths(14).W))
    val probe15 = Input(UInt(probeWidths(15).W))
    val probe16 = Input(UInt(probeWidths(16).W))
    val probe17 = Input(UInt(probeWidths(17).W))
    val probe18 = Input(UInt(probeWidths(18).W))
    val probe19 = Input(UInt(probeWidths(19).W))
}

class ILACore(name: String, probeWidths: Seq[Int]) extends BlackBox with HasBlackBoxInline {
    val io = IO(new ILACoreIO(probeWidths))

    override def desiredName = s"BlackBox${name}"

    setInline(s"BlackBox${name}.v",
    s"""`timescale 1ns/1ps
    |
    |module BlackBox${name} (
    |    input clk,
    ${(probeWidths.zip(0 until MarkDebug.NUM_PROBES) map {
        case (w, idx) => s"|    input [${w-1}:0] probe${idx}"}).mkString(",\n")}
    |);
    |
    |ila_debug_${name} debug_${name} (
    |    .clk(clk),
    ${((0 until probeWidths.length) map {
        i => s"|    .probe${i}(probe${i})"}).mkString(",\n")}
    |);
    |
    |endmodule""".stripMargin)

    ILAGenerator(name, probeWidths)
}

object ILAGenerator {
    def apply(name: String, probeWidths: Seq[Int]) = {
        var builder = s"""
        create_ip -vendor xilinx.com -library ip -name ila -version 6.2 -module_name ila_debug_${name} -dir $$ipdir -force
        set_property -dict [list \\
            CONFIG.ALL_PROBE_SAME_MU          {TRUE} \\
            CONFIG.ALL_PROBE_SAME_MU_CNT      {1} \\
            CONFIG.C_ADV_TRIGGER              {FALSE} \\
            CONFIG.C_CLKFBOUT_MULT_F          {10} \\
            CONFIG.C_CLKOUT0_DIVIDE_F         {10} \\
            CONFIG.C_CLK_FREQ                 {200} \\
            CONFIG.C_CLK_PERIOD               {5} \\
            CONFIG.C_DATA_DEPTH               {32768} \\
            CONFIG.C_DDR_CLK_GEN              {FALSE} \\
            CONFIG.C_DIVCLK_DIVIDE            {3} \\
            CONFIG.C_ENABLE_ILA_AXI_MON       {false} \\
            CONFIG.C_EN_DDR_ILA               {FALSE} \\
            CONFIG.C_EN_STRG_QUAL             {0} \\
            CONFIG.C_EN_TIME_TAG              {0} \\
            CONFIG.C_ILA_CLK_FREQ             {2000000} \\
            CONFIG.C_INPUT_PIPE_STAGES        {0} \\
            CONFIG.C_MONITOR_TYPE             {Native} \\
            CONFIG.C_NUM_MONITOR_SLOTS        {1} \\"""
        builder += s"\n            CONFIG.C_NUM_OF_PROBES            {${probeWidths.length}} \\"
        for (i <- 0 until probeWidths.length) {
            builder += s"\n            CONFIG.C_PROBE${i}_MU_CNT            {1} \\"
            builder += s"\n            CONFIG.C_PROBE${i}_TYPE              {0} \\"
            builder += s"\n            CONFIG.C_PROBE${i}_WIDTH             {${probeWidths(i)}} \\"
        }
        builder += s"""
            CONFIG.C_SLOT_0_AXIS_TDATA_WIDTH  {32} \\
            CONFIG.C_SLOT_0_AXIS_TDEST_WIDTH  {1} \\
            CONFIG.C_SLOT_0_AXIS_TID_WIDTH    {1} \\
            CONFIG.C_SLOT_0_AXIS_TUSER_WIDTH  {1} \\
            CONFIG.C_SLOT_0_AXI_ADDR_WIDTH    {32} \\
            CONFIG.C_SLOT_0_AXI_ARUSER_WIDTH  {1} \\
            CONFIG.C_SLOT_0_AXI_AWUSER_WIDTH  {1} \\
            CONFIG.C_SLOT_0_AXI_BUSER_WIDTH   {1} \\
            CONFIG.C_SLOT_0_AXI_DATA_WIDTH    {32} \\
            CONFIG.C_SLOT_0_AXI_ID_WIDTH      {1} \\
            CONFIG.C_SLOT_0_AXI_PROTOCOL      {AXI4} \\
            CONFIG.C_SLOT_0_AXI_RUSER_WIDTH   {1} \\
            CONFIG.C_SLOT_0_AXI_WUSER_WIDTH   {1} \\
            CONFIG.C_TIME_TAG_WIDTH           {32} \\
            CONFIG.C_TRIGIN_EN                {false} \\
            CONFIG.C_TRIGOUT_EN               {false} \\
            CONFIG.C_XLNX_HW_PROBE_INFO       {DEFAULT} \\
            CONFIG.EN_BRAM_DRC                {TRUE} \\
            CONFIG.SIGNAL_CLOCK.FREQ_HZ       {100000000} \\
            CONFIG.SIGNAL_CLOCK.INSERT_VIP    {0} \\
            CONFIG.SLOT_0_AXI.INSERT_VIP      {0} \\
            CONFIG.SLOT_0_AXIS.INSERT_VIP     {0} \\
        ] [get_ips ila_debug_${name}]
        """
        ElaborationArtefacts.add( s"iladebug${name}.vivado.tcl", builder)
    }
}
Reply all
Reply to author
Forward
0 new messages