0
\$\begingroup\$

I implemented a FSM in vhdl using two processes;

  1. A sync process for state transition
RegProxy_FSM_Proc : process (USB_CLK)
begin
   if falling_edge(USB_CLK) then
      if (USB_CLR = '1') then
         RegProxyUSBState <= IDLE;
      else
         case RegProxyUSBState is
            when IDLE => 
               if USB3_RXF_L = '0' then
                  RegProxyUSBState <= OP_PRE_INIT;
               elsif USB3_TXE_L = '0' then
                  case OffsetReg(11 downto 8) is
                     when "1110" =>
                        RegProxyUSBState <= FIFO_SEND_INIT;
                     when "1111" =>
                        RegProxyUSBState <= ATOM4_SEND_INIT;
                     when others =>
                        RegProxyUSBState <= REG_SEND_INIT;
                  end case;
               end if;
            
            when OP_PRE_INIT       => RegProxyUSBState <= OP_INIT;            
            when OP_INIT           => RegProxyUSBState <= GET_OPCODE_OFFSET;
            when GET_OPCODE_OFFSET => RegProxyUSBState <= GET_LENGTH;
            when GET_LENGTH =>
               case OpCodeReg(3 downto 2) is
                  when "00" =>
                     RegProxyUSBState <= GET_DATA_LSW;
                  when "01" =>
                     RegProxyUSBState <= GET_MASK_LSW;
                  when others =>
                     RegProxyUSBState <= IDLE;
               end case;
               
            when GET_DATA_LSW => RegProxyUSBState <= GET_DATA_MSW;
            when GET_MASK_LSW => RegProxyUSBState <= GET_MASK_MSW;
            
            when GET_DATA_MSW => 
               if RegLengthCountNotEmpty = '1' then
                  RegProxyUSBState <= GET_DATA_LSW;
               else  
                  RegProxyUSBState <= IDLE;
               end if;
                                
            when REG_SEND_INIT   => RegProxyUSBState <= REG_SEND_LSW;
            when REG_SEND_LSW    => RegProxyUSBState <= REG_SEND_MSW;
            when FIFO_SEND_INIT  => RegProxyUSBState <= FIFO_SEND;
            when ATOM4_SEND_INIT => RegProxyUSBState <= ATOM4_SEND;
            
            when REG_SEND_MSW =>
               if RegLengthCountNotEmpty = '1' then
                  RegProxyUSBState <= REG_SEND_LSW;
               else
                  RegProxyUSBState <= IDLE;
               end if;
            
            when ATOM4_SEND | FIFO_SEND =>
               if Atom4LengthCountNotEmpty = '0' then
                  RegProxyUSBState <= IDLE;
               end if;
            
            when others => 
               RegProxyUSBState <= IDLE;
         end case;
      end if;
   end if;
end process RegProxy_FSM_Proc;
  1. A combinational process for setting outputs (and reading inputs)
USB_CLK_PROC : process (RegProxyUSBState, USB_DATA)
begin   
   case RegProxyUSBState is
      when IDLE     | OP_PRE_INIT   | OP_INIT =>
         OpCodeReg          <= (others => '0');   
         OffsetReg          <= (others => '0');    
         LengthReg          <= (others => '0');    
         RegPointer         <= (others => '0');    
         RegLengthCounter   <= (others => '0');   
         Atom4LengthCounter <= (others => '0');   
         FifODataReady      <= '0';                

      when GET_OPCODE_OFFSET =>                  
         OpCodeReg <= USB3_DATA(3 downto 0);
         OffsetReg <= USB3_DATA(15 downto 4);

      when GET_LENGTH =>         
         RegLengthCounter <= unsigned(USB3_DATA(11 downto 0));
         RegPointer       <= unsigned(OffsetReg);
         if F_MemSelect = '1' then
            Atom4LengthCounter(13 downto 2) <= unsigned(USB3_DATA(11 downto 0));
            Atom4LengthCounter( 1 downto 0) <= (others => '0');
         else
            Atom4LengthCounter <= ATOM4_LEN_COUNT_FOUR;
         end if;
            
      when GET_DATA_LSW | REG_SEND_LSW =>
         if RegLengthCountNotEmpty = '1' then
            RegLengthCounter <= RegLengthCounter - REG_LEN_COUNT_ONE;
         end if;
        
      when GET_DATA_MSW =>
         if RegLengthCountNotEmpty = '1' then
             RegPointer <= RegPointer + REG_PTR_ONE;
         end if; 
 
      when ATOM4_SEND =>
         if Atom4LengthCountNotEmpty = '1' and Atom4DataReady = '1' then
            Atom4LengthCounter <= Atom4LengthCounter - ATOM4_LEN_COUNT_ONE;
         end if;
        
      when FIFO_SEND =>
         if Atom4LengthCountNotEmpty = '1' then
            Atom4LengthCounter <= Atom4LengthCounter - ATOM4_LEN_COUNT_ONE;
         end if;

      when others =>
         OpCodeReg          <= OpCodeReg;           -- Ahmed: to avoid latches
         OffsetReg          <= OffsetReg;           -- Ahmed: IDLE->OP_INIT Cases
         LengthReg          <= LengthReg;           -- Ahmed: IDLE->OP_INIT Cases
         RegPointer         <= RegPointer;          -- Ahmed: IDLE->OP_INIT Cases
         RegLengthCounter   <= RegLengthCounter;    -- Ahmed: IDLE->OP_INIT Cases
         Atom4LengthCounter <= Atom4LengthCounter;  -- Ahmed: IDLE->OP_INIT Cases
         FifODataReady      <= FifODataReady;       -- Ahmed: IDLE->OP_INIT Cases

  end case;

  FifODataReady <= SFDRdReq or PFDRdReq;
   
end process USB_CLK_PROC;

Brief explanation of signals:

  • USB_CLK is the main system clock
  • USB3_DATA is a 16 bit input std_logic_vector that changes at falling clock edge
  • OpCodeReg: output signal that should be set to USB3_DATA(3 downto 0) when RegProxyUSBState is in GET_OPCODE_OFFSET state, then keep it's value till the FSM reaches the last state
  • OffsetReg: similar to OpCodeReg, USB3_DATA(15 downto 4), and should keep its value as well
  • RegLengthCounter: should take the value in GET_LENGTH state and is equal to unsigned(USB3_DATA(11 downto 0));

Problem: Unexpected 2nd transition in those signal values is observed in Modelsim as shown in the image

enter image description here

I then tried removing USB_DATA from the process sensitivity list and observed that OpCodeReg and OffsetReg values are now read from the previous clock cycle (Not sure why), while RegLengthCounter was read at the current clock cycle (GET_LENGTH).

enter image description here

Could you please explain what is happening in both cases, and how to get the OpCodeReg and OffsetReg to register correctly at the GET_OFFSET_OPCODE state?

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Assigning to registers and counting in counters from an unclocked process is a VERY BAD IDEA. I expect that if you rewrite this in single process form most of the mysterious behaviour will go away. It's also more cohesive : if you ask "what happens in state GET_THIS_VALUE", you don't have to read two processes to find out. \$\endgroup\$
    – user16324
    Commented Jul 4, 2021 at 22:51
  • 1
    \$\begingroup\$ I got that template from here: vhdlwhiz.com/n-process-state-machine \$\endgroup\$
    – Ahmed
    Commented Jul 4, 2021 at 22:55
  • \$\begingroup\$ Then you have some single process examples to follow. \$\endgroup\$
    – user16324
    Commented Jul 4, 2021 at 23:02
  • 1
    \$\begingroup\$ If you read through the article, it says all of these coding styles yield the same thing. Nevertheless, I just tried the single process, and again, OpcodeReg and OffsetReg register the "previous" state Value, while RegLengthCounter worked fine.... \$\endgroup\$
    – Ahmed
    Commented Jul 4, 2021 at 23:40

2 Answers 2

-2
\$\begingroup\$

The testbench was written without taking care of "Delta Cycles" of each signal. Clock edge arrived at a delta cycle which is before the signal actually changes.

This is a non ideal way the high level sequential computer language (C or similar) uses to simulate VHDL code.

Read more here: https://vhdlwhiz.com/delta-cycles-explained/

enter image description here

\$\endgroup\$
-3
\$\begingroup\$

USB3_DATA is a 16 bit input std_logic_vector that changes at falling clock edge

if falling_edge(USB_CLK) then

Changing an input on the active clock edge of a flip-flop violates the flip-flop setup time and hold time.

Consider using the positive edge instead:

if rising_edge(USB_CLK) then

As you have discovered, changing the sensitivity list changes the behaviour of the simulator. To eradicate this inconsistency, use:

process(all)

...and simulate/compile for VHDL-2008 or VHDL-2019. This will also avoid simulator-synthesis mismatches where the simulator behaves differently to the synthesiser.

Historically, sensitivity lists used to cause lots of bugs due to simulation-synthesis mismatches, but, thankfully, that is now a thing of the past. Early simulators didn't have much computing power, so they used the sensitivity list as a kind of short cut cheat sheet rather than analyse the code in the same in-depth manner as the synthesiser, but more powerful modern simulators infer the sensitivity list by analysing the code in the process. So, from a standards perspective and a practical perspective, sensitivity lists nowadays are officially deprecated. Here's a debate from 2009 on the Altera (now Intel) forum discussing the issue of sensitivity lists and process(all) shortly after VHDL-2008 was introduced.

To enable VHDL-2008 in Modelsim, go to:

Menu -> Compile -> Compile Options... -> VHDL tab -> Select "Use 1076-2008"

And to enable VHDL-2008 in Quartus Prime, go to:

Menu -> Assignments -> Compiler Settings -> VHDL Input -> Select VHDL 2008

Though the Lite edition only partially implements it.

VHDL-2008 – Status: Superceded by VHDL-2019

VHDL-2019 – Status: Active and Approved

As an aside, the equivalent for combinational logic in Verilog is always @* and in SystemVerilog it's always_comb.

\$\endgroup\$

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.