#-----------------------------------------------------------------------------
# Title      : Convert Memory File to BRAM
#-----------------------------------------------------------------------------
# File       : mem2bram.tcl
# Author     : Peter Jansweijer <peterj@nikhef.nl>
# Company    : Nikhef
# Created    : 2020-03-25
# Last update: 2021-06-01
# Platform   : FPGA-generics
# Standard   : VHDL
#-----------------------------------------------------------------------------
# Description:
#
# $1	The first paramter is the filename to be used for input and output
# $2    Second parameter is the ramsize in bytes
#-----------------------------------------------------------------------------
#
# Copyright (c) 2021 Nikhef, Peter Jansweijer
#
# This source file is free software; you can redistribute it   
# and/or modify it under the terms of the GNU Lesser General   
# Public License as published by the Free Software Foundation; 
# either version 2.1 of the License, or (at your option) any   
# later version.                                               
#
# This source is distributed in the hope that it will be       
# useful, but WITHOUT ANY WARRANTY; without even the implied   
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR      
# PURPOSE.  See the GNU Lesser General Public License for more 
# details.                                                     
#
# You should have received a copy of the GNU Lesser General    
# Public License along with this source; if not, download it   
# from http://www.gnu.org/licenses/lgpl-2.1.html
#
#-----------------------------------------------------------------------------

# The names of the input MEM file and the output bram file:
puts "MEM input file name = [set mem_file "$1.mem"]"
puts "BRAM output file name = [set bram_file "$1.bram"]"

if {$argc == 2} {
  puts "ramsize in bytes = [set ram_size [expr (int($2))]]"
} else {
  # use default 13072
  puts "ramsize in bytes = [set ram_size 131072]"
}

# The format used by the White Rabbit community (memory_loader_pkg.vhd)
# 32 bit binary data, one address per line

puts "DPRAM_Size = [set dpram_size [expr {$ram_size/4}]], 32 bit words"

###############################################################################

proc endian_swap32 {bin32} {
  if { [string length $bin32] == 32 } {
    set swap    [string range $bin32 24 31]
    append swap [string range $bin32 16 23]
    append swap [string range $bin32  8 15]
    append swap [string range $bin32  0  7]
  } else {
    set swap 0
    puts "Warning: try to endian swap 32 bits with less than 32 bits input"
  }
  return $swap
}

###############################################################################

set addr_ptr 0
set bramline ""

if {[file exists $mem_file]} {
  set mem_fileptr [open $mem_file]
  set bram_fileptr [open $bram_file w]
  set lineCount 0
  set first true

  # Note from UG898:
  # "IMPORTANT: Although Processor Endianness is defined in the MMI file, it is not supported by
  # UpdateMEM at this time."
  # updatemem acts by default "Little" endian! Swap necessairy for Big endian LM32!
  set swap false

  while {[gets $mem_fileptr line] >= 0} {
    set lineElements [llength $line]
    # Skip empty and comment lines (preceeded by "//")
    if { $lineElements != 0 && [lindex $line 0] != "//"} {
      for {set x 0} {$x < $lineElements} {incr x} { 
        set lineElem [lindex $line $x]
        
        # Addresses are preceeded with a "@" character
        if { [string index $lineElem 0] == "@"} {
          set addr [scan $lineElem "@%x"]
          # If the BRAM file address pointer did not yet reach the addresses value
          # in the MEM file then fill the BRAM file with "0" until the BRAM file
          # address pointer is lined up.
          while {$addr_ptr < $addr} {
            set bramline [format "%032b" 0]
            puts $bram_fileptr $bramline
            incr addr_ptr
          }
        } else {
          # Each memfile line contains either 32x 1-byte or 8x 32-bit-words
          if {$first} {
            set first false
            if { $lineElements == 8 } {
              #puts "read 32 bit words"
			  set element_size 32
            } else {
              #puts "read bytes"
			  set element_size 8
            }
          }

          # if $element_size == 8  then we are reading bytes => read 4 bytes at a time
          # if $element_size == 32 then we are reading 32-bit words => read one word at a time
          # either read 4 bytes or 1 word
          # Scan an element from the MEM file
          if {($element_size == 8)} {
            # read bytes
			append bramline [format "%08b" [scan $lineElem "%x"]]
          } else {
            # read 32-bit-words
			set bin32 [format "%032b" [scan $lineElem "%x"]]
			
			# 32 bit endian swap necesairy?
			if {$swap} { 
			  append bramline [endian_swap $bin32]
			} else {
              append bramline $bin32
			}
          }
		  
          # If we are reading bytes and this is the last line element then...
          if { ($element_size == 8) && ($x == $lineElements - 1)} {
            # check whether all bytes were read from the BRAM line
            # i.e. the byte pointer points to the last (i.e. 3-rd) byte of the word
            if { $x % 4 != 3} {
              puts $bram_fileptr "Warning: word length and bytes per line ($lineElements) are inconsistent!"
              puts "Warning: word length and bytes per line ($lineElements) are inconsistent!"
            }
          }
          # when all bytes are scanned then write them to file
          if {(($element_size == 8) && ($x % 4 == 3)) || 
               ($element_size == 32)} {
            puts $bram_fileptr $bramline
			set bramline ""
            incr addr_ptr
          }
        }
      }      
   	}
  }
  # Append the loader file with zeros until the full dpram_size is filled
  while {$addr_ptr < $dpram_size} {
    set bramline [format "%032b" 0]
    puts $bram_fileptr $bramline
    incr addr_ptr
  }
  close $mem_fileptr
  close $bram_fileptr
} else {
    puts "WARNING $mem_file not found..."
}

###############################################################################

