Skip to content

Instantly share code, notes, and snippets.

@kimushu
Last active October 23, 2017 12:37
Show Gist options
  • Save kimushu/f9c4c31997e9cb001e4a82393f66d703 to your computer and use it in GitHub Desktop.
Save kimushu/f9c4c31997e9cb001e4a82393f66d703 to your computer and use it in GitHub Desktop.
Memory initialization data generator for Intel Quartus Prime

メモリ初期化ファイル生成用tclスクリプト

このスクリプトは、Intel(Altera) Nios II EDS 上で作成された elf ファイルから、 各種 RAM (ROM) の初期値ファイルを生成するツールです。

Intel純正で用意されている make mem_init_generate の機能ではhexしか生成できないため、mif対応のツールを作成しました。

最近のQuartusではhexだと「Update Memory Initialization Files」機能が正常に動きませんが、mifを使うことでその問題を回避できます。(根本対策ではありませんが…)

使い方 (Makefileに統合する場合)

  • qsfのあるQuartusプロジェクトフォルダから2階層下に、ソフトウェアが配置されているとします。

    プロジェクトフォルダ
    ├─ hoge.qsf
    └─ software/
        ├─ hoge_application/
        │   └─ Makefile
        └─ hoge_bsp/
    
  • generate_mem_init.tclをqsfのあるフォルダに置きます。

  • NiosIIアプリケーション(BSPの方ではありません)のMakefileを編集し、$(ELFPATCH)の次の行にこのスクリプトの呼び出しを加えます。(下記の差分を参照)

      $(ELF) : $(APP_OBJS) $(LINKER_SCRIPT) $(APP_LDDEPS)
          @$(ECHO) Info: Linking $@
          $(LD) $(APP_LDFLAGS) $(APP_CFLAGS) -o $@ ...
      ifneq ($(DISABLE_ELFPATCH),1)
          $(ELFPATCH) $@ $(ELF_PATCH_FLAG)
    +     tclsh $(dir $(SOPCINFO_FILE))/generate_mem_init.tcl $@ $(dir $(SOPCINFO_FILE))
      endif
  • アプリケーションをビルドします。

    • 下記の例のように、ビルド成功すると、自動的にhex/mifが生成されます。生成場所はQuartusプロジェクトのフォルダです。
      Info: Generating ../../olive_piccolo_core_ipl.mif
      Info: Generating ../../altera_onchip_flash.mif
      
  • Quartus上で Update Memory Initialization Files を実行します。

    • 生成されたhex/mifが取り込まれます。
  • Quartus上でAssemblerをStart againします。

    • 新しいメモリ初期値データでsof/pof等が生成されます。

使い方 (端末で使う場合)

  • -h オプションを付けて起動して下さい。使い方が表示されます。

仕組み

Nios II EDSでは、ビルドしたelfファイルにQsysのシステムに関する情報(cpu名や、sopcinfoの中身まるごと等)を結合するようになっています(Makefile内でELFPATCHと命名されているのがそれです)。このスクリプトでは、それらの情報を用いてメモリ初期化データを生成します。

  1. 指定されたelfのロードセグメントおよび.cpu/.sopcinfoセクションを読み込む
  2. .sopcinfoセクションにあるxmlをパースする
  3. メモリ初期化ファイルを要求するモジュールを絞り込む
  4. .cpuセクションを元にバスマスタを絞り込み、各メモリモジュールへのアドレスマッピングを特定する。
  5. 各メモリモジュールについて、ロードセグメントのデータを用いて初期値を求める
  6. 初期値データから、mifまたはhexを生成する (生成形式はファイル名の拡張子で自動判別)

ライセンス

MIT

#================================================================================
# Memory initialization file generator for Intel Quartus Prime by @kimu_shu
#
# This is an alternative to Intel's generator (mem_init_generate) with
# workarounds for bugs of "quartus_cdb --update_mif"...
#
# **** How to use ****
# 1. Place this script to your Quartus project folder
# 2. Edit your Makefile like below:
#
# $(ELF) : $(APP_OBJS) $(LINKER_SCRIPT) $(APP_LDDEPS)
# @$(ECHO) Info: Linking $@
# $(LD) $(APP_LDFLAGS) $(APP_CFLAGS) -o $@ ...
# ifneq ($(DISABLE_ELFPATCH),1)
# $(ELFPATCH) $@ $(ELF_PATCH_FLAG)
# + tclsh $(dir $(SOPCINFO_FILE))/generate_mem_init.tcl $@ $(dir $(SOPCINFO_FILE))
# endif
#
# 3. Every time you build your application, memory initialization files
# will be updated with the latest content :-)
#================================================================================
proc usage {} {
puts stderr "Usage: $argv0 \[<options>\] <elf_file> \[<output_directory>\]"
puts stderr ""
puts stderr "Options:"
puts stderr " -f, --fill <value> : Set filler byte (default: 0x00)"
puts stderr " -h, --help : Print this help"
puts stderr " -v, --verbose : Verbose mode"
}
proc ELF_read_file { path } {
set fd [ open $path r ]
fconfigure $fd -translation binary
set bin [ read $fd ]
close $fd
ELF_read $bin
}
proc ELF_read { bin } {
set EH_SIZE 0x34
set ELFMAG 0x464c457f
set EM_ALTERA_NIOS2 113
set SHT_STRTAB 3
set sopcinfo {}
set cpu {}
# Read file header
binary scan [ string range $bin 0 [ expr $EH_SIZE - 1 ] ] i4ssiiiiissssss \
idt typ mac ver ent pho sho flg ehs phs phn shs shn ssx
if { [ lindex $idt 0 ] != $ELFMAG || $mac != $EM_ALTERA_NIOS2 } {
puts stderr "Error: Not a valid ELF file for NiosII"
exit 1
}
array set ehdr [ list \
e_ident $idt \
e_type $typ \
e_machine $mac \
e_version $ver \
e_entry $ent \
e_phoff $pho \
e_shoff $sho \
e_flags $flg \
e_ehsize $ehs \
e_phentsize $phs \
e_phnum $phn \
e_shentsize $shs \
e_shnum $shn \
e_shstrndx $ssx \
]
# Read section header string table
set tmp [ expr $ehdr(e_shoff) + \
$ehdr(e_shentsize) * $ehdr(e_shstrndx) ]
binary scan \
[ string range $bin $tmp [ expr $tmp + $ehdr(e_shentsize) - 1 ] ] \
iiiiiiiiii \
nam typ flg adr ofs siz lnk inf aln esz
if { $typ == $SHT_STRTAB } {
set shstrtab [ string range $bin $ofs [ expr $tmp + $siz - 1 ] ]
} else {
set shstrtab {}
}
# Read section headers
set shlist [ list ]
for { set i 0 } { $i < $ehdr(e_shnum) } { incr i } {
set tmp [ expr $ehdr(e_shoff) + $ehdr(e_shentsize) * $i ]
binary scan \
[ string range $bin $tmp [ expr $tmp + $ehdr(e_shentsize) - 1 ] ] \
iiiiiiiiii \
nam typ flg adr ofs siz lnk inf aln esz
set npos [ string first "\0" $shstrtab $nam ]
if { $npos < 0 } { continue }
set str [ string range $shstrtab $nam [ expr $npos - 1 ] ]
lappend shlist [ list \
name $str \
sh_addr $adr \
sh_size $siz \
]
if { $str == ".sopcinfo" } {
set sopcinfo [ string range $bin $ofs [ expr $ofs + $siz - 1 ] ]
}
if { $str == ".cpu" } {
set cpu [ string range $bin $ofs [ expr $ofs + $siz - 1 ] ]
}
}
# Read program headers
set phlist [ list ]
lappend phlist [ array get ehdr ]
for { set i 0 } { $i < $ehdr(e_phnum) } { incr i } {
set tmp [ expr $ehdr(e_phoff) + $ehdr(e_phentsize) * $i ]
binary scan \
[ string range $bin $tmp [ expr $tmp + $ehdr(e_phentsize) - 1 ] ] \
iiiiiiii \
typ off vad pad fsz msz flg aln
# Search included sections
set sec {}
for { set j 0 } { $j < [ llength $shlist ] } { incr j } {
set tmp [ lindex $shlist $j ]
array set shdr $tmp
if { ($vad <= $shdr(sh_addr)) \
&& ($shdr(sh_addr) < ($vad + $msz)) } {
lappend sec $tmp
}
}
# Store data
lappend phlist [ list \
p_type $typ \
p_offset $off \
p_vaddr $vad \
p_paddr $pad \
p_filesz $fsz \
p_memsz $msz \
p_flags $flg \
p_align $aln \
content [ string range $bin $off [ expr $off + $fsz - 1 ] ] \
sections $sec \
discard 0
]
}
return [ list $phlist $sopcinfo $cpu ]
}
proc XML_load { path } {
set fd [ open $path r ]
fconfigure $fd -translation binary
set bin [ read $fd ]
close $fd
return [ XML_parse $bin ]
}
proc XML_parse { bin } {
set idx 0
return [ XML_parse_inner bin idx {} ]
}
proc XML_parse_inner { binname idxname parent } {
upvar $binname bin
upvar $idxname idx
set end [ string length $bin ]
set closed 0
set tags {}
set data {}
while { $idx < $end } {
set tname {}
set attrs {}
if { [ regexp -start $idx {\A\s+} $bin match ] } {
# Spaces => Skip
} elseif { [ regexp -start $idx {\A<\?[^>]+\?>} $bin match ] } {
# XML declaration => Skip
} elseif { [ regexp -start $idx {\A<!--.*?-->} $bin match ] } {
# Comments => Skip
} elseif { [ regexp -start $idx {\A<!\[CDATA\[.*?\]\]>} $bin match ] } {
# CDATA => Skip
} elseif { [ regexp -start $idx {\A</([^\s>]+)>} $bin match tname ] } {
# Close tag
if { $tname != $parent } {
puts stderr "Error: invalid close tag: $tname (expected $parent) "
exit 1
}
set closed 1
} elseif { [ regexp -start $idx {\A<([^\s/>]+)([^>]*)>} $bin match tname tattr ] } {
# Open tag
set aidx 0
set aend [ string length $tattr ]
while { $aidx < $aend } {
if { [ regexp -start $aidx {\A\s+} $tattr amatch ] } {
# Spaces => Skip
} elseif { [ regexp -start $aidx {\A([^=\s]+)=\"([^\"]*)\"\s*} $tattr amatch aname avalue ] } {
# Value with double-quote
lappend attrs $aname [ regsub -all {\\\"} $avalue \" ]
} elseif { [ regexp -start $aidx {\A([^=\s]+)=\'([^\']*)\'\s*} $tattr amatch aname avalue ] } {
# Value with single-quote
lappend attrs $aname [ regsub -all {\\\'} $avalue \' ]
} elseif { [ regexp -start $aidx {\A/$} $tattr amatch ] } {
set tname {}
break
} else {
puts stderr "Error: invalid attribute: ${tattr}"
exit 1
}
set aidx [ expr $aidx + [ string length $amatch ] ]
}
} elseif { [ regexp -start $idx {\A[^<]+} $bin match ] } {
# Data
set data $match
} else {
# Invalid syntax
puts stderr "Error: invalid XML: [ string range $bin $idx [ expr $idx + 99 ] ]"
exit 1
}
set idx [ expr $idx + [ string length $match ] ]
if { $closed == 1 } {
break;
}
if { $tname != "" } {
set tag [ XML_parse_inner bin idx $tname ]
lappend tag name $tname attrs $attrs
lappend tags $tag
}
}
if { $closed == 0 && $parent != "" } {
puts stderr "Error: tag <$parent> not closed"
exit 1
}
return [ list tags $tags data $data ]
}
proc XML_get_data { docVar } {
upvar $docVar docList
array set doc $docList
return $doc(data)
}
proc XML_get_attr { docVar name } {
upvar $docVar docList
array set doc $docList
array set attrs $doc(attrs)
return [ array get attrs $name ]
}
proc XML_list_tags { docVar name args } {
upvar $docVar docList
array set doc $docList
set argc [ llength $args ]
set tags {}
foreach tagList $doc(tags) {
array set tag $tagList
if { $tag(name) != $name } { continue }
if { $argc >= 1 } {
set attr [ XML_get_attr tagList [ lindex $args 0 ] ]
if { $attr == "" } { continue }
set value [ lindex $attr 1 ]
if { $argc >= 2 && $value != [ lindex $args 1 ] } { continue }
}
lappend tags $tagList
}
return $tags
}
proc generate_hex { outfile dataVar base span width baddr } {
upvar $dataVar data
proc hex_line { fd addr type data } {
binary scan $data c* data
set bytes [ concat \
[ llength $data ] \
[ expr $addr >> 8 ] [ expr $addr & 0xff ] \
[ expr $type ] \
$data \
]
set sum 0
foreach byte $bytes {
incr sum $byte
}
lappend bytes [ expr (-$sum) & 0xff ]
binary scan [ binary format c* $bytes ] H* line
puts $fd ":[ string toupper $line ]"
}
set seg {}
set step [ expr $width / 8 ]
set records 32
set fd [ open $outfile w ]
for { set ofs 0 } { $ofs < $span } { incr ofs $records } {
set chunk [ string range $data $ofs [ expr $ofs + $records - 1 ] ]
if { $baddr == 0 } {
set addr [ expr $ofs / $step ]
switch $width {
64 {
binary scan $chunk w* words
set chunk [ binary format W* $words ]
}
32 {
binary scan $chunk i* words
set chunk [ binary format I* $words ]
}
16 {
binary scan $chunk s* words
set chunk [ binary format S* $words ]
}
}
} else {
set addr $ofs
}
set nseg [ expr ($addr >> 4) & 0xf000 ]
if { $nseg != $seg } {
hex_line $fd 0 0x02 [ binary format S $nseg ]
set seg $nseg
}
hex_line $fd [ expr $addr & 0xffff ] 0x00 $chunk
}
hex_line $fd 0 0x01 ""
close $fd
}
proc generate_mif { outfile dataVar base span width baddr } {
upvar $dataVar data
set step [ expr $width / 8 ]
set fd [ open $outfile w ]
puts $fd "DEPTH = [ expr $span / $step ];"
puts $fd "WIDTH = $width;"
puts $fd "ADDRESS_RADIX = HEX;"
puts $fd "DATA_RADIX = HEX;"
puts $fd "CONTENT"
puts $fd "BEGIN"
for { set ofs 0 } { $ofs < $span } { incr ofs $step } {
set addr [ expr $ofs / $step ]
set chunk [ string range $data $ofs [ expr $ofs + $step - 1 ] ]
switch $width {
64 {
binary scan $chunk w word
}
32 {
binary scan $chunk i word
set word [ expr $word & 0xffffffff ]
}
16 {
binary scan $chunk s word
set word [ expr $word & 0xffff ]
}
8 {
binary scan $chunk c word
set word [ expr $word & 0xff ]
}
}
puts $fd [ format "%05X : %0[ expr $width / 4 ]X;" $addr $word ]
}
puts $fd "END;"
close $fd
}
proc main { argv0 argv } {
set verbose 0
set elf {}
set dir {}
set filler "\x00"
for { set argi 0 } { $argi < [ llength $argv ] } { incr argi } {
set arg [ lindex $argv $argi ]
switch -glob -- $arg {
-f -
--fill {
incr argi
set filler [ binary format c [ lindex $argv $argi ] ]
}
-h -
--help {
usage
exit 1
}
-v -
--verbose {
set verbose [ expr $verbose + 1 ]
}
-* {
puts stderr "Error: Unknown option: $arg"
exit 1
}
default {
if { $elf == "" } {
set elf $arg
} elseif { $dir == "" } {
set dir $arg
} else {
puts stderr "Error: Too many arguments: $arg"
exit 1
}
}
}
}
if { $elf == "" } {
puts stderr "Error: no input file"
exit 1
}
if { $dir == "" } {
set dir "./"
}
if { $verbose > 0 } {
puts stderr "Info: Reading ELF file ($elf)"
}
set temp [ ELF_read_file $elf ]
set phlist [ lindex $temp 0 ]
set sopcinfo [ lindex $temp 1 ]
set cpu_path [ lindex $temp 2 ]
unset temp
if { $verbose > 0 } {
puts stderr "Info: Parsing sopcinfo XML ([ string length $sopcinfo ] bytes)"
}
set xml [ XML_parse $sopcinfo ]
set root [ lindex [ XML_list_tags xml EnsembleReport ] 0 ]
set modules [ XML_list_tags root module ]
if { $verbose > 0 } {
puts stderr "Info: Found [ llength $modules ] modules"
}
set cpu [ lindex [ XML_list_tags root module path $cpu_path ] 0 ]
if { $verbose > 0 } {
set kind [ lindex [ XML_get_attr cpu kind ] 1 ]
puts stderr "Info: Found CPU ($kind @ $cpu_path)"
}
set masters [ XML_list_tags cpu interface kind avalon_master ]
if { $verbose > 0 } {
puts stderr "Info: Found [ llength $masters ] avalon masters from CPU"
}
foreach module $modules {
set temp [ lindex [ XML_list_tags module parameter name initMemContent ] 0 ]
if { $temp != "" } {
set temp [ lindex [ XML_list_tags temp value ] 0 ]
set temp [ XML_get_data temp ]
if { $temp == "false" } { continue }
}
set temp [ lindex [ XML_list_tags module parameter name initFlashContent ] 0 ]
if { $temp != "" } {
set temp [ lindex [ XML_list_tags temp value ] 0 ]
set temp [ XML_get_data temp ]
if { $temp == "false" } { continue }
}
set temp [ lindex [ XML_list_tags module parameter name initializationFileName ] 0 ]
if { $temp == "" } { continue }
set mod_path [ lindex [ XML_get_attr module path ] 1 ]
if { $verbose > 0 } {
set kind [ lindex [ XML_get_attr module kind ] 1 ]
puts stderr "Info: Found module with memory initialization ($kind @ $mod_path)"
}
set temp [ lindex [ XML_list_tags temp value ] 0 ]
set outfile $dir[ XML_get_data temp ]
set temp [ lindex [ XML_list_tags module parameter name MAX_UFM_VALID_ADDR ] 0 ]
if { $temp != "" } {
set temp [ lindex [ XML_list_tags temp value ] 0 ]
set temp [ XML_get_data temp ]
set max_span [ expr $temp + 1 ]
} else {
set max_span -1
}
set assignments [ XML_list_tags module assignment ]
set width {}
set baddr 0
foreach assignment $assignments {
set temp [ lindex [ XML_list_tags assignment name ] 0 ]
set aname [ XML_get_data temp ]
set temp [ lindex [ XML_list_tags assignment value ] 0 ]
set avalue [ XML_get_data temp ]
switch $aname {
"embeddedsw.memoryInfo.MEM_INIT_DATA_WIDTH" {
set width $avalue
}
"embeddedsw.memoryInfo.USE_BYTE_ADDRESSING_FOR_HEX" {
set baddr $avalue
}
}
}
if { $verbose > 0 } {
puts stderr "Info: Data width is $width"
if { $baddr == 1 } {
puts stderr "Info: Byte-addressing mode enabled"
}
}
set slaves [ XML_list_tags module interface kind avalon_slave ]
set slave_name {}
foreach slave $slaves {
set assignments [ XML_list_tags slave assignment ]
foreach assignment $assignments {
set temp [ lindex [ XML_list_tags assignment name ] 0 ]
set aname [ XML_get_data temp ]
set temp [ lindex [ XML_list_tags assignment value ] 0 ]
set avalue [ XML_get_data temp ]
switch $aname {
"embeddedsw.configuration.isMemoryDevice" {
if { $avalue == "1" } {
set slave_name [ lindex [ XML_get_attr slave name ] 1 ]
break
}
}
}
}
if { $slave_name != "" } { break }
}
set slave_path "$mod_path.$slave_name"
if { $verbose > 0 } {
puts stderr "Info: Looking up avalon masters connected to $slave_path"
}
set base {}
set span {}
foreach master $masters {
set master_path "$cpu_path.[ lindex [ XML_get_attr master name ] 1 ]"
set blocks [ XML_list_tags master memoryBlock ]
foreach block $blocks {
set temp [ lindex [ XML_list_tags block name ] 0 ]
set temp [ XML_get_data temp ]
if { $temp == "$slave_path" } {
set temp [ lindex [ XML_list_tags block baseAddress ] 0 ]
set base [ XML_get_data temp ]
set temp [ lindex [ XML_list_tags block span ] 0 ]
set span [ XML_get_data temp ]
break
}
}
if { $base != "" } { break }
}
if { ($max_span >= 0) && ($span > $max_span) } {
set span $max_span
}
if { $base == "" } {
puts stderr "Warning: No master connected to $slave_path"
puts stderr "Info: Skipping $outfile"
continue
}
if { $verbose > 0 } {
set temp [ format "%d bytes @ 0x%08x" $span $base ]
puts stderr "Info: Found connection between $slave_path and $master_path ($temp)"
}
set eaddr [ expr $base + $span ]
set data [ string repeat $filler $span ]
set init 0
set used 0
foreach ph [ lrange $phlist 1 end ] {
array set phdr $ph
set ofs [ expr $phdr(p_paddr) - $base ]
if { $ofs < 0 } { continue }
if { $ofs >= $span } { continue }
if { $phdr(p_filesz) > ($span - $ofs) } {
puts stderr "Error: Program exceeds memory size! ([
format "0x%08x-0x%08x" $phdr(p_paddr) [ expr $phdr(p_paddr) + $phdr(p_filesz) ]
])"
exit 1
}
set last [ expr $ofs + $phdr(p_filesz) - 1 ]
if { ($last + 1) > $used } {
set used [ expr $last + 1 ]
}
set data [ string replace $data $ofs $last $phdr(content) ]
set init 1
}
if { $init == 0 } {
puts stderr "Warning: No initialization data for $slave_path"
puts stderr "Info: Skipping $outfile"
continue
}
set ratio [ expr $used * 100 / $span ]
puts stderr "Info: Generating $outfile : $used bytes ($ratio%) used"
switch -glob -nocase $outfile {
*.hex {
generate_hex $outfile data $base $span $width $baddr
}
*.mif {
generate_mif $outfile data $base $span $width $baddr
}
default {
puts stderr "Error: Unknown format ($outfile)"
exit 1
}
}
}
}
main $::argv0 $::argv
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment