0
\$\begingroup\$

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;
\$\endgroup\$
10
  • \$\begingroup\$ Lower than expected USB throughput often results from transferring fewer bytes per packet than possible. This can be a problem with the device implemention or external data source, the driver or operating system, or even with the implementation of host-side client applications or runtimes on which they are based. I can recall a degenerate case where a .NET solution from a contractor ended up trying to accessing a USB-serial converter one byte at a time, with a painfully slow result. \$\endgroup\$ Commented Nov 3, 2012 at 16:46
  • \$\begingroup\$ Can you post your CPLD code? \$\endgroup\$
    – Dave Tweed
    Commented Nov 3, 2012 at 17:39
  • \$\begingroup\$ @DaveTweed Please see CPLD code edited into question. It looks like a lot but all the magic is in that one state machine (I think!) Hopefully this will clear things up. \$\endgroup\$
    – deed02392
    Commented Nov 6, 2012 at 16:34
  • \$\begingroup\$ @ChrisStratton I have provided some more information into the state of things in another question: electronics.stackexchange.com/questions/48383/… Perhaps this will help us determine where the problem exists in the chain of things. \$\endgroup\$
    – deed02392
    Commented Nov 7, 2012 at 11:52
  • \$\begingroup\$ You should put that additional information here, not start another question. \$\endgroup\$
    – Dave Tweed
    Commented Nov 7, 2012 at 12:11

1 Answer 1

0
\$\begingroup\$

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.

\$\endgroup\$
9
  • 1
    \$\begingroup\$ Premise is untrue. Constant sending (attempts) does not preclude the opportunity to read. Also a USB device can refuse to acknowledge sent data it is not ready to handle. \$\endgroup\$ Commented Nov 3, 2012 at 14:17
  • \$\begingroup\$ @ChrisStratton: Without seeing the CPLD code, it is impossible to tell whether the FT245R receive FIFO is filling up. I'm guessing that it isn't, and that the CPLD is discarding data that it can't send back. In any case, Windows (and possibly other USB hosts as well) only polls the transmit buffer every 2 ms or so; if it contains data, it will read it all out (even if more arrives in the meantime), but if it is empty, it won't check again for another 2 ms. Believe me, I have been all through this with my own FPGA/FTDI-based designs. \$\endgroup\$
    – Dave Tweed
    Commented Nov 3, 2012 at 16:12
  • \$\begingroup\$ That is beside the point. Your claim that sending USB data will preclude the opportunity to receive any is mistaken. \$\endgroup\$ Commented Nov 3, 2012 at 16:43
  • \$\begingroup\$ @ChrisStratton: Are you denying that USB is physically half-duplex? In that case, you're simply wrong. \$\endgroup\$
    – Dave Tweed
    Commented Nov 3, 2012 at 16:45
  • 1
    \$\begingroup\$ Your conclusion that the single-pair + power electrical nature means outbound data can preclude inbound data is mistaken. USB is a packetized system more than it is a half-duplex one, and the management algorithms are not so foolish as to allow that to happen. You don't just get endless sends without an opportunity to reply. \$\endgroup\$ Commented Nov 3, 2012 at 16:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.