Program Listing for File EmitHLSToVivadoTcl.cpp

↰ Return to documentation for file (lib/Target/EmitHLSToVivadoTcl/EmitHLSToVivadoTcl.cpp)

// Implementation of translation EmitHLSToVivadoTcl
//
// @file
// @author     Jiahong Bi (jiahong.bi@tu-dresden.de)

#include "laksa-mlir/Dialect/EmitHLS/IR/EmitHLS.h"
#include "laksa-mlir/Target/EmitHLSToVivadoTcl/VivadoTclEmitter.h"

#include "llvm/Support/CommandLine.h"

using namespace mlir;
using namespace mlir::emithls;

namespace {

llvm::cl::opt<std::string> clVivadoTargetDevice(
    "vivado-target-device",
    llvm::cl::desc(
        "Target part passed to Vivado's create_project in the generated "
        "run_vivado.tcl"),
    llvm::cl::init("xck26-sfvc784-2LV-c"));

} // namespace

// Finds the function marked as the design's top function, i.e. the one whose
// body contains a PragmaTopInterfaceOp. Its symbol name doubles as the
// Vivado project name and the HLS IP's VLNV/cell name.
static FailureOr<FuncOp> findTopFunc(ModuleOp moduleOp)
{
    FuncOp topFunc = nullptr;
    auto result = moduleOp.walk([&](PragmaTopInterfaceOp topInterfaceOp) {
        FuncOp funcOp = topInterfaceOp->getParentOfType<FuncOp>();
        if (!funcOp || topFunc) return WalkResult::interrupt();
        topFunc = funcOp;
        return WalkResult::advance();
    });
    if (result.wasInterrupted() || !topFunc)
        return moduleOp->emitError(
            "expected exactly one function containing a top_interface "
            "pragma");
    return topFunc;
}

//===----------------------------------------------------------------------===//
// Translation entry point
//===----------------------------------------------------------------------===//

LogicalResult emithls::translateEmitHLSToVivadoTcl(ModuleOp op, raw_ostream &os)
{
    FailureOr<FuncOp> topFunc = findTopFunc(op);
    if (failed(topFunc)) return failure();
    StringRef projectName = topFunc->getSymName();
    unsigned numArgs = topFunc->getNumArguments();

    os << "set project_name \"" << projectName << "\"\n"
       << "set project_dir \"./vivado_project/\"\n"
       << "set project_path \"$project_dir/$project_name.xpr\"\n"
       << "set target_device \"" << clVivadoTargetDevice << "\"\n"
       << "set ip_repo_dir \"./hls_project\"\n"
       << "if {[file exists $project_dir]} {\n"
       << "  puts \"INFO: Project directory exists.\"\n"
       << "  exit 1\n"
       << "}\n"
       << "puts \"INFO: Creating a new project...\"\n"
       << "create_project $project_name $project_dir -part $target_device\n"
       << "set_property part $target_device [current_project]\n"
       << "set_property default_lib xil_defaultlib [current_project]\n"
       << "set_property target_language Verilog [current_project]\n"
       << "if {[file exists $ip_repo_dir]} {\n"
       << "  puts \"INFO: Adding IP repository: $ip_repo_dir\"\n"
       << "  set_property ip_repo_paths $ip_repo_dir [current_project]\n"
       << "} else {\n"
       << "  puts \"WARNING: IP repository directory $ip_repo_dir does not "
          "exist!\"\n"
       << "  exit 1\n"
       << "}\n"
       << "set bd_name \"${project_name}_bd\"\n"
       << "puts \"INFO: Creating block design: $bd_name\"\n"
       << "create_bd_design $bd_name\n"
       << "puts \"INFO: Adding Zynq MPSoC to the block design\"\n"
       << "set zynq_mpsoc [create_bd_cell -type ip -vlnv "
          "xilinx.com:ip:zynq_ultra_ps_e:3.5 zynq_mpsoc]\n"
       << "puts \"INFO: Applying board preset to Zynq MPSoC\"\n"
       << "apply_bd_automation -rule xilinx.com:bd_rule:zynq_ultra_ps_e "
          "-config {apply_board_preset \"1\"} $zynq_mpsoc\n"
       << "set_property CONFIG.PSU__FPGA_PL0_ENABLE {1} $zynq_mpsoc\n"
       << "set_property CONFIG.PSU__USE__M_AXI_GP0 {1} $zynq_mpsoc\n"
       << "set_property CONFIG.PSU__USE__M_AXI_GP1 {0} $zynq_mpsoc\n"
       << "set_property CONFIG.PSU__USE__M_AXI_GP2 {0} $zynq_mpsoc\n"
       << "set_property CONFIG.PSU__USE__S_AXI_GP0 {1} $zynq_mpsoc\n"
       << "set_property CONFIG.PSU__CRL_APB__PL0_REF_CTRL__FREQMHZ {100} "
          "$zynq_mpsoc\n"
       << "puts \"INFO: Adding HLS IP kernel to the block design\"\n"
       << "create_bd_cell -type ip -vlnv xilinx.com:hls:" << projectName
       << ":1.0 " << projectName << "\n"
       << "apply_bd_automation -rule xilinx.com:bd_rule:axi4 -config { "
          "Clk_master {Auto} Clk_slave {Auto} Clk_xbar {Auto} Master "
          "{/zynq_mpsoc/M_AXI_HPM0_FPD} Slave {/"
       << projectName
       << "/s_axi_control} ddr_seg {Auto} intc_ip {New AXI SmartConnect} "
          "master_apm {0}}  [get_bd_intf_pins "
       << projectName << "/s_axi_control]\n";

    // Each top-function argument becomes an m_axi_gmem_argN master port on the
    // HLS IP, which needs to be wired to the Zynq's HPC0 slave port. Automating
    // the first one enables Zynq's S_AXI_HPC0_FPD slave port and creates a new
    // AXI SmartConnect. Every subsequent one reuses that same SmartConnect,
    // automating the HLS IP's own port instead since the Zynq side is already
    // wired up.
    for (unsigned i = 0; i < numArgs; ++i) {
        os << "apply_bd_automation -rule xilinx.com:bd_rule:axi4 -config { "
              "Clk_master {Auto} Clk_slave {Auto} Clk_xbar {Auto} Master {/"
           << projectName << "/m_axi_gmem_arg" << i
           << "} Slave {/zynq_mpsoc/S_AXI_HPC0_FPD} ddr_seg {Auto} intc_ip {";
        if (i == 0)
            os << "New AXI SmartConnect";
        else
            os << "/axi_smc_1";
        os << "} master_apm {0}} [get_bd_intf_pins ";
        if (i == 0)
            os << "zynq_mpsoc/S_AXI_HPC0_FPD";
        else
            os << projectName << "/m_axi_gmem_arg" << i;
        os << "]\n";
    }

    os << "puts \"INFO: Validating block design: $bd_name\"\n"
       << "if {[catch {validate_bd_design} result]} {\n"
       << "    puts \"Block design validation failed: $result\"\n"
       << "    exit 1\n"
       << "}\n"
       << "puts \"INFO: Validating succeeded\"\n"
       << "regenerate_bd_layout\n"
       << "save_bd_design\n"
       << "puts \"INFO: Closing block design: $bd_name\"\n"
       << "make_wrapper -files [get_files "
          "\"$project_dir/$project_name.srcs/sources_1/bd/$bd_name/"
          "$bd_name.bd\"] -top\n"
       << "set wrapper_path "
          "\"$project_dir/$project_name.srcs/sources_1/bd/$bd_name/hdl/"
          "${bd_name}_wrapper.v\"\n"
       << "add_files $wrapper_path\n"
       << "set_property top \"${bd_name}_wrapper\" [get_filesets sources_1]\n"
       << "set max_cores [get_param general.maxThreads]\n"
       << "puts \"INFO: Using up to $max_cores threads for synthesis and "
          "implementation.\"\n"
       << "puts \"INFO: Running Synthesis...\"\n"
       << "launch_runs synth_1 -jobs $max_cores\n"
       << "wait_on_run synth_1\n"
       << "set synth_status [get_property STATUS [get_runs synth_1]]\n"
       << "if {![string match \"*Complete!*\" $synth_status]} {\n"
       << "    puts \"ERROR: Synthesis failed with status: $synth_status\"\n"
       << "    exit 1\n"
       << "}\n"
       << "puts \"INFO: Synthesis completed successfully\"\n"
       << "puts \"INFO: Running Implementation...\"\n"
       << "reset_run impl_1\n"
       << "launch_runs impl_1 -to_step write_bitstream -jobs $max_cores\n"
       << "wait_on_run impl_1\n"
       << "set impl_status [get_property STATUS [get_runs impl_1]]\n"
       << "if {![string match \"*Complete!*\" $impl_status]} {\n"
       << "    puts \"ERROR: Implementation failed with status: "
          "$impl_status\"\n"
       << "    exit 1\n"
       << "}\n"
       << "puts \"INFO: Implementation completed successfully\"\n"
       << "puts \"INFO: Exporting hardware with bitstream\"\n"
       << "write_hw_platform -fixed -include_bit -force -file "
          "$project_dir/${project_name}_bd.xsa\n"
       << "exit\n";

    return success();
}