I learned VHDL 5 years back, and never used after that as I was working on different domain. Now I'm working in a project that required some work in VHDL. I have to implement SPI to program a ADF4158 device. I opened a book for syntax and tried to program. I need to develop the below module, I coded as per my understanding and also a test bench but it doesnot work in the simulation like I need. Below is the module I want to develop and the over all block diagram.
Block diagram of process and vhdl module (clickable):
The following is the SPI timing diagram as follows (clickable):
The below is the VHDL code I wrote to acheive the above SPI communication:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use IEEE.NUMERIC_STD.all;
entity sender is
Generic( DATA_WIDTH : INTEGER := 32); --32 bit registers to pogram (total 8 registers to program)
Port ( sys_clk : in STD_LOGIC; --clock from microblaze (32 MHz)
enable : in STD_LOGIC; --from microblaze to trigger start
reset : in STD_LOGIC; --reset spi clk generation
start_data_read : out STD_LOGIC; --to microblaze as flag so that microblaze starts sending register_select codes (pgogram data for target register) and the data to pogram - to buffer
start_data_write : out STD_LOGIC; --to microblaze as flag so that microblaze starts sending register_select codes (pgogram data for target register) and the data to pogram - to mosi port
data_in : in STD_LOGIC_VECTOR(DATA_WIDTH-1 DOWNTO 0); --program data (32bit data in 8 phases depending on register_select)
process_done : out STD_LOGIC; --flag bit to microblaze when complete process is complete
register_select : in STD_LOGIC_VECTOR(2 downto 0); --select code to identify which register to grogram, theorder can be manupulated from microblaze
sclk : buffer STD_LOGIC; --spi clock for ADC (3.2 MHz)
ss : out STD_LOGIC := '1'; --select line for ADF (slave)
mosi : out STD_LOGIC); --program data for ADF
end sender;
architecture Behavioral of sender is
type machine is(store, sending); --states of state-machine
signal state : machine := store;
signal clk_divide: STD_LOGIC_VECTOR(5 downto 0); --clock cycle ratio between system clock from microblaze and spi clock for ADF
signal tx_buffer_0 : STD_LOGIC_VECTOR(DATA_WIDTH-1 DOWNTO 0); --IO Buffer to hold program data to mosi (register 0)
signal tx_buffer_1 : STD_LOGIC_VECTOR(DATA_WIDTH-1 DOWNTO 0); --IO Buffer to hold program data to mosi (register 1)
signal tx_buffer_2 : STD_LOGIC_VECTOR(DATA_WIDTH-1 DOWNTO 0); --IO Buffer to hold program data to mosi (register 2)
signal tx_buffer_3 : STD_LOGIC_VECTOR(DATA_WIDTH-1 DOWNTO 0); --IO Buffer to hold program data to mosi (register 3)
signal tx_buffer_4 : STD_LOGIC_VECTOR(DATA_WIDTH-1 DOWNTO 0); --IO Buffer to hold program data to mosi (register 4)
signal tx_buffer_5 : STD_LOGIC_VECTOR(DATA_WIDTH-1 DOWNTO 0); --IO Buffer to hold program data to mosi (register 5)
signal tx_buffer_6 : STD_LOGIC_VECTOR(DATA_WIDTH-1 DOWNTO 0); --IO Buffer to hold program data to mosi (register 6)
signal tx_buffer_7 : STD_LOGIC_VECTOR(DATA_WIDTH-1 DOWNTO 0); --IO Buffer to hold program data to mosi (register 7)
begin
ClK_GEN: process(sys_clk)
begin --spi sclk 20:1 cycles of sys_clk(system runs at 32 MHz and SPI CLK required is 1.6 MHz)
if rising_edge(sys_clk) then
if reset = '1' then
clk_divide <= (others => '0');
mosi <= 'Z'; --send high impedence
else
if clk_divide < "001010" then --10
sclk <= '0';
clk_divide <= clk_divide + 1;
else
if clk_divide < "010100" then --20
sclk <= '1';
else
clk_divide <= (others => '0');
end if;
end if;
end if;
end if;
end process ClK_GEN;
SEND: process(sclk,enable,register_select)
begin
if rising_edge(sclk) then
if enable = '1' then
case state is
when store =>
start_data_read <= '1'; --ask microblaze to send register_selectcodes and data
case register_select is
when "000" =>
tx_buffer_7 <= data_in; --copy data to buffer
when "001" =>
tx_buffer_6 <= data_in;
when "010" =>
tx_buffer_5 <= data_in;
when "011" =>
tx_buffer_4 <= data_in;
when "100" =>
tx_buffer_3 <= data_in;
when "101" =>
tx_buffer_2 <= data_in;
when "110" =>
tx_buffer_1 <= data_in;
when "111" =>
tx_buffer_0 <= data_in;
when others =>
tx_buffer_1 <= (OTHERS => '0');
end case;
state <= sending; --change state to next
when sending =>
start_data_write <= '1'; --ask microblaze to send register_select codes to pgrogram a register
case register_select is
when "000" =>
ss <= '0';
mosi <= tx_buffer_7(DATA_WIDTH-1);
tx_buffer_7 <= tx_buffer_7(DATA_WIDTH-2 downto 0) & '0';
ss <= '1';
when "001" =>
ss <= '0';
mosi <= tx_buffer_7(DATA_WIDTH-1);
tx_buffer_6 <= tx_buffer_7(DATA_WIDTH-2 downto 0) & '0';
ss <= '1';
when "010" =>
ss <= '0';
mosi <= tx_buffer_7(DATA_WIDTH-1);
ss <= '1';
when "011" =>
ss <= '0';
mosi <= tx_buffer_7(DATA_WIDTH-1);
tx_buffer_4 <= tx_buffer_7(DATA_WIDTH-2 downto 0) & '0';
ss <= '1';
when "100" =>
ss <= '0';
mosi <= tx_buffer_7(DATA_WIDTH-1);
tx_buffer_3 <= tx_buffer_7(DATA_WIDTH-2 downto 0) & '0';
ss <= '1';
when "101" =>
ss <= '0';
mosi <= tx_buffer_7(DATA_WIDTH-1);
tx_buffer_2 <= tx_buffer_7(DATA_WIDTH-2 downto 0) & '0';
ss <= '1';
when "110" =>
ss <= '0';
mosi <= tx_buffer_7(DATA_WIDTH-1);
tx_buffer_1 <= tx_buffer_7(DATA_WIDTH-2 downto 0) & '0';
ss <= '1';
when "111" =>
ss <= '0';
mosi <= tx_buffer_7(DATA_WIDTH-1);
tx_buffer_0 <= tx_buffer_7(DATA_WIDTH-2 downto 0) & '0';
ss <= '1';
when others =>
mosi <= '0';
end case;
end case;
end if;
end if;
end process SEND;
end Behavioral;
I also wrote a test bench code to send the above module, belowis the code:
LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
ENTITY test_sender IS
END test_sender;
ARCHITECTURE behavior OF test_sender IS
-- Component Declaration for the Unit Under Test (UUT)
COMPONENT sender
PORT(
sys_clk : IN std_logic;
enable : IN std_logic;
reset : IN std_logic;
start_data_read : OUT std_logic;
start_data_write : OUT std_logic;
data_in : IN std_logic_vector(31 downto 0);
process_done : OUT std_logic;
register_select : IN std_logic_vector(2 downto 0);
sclk : buffer std_logic;
ss : OUT std_logic;
mosi : OUT std_logic
);
END COMPONENT;
--Inputs
signal sys_clk : std_logic := '0';
signal enable : std_logic := '0';
signal reset : std_logic := '0';
signal data_in : std_logic_vector(31 downto 0) := (others => '0');
signal register_select : std_logic_vector(2 downto 0) := (others => '0');
--Outputs
signal start_data_read : std_logic;
signal start_data_write : std_logic;
signal process_done : std_logic;
signal sclk : std_logic;
signal ss : std_logic;
signal mosi : std_logic;
-- Clock period definitions
constant clk_sys_period : time := 31.25 ns;
BEGIN
-- Instantiate the Unit Under Test (UUT)
uut: sender PORT MAP (
sys_clk => sys_clk,
enable => enable,
reset => reset,
start_data_read => start_data_read,
start_data_write => start_data_write,
data_in => data_in,
process_done => process_done,
register_select => register_select,
sclk => sclk,
ss => ss,
mosi => mosi
);
-- Clock process definitions
clk_sys_process :process
begin
sys_clk <= '0';
wait for clk_sys_period/2;
sys_clk <= '1';
wait for clk_sys_period/2;
end process;
-- Stimulus process
stim_proc: process
begin
-- hold reset state for 100 ns.
wait for 1ns;
enable <= '1';
if start_data_read = '1' then
wait for 1ns;
register_select <= "000";
wait for 1 ns;
data_in <= "01001111001101010101010111100001";
wait for 5 ns;
register_select <= "001";
wait for 1 ns;
data_in <= "11001111001101010111011111100101";
wait for 5 ns;
register_select <= "010";
wait for 1 ns;
data_in <= "00000011001101010101011101100101";
wait for 5 ns;
register_select <= "011";
wait for 1 ns;
data_in <= "00011111001101010101010011100101";
wait for 5 ns;
register_select <= "100";
wait for 1 ns;
data_in <= "10001111001101010101011111100001";
wait for 5 ns;
register_select <= "101";
wait for 1 ns;
data_in <= "11001111001101010101011110000101";
wait for 5 ns;
register_select <= "110";
wait for 1 ns;
data_in <= "00101000001101010101011111100101";
wait for 5 ns;
register_select <= "111";
wait for 1 ns;
data_in <= "11111111001101010101011110100101";
wait for 5 ns;
end if;
if start_data_write = '1' then
wait for 1ns;
register_select <= "000";
wait for 5 ns;
register_select <= "001";
wait for 5 ns;
register_select <= "010";
wait for 5 ns;
register_select <= "011";
wait for 5 ns;
register_select <= "100";
wait for 5 ns;
register_select <= "101";
wait for 5 ns;
register_select <= "110";
wait for 5 ns;
register_select <= "111";
wait for 5 ns;
end if;
-- insert stimulus here
wait;
end process;
END;
The simulation is not working properly, and so the code is wrong, but I can't find out where is the mistake, it would be great if some one can help me and make me understand my mistake.
Do you mean to do a data_read and data_write in this simulation? In RTL we can do a check like the following because we look many times (probably on an edge of clock):
if start_data_read = '1' then
However your testbench process does not loop. So instead of doing an "IF" the way we do in RTL code, you need to do something to cause your test to stop until that condition exists. So your test would be the following:
stim_proc: process
begin
-- hold reset state for 100 ns.
wait for 1ns;
enable <= '1';
if start_data_read /= '1' then
wait until start_data_read = '1' ;
end if ;
-- Do a read sequence
. . .
if start_data_write /= '1' then
wait until start_data_write = '1' ;
end if ;
-- Do a write sequence
Within your read and write sequence, you have "wait for 5 ns". Can your device really accept operations this quickly? This is faster than a clock cycle. Are the operations in any way aligned with clock? You can wait for clock cycles by doing either of the following:
wait until rising_edge(Clk) ;
wait until Clk = '1' ; -- assumes Clk is only '0' or '1' (safe for clocks)
Once you get the basics working, you might further consider encapsulating a single write or read operation into a subprogram.