システムの基幹クロックを分周して利用する際、その分周されたクロック信号の特定のタイミング、特に立ち上がりエッジを正確に識別し、それをフラグ信号として利用する必要が生じることがあります。このフラグ信号は、後続のデジタル回路の動作を分周クロックに同期させるために不可欠です。分周比が設計時に既知である場合でも、設計の柔軟性を高め、将来的な分周比の変更に容易に対応できるように、あるいは未知のクロックソースの周期を検出する汎用的な方法として、以下の二つの手法が考えられます。
検出手法
- シングルサイクル遅延とAND演算による検出 この手法は、対象となる分周クロック信号を、システムクロック(上位の高速クロック)で1サイクルだけ遅延させた信号を生成し、元の分周クロック信号とその遅延信号の反転値を論理AND演算することで、立ち上がりエッジを検出します。
具体的には、現在の分周クロック信号を input_divided_clk_i、それをシステムクロックで1サイクル遅延させた信号を delayed_divided_clk_r とした場合、検出フラグは input_divided_clk_i & (~delayed_divided_clk_r) の形で生成されます。
このフラグ信号は、input_divided_clk_i の立ち上がりエッジが発生したシステムクロックサイクルでアサートされ、システムクロックの1サイクル分だけハイレベルを維持します。これは、分周クロックのエッジに同期した即時的なイベントトリガが必要な場合に適しています。
- シフトレジスタによるパターン検出 この方法では、分周クロック信号を2ビットのシフトレジスタに入力し、レジスタに保持される直前のクロック状態と現在のクロック状態のパターンを監視します。このパターンを解析することで、クロックの遷移を識別します。
例えば、シフトレジスタが {直前の状態, 現在の状態} のように構成されている場合、2'b01 というパターンが検出されれば立ち上がりエッジを示し、2'b10 というパターンが検出されれば立ち下がりエッジを示します。
reg [1:0] clk_state_history_r; // クロックの状態履歴を保持するシフトレジスタ
always @(posedge sys_clk_i or negedge reset_n_i)
begin
if(reset_n_i == 1'b0)
clk_state_history_r <= 2'b00; // リセット時は初期状態に設定
else
// 現在の分周クロックの状態をシフトレジスタに格納
// {古い状態, 新しい状態}
clk_state_history_r <= {clk_state_history_r[0], input_divided_clk_i};
end
// シフトレジスタのパターンを基にエッジを検出
always @(posedge sys_clk_i or negedge reset_n_i)
begin
if(reset_n_i == 1'b0)
// リセット時の動作(例: フラグのリセットなど)
// ...
else if(clk_state_history_r == 2'b01) // 立ち上がりエッジ (0->1) を検出
// 立ち上がりエッジ検出時の処理
// ...
else if(clk_state_history_r == 2'b10) // 立ち下がりエッジ (1->0) を検出
// 立ち下がりエッジ検出時の処理
// ...
else
// その他の状態またはエッジ未検出時の処理
// ...
end
このコードは、シフトレジスタの値を監視することで、クロックの立ち上がりや立ち下がりを検出する基本的な構造を示しています。
Verilog HDLによる実装例
以下のVerilog HDLコードは、上記で説明した分周クロック生成モジュールと、周期検出フラグ生成モジュールの具体的な実装例です。分周クロック生成モジュールの分周比は、パラメータを通じて柔軟に変更可能です。
分周クロック生成モジュール
module frequency_divider_module
#(
parameter integer PARAM_DIV_RATIO = 4, // 分周比 (偶数のみ可)。出力クロックの周期はPARAM_DIV_RATIO * sys_clk_i の周期
parameter integer PARAM_CNT_WIDTH = $clog2(PARAM_DIV_RATIO), // カウンタのビット幅
parameter integer PARAM_MAX_CNT_VAL = PARAM_DIV_RATIO - 1 // カウンタの最大値
)
(
input wire reset_n_i, // 非同期リセット (Lowアクティブ)
input wire sys_clk_i, // システムクロック
output reg output_divided_clk_o // 分周されたクロック出力
);
reg [PARAM_CNT_WIDTH-1:0] clk_counter_reg; // クロック分周用カウンタ
// 分周クロック生成ロジック
always @(posedge sys_clk_i or negedge reset_n_i) begin
if (reset_n_i == 1'b0) begin
output_divided_clk_o <= 1'b0; // リセット時に分周クロックをLowに初期化
end else if (clk_counter_reg == PARAM_MAX_CNT_VAL) begin
output_divided_clk_o <= 1'b0; // カウンタが最大値に達したらLowに
end else if (clk_counter_reg == PARAM_MAX_CNT_VAL / 2) begin // 周期の中間点
output_divided_clk_o <= 1'b1; // 分周クロックをHighに設定
end else begin
output_divided_clk_o <= output_divided_clk_o; // 現在の状態を維持
end
end
// 分周カウンタのロジック
always @(posedge sys_clk_i or negedge reset_n_i) begin
if (reset_n_i == 1'b0) begin
clk_counter_reg <= 0; // リセット時にカウンタを0に初期化
end else if (clk_counter_reg == PARAM_MAX_CNT_VAL) begin
clk_counter_reg <= 0; // カウンタが最大値に達したらリセット
end else begin
clk_counter_reg <= clk_counter_reg + 1'b1; // カウンタをインクリメント
end
end
endmodule
周期検出フラグ生成モジュール
module divided_clock_edge_detector
(
input wire reset_n_i, // 非同期リセット (Lowアクティブ)
input wire sys_clk_i, // システムクロック
input wire input_divided_clk_i, // 分周された入力クロック
output wire edge_flag_method1_o, // 手法1によるエッジ検出フラグ
output wire edge_flag_method2_o // 手法2によるエッジ検出フラグ
);
reg delayed_divided_clk_r; // 手法1用: 分周クロックの1サイクル遅延レジスタ
reg [1:0] clk_state_history_r; // 手法2用: クロックの状態履歴シフトレジスタ
// ========== 手法1: シングルサイクル遅延とAND演算による検出 ==========
always @(posedge sys_clk_i or negedge reset_n_i) begin
if (reset_n_i == 1'b0) begin
delayed_divided_clk_r <= 1'b0;
end else begin
delayed_divided_clk_r <= input_divided_clk_i; // 入力クロックを1システムクロックサイクル遅延
end
end
assign edge_flag_method1_o = input_divided_clk_i & (~delayed_divided_clk_r); // 立ち上がりエッジを検出
// ========== 手法2: シフトレジスタによるパターン検出 ==========
always @(posedge sys_clk_i or negedge reset_n_i) begin
if (reset_n_i == 1'b0) begin
clk_state_history_r <= 2'b00; // リセット時に既知の状態に初期化
end else
// 現在の分周クロックの状態をシフトレジスタに格納
clk_state_history_r <= {clk_state_history_r[0], input_divided_clk_i};
end
assign edge_flag_method2_o = (clk_state_history_r == 2'b01); // 0から1への遷移 (立ち上がりエッジ) をフラグとする
endmodule
シミュレーションテストベンチ
以下のテストベンチは、システムクロックとリセット信号を生成し、上記モジュールをインスタンス化して動作を確認します。
`timescale 1ns / 1ps
module testbench_edge_detection();
parameter MASTER_CLK_PERIOD_NS = 10; // システムクロック周期 (ns)
parameter HALF_MASTER_CLK_PERIOD_NS = MASTER_CLK_PERIOD_NS / 2; // 半周期
reg tb_reset_n; // テストベンチ用リセット信号
reg tb_master_clk; // テストベンチ用システムクロック
wire tb_divided_clock_signal; // 分周クロック出力
wire tb_flag_m1; // 手法1による検出フラグ
wire tb_flag_m2; // 手法2による検出フラグ
// システムクロック生成
always #HALF_MASTER_CLK_PERIOD_NS tb_master_clk = ~tb_master_clk;
// リセットシーケンス
initial begin
tb_master_clk = 1'b0; // クロックをLowから開始
tb_reset_n <= 1'b0; // リセットをアサート
# (MASTER_CLK_PERIOD_NS * 4); // 数サイクルリセットを保持
tb_reset_n <= 1'b1; // リセットをディアサート
# (MASTER_CLK_PERIOD_NS * 100); // しばらくシミュレーションを実行
$finish; // シミュレーション終了
end
// 分周器モジュールのインスタンス化
frequency_divider_module
#(
.PARAM_DIV_RATIO (8) // 8分周を設定 (出力クロック周期はマスタークロックの8倍)
)
u_frequency_divider
(
.reset_n_i (tb_reset_n),
.sys_clk_i (tb_master_clk),
.output_divided_clk_o (tb_divided_clock_signal)
);
// エッジ検出モジュールのインスタンス化
divided_clock_edge_detector
u_edge_detector
(
.reset_n_i (tb_reset_n),
.sys_clk_i (tb_master_clk),
.input_divided_clk_i (tb_divided_clock_signal),
.edge_flag_method1_o (tb_flag_m1),
.edge_flag_method2_o (tb_flag_m2)
);
endmodule
シミュレーション結果
シミュレーションを行うと、各検出手法で生成されるフラグ信号のタイミングに明確な違いがあることが確認できます。
- 手法1 (シングルサイクル遅延とAND演算): この手法で生成されたフラグ信号
edge_flag_method1_oは、input_divided_clk_iの立ち上がりエッジが発生したシステムクロックサイクルでアサートされ、システムクロック1サイクル分だけハイレベルを維持します。これは、分周クロックのエッジに同期した即座のイベントトリガに適しています。 - 手法2 (シフトレジスタによるパターン検出): この手法で生成されたフラグ信号
edge_flag_method2_oは、input_divided_clk_iの立ち上がりエッジが検出されたシステムクロックの「次のサイクル」でアサートされ、システムクロック1サイクル分だけハイレベルを維持します。1システムクロックサイクルの遅延があるため、即時性よりも状態遷移の確実な検出や、後の段階での処理に適していると言えます。