#!/usr/bin/env python

# Copyright 2017 ETH Zurich and University of Bologna.
# Copyright and related rights are licensed under the Solderpad Hardware
# License, Version 0.51 (the License); you may not use this file except in
# compliance with the License.  You may obtain a copy of the License at
# http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
# or agreed to in writing, software, hardware and materials distributed under
# this License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.

# ////////////////////////////////////////////////////////////////////////////////
# // Company:        Multitherman Laboratory @ DEIS - University of Bologna     //
# //                    Viale Risorgimento 2 40136                              //
# //                    Bologna - fax 0512093785 -                              //
# //                                                                            //
# // Engineer:       Davide Rossi - davide.rossi@unibo.it                       //
# //                                                                            //
# // Additional contributions by:                                               //
# //                 Andreas Traber - atraber@student.ethz.ch                   //
# //                                                                            //
# // Create Date:    05/04/2013                                                 //
# // Design Name:    ULPSoC                                                     //
# // Project Name:   ULPSoC                                                     //
# // Language:       tcl, now python                                            //
# //                                                                            //
# // Description:    s19 to slm conversion tool for stxp70 cluster compilation  //
# //                                                                            //
# // Revision:                                                                  //
# // Revision v0.1 - File Created                                               //
# // Revision v0.2 - Modification: Compiler does now generate little endian     //
# //                 directly. revert bytes!                                    //
# // Revision v0.3 - Moved from 128 bit s19 to 8 bit s19 file. This solves our  //
# //                 problems with misaligned addresses in s19 files.           //
# // Revision v0.4 - Added TCDM memory initialization                           //
# // Revision v0.5 - Rewrote the whole thing in python as tcl cannot handle     //
# //                 long file names properly
# ////////////////////////////////////////////////////////////////////////////////

import sys
import math


###############################################################################
# Function to dump single bytes of a string to a file
###############################################################################
def dump_bytes( filetoprint, addr, data_s):
    for i in xrange(0,4,1):
        filetoprint.write("@%08X %s\n" % ( addr+i,  data_s[i*2:(i+1)*2] ))

###############################################################################
# Read s19 file and put data bytes into a dictionary
###############################################################################
def s19_parse(filename, s19_dict):
    s19_file = open(filename, 'r')
    for line in s19_file:
        rec_field = line[:2]
        prefix    = line[:4]

        if rec_field == "S0" or prefix == "S009" or prefix == "S505" or prefix == "S705" or prefix == "S017" or prefix == "S804" or line == "":
            continue

        data = line[-6:-4] # extract data byte
        str_addr = line[4:-6]

        addr = int("0x%s" % str_addr, 0)


        s19_dict[addr] = data

    s19_file.close()

###############################################################################
# arrange bytes in words
###############################################################################
def bytes_to_words(byte_dict, word_dict):
    for addr in byte_dict:
        wordaddr = addr >> 2
        data = "00000000"

        if wordaddr in word_dict:
            data = word_dict[wordaddr]

        byte = addr % 4
        byte0 = data[0:2]
        byte1 = data[2:4]
        byte2 = data[4:6]
        byte3 = data[6:8]
        new   = byte_dict[addr]

        if byte == 0:
            data = "%s%s%s%s" % (byte0, byte1, byte2, new)
        elif byte == 1:
            data = "%s%s%s%s" % (byte0, byte1, new,   byte3)
        elif byte == 2:
            data = "%s%s%s%s" % (byte0, new,   byte2, byte3)
        elif byte == 3:
            data = "%s%s%s%s" % (new,   byte1, byte2, byte3)

        word_dict[wordaddr] = data

###############################################################################
# Start of file
###############################################################################
if(len(sys.argv) < 2):
    print "Usage s19toslm.py FILENAME"
    quit()


l2_banks     = 1
l2_bank_size = 8192 # in words (32 bit)
l2_start     = 0x00000000
l2_end       = l2_start + l2_banks * l2_bank_size * 4 - 1

tcdm_banks     = 1
tcdm_bank_size = 6144 # in words (32 bit)
tcdm_start     = 0x00100000
tcdm_end       = tcdm_start + tcdm_banks * tcdm_bank_size * 4 - 1
tcdm_bank_bits = int(math.log(tcdm_banks, 2))


###############################################################################
# Parse s19 file
###############################################################################
s19_dict = {}
slm_dict = {}

s19_parse(sys.argv[1], s19_dict)

bytes_to_words(s19_dict, slm_dict)


# word align all addresses
l2_start   = l2_start   >> 2
l2_end     = l2_end     >> 2
tcdm_start = tcdm_start >> 2
tcdm_end   = tcdm_end   >> 2
# actual size of L2 and TCDM (for flash)
l2_size     = 0
tcdm_size   = 0
l2_blocks   = 0
tcdm_blocks = 0
###############################################################################
# open files
###############################################################################
l2_cut_files = {}
for i in range(0, l2_banks):
    l2_cut_files[i] = {}
    l2_cut_files[i]['lo'] = open("l2_ram_cut%d_lo.slm" % i, 'w')
    l2_cut_files[i]['hi'] = open("l2_ram_cut%d_hi.slm" % i, 'w')

tcdm_files = {}
for i in range(0, tcdm_banks):
    tcdm_files[i] = open("tcdm_bank%d.slm" % i, 'w')


spi_stim = open("spi_stim.txt",   'w')
l2_stim  = open("l2_stim.slm",    'w')
flash    = open("flash_stim.slm", 'w')

###############################################################################
# write the stimuli
###############################################################################
for addr in sorted(slm_dict.keys()):
    data = slm_dict[addr]

    # l2 address range
    if(addr >= l2_start and addr <= l2_end):
        l2_base = addr - l2_start
        cut     = l2_base >> (int(math.log(l2_bank_size, 2) + 1))
        hilo    = 'hi' if (addr & 0x1) else 'lo'
        l2_addr = (l2_base >> 1) % l2_bank_size
        l2_size += 1

        if addr & 0x4:
            l2_cut_files[cut][hilo].write("@%08X %s\n" % (l2_addr, data))
        else:
            l2_cut_files[cut][hilo].write("@%08X %s\n" % (l2_addr, data))

        l2_stim.write("@%08X %s\n" % (l2_base, data))
        spi_stim.write("%08X_%s\n" % (addr << 2, data))


    # tcdm address range
    if(addr >= tcdm_start and addr <= tcdm_end):
        tcdm_addr = (addr - tcdm_start) >> tcdm_bank_bits
        bank      = addr % tcdm_banks
        tcdm_files[bank].write("@%08X %s\n" % (tcdm_addr, data))
        tcdm_size += 1

        spi_stim.write("%08X_%s\n" % (addr << 2, data))
###############################################################################
# write flash
###############################################################################

# 4KB blocks
l2_blocks   = (l2_size/1024+1)
tcdm_blocks = (tcdm_size/1024+1)
header_size = 8<<2

l2_off_s    = "%08X"%(((tcdm_size+8)/1024 + 1)*1024 <<2)
l2_start_s  = "%08X"%(l2_start << 2)
l2_size_s   = "%08X"%(l2_size << 2)
l2_blocks_s = "%08X"%(l2_blocks)

tcdm_off_s    = "%08X"%(header_size)
tcdm_start_s  = "%08X"%(tcdm_start << 2)
tcdm_size_s   = "%08X"%(tcdm_size << 2)
tcdm_blocks_s = "%08X"%(tcdm_blocks)


# write header
dump_bytes(flash, 0,  l2_off_s)
dump_bytes(flash, 4,  l2_start_s)
dump_bytes(flash, 8,  l2_size_s)
dump_bytes(flash, 12, l2_blocks_s)
dump_bytes(flash, 16, tcdm_off_s)
dump_bytes(flash, 20, tcdm_start_s)
dump_bytes(flash, 24, tcdm_size_s)
dump_bytes(flash, 28, tcdm_blocks_s)

# write data
for addr in sorted(slm_dict.keys()):
    data = slm_dict[addr]

    # l2 address range
    if(addr >= l2_start and addr <= l2_end):
        l2_base = (addr - l2_start)
        l2_addr = l2_base  + ((tcdm_size+8)/1024+1)*1024
        dump_bytes(flash, l2_addr * 4, data)

    # tcdm address range
    if(addr >= tcdm_start and addr <= tcdm_end):
        tcdm_addr = (addr - tcdm_start)

        dump_bytes(flash, tcdm_addr * 4 + header_size, data)

###############################################################################
# close all files
###############################################################################
for i in l2_cut_files:
    l2_cut_files[i]['hi'].close()
    l2_cut_files[i]['lo'].close()

for i in tcdm_files:
    tcdm_files[i].close()

spi_stim.close()
l2_stim.close()
flash.close()

###############################################################################
# write flash bin files for FPGA only
# 
# 0x400000_dmem.bin
#   header: dmem offset, addr, size, blocks
#   dmem  : in bytes
#
# 0x401000_imem.bin
#   imem  : in bytes
#
###############################################################################
# To generate .mcs in Vivado :
# 
# write_cfgmem  \
#  -format mcs \
#  -size 16 \
#  -interface SPIx4 \
#  -loadbit  "up 0x00000000 /path/to/arty_top.bit " \
#  -loaddata "up 0x00400000 /path/to/0x400000_dmem.bin  \
#             up 0x00401000 /path/to/0x401000_imem.bin " \
#  -force \
#  -file "/path/to/arty_top.mcs"
# 
###############################################################################
def dump_bytes_bin( filetoprint, addr, data_s):
    for i in xrange(0,4,1):
        #print data_s[i*2:(i+1)*2]
        filetoprint.write(chr(int(data_s[i*2:(i+1)*2],16)))

flash_start_addr = 0x400000
flash_l2_off_s   = "%08X"%(flash_start_addr + (((tcdm_size+8)/1024 + 1)*1024 <<2))
flash_tcdm_off_s = "%08X"%(flash_start_addr)

dmem_bin= open("0xFF8000_dmem.bin", 'wb')
imem_bin= open("0xFF0000_imem.bin", 'wb')

#dump_bytes_bin(dmem_bin, 0,  flash_l2_off_s)
#dump_bytes_bin(dmem_bin, 4,  l2_start_s)
#dump_bytes_bin(dmem_bin, 8,  l2_size_s)
#dump_bytes_bin(dmem_bin, 12, l2_blocks_s)
#dump_bytes_bin(dmem_bin, 16, flash_tcdm_off_s)
#dump_bytes_bin(dmem_bin, 20, tcdm_start_s)
#dump_bytes_bin(dmem_bin, 24, tcdm_size_s)
#dump_bytes_bin(dmem_bin, 28, tcdm_blocks_s)

for addr in sorted(slm_dict.keys()):
    data = slm_dict[addr]

    # l2 address range
    if(addr >= l2_start and addr <= l2_end):
        l2_base = (addr - l2_start)
        l2_addr = l2_base  + ((tcdm_size+8)/1024+1)*1024
        dump_bytes_bin(imem_bin, l2_addr * 4, data)

    # tcdm address range
    if(addr >= tcdm_start and addr <= tcdm_end):
        tcdm_addr = (addr - tcdm_start)
        dump_bytes_bin(dmem_bin, tcdm_addr * 4, data)

dmem_bin.close()
imem_bin.close()
