#!/bin/bash

#
# File .......... swuk_create_project
# Author ........ Steve Haywood
# Website ....... http://www.spacewire.co.uk
# Project ....... Common (SpaceWire UK Tutorial)
# Date .......... 24 Jun 2025
# Version ....... 2.0
# History .......
#   1.0 common/other/src/script/create_project_structure.sh
# Description ...
#   Simple script to create a possible project directory structure for combined
# firmware, hardware, operating system & software development. Very much work
# in progress and by all means not a golden solution to anything. Now includes
# the option of creating a baseline project. Edit the 'User constants' &
# 'Directory structure' to suit.
#

# Strict
set -euo pipefail

# User constants
declare -r c_author="Steve Haywood"                # Str: Author
declare -r c_company="SpaceWire UK"                # Str: Company
declare -r c_website="http://www.spacewire.co.uk"  # Str: Website
declare -r c_project="Zedboard Baseline"           # Str: Project
declare -r c_tutorial="SpaceWire UK Tutorial"      # Str: Tutorial
declare -r c_date=$(date '+%d %b %Y')              # Str: Date
declare -r c_version="1.0"                         # Str: Version

# Directory structure
declare -ra c_structure=(  # Arr: Project subdirectories
  # Firmware
  "fw/src/constraint"
  "fw/src/design"
  "fw/src/diagram"
  "fw/src/document"
  "fw/src/ip"
  "fw/src/ip_repo"
  "fw/src/other"
  "fw/src/script"
  "fw/src/testbench"
  # Hardware
  "hw/src/schematic"
  # Operating System
  "os/src/other"
  # Software
  "sw/src/c"
  "sw/src/other"
  "sw/src/script"
)


################################################################################
# Create directory structure.
# Arguments ...... $1 ... Str: Project directory
# Return ......... None
# Shared (In) .... None
# Shared (Out) ... None
create_structure()
{
  # Declare local constants
  local -r  projdir=$1  # Str: Project directory

  # Declare local variables
  local     dir         # Str: Current directory from c_structure array

  # Echo operation
  echo "Creating project directory structure..."

  # Iterate through directories
  for dir in "${c_structure[@]}"
  do
    mkdir -p "${projdir}/${dir}"
  done
}


################################################################################
# Create baseline project.
# Arguments ...... $1 ... Str: Project directory
#                  $2 ... Str: Author
#                  $3 ... Str: Company
#                  $4 ... Str: Website
#                  $5 ... Str: Project
#                  $6 ... Str: Tutorial
# Return ......... None
# Shared (In) .... None
# Shared (Out) ... None
create_baseline()
{
  # Declare local constants
  local -r projdir=$1   # Str: Project directory
  local -r author=$2    # Str: Author
  local -r company=$3   # Str: Company
  local -r website=$4   # Str: Website
  local -r project=$5   # Str: Project
  local -r tutorial=$6  # Str: Tutorial

  [[ -z ${tutorial} ]] && local -r combo="$project" || local -r combo="$project ($tutorial)"

  # Echo operation
  echo "Creating baseline project..."

  # Create constraints file
cat << EOF > ${projdir}/fw/src/constraint/${projdir}.xdc
#
# File .......... ${projdir}.xdc
# Author ........ ${author}
# Website ....... ${website}
# Project ....... ${combo}
# Date .......... ${c_date}
# Version ....... ${c_version}
# Description ...
#   Top level pin & timing constraints.
#


# User LEDs - Bank 33
set_property PACKAGE_PIN T22 [get_ports {leds[0]}];  # "LD0"
set_property PACKAGE_PIN T21 [get_ports {leds[1]}];  # "LD1"
set_property PACKAGE_PIN U22 [get_ports {leds[2]}];  # "LD2"
set_property PACKAGE_PIN U21 [get_ports {leds[3]}];  # "LD3"
set_property PACKAGE_PIN V22 [get_ports {leds[4]}];  # "LD4"
set_property PACKAGE_PIN W22 [get_ports {leds[5]}];  # "LD5"
set_property PACKAGE_PIN U19 [get_ports {leds[6]}];  # "LD6"
set_property PACKAGE_PIN U14 [get_ports {leds[7]}];  # "LD7"


# User Push Buttons - Bank 34
set_property PACKAGE_PIN P16 [get_ports {buttons[0]}];  # "BTNC"
set_property PACKAGE_PIN R16 [get_ports {buttons[1]}];  # "BTND"
set_property PACKAGE_PIN N15 [get_ports {buttons[2]}];  # "BTNL"
set_property PACKAGE_PIN R18 [get_ports {buttons[3]}];  # "BTNR"
set_property PACKAGE_PIN T18 [get_ports {buttons[4]}];  # "BTNU"


# User DIP Switches - Bank 34 & 35
set_property PACKAGE_PIN F22 [get_ports {switches[0]}];  # "SW0"
set_property PACKAGE_PIN G22 [get_ports {switches[1]}];  # "SW1"
set_property PACKAGE_PIN H22 [get_ports {switches[2]}];  # "SW2"
set_property PACKAGE_PIN F21 [get_ports {switches[3]}];  # "SW3"
set_property PACKAGE_PIN H19 [get_ports {switches[4]}];  # "SW4"
set_property PACKAGE_PIN H18 [get_ports {switches[5]}];  # "SW5"
set_property PACKAGE_PIN H17 [get_ports {switches[6]}];  # "SW6"
set_property PACKAGE_PIN M15 [get_ports {switches[7]}];  # "SW7"


# Banks
set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 33]];
set_property IOSTANDARD LVCMOS18 [get_ports -of_objects [get_iobanks 34]];
set_property IOSTANDARD LVCMOS18 [get_ports -of_objects [get_iobanks 35]];


# False Paths
set_false_path -to [get_pins {system_i/axi_gpio_zed_0/inst/genblk1[*].meta_hardener/meta_reg/D}]
EOF

  # Create top-level design file
cat << EOF > ${projdir}/fw/src/design/${projdir}.sv
//
// File .......... ${projdir}.sv
// Author ........ ${author}
// Website ....... ${website}
// Project ....... ${combo}
// Date .......... ${c_date}
// Version ....... ${c_version}
// Description ...
//   Top level wrapper for the system block design.
//


timeunit      1ns;
timeprecision 1ps;


module ${projdir} #
(
  // Parameters
  parameter string id_description = "",  // P:Description ............ Max 128 Characters
  parameter string id_company     = "",  // P:Company ................ Max  64 Characters
  parameter string id_author      = "",  // P:Author ................. Max  64 Characters
  parameter string id_version     = "",  // P:Version ................ Max  32 Characters
  parameter string id_timestamp   = "",  // P:Timestamp .............. Max  32 Characters
  parameter string id_hash        = ""   // P:Hash ................... Max  64 Characters
)
(
  // LEDs
  output [ 7:0] leds,               // O:LEDs
  // DIP Switches
  input  [ 7:0] switches,           // I:DIP Switches
  // Push Buttons
  input  [ 4:0] buttons,            // I:Push Buttons
  // System
  inout  [14:0] DDR_addr,           // B:Address
  inout  [ 2:0] DDR_ba,             // B:Bank Address
  inout         DDR_cas_n,          // B:Column Address Select
  inout         DDR_ck_n,           // B:Clock (Neg)
  inout         DDR_ck_p,           // B:Clock (Pos)
  inout         DDR_cke,            // B:Clock Enable
  inout         DDR_cs_n,           // B:Chip Select
  inout  [ 3:0] DDR_dm,             // B:Data Mask
  inout  [31:0] DDR_dq,             // B:Data Input/Output
  inout  [ 3:0] DDR_dqs_n,          // B:Data Strobe (Neg)
  inout  [ 3:0] DDR_dqs_p,          // B:Data Strobe (Pos)
  inout         DDR_odt,            // B:Output Dynamic Termination
  inout         DDR_ras_n,          // B:Row Address Select
  inout         DDR_reset_n,        // B:Reset
  inout         DDR_we_n,           // B:Write Enable
  inout         FIXED_IO_ddr_vrn,   // B:Termination Voltage
  inout         FIXED_IO_ddr_vrp,   // B:Termination Voltage
  inout  [53:0] FIXED_IO_mio,       // B:Peripheral Input/Output
  inout         FIXED_IO_ps_clk,    // B:System Reference Clock
  inout         FIXED_IO_ps_porb,   // B:Power On Reset
  inout         FIXED_IO_ps_srstb   // B:External System Reset
);


  // Function to convert a string to a vector (max 128 characters)
  function automatic [1023:0] fmt(input string str);
    int len;
    bit [1023:0] tmp;
    len = str.len();
    for (int i=0; i<len; i++)
      tmp[8*i +: 8] = str.getc(i);
    fmt = tmp;
  endfunction


  // Top-Level Block Design
  system system_i
   (
    // Identification Strings
    .id_description    ( fmt(id_description) ),  // P:Description
    .id_company        ( fmt(id_company)     ),  // P:Company
    .id_author         ( fmt(id_author)      ),  // P:Author
    .id_version        ( fmt(id_version)     ),  // P:Version
    .id_timestamp      ( fmt(id_timestamp)   ),  // P:Timestamp
    .id_hash           ( fmt(id_hash)        ),  // P:Hash
    // LEDs
    .leds              ( leds                ),  // O:LEDs
    // DIP Switches
    .switches          ( switches            ),  // I:DIP Switches
    // Push Buttons
    .buttons           ( buttons             ),  // I:Push Buttons
    // System
    .DDR_addr          ( DDR_addr            ),  // B:Address
    .DDR_ba            ( DDR_ba              ),  // B:Bank Address
    .DDR_cas_n         ( DDR_cas_n           ),  // B:Column Address Select
    .DDR_ck_n          ( DDR_ck_n            ),  // B:Clock (Neg)
    .DDR_ck_p          ( DDR_ck_p            ),  // B:Clock (Pos)
    .DDR_cke           ( DDR_cke             ),  // B:Clock Enable
    .DDR_cs_n          ( DDR_cs_n            ),  // B:Chip Select
    .DDR_dm            ( DDR_dm              ),  // B:Data Mask
    .DDR_dq            ( DDR_dq              ),  // B:Data Input/Output
    .DDR_dqs_n         ( DDR_dqs_n           ),  // B:Data Strobe (Neg)
    .DDR_dqs_p         ( DDR_dqs_p           ),  // B:Data Strobe (Pos)
    .DDR_odt           ( DDR_odt             ),  // B:Output Dynamic Termination
    .DDR_ras_n         ( DDR_ras_n           ),  // B:Row Address Select
    .DDR_reset_n       ( DDR_reset_n         ),  // B:Reset
    .DDR_we_n          ( DDR_we_n            ),  // B:Write Enable
    .FIXED_IO_ddr_vrn  ( FIXED_IO_ddr_vrn    ),  // B:Termination Voltage
    .FIXED_IO_ddr_vrp  ( FIXED_IO_ddr_vrp    ),  // B:Termination Voltage
    .FIXED_IO_mio      ( FIXED_IO_mio        ),  // B:Peripheral Input/Output
    .FIXED_IO_ps_clk   ( FIXED_IO_ps_clk     ),  // B:System Reference Clock
    .FIXED_IO_ps_porb  ( FIXED_IO_ps_porb    ),  // B:Power On Reset
    .FIXED_IO_ps_srstb ( FIXED_IO_ps_srstb   )   // B:External System Reset
   );


endmodule
EOF

  # Create project file
cat << EOF > ${projdir}/fw/project.txt
${project}
${company}
${author}
${c_version}
EOF

  # Copy .gitignore
  cp ${swuk_tutorial}/common/.gitignore ${projdir}
}


################################################################################
# Main function.
# Arguments ...... ${@}
# Return ......... None
# Exit code ...... Status (0=success, 1=failure)
# Shared (In) .... None
# Shared (Out) ... None
main()
{
  # Declare constants
  local -r  c_optbase="--baseline"  # Str: Baseline option name
  local -r  c_opthelp="--help"      # Str: Help option name
  local -Ar c_options=(             # ARR: Options & associated help information
    [${c_optbase}]="Create a baseline project inside the project directory structure."
    [${c_opthelp}]="Display this help and exit."
  )
  local -ar c_optorder=(            # Arr: Help options display order
    ${c_optbase}
    ${c_opthelp}
  )
  local -ar argv=(${@})             # Arr: Get argument values (space-separated) into array

  # Declare variables
  local     arg                     # Str: Current argument from argv array
  local     option                  # Str: Current option from c_optorder array
  local     projdir=""              # Str: Project directory [arg] (mandatory)

  local     author                  # Str: Author
  local     company                 # Str: Company
  local     website                 # Str: Website
  local     project                 # Str: Project
  local     tutorial                # Str: Tutorial

  # Display help information
  if [[ " ${argv[*]} " =~ " ${c_opthelp} " ]]; then
    echo "Usage: $(basename ${0}) PROJECT-DIRECTORY... [OPTION]..."
    echo "Create a new SWUK project (directory structure) in the PROJECT-DIRECTORY & optionally generate a baseline project."
    echo
    for option in ${c_optorder[@]}
    do
      echo "      ${option} $(printf ' %.0s' {1..12} | head -c $((12-${#option}))) ${c_options[${option}]}"
    done
    echo
    exit 0
  fi

  # Get & check the arguments
  for arg in ${argv[@]}; do
    if [[ ${arg:0:2} == "--" ]]; then  # Option
      [[ ! -v c_options[${arg}] ]] && echo "Option (${arg}) is not recognised!" >&2 && exit 1
    elif [[ -z ${projdir} ]]; then  # Project directory
      projdir=${arg}
      [[ -d ${projdir} ]] && echo "Project directory (${arg}) already exists!" >&2 && exit 1
    else
      echo "Unexpected argument (${arg}) found!" >&2
      exit 1
    fi
  done

  # Further check the arguments
  [[ -z ${projdir} ]] && echo "No project directory specified!" >&2 && exit 1

  if [[ " ${argv[*]} " =~ " ${c_optbase} " ]]; then
    echo "Enter details for the header blocks..."
    echo "File .......... ${projdir}"
    read -e -p "Author ........ " -i "${c_author}" author
    [[ -z ${author} ]] && echo "No author specified!" >&2 && exit 1
    read -e -p "Company ....... " -i "${c_company}" company
    [[ -z ${company} ]] && echo "No company specified!" >&2 && exit 1
    read -e -p "Website ....... " -i "${c_website}" website
    [[ -z ${website} ]] && echo "No website specified!" >&2 && exit 1
    read -e -p "Project ....... " -i "${c_project}" project
    [[ -z ${project} ]] && echo "No project specified!" >&2 && exit 1
    read -e -p "Tutorial ...... " -i "${c_tutorial}" tutorial
    echo "Date .......... ${c_date}"
    echo "Version ....... ${c_version}"
  fi

  # Create directory structure.
  create_structure ${projdir}

  # Create baseline project.
  [[ " ${argv[*]} " =~ " ${c_optbase} " ]] &&
    create_baseline "${projdir}" "${author}" "${company}" "${website}" \
                    "${project}" "${tutorial}"
}


################################################################################
# Opening gambit.
# Arguments ...... ${@}
# Return ......... None
# Exit code ...... Status (0=success, 1=failure)
# Shared (In) .... None
# Shared (Out) ... None
main ${@}
exit 0
