送信側のTestBenchが出来たので、受信側を設計してみます。
受信側は、さまざまな検証のためにチェック機構が必要になります。受信したデータを期待値と比較して一致しているか、プロトコル違反はなかったか などなど。
UART受信側の Testbench module の全体は以下のコードとなります。
1 `ifndef non_parity
2 `define non_parity 2'b00
3 `endif
4
5 `ifndef odd_parity
6 `define odd_parity 2'b01
7 `endif
8
9 `ifndef even_parity
10 `define even_parity 2'b10
11 `endif
12
13 `timescale 1ns/1ps
14
15 module TB_UART_Rx
16 (/*AUTOARG*/
17 // Inputs
18 UART_rx
19 );
20
21 input UART_rx;
22
23 time bit_period = 1000;
24 time bit_period_margin = 30; // 3%
25 integer data_length = 8; // data length: 8-bit
26 integer stop_length = 2;
27 reg [1:0] parity_mode = `odd_parity;
28 reg LSB_first = 0;
29
20 ////////////////////////////////////////////////////////////
31 // Do not touch below variables
32
33 reg [127:0] data = 'bx;
34 reg parity = 1'bx;
35
36 ////////////////////////////////////////////////////////////
37 // margin in that period signal transition is not verified
38
39 always @(bit_period) begin
30 bit_period_margin = bit_period * 0.03;
41 end
42
43 ////////////////////////////////////////////////////////////
44 // Statemachine controls procedure
45
46 parameter IDLE = 3'h0;
47 parameter START = 3'h1;
48 parameter RCV_DATA = 3'h2;
49 parameter RCV_PARITY = 3'h3;
40 parameter STOP = 3'h4;
51 reg [2:0] STATE = IDLE;
52 event activate;
53 event deactivate;
54 event STATE_move_next;
55
56 always @(activate) begin
57 STATE = START;
58 end
59
50 always @(deactivate) begin
61 STATE = IDLE;
62 end
63
64 always @(STATE_move_next) begin
65 case (STATE)
66 START : begin
67 STATE = RCV_DATA;
68 end
69 RCV_DATA : begin
60 if ((parity_mode === `odd_parity) ||
71 (parity_mode === `even_parity)) begin
72 STATE = RCV_PARITY;
73 end
74 else begin
75 STATE = STOP;
76 end
77 end
78 RCV_PARITY : begin
79 STATE = STOP;
70 end
81 STOP : begin
82 STATE = START;
83 end
84 endcase
85 end
86
87 ////////////////////////////////////////////////////////////
88 // If the START bit period is less than expected,
89 // this statement generates error.
80 // If the START bit period is longer than expected,
91 // this statement let State Machine move to next.
92
93 time START_negedge_time;
94 time START_posedge_time;
95 time START_period;
97
98 always @(negedge UART_rx) begin
99 if (STATE == START) begin
100 if (UART_rx === 1'b0) begin
101 START_negedge_time = $time;
102 fork : start_length_check
103 begin
104 wait(UART_rx === 1'b1);
105 disable start_length_check;
106 end
107 begin
108 #(bit_period+0.01);
109 disable start_length_check;
110 end
111 join
112 START_posedge_time = $time;
113 START_period = START_posedge_time - START_negedge_time;
114 if (START_period < (bit_period - bit_period_margin)) begin
115 $display("%t ##ERROR## %m - Shorter START-bit was detected. The period was %0t", $time, START_period);
116 end
117 else begin
118 -> STATE_move_next;
119 end
120 end
121 end
122 end
123
124 ////////////////////////////////////////////////////////////
125 // Store received data
126
127 event rcv_bit_start;
128 event rcv_bit_end;
129 integer rcv_bit_num = 0;
130
131 always @(STATE) begin
132 rcv_bit_num = 0;
133 if (STATE === RCV_DATA) begin
134 #(bit_period_margin);
135 -> rcv_bit_start;
136 end
137 end
138
139 always @(rcv_bit_start) begin
140 if (STATE === RCV_DATA) begin
141 #(bit_period-(2*bit_period_margin));
142 -> rcv_bit_end;
143 #(bit_period_margin);
144 if (LSB_first === 1) begin
145 rcv_bit_num = rcv_bit_num + 1;
146 end
147 else begin
148 rcv_bit_num = rcv_bit_num - 1;
149 end
150 end
151 end
152
153 always @(rcv_bit_end) begin
154 if (STATE === RCV_DATA) begin
155 #(2*bit_period_margin);
156 -> rcv_bit_start;
157 end
158 end
159
160 always @(rcv_bit_start) begin
161 if (STATE === RCV_DATA) begin
162 fork : rcv_data_bit_check
163 begin
164 @(UART_rx);
165 $display("%t ##ERROR## %m - During DATA bit period, signal is toggled", $time);
166 -> activate;
167 disable rcv_data_bit_check;
168 end
169 begin
170 @(rcv_bit_end);
171 data[rcv_bit_num] = UART_rx;
172 disable rcv_data_bit_check;
173 end
174 join
175 end
176 end
177
178 always @(rcv_bit_end) begin
179 if (STATE === RCV_DATA) begin
180 #(bit_period_margin);
181 if (LSB_first === 1) begin
182 if (rcv_bit_num >= data_length) begin
183 -> STATE_move_next;
184 end
185 end
186 else begin
187 if (rcv_bit_num < 0) begin
188 -> STATE_move_next;
189 end
190 end
191 end
192 end
193
194 ////////////////////////////////////////////////////////////
195 // Parity
196
197 always @(STATE) begin
198 if (STATE === RCV_PARITY) begin
199 #(bit_period_margin);
200 -> rcv_bit_start;
201 #(bit_period-(2*bit_period_margin));
202 -> rcv_bit_end;
203 end
204 end
205
206 always @(STATE) begin
207 if (STATE === RCV_PARITY) begin
208 @(rcv_bit_sta
209 fork : rcv_parity_bit_check
210 begin
211 @(UART_rx);
212 $display("%t ##ERROR## %m - During PARITY bit period, signal is toggled", $time);
213 -> activate;
214 disable rcv_parity_bit_check;
215 end
216 begin
217 @(rcv_bit_end);
218 parity = UART_rx;
219 disable rcv_parity_bit_check;
220 end
221 join
222 end
223 end
224
225 always @(rcv_bit_start) begin
226 if (STATE === RCV_PARITY) begin
227 @(rcv_bit_end);
228 #(bit_period_margin);
229 -> STATE_move_next;
230 end
231 end
232
233 ////////////////////////////////////////////////////////////
234 // STOP
235
236 always @(STATE) begin
237 if (STATE === STOP) begin
238 #(bit_period_margin);
239 -> rcv_bit_start;
240 #(bit_period * stop_length);
241 -> rcv_bit_end;
242 end
243 end
244
245 always @(STATE) begin
246 if (STATE === STOP) begin
247 @(rcv_bit_start);
248 if (UART_rx !== 1'b1) begin
249 $display("%t ##ERROR## %m - In STOP state, signal is not High", $time);
250 end
251 fork : rcv_stop_bit_check
252 begin
253 @(UART_rx);
254 $display("%t ##ERROR## %m - During STOP bit period, signal is toggled", $time);
255 -> activate;
256 disable rcv_stop_bit_check;
257 end
258 begin
259 @(rcv_bit_end);
260 disable rcv_stop_bit_check;
261 end
262 join
263 end
264 end
265
266 reg parity_calc = 1'b0;
267
268 always @(rcv_bit_end) begin
269 if (STATE === STOP) begin
270 $write("%t ##INFO## %m - UART Received data ", $time);
271 parity_calc = 1'b0;
272 for (rcv_bit_num = data_length - 1; rcv_bit_num >= 0; rcv_bit_num = rcv_bit_num -1) begin
273 $write("%b", data[rcv_bit_num]);
274 parity_calc = parity_calc ^ data[rcv_bit_num];
275 end
276 $write("\n");
277 case (parity_mode)
278 `odd_parity : begin
279 if (parity !== parity_calc) begin
280 $display("%t ##ERROR## %m - PARITY error was detected", $time);
281 end
282 end
283 `even_parity : begin
284 if (parity !== ~parity_calc) begin
285 $display("%t ##ERROR## %m - PARITY error was detected", $time);
286 end
287 end
288 endcase
289 -> STATE_move_next;
290 end
291 end
292
293 endmodule // TB_UART_rx
上記の受信側テストベンチについて、各部の説明をします。
ステートマシン
43行目から86行目にはステートマシンが記述されています。
IDLE : イニシャル ステートです。このステートにある場合、TB_UART_Rx は何の処理も行いません。
START : このステートにあるとき、START bit の検出と START bit の Lowレベル 期間のチェックを行います。
DATA : このステートにあるとき、DATA の受信を行います。
PARITY : このステートにあるとき、PARITY bit の受信を行います。
STOP : このステートにあるとき、STOP bit が一定時間 Highレベル であることを確認します。
event activate がセットされることによって、ステートが START に移動して START bit を待ちます。
また、event deactivate がセットされると、ステートは IDLE に移動して動作を停止します。
START bit の検出
ステートが START にあるとき、START bit の検出を行います。
START bit であると検出する条件は、1-bit 分の期間 Lowレベル であることです。
以下に示すのが、START bit の判定を行っているコードになります。
UARTの入力信号 UART_rx が Lowレベル になった時間を記録し、次に Highレベル になる時間までの期間を計算で求め、マージン時間を設けても、十分な期間の Lowレベルであれば START bit であると判定します。
ただし、問題は、START bit に続く最初のデータが 0 である場合、START bit の期間を経過しても Lowレベル のままになってしまいます。
この問題を以下のコードでは fork 〜 join 文を使って解決しています。UART_rx が Highレベル になるのを待つのと同時に 1-bitの期間+0.01nsまで待ち、どちらか先に有効となったところで fork 〜 join から抜け出すため、fork についた ラベル を disable 文で指定しています。なお、disable 文はその ラベル のついた順序処理文の内側でなければなりません。
disable 文によって fork 〜 join 文が強制終了されると、fork 〜 join 文のなかのプロセスも終了されます。
100 if (UART_rx === 1'b0) begin
101 START_negedge_time = $time;
102 fork : start_length_check
103 begin
104 wait(UART_rx === 1'b1);
105 disable start_length_check;
106 end
107 begin
108 #(bit_period+0.01);
109 disable start_length_check;
110 end
111 join
112 START_posedge_time = $time;
113 START_period = START_posedge_time - START_negedge_time;
114 if (START_period < (bit_period - bit_period_margin)) begin
115 $display("%t ##ERROR## %m - Shorter START-bit was detected.
The period was %0t", $time, START_period);
116 end
117 else begin
118 -> STATE_move_next;
119 end
fork 〜 join_any というのが SystemVerilog で追加されて 上記の動作を disable 文なしで行えるようになりました。fork 〜 join_any の中の1つでもプロセスが終了すれば、fork 〜 join_any の外のプロセスに移動しますが、なかのプロセスが継続されます。そこがこれまでの disable 文で強制終了させる動作と異なるので注意が必要です。
fork 〜 join_any のなかのプロセスを終了させるためには disable fork; を記述します。上記の fork 〜 join 文を fork 〜 join_any で書き換えると以下のようになります。
102 fork
103 begin
104 wait(UART_rx === 1'b1);
105 end
106 begin
107 #(bit_period+0.01);
108 end
109 join_any
110 disable fork;
残念ながら、Icarus Verilog では この SystemVerilog 準拠の fork 〜 join_any 記述はサポートされていないようです。
データの受信
データの受信には データの変化点の前後にそれぞれマージン (bit_period_margin) を設けています。
このマージンの期間で信号が変化することは期待通りで構わないことにし、マージン以外の期間で信号が変化することは違反であるとして、この違反を検出して ##ERROR## をレポートするようにしています。
この違反の検出には、START bit の検出と同様に fork 〜 join 文を使っています。
UART はクロックの受け渡しのない 1線式の通信です。受信側は 1-bit の期間のどの位置でデータを受け取るかは 分かりません。真ん中の 1ポイントだけで判定する受信装置かも知れませんし、真ん中付近の 3ポイントを多数決によって 0 or 1 決定する方法でノイズの影響を少なくしようとする受信装置かも知れません。
また、送信装置のクロック速度と受信側のクロック速度は 電源電圧、温度、プロセスなどの条件により、完全には一致していません。そのため、受信装置のクロックを基準に見た場合、送信されてくる UARTの信号は緩やかにずれていきます。
そのような理由から、データは設定された bit_period_margin の期間を除いた期間で判定する Testbench にしました。
海外発激安ガジェットショップ!送料無料!【DX.com】
|