これまでとぎれとぎれに進めていた、Pure Data→logue SDK変換ツール(hvccのexternal generator)のNTS-1 mkII対応ですが、夏休み中に見直しと整理を進めていました。
大きな進捗としては、前の記事で触れたオシレータに加えて、新たにエフェクトユニットが生成できるようになりました。
旧logueシリーズでは、エフェクトでは利用できるRAMが少なく、なんとかRAMが確保できるのはオシレータだけでした。NTS-1 mkIIでは、オシレータもエフェクタも使えるメモリ容量が若干増えました。
もちろん十分な容量ではないのですが、それに加えてNTS-1 mkIIのMPUであるSTM32H7では、FPUが倍精度対応になったため、コードサイズが若干削減できます。(hvccのコードは全般に浮動小数点演算を倍精度で行っています。)
実験中、生成されたバイナリのサイズが結構大きくて、内容を調べると特に
888 0 0 0 888 libgcc.a(_arm_addsubdf3.o)
596 0 0 0 596 libgcc.a(_arm_muldf3.o)
1060 0 0 0 1060 libgcc.a(_arm_muldivdf3.o)
という3つのオブジェクトが目立ちました。これらは倍精度型浮動小数点演算のソフトウェア実装ですが、STM32H7ではこれらはハードウェアで実行できる演算で、ソフトウェア実装は本来不要のはずです。
logue SDKで提供されているMakefileを見ると、gcc/g++のFPUに関する指定が-mfpu=fpv4-sp-d16となっていて、これだと単精度FPU用のコードを生成してしまいます。ここを -mfpu=fpv5-d16 に変更したところ、上記のオブジェクトはリンクされなくなり、バイナリサイズが大幅に小さくなりました。旧logueシリーズはSTM32F4で単精度のみ対応でしたので、そのときの設定がそのまま残っていたのかもしれません。
modユニットではバイナリサイズが16KBまでなので、これでもまだ、音量変更のようなごく簡単なエフェクタしか変換できません。音量変更のパッチはこんな感じです。

このパッチでも、Cに変換してビルドしてみると既にほぼ16KBです。なかなか厳しいですね。
text data bss dec hex filename
12606 648 2712 15966 3e5e changevol.elf
bssの2712バイトのうち2600バイトは、hvccで生成したコードが使用してるメモリなのですが、さらにその内訳(mallocのログ)を見ると、以下のようになっていました。
・256バイト ×1
・1024バイト ×2
・40バイト ×1
・16バイト ×16
256バイトは主役となるhvccのオブジェクトで、サイズは元のPure Dataパッチの内容に依存します。1024バイトの2つはそれぞれ「メッセージプール」と「インプットバッファ」のようです。
この1024バイトはバッファ用っぽいので、この領域をSDRAM側へ掃きだすことでもう少しメモリが稼げます。オシレータユニットはSDRAMが使えなかったのですが、エフェクタユニットでは、ディレイラインのような大きなメモリ領域をSDRAMからメモリを確保する、専用のAPIが用意されています。
ただ、SDRAMは初期化時に確保を済ませておく必要がありますし、独自調査では最大16個のブロックしか確保できないようです。
そのため、簡易mallocのコードを拡張して、ユニット初期化時に、必要となるSDRAM領域をまとめて確保した上で、閾値を超えたmallocはSDRAM、閾値未満のmallocはSRAM(staticな配列)からメモリを確保するように変更しました。
hvccでは、現状、メモリ確保は共通の関数1つだけ(hv_malloc)で行われており、通常のメモリの確保とSDRAMの確保を使い分けることができません。そのため、ややアドホックですが、確保するメモリブロックのサイズだけを頼りにSDRAMとSRAMに振り分けざるを得ませんでした。
閾値を512バイトとしてみると、上のパッチをビルドするとbssが2KB減ってこんな感じになりました。これなら音量変更よりもう少し複雑なことが出来そうです。
text data bss dec hex filename
12682 648 672 14002 36b2 changevol.elf
(2025/8/21追記:この1024バイトのmallocの内、1つ目はhvccが生成するmp_init()という関数から呼ばれているのですが、こちらをSDRAMから確保すると、パッチの中でdelwrite~などでディレイラインを使う場合にうまく動作しないようです。SDRAMではなくSRAMに確保すると問題なく動作しますし、ディレイラインを使わなければSDRAMに置いても問題ないので、謎な挙動です・・・。)
ところで、上記はsizeコマンドの出力ですが、ユニットが実際にRAM上にロードされたときのフットプリントは、ここで表示されている数字の合計より若干大きくなります。これにはアラインメントが影響しているようですが、実際のメモリ配置はreadelfコマンドを使って
$ readelf -lW myunit.elf
Elf file type is DYN (Shared object file)
Entry point 0x0
There are 5 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x00000034 0x00000034 0x000a0 0x000a0 R 0x4
LOAD 0x000000 0x00000000 0x00000000 0x02f2c 0x02f2c R E 0x80
LOAD 0x002f30 0x00002f30 0x00002f30 0x00330 0x00330 R 0x80
LOAD 0x003260 0x00003260 0x00003260 0x00288 0x00d34 RW 0x80
DYNAMIC 0x003260 0x00003260 0x00003260 0x00098 0x00098 RW 0x8
のように得られる、LOADのアドレス+メモリサイズから求められます(この例の場合、LOADの行が3つあり、最後の行が0x3260から0xd34バイト使用していますので、その終端の0x3260 + 0x0d34 = 0x3F94 = 16276がフットプリントになります)。
前述の通り、NTS-1 mkIIのユニットのフットプリントには16KB、24KB、48KBといった制約がありますが、SDKとしては特にサイズのチェックはしておらず、制約以上のサイズでもビルドはできてしまいます。ただ、それをKONTROL EDITORでNTS-1 mkIIに転送する段になって、サイズのエラーで弾かれてがっかりすることになります。
sizeコマンドと実際のフットプリントとの数十バイトの差分が成功・不成功の分かれ目になる場合もあるので、sizeコマンドに加えて上記のreadelfコマンドの出力(最終的なフットプリントの予測値)も得られるように、Makefileに機能を追加しました。新しいMakefileでは、ELFファイルのビルド後に
text data bss dec hex filename
12682 648 672 14002 36b2 changevol.elf
Memory footprint: 14228 bytes
というように出力され、制約値を超えていればWARNINGメッセージが出ます。
こんな感じで、ユニットのロード後のフットプリントをいかに下げるかが勝負になってきていますが、リングモジュレータ、コーラス、シンプルなディレイといったエフェクトがビルドできるようになっているので、大きな山は越えたかなと思っています。


コメント