ShrikeLiteで周波数カウンタを製作したいと思っています。過去にGPSDOを製作したことがありますが、10MHzで10ppbクラスの周波数確度を目指すとすれば、ゲートを10秒間開けて100Mカウントすることになるので、28ビット程度の桁数が必要です。GPSレシーバの1pps信号を利用してゲートを制御すれば、正確な周波数カウンタを自作することができます。私は、未だVerilogの学習途上なので、フルスクラッチで記述する自信がありません。そこで、GPSDOの一部としての周波数カウンタをAIに設計してもらいました。Geminiに次のように問いかけました。
28ビットの周波数カウンターをverilogで書いてください。
ただし、ゲート時間は、外部から入力されるgate_inにより制御され、カウント中はbusy信号を出力することとします。すると、次のようなVerilogのコードを提示されました。
module freq_counter_28bit (
input wire clk, // システムクロック
input wire reset_n, // 非同期リセット(負論理)
input wire sig_in, // 測定対象の入力信号
input wire gate_in, // 外部ゲート入力
output reg [27:0] freq_out, // 測定された周波数データ
output reg busy // カウント中フラグ
);
// 内部レジスタ
reg [27:0] count_reg;
reg gate_sync_0, gate_sync_1; // gate_in同期化用
reg sig_sync_0, sig_sync_1; // sig_inエッジ検出用
// 信号の同期化とエッジ検出用バッファ
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
gate_sync_0 <= 1'b0;
gate_sync_1 <= 1'b0;
sig_sync_0 <= 1'b0;
sig_sync_1 <= 1'b0;
end else begin
gate_sync_0 <= gate_in;
gate_sync_1 <= gate_sync_0;
sig_sync_0 <= sig_in;
sig_sync_1 <= sig_sync_0;
end
end
// エッジ検出フラグ
wire gate_rising = (gate_sync_0 && !gate_sync_1);
wire gate_falling = (!gate_sync_0 && gate_sync_1);
wire sig_rising = (sig_sync_0 && !sig_sync_1);
// メイン制御ロジック
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
count_reg <= 28'd0;
freq_out <= 28'd0;
busy <= 1'b0;
end else begin
if (gate_rising) begin
// カウント開始
count_reg <= 28'd0;
busy <= 1'b1;
end
else if (busy) begin
if (gate_falling) begin
// カウント終了、結果を保持
freq_out <= count_reg;
busy <= 1'b0;
end else if (sig_rising) begin
// ゲート期間中の信号立ち上がりをカウント
count_reg <= count_reg + 1'b1;
end
end
end
end
endmodule日本語のコメント付きなので、初心者にとっては勉強になります。Geminiは、テストベンチの作成もお手伝いしましょうかとのオファーをくれたので、お願いしたところ、次のようなテストベンチのVerilogコードを提示してくれました。
`timescale 1ns / 1ps
module freq_counter_tb();
// 信号定義
reg clk;
reg reset_n;
reg sig_in;
reg gate_in;
wire [27:0] freq_out;
wire busy;
// クロック周期の定義
parameter CLK_PERIOD = 10; // 100MHz
parameter SIG_PERIOD = 40; // 25MHz (テスト対象信号)
// インスタンス化
freq_counter_28bit uut (
.clk(clk),
.reset_n(reset_n),
.sig_in(sig_in),
.gate_in(gate_in),
.freq_out(freq_out),
.busy(busy)
);
// システムクロック生成
initial clk = 0;
always #(CLK_PERIOD/2) clk = ~clk;
// テスト対象信号 (sig_in) 生成
initial sig_in = 0;
always #(SIG_PERIOD/2) sig_in = ~sig_in;
// テストシナリオ
initial begin
// 初期状態
reset_n = 0;
gate_in = 0;
// リセット解除
#(CLK_PERIOD * 5);
reset_n = 1;
#(CLK_PERIOD * 5);
// --- 測定サイクル 1 ---
$display("Measurement 1 start...");
gate_in = 1;
#(SIG_PERIOD * 10.5); // sig_inを10回分カウントする程度の時間
gate_in = 0;
// busyが下がるのを待つ
wait(busy == 0);
$display("Result 1: %d pulses counted", freq_out);
#(CLK_PERIOD * 10);
// --- 測定サイクル 2 ---
$display("Measurement 2 start...");
gate_in = 1;
#(SIG_PERIOD * 25.5); // sig_inを25回分カウントする程度の時間
gate_in = 0;
wait(busy == 0);
$display("Result 2: %d pulses counted", freq_out);
#(CLK_PERIOD * 20);
$finish;
end
// 波形出力用 (GTKWave等で確認する場合)
initial begin
$dumpfile("freq_counter_tb.vcd");
$dumpvars(0, freq_counter_tb);
end
endmodule
この2つのVerilogコードをicarus(iverilog)でコンパイルしてGTKWaveで確認してみました。Verilog初心者にとっては、回路設計だけでも大変なのに、シミュレータ用のコード(テストベンチ)まで作ってくれて大変助かります。しかもオールフリー(全部無料)です。私が現役だった20年前に比べると夢のような話です。


コメント