Trung tâm đào tạo thiết kế vi mạch Semicon


  • ĐĂNG KÝ TÀI KHOẢN ĐỂ TRUY CẬP NHIỀU TÀI LIỆU HƠN!
  • Create an account
    *
    *
    *
    *
    *
    Fields marked with an asterisk (*) are required.
wafer.jpg

Thiết kế UART Receiver sử dụng FSM (Verilog code)

E-mail Print PDF

Làm thế nào để thiết kế một module phần cứng có thể nhận data theo chuẩn UART ?

Tìm hiểu đối tượng thiết kế:

Trước hết, thử tìm hiểu chuẩn UART là gì?

Từ google, ta có thể tìm thấy được các tài liệu về chuẩn bus này. Có bạn có thể chon bất kỳ nguồn nào cũng được vì chuẩn này rất thông dụng.

 

Ví dụ, Wikipedia có đoạn mô tả về chuẩn UART như sau:

 

Tóm tắt các thông tin yêu cầu của thiết kế và các ý tưởng thiết kế sơ khai.

Để thiết kế được phần cứng nhận theo chuẩn UART, các bạn cần 1 số lưu ý sau:

1.Baudrate: Là tốc độ bit được truyền, tính bằng số bit/giây. Giá trị này chỉ đúng khi xét ở một lần truyền, với 1 byte data tương ứng.

2.Tín hiệu clock: Để xử lý nhận dữ liệu thông qua chân RX, chúng ta phải sử dụng clock nội ( internal clock ). Clock nội này có tần số phải đủ lớn để có thể lấy mẫu được RX đồng thời có thể phát hiện được sự thay đổi mức của RX. Thông thường, các board FPGA đều có nguồn clock trên chip từ 50Mhz - 100Mhz. Chúng ta có thể sử dụng trực tiếp nguồn clock này.

3.Số lượng bit trong 1 lần truyền: Thông thường, UART có các 3 thông tin cần phân biệt: Baudrate, Stop bit, Parity bit. Tùy theo nhu cầu, chuẩn UART có dạng như sau:

- START BIT - 8BIT DATA - PARITY BIT - STOP BIT: Tổng cộng sẽ có 11bits trong 1 lần truyền.

- START BIT - 8BIT DATA - STOP BIT: Tổng cộng sẽ có 10bits trong 1 lần truyền

- START BIT - 8BIT DATA: Tổng cộng sẽ có 9bits trong 1 lần truyền.

4. FIFO lưu dữ liệu: Vì UART truyền đi 1 byte với các bit là nối tiếp nhau, do đó, để lưu lại và sử dụng data 8bits sau khi kết thúc, chúng ta cần có 1 bộ đệm để lưu các bits dữ liệu. Người ta gọi đó là Buffer, hoặc FIFO, hoặc đơn giản là Registers.

5. State Machine: Đối với những khối logic có chức năng và hoạt động mang tính chu trình, lặp đi lặp lại và đi qua các trạng thái cố định, chúng ta thường sử dụng State Machine. Logic của State Machine này sẽ được nói đến chi tiết ở phần sau.

Sau đây, chúng ta tìm hiểu thiết kế 1 module nhận - UART Receiver có đặc điểm:

- 1 bit Start, 1 bit Stop, 8 Bit data, 0 Parity

- Baudrate 9600

- Internal clock 100Mhz

- Bộ đệm nhận giá trị ( Receiver FIFO) 8Bit.

// Code:

// counter of baudrate

always @ ( posedge clock ) begin

    if ( resetn == 1'b0 ) begin

         counter <= 14'd0;

    end

    else begin

       if ( state != `IDLE) begin

           if ( counter_end == 1'b1 ) counter <= 14'd0;

           else                       counter <= counter + 14'd1;

       end

    end

end

// rx data counter

always @ ( posedge clock ) begin

    if ( resetn == 1'b0 )        data_cnt <= 3'b000;

    else if ((state == `DATA) && (counter_end == 1'b1)) data_cnt <= data_cnt + 3'd1;

end

Triển khai mô tả đầy đủ của phần cứng dạng Block Diagram

Sơ đồ State Machine:

Sử dụng state machine với 4 State. IDLE, START, DATA, STOP. Do đó, cần 1 register 2 bits để có thể mã hóa được 4 states trên. Trong đó, START và STOP kéo dài trong 1 thời gian bit theo Baudrate. DATA kéo dài trong 8 thời gian bit theo Baudrate. IDLE thì ko có thời hằng, đó là trạng thái tồn tại khi không có quá trình nhận được yêu cầu từ tín hiệu RX.

 

State Machine UART Receiver

Timing Counter:

_timing counter: dùng để đếm số lượng xung internal clock để cho ra thời gian bit theo Baudrate. Ví dụ, đối với Baudrate 9600, số lượng bit trong 1 giây là 9600, tương ứng với tần số 9600Hz, hay 1 chu kỳ bit là 1/9600 =  1.04167e-4 (s) = 0.104167 (ms) = 1041.67(us) = 1041670 (ns).

Tần số của Internal clock là 100Mhz, tương ứng với chu kỳ là 10(ns). Vậy để tạo ra chu kỳ bits theo Baudrate 9600 thì timing counter theo internal clock phải đếm đến giá trị 1041670/10 = 104167.

Giá trị 104167 trong thập phân tương ứng với 13bit nhị phân: 13'b1_1001_0110_1110_0111. Do đó, timing counter phải có bitwidth là 13.

Data Bit counter: Data Bit counter có 2 tác dụng:

- Để tạo điều kiện dừng cho State Machine trong state DATA

- Tạo chỉ số để lưu giá trị nhận vào FIFO.

Giá trị của Data Bit Counter tối đa là 7, vì số lượng bit data là 8 và chỉ số bắt đầu là từ "0".

Do đó, Data Bit Counter chỉ cần dùng 3 bits là đủ.

Received Data FIFO:

 

Dạng sóng của UART

Như hình vẽ về waveform của UART, data có thể được chốt tại mỗi khoảng giữa của thời gian bits. Có nghĩa là khoảng thời gian khi timing counter được 1/2 giá trị. Tại thời điểm đó, tín hiệu DATA đã ổn định, sẽ được lưu vào FIFO. Theo Baudrate 9600, giá trị counter để lưu dữ liệu là 104167/2. Để có thể lấy được số nguyên, ta chấp nhận 52083. Thực chất, đây là một thanh ghi ( FlipFlop) 8bits.

Đồng bộ tín hiệu RX:

Để có thể chuyển từ IDLE sang START cần có 1 tín hiệu báo hiệu sự sườn xuống của tín hiệu RX. Để làm điều này, trước tiên, chúng ta phải đồng bộ tín hiệu RX về internal clock. Việc đồng bộ này nhằm tránh hiện tượng không ổn định của tín hiệu do sự sai lệch về pha của RX và Internal clock. Mạch đồng bộ thường gồm 2 FlipFlop nối nối tiếp nhau, được cấp clock là internal.

 

Mạch đồng bộ RX

Phát hiện sự kiện START

Để bắt được sườn xuống của RX làm sự kiện Start, chúng ta dùng mạch bắt cạnh xuống. Tín hiêu ra sẽ báo hiệu thời điểm bắt đầu của Bit START.

 

Mạch phát hiện sườn xuống của RX

Tín hiệu start sẽ được nối vào Logic của state. Tuy nhiên, để chuyển state đầy đủ thì chúng ta cần các tín hiệu khác nữa, bao gồm Timing Counter và Data Bit counter.

// Code:

// synchronous ff for rx

always @ ( posedge clock ) begin

 if ( resetn == 1'b0 ) begin

  syncff0 <= 1'b1;

  syncff1 <= 1'b1;

 end

 else begin

  syncff0 <= rx;

  syncff1 <= syncff0;

 end

end

// start detected

always @ ( posedge clock ) begin

 if ( resetn == 1'b0 )  rx_ <= 1'b1;

 else   rx_ <= syncff1;

end

// start

assign start = ~syncff1 & rx_;

 

Phần điều khiển của State Machine

// Code:

wire counter_end, start, start_end, data_end, stop_end, get_dat;

assign get_dat      = (counter == `CNT_HALF); // timing of getting serial data

assign counter_end  = (counter == `CNT);  // end timing of baud period

assign start_end    = (state   == `START) && (counter_end == 1'b1); //

assign data_end     = (state   == `DATA)  && (counter_end == 1'b1) && (data_cnt == 3'd7);

assign stop_end     = (state   == `STOP)  && (counter_end == 1'b1);

// state

always @ ( posedge clock ) begin

 if ( resetn == 1'b0 )  state <= `IDLE;

 else                   state <= next;

end

// next

always @ (*) begin

 case ( state )

  `IDLE:  if ( start == 1'b1 )            next = `START;

                else                                    next = `IDLE;

  `START: if ( start_end == 1'b1 )   next = `DATA;

                 else                                   next = `START;

  `DATA:  if ( data_end == 1'b1 )    next = `STOP;

                   else                                   next = `DATA;

  `STOP:  if ( stop_end == 1'b1 )      next = `IDLE;

                 else                                    next = `STOP;

  default:                                              next = `IDLE;

 endcase

end

Điều khiển thông qua giá trị nhận của Reveiver:

Nếu chỉ nhận giá trị của RX mà không sử dụng thì cũng không có ý nghĩa gì. Để áp dụng một số hoạt động điều khiển vào mạch nhận, chúng ta cần thêm một vài logic để điều khiển. Về cơ bản, có thể dùng chính giá trị của RX data nhận được và mã hóa nó thành giá trị điều khiển. Giá trị data này là 8 bits, do đó, về lý thuyết thì có thể mã hóa được 2^8 = 256 lệnh. Sau đây là sơ đồ khối cho chức năng này:

 

Các ứng dụng điều khiển thông qua giá trị dữ liệu nhận được.

Ở ví dụ của code này, sẽ thực hiện 3 hành động. Bật tắt LED FIX và LED TOUCH.

// rx data

always @ ( posedge clock ) begin

    if ( resetn == 1'b0 ) begin

      rdat <= 8'h00;

    end

    else begin

       if ((state == `DATA) && (get_dat == 1'b1))  rdat <= {syncff1, rdat[7:1]};

       else           rdat <= rdat;

    end

end

// output decode

always @ (posedge clock) begin

 if ( resetn == 1'b0 ) begin

      led_fix  <= 1'b0;

      led_touch  <= 1'b0;

 end

 else begin

    if ( stop_end == 1'b1 ) begin

      case (rdat)

        8'h61:  begin // button1 push

                    led_fix   <= ~led_fix;

                end

        8'h62:  begin // button2 down

                    led_touch  <= 1'b1;

                end

        8'h63:  begin // button2 up

                    led_touch  <= 1'b0;

                end

        default: begin

             led_fix    <= 1'b0;

             led_touch  <= 1'b0;

        end

     endcase

   end

 end

end

Tổng hơp:

Từ tất cả các phần bên trên, ta có một phần cứng nhận UART như sau:

 

Các bạn có thể dùng ý tưởng này để coding và thiết kế cho hệ thống có hỗ trợ chuẩn truyền UART. Các module thông dụng như GPS, Camera, BlueTooth cũng sử dụng chuẩn UART để giao tiếp.

//Full code:

`timescale 1ns / 1ps

//////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////

`define CNT       10416

`define CNT_HALF  5028

`define IDLE      2'b00

`define START     2'b01

`define DATA      2'b10

`define STOP      2'b11

 

module uart_rx_top(

 input wire clock,

 input wire resetn,

 input wire rx,

 //output wire tx

 output reg led_fix,

 output reg led_touch

    );

 // baudrate 9600 counter.

reg [13:0] counter;

reg syncff0, syncff1;

reg rx_;

reg [1:0] state, next;

reg [7:0] rdat;

reg [2:0] data_cnt;

wire counter_end, start, start_end, data_end, stop_end, get_dat;

assign get_dat      = (counter == `CNT_HALF); // timing of getting serial data

assign counter_end  = (counter == `CNT);  // end timing of baud period

assign start_end    = (state   == `START) && (counter_end == 1'b1); //

assign data_end     = (state   == `DATA)  && (counter_end == 1'b1) && (data_cnt == 3'd7);

assign stop_end     = (state   == `STOP)  && (counter_end == 1'b1);

// synchronous ff for rx

always @ ( posedge clock ) begin

 if ( resetn == 1'b0 ) begin

  syncff0 <= 1'b1;

  syncff1 <= 1'b1;

 end

 else begin

  syncff0 <= rx;

  syncff1 <= syncff0;

 end

end

// Company: DegicLab

// Engineer:  Admin

// Website: degiclab.blogspot.com

// start detected

always @ ( posedge clock ) begin

 if ( resetn == 1'b0 )  rx_ <= 1'b1;

 else     rx_ <= syncff1;

end

// start

assign start = ~syncff1 & rx_;

// state

always @ ( posedge clock ) begin

 if ( resetn == 1'b0 )  state <= `IDLE;

 else      state <= next;

end

// next

always @ (*) begin

 case ( state )

  `IDLE:  if ( start == 1'b1 )     next = `START;

          else                     next = `IDLE;

  `START: if ( start_end == 1'b1 ) next = `DATA;

          else                     next = `START;

  `DATA:  if ( data_end == 1'b1 )  next = `STOP;

          else                     next = `DATA;

  `STOP:  if ( stop_end == 1'b1 )  next = `IDLE;

          else                     next = `STOP;

  default:                         next = `IDLE;

 endcase

end

// Company: DegicLab

// Engineer:  Admin

// Website: degiclab.blogspot.com

// counter of baudrate

always @ ( posedge clock ) begin

 if ( resetn == 1'b0 ) begin

  counter <= 14'd0;

 end

 else begin

  if ( state != `IDLE) begin

   if ( counter_end == 1'b1 ) counter <= 14'd0;

   else                       counter <= counter + 14'd1;

  end

 end

end

// rx data counter

always @ ( posedge clock ) begin

 if ( resetn == 1'b0 )        data_cnt <= 3'b000;

 else if ((state == `DATA) && (counter_end == 1'b1)) data_cnt <= data_cnt + 3'd1;

end

// rx data

always @ ( posedge clock ) begin

 if ( resetn == 1'b0 ) begin

    rdat <= 8'h00;

 end

 else begin

  if ((state == `DATA) && (get_dat == 1'b1))  rdat <= {syncff1, rdat[7:1]};

  else                                        rdat <= rdat;

 end

end

// output decode

always @ (posedge clock) begin

 if ( resetn == 1'b0 ) begin

  led_fix    <= 1'b0;

  led_touch  <= 1'b0;

 end

 else begin

  if ( stop_end == 1'b1 ) begin

   case (rdat)

    8'h55:  begin // button1 push

       led_fix   <= ~led_fix;

      end

    8'h62:  begin // button2 down

       led_touch  <= 1'b1;

      end

    8'h63:  begin // button2 up

       led_touch  <= 1'b0;

      end

    default: begin

       led_fix    <= 1'b0;

       led_touch  <= 1'b0;

      end

   endcase

  end

 end

end

endmodule

 

Testbench:

`timescale 1ns / 1ps

//////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////

module testbench();

reg clock, resetn, rx;

wire led_fix, led_touch;

integer i;

uart_rx_top dut (

 .clock     ( clock     ),

 .resetn    ( resetn    ),

 .rx        ( rx        ),

 .led_fix   ( led_fix   ),

 .led_touch ( led_touch )

);

initial begin

 clock  = 1'b0;

 resetn = 1'b1;

 rx     = 1'b1;

end

always begin

 #5; clock = ~ clock;

end

// DegicLab Group

// Engineer:  Admin

// Website:   https://degiclab.blogspot.com/

// Facebook:  https://www.facebook.com/DegicLab/

initial begin

 #100; resetn = 1'b0;

 #100; resetn = 1'b1;

 #10000; rx     = 1'b0; // Start

 // baudrate at 9600.

 // time unit is in (ns)

 for ( i = 0; i <= 7; i = i + 1) begin

  #104170; rx = ~rx;

 end

 #104170; rx = 1'b1; // stop

 #1000;

 $finish;

end

endmodule

Waveform:

 

Nguồn: degiclab.blogspot.com

Bạn Có Đam Mê Với Vi Mạch hay Nhúng      -     Bạn Muốn Trau Dồi Thêm Kĩ Năng

Mong Muốn Có Thêm Cơ Hội Trong Công Việc

    Và Trở Thành Một Người Có Giá Trị Hơn

Bạn Chưa Biết Phương Thức Nào Nhanh Chóng Để Đạt Được Chúng

Hãy Để Chúng Tôi Hỗ Trợ Cho Bạn. SEMICON  

 

Hotline: 0972.800.931 - 0938.838.404 (Mr Long)

 

 

Last Updated ( Friday, 13 September 2019 20:49 )  

Related Articles

Chat Zalo