序文
実験のこの部分は、Electronic Forest Little Feet Development Board のデジタル エレクトロニクス入門チュートリアルの実験に基づいています。実験リンク: Step-mxo2 入門チュートリアル 電子の森] (eetree.cn)
このうちコードはブロガーが学習後に自分の考えに従って打ち込んだものであり、直接コピーするものではなく、学習とコミュニケーション、侵入と削除のみに使用されます。
ラティス環境の構成についてはここでは説明しません~
ボタンLED
module LED (key,sw,led);
input [3:0] key;
input [3:0] sw;
output [7:0] led;
assign led={key, sw};
endmodule
3色LED
開発ボード上の 3 色の LED ライト。どの色は 3 ビット値を割り当てることによって決定されます。3つのボタンで簡単にコントロール
module LED (key,led);
input [2:0] key;
output [2:0] led;
assign led={key};
endmodule
3-8デコーダ
3 つのボタンは 8 つの状態を出力します。主にalways caseの使い方を練習します。
module LED (key,led);
input [2:0] key;
output [7:0] led;
reg [7:0] led;
always @(key)
begin
case(key)
3'b000:led=8'b11111110;
3'b001:led=8'b11111101;
3'b010:led=8'b11111011;
3'b011:led=8'b11110111;
3'b100:led=8'b11101111;
3'b101:led=8'b11011111;
3'b110:led=8'b10111111;
3'b111:led=8'b01111111;
default:;
endcase
end
endmodule
ニキシー管ディスプレイ
2 つのデジタル管は、それぞれ 9 ビット入力を必要とし、そのうち 4 つはデータ入力であり、0 ~ 9 に対応する 10 状態を入力できます。
ボタンと DIP スイッチを介してデータを渡します。必要に応じて LED 設定を覚えておくだけです。
module SEG_LED (key,sw,seg_led_1,seg_led_2);
input [3:0] key;
input [3:0] sw;
output [8:0] seg_led_1;
output [8:0] seg_led_2;
reg [8:0] seg [9:0];
initial
begin
seg[0]=9'h3f;
seg[1]=9'h06;
seg[2]=9'h5b;
seg[3]=9'h4f;
seg[4]=9'h66;
seg[5]=9'h6d;
seg[6]=9'h7d;
seg[7]=9'h07;
seg[8]=9'h7f;
seg[9]=9'h6f;
end
assign seg_led_1=seg[key];
assign seg_led_2=seg[sw];
endmodule
クロック分周
アルゴリズム
偶数周波数逓倍: 立ち上がりエッジまたは立ち下がりエッジの数を見て、0 N/2-1 のときに反転し、 N/2 N-1 のときに再度反転します。
奇数乗数:下図のような形状になります。
図のように周波数を5分周し、5サイクル入力、1サイクル出力します。したがって、たとえば、立ち上がりエッジから開始して 2 つの立ち上がりエッジを通過し、最後の立ち下がりエッジの後に反転するというように、2.5 サイクルごとに反転します。
このように、最後は立ち上がりと立ち下がりの半分なので、立ち上がりか立ち下がりだけで判断することはできません。ただし、純粋な立ち上がりエッジと立ち下がりエッジの波形 (立ち上がりエッジは立ち下がりエッジより 1 サイクル長い) を見ると、2 つの および 演算によって必要な結果が得られることがわかります。
達成
module div(clk_in, rst_n ,clk_out);
input clk_in,rst_n;
output clk_out;
parameter WIDTH=3;
parameter N=5;
reg [WIDTH-1:0] cnt_p, cnt_n;
reg clk_p, clk_n;
always @ (posedge clk_in or negedge rst_n)
begin
if(!rst_n)cnt_p<=0;
else if(cnt_p==(N-1))
cnt_p<=0;
else
cnt_p<=cnt_p+1;
end
always @ (negedge clk_in or negedge rst_n)
begin
if(!rst_n)cnt_n<=0;
else if(cnt_n==(N-1))
cnt_n<=0;
else
cnt_n<=cnt_n+1;
end
always @ (posedge clk_in or negedge rst_n)
begin
if(!rst_n)clk_p<=0;
else if(cnt_p<(N>>1))
clk_p<=0;
else
clk_p<=1;
end
always @ (negedge clk_in or negedge rst_n)
begin
if(!rst_n)clk_n<=0;
else if(cnt_n<(N>>1))
clk_n<=0;
else
clk_n<=1;
end
assign clk_out=(N==1)?clk_in:(N[0])?clk_p&clk_n:clk_p;
endmodule
エミュレーションを開けない場合は、ライセンス エラーが表示されます。エミュレーションとライセンスのバージョンが間違っている可能性があります。
水ランプ
モジュールの考え方を採用します。先ほどの3-8デコーダ(led)部分とクロック分周部分のコードを組み合わせて、モジュールを使って入出力の転送を行います。
module flashled (clk,rst,led);
input clk,rst;
output [7:0]led;
reg [2:0] cnt;
wire clk1h;
LED u1 (
.key(cnt),
.led(led)
);
div #(.WIDTH(32),.N(12000000)) u2 (
.clk_in(clk),
.rst_n(rst),
.clk_out(clk1h)
);
always @(posedge clk1h or negedge rst)
if(!rst)
cnt<=0;
else
cnt<=cnt+1;
endmodule
簡単な LED 割り当て方法もあります。LED の形状は 1111 1101 のようなものであることがわかります。つまり、8 ビットが連続的に循環して流水ランプの効果を形成します。したがって、LED を連続サイクルで左または右に移動させるだけで済みます。
led={led[0],led[7:1]};
キーのデバウンス
ボタンを押すだけで LED ライトのステータスを切り替えるだけなら、次のように書くのは非常に簡単です。
module debounce (key,rst,key_pulse);
input key,rst;
output key_pulse;
reg key_pulse;
always @(negedge rst or negedge key)
begin
if(!rst)
key_pulse=1;
else
key_pulse=~key_pulse;
end
endmodule
最初のボタンとして 1 つのボタンを選択し、LED の入力として 1 つのボタンを選択します。
しかし、典型的な問題は、ボタンを押したり放したりすると、ボタンのレベルが常に揺れて不安定になることです。
ここでは(立ち下がりエッジトリガ)を例に挙げます。解決策は次のとおりです。最初の立ち下がりエッジを検出した後、最初に 10ms を超える遅延を発生させ、次にボタンがまだ低いレベルにあるかどうかを確認します。低いレベルにある場合は、実際に押されていることを意味します。そうでない場合は、単にわずかなレベルである可能性があります。ジッター。
module debounce (clk,key,rst,key_pulse);
input key;
input rst;
input clk;
output key_pulse;
reg key_rst_pre;
reg key_rst;
reg [17:0] cnt;
wire key_edge;
always @(posedge clk or negedge rst)
begin
if(!rst) begin
key_rst_pre<=1'b1;
key_rst<=1'b1;
end
else begin
key_rst<=key;
key_rst_pre<=key_rst;
end
end
assign key_edge=key_rst_pre&~(key_rst);
reg key_sec_pre,key_sec;
always @(posedge clk or negedge rst)
begin
if(!rst)
cnt<=18'h0;
else if(key_edge)
cnt<=18'b0;
else
cnt<=cnt+1'h1;
end
always @(posedge clk or negedge rst)
begin
if(!rst)
key_sec<=1'b1;
else if(cnt==18'h3ffff)
key_sec<=key;
end
always @(posedge clk or negedge rst)
begin
if(!rst)
key_sec_pre<=1'b1;
else
key_sec_pre<=key_sec;
end
assign key_pulse=key_sec_pre&~(key_sec);
endmodule
key_edge は立ち下がりエッジで 1 に設定され、初期状態の立ち上がりエッジから一定時間以内に新たな立ち下がりエッジがなく (ジッターなし)、key_edge がまだ立ち下がっている場合にのみパルス情報が 1 になります。 。
パルスの処理部分は非常に単純で、パルスが 1 の場合は LED が反転し、それ以外の場合は LED が維持されます。
module top (clk,key,led,rst);
input clk;
input key;
input rst;
output reg led;
wire key_pulse;
debounce u1 (
.clk(clk),
.key(key),
.rst(rst),
.key_pulse(key_pulse)
);
always @ (posedge clk or negedge rst)
begin
if(!rst)
led<=1'b1;
else if(key_pulse)
led<=~led;
else
led<=led;
end
endmodule
ここを書いているときに非常に興味深いバグがあります。cnt count ++ の部分を省略したため、ピン マップ定義を生成するときに、clk キーの最初のピンはすべて未接続のピンです。それを確認すると、「コンパイラはこれらのいくつかのピンを考慮している」と表示されます。入力は出力に影響を与えず、自動的に無視されます。」
タイミング制御
要件: バスケットボールの 24 秒計測ソフトウェアを作成します。必須:
- 電源を入れると、カウントダウンが 24 から 0 まで始まり、停止します。
- rst を押すと 24 に戻ります。
- ボタンを押すと現在のタイミングが一時停止され、もう一度押すとタイミングが再開されます。
主なことは、クロック分周モジュール (パラメータ設定間隔は 1s) とボタン モジュール (先ほど使用したデバウンスを使用) を使用するモジュールを作成することです。デジタル管ディスプレイと LED ディスプレイの両方をこのモジュールで完成させることができます。
書いていたら、always代入構文の仕様を見つけました:変数代入は複数のalwaysで代入してはいけない、エラーが報告されるので、always 1つに集中するようにしてください。
module BasketballBoard (key, clk, rst, seg_led_1,seg_led_2,led);
input key, clk, rst;
output [8:0] seg_led_1;
output [8:0] seg_led_2;
output reg [7:0] led;
reg [8:0] seg [9:0];
reg [4:0] SED_LED_H;
reg [4:0] SED_LED_L;
wire key_pulse;
wire clk1h;
reg stop_flag=0;
reg rst_flag=0;
initial
begin
seg[0]=9'h3f;
seg[1]=9'h06;
seg[2]=9'h5b;
seg[3]=9'h4f;
seg[4]=9'h66;
seg[5]=9'h6d;
seg[6]=9'h7d;
seg[7]=9'h07;
seg[8]=9'h7f;
seg[9]=9'h6f;
end
always @ (posedge key_pulse)
if(rst)
rst_flag<=~rst_flag;
debounce u1 (
.clk(clk),
.rst(rst),
.key(key),
.key_pulse(key_pulse)
);
div #(.WIDTH(32),.N(12000000)) u2 (
.clk_in(clk),
.rst_n(rst),
.clk_out(clk1h)
);
always @ (posedge clk1h or negedge rst)
begin
if(!rst)
begin
SED_LED_H<=4'd2;
SED_LED_L<=4'd4;
stop_flag<=0;
end
else if((!rst_flag)&(!stop_flag))
begin
led<=8'b11111111;
if(SED_LED_L)
SED_LED_L<=SED_LED_L-4'd1;
else if(SED_LED_H) begin
SED_LED_L<=4'd9;
SED_LED_H<=SED_LED_H-4'd1;
end
if((!SED_LED_H)&(!SED_LED_L))
stop_flag<=1;
end
else
led<=8'b00000000;
end
assign seg_led_1=seg[SED_LED_H];
assign seg_led_2=seg[SED_LED_L];
endmodule
LED seg key rst clk これらは以前と同じピン設定です。先ほどの div モジュールと key モジュールを追加するだけです。
プレゼンテーション効果: タイミングが終了して一時停止ボタンが押されると、LED ライトがすべて点灯し、時計の一時停止を表します。
LEDウォーターランプ
PWM原理。デューティ サイクルを変化させることで、時間の経過とともに明るさが変化する LED。
たとえば、各サイクルの長さは 100 で、最初のサイクルは高レベル 0 低レベル 100 を設定し、2 番目のサイクルは高レベル 10 低レベル 90 を設定し、3 番目のサイクルは高レベル 20 低レベル 80 を設定します...
つまり、2 つのタイマーが必要です。1 つ目は、0 ~ 期間 -1 のサイクルを次々と正直に記録することです。
一方では、最初のタイマーがサイクルを終了すると、2 番目のタイマーのデューティ サイクルが変更されます。たとえば、上記の例では、各サイクルの終わりのハイ レベル部分は +10 です。
一方、第 2 のタイマーは第 1 のタイマーと比較されます。たとえば、現在の第 2 タイマーは 20、つまり 20 ハイレベル、80 ローレベルであり、タイマー 1<タイマー 2 の場合はハイレベルに設定され、タイマー 1>タイマー 2 の場合はローレベルに設定されます。
module WaterLED(clk,rst,led);
input clk, rst;
output led;
reg [13:0] cnt1;
reg [13:0] cnt2;
parameter CNT_NUM=2400;
reg flag=0;
always @ (posedge clk or negedge rst)
begin
if(!rst)
cnt1<=13'b0;
else if(cnt1==CNT_NUM-1) begin
cnt1<=13'b0;
if(flag==0)
if(cnt2>=CNT_NUM) begin
flag<=1'b1;
end
else cnt2<=cnt2+13'b1;
else if(flag==1)
if(cnt2<=0) begin
flag<=1'b0;
end
else cnt2<=cnt2-13'b1;
end
else cnt1<=cnt1+13'b1;
end
assign led=cnt1<cnt2?1:0;
endmodule
コードは難しくありません。
信号機
2 つの 3 色のモジュールはそれぞれ明るく、幹線道路と分岐道路の信号機を示します。
幹線道路:緑15、黄3、赤10(単位:秒)。
枝: 緑 7 本、黄色 3 本、赤 18 本。
概念: ステート マシン、抽象モデル。オブジェクトには、状態 (この例では 2 つの信号機の 3 つの状態など)、イベント (カウントを許可したり、状態の色を切り替えたりするなど、外界によって課されたイベント)、動作 (アクション) があります。 、信号機はイベント アクション (色の切断など)、状態の切り替え (赤信号、青信号、黄色信号の間の 3 つの状態の遷移など) に従って自動的に実行されます。
最初は書きやすかったです。幹線道路分岐の状態テーブルを格納するのは 2 つの配列だけです。
module TrafficLED (clk,rst,LED_1,LED_2);
input clk,rst;
output reg [2:0] LED_1;
output reg [2:0] LED_2;
reg [4:0] state_1 [2:0];
reg [4:0] state_2 [2:0];
reg [2:0] color_state [2:0];
reg [2:0] cur_state_1;
reg [2:0] cur_state_2;
reg [4:0] cur_time_1;
reg [4:0] cur_time_2;
wire clk1h;
initial begin
state_1[0]=5'd14;
state_1[1]=5'd2;
state_1[2]=5'd9;
state_2[0]=5'd6;
state_2[1]=5'd2;
state_2[2]=5'd17;
color_state[0]=3'b101;
color_state[1]=3'b100;
color_state[2]=3'b110;
cur_time_1=5'd14;
cur_time_2=5'd6;
end
div #(.WIDTH(32),.N(12000000)) u2 (
.clk_in(clk),
.rst_n(rst),
.clk_out(clk1h)
);
always @ (posedge clk1h or negedge rst) begin
if(!rst) begin
cur_state_1<=0;
cur_state_2<=0;
cur_time_1<=5'd14;
cur_time_2<=5'd6;
LED_1<=color_state[cur_state_1];
LED_2<=color_state[cur_state_2];
end
else begin
if(cur_time_1)cur_time_1<=cur_time_1-5'b1;
else begin
if(cur_state_1<2)cur_state_1<=(cur_state_1+1);
else cur_state_1<=0;
cur_time_1<=state_1[cur_state_1+1];
end
if(cur_time_2)cur_time_2<=cur_time_2-5'b1;
else begin
if(cur_state_2<2)cur_state_2<=(cur_state_2+1);
else cur_state_2<=0;
cur_time_2<=state_2[cur_state_2+1];
end
LED_1<=color_state[cur_state_1];
LED_2<=color_state[cur_state_2];
end
end
endmodule
注意点は 2 つあり、どちらも fpga のノンブロッキング代入に関連しています。
<= を使用して always に値を代入すると、always の終了時にすべての変数が同時に代入されることは以前からわかっていました。最初のタイプミスは次のように書かれています。
if(cur_time_1)cur_time_1<=cur_time_1-5'b1;
else begin
if(cur_state_1<2)cur_state_1<=(cur_state_1+1);
else cur_state_1<=0;
cur_time_1<=state_1[cur_state_1];//这里有区别
end
if(cur_time_2)cur_time_2<=cur_time_2-5'b1;
else begin
if(cur_state_2<2)cur_state_2<=(cur_state_2+1);
else cur_state_2<=0;
cur_time_2<=state_2[cur_state_2];//这里有区别
end
LED_1<=color_state[cur_state_1];
LED_2<=color_state[cur_state_2];
なぜなら、私が思うのは、cur_state+1以降、cur_stateの現在の状態、つまり+1が経過した後の状態に切り替わるということです。
実際にはそうではなく、2 つの命令が同時に実行される、つまり、state[cur_state] はまだ +1 されていない状態にあります。
そこで、state[cur_state+1]に変更しました。
2 番目のエラーは次のとおりです。
if(cur_time_1)cur_time_1<=cur_time_1-5'b1;
else begin
cur_state_1<=(cur_state_1+1);//这里有区别
if(cur_state_1==3)cur_state_1<=0;//这里有区别
cur_time_1<=state_1[cur_state_1+1];
end
if(cur_time_2)cur_time_2<=cur_time_2-5'b1;
else begin
cur_state_2<=(cur_state_2+1);//这里有区别
if(cur_state_2==3)cur_state_2<=0;//这里有区别
cur_time_2<=state_2[cur_state_2+1];
end
LED_1<=color_state[cur_state_1];
LED_2<=color_state[cur_state_2];
やりたいことは逐次実行して、まず+1して配列範囲(0,1,2)を超えているかどうかを判定し、超えたら0に戻すことです。
ただし、LED の割り当てと cur_state+1 は同時に実行されます。つまり、私の cur_state は 1 秒間 3 であり、LED は配列 3 の添え字の状態も取得しようとします。
したがって、単純に値を代入する場合は、値が 2 の場合は直接 0 に戻し、それ以外の場合は ++ を返します。このようにして、さらなる判断は必要ありません。