FT245R transmit buffer filling up, repeated IOCTL_SERIAL_WAIT_ON_MASK messages

ftdixilinx

I'm stress testing the FT245R chip using a basic CPLD to negotiate reading and writing with the chip and PC as the USB host.

Basically I have programmed up the CPLD to read in 8 bit words from the USB host, store these data and then when I have two words stored, request to write them back.

I am testing with realterm. I use the Send tab to send some ASCII characters i.e. 'abcdef'. This works fine with 1 repeat, 10 repeats but at 100 repeats, I get some inconsistent counts. If I push it all the way and use thousands of repeats, something interesting happens – the TXE signal of the FTDI chip goes high and stays high (from the datasheet: "When low, data can be written into the FIFO. When high, do not write data into the FIFO"). Nothing else will happen, the RXD indicator in realterm stays solid.

Is the FTDI chip indicating I can't write because the USB host i.e. realterm is not reading from the FIFO transmit buffer? The only way to exit this state seems to be to close the COM connection… should it be this easy to crash a communication session? How can I mitigate that risk from the CPLD side of things? Perhaps reset the module when it detects TXE is high for too long?

The datasheet is rather unclear but here it is for reference. I'll be really grateful if you can shed some light on what's happening and how to proceed.

Some more information on what's happening, now I've spied on the port messages with Portmon:

IRP_MJ_WRITE Length: 12
IRP_MJ_WRITE Length: 90
IRP_MJ_WRITE Length: 180
IRP_MJ_WRITE Length: 198
IRP_MJ_WRITE Length: 204
IOCTL_SERIAL_WAIT_ON_MASK SUCCESS
IRP_MJ_WRITE Length: 198
IRP_MJ_WRITE Length: 204
...

Inbetween these writes as you can see above we get a WAIT_ON_MASK request. From what I understand this is an instructino from realterm to the FTDI driver to wait for the FTDI chip to do any of the set wait masks, namely:

IOCTL_SERIAL_SET_WAIT_MASK Mask: RXCHAR CTS DSR RLSD BRK ERR RING

BUT what is happening to me is, after I have sent a huge number of WRITEs, (in amongst these wait messages), I get into a state where I simply get repeated IOCTL_SERIAL_WAIT_ON_MASK messages. This goes on and on, and I cannot stop this unless I close the port (whereupon IOCTL_SERIAL_PURGE messages are sent successfully).

I can hence reopen the port and the connection is fine, i.e. with a smaller burst of writing I get all the data echoed back (as I have configured the CPLD controlling the FTDI chip to do).

IT is probably worth noting also that in this state where after I have sent LOTS of write commands, at the point it starts to hang the FTDI chip indicates no ability to write (i.e. TXE remains high, the datasheet is not clear but I think this means the FIFO write buffer is full). If I disconnect during the long sequence of WAIT messages I note an:

IRP_MJ_WRITE CANCELLED

message of usually quite a long length.

Datasheet:
http://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT245R.pdf

CPLD Code (VHDL):

----------------------------------------------------------------------------------
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;

ENTITY main IS
    PORT ( clk : IN STD_LOGIC; -- 8 MHz, 125 ns osc
              rst : IN STD_LOGIC; -- reset (ACTIVE LOW)
              usb0_rxf_led : OUT  STD_LOGIC; -- usb activity, illuminate with logic 0
              usb1_rxf_led : OUT STD_LOGIC; -- unused, illuminate with logic 0
           max_temp_log_led : OUT  STD_LOGIC; -- overtemp, illuminate with logic 0
              one_wire_bus : INOUT STD_LOGIC; -- 1-wire bus with thermometer
              -- usb ctrl
              usb_rd : OUT STD_LOGIC;
              usb_wr : OUT STD_LOGIC;
              usb_rxf : IN STD_LOGIC;
              usb_txe : IN STD_LOGIC;
              -- usb databus ports
              usb_db0 : INOUT STD_LOGIC;
              usb_db1 : INOUT STD_LOGIC;
              usb_db2 : INOUT STD_LOGIC;
              usb_db3 : INOUT STD_LOGIC;
              usb_db4 : INOUT STD_LOGIC;
              usb_db5 : INOUT STD_LOGIC;
              usb_db6 : INOUT STD_LOGIC;
              usb_db7 : INOUT STD_LOGIC;
              -- plel databus ports
              plel_db0 : INOUT STD_LOGIC;
              plel_db1 : INOUT STD_LOGIC;
              plel_db2 : INOUT STD_LOGIC;
              plel_db3 : INOUT STD_LOGIC;
              plel_db4 : INOUT STD_LOGIC;
              plel_db5 : INOUT STD_LOGIC;
              plel_db6 : INOUT STD_LOGIC;
              plel_db7 : INOUT STD_LOGIC;
              plel_db8 : INOUT STD_LOGIC;
              plel_db9 : INOUT STD_LOGIC;
              plel_db10 : INOUT STD_LOGIC;
              plel_db11 : INOUT STD_LOGIC;
              plel_db12 : INOUT STD_LOGIC;
              plel_db13 : INOUT STD_LOGIC;
              plel_db14 : INOUT STD_LOGIC;
              plel_db15 : INOUT STD_LOGIC
      );

END main;

ARCHITECTURE Behavioral OF main IS

    -- define states (in terms of ftdi chip, i.e. reading from usb host, writing to usb host)
    TYPE usb_state_type IS
        (idle, read_ready, reading, read_done, write_req, writing, write_complete, inout_initialise);
    SIGNAL next_state   : usb_state_type;
    -- constants
    CONSTANT fsm_delay_const        : INTEGER := 1; -- 1/const = delay factor
    CONSTANT write_timeout_const    : INTEGER := 80000; -- clock cycles (125 ns) : 80,000 = 10 ms
    --signals
    SIGNAL read_store           : STD_LOGIC_VECTOR (15 DOWNTO 0) := "0000000000000000";
    SIGNAL write_waiting        : STD_LOGIC := '0';
    SIGNAL counter              : INTEGER RANGE fsm_delay_const DOWNTO 0;
    SIGNAL write_wait_counter   : INTEGER RANGE write_timeout_const DOWNTO 0;
    SIGNAL fsm_enable           : STD_LOGIC;
    SIGNAL word_half_rd         : STD_LOGIC;
    SIGNAL word_half_wr         : STD_LOGIC;

    SIGNAL usb_rxf_d0               : STD_LOGIC;
    SIGNAL usb_rxf_d1               : STD_LOGIC;
    SIGNAL usb_txe_d0               : STD_LOGIC;
    SIGNAL usb_txe_d1               : STD_LOGIC;
BEGIN

    run:
    PROCESS(clk)
    BEGIN

        IF RISING_EDGE(clk) THEN

            IF(rst = '0') THEN

                -- Reset states
                next_state          <= idle;
                usb_rd                  <= '1';
                usb_wr                  <= '0';
                usb_db0                 <= 'Z';
                usb_db1                 <= 'Z';
                usb_db2                 <= 'Z';
                usb_db3                 <= 'Z';
                usb_db4                 <= 'Z';
                usb_db5                 <= 'Z';
                usb_db6                 <= 'Z';
                usb_db7                 <= 'Z';
                plel_db0            <= 'Z';
                plel_db1            <= 'Z';
                plel_db2            <= 'Z';
                plel_db3            <= 'Z';
                plel_db4            <= 'Z';
                plel_db5            <= 'Z';
                plel_db6            <= 'Z';
                plel_db7            <= 'Z';
                plel_db8            <= 'Z';
                plel_db9            <= 'Z';
                plel_db10           <= 'Z';
                plel_db11           <= 'Z';
                plel_db12           <= 'Z';
                plel_db13           <= 'Z';
                plel_db14           <= 'Z';
                plel_db15           <= 'Z';
                one_wire_bus        <= 'Z';
                max_temp_log_led    <= '1';
                --usb1_rxf_led      <= '1';
                write_waiting       <= '0';
                word_half_rd        <= '0';
                word_half_wr        <= '0';

            ELSIF fsm_enable = '1' THEN

                usb_rd <= '1';
                usb_wr <= '0';

                CASE next_state IS

                    WHEN idle =>

                        IF (write_waiting = '1') THEN
                            next_state <= write_req;
                        ELSIF (usb_rxf_d1 = '0') THEN
                            next_state <= read_ready;
                        ELSE
                            next_state <= idle;
                        END IF;

                    WHEN read_ready =>

                        -- rxf low, data available. set rd# low
                        usb_rd <= '0';
                        next_state <= reading;

                    WHEN reading => 

                        -- valid data within 20-50 ns. one period is 125 ns so valid data should definitely be
                        -- available by now.
                        -- read and store with valid data. set rd# high again
                        usb_rd <= '1';

                        CASE word_half_rd IS
                            WHEN '0' =>
                                read_store(0) <= usb_db0;
                                read_store(1) <= usb_db1;
                                read_store(2) <= usb_db2;
                                read_store(3) <= usb_db3;
                                read_store(4) <= usb_db4;
                                read_store(5) <= usb_db5;
                                read_store(6) <= usb_db6;
                                read_store(7) <= usb_db7;
                                word_half_rd <= '1';
                            WHEN '1' =>
                                read_store(8) <= usb_db0;
                                read_store(9) <= usb_db1;
                                read_store(10) <= usb_db2;
                                read_store(11) <= usb_db3;
                                read_store(12) <= usb_db4;
                                read_store(13) <= usb_db5;
                                read_store(14) <= usb_db6;
                                read_store(15) <= usb_db7;
                                word_half_rd <= '0';
                                write_waiting <= '1';
                            WHEN OTHERS =>
                        END CASE;       

                        next_state <= read_done;

                    WHEN read_done =>

                        next_state <= idle;

                    WHEN write_req =>
                        IF (usb_txe_d1 = '0') THEN
                            -- pull wr high, with valid data on d0-7
                            usb_wr <= '1';

                            CASE word_half_wr IS
                                WHEN '0' =>
                                    usb_db0 <= read_store(0);
                                    usb_db1 <= read_store(1);
                                    usb_db2 <= read_store(2);
                                    usb_db3 <= read_store(3);
                                    usb_db4 <= read_store(4);
                                    usb_db5 <= read_store(5);
                                    usb_db6 <= read_store(6);
                                    usb_db7 <= read_store(7);
                                    word_half_wr <= '1';
                                WHEN '1' =>
                                    usb_db0 <= read_store(8);
                                    usb_db1 <= read_store(9);
                                    usb_db2 <= read_store(10);
                                    usb_db3 <= read_store(11);
                                    usb_db4 <= read_store(12);
                                    usb_db5 <= read_store(13);
                                    usb_db6 <= read_store(14);
                                    usb_db7 <= read_store(15);
                                    word_half_wr <= '0';
                                    write_waiting <= '0';
                                WHEN OTHERS =>
                            END CASE;
                            next_state <= writing;

                        ELSIF (write_wait_counter > 0) THEN
                            -- cannot write this time! should we timeout?
                            write_wait_counter <= write_wait_counter - 1;
                            next_state <= write_req;

                        ELSE
                            -- cannot write, we have waited long enough. abort the write.
                            write_wait_counter <= write_timeout_const;
                            write_waiting <= '0';
                            next_state <= inout_initialise;
                        END IF;

                    WHEN writing =>

                        -- drop wr low, data is written to FIFO when txe drops
                        usb_wr <= '0';
                        next_state <= write_complete;

                    WHEN write_complete =>

                        -- stay in this state until ft254r has accepted the write into fifo
                        next_state <= inout_initialise;

                    WHEN inout_initialise =>
                        usb_db0 <= 'Z';
                        usb_db1 <= 'Z';
                        usb_db2 <= 'Z';
                        usb_db3 <= 'Z';
                        usb_db4 <= 'Z';
                        usb_db5 <= 'Z';
                        usb_db6 <= 'Z';
                        usb_db7 <= 'Z';
                        next_state <= idle;

                    WHEN OTHERS =>
                        next_state <= idle;

                END CASE;

            END IF; -- enable
        END IF; -- clock
    END PROCESS;


    delay_proc:
    PROCESS (clk)
    BEGIN
        IF RISING_EDGE(clk) THEN

            IF rst = '0' THEN
                --DO RESET LOGIC HERE...
                fsm_enable <= '0';
                counter <= fsm_delay_const;
            ELSE

                fsm_enable <= '0';

                IF counter = 0 THEN
                    fsm_enable <= '1';
                    counter <= fsm_delay_const;
                ELSE
                    counter <= counter - 1;
                END IF;             

            END IF; --ELSE IF rst = '1' THEN

        END IF; --IF RISING_EDGE(clk) THEN

    END PROCESS;


    activity_led:
    PROCESS (usb_rxf)
    BEGIN
        usb0_rxf_led <= usb_rxf;
        usb1_rxf_led <= usb_txe;
    END PROCESS;

    anti_meta:
    PROCESS (clk)
    BEGIN
        IF RISING_EDGE(clk) THEN
            IF rst = '0' THEN
                usb_rxf_d0 <= '1';
                usb_rxf_d1 <= '1';
                usb_txe_d0 <= '1';
                usb_txe_d1 <= '1';
            ELSE
                usb_rxf_d0 <= usb_rxf;
                usb_rxf_d1 <= usb_rxf_d0;
                usb_txe_d0 <= usb_txe;
                usb_txe_d1 <= usb_txe_d0;
            END IF;
        END IF;
    END PROCESS;

END Behavioral;

Best Answer

Yes, you are indeed filling up the FT245R's 256-byte transmit buffer.

Remember, USB is fundamentally a half-duplex communications protocol. If you send hundreds of bytes to a device in a burst, there's no opportunity to receive bytes back from the device until the burst is over.

Your CPLD is having no trouble accepting data from the 128-byte receive buffer, keeping it from filling up, at least until the transmit buffer fills up. But with a total transmit capacity of 256 + 2 bytes of buffering, any burst over 258 bytes is likely to fail.

There is some "randomness" in the results with hundreds of bytes because of the fact that Windows polls the device status every 1-2 ms. If it happens to poll the device in the middle of a large burst, before the FIFO has filled up, it will drain the available data from transmit FIFO, making room for another 258 bytes before it fails. But at a peak transfer rate of 1 MBps, any transfer longer than about 500 bytes is pretty much guaranteed to fail.

Bottom line is, if you need to transfer large amounts of data in bursts, you need to have sufficient memory for buffering those bursts outside the FT245R.

If you can use your CPLD to set up a 2-KB data FIFO (perhaps using an external chip), your stress test will work perfectly with any amount of data.

Related Topic