VHDL FSM: works in simulation, not in Lattice CPLD - Page 1
Forum section: electronics / general

author: Torrentula
author_rank: Regular Contributor
timestamp: August 10, 2014, 03:26:03 pm
content: Hey I'm currently getting into VHDL and programmable devices.I'm currently working on an FSM to decode the open pixel control protocol (OPC):http://openpixelcontrol.org/The problem I have now is one of the classic "it works in simulation but not in the actual device" kind.I have written a testbench that does exactly what my circuit does: send a couple of bytes to the CPLD via serial (9600 8N1) and then have the CPLD decode the messages and generate a proper output.My FSM is depening on the i_dataready signal which tells it that the serial/RS232 receiver has received a new byte. That signal is high for exactly one clock cycle. Both modules are connected to the same global clock net in the top level entity / through instantiation. The global clock frequency is 2MHz.The i_dataready signal properly triggers my serial TX module and another module, it just doesn't seem to be properly picked up by the OPC decoder FSM.After fixing some other issues in FSM behavior using the simulator I'm now stuck at my original problem: the FSM that's synthesized into the Lattice CPLD doesn't change states, the simulation does.I suspect that my code is working fine and that there is some problem related to the place and route introducing excessive delays into the i_dataready signal from the RX block. Is there any way to get around this? The synthesizer report shows that the design easily meets 100MHz timing constraints so I'm not really sure how to proceed here.My OPC decoder FSM:Code:library IEEE;use IEEE.STD_LOGIC_1164.all;use IEEE.NUMERIC_STD.all;entity opc_decoder isport(i_gclk: in std_logic;-- global system clocki_dataready: in std_logic;-- data ready signal from previous block (length: 1 gclk cycle)i_rawdata: in std_logic_vector(7 downto 0);-- raw byte received by previous blocko_channel: out std_logic_vector(7 downto 0);-- output channel address for output MUXo_rgbdata: out std_logic_vector(7 downto 0);-- decoded OPC data byte (R|G|B)o_state: out std_logic_vector(2 downto 0);o_newdata: out std_logic-- data synchronization signal for next block);end opc_decoder;architecture rtl of opc_decoder istype t_OPC_SM is (s_IDLE, s_OPC_CMD, s_OPC_LEN_HI, s_OPC_LEN_LO, s_OPC_DATA);signal r_OPC_SM: t_OPC_SM := s_IDLE;signal r_OPC_CHANNEL: std_logic_vector(7 downto 0);-- decoded channel number/addresssignal r_OPC_COUNT: unsigned(15 downto 0); -- number of actual RGB data bytes to be receivedbeginp_OPC_SM: process(i_gclk)beginif(rising_edge(i_gclk)) theno_newdata <= '0';case r_OPC_SM iswhen s_IDLE =>r_OPC_CHANNEL <= (others=>'0');r_OPC_COUNT <= (others=>'0');if(i_dataready = '1') thenr_OPC_CHANNEL <= i_rawdata;r_OPC_SM <= s_OPC_CMD;elser_OPC_SM <= s_IDLE;end if;when s_OPC_CMD =>if(i_dataready = '1') thenr_OPC_SM <= s_OPC_LEN_HI;elser_OPC_SM <= s_OPC_CMD;end if;when s_OPC_LEN_HI =>if(i_dataready = '1') thenr_OPC_COUNT <= unsigned(i_rawdata) & r_OPC_COUNT(7 downto 0);r_OPC_SM <= s_OPC_LEN_LO;elser_OPC_SM <= s_OPC_LEN_HI;end if;when s_OPC_LEN_LO =>if(i_dataready = '1') thenr_OPC_COUNT <= r_OPC_COUNT(15 downto 8) & unsigned(i_rawdata);r_OPC_SM <= s_OPC_DATA;elser_OPC_SM <= s_OPC_LEN_LO;end if;when s_OPC_DATA =>if(i_dataready = '1') theno_rgbdata <= i_rawdata;o_newdata <= '1';r_OPC_COUNT <= r_OPC_COUNT - 1;end if;if(r_OPC_COUNT > 0) thenr_OPC_SM <= s_OPC_DATA;elser_OPC_SM <= s_IDLE;end if;when others =>r_OPC_SM <= s_IDLE;end case;end if;end process p_OPC_SM;-- output state informationwith r_OPC_SM selecto_state <= "001" when s_IDLE,"010" when s_OPC_CMD,"011" when s_OPC_LEN_HI,"100" when s_OPC_LEN_LO,"101" when s_OPC_DATA,"000" when others;o_channel <= r_OPC_CHANNEL;end rtl;My top level entity where the decoder, RX and TX are instantiated:Code:library IEEE;use IEEE.STD_LOGIC_1164.all;entity serial_test isport(i_gclk: in std_logic;i_rx: in std_logic;o_tx: out std_logic;o_go: out std_logic;o_newdata: out std_logic;o_ws: out std_logic;o_leds: out std_logic_vector(2 downto 0));end serial_test;architecture rtl of serial_test isconstant c_UART_CLKS_PER_BIT: integer := 208;constant c_WS2812_CLKS_PER_TICK: integer := 5;signal txact, txdone: std_logic;signal rxdata: std_logic_vector(7 downto 0);signal rgbdata: std_logic_vector(7 downto 0);signal opc_chan: std_logic_vector(7 downto 0);signal go: std_logic;signal opc_state: std_logic_vector(2 downto 0);signal newrgb: std_logic;beginopcdec_inst: entity work.opc_decoderport map(i_gclk => i_gclk,i_dataready => go,i_rawdata => rxdata,o_channel => opc_chan,o_rgbdata => rgbdata,o_state => opc_state,o_newdata => newrgb);urx_inst: entity work.uart_rxgeneric map(g_CLKS_PER_BIT => c_UART_CLKS_PER_BIT)port map(i_clk => i_gclk,i_rx_serial => i_rx,o_rx_dv => go,o_rx_byte => rxdata);utx_inst: entity work.uart_txgeneric map(g_CLKS_PER_BIT => c_UART_CLKS_PER_BIT)port map(i_clk => i_gclk,i_tx_dv => go,i_tx_byte => rxdata,o_tx_active => txact,o_tx_serial => o_tx,o_tx_done => txdone);wsdrv_inst: entity work.ws2812_drivergeneric map(g_GCLKS_PER_TICK => c_WS2812_CLKS_PER_TICK)port map(i_gclk => i_gclk,i_rgbdata => rgbdata,i_newdata => newrgb,o_serial => o_ws);-- output FSM state to LEDso_leds <= opc_state;-- output go signal for signal analyzero_go <= go;o_newdata <= newrgb;end rtl;And finally the test bench which produces correct (expected) results:Code:LIBRARY ieee;USE ieee.std_logic_1164.ALL;USE ieee.numeric_std.ALL;ENTITY testbench ISEND testbench;ARCHITECTURE behavior OF testbench ISCOMPONENT serial_testPORT(i_gclk : IN std_logic;i_rx : IN std_logic;o_tx: OUT std_logic;o_go : OUT std_logic;o_newdata : OUT std_logic;o_ws : OUT std_logic;o_leds : OUT std_logic_vector(2 downto 0));END COMPONENT;SIGNAL i_gclk : std_logic := '1';SIGNAL i_rx : std_logic := '1';SIGNAL o_tx : std_logic;SIGNAL o_go : std_logic;SIGNAL o_newdata : std_logic;SIGNAL o_ws : std_logic;SIGNAL o_leds : std_logic_vector(2 downto 0);BEGIN-- Please check and add your generic clause manuallyuut: serial_test PORT MAP(i_gclk => i_gclk,i_rx => i_rx,o_tx => o_tx,o_go => o_go,o_newdata => o_newdata,o_ws => o_ws,o_leds => o_leds);p_GCLK: processbegini_gclk <= not i_gclk;wait for 250ns;end process p_GCLK;p_RX: processbegin--wait for 375ns;wait for 1ms;-- first byte: 0x01(CHANNEL)i_rx <= '0';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '1';wait for 104us;-- second byte: 0x00 (COMMAND)i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '1';wait for 104us;-- third byte 0x00(LENGTH MSB)i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '1';wait for 104us;-- fourth byte 0x03 (LENGTH LSB)i_rx <= '0';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '1';wait for 104us;-- fifth byte 0xCDi_rx <= '0';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;-- sixth byte 0xBAi_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;-- seventh byte 0xFEi_rx <= '0';wait for 104us;i_rx <= '0';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;i_rx <= '1';wait for 104us;wait for 10ms;end process p_RX;END;Attached are some screenshots, the first shows the waveforms generated by the Active-HDL simulator and the second one shows the actual signals probed with a logic analyzer.Thanks a lot,Elia

author: hamster_nz
author_rank: Super Contributor
timestamp: August 10, 2014, 10:29:33 pm
content: Hi!I would look carefully at this bit:Code:if(i_dataready = '1') theno_rgbdata <= i_rawdata;o_newdata <= '1';r_OPC_COUNT <= r_OPC_COUNT - 1;end if;if(r_OPC_COUNT > 0) thenr_OPC_SM <= s_OPC_DATA;elser_OPC_SM <= s_IDLE;end if;Just in case there is a fence-post error due to the delayed assignment.The code is functionally identical to:Code:if(r_OPC_COUNT > 0) thenr_OPC_SM <= s_OPC_DATA;elser_OPC_SM <= s_IDLE;end if;if(i_dataready = '1') theno_rgbdata <= i_rawdata;o_newdata <= '1';r_OPC_COUNT <= r_OPC_COUNT - 1;end if;When what you might be wanting is:Code:r_OPC_SM <= s_OPC_DATA;if(i_dataready = '1') theno_rgbdata <= i_rawdata;o_newdata <= '1';if (r_OPC_COUNT = 1) thenr_OPC_SM <= s_IDLE;elser_OPC_COUNT <= r_OPC_COUNT - 1;end if;end if;And I'm also not sure if the comparison of r_OPC_COUNT should be against '1' or '0' - that depends on the OPC protocol. I assume a count of 0 means that 64k of data follows, and a count of one means that 1 data word will follow.

author: Torrentula
author_rank: Regular Contributor
timestamp: August 11, 2014, 03:48:42 pm
content: Thanks hasmter_nz for your reply.I've changed the code to what you suggested as that should do what I want (I wasn't aware of the fact that those two blocks of VHDL you presented were functionally identical, I'm probably still too used to sequential programming (altough process() is sequential??)) .And yet again the simulator still insists that the design works. The code is synthesized without problems but the actual device does not work as expected.QuoteThe r_OPC_COUNT register represents the number of data bytes that will be transmitted with the OPC message. I first read the number of bytes from the serial RX in two previous states of the state machine and then afterwards I use it as a byte counter. In this case the code you've provided is correct in comparing to 1 as it means that the last byte to be sent has just been received.Any other suggestions or ideas why this thing just won't seem to work in actual hardware when neither the synthesizer complains nor the simulator gives obviously wrong results?EDIT: Okay this problem is kind of solved. I have used the Lattice Synthesis Engine (LSE) and just for kicks I just now tried to use Synplify Pro as the synthesizer and all of a sudden now it works. Can anybody think of why this is the case? Is there a general rule of keeping your fingers off of the LSE and stick with known-good third party tools like Synplify?Have I maybe just overlooked some setting in the LSE configuration?

author: Bassman59
author_rank: Super Contributor
timestamp: August 11, 2014, 04:24:10 pm
content: Quote from: Torrentula on August 10, 2014, 03:26:03 pmLet me preface by saying I haven't used a Lattice CPLD in over 15 years, but I do Xilinx and Actel (MicroSemi) all the time.In your code you have an initializer on the state register:signal r_OPC_SM: t_OPC_SM := s_IDLE;and this is perfectly acceptable, and in fact it is the preferred thing to do with Xilinx FPGA devices.And further in your code, your state machine's process doesn't have a reset. Again, with the initializer on the state register, in a Xilinx FPGA this is exactly what you should do (assuming, of course, that other operational considerations don't require the state machine to be reset).Now, the rub: I do not know if the Lattice CPLDs support such initializers, but I know that Xilinx CPLDs don't, and as such you need to provide an explicit reset to your state machine.I see that you bring out a test point encoding the state register for debug. What value is it stuck out.

author: Bassman59
author_rank: Super Contributor
timestamp: August 11, 2014, 04:38:19 pm
content: Quote from: Torrentula on August 11, 2014, 03:48:42 pmA process is an interesting beast. They suspend until an event occurs on (any of) the signal(s) on the sensitivity list. Then each statement in the process is evaluated in order, and for signal (non-blocking) assignments the signals on the left hand sides of all assignments arescheduledfor update. Only after all of the right-hand sides have been evaluated -- and this includes other processes which may be triggered at the same time, such as other processes using the same clock -- do the left-hand-side signals update.The way you can think of it is this: at the rising edge of the clock, the state of all signals on the right-hand side of all assignments is frozen. Then each equation is evaluated, using those frozen values. Finally, after all evaluations are complete, every left-hand-side signal updates.QuoteOh, now I remember. When I first looked at the Cypress CPLDs (after using their PALs for years), I recall that the synthesis tool was a complete piece of shit, and that's saying a lot, because at the time I was using the FPGA Express that Xilinx provided with very early versions of ISE. And FPGA Express was pretty shitty, too, so I convinced the boss that a Synplify license was necessary (IT WAS), and that shittiness is likely why Synopsys ditched FPGA Express and bought Synplicity, which continues to be a good product.Anyways, it's no surprise that the Cypress tool sucks balls.

author: Torrentula
author_rank: Regular Contributor
timestamp: August 11, 2014, 04:39:45 pm
content: QuoteIt is stuck at 1, the IDLE state of the FSM. So you're saying that a simple (asynchronous) reset signal might make it work in the Lattice CPLD?If it was the case that the Lattice MachXO doesn't support those initializers, why does it work with the above code when synthesized with Synplify then?

author: Bassman59
author_rank: Super Contributor
timestamp: August 11, 2014, 04:46:24 pm
content: Quote from: Torrentula on August 11, 2014, 04:39:45 pmThat's my guess. It's easy enough to verify.QuoteSynplify may know how to properly use the initializers. Synplify outputs a schematic how it interprets the RTL. See what the Lattice tool does in that respect. It might just do something weird, and the schematic will help you sort it out.

author: Dongulus
author_rank: Regular Contributor
timestamp: August 11, 2014, 04:55:34 pm
content: I was going to suggest an asynchronous reset as well. I've almost always had exclusively asynchronous resets in my designs so I don't know what would happen using just implied reset statements in signal definitions.Though unrelated to your problem, here's a helpful tip: in your testbench code, you can use your uart_tx block to provide the stimulus for RX input. This way you can easily add it to your testbench as a component and you don't have to do the work of figuring out the correct timing yourself.

author: Torrentula
author_rank: Regular Contributor
timestamp: August 12, 2014, 07:39:30 pm
content: Alright I have added an asynchronous reset input to the FSM to bring it into a known state (s_IDLE) via an external signal. So far works very nicely and gets the FSM out of being stuck in the s_OPC_DATA state after a microcontroller (serial data source) reset.Thanks everyone for your help!Cheers,Elia