#Import template library
source templatcl.tcl

#Read primitive polynomials
poly readFile prim_polys

#Convert number to HEX
proc hex {num} {
	return [format %X $num]
}

# Source: http://wiki.tcl.tk/1591
# Convert  number to binary representation
proc dec2bin {i {width {}}} {
    #returns the binary representation of $i
    # width determines the length of the returned string (left truncated or added left 0)
    # use of width allows concatenation of bits sub-fields

    set res {}
    if {$i<0} {
        set sign -
        set i [expr {abs($i)}]
    } else {
        set sign {}
    }
    while {$i>0} {
        set res [expr {$i%2}]$res
        set i [expr {$i/2}]
    }
    if {$res eq {}} {set res 0}

    if {$width ne {}} {
        append d [string repeat 0 $width] $res
        set res [string range $d [string length $res] end]
    }
    return $sign$res
}

#Convert number to BIN
proc bin {num} {
	return [dec2bin $num]
}

proc binWidth {num width} {
  return [dec2bin $num $width]
}

###############################################
# expandVector pattern length                 #
# repeat $pattern to make vector $length long #
#                                             #
# Example:                                    #
#  expandVector 01 5 -> 10101                 #
###############################################
proc expandVector {pattern length} {
  # Save pattern length - to make successive lines shorter
  set patternLength [string length $pattern]

  # How many times we should repeat pattern to make vector
  set repeatTimes [expr {$length/$patternLength + 1}]

  # Repeat it
  set vector [string repeat $pattern $repeatTimes]

  # Range to be used from vector
  set vectorStart [expr {[string length $vector]-$length}]
  set vectorEnd   [string length $vector]

  # Truncate to expected length
  return [string range $vector $vectorStart $vectorEnd ]
}

proc doTclTemplate {fileName data} {
  #Read template from file
  set fp [open $fileName]
  set tmpl [read $fp]
  close $fp

  #Add current dateTime/user/templateName into data collection
  dict set data dateTime [clock format [clock seconds]]
  dict set data user $::env(USER)
  dict set data templateName $fileName

  dict for {k v} $data {
    set $k $v
  }

  #Process template with data
  set code [parse $tmpl]
	return [eval $code]
}

#Helper for saving data
proc saveFile {filename data} {
  set outFile [open $filename "w+"]
  puts $outFile $data
  close $outFile
}

#This method will create Test Sequencer by defined parameters
proc createTS {targetDir sourceFile memories} {
  puts "Test sequencer:"

  #Calculate parameters of counters
  set rowWidth 0
  set colWidth 0
  set wordWidth 0

  foreach {memory} $memories {
    set wordWidth [expr {$wordWidth + [dictGetCI $memory NumberOfBits]}]
    if {$colWidth < [dictGetCI $memory ColWidth]} {
      set colWidth [dictGetCI $memory ColWidth]
    }
    if {$rowWidth < [dictGetCI $memory RowWidth]} {
      set rowWidth [dictGetCI $memory RowWidth]
    }
  }
  puts "\tWord width: $wordWidth"
  puts "\tColumn counter width: $colWidth"
  puts "\tRow counter width: $rowWidth"

  puts "\tMarch algorithm source: $sourceFile"

  #Read microcode
  microcode create test $sourceFile

  #Compile it
  set compiled [test compile]
  set numInstructions [llength $compiled]
  puts "\t# microinstructions: $numInstructions"

  #Find required address width for microcode
  set addressWidth 0
  foreach instr $compiled {
    # Width = roundUp(log2(address))
    set currWidth [expr {ceil(log10([dict get $instr "next_adr"])/log10(2))}]

    if { $currWidth > $addressWidth} {
      set addressWidth [expr {int($currWidth)}]
    }

    set currWidth [expr {ceil(log10([dict get $instr "jump_adr"])/log10(2))}]

    if { $currWidth > $addressWidth} {
      set addressWidth [expr {int($currWidth)}]
    }
  }

  puts "\tMicroinstruction address width: $addressWidth"
  set rowPoly [poly getPrimitive $rowWidth]
  set colPoly [poly getPrimitive $colWidth]
  puts "\tRow CFSR polynom: [poly print $rowPoly]"
  puts "\tColumn CFSR polynom: [poly print $colPoly]"

  #Run trough template
  set properties [dict create microcode $compiled addressWidth $addressWidth \
  rowWidth $rowWidth colWidth $colWidth \
  rowPoly  $rowPoly colPoly  $colPoly \
  ncntSteps $wordWidth ncntWidth [expr {int(ceil(log10($wordWidth)/log10(2)))}]      \
  numInstructions [expr {int(pow(2,$addressWidth)-1)}] ]

  #Generate Test sequencer
  set targetFile $targetDir/mbist_ts_core.str.vhd
  set ts [doTclTemplate templates/mbist_ts_core.rtl.vhd.ttcl $properties]

  #Save to correctly names file
  saveFile $targetDir/mbist_ts_core.str.vhd $ts
  puts "\tTarget file: $targetFile"

  #Generate correct PKG for it
  createPkg $targetDir $rowWidth $colWidth $wordWidth $addressWidth

  return [dict create MicrocodeLength [llength $compiled]]
}

proc createPkg {targetDir rowWidth colWidth wordBits mcAddrWidth} {
  puts "Generate BIST package"

  #Make sure that all is integer
  set rowWidth [expr {int($rowWidth)}]
  set colWidth [expr {int($colWidth)}]
  set mcAddrWidth [expr {int($mcAddrWidth)}]

  #Debug info
  puts "\tRow width: $rowWidth"
  puts "\tColumn width: $colWidth"
  puts "\tTotal number of bits in word: $wordBits"
  puts "\tMicrocode address width: $mcAddrWidth"
  #Generate parameters dictionary
  set parameters [dict create rowWidth $rowWidth colWidth $colWidth mcAddrWidth $mcAddrWidth]

  #Process template and save result
  set targetFile $targetDir/mbist_pkg.vhd
  puts "\tTarget file: $targetFile"
  set pkg [doTclTemplate templates/mbist_pkg.vhd.ttcl $parameters]
  saveFile $targetFile $pkg
}

# Parse Name[MSB:LSB] into array
proc getRange {text} {
  set params [split $text "\[:\]"]
  set range(Name) [string trim [lindex $params 0]]
  if {[llength $params] > 1} {
    set range(MSB)  [lindex $params 1]
    set range(LSB)  [lindex $params 2]
  }

  return [array get range]
}

#Find item in dictionary. Perform Case-insensitive search
proc dictGetCI {dictionary key {default 0}} {
  foreach {k} [dict keys $dictionary] {
    if {[string toupper $key] == [string toupper $k]} {
      return [dict get $dictionary $k]
    }
  }
  if {$default != 0} {return $default}
}

proc dictExistsCI {dictionary key} {
  foreach {k} [dict keys $dictionary] {
    if {[string toupper $key] == [string toupper $k]} {
      return 1
    }
  }
  return 0
}


proc createSS {targetDir memoryFile} {

  puts "Generating interface for SSRAM $memoryFile"

  #Read memory configuration
  set memoryLvlib [readLvlib $memoryFile]

  #Parameters collection
  set parameters [dict create]

  #Name of the memory cell
  set memoryId [dict get $memoryLvlib CellName]
  dict set parameters ComponentName $memoryId
  puts "\tCellName: $memoryId"


  #Get memory ports
  set portSection [dict filter $memoryLvlib script {k v} {
    # 1. $v has to have number of items dividable by 2 -> can be DICT
    # 2. Test for element #type - on Tcl 8.5 will fail when $v is not dictionary
    # 3. Finally test that #type is Port
    expr {
      ([llength $v] %2 eq 0)
      &&
      ([dict exist $v "#type"] eq 1)
      &&
      ([dict get $v "#type"] eq "Port")
    }
  }]


  #Create dict with ports for template
  set ports ""

  set dbgPorts {}
  #Parse port range
  dict for {k v} $portSection {

    #Parse range property
    set port [getRange $k]

    #Split name to parts (Name, range)
    array set nameParts [getRange $k]

    dict set v Name $nameParts(Name)
    append dbgPorts " $nameParts(Name)"

    if {[array size nameParts] > 1} {
      dict set v MSB  $nameParts(MSB)
      dict set v LSB  $nameParts(LSB)

      append dbgPorts "\[$nameParts(MSB):$nameParts(LSB)\]"
    }

    array unset nameParts

    lappend ports $v
  }

  dict set parameters Ports $ports
  puts "\tMemory ports: $dbgPorts"

  #Get address mapping
  set addressMaP [dict get [dict get [dict get $memoryLvlib AddressCounter] Address] LogicalAddressMaP]

  #Save it in usable form - parse ranges and save items into array
  set addresses {}
  set rowAddrWidth 0
  set colAddrWidth 0
  dict for {k v} $addressMaP {
    array set target [getRange $v]
    array set counter [getRange $k]

    set address [dict create]
    dict set address source_MSB $counter(MSB)
    dict set address source_LSB $counter(LSB)
    dict set address target_MSB  $target(MSB)
    dict set address target_LSB  $target(LSB)
    dict set address source     [string toupper $counter(Name)]

    array set range [getRange [lindex [dict keys [dictGetCI [dictGetCI $memoryLvlib AddressCounter] $counter(Name)]] 0]]

    #Address ranges for specified memory. In other addresses bypass logic is enabled
    if {$range(MSB) < $range(LSB)} {
      dict set address low_limit  $range(MSB)
      dict set address high_limit $range(LSB)
    } {
      dict set address low_limit  $range(LSB)
      dict set address high_limit $range(MSB)
    }

    if {[string compare [dictGetCI $address source] "COLUMNADDRESS"]==0} {
      set colAddrWidth [expr {[dictGetCI $address source_MSB]+1}]
    }
    if {[string compare [dictGetCI $address source] "ROWADDRESS"]==0} {
      set rowAddrWidth [expr {[dictGetCI $address source_MSB]+1}]
    }

    lappend addresses $address
  }
  dict set parameters Addresses $addresses
  puts "\tRow address width : $rowAddrWidth"
  puts "\tCol address width : $colAddrWidth"

  #Width of word
  set numberOfBits [dictGetCI $memoryLvlib NumberOfBits]
  dict set parameters NumberOfBits $numberOfBits
  puts "\tNumber of bits in world: $numberOfBits"

  #Process template and save result
  set targetFile "$targetDir/mbist_mai_$memoryId.rtl.vhd"
  puts "\tTarget file : $targetFile"
  set mai [doTclTemplate templates/mai_ssram.ttcl $parameters]
  saveFile $targetFile $mai

  return [dict create Ports $ports NumberOfBits [dictGetCI $memoryLvlib NumberOfBits] ComponentName [dictGetCI $memoryLvlib CellName] RowWidth $rowAddrWidth ColWidth $colAddrWidth AddressMaP $addresses]
}

###############################################
# createTop targetFolder memories             #
# ------------------------------------------- #
#  - targetFolder - folder to save the result #
#  - memories - list of dicts about memory    #
#             - structure:                    #
# dict - Ports                                #
#        - Name                               #
#        - MSB, LSB                           #
#        - Dicrection                         #
#      - NumberOfBits                         #
#      - ComponentName                        #
###############################################
proc createTop {targetFolder memories memNames words} {

  puts "Generating TOP"

  set parameters [dict create Memories $memories Names $memNames Words $words]

  #Save generated TOP
  set targetFile $targetFolder/mbist_top.str.vhd
  puts "\tTarget file : $targetFile"
  set top [doTclTemplate templates/mbist_top.str.vhd.ttcl $parameters]
  saveFile $targetFile $top
}

###############################################
# createTB  targetFolder memories             #
# ------------------------------------------- #
#  - targetFolder - folder to save the result #
#  - memories - list of dicts about memory    #
#             - structure:                    #
# dict - Ports                                #
#        - Name                               #
#        - MSB, LSB                           #
#        - Dicrection                         #
#      - NumberOfBits                         #
#      - ComponentName                        #
###############################################
proc createTB {targetFolder memories memNames words} {

  puts "Generating TB"

  set parameters [dict create Memories $memories Names $memNames Words $words]

  #Save generated TOP
  set targetFile $targetFolder/tb.sv
  puts "\tTarget file : $targetFile"
  set top [doTclTemplate templates/mbist_tb.sv.ttcl $parameters]
  saveFile $targetFile $top
}

###############################################
# createFof targetFolder memories             #
# ------------------------------------------- #
#  - targetFolder - folder to save the result #
#  - memories - list of dicts about memory    #
#             - structure:                    #
# dict - Ports                                #
#        - Name                               #
#        - MSB, LSB                           #
#        - Dicrection                         #
#      - NumberOfBits                         #
#      - ComponentName                        #
###############################################
proc createFof {targetFolder memories} {
  puts "Generating FOF"

  set parameters [dict create Memories $memories]

  set targetFile $targetFolder/mbist.fof.tcl
  puts "\tTarget file: $targetFile"
  set fof [doTclTemplate templates/mbist.fof.tcl.ttcl $parameters]
  saveFile $targetFile $fof
}

###############################################
# createBfm targetFolder memories             #
# ------------------------------------------- #
#  - targetFolder - folder to save the result #
#  - memories - list of dicts about memory    #
#             - structure:                    #
# dict - Ports                                #
#        - Name                               #
#        - MSB, LSB                           #
#        - Dicrection                         #
#      - NumberOfBits                         #
#      - ComponentName                        #
###############################################
proc createBfm {targetFolder memories memNames ts} {
  puts "Generating BFM"

  set rowWidth 0
  set colWidth 0
  set wordWidth 0

  foreach {memory} $memories {
    set wordWidth [expr {$wordWidth + [dictGetCI $memory NumberOfBits]}]
    if {$colWidth < [dictGetCI $memory ColWidth]} {
      set colWidth [dictGetCI $memory ColWidth]
    }
    if {$rowWidth < [dictGetCI $memory RowWidth]} {
      set rowWidth [dictGetCI $memory RowWidth]
    }
  }

  set mcLen [dictGetCI $ts MicrocodeLength]

  puts "\tWidth of word: $wordWidth"
  puts "\tWidth of row address: $rowWidth"
  puts "\tWidth of column address: $colWidth"
  puts "\tLength of microcode : $mcLen"
  set parameters [dict create ncntSteps $wordWidth rowWidth $rowWidth colWidth $colWidth mcLength $mcLen memories $memories memNames $memNames]

  set targetFile $targetFolder/mbist.sv
  puts "\tTarget file: $targetFile"
  set fof [doTclTemplate templates/mbist_bfm.sv.ttcl $parameters]
  saveFile $targetFile $fof
}
