# FT245R transmit buffer filling up, repeated IOCTL_SERIAL_WAIT_ON_MASK messages

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
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.

--

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
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
ELSE
next_state <= idle;
END IF;

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

-- 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' =>
word_half_rd <= '1';
WHEN '1' =>
word_half_rd <= '0';
write_waiting <= '1';
WHEN OTHERS =>
END CASE;

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' =>
word_half_wr <= '1';
WHEN '1' =>
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;

-
 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. – Chris Stratton Nov 3 '12 at 16:46 Can you post your CPLD code? – Dave Tweed Nov 3 '12 at 17:39 @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. – deed02392 Nov 6 '12 at 16:34 @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. – deed02392 Nov 7 '12 at 11:52 You should put that additional information here, not start another question. – Dave Tweed Nov 7 '12 at 12:11

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.

-
 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. – Chris Stratton Nov 3 '12 at 14:17 @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. – Dave Tweed Nov 3 '12 at 16:12 That is beside the point. Your claim that sending USB data will preclude the opportunity to receive any is mistaken. – Chris Stratton Nov 3 '12 at 16:43 @ChrisStratton: Are you denying that USB is physically half-duplex? In that case, you're simply wrong. – Dave Tweed Nov 3 '12 at 16:45 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. – Chris Stratton Nov 3 '12 at 16:49