點 LED 跑馬燈的 3 種方法

很多類型開發板教學都是點 LED 跑馬燈開始的而 FPGA 也不例外,Verilog HDL 語法簡單也代表一種功能能用多種的不同方法表示,好的 coding style 還有合理的設計才能確保合成器合成出好的電路。

跑馬燈架構大致如下 :

頂層包括二個主要模組,tick_1s 模組是產生1秒鐘的 tick 訊號後送至跑馬燈模組,也就是跑馬燈一秒位移一次,而跑馬燈模組會使用, (1)移位暫存器 (2)狀態機 (3)計數器 分別實作。

這次實驗使用 Arty A7 作為開發作平台,需要特別注意的是 LED 是1亮0不亮。

跑馬燈架構

頂層 RTL 如下 :

`timescale 1ns / 1ps
module MARQUEE_TOP(
    input CLK_100M,
    input RESET_N,

    output [3:0] LED
);

wire wTick1s;

tick_1s tick_1s_inst (
    .clk        (CLK_100M),
    .rstN       (RESET_N),

    .oTick1s    (wTick1s)
);

marquee_shift marquee_shift_inst (
    .clk        (CLK_100M),
    .rstN       (RESET_N),

    .iTick1s    (wTick1s),
    .ovLed      (LED)
);

// marquee_counter marquee_counter_inst (
//     .clk        (CLK_100M),
//     .rstN       (RESET_N),

//     .iTick1s    (wTick1s),
//     .ovLed      (LED)
// );

// marquee_state_machine marquee_state_machine_inst (
//     .clk        (CLK_100M),
//     .rstN       (RESET_N),

//     .iTick1s    (wTick1s),
//     .ovLed      (LED)
// );

endmodule

(1) 使用移位暫存器 RTL :

module marquee_shift (
    input clk,
    input rstN,

    input iTick1s,
    output [3:0] ovLed
);

reg [3:0] rvLed_d, rvLed_q;

//shift
always @(posedge clk or negedge rstN) begin
    if(!rstN)
        rvLed_q <= 4'b0001;
    else
        rvLed_q <= rvLed_d;
end

always @(*) begin
    if(iTick1s)
        rvLed_d = {rvLed_q[2:0], rvLed_q[3]};
    else
        rvLed_d = rvLed_q;
end
assign ovLed = rvLed_q;

endmodule

(2) 使用狀態機 RTL :

module marquee_state_machine (
    input clk,
    input rstN,

    input iTick1s,
    output reg [3:0] ovLed
);

localparam [1:0] S0 = 0;
localparam [1:0] S1 = 1;
localparam [1:0] S2 = 2;
localparam [1:0] S3 = 3;

reg [1:0] cState, nState;

//state machine
wire S0toS1 = (cState == S0) && iTick1s;
wire S1toS2 = (cState == S1) && iTick1s;
wire S2toS3 = (cState == S2) && iTick1s;
wire S3toS1 = (cState == S3) && iTick1s;

always @(posedge clk or negedge rstN) begin
    if(!rstN)
        cState <= S0;
    else
        cState <= nState;
end

always @(*) begin
    case (cState)
        S0 : nState = S0toS1 ? S1 : cState;
        S1 : nState = S1toS2 ? S2 : cState;
        S2 : nState = S2toS3 ? S3 : cState;
        S3 : nState = S3toS1 ? S0 : cState; 
        default:  nState = S0; 
    endcase
end

//led output
always @(*) begin
    case(cState)
        S0 : ovLed = 4'b0001;
        S1 : ovLed = 4'b0010;
        S2 : ovLed = 4'b0100;
        S3 : ovLed = 4'b1000;
        default : ovLed = 4'b0000;
    endcase
end

endmodule

(3) 使用計數器 RTL :

module marquee_counter (
    input clk,
    input rstN,

    input iTick1s,
    output reg [3:0] ovLed
);

reg [1:0] rvCnt_d, rvCnt_q;
wire AddCnt, EndCnt;

//counter
always @(posedge clk or negedge rstN) begin
    if(!rstN)
        rvCnt_q <= 2'h0;
    else
        rvCnt_q <= rvCnt_d;
end

always @(*) begin
    if(iTick1s)
        rvCnt_d = rvCnt_q + 1'b1;
    else
        rvCnt_d = rvCnt_q;
end

//led output
always @(*) begin
    case(rvCnt_q)
        2'd0: ovLed = 4'b0001;
        2'd1: ovLed = 4'b0010;
        2'd2: ovLed = 4'b0100;
        2'd3: ovLed = 4'b1000;
        default: ovLed = 4'b0000;
    endcase
end

endmodule

xdc file 如下:

set_property PACKAGE_PIN E3 [get_ports CLK_100M]
set_property IOSTANDARD LVCMOS33 [get_ports CLK_100M]
set_property PACKAGE_PIN H5 [get_ports {LED[3]}]
set_property PACKAGE_PIN J5 [get_ports {LED[2]}]
set_property PACKAGE_PIN T9 [get_ports {LED[1]}]
set_property PACKAGE_PIN T10 [get_ports {LED[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports RESET_N]
set_property PACKAGE_PIN C2 [get_ports RESET_N]

測試結果

跑馬燈測試

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *