製作物

CPLDで三進↔二進変換回路を作る

投稿日:

CPLDとはプログラマブルロジックと呼ばれるデバイスの1つで、中身を書き換えることによってちょっとした組み合わせ回路や順序回路を作ることができます。立ち位置としてはGALよりもリッチで、FPGAよりもショボいという感じです。

今回はマルツにて在庫処分セールで1個72円で売っていたATF16V8BQL-15PUを用いて、自作三進CPUで使用する三進↔二進変換回路を作成してみました。

CPLDというデバイスの制約や、開発ツールの不具合に悩まされましたが、色々と新しい知見を得ることができたので記事にまとめたいと思います。

なお、この記事に関する開発物はこちらのGitHubリポジトリに置いてあるので良かったら参考にしてください。

作りたいもの

自作三進CPUではレジスタ等はちゃんと5 V, 0 V, -5 Vの3状態を使って保持していますが、メモリに関してはどうしても二進のものを使用することになります。

そのため三進数を二進数に変換する必要があり、現状としては下の表のように1 trit(三進1桁のこと)を二進2 bitに変換することによって5 tritのアドレスやデータを10 bitに変換しています。これをBinary Coded Ternary (BCT)と呼ぶことにします。

三進二進
110
000
-101
三進↔BCT変換表

3^5 = 243 < 2^8なので、本当は8 bitに収まるのですが、BCTでは10 bitとなります。

現状は10 bitのBCTをメモリ代わりのマイコンで読み書きしていますが、将来的にマイコンを排除してメモリICを使うためには、冗長性を排除して8 bitに収める必要があります。

そのため、作るべきものは「10入力8出力の組み合わせ回路、ただし入力は2^10 = 1024通りのうち243通りしか登場しない」となります。また、逆変換の組み合わせ回路も作りたいです。

この変換はかなり複雑な真理値表になるので、どう実現するかに悩んでいました。最初に思いつくのはFPGAですが、安価なTang Nano 9Kでも数千円するので複数個使うことを思うと気が引けます。そんな折、このCPLDを見つけて安く作れるのではと考えました。

CPLDの仕組み

まずはCPLDに何ができるのかを知るために、内部回路の仕組みを調査しました。こちら(https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-0364-PLD-ATF16V8B-8BQ-8BQL-Datasheet.pdf) のデータシートから図等を引用します。

このCPLDにはRegister, Complex, Simpleの3つの動作モードがあり、フリップフロップを使う場合はRegisterモード、フリップフロップは使わないが出力イネーブルピンを使う場合はComplexモード、出力イネーブルピンも使用しない場合はSimpleモードを使用します。

今回は単純な組み合わせ回路なので、Simpleモードを使用します。

ヒューズアレイ

CPLDがプログラマブルな動作を実現する仕組みは下図(一部分を示しています)のヒューズアレイです。

CPLDの内部回路図

まず縦に伸びるInput Linesが32本あり、その内訳は、外部入力ピン8本、その反転8本、Output Logicのフィードバック等8本、その反転8本となっています。

横の線に繋がるANDゲートは図では1入力に見えますが、これは省略されていて、実際には32入力でそれぞれのInput Linesに対応しています。

縦と横の線が交差するところにはヒューズ(実際にはリセット可能な電気的なスイッチ)があり、ANDゲートにどの信号を入力するかを選択することができます。

なおRegisterモードやComplexモードでも基本は同じですが、使用できるANDゲートの数やフィードバック信号の出処等が少し違います。

出力ブロック

SimpleモードにおけるOutput Logicの中身は以下のようになっています。

CPLDの出力ブロックの内部回路図

8個のANDゲートの値がORゲートに入力されます。これによって、論理式を積和標準形に対応する形で表現できます。

図中のXORは選択的に出力を反転するためのもので、1を作るための論理式より0を作る論理式が単純な場合等に使われます。

出力を有効化するかは(プログラム時に)選択可能で、出力しない場合はそのピンを入力として利用可能です。

なおRegisterモードではその名の通りここにフリップフロップがあったりします。

開発環境

CPLDの開発にはCUPLという言語を使用します。CUPLのコンパイルやシミュレーションにはWinCUPL IIというツールを使用できます。

コンパイル結果をICに書き込むにはプログラマーが必要ですが、汎用的なプログラマーは結構値が張るので、Afterbunerというプロジェクトを参考に自作しました。2000円弱で作れたと思います。

とりあえず作ってみる

Simpleモードでは入力専用ピンが10本、出力ブロックが繋がる入出力ピンが8本あります。回路規模的に難しそうではありますが、ピン数的には10入力8出力の真理値表が作れるのでダメ元で試しに書いてみることにします。

WinCUPL IIを起動しプロジェクトを新規作成して、Deveice FamilyにATF16V8BQL、Package Typeに20-DIP、OptionにSimpleを指定します。

WinCUPLのプロジェクト新規作成画面

プロジェクトが開かれると、下図のようにGUIでデザインフローが示されていて、Design File ManagerのNEW/EDITを押すと、CUPLを書くpldファイルが開かれます。

ただこの内蔵エディタは使いづらいので、作成されたpldファイルをvscode等のエディタで編集した方が使いやすいと思います。

WinCUPLのプロジェクトを開いた画面

pldファイルに以下のようにコードを書きます。一部省略しているので、全体を見たい場合は こちら からどうぞ。

CUPLではANDやORに対応する演算を直接書くほかに、このようにTABLEを用いて真理値表を直接書くこともできます。論理式への変換と最適化はコンパイラがやってくれます。

なおこのテーブルは こちら のPythonスクリプトで生成しています。

WinCUPL IIでDesign CompilationのCOMPILEを押すとコンパイルが実行されますが、excessive number of product termsというメッセージとともに失敗します。

product termというのは複数入力をAND演算した項のことで、ORに入力される値となります。先ほど見た内部回路としてはこれが8個以下に収まる必要があるのですが、それを超えてしまっているということです。

WinCUPL IIのCompiler OptionsのOutput Filesで下図のようにEquationsにチェックを入れると、どのような積和演算に変換されたかを確認することができます。

WinCUPL IIのcompiler optionsの画面でEquationsの欄にチェックが入っている

出力されたdocファイルを確認すると、次のようになっていました。

Y0 =>
    !A0 & !A1 & !A2 & A3 & !A4 & A5 & !A6 & A7 & !A8 & A9
  # !A0 & !A1 & A2 & !A3 & A4 & !A5 & A6 & !A7 & A8 & !A9
  # A0 & !A1 & !A2 & !A3 & A4 & !A5 & A6 & !A7 & A8 & !A9
  # !A0 & A1 & !A2 & !A3 & A4 & !A5 & A6 & !A7 & A8 & !A9
  ...

1行が1つのproduct termに対応していて、最も項数の多いY0では121項にもなっていました。

Compiler OptionsのMinimizationのアルゴリズムを変えることで多少小さくはなりましたが、8項に収めるのは到底無理なので別の戦略を考える必要があります。

困難は分割せよ

項数が膨れ上がってしまうのは、回路の複雑性が入力信号数に対して指数的に増えていくことが原因です。そこで、変換を2段階に分割することにします。

まずは3 tritに対応するBCT 6 bit (B5..0)を0 ~ 26の5 bit (X4..0)に変換することを考え、以下のコードを作成しました。

これのコンパイル結果はまたしてもexcessive number of product termsとなりましたが、X0が13項、X1が9項でX2, X3, X4は8項以下に収まっています。

CPLDが持つ8個の出力ブロックのうち5個しか使っていないので、余っているブロックとフィードバックを使えばどうにかなりそうです。

ということで以下のコードを作成しました。

Z1, Z0が中間値です。X0の13項のうち8項をZ0にまとめてからX0の入力にフィードバックすることによって、どの出力ブロックも8項以下に抑えることができています。

注意点として、Z1やZ0をピンに割り当てないと、単なる内部変数として論理式をまとめられてしまうので明示する必要があります。また、Simpleモードではフィードバックに使えるピンと使えないピンがあるのでそこも確認する必要があります。

シミュレーション

WinCUPL IIではシミュレーションも実行できるので、コンパイル成功後はシミュレーションできちんと期待する動作になっているかを確認します。

後述しますが、コンパイルは成功してるのに結果がおかしいことがあったりするのでシミュレーションは絶対やった方がいいと思います。(これも後述しますが、コンパイル結果が間違ってるのにシミュレーションでは正しく動くこともあるんですけどね。クソが

組み合わせ回路のシミュレーションでは、以下のように入力信号を0/1、期待する出力をH/Lで並べます。

このコードをsiファイルに書き、WinCUPL IIでDesign SimulationのSIMULATEを押すとシミュレーションが実行されます。失敗した場合にはどのように違ったかがsoファイルに書き込まれます。

分割その後

3 tritを5 bitに変換することができました。残りは2 tritですが、3^2 = 9なので、二進数で表すには4桁必要です。先ほどの結果と合わせると5 + 4 = 9 bitとなり8 bitに収めることはできません。困りましたね。

しかしここで諦めるわけにはいきません。うまく8 bitに収める方法を考えます。

0 ~ 26の5 bitをX4..0、0 ~ 8の4 bitをA3..0とします。以下に取りうる値のビットパターンを示します。

特にA3が顕著ですが、A3はほとんど0なのに他の桁と同じ1 bitの大きさを持っていて、これは無駄がありそうです。

NA3..0
00 0 0 0
10 0 0 1
70 1 1 1
81 0 0 0
NX4..0
00 0 0 0 0
150 1 1 1 1
161 0 0 0 0
231 0 1 1 1
241 1 0 0 0
251 1 0 0 1
261 1 0 1 0

情報の符号化には様々な方法がありますが、効率的な符号化の鉄則は、よく出るパターンには短い符号を割り当てることです。これに従って、次のような符号化を考えてみましょう。

Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 条件
A2..0 0 X3..0 A3 = 0, X4 = 0
A2..0 1 0 X2..0 A3 = 0, X4..3 = 10
A2..0 1 1 0 X1..0 A3 = 0, X4..2 = 110
X4..2 1 1 1 X1..0 A3 = 1

Y4..2の部分に可変長の符号を割り当てています。この符号によって条件を表し、それ以外の部分にAやXの残りの値を入れることで、きれいに8 bitに収めることができました。

できるはずだという直感に従ってやってみたらできた、という感じです。

注目すべき点として、Y1, Y0にはどの条件においてもX1, X0が入るので、この部分は計算する必要がありません。

したがって2段目の変換では残りのBCT 4 bitと、1段目の変換結果のうちのX4..2の3 bitを入力として、Y7..2を出力する回路を作ればよいことになります。

これについても、余っている出力ブロックによるフィードバックを利用することで、回路資源に収めることができました。コードは以下のようになっています。

コード中のコメントにあるように、本当はB9..6の名前を使いたかったんですが、0始まりでない変数を使うと何故かうまくいかないので、気を付けてください。

コンパイルエラーは出ないんですが、論理式の展開結果が普通におかしくなりました。こういうことがあるのでシミュレーションはちゃんとやりましょう。

逆変換回路

BCT 10 bit → 二進8 bitの変換は2個のCPLDで実現できました。もとの三進の値と変換後の二進の値はバラバラな対応になってしまっていますが、一対一に対応さえしていればCPU側からは何も問題ありません。

そのため次は逆変換の回路を作る必要がありますが、こちらも2個のCPLDを使って、それぞれの変換の逆変換を組み合わせることで実現できました。

やるだけだったのでコードは割愛します。

順変換及び逆変換のICレベルでの構成図は以下のようになりました。

2個のCPLDでBCTから二進に変換する様子と、2個のCPLDで逆の変換をする様子

実機テスト

CPLD 4つ分のコンパイル、シミュレーションに成功したので、満を持して実機に書き込みました。

無事に書き込みにも成功したので実機テストを行います。マイコンにArduino互換機のPro Microボードを使用し、10 bitのBCTの243全パターンをテストします。BCT→二進→BCTとCPLDで変換して、入力値と出力値が一致することを確かめます。

GPIOが足りないので、その辺にあった74HC595を使って拡張しました。ジャンパワイヤだらけですが、テスト回路の様子はこんな感じです。

ブレッドボード上で実機テストを行う様子の写真

デバッグ祭り

実機テストの結果、全然正しく動きませんでした…。

とりあえず1段目だけ取り出して入力パターンを固定し、信号を一つずつ追っていきました。するとフィードバックに使っているピンが正しく動いてなさそうということが分かりました。

デバイスの仕様を勘違いしたか?と思い何度もデータシートを確認しましたが、フィードバックの使い方は間違ってなさそうです。

フィードバック周りの挙動を確認するために以下のような非常にシンプルな回路で試してみました。

これでも正しく動きません。これはいよいよコンパイラが間違っているのでは?という気がしてきました。

試しにデバイスの動作モードをSimpleではなくComplexにしてみたところ、ちゃんと動きます。

ただComplexモードではproduct termが7項に制限され、足りない部分がでてきてしまうので、どうにかSimpleモードで動かしたいです。

ここまでの調査の結果、Simpleモードではフィードバックに使うピンが出力ではなく入力に設定され、かつ入力ピンは内部でプルアップされていることが分かりました。

人力コンパイル

最終手段ということでコンパイル結果のバイナリ(ほぼテキストファイルですが)を見てみることにしました。

コンパイル結果はjedファイルで、以下のような内容になっています。


CUPL(WM)        5.0a  Serial# MW-10400000
Device          g16v8s  Library DLIB-h-40-9
Created         Wed Jul 01 23:27:01 2026
Name            bct2bin.pld
Partno          
Revision        
Date            
Designer        
Company         
Assembly        
Location        
*QP20 
*QF2194 
*G0 
*F0 
*L00000 10010111101101111011111111111111
*L00032 10100111101111111011111111111111
*L00064 10010111101110111111111111111111
*L00096 10011011111101111011111111111111
*L00128 10011011111110111111111111111111
*L00256 10010111101111111011111111111111
*L00288 01100111101101111011111111111111
*L00320 10110111101110110111111111111111
*L00352 10101011111101111011111111111111
*L00384 10101011111110111111111111111111
*L00512 10011011011101111011111111111111
*L00544 01101011101111111011111111111111
*L00576 01100111101110111111111111111111
*L00608 10111011101110111111111111111111
*L00640 10111011101101111011111111111111
*L00672 10100111101110110111111111111111
*L00768 11111111111111111101111111111111
*L00800 10011011011110110111111111111111
*L00832 10011011101110111011111111111111
*L01024 11111111111111111111110111111111
*L01056 10101011101110111011111111111111
*L01088 10100111101110110111111111111111
*L01120 10100111101101111011111111111111
*L01152 10011011011110111011111111111111
*L01184 10011011101110110111111111111111
*L01216 10011011101101111011111111111111
*L01280 10010111101101111011111111111111
*L01312 11101011011101111011111111111111
*L01344 01101111101110110111111111111111
*L01376 01100111101110111011111111111111
*L01408 10111011011110111011111111111111
*L01440 10111011101101111011111111111111
*L01472 10100111101110110111111111111111
*L01536 10010111101110111011111111111111
*L01568 01101011011110111011111111111111
*L01600 01101011101110110111111111111111
*L01632 01101011101101111011111111111111
*L01664 01100111101110111011111111111111
*L01696 10101011011110110111111111111111
*L01728 10101011011101111011111111111111
*L02048 11111110000000000000000000000000
*L02112 00000000000001111111111111111111
*L02144 11111111111111111111111111111111
*L02176 111111111111111110
*C9FAA
*9B66

この01の列が何に対応するかというのが感じですが、こちら (https://www.official.cz/static/_dokumenty/9/3/6/9/GAL16V8x.pdf) のデータシートに載っていました。

Simpleモードのアドレスは以下の図に書かれています。

CPLDのアドレスマップが書かれた図

ヒューズアレイのところに0000とか0224とか書かれていて、交点のヒューズに順番に対応していることが分かります。先ほどのバイナリと比べても間違いなさそうです。

入出力を制御しているのは出力ブロックのAC1というビットで、2120 ~ 2127がそれです。さっきのバイナリからここを見てみると…

*L02112 00000000000001111111111111111111

ビンゴ!フィードバックに使っているピンのAC1が、出力の0ではなく、入力の1になってました!

ここを0に書き換えてみます。ただそのままだとチェックサムが壊れて書き込めないので、Cから始まっている *C9FAAという行を消します。チェックサムは無くても書き込めました。

4つのCPLD全部対応して、改めてテストすると、全パターンでテストに通りました!!

まとめ

CPLDというプログラマブルロジックを使ってBCT↔二進の変換回路を作りました。

色々と頭を使うことで順変換と逆変換をIC 2個ずつで実装でき、かなりハードウェア資源を上手く活用できたと思います。

コンパイラにバグがあったせいでかなりデバッグに苦しみました。クソが

値段の安さ、ピンの多さ、5 V駆動といったあたりで、ピンポイントで刺さる場面があるんじゃないかなと思います。皆さんも是非CPLDを使ってみてはいかがでしょうか。

-製作物

執筆者:


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


関連記事

【PIC】圧電素子を使って2000円で電子マリンバを作ってみた

前回の記事で圧電素子の使い方について紹介したので、今回はその応用編として電子マリンバを作ってみました。 以前ネットで電子マリンバなるものを見つけたとき、めちゃくちゃ欲しいと思ったんですが、ウン十万とい …

1セル1基板のアナログライフゲーム作ってみた vol.2

この記事は前回からの続きとなります。前回をご覧になっていない方は是非こちらもご覧ください。https://rikeden.net/?p=758   目次1 コントローラー2 555+可変抵抗 …

【Pico W】QR Clock ver.2を作ってみた【ソフト編】

前回の記事でQR Clock ver.2のハードウェアを紹介しました。今回はソフトウェアについて紹介していきます。 マイコンにはRaspberry Pi Pico Wを使用しています。ピン数が多いこと …

PICとグラフィックLCDでシンプルな卓上時計を作ってみた

今回はある程度実用的な卓上時計を作ってみました。日時の表示と温度、湿度、気圧の表示だけのシンプルな機能としました。 目次1 主な使用部品2 回路解説2.1 電源部2.2 RTC2.3 ICSP, RE …

【Pico W】QR Clock ver.2を作ってみた【ハード編】

以前にマイクロQRコードで時刻を表示する時計を作ったのですが(記事書いてなかった)、スマホ標準のQRコード読み取り機能ではマイクロQRコードに対応していなくて、展示会でお客さんに読み取って貰えないとい …