i8031のプログラム領域とデータ領域はアーキテクチャ的に分離していて、さらに今回の基板では物理的にRAMも別にしたので普通であればプログラムコードはデータとして扱えません。それを唯一可能にするのがmovc命令なんですが、コンパイラが吐いたアセンブラコードを見ると一見してmovcが見当たりません。それではどうやってアクセスしているのかと。
まずmain()の中でlcdstr()関数を呼んでいる部分です
コードに埋め込まれた"*** TEST ***"という文字列のアドレス#___str_0をdptrレジスタにセットしてlcdstrを呼んでいるのはいいですが、ここでミソなのは同時にbレジスタに0x80をセットしてから呼んでいるところです。
次にlcdstr()は簡単な処理なのに長大なコードに展開されています。これで半分くらいです。
やっと中ほどに出てくる_gptrgetルーチンがポインタ変数から値を取り出す(*str)処理の実体です。(get value for a generic pointer)
この処理はビルド時にリンクされるライブラリに含まれるようでソースコードも公開されていました。要はbレジスタにセットされたフラグを見てポインタの指す先がプログラム領域かデータ領域を判断しそれによりmovcかmovxを使い分けるようです。フラグが0x80ならプログラム領域であるということでここでやっとmovcが出てきました。
LCD自体がもともとアクセスが低速だし、目で追える速度で更新できれば十分なのでいいですがそれにしても迂遠なコードです。アセンブラのベタ手書きだったら数行で済みそうです