DE248 Home Page

DE248 Drive Electronics

RS-232 PHY TRANSMITTER MODULE

(Last Modified: 04 November 2010 06:09:06 PM )


NOTE: This page has not been finished yet.


Top Level Instantiation

rs232_phy_tx inst_name (.serial_out(rs232_rx, .busy(busy), .data_in(data_to_dte),

                        .send(send), .baud16(baud16), .rst(rst), .clk(clk));
 


User-level Description

The RS-232 PHY Transmitter initiates the transmission of a packet whenever it senses a HI on the SEND input while the BUSY output is LO. The BUSY signal will be asserted as soon as the module begins transmission of the Start bit and the module will ignore the values on the DATA and SEND inputs during this time. The BUSY signal will go low as soon as the module begins transmission of the Stop bit. At this point it is again sensitive to the status of the DATA and SEND inputs.

There are several ways to operate the transmitter. The first is in an active handshaking protocol that loops through the following process:

  1. WHILE (FOREVER)
    1. WHILE (BUSY = HI) OR (No data to transmit)
      1. SET: SEND = LO
    2. SET: DATA = (data to be transmitted)
    3. SET: SEND = HI
    4. WHILE (BUSY = LO)
      1. Keep DATA and SEND values static

Another more-or-less automatic way to handle the handshaking is to place a FIFO in front of the transmitter and use the inverse of the empty flag as the SEND command. If each separate assertion of the BUSY output is used to POP one item out of the FIFO, then the transmitter will automatically keep transmitting as long as data is available and go idle whenever the FIFO is emptied. As long as data can be made available, the transmitter will output a continuous stream of packets with no idle time between the Stop bit of one packet and the Start bit of the next.

A wrinkle on the above situation exists if software flow control is to be used since the Xon and Xoff characters must be able to be inserted into the data stream ahead of the FIFO contents without disrupting the FIFO. However, this can be handled by inserting a software flow controller module between the send FIFO and the transmitter. The controller appears likes a normal FIFO interface to the transmitter and as a normal transmitter interface to the FIFO.

It should be kept in mind that, even at the fastest supported baud rate of 115,200 baud and using a 50 MHz system clock, each symbol lasts for 434 system clocks leaving plenty of time to perform the necessary handshaking and data transfers. However, there are some "fine points" that need to be considered. For instance, it is important that once a SEND command is issued the data be held static until after the BUSY signal is asserted. The reason is that the data is not actually captured into the transmitter's shift register until the same clock cycle on which BUSY is asserted. Similarly, the SEND command needs to remain asserted until it is acknowledged by the assertion of the BUSY signal otherwise it may not be recognized and acted upon.


Module Code

module rs232_phy_tx(serial_out, busy, data_in, send, baud16, rst, clk);

    output serial_out;
    output busy;
    input [7:0] data_in;
    input send;
    input baud16, rst, clk;
   
    parameter lo = 1'b0, hi = 1'b1;
    parameter data_length = 3'd7;
    parameter
        s_idle  = 2'd0,
        s_start = 2'd1,
        s_data  = 2'd2,
        s_stop  = 2'd3;
   
    reg serial_out;
    reg busy, next_busy;
    reg [8:0] data, next_data; // 9-bit shift register
    reg [1:0] state, next_state;
    reg [3:0] baud_count, next_baud_count;
    reg [2:0] data_count, next_data_count;
   
    wire baud;
    wire next_serial_out;
   
    assign baud = ((baud16 == hi)&&(baud_count == 4'd0))? hi:lo;
    assign next_serial_out = data[0];
   
    // Anynchronous clear and synchronous update of all registers
    always @ (posedge clk or posedge rst)
    begin
        if (rst == 1)
            begin
                state <= s_idle;
                baud_count <= 4'd0;
                data_count <= data_length;
                serial_out <= hi;
                data <= 9'h1FF;
                busy <= 1'd0;
            end
        else
            begin
                state <= next_state;
                baud_count <= next_baud_count;
                data_count <= next_data_count;
                serial_out <= next_serial_out;
                data <= next_data;
                busy <= next_busy;
            end
    end
   
    // Generate the baud tick:
    // When idle: Every baud16 tick.
    // When busy: Every 16th baud16 tick.
   
    always @ (next_busy, baud_count, baud16)
    begin
        if (next_busy == hi)
            if (baud16 == hi)
                if (baud_count == 4'd0)
                    next_baud_count = 4'd15;
                else   
                    next_baud_count = baud_count - 1;
            else
                next_baud_count = baud_count;
        else
            next_baud_count = 4'd0;
    end
   
    always @ (state or data or baud or send
              or data_in or busy or data_count)
    begin
        case (state)
            s_idle:
                begin
                    next_data_count = data_length;
                    if (baud == hi)
                        if (send==hi)
                            begin
                                next_data = {data_in,1'b0}; // Load
                                next_busy = hi;
                                next_state = s_start;
                            end
                        else
                            begin
                                next_data = {1'b1,data[8:1]}; // Shift in a HI
                                next_busy = lo;
                                next_state = state;
                            end
                    else
                        begin
                            next_data = data;
                            next_busy = busy;
                            next_state = state;
                        end
                end
            s_start:
                begin
                    next_data_count = data_length;
                    if (baud == hi)
                        begin
                            next_data = {1'b1, data[8:1]}; // Shift in a HI
                            next_busy = hi;
                            next_state = s_data;
                        end
                    else
                        begin
                            next_data = data;
                            next_busy = busy;
                            next_state = state;
                        end
                end
            s_data:
                begin
                    if (baud == hi)
                        begin
                            next_data = {1'b1,data[8:1]}; // Shift in a HI
                            if (data_count != 3'd0)
                                begin
                                    next_data_count = data_count - 1;
                                    next_busy = hi;
                                    next_state = state;
                                end
                            else
                                begin
                                    next_data_count = data_length;
                                    next_busy = lo;
                                    next_state = s_stop;
                                end
                        end
                    else
                        begin
                            next_data = data;
                            next_data_count = data_count;
                            next_busy = busy;
                            next_state = state;
                        end
                end
            s_stop:
                begin
                    next_data_count = data_length;
                    if (baud == hi)
                        if (send==hi)
                            begin
                                next_data = {data_in,1'b0}; // Load
                                next_busy = hi;
                                next_state = s_start;
                            end
                        else
                            begin
                                next_data = {1'b1,data[8:1]}; // Shift in a HI
                                next_busy = lo;
                                next_state = s_idle;
                            end
                    else
                        begin
                            next_data = data;
                            next_busy = busy;
                            next_state = state;
                        end
                end
            default:
                begin
                    next_data_count = data_length;
                    next_data = data;
                    next_busy = lo;
                    next_state = s_idle;
                end
        endcase
    end
   
endmodule

 


Theory of Operation

The core of the transmitter module is a four-state finite state machine (FSM) that is only allowed to transition from one state to another concurrent with the possible start of a symbol. This means that it can only change state with the baud tick signal is HI. When in the idle state that baud tick signal is HI at every baud16 tick, the reason is that since no symbol is being transmitted, the next symbol can theoretically start at any time; however, for simplicity of implementation, the module restricts the beginning of a new symbol so that it is aligned with the baud16 clock. For readability, the states are parameterized as follows:

    parameter data_length = 3'd7;
    parameter
        s_idle  = 2'd0,
        s_start = 2'd1,
        s_data  = 2'd2,
        s_stop  = 2'd3;

The data_length parameter is the preset value for a down counter that determines how many data bits are in each packet. As is commonly the case, the actual number of bits is one more than this parameter because the counter period includes the value zero.

To avoid confusion, it should be pointed out that the s_data state actually has a series of substates, one for each bit of data in the packet. This is down by having a sub-state variable, data_count in this case, that is initialized to data_length and allowed to decrement to zero before the overall state machine advances from the s_data state to the s_stop state.

Two of the key signals in the module are simple continuous assignments as shown below:

    assign baud = ((baud16 == hi)&&(baud_count == 4'd0))? hi:lo;
    assign next_serial_out = data[0];

The first is the baud tick signal. Except when actually transmitting a packet, the baud_count variable is held at a value of zero and hence the baud tick signal will be equal to the baud16 signal. However, when the transmitter is actually sending data, baud_count is a down counter with a period of sixteen baud16 ticks and hence will produce one baud tick every sixteen baud16 ticks.

The next_serial_out signal is hard-coded to look at the least significant bit (lsb) of the data shift register. This is a 9-bit shift register that, at the beginning of the transmission, has the data to be transmitted loaded into the upper eight bits and a LO value loaded into the lsb. This LO bit is the packet's Start Bit. As transmission progresses, the data will be shifted toward the lsb and HI bits will be loaded into the most significant bit (msb) of the register. The first of these will serve as the packet's Stop Bit and, since shifting will stop at that point if there is no additional data to transmit, it will also serve as the Idle Bit until such time that a new transmission is started.

 during the idle state, contains all  The transmitter is paced by the baud signal, which is HI for one master clock period at the beginning of each possible symbol period. Technically, since RS-232 is a completely asynchronous protocol, a new frame can start at any time following the completion of the prior frame, however for simplicity of implementation the transmitter is constrained to a granularity of the baud16 clock. This means that, in the s_idle state, the baud signal is equal to the baud16 signal since each is potentially the start of a new symbol period. Once the transmitter leaves the s_idle state, however, the baud signal is asserted only once every sixteen baud16 clocks.

Most of the signals in the module are registered. As is the case with most well-formed synchronous state machines, the registered variable is updated at every clock rising edge with a new value for that variable based on some kind of next-state logic. In the Verilog code, this is implemented explicitly by having one process that takes care of the asynchronous reset and synchronous update behavior and another process that implements the next state behavior.

    // Anynchronous clear and synchronous update of all registers
    always @ (posedge clk or posedge rst)
    begin
        if (rst == 1)
            begin
                state <= s_idle;
                baud_count <= 4'd0;
                data_count <= data_length;
                serial_out <= hi;
                data <= 9'h1FF;
                busy <= 1'd0;
            end
        else
            begin
                state <= next_state;
                baud_count <= next_baud_count;
                data_count <= next_data_count;
                serial_out <= next_serial_out;
                data <= next_data;
                busy <= next_busy;
            end
    end

The heart of any finite state machine is the next-state logic, which in this module has been implemented as a single case structure. Although the listing is fairly lengthy, the logic itself is very straightforward. First, within any state any variables that will not change value at all are encoded at the very beginning of that state's block. Then notice that each state contains an if()/else block that activates when the baud tick is HI and whose else section keeps all variables unchanged. This implements the concept that state changes occur only in coincidence with the baud clock.

    always @ (state or data or baud or send
              or data_in or busy or data_count)
    begin
        case (state)
            s_idle:
                begin
                    next_data_count = data_length;
                    if (baud == hi)
                        if (send==hi)
                            begin
                                next_data = {data_in,1'b0}; // Load
                                next_busy = hi;
                                next_state = s_start;
                            end
                        else
                            begin
                                next_data = {1'b1,data[8:1]}; // Shift in a HI
                                next_busy = lo;
                                next_state = state;
                            end
                    else
                        begin
                            next_data = data;
                            next_busy = busy;
                            next_state = state;
                        end
                end
            s_start:
                begin
                    next_data_count = data_length;
                    if (baud == hi)
                        begin
                            next_data = {1'b1, data[8:1]}; // Shift in a HI
                            next_busy = hi;
                            next_state = s_data;
                        end
                    else
                        begin
                            next_data = data;
                            next_busy = busy;
                            next_state = state;
                        end
                end
            s_data:
                begin
                    if (baud == hi)
                        begin
                            next_data = {1'b1,data[8:1]}; // Shift in a HI
                            if (data_count != 3'd0)
                                begin
                                    next_data_count = data_count - 1;
                                    next_busy = hi;
                                    next_state = state;
                                end
                            else
                                begin
                                    next_data_count = data_length;
                                    next_busy = lo;
                                    next_state = s_stop;
                                end
                        end
                    else
                        begin
                            next_data = data;
                            next_data_count = data_count;
                            next_busy = busy;
                            next_state = state;
                        end
                end
            s_stop:
                begin
                    next_data_count = data_length;
                    if (baud == hi)
                        if (send==hi)
                            begin
                                next_data = {data_in,1'b0}; // Load
                                next_busy = hi;
                                next_state = s_start;
                            end
                        else
                            begin
                                next_data = {1'b1,data[8:1]}; // Shift in a HI
                                next_busy = lo;
                                next_state = s_idle;
                            end
                    else
                        begin
                            next_data = data;
                            next_busy = busy;
                            next_state = state;
                        end
                end
            default:
                begin
                    next_data_count = data_length;
                    next_data = data;
                    next_busy = lo;
                    next_state = s_idle;
                end
        endcase
    end
   
endmodule