Interface編集部
第1部第3章 おまけ
●物理制約ファイル
IO_LOC “digit_output[5]” T12;
IO_PORT “digit_output[5]” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=24 OPEN_DRAIN=ON BANK_VCCIO=3.3;
IO_LOC “digit_output[4]” T11;
IO_PORT “digit_output[4]” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=24 OPEN_DRAIN=ON BANK_VCCIO=3.3;
IO_LOC “digit_output[3]” P9;
IO_PORT “digit_output[3]” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=24 OPEN_DRAIN=ON BANK_VCCIO=3.3;
IO_LOC “digit_output[2]” T8;
IO_PORT “digit_output[2]” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=24 OPEN_DRAIN=ON BANK_VCCIO=3.3;
IO_LOC “digit_output[1]” T7;
IO_PORT “digit_output[1]” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=24 OPEN_DRAIN=ON BANK_VCCIO=3.3;
IO_LOC “digit_output[0]” T6;
IO_PORT “digit_output[0]” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=24 OPEN_DRAIN=ON BANK_VCCIO=3.3;
IO_LOC “dot_output” D14;
IO_PORT “dot_output” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16 BANK_VCCIO=3.3;
IO_LOC “segment_output[6]” B14;
IO_PORT “segment_output[6]” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16 BANK_VCCIO=3.3;
IO_LOC “segment_output[5]” B13;
IO_PORT “segment_output[5]” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16 BANK_VCCIO=3.3;
IO_LOC “segment_output[4]” B12;
IO_PORT “segment_output[4]” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16 BANK_VCCIO=3.3;
IO_LOC “segment_output[3]” A11;
IO_PORT “segment_output[3]” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16 BANK_VCCIO=3.3;
IO_LOC “segment_output[2]” N6;
IO_PORT “segment_output[2]” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16 BANK_VCCIO=3.3;
IO_LOC “segment_output[1]” N9;
IO_PORT “segment_output[1]” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16 BANK_VCCIO=3.3;
IO_LOC “segment_output[0]” L9;
IO_PORT “segment_output[0]” IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=16 BANK_VCCIO=3.3;
IO_LOC “rotary_encoder_a” N7;
IO_PORT “rotary_encoder_a” IO_TYPE=LVCMOS33 PULL_MODE=UP BANK_VCCIO=3.3;
IO_LOC “rotary_encoder_b” N8;
IO_PORT “rotary_encoder_b” IO_TYPE=LVCMOS33 PULL_MODE=UP BANK_VCCIO=3.3;
IO_LOC “clk” H11;
IO_PORT “clk” IO_TYPE=LVCMOS33 PULL_MODE=UP BANK_VCCIO=3.3;
●SystemVerilogファイル
`default_nettype none
module timer #(
parameter COUNT_LIMIT = 27_000_000
) (
input wire clk,
output wire count_max
);
logic [$clog2(COUNT_LIMIT+1)-1:0] counter = ‘d0;
assign count_max = (counter == COUNT_LIMIT)? 1’b1: 1’b0;
always_ff @ (posedge clk) begin
if (counter == COUNT_LIMIT) begin
counter <= ‘d0;
end else begin
counter <= counter + ‘d1;
end
end
endmodule
module digit_driver #(
parameter CLK_FREQ = 27_000_000 // 入力クロックの周波数を指定
) (
input wire clk, // 27MHzクロック入力
input wire [19:0] digit_interval_ms, // 次の桁へ遷移する間隔を指定
output wire [5:0] digit_output, // 表示する桁の指定
output wire [3:0] digit_count // 表示している桁番号
);
// 前章で作成したタイマーモジュール
// 1ミリ秒に1回digit_count_upがHiになる
wire count_max;
timer #(
.COUNT_LIMIT (CLK_FREQ / 1_000)
) timer_i (
.*
);
// 指定された遷移間隔に応じてdigit_count_upをHiにする
logic [19:0] digit_interval_counter = ‘0;
always_ff @ (posedge clk) begin
if (count_max) begin
if (digit_interval_counter >= (digit_interval_ms – 20’d1) || digit_interval_ms == 20’d0) begin
digit_interval_counter <= ‘0;
end else begin
digit_interval_counter <= digit_interval_counter + 20’d1;
end
end
end
wire digit_count_up = count_max & (digit_interval_counter == 20’d0);
// digit_count_upがHiになったタイミングでdigit_counterをインクリメントするロジック
// 今回使用する7セグLEDの桁数は6なので、digit_counterは0~5のいずれかの値をとるように設計
logic [3:0] digit_counter = 4’d0;
always_ff @ (posedge clk) begin
if (digit_count_up) begin
if (digit_counter == 4’d5) begin
digit_counter <= 4’d0;
end else begin
digit_counter <= digit_counter + 4’d1;
end
end
end
// digit_counterの値に応じてdigitの1bitだけをHiとするロジック
// digitは後のassign文で反転されてdigit_outputとなる
logic [5:0] digit = 6’b000000;
always_comb begin
case (digit_counter)
4’d0: digit = 6’b000001;
4’d1: digit = 6’b000010;
4’d2: digit = 6’b000100;
4’d3: digit = 6’b001000;
4’d4: digit = 6’b010000;
4’d5: digit = 6’b100000;
default: digit = 6’b000000; // 通常ここには入らない
endcase
end
assign digit_output = ~digit; // digit_outputは1bitだけLoとなる
assign digit_count = digit_counter;
endmodule
module segment_driver (
input wire [3:0] decimal_number, // 7セグLEDに表示したい10進数の入力
output wire [6:0] segment_output // 各LEDへの出力。[0]がA、[6]がGのLEDに対応
);
logic [6:0] segment = 7’b00000000;
always_comb begin
case (decimal_number) // 10進数の入力に応じて各LEDへの出力を決定
4’d0: segment[6:0] = 7’b0111111;
4’d1: segment[6:0] = 7’b0000110;
4’d2: segment[6:0] = 7’b1011011;
4’d3: segment[6:0] = 7’b1001111;
4’d4: segment[6:0] = 7’b1100110;
4’d5: segment[6:0] = 7’b1101101;
4’d6: segment[6:0] = 7’b1111101;
4’d7: segment[6:0] = 7’b0000111;
4’d8: segment[6:0] = 7’b1111111;
4’d9: segment[6:0] = 7’b1101111;
default: segment[6:0] = 7’b0000000; // 0~9以外が入力されたときには何も表示しない
endcase
end
assign segment_output = segment;
endmodule
module decimal_counter #(
parameter CLK_FREQ = 27_000_000, // 入力クロックの周波数を指定
parameter INIT_VAL = 0 // 初期値を指定
) (
input wire clk, // 27MHzクロック入力
input wire count_enable, // カウンタをカウントアップ or カウントダウン
input wire is_count_up, // 1: カウントアップ, 0: カウントダウン
output wire [3:0] counter_output[6] // 6桁の10進数カウンタの値
);
// 6桁の10進数カウントアップロジック
logic [3:0] counter_reg[6] = {(INIT_VAL / 1) % 10,
(INIT_VAL / 10) % 10,
(INIT_VAL / 100) % 10,
(INIT_VAL / 1_000) % 10,
(INIT_VAL / 10_000) % 10,
(INIT_VAL / 100_000) % 10};
logic [3:0] counter_next_value[6];
logic [6:0] carry;
assign carry[0] = count_enable; // count_enable 入力時にカウントアップ or カウントダウン
// 6桁分の10進数カウンタをインスタンシエート
for (genvar i = 0; i < 6; i++) begin
// カウンタの次の値を計算する組み合わせ回路
always_comb begin
if (is_count_up) begin
// カウントアップ
if (carry[i] == 1’b1) begin // 下の桁からのキャリーが1場合は、次の値が変わる
if (counter_reg[i] == 4’d9) begin // 現在の値が9のときは、
counter_next_value[i] = 4’d0; // – 次の値は0
carry[i + 1] = 1’b1; // – 上の桁へのキャリーは1
end else begin // 現在の値が9以外のときは、
counter_next_value[i] = counter_reg[i] + 4’d1; // – 次の値は現在の値+1
carry[i + 1] = 1’b0; // – 上の桁へのキャリーは0
end
end else begin // 下の桁からキャリーがない場合は次の値は現在の値と同じ
counter_next_value[i] = counter_reg[i];
carry[i + 1] = 1’b0;
end
end else begin
// カウントダウン
if (carry[i] == 1’b1) begin // 下の桁からのキャリーが1場合は、次の値が変わる
if (counter_reg[i] == 4’d0) begin // 現在の値が0のときは、
counter_next_value[i] = 4’d9; // – 次の値は9
carry[i + 1] = 1’b1; // – 上の桁へのキャリーは1
end else begin // 現在の値が0以外のときは、
counter_next_value[i] = counter_reg[i] – 4’d1; // – 次の値は現在の値-1
carry[i + 1] = 1’b0; // – 上の桁へのキャリーは0
end
end else begin // 下の桁からキャリーがない場合は次の値は現在の値と同じ
counter_next_value[i] = counter_reg[i];
carry[i + 1] = 1’b0;
end
end
end
// カウンタ値を保持するレジスタ
always_ff @ (posedge clk) begin
counter_reg[i] <= counter_next_value[i];
end
end
assign counter_output = counter_reg;
endmodule
module top (
input wire clk, // 27MHzクロック入力
input wire rotary_encoder_a, // ロータリーエンコーダA相からの入力
input wire rotary_encoder_b, // ロータリーエンコーダB相からの入力
output wire [6:0] segment_output, // LED(A~G)への出力
output wire dot_output, // LED(DP)への出力
output wire [5:0] digit_output // 表示する桁の指定
);
localparam CLK_FREQ = 27_000_000;
localparam INIT_DIGIT_INTERVAL_MS = 20;
localparam CAPTURE_FREQ_HZ = 1_000;
// タイマーモジュール
// 1ミリ秒に1回sw_in_captureがHiになる
wire sw_in_capture;
timer #(
.COUNT_LIMIT (CLK_FREQ / CAPTURE_FREQ_HZ)
) timer_i (
.count_max (sw_in_capture),
.*
);
// メタステーブル防止のため、ロータリーエンコーダからからの入力を2段のフリップフロップで受ける
logic [1:0] rotary_encoder_a_reg = ‘1;
logic [1:0] rotary_encoder_b_reg = ‘1;
always @ (posedge clk) begin
rotary_encoder_a_reg[0] <= rotary_encoder_a;
rotary_encoder_a_reg[1] <= rotary_encoder_a_reg[0];
rotary_encoder_b_reg[0] <= rotary_encoder_b;
rotary_encoder_b_reg[1] <= rotary_encoder_b_reg[0];
end
// ロータリーエンコーダからの入力を2段のフリップフロップに記憶する
logic [1:0] rotary_encoder_a_acc_reg = ‘1;
logic [1:0] rotary_encoder_b_acc_reg = ‘1;
always @ (posedge clk) begin
if (sw_in_capture) begin // 1ミリ秒に一度だけスイッチ入力をキャプチャする
rotary_encoder_a_acc_reg[0] <= rotary_encoder_a_reg[1];
rotary_encoder_b_acc_reg[0] <= rotary_encoder_b_reg[1];
rotary_encoder_a_acc_reg[1] <= rotary_encoder_a_acc_reg[0];
rotary_encoder_b_acc_reg[1] <= rotary_encoder_b_acc_reg[0];
end
end
// 2段のフリップフロップの値からB相のエッジを検出する
wire rotary_encoder_b_posedge = ~rotary_encoder_b_acc_reg[1] & rotary_encoder_b_acc_reg[0] & sw_in_capture;
wire rotary_encoder_b_negedge = rotary_encoder_b_acc_reg[1] & ~rotary_encoder_b_acc_reg[0] & sw_in_capture;
// A相がLo かつB相のエッジを検出したときにカウントアップ or カウントダウン
wire count_enable = ~rotary_encoder_a_acc_reg[1] & (rotary_encoder_b_posedge | rotary_encoder_b_negedge);
// B相が立ち下がりときにカウントアップ
wire is_count_up = rotary_encoder_b_negedge;
// 6桁の10進数カウントアップモジュール
// ロータリーエンコーダからの入力に応じてカウントアップ or カウントダウンする
wire [3:0] decimal_number[6];
decimal_counter #(
.CLK_FREQ (CLK_FREQ),
.INIT_VAL (INIT_DIGIT_INTERVAL_MS)
) decimal_counter_i (
.counter_output (decimal_number),
.*
);
// ロータリーエンコーダからの入力に応じて、次の桁へ遷移する間隔を変化させる
logic [19:0] digit_interval_ms = INIT_DIGIT_INTERVAL_MS;
always_ff @ (posedge clk) begin
if (count_enable) begin
if (is_count_up) begin
if (digit_interval_ms == 20’d999_999) begin
digit_interval_ms <= 20’d0;
end else begin
digit_interval_ms <= digit_interval_ms + 20’d1;
end
end else begin
if (digit_interval_ms == 20’d0) begin
digit_interval_ms <= 20’d999_999;
end else begin
digit_interval_ms <= digit_interval_ms – 20’d1;
end
end
end
end
// 表示する桁を指定するモジュール
// 一定の間隔で次の桁へ移動する
wire [3:0] digit_count;
digit_driver #(
.CLK_FREQ (CLK_FREQ)
) digit_driver_i (
.*
);
// decimal_numberの値をsegment_outputをへ変換するモジュール
segment_driver segment_driver_i (
.decimal_number(decimal_number[digit_count]),
.*
);
endmodule
`default_nettype wire