Issue with implementing data processing in VHDL code - Page 1
Forum section: electronics / general

author: FlyingDutch
author_rank: Regular Contributor
timestamp: June 16, 2022, 06:59:00 am
content: Hello Guys,i have one issue with VHDL code. The prooblem is related to multiple drivers. I have in some place of my code such fragment (VHDL):Code:PROCESS (lpc_en, lclk)BEGINIF (lpc_en'EVENT AND lpc_en = '1') THENcycle_cnt <= cycle_cnt+1;IF (cycle_cnt >= 8) THENcycle_cnt <= 1;sendPackage <= '1';IF (packageNumber = 1) THENpackageNumber <= 2;package2Send <= 1;ELSIF (packageNumber = 2) THENpackageNumber <= 1;package2Send <= 2;END IF;-- zapisz do BRAM ostatni cykl + wyślij 8 poprzednio zapisanychELSE-- tylko zapisz do BRAM ostatni cyklsendPackage <= '0';END IF;addrAbufU <= to_unsigned(((packageNumber-1)*8+cycle_cnt-1),addrAbufU'length);addrAbuf <= std_logic_vector(addrAbufU); --adres zapisu w BRAM---------------lpc_addrBuf <= lpc_addr;lpc_data_inBuf <= lpc_data_in;--wykonaj zapis do BRAM pod wskazany adressaveCycle <= '1';END IF;END PROCESS;In this process i am setting saveCycle <= '1'; - which means that there is need to write last data chunk to BRAM memory. And in next process I am checking this signal saveCycle and if it is equal '1' I am writing data chunk to BRAM - see code:Code:PROCESS (lclk, lreset_n) --save current cycle in BRAMBEGINIF (NOT lreset_n = '1') THENcnt2 <= b"0000000000000";ELSIF (lclk'EVENT AND lclk = '1') THENIF (saveCycle = '1') THEN --trzeba zapisac aktualny cykl do BRAMif (cnt2 = 0) thencnt2 <= cnt2 + 1;elsif ((cnt2 > 0) and (cnt2 < 10)) thencnt2 <= cnt2 + 1;enaB <= '1';weaB <= "1";dinAbuf(31 downto 28) <= "0000";dinAbuf(27 downto 12) <= lpc_addrBuf;dinAbuf(11 downto 4) <= lpc_data_inBuf;dinAbuf(3 downto 2) <= "00";dinAbuf(1 downto 0) <= std_logic_vector(cycle_type);elsif ((cnt2 >= 10) and (cnt2 < 14)) thencnt2 <= cnt2 + 1;weaB <= "0";saveCycle <= '0';end if;END IF;END IF;END PROCESS;Could I ask more experienced colleagues to advice me how to properly organize such data processing?Thanks in advanvce and regards

author: hamster_nz
author_rank: Super Contributor
timestamp: June 16, 2022, 07:38:55 am
content: First process hasCode:saveCycle <= '1';Second process has:Code:saveCycle <= '0';A single FF can't have be updated by two different clocks..Also...Code:IF (lclk'EVENT AND lclk = '1') THENis outdated.UseCode:IF rising_edge(lclk) THENBut back to original problem. Working int two different clock domains is problematic. Better off to use a vendor's async FIFO to cross the clock domains

author: hamster_nz
author_rank: Super Contributor
timestamp: June 16, 2022, 07:53:36 am
content: Oh, what vendor & tools are you using? I could knock you up something similar perhaps...What are you working on? Some sort of trace buffer?

author: FlyingDutch
author_rank: Regular Contributor
timestamp: June 16, 2022, 09:09:38 am
content: Hello @hamster_nz,I am using "Xilinx Vivado 2019.1" and Digilent "Zybo" FPGA board (with Xilinx Zynq-7000 SoC). I am working on implementation of LPC protocol:https://en.wikipedia.org/wiki/Low_Pin_CountAfter finishing receive one LPC I/O cycle I am latching LPC address(16-bit), LPC data (8-bit) and cycle type (Read or write) and writing this data to BRAM. After i completed 8 such packages I send package of 8 data chunks by AXI bus to hard CPU for further processing.This is FPGA (PL) part - data written to BRAM finally will be send to (PS) ARM CPU hard-core by AXI Bus, and furter processing will be done by CPU. I based my code on "Lattice: company code from this link:https://www.latticesemi.com/-/media/LatticeSemi/Documents/ReferenceDesigns/JM2/FPGA-RD-02114-1-7-LPC-Low-Pin-Count-Bus-Controller-Reference-Design-Documentation.ashx?document_id=32341Then I expanded this code and fix error in original code related to latching "LPC address". Here iis screenshot from simulation of "LPC Peripheral" (not LPC host). You can see two write cycles on screen. The signal "peri_en" is not second clock -0it just tells that "LPC peripheral" is enabled.I solved this described by me issue, but not in such elegant way as you described.Best Regards

author: hamster_nz
author_rank: Super Contributor
timestamp: June 16, 2022, 09:12:12 am
content: You seem to have got me started... so here's what I would do, if I couldn't use vendor IP.I would take the passing of data between clock domains into it's own module:Code:entity data_passing isPort ( clk_a : in STD_LOGIC;data_in_valid : in STD_LOGIC;data_in : in STD_LOGIC_VECTOR (10 downto 0);clk_b : in STD_LOGIC;data_out_valid : out STD_LOGIC := '0';data_out : out STD_LOGIC_VECTOR (10 downto 0) := (others => '0'));end data_passing;You will need to adjust the widths of the data ports, but the intent should be reasonably clear - you set "data_in" and assert "data_in_valid" for one cycle in the source clock domain, and you expect the data to pop out on "data_out", with "data_out_valid" asserted for once cycle.I would then write a test bench for that module, just to see if it will do what I wantCode:library IEEE;use IEEE.STD_LOGIC_1164.ALL;use IEEE.NUMERIC_STD.ALL;entity tb_data_passing isend tb_data_passing;architecture Behavioral of tb_data_passing iscomponent data_passing isPort ( clk_a : in STD_LOGIC;data_in_valid : in STD_LOGIC;data_in : in STD_LOGIC_VECTOR (10 downto 0);clk_b : in STD_LOGIC;data_out_valid : out STD_LOGIC := '0';data_out : out STD_LOGIC_VECTOR (10 downto 0) := (others => '0'));end component;signal clk_a : STD_LOGIC := '0';signal data_in_valid : STD_LOGIC := '0';signal data_in : STD_LOGIC_VECTOR (10 downto 0) := (others => '0');signal clk_b : STD_LOGIC := '0';signal data_out_valid : STD_LOGIC := '0';signal data_out : STD_LOGIC_VECTOR (10 downto 0);signal count : unsigned(3 downto 0) := (others => '0');beginclk_a_proc: processbeginclk_a <= '0';wait for 3 ns;clk_a <= '1';wait for 3 ns;end process;clk_b_proc: processbeginclk_b <= '0';wait for 7 ns;clk_b <= '1';wait for 7 ns;end process;uut: data_passing port map (clk_a => clk_a,data_in_valid => data_in_valid,data_in => data_in,clk_b => clk_b,data_out_valid => data_out_valid,data_out => data_out);stim_proc: process(clk_a)beginif rising_edge(clk_a) thendata_in_valid <= '0';data_in <= (others => '0');case to_integer(count) iswhen 6 => data_in_valid <= '1'; data_in <= "10101010101";when 12 => data_in_valid <= '1'; data_in <= "01010101010";when others =>end case;count <= count + 1;end if;end process;end Behavioral;Then I would implement the module. In this case a small shared memory, addressed by a "write pointer" and a "read pointer", and the destination domain monitors a synchronized copy of the write pointer to see when new data is available.To make the clock domain crossing work, the counter needs to be in Gray code, so only one bit changes at a time, a lookup table is the easiest way to do this:Code:library IEEE;use IEEE.STD_LOGIC_1164.ALL;use IEEE.NUMERIC_STD.ALL;entity data_passing isPort ( clk_a : in STD_LOGIC;data_in_valid : in STD_LOGIC;data_in : in STD_LOGIC_VECTOR (10 downto 0);clk_b : in STD_LOGIC;data_out_valid : out STD_LOGIC := '0';data_out : out STD_LOGIC_VECTOR (10 downto 0) := (others => '0'));end data_passing;architecture Behavioral of data_passing istype t_buffer is array(0 to 7) of STD_LOGIC_VECTOR (10 downto 0);signal shared_buffer : t_buffer;signal wr_pointer : unsigned(2 downto 0) := (others => '0');signal wr_pointer_unsafe : unsigned(2 downto 0);signal wr_pointer_synced : unsigned(2 downto 0);signal rd_pointer : unsigned(2 downto 0) := (others => '0');type a_next is array (0 to 7) of unsigned(2 downto 0);constant gray_next : a_next := ("001", -- 1 gray coded"011", -- 2 gray coded"010", -- 3 gray coded"110", -- 4 gray coded"111", -- 5 gray coded"101", -- 6 gray coded"100", -- 7 gray coded"000" -- 0 gray coded);beginprocess(clk_a)beginif rising_edge(clk_a) thenif data_in_valid = '1' thenshared_buffer(to_integer(unsigned(wr_pointer))) <= data_in;wr_pointer <= gray_next(to_integer(wr_pointer));end if;end if;end process;process(clk_b)beginif rising_edge(clk_b) thendata_out_valid <= '0';data_out <= (others => '0');if wr_pointer_synced /= rd_pointer thendata_out_valid <= '1';data_out <= shared_buffer(to_integer(unsigned(rd_pointer)));rd_pointer <= gray_next(to_integer(rd_pointer));end if;-- A poor crossing of the write pointer into clk_b clock domain.wr_pointer_synced <= wr_pointer_unsafe;wr_pointer_unsafe <= wr_pointer;end if;end process;end Behavioral;You can then safely pass data between two clock domains, as shown in the simulation image attachedAdditional design constraints will be needed to make the timing analysis work, and you will need to set the "ASYNC_REG" attributes appropriately (or your vendor's equivalent) but it is relatively solid design... as long as you don't try to clock data in quicker than it can be retrieved in the target clock domain.(this is not fully tested or debugged or anything, your milage may vary....)

author: Bassman59
author_rank: Super Contributor
timestamp: June 16, 2022, 08:32:16 pm
content: Quote from: FlyingDutch on June 16, 2022, 09:09:38 amThen please consider using a modern dialect of VHDL.