エレキーのFPGA実装

 買って積みっぱなしの基板を何とかする話です。最近はそこそこ使えるFPGAも安くなりまた。ダウンロードケーブルを持っていなくともボードだけ購入すれば取りあえず遊べるプロダクトに引かれPapilioを購入、なんとなく手を出さずに1年くらい過ぎてしまいました。このボードはZAP IDEなるものを使うことでArduinoスケッチを高速に実行できる仕組みが提供されていますので、Arduinoの経験があればすぐにでも活用できる基板です。でも、折角のFPGAですから直接使ってみたいと思います。

 このボードにはXilinx社のSpartan3Eが載っています。同社の開発環境ISEをインストールしてVerilog HDLやVHDLで回路を書いて、合成された回路のファイルをPapilio Loaderというソフトでボードに転送する手順でFPGAを使うことができます。

 話は飛びますが、最近のエレキー工作はマイコンを使った高機能な物が主流で、ロジックICを使ったものをあまり見かけなくなりました。昔々、ロジックICのエレキーを組み立てて楽しんだ思い出があります。当時最高のハックと思っていたことは、エレキー回路をジョイスティックに接続して連射機能を実現したことでした。不思議なことにFPGAボードを買うときに、ふとエレキーのロジック回路が頭をよぎり、きっと使わないけどなんとなく実装してみたくなったのです。言語ですが、VHDLに比べてVerilog HDLの方が記述量が少なそうなのでVerilog HDLを選択しました。

 実装するエレキーの回路は以下のものです。

クリックで大きくなります。

■回路の概要

 発振回路が2つあります。左上付近のNAND(IC5D)とNOR(IC4B)が符号を作るための基準クロック(clk2)を出力します。速度はR7で調整するようになっています。右下付近のNAND(IC6A,B,C)はモニター用のトーンを作るためのクロック(clk1)です。

 clk2は2つのJKFF(inst1,2)に入力されます。ここで長点と短点を作ります。長点と短点の選択はinst2のRピンによって判定され、Rピン(dash_en)がHIGHのときinst2のQはLOWになりますので短点が出力されます。符号のON/OFFはinst1のRピンに入力されるclk_enで決まります。clk_enがHIGHのとき符号は止まります。

 上記のclk_enとdash_enを適切に作ることでエレキーは動作します。パドルの状態からclk_enとdash_enを作る部分が左下の回路になります。パドルが押されるとRSラッチ(IC1B,D)で記憶されclk_enとdash_enが作られます。DOTとDASHを両方抑えた時は長点と短点を交互に出します。ラッチの更新は2つのJKFF(inst3,4)を使いそのタイミングをNOR(IC4C)で作っています。

■FPGAへの実装

 発振回路はFPGAのクロックを利用しますので、発振回路以外はほとんどそのままVerilog HDLで記述することにしました。部品として、JKFFとRSラッチを用意します。以下の様な感じです。

module JKFF_SR(
input CK,
input J,
input K,
input R,
input S,
output reg Q = 0,
output QB
);

always@(posedge CK or posedge R or posedge S) begin
if (S == 1) Q <= 1;
else if (R == 1) Q <= 0;
else
case ({J, K})
2'b00 : Q <= Q;
2'b01 : Q <= 0;
2'b10 : Q <= 1;
2'b11 : Q <= ~Q;
endcase
end
assign QB = ~Q;
endmodule

module RS_latch(
input R,
input S,
output Q,
output QB
);

assign Q = ~(R | QB);
assign QB = ~(S | Q);
endmodule

 JKFFを2つ使って短点と長点の生成モジュールを作ります。

module dot_dash_gen(
input clk,
input dot_en,
input dash_en,
output key
);

wire q1, q2;
JKFF_SR JKFF_SR_inst1( .CK(clk), .J(1'b1), .K(1'b1), .R(dot_en), .S(1'b0), .Q(q1), .QB());
JKFF_SR JKFF_SR_inst2( .CK(q1), .J(1'b1), .K(1'b1), .R(dash_en), .S(1'b0), .Q(q2), .QB());
assign key = q1 | q2;
endmodule

 パドルの入力を受け取り、clk_enとdash_enを作るモジュールを作ります。

module gen_driver(
input dot,
input dash,
input alt_clk,
output dot_en,
output dash_en
);
wire set_dot, set_dash;
wire mem_dot, mem_dash;

assign set_dot = ~(dot | mem_dot);
assign set_dash = ~(dash | mem_dash);

wire qb3, qb4;
JKFF_SR JKFF_SR_inst3( .CK(alt_clk), .J(mem_dot), .K(mem_dot), .R(1'b0), .S(set_dot), .Q(), .QB(qb3));
JKFF_SR JKFF_SR_inst4( .CK(alt_clk), .J(mem_dash), .K(mem_dash), .R(1'b0), .S(set_dash), .Q(), .QB(qb4));
RS_latch RS_latch_inst( .R(qb3), .S(qb4), .Q(mem_dot), .QB(mem_dash));

assign dot_en = ~(mem_dash | mem_dot);
assign dash_en = ~mem_dash;
endmodule

 dot_dash_genとgen_driverを接続するモジュールを作ります。

module elekey(
input CLK,
input DOT,
input DASH,
output CLK_EN,
output O
);

wire dot_en, dash_en, alt_clk;
dot_dash_gen dot_dash_gen_inst( .clk(CLK), .dot_en(dot_en), .dash_en(dash_en), .key(O));
gen_driver gen_driver_inst( .dot(DOT), .dash(DASH), .alt_clk(alt_clk), .dot_en(dot_en), .dash_en(dash_en));
assign alt_clk = ~(O | CLK);
assign CLK_EN = ~dot_en;
endmodule

 elekeyモジュールにクロックを供給すれば動く状態になりましたので、クロック生成のモジュールを作ります。

module gen_clk(
input mclk,
input rst,
input [25:0]th,
output reg gclk = 0
);

reg [25:0] count = 0;
always @(posedge mclk or posedge rst) begin
if (rst == 1'b1) begin
count <= 0;
gclk <= 0;
end
else
if (count == th) begin
count <= 0;
gclk <= ~gclk;
end
else count <= count + 1;
end
endmodule

 最後にelekeyにクロックを供給し、elekeyの出力に従ってサイドトーンを出す機能をmainに記述して完成です。

module main(
input CLK,
input DOT,
input DASH,
output O,
output BZ
);
parameter XTAL = 32 * 1000 * 1000;
parameter FRQ_BZ = 800;
parameter TH_BZ = XTAL / (FRQ_BZ * 2);
parameter FRQ_CW = 15;
parameter TH_CW = XTAL / (FRQ_CW * 2);

wire clk1;
gen_clk gen_clk_inst1( .mclk(CLK), .rst(1'b0), .th(TH_BZ), .gclk(clk1));
wire clk2;
wire clk_en;
gen_clk gen_clk_inst2( .mclk(CLK), .rst(~clk_en), .th(TH_CW), .gclk(clk2));
elekey elekey_inst( .CLK(clk2), .DOT(DOT), .DASH(DASH), .CLK_EN(clk_en), .O(O));

assign BZ = O & clk1;
endmodule

 発生する周波数はFRQ_CW, FER_BZで指定します。XTALはPapilioのクロック(32MHz)を定義していますので、他のボードで使うときは適宜変更してください。最後にPapilio One用のUCFを掲載します。

# PlanAhead Generated IO constraints

NET "CLK" IOSTANDARD = LVTTL;
NET "DASH" IOSTANDARD = LVTTL;
NET "DOT" IOSTANDARD = LVTTL;
NET "O" IOSTANDARD = LVTTL;
NET "BZ" IOSTANDARD = LVTTL;

# PlanAhead Generated physical constraints

NET "CLK" LOC = P89;
NET "DASH" LOC = P91 | PULLUP;
NET "DOT" LOC = P92 | PULLUP;
NET "O" LOC = P94;
NET "BZ" LOC = P95;

 C00にパドルのDASH、C01にパドルのDOT、両方ともにFPGA内でPULLUPされています。C02はトランジスタを接続してから無線機へ、C03に圧電素子を接続すると動作します。

 何十年ぶりにパドルを出してFPGAに接続しました。動きませんorz、パドルの接点がくすんで接触不良でした。接点を磨くと快調に動き出しました。喜んで符号を出そうとしますがパドルよりも人間の劣化が激しく、つっかえつっかえになってしまうところが何ともかっこ悪い感じです。。。


Latest update at 2015/4/27