Commit 475932c2 authored by Jan Pospisil's avatar Jan Pospisil

added entity for automatic control of AD9512 divider + testbench

parent bb9540eb
------------------------------------------------------------------------
-- Title : AD9512 Control
-- Project : http://www.ohwr.org/projects/fmc-del-1ns-2cha/
------------------------------------------------------------------------
-- File : Ad9512Control.vhd
-- Author : T. Levens, J. Pospisil
-- Company : CERN BE-BI-QP
-- Created : 2015-08-17
-- Last update: 2016-08-18
-- Platform : FPGA-generic
-- Standard : VHDL
------------------------------------------------------------------------
-- Description:
--
-- AD9512 control using OpenCores SPI master.
--
-- Based on https://gitlab.cern.ch/bi/mim/blob/master/hdl/rtl/PllControl.v
------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity Ad9512Control is
generic (
g_ClkFrequency: positive -- input clock frequency in Hz
);
port (
Clk_ik: in std_logic;
Rst_ir: in std_logic;
Cfg_i: in std_logic;
Busy_o: out std_logic;
ClockSelection_i: in std_logic; -- 0 - CLK1, 1 - CLK2
ClockRatioMinus1_ib: in unsigned(4 downto 0);
SpiAd9512Sclk_o: out std_logic;
SpiAd9512Mosi_o: out std_logic;
SpiAd9512Miso_i: in std_logic;
SpiAd9512Cs_on: out std_logic
);
end entity;
architecture syn of Ad9512Control is
--! computes least possible width of the vector to store value X
-- http://stackoverflow.com/a/12751341/615627
function f_log2(x: natural) return natural is
variable i: natural;
begin
i := 0;
while (2**i < x) loop
i := i + 1;
end loop;
return i;
end function;
function if_slv(condition: boolean; pos: std_logic_vector; neg: std_logic_vector) return std_logic_vector is begin
if condition then
return pos;
else
return neg;
end if;
end function;
------------------------------------------------------------------------
-- Input registers for config
------------------------------------------------------------------------
signal ClockSelection: std_logic := '0';
signal ClockRatioMinus1_b: unsigned(ClockRatioMinus1_ib'range) := to_unsigned(2-1, ClockRatioMinus1_ib'length);
------------------------------------------------------------------------
-- AD9512 config registers
------------------------------------------------------------------------
type t_Ad9512Register is record
Address: std_logic_vector(7 downto 0);
Data: std_logic_vector(7 downto 0);
end record;
function f_Ad9512Register2WriteInstruction(Reg: t_Ad9512Register) return std_logic_vector is
variable ZerosToPad: integer := 32-Reg.Address'length-Reg.Data'length;
begin
return std_logic_vector(to_unsigned(0, ZerosToPad)) & Reg.Address & Reg.Data;
end function;
constant c_Ad9512RegisterCount: positive := 14;
type t_Ad9512RegisterFiled is array (integer range <>) of t_Ad9512Register;
signal c_Ad9512Registers: t_Ad9512RegisterFiled(0 to c_Ad9512RegisterCount-1);
-- calculate cycles according to provided ratio
function f_CreateDivideReg1(ClockRatioMinus1_b: unsigned) return std_logic_vector is
variable LowCycles, HighCycles: unsigned(3 downto 0);
begin
HighCycles := ClockRatioMinus1_b(4 downto 1);
if ClockRatioMinus1_b(0) = '1' then
LowCycles := HighCycles;
else
LowCycles := HighCycles-1;
end if;
if ClockRatioMinus1_b = "00000" then
return "00000000";
else
return std_logic_vector(LowCycles & HighCycles);
end if;
end function;
-- divider bypass, phase, ...
function f_CreateDivideReg2(ClockRatioMinus1_b: unsigned) return std_logic_vector is
variable PhaseOffset: std_logic_vector(3 downto 0) := "0000";
variable StartHL: std_logic := '0';
variable ForceState: std_logic := '0';
variable NoSync: std_logic := '0';
variable Bypass: std_logic;
begin
if ClockRatioMinus1_b = "00000" then
Bypass := '1';
else
Bypass := '0';
end if;
return Bypass & NoSync & ForceState & StartHL & PhaseOffset;
end function;
------------------------------------------------------------------------
-- SPI core config
------------------------------------------------------------------------
function f_SpiCalcDivider(WbFreq, SpiMaxFreq: positive) return integer is begin
if WbFreq < 2 * SpiMaxFreq then
return 0;
else
return (WbFreq / (SpiMaxFreq * 2));
end if;
end function;
constant c_SpiMaxFrequency: positive := 25e6; -- in Hz
-- ---------------------- ASS
-- / ------------------- IE
-- / / ---------------- LSB
-- / / / ------------- Tx_NEG
-- / / / / ---------- Rx_NEG
-- / / / / / ------- GO_BSY
-- / / / / / /
-- / / / / / / --- CHAR_LEN
-- / / / / / / /
constant c_SpiCtrlReg_b32: -- | | | | | | |
std_logic_vector(13 downto 0) := ("1"&"1"&"0"&"1"&"1"&"0" & X"18");
constant c_SpiGo_b32: -- | | | | | | |
std_logic_vector(13 downto 0) := ("0"&"0"&"0"&"0"&"0"&"1" & X"00");
constant c_SpiSs: std_logic_vector(31 downto 0) := X"00000001";
constant c_SpiDivider: std_logic_vector(31 downto 0) :=
std_logic_vector(to_unsigned(f_SpiCalcDivider(g_ClkFrequency, c_SpiMaxFrequency), 32));
-- WB control signals
signal WbAdr_b5: std_logic_vector(4 downto 0);
signal WbDatIn_b32: std_logic_vector(31 downto 0);
signal WbDatOut_b32: std_logic_vector(31 downto 0);
signal WbStb: std_logic;
signal WbWe: std_logic;
signal WbAck: std_logic;
signal WbInt: std_logic;
-- FSM
type t_State is (
S_INIT,
S_DIVIDER,
S_SS,
S_CTRL,
S_IDLE,
S_WR_DATA,
S_WR_CTRL,
S_WAIT,
S_COUNT_DEC
);
constant c_ResetState: t_State := S_INIT;
signal SeqCurrentState: t_State := c_ResetState;
-- Write counter
signal SeqCnt: unsigned(3 downto 0) := (others => '0');
signal SeqCntEnable, SeqCntReset: std_logic := '0';
signal SeqCntOverflow: std_logic;
signal SpiAd9512Cs_n: std_logic_vector(0 downto 0);
begin
cConfigInputRegisters: process (Clk_ik) is begin
if rising_edge(Clk_ik) then
if Cfg_i = '1' then
ClockSelection <= ClockSelection_i;
ClockRatioMinus1_b <= ClockRatioMinus1_ib;
end if;
end if;
end process;
c_Ad9512Registers <= (
-- power down unused output (OUT2)
(X"3f", X"03"),
-- select CLK1 input, power down CLK2 input
(X"45", if_slv(ClockSelection = '0', X"05", X"02")),
-- output frequency 200 MHz (divide by 2)
(X"4a", f_CreateDivideReg1(ClockRatioMinus1_b)), -- OUT0
(X"4c", f_CreateDivideReg1(ClockRatioMinus1_b)), -- OUT1
(X"4e", f_CreateDivideReg1(ClockRatioMinus1_b)), -- OUT2
(X"50", f_CreateDivideReg1(ClockRatioMinus1_b)), -- OUT3
(X"52", f_CreateDivideReg1(ClockRatioMinus1_b)), -- OUT4
-- phase 0
(X"4b", f_CreateDivideReg2(ClockRatioMinus1_b)), -- OUT0
(X"4d", f_CreateDivideReg2(ClockRatioMinus1_b)), -- OUT1
(X"4f", f_CreateDivideReg2(ClockRatioMinus1_b)), -- OUT2
(X"51", f_CreateDivideReg2(ClockRatioMinus1_b)), -- OUT3
(X"53", f_CreateDivideReg2(ClockRatioMinus1_b)), -- OUT4
-- function pin as sync
(X"58", X"20"),
-- confirm write
(X"5a", X"01")
);
cRegisterCounter: entity work.Counter(syn)
generic map (
g_Width => f_log2(c_Ad9512RegisterCount),
g_Limit => c_Ad9512RegisterCount
)
port map (
Clk_ik => Clk_ik,
Reset_ir => SeqCntReset,
Enable_i => SeqCntEnable,
Set_i => '0',
SetValue_ib => (others => '0'),
Overflow_o => SeqCntOverflow,
Value_ob => SeqCnt
);
pFsmTransitions: process(Clk_ik) is begin
if rising_edge(Clk_ik) then
if Rst_ir = '1' then
SeqCurrentState <= c_ResetState;
else
SeqCurrentState <= SeqCurrentState;
case SeqCurrentState is
when S_INIT =>
SeqCurrentState <= S_DIVIDER;
when S_DIVIDER =>
if WbAck = '1' then
SeqCurrentState <= S_CTRL;
end if;
when S_CTRL =>
if WbAck = '1' then
SeqCurrentState <= S_SS;
end if;
when S_SS =>
if WbAck = '1' then
SeqCurrentState <= S_WR_DATA;
end if;
when S_IDLE =>
if Cfg_i = '1' then
SeqCurrentState <= S_WR_DATA;
end if;
when S_WR_DATA =>
if WbAck = '1' then
SeqCurrentState <= S_WR_CTRL;
end if;
when S_WR_CTRL =>
if WbAck = '1' then
SeqCurrentState <= S_WAIT;
end if;
when S_WAIT =>
if (WbInt = '1') and (SeqCntOverflow = '1') then
SeqCurrentState <= S_IDLE;
elsif WbInt = '1' then
SeqCurrentState <= S_COUNT_DEC;
end if;
when S_COUNT_DEC =>
SeqCurrentState <= S_WR_DATA;
end case;
end if;
end if;
end process;
pFsmOutputs: process (SeqCurrentState, SeqCnt) is begin
WbAdr_b5 <= (others => '0');
WbDatIn_b32 <= (others => '0');
WbStb <= '0';
WbWe <= '0';
Busy_o <= '1';
SeqCntReset <= '0';
SeqCntEnable <= '0';
case SeqCurrentState is
when S_INIT => -- Do nothing
null;
when S_DIVIDER =>
WbAdr_b5 <= std_logic_vector(to_unsigned(16#14#, 5));
WbDatIn_b32 <= c_SpiDivider;
WbStb <= '1';
WbWe <= '1';
when S_CTRL =>
WbAdr_b5 <= std_logic_vector(to_unsigned(16#10#, 5));
WbDatIn_b32(13 downto 0) <= c_SpiCtrlReg_b32;
WbStb <= '1';
WbWe <= '1';
when S_SS =>
WbAdr_b5 <= std_logic_vector(to_unsigned(16#18#, 5));
WbDatIn_b32 <= c_SpiSs;
WbStb <= '1';
WbWe <= '1';
SeqCntReset <= '1';
when S_IDLE =>
Busy_o <= '0';
SeqCntReset <= '1';
when S_WR_DATA =>
WbAdr_b5 <= std_logic_vector(to_unsigned(16#00#, 5));
WbDatIn_b32 <=
f_Ad9512Register2WriteInstruction(c_Ad9512Registers(to_integer(SeqCnt)));
WbStb <= '1';
WbWe <= '1';
when S_WR_CTRL =>
WbAdr_b5 <= std_logic_vector(to_unsigned(16#10#, 5));
WbDatIn_b32(13 downto 0) <= c_SpiCtrlReg_b32 or c_SpiGo_b32;
WbStb <= '1';
WbWe <= '1';
when S_WAIT => -- do nothing
null;
when S_COUNT_DEC =>
SeqCntEnable <= '1';
end case;
end process;
------------------------------------------------------------------------
-- Wishbone SPI master
------------------------------------------------------------------------
i_spi_top: entity work.spi_top
generic map (
SPI_SS_NB => 1
)
port map (
wb_clk_i => Clk_ik,
wb_rst_i => Rst_ir,
wb_adr_i => WbAdr_b5,
wb_dat_i => WbDatIn_b32,
wb_dat_o => WbDatOut_b32,
wb_sel_i => (others => '1'),
wb_we_i => WbWe,
wb_stb_i => WbStb,
wb_cyc_i => WbStb,
wb_ack_o => WbAck,
wb_err_o => open,
wb_int_o => WbInt,
ss_pad_o => SpiAd9512Cs_n,
sclk_pad_o => SpiAd9512Sclk_o,
mosi_pad_o => SpiAd9512Mosi_o,
miso_pad_i => SpiAd9512Miso_i
);
SpiAd9512Cs_on <= SpiAd9512Cs_n(0);
end architecture;
\ No newline at end of file
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity Ad9512Control_tb is
end entity;
architecture testbench of Ad9512Control_tb is
constant c_ClkFrequency: positive := 100_000_000;
constant c_ClkPeriod: time := (1 sec)/real(c_ClkFrequency);
signal Clk_ik, Rst_ir, Cfg_i, Busy_o: std_logic := '0';
signal ClockSelection_i: std_logic := '0';
signal ClockRatioMinus1_ib: unsigned(4 downto 0) := to_unsigned(2-1, 5);
signal SpiAd9512Sclk_o, SpiAd9512Mosi_o, SpiAd9512Miso_i, SpiAd9512Cs_on: std_logic := '0';
subtype t_Ratio is Integer range 1 to 32;
procedure f_Tick(ticks: in natural) is begin
wait for ticks * c_ClkPeriod;
end procedure;
begin
cDUT: entity work.Ad9512Control(syn)
generic map (
g_ClkFrequency => c_ClkFrequency
)
port map (
Clk_ik => Clk_ik,
Rst_ir => Rst_ir,
Cfg_i => Cfg_i,
Busy_o => Busy_o,
ClockSelection_i => ClockSelection_i,
ClockRatioMinus1_ib => ClockRatioMinus1_ib,
SpiAd9512Sclk_o => SpiAd9512Sclk_o,
SpiAd9512Mosi_o => SpiAd9512Mosi_o,
SpiAd9512Miso_i => SpiAd9512Miso_i,
SpiAd9512Cs_on => SpiAd9512Cs_on
);
pClk: process is begin
Clk_ik <= '0';
wait for c_ClkPeriod/2;
Clk_ik <= '1';
wait for c_ClkPeriod/2;
end process;
pTest: process is
variable Ratio: t_Ratio;
begin
Rst_ir <= '1';
f_Tick(5);
Rst_ir <= '0';
f_Tick(2300);
ClockSelection_i <= '1';
Ratio := 23;
ClockRatioMinus1_ib <= to_unsigned(Ratio-1, ClockRatioMinus1_ib'length);
Cfg_i <= '1';
f_Tick(1);
ClockSelection_i <= '0';
Ratio := 10;
ClockRatioMinus1_ib <= to_unsigned(Ratio-1, ClockRatioMinus1_ib'length);
Cfg_i <= '0';
f_Tick(2300);
assert false report "NONE. End of simulation." severity failure;
wait;
end process;
end architecture;
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment