送信側の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行目にはステートマシンが記述されています。

UART_TB_Rx_State

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 記述はサポートされていないようです。

データの受信

UART_bit_period

データの受信には データの変化点の前後にそれぞれマージン (bit_period_margin) を設けています。

このマージンの期間で信号が変化することは期待通りで構わないことにし、マージン以外の期間で信号が変化することは違反であるとして、この違反を検出して ##ERROR## をレポートするようにしています。

この違反の検出には、START bit の検出と同様に fork 〜 join 文を使っています。

UART はクロックの受け渡しのない 1線式の通信です。受信側は 1-bit の期間のどの位置でデータを受け取るかは 分かりません。真ん中の 1ポイントだけで判定する受信装置かも知れませんし、真ん中付近の 3ポイントを多数決によって 0 or 1 決定する方法でノイズの影響を少なくしようとする受信装置かも知れません。

また、送信装置のクロック速度と受信側のクロック速度は 電源電圧、温度、プロセスなどの条件により、完全には一致していません。そのため、受信装置のクロックを基準に見た場合、送信されてくる UARTの信号は緩やかにずれていきます。

そのような理由から、データは設定された bit_period_margin の期間を除いた期間で判定する Testbench にしました。



海外発激安ガジェットショップ!送料無料!【DX.com】
TITLE

自己紹介

50才になる半導体エンジニアです。
大学で電子電気工学を学び、1990年にその分野のまま就職。ASICやマイコンの設計を長く続けてきましたが20年も同じ分野にいると業態も衰退したり変化するもので退職し、今は外資のIT系会社に再就職して設計請負業をやっております。
お問い合わせは
nakata.xianzhi@outlook.com







Linux と 小ネタ

デジタル回路設計

海外駐在後記