前回はunicode→S-JIS変換を、CPUパワーとメモリの限られた組み込み用途で実行するための方法について検討しました。今回は、その回路(ハードウェア)を設計して行きます。
ハードウェアを作るので、FPGAボード(Sipeed Lichee Tang Primer)と、その上で動作するオリジナル16ビットCPUコアMoscoviumを使います。
unicode⇔S-JIS変換UNISJIユニットの詳細設計を進めて行きます。以下図にブロック図を示して、各サブブロックの機能設計を実施します。
※黄色に着色した箱は、CPUからアクセス可能なI/Oレジスタです。
ブロック機能設計:
以下にI/Oレジスタ表を示して、その後に解説を続けます。
Addr | Name | R/W | Bit:15〜0 | Memo | |||||||||||||||
0xf100 | unsjctl | R | − | − | − | − | − | − | − | − |
UN SJ |
− | − | − | − | − |
SJ IS |
BS Y |
conversion control |
0xf102 | unsjunic | RW | UNIC[15:0] | unicode register | |||||||||||||||
0xf104 | unsjsjis | RW | SJIS[15:0] | s-jis code register | |||||||||||||||
0xf106 | unsjkutn | R | − | KU[6:0] | − | TEN[6:0] | ku-ten code register | ||||||||||||
0xf108 | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | reserved |
0xf10a | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | reserved |
0xf10c | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | reserved |
0xf10e | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | reserved |
BUSC出力のセレクト信号はbcs_unsj_nです。上表の範囲でアサートされるので、UNISJIユニット内では、アドレスbadr[3:0]を復号してレジスタを特定しています。このI/Oユニットはバイトアクセス禁止です。必ずワードアクセスしてください。
レジスタ解説:
S-JISコードは、7ビットASCIIコードと8ビット半角カタカナコードの空き領域を使って符号化した2バイトコードなので、各バイトデータには決まったコード範囲があります。S-JISコードの第一バイトを上位バイト、第二バイトを下位バイトとするunsjsjis[15:0]レジスタを使うと、S-JISコード範囲は以下の様になっています。
// usjs_is_sjis=0 (not an S-JIS code)/1 (is an S-JIS code)
wire usjs_is_sjis_1=(
(8'h81<=unsjsjis[15:8] && unsjsjis[15:8]<=8'h9f) ||
(8'he0<=unsjsjis[15:8] && unsjsjis[15:8]<=8'hea) );
wire usjs_is_sjis_2=(
(8'h40<=unsjsjis[7:0] && unsjsjis[7:0]<=8'h7e) ||
(8'h80<=unsjsjis[7:0] && unsjsjis[7:0]<=8'hfc) );
assign usjs_is_sjis=( usjs_is_sjis_1 && usjs_is_sjis_2 );
上のコードは、Verilog-HDLによる実際の記述です。書き込んだS-JISコードがこの範囲から逸脱していた場合、unsjctlレジスタのSJISフラグ=0となって、エラー状態を示します。
unicode→S-JIS変換時の変換時間⌚について議論しておきます。
今回のUNISJIユニットでは、1サイクルで2ライン分のデータを同時に読み出して、コード検索を実行しています。変換テーブルは全部で1,344ラインあるので、もし、コードが見つからなかった場合、最大で1,344ライン÷2=672サイクルの検索時間がかかります。
これがほぼそのまま、変換時間となります。24MHzクロックでは、28μsに相当します。← 1文字の変換に要する最大変換時間です。
通常は探している文字が、どこか途中のラインで見つかるので、見つかった時点で変換は終了します。もし、最初のラインで見つかった場合の最小変換時間は1サイクルとなります。
全ての文字が同じ確率で発生すると仮定した場合、平均変換時間=(最大変換時間+最小変換時間)÷2≒337サイクルとなります。約14μs @24MHzに相当します。
ですが、日本語である以上、全ての文字は同じ確率では登場しません!
漢字と平仮名(カタカナ含む)の構成比率は、漢字:平仮名=2:8 〜 3:7と言われているので、テキスト全体の中で70〜80[%]は平仮名(カタカナ含む)のはずです。
こちらの区点コードを上から順に(1区→84区)、検索して行きます。
平仮名(4区)やカタカナ(5区)は上の方に登場するので、70〜80[%]の文字は比較的高速に検索が終了します。
それをすり抜けた「漢字」の内、大半の良く使う漢字は、第一水準(16〜47区)で見つかります。
更にそれをすり抜けた使用頻度の低い第二水準漢字だけが、最後の84区まで検索される可能性があります。
あるいは、S-JISに存在しないunicode文字を指定した場合、最後の84区まで検索してみて初めて、「見つからない」と判ることになります。
実際の日本語テキストで検証してみないと統計的な平均変換時間は不明なので、ここでは言及できません。が、一般論として、ランダムに出力したunicodeでない限り、日本語テキストに存在するunicode文字を変換する場合は、最大変換時間=672サイクル/1文字(28μs @24MHz)や、等確率平均変換時間=337サイクル/1文字(14μs @24MHz)より、ずっと高速に変換を終了できるのです。
例えば、平仮名(カタカナ含む)70%、第一水準漢字25%、第二水準漢字4%、範囲外1%の計100%を想定した平均変換時間(1文字当たり)は、以下の通りです。
平均変換時間=(
(4+5)区÷2×0.70+
(16+47)区÷2×0.25+
(48+84)区÷2×0.04+
84区×0.01)÷84区×1,344サイクル÷2
≒120サイクル≒4.8μs @24MHz
※先頭アドレスからのシーケンシャル検索を正当化する言い訳に聞こえましたか\(1^−^;)/??
こうして設計したunicode⇔S-JIS変換UNISJIユニットですが、専用ハードウェアが必要なので、他のArduino UNOやDUE等では恩恵にあずかることができません。
そこで、MoscoviumのI2Cユニット(STWSERユニット)を使い、UNISJIユニットを搭載したMoscovium側がI2Cスレーブモジュールとなる、unicode⇔S-JIS変換モジュールを作りたいと思います。
Arduino UNOやDUE等とI2Cで接続すると、UNOやDUE等からもUNISJIユニットを使ったunicode⇔S-JIS変換機能を使用することができるようになります。以下をunicode⇔S-JIS変換モジュールの目標仕様とします。
目標仕様:
addr | name | memo |
0x00 | U2SJS[15:0] |
16ビットunicodeを書き込むと、unicode→S-JIS変換がスタートします。 変換が終了するまで、本レジスタから0x0000が読み出せるので、 I2Cマスタは非ゼロ値が読み出せるまでポーリングしてください。 読み出した非ゼロ値が、対応するS-JISコードです。 エラー発生時は0xffffが読み出されます。 7ビットASCIIコードが書き込まれた場合、同じコードが読み出せます。 変換動作中は、本レジスタへの書き込みは無効です。 |
0x01 | SJS2U[15:0] |
S-JISコードを書き込むと、S-JIS→unicode変換がスタートします。 変換が終了するまで、本レジスタから0x0000が読み出せるので、 I2Cマスタは非ゼロ値が読み出せるまでポーリングしてください。 読み出した非ゼロ値が、対応するunicodeです。 エラー発生時は0xffffが読み出されます。 7ビットASCIIコードが書き込まれた場合、同じコードが読み出せます。 8ビット半角カタカナコードを書き込んだ場合も、正しく変換した値が 読み出せます。 変換動作中は、本レジスタへの書き込みは無効です。 |
0x02 | FONT0[15:0] |
SJS2Uレジスタ(0x01)に書き込んだS-JISコードに対応する フォントデータを読み出すことができます。 フォントはFONTJPユニットの8×8[ドット]日本語フォントです。 データサイズは8バイトで、レジスタ×4個に分割して読み出します。 本レジスタへの書き込みは無効です。 |
0x03 | FONT1[15:0] | |
0x04 | FONT2[15:0] | |
0x05 | FONT3[15:0] | |
0x06 | FCTRL[15:0] |
フォントデータを読み出す時のアフィン変換を指定します。 0: 通常フォントデータ読み出し。 +16: 0/1を反転。 +8: 横方向を反転。 +4: 縦方向を反転。 +1〜3: 時計回りにn×90°回転(nは加算する数値1〜3)。 上記載の順番でアフィン変換がかかります。詳細はこちらです。 本レジスタからの読み出しは無効です。 |
0x07 | FDOBL[15:0] |
下位バイトFDOBL[7:0]に書き込んだフォントデータ(8ドット分)を、 倍角変換(16ドット、FDOBL[15:0])したデータを読み出すことができます。 |
0x08 | IDCOD[15:0] | IDコード0x7573 (ASCIIコードで「us」) |
レジスタは全て16ビット幅で、バイトアクセスはできません。プロトコルはSMBusワードリード/ライトのみです。
I/FがI2C(400kHz)になった時点で、unicode→S-JIS変換サイクル数は、ほとんど見えないはずですが、変換結果が出力されるまで(非ゼロ値が読み出せるまで)、ポーリングしてください。
フォントデータ(FONT0〜3)の読み出し値とフォントの関係は、以下図の様になります。
レジスタ |
横→ ↓縦 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
読み出し データ例 |
FONT0[15:8] | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0x20 |
FONT0[7:0] | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0x7c |
FONT1[15:8] | 2 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0x20 |
FONT1[7:0] | 3 | 0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0x7c |
FONT2[15:8] | 4 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0xaa |
FONT2[7:0] | 5 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0xb2 |
FONT3[15:8] | 6 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0x64 |
FONT3[7:0] | 7 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0x00 |
FCTRLレジスタを使ってアフィン変換を指定したり、FDOBLレジスタを使って倍角変換したりすると、様々なグラフィックTFT/LCDディスプレイに対応できるはずです。
こうしてMoscoviumでモジュールを作っておけば、Arduino UNOやDUE等からI2Cでアクセスして、unicode⇔S-JIS変換が可能になります。ついでに日本語フォントも使うことができます。メモリの少ないUNO等でも、unicodeや日本語テキストを取り扱える可能性を提供することができます。
MoscoviumでHPデータをハンドリングするプログラムを組むこともできますが、Moscoviumだとアセンブラでのプログラミングになってしまうので、色々と面倒でしょう?
目標仕様に従い、Moscoviumで動作させるプログラムを作成しました。以下にリンクしておきます。
完成プログラム:code_conv.asm
アセンブルに必要なインクルードファイルなどは、こちらからダウンロードしてください。STWSERユニット用のWireライブラリ(スレーブ)を使っています。
※デバッグ用にシリアルモニタは活きていますが、シリアルモニタ無しで自立できるプログラムになっています。
前節で設計したアプリケーションでは、新規設計したUNISJIユニットだけでなく、日本語フォントFONTJPユニットやSTWSERユニット(I2C)が同時に搭載されたマイコンが必要となります。
必要な機能を備えたマイコンを、MCOC115US0816として作っておきました。以下リンクより、デザインをダウンロードできます。
FPGAボード(Sipeed Lichee Tang Primer)への書き込み手順はこちらを参照してください。
コンパイル(論理合成・配置配線)する時、S-JIS→unicode変換テーブルファイル「unsj_rom.mif」が必要となりますので、こちらの手順に従って生成してください。
以下に、TD IDEでのコンパイル(論理合成・配置配線)結果をまとめておきます。
実は、日本語フォントFONTJPユニットと共存させるため、unicode⇔S-JIS変換UNISJIユニットの方には、32KビットROMマクロを使いました。9KビットROMマクロを使ってしまうと、FONTJPの使用数と合わせて、64個(最大数)を超えてしまうためです。Moscoviumのメインメモリ用に使っている32KビットROMマクロが圧迫されるのですが、止む無しです。
今回のデザイン、論理規模(LUT数)は大したことありませんが、メモリマクロの方は結構派手に使っています。
Arduino UNOを使って、unicode⇔S-JIS変換モジュールのテストをしてみたいと思います。
まずは、UNO側で動作させるプログラムを作ります。今回は、何らかの応用プログラムと言うより、汎用のSMBus通信プログラムを作って、直接unicode⇔S-JIS変換モジュールのI/Oレジスタにアクセスしてみようと思います。
目標仕様:
目標仕様に従って、Arduino標準のWireライブラリを使って作成したプログラムを以下にリンクしておきます。
完成プログラム:smbus_master.ino
コンパイルしてArduinoへ書き込むと、シリアルモニタから以下のコマンドを受け付けるようになっています。
シリアルモニタコマンド仕様:
汎用のSMBusアクセスプログラムなので、今回の用途の他、SMBusに準拠したI2C接続のモジュールがあれば、アクセスして動作を確認することができます。でも、意外とSMBusに準拠したモジュールって、見ないんですよね。似てる物はいくつか見かけるのですが……。
Arduino UNOとFPGAボードを以下表の様に接続します。UNO(5V定格)を使うので、レベルシフタ(L/S)を通過して、FPGAボード(3.3V定格)と接続してください。
Arduino UNO | 接続 | レベルシフタ | 接続 | FPGAボード |
5V | → | HV | ||
A5(SCL) | ⇔ | HV1 | ||
A4(SDA) | ⇔ | HV2 | ||
GND | ⇔ | GND | ⇔ | GND |
LV | ← | 3.3V | ||
LV1 | ⇔ | stws_scl | ||
LV2 | ⇔ | stws_sda |
※DUE等3.3V定格のマイコンボードを使う場合には、L/Sを通さずに直結してください。
UNOには、前節で作ったプログラム(smbus_master.ino)をコンパイルして書き込んでおいてください。
FPGAボードには、オリジナル16ビットCPUコアMoscoviumマイコンMCOC115US0816とプログラム(code_conv.asm)を書き込んでください。FPGAボード(Sipeed Lichee Tang Primer)への書き込み手順はこちらを参照してください。
以下、実験中の様子です。
実験サンプル:【 】内はUNOシリアルモニタからの送信データ
【scan】
OK+ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f
00: 08 -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
OK: scan i2c slave address
まずは、【scan】コマンドを使って、I2Cに接続されたスレーブアドレスをスキャンしました。
スレーブアドレス0x08が、今回制作したunicode⇔S-JIS変換モジュールです。アドレスを変更したい場合には、プログラム(code_conv.asm)の定数「equ i2c_slv_adr,0x08」を変更してアセンブルしてください。
スレーブアドレス0x48は、FPGAボード上に載っているタッチスクリーンコントローラNS2009が見えています。今回は使わないので、無視してください。
【slv,08】
OK: i2c_slv_adr=0x08
【clk,400】
OK: f_scl=400[kHz]
SMBusでのアクセス対象をスレーブアドレス0x08(unicode⇔S-JIS変換モジュール)に指定しました。また、I2C通信周波数を400kHzに設定しました。
【srd2,08】
OK: srd2.08[0x08]=0x7573
unicode⇔S-JIS変換モジュールのIDCODレジスタ(0x08)を読み出して、ID=0x7573(期待通り)が確認できました。
【swr2,00,7199】
OK: swr2.08[0x00]=0x7199
【srd2,00,eaa4】
OK: srd2.08[0x00]=0xeaa4
unicode⇔S-JIS変換モジュールのU2SJSレジスタ(0x00)にunicode=0x7199 (熙)を書き込み、変換結果S-JIS=0xeaa4 (熙)を確認しました。読み出し【srd2】コマンドで期待値(eaa4)を指定したので、実際に読み出したデータが0xeaa4ではなかった場合、「OK:」の代わりに「ERR:」で応答が返ります。
【swr2,01,889f】
OK: swr2.08[0x01]=0x889f
【srd2,01,4e9c】
OK: srd2.08[0x01]=0x4e9c
unicode⇔S-JIS変換モジュールのSJS2Uレジスタ(0x01)にS-JIS=0x889f (亜)を書き込み、変換結果unicode=0x4e9c(亜)を確認しました。
その他、以下の様な特徴的なコードで双方向変換できていることを確認しました。
unicode⇔S-JIS変換期待値例 | ||||
S-JIS | ⇔ | unicode | 文字 | 備考 |
0x0058 | ⇔ | 0x0058 | X | 7ビットASCIIコード |
0x00b6 | 0xff76 | カ | 8ビット半角カタカナ | |
0x8145 | 0x30fb | ・ | 記号 | |
0x8254 | 0xff15 | 5 | 全角数字 | |
0x8270 | 0xff31 | Q | 全角アルファベット | |
0x82de | 0x3080 | む | 全角ひらがな | |
0x8355 | 0x30b6 | ザ | 全角カタカナ | |
0x9872 | 0x8155 | 腕 |
第一水準最後の漢字 最初の漢字は「亜」(テスト済み) |
|
0x989f | 0x5f0c | 弌 |
第二水準最初の漢字 最後の漢字は「熙」(テスト済み) |
詳細は割愛しますが、シミュレーションでもランダムに数100パタンの変換結果を確認してあります。
※高々7,000文字前後の変換テーブルなので、全数テストする方が確実なのですが、当方では実施していませんので、各自、検証してお使いください。
今回制作したモジュールは、I2C接続で8×8[ドット]日本語フォントの読み出しにも対応しているので、色々なアプリケーションに応用が利くはずです。