Xilinx Project: Arty

The Xilinx Arty Board

The Artix-7 App Board

 

This originally started off as making a general purpose VHDL Vivado project for the Arty board to serve as a sort of Arty Base project or Arty Master project.  There already exists an Arty Base project out there, but I don’t like it. 

It’s a BD project and relies on a softcore μ-Blaze CPU and lots of Xilinx IP.  I see no reason to rely on a RISC processor and the required Peripherals not to mention their drivers and SW to go along with it. 

There’s an abundance of VHDL source code out there and it’s easy enough to code your own once you get a hang of writing VHDL.   Why does a UART or any or the other standard interfaces need a microprocessor?  

Unfortunately VHDL is much like chess and although you could be coding away in your first few days, it take years to master.  Fortunately there are no shortage of tutorials and example code to play with.  (Note: if you use someone else’s code you should probably credit them if not ask their permission.  There’s an honor code between engineers that should be respected.)

Within an hour of planning the Vivado project I realized that some sort of target application would be needed to make the process more authentic.  This epiphany came to light when instantiating the MMCME2_ADV clocking module in the Clock Management Tile (CMT) .  What clock frequencies do I need?  So I chose a little  audio project with A/D and D/A converters we can interface via the PMOD connectors.

This would make a  good intermediate level project for the Arty board and could be used later  for digital processing projects using the embedded 7 Series DSP48E1 Slice. 

7 Series FPGA DSP48E1 Slice

 

I got a few things together to make cables to the headers and got a Cirrus CS5340,  24-bit 192 KHz ( 101 dB dynamic range), dual channel serial A/D converter.  This is  a proper HD audio A/D converter in an  TSSOP-16 package.

The CS5340 is a complete analog-to-digital converter for digital audio systems. It performs sampling, A/D conversion, and anti-alias filtering, generating 24-bit values for both left and right inputs in serial form at sample rates up to 200 kHz per channel. The CS5340 uses a 5th-order, multi-bit Delta-Sigma modulator followed by digital filtering and decimation, which removes the need for an external anti-alias filter.

CS5340 Simplified Functional Block Diagram

 

According to the spec sheet, this part can operate at our 3.3 VDC or the USB 5 VDC, so we should check the specs to see if we want to go through the trouble of running the A/D at 3.3V or 5V.  (You can click on the CS5340 link above to view the datasheet yourself as I have only taken the clips of the parts discussed here.  I encourage you to find flaws in my design.  We all make mistakes and I like to correct my mistakes and won’t take it personally if you find flaws.)

Power Supply Operating Range

 

looking further into the specs we find the Absolute Maximum Ratings

We can see that the USB voltage is safe as it should never exceed 5.5VDC.  We also see that exceeding these ABSOLUTE MAX ratings should not be exceeded at any time, so we need good surge suppression if we operate at the USB supply voltage whereas the 3.3 VDC gives  us plenty of head room.  While the power input current must not exceed 10mA, the logic and analog inputs could surge to 100mA.  Later we’ll see if this deice will need a heat sink to prevent the device from exceeding 95 ºC.

Now we need to see what we have to gain or lose by using our yet TBD Supply Voltage.   We’ll be operating this in Quad-Speed Mode to achieve our desired sampling rate of 192 KHz.

We can see right away that we don’t lose much by going with 3.3V as our supply voltage. Two (2) or three (3) dB at a reference dynamic range ≈ 100dB will not be noticeable.

 

 

For the D/A side The Cirrus CS4350 is , 24-Bit, 192-kHz Stereo D/C with Integrated PLL in TSSOP-24 package.  We can use the MCLK output of this to clock the D/A converter.

Before going too far off on a tangent, I have a full HD-Audio CODEC that sits on a FPGA Mezzanine Card (FMC) MAX11905ETP+ to be used for a project on the Xilinx Artix-7 AC701 app board.  It is made by Maxim (acquired by Analog Devices since purchased) and now has a great support page.  

That MAX11905 CODEC is a 20-bit part with a sampling rate  of 1.6MS/sec which should give us the ability to have 4x oversampling for an audio sample rate 192 KHz.  For true HD audio you need 20-bits and a 192KHz sampling rate.  We can discuss this at another time, but you only need 20-bits, not 24 and certainly not 32. 

Let’s flip to the other end where we convert back to analog with a DAC.  Most commercial electronic regulators have about 150uV peak-to-peak noise and the best ultralow-noise power regulators used in the best-of-the-best of audiophile electronics have about 5uV of peak-to-peak noise. So even the 5V output of a balanced DAC could not resolve anything close to the LSB voltage of a 24-bit audio.     

Based on a 5 V output of a MAX5717 single-ended DAC , below are the voltages power supply noise must be below in order to hear the LSB:

  • 16-bit LSB noise floor voltage = 35 uV  
  • 20-bit LSB noise floor voltage = 2.36 uV
  • 24-bit LSB noise floor voltage = 0.15 uV

So anything over 20-bits is pointless.  We may as well lop-off those last 4-bits truncating the 24-bit to a 20-bit, but this would probably be easier to do inside the FPGA for signal processing.

Regarding sampling rate, you may think that even at 20 KHz the required minimum  sampling rate to satisfy the 2 x Fmax Nyquist sampling rate, even 55KHz is more than sufficient.  This is a common misconception because it ignores time domain requirements.  Humans can hear the influence of a 45 kHz frequency superimposed on a 15 kHz fundamental, even though humans cannot hear the 45 kHz frequency when it is emitted alone.  Think of the sound of cracking a whip.  We often think of transients as a bad thing in signal analysis, but for music, it is essential for attack.

So we can stop here with the audio signal analysis for music.  20-bits at 192 KHz is what we want.  In the future I would like to look into using 2 x 96 KHz, shifting the sample time of one clock and interleave the samples to make a 192 KHz A/D out of the two (2) 96 KHz parts.  Using serialized data, this might not be so straight forward.

I will be making a page to discuss the audio theory and hope to get input from others to make a better project.  This will have to wait for another day.

Now back to the project requirements…

 

Cables and Connectors

We’ll also need to build cables from the IDC headers.  I bought 10 on eBay :

10Pcs 2.54mm 2×6 Pin 12 Pin Male Header IDC Ribbon Cable Transition Connector  $3.79 w free shipping.

I have plenty of ribbon cable, so when the parts get here in 13 months or however long it takes to ship from China.  I’d do Digikey, but you have to spend like 100’s of $$$ to get free shipping.  Last time I got suckered into buying an Altera Max-10 app board for $50 bucks, but I can’t program it without the $300 cable!  It’s worthless. Strike 1 Intel, you’re already making Altera suck.

Getting started

so I have the D2A with my proto-board and IDC row pin header, so I’ll solder it to the board and then write some VHDL code to interface to this thing.

 

Choose JA and JD to save diff I/O ports for future use
Strategize connection to female IDC-12 on board

The Vivado Project

Let’s get our Vivado project started. I try to keep my Vivado tools up to date, so I will be re-writing this periodically. This update is being written on Dec 29, 2021 with the 2021.2 ver of Vivado.

The Arty Board (see schematic) has a 100.0 MHz oscillator as the primary clock input. We need to generate clock for out A/D and D/A converters. We can generate a 100MHz internal clock system clock from this and attempt to generate clocks to make our design job a little easier.

The D/A converter device requires external generation of the left/right (LRCK) and serial (SCLK) clocks. The left/right clock frequency is equal to the input sample rate (Fs = 192 KHz). A design guide for this part can be found here. The required is SCLK 55.3 MHz

We will generate SCLK from one of the Artix-7 CMT Tile, MMCME2_ADV clock modules. The LRCK has a period of 5.2083 us which would result in a .3 ns error which is not ideal, but well within acceptable tolerances.

Open a new Vivado project and select the Arty Board as the target device. (you may have to update your board files.)

Start Vivado (I run as admin) and choose open a new project

Open A New Proj Arty_Master

Select the default RTL Project for project type and check the Do not specify sources at this time box.

“Project Type” Screen

Select the Arty Board as the target .

Now Create a new VHDL file CMT_rst.vhd.

Add or Create Design sources

We need to start with the Power on Reset for the CMT. We can achieve this with a 16-bit shift register, initialized to all 1’s and shift a logic ‘0’ through. If we attach the MSB to the CMT reset input, the active high reset will remain asserted for the first 16, 100 MHz cycles.

Select Add or Create New Design Source and CREATE new source called, CMT_rst.

Just hit finish without adding signals to the port because you’re just going to cut and paste the CMT_rst code below:

entity CMT_rst is

port 
( 
  Clk              : in    std_logic;
  Rst_out          : out   std_logic
 );
end CMT_rst;

-- ------------------------------------------------------------------------------------------
-- Architecture 
-- ------------------------------------------------------------------------------------------
architecture rtl of CMT_rst is

-- -----------------------------------------------------------------------------
-- Signals
-- -----------------------------------------------------------------------------

--  MMCME2_ADV - Power on Reset
signal start     : unsigned(15 downto 0) := (others => '1');  -- init Reg to all 1's


begin

  -- ---------------------------------------------------------------------------  
  --  MMCME2_ADV - Power on Reset
  -- ---------------------------------------------------------------------------
  --
CMT_rst_P:  process (Clk)
  begin
    if rising_edge(Clk) then
      start <= start(14 downto 0) & '0';
    end if;    
  end process CMT_rst_P;


    Rst_out <= start(15);
 

end rtl;

When you save the file it will show up in the Sources Pane. Click on the file and observe the Source File Properties pane.

Change the File Type from VHDL to “VHDL-2008” and change the library to “work”.

Do this for ALL the design files you will be adding to the project.

Instantiating the MMCM (Memory Management Clocking Module)

This can be done using a Xilinx Clocking Wizard, but in the spirit of maintaining the
project as close to a true HDL coded design as possible and to better describe what is actually being implemented. The parameters of the instantiated MMCM are shown and the process of determining their values is briefly explained in the code comments.

The Voltage Controlled Oscillator (VCO) in the MMCM needs to operate btw 600 and 1600 MHz. The Vco frequency is the product input frequency (100MHz) and the feedback out multiplier, M. If we choose a multiplier of 11.06
Vco = Fin * M => 100 MHz * 11.06 = 11,060 MHz

For this design the input clock from the CMOD board is 100 MHz which is a
clock period of 10.00 ns. The other generic parameters
A Freq FB multiplier of 11.06 and div by 11.06 get’s us the base 100 MHz clock and a
div by 20 gets us a the 53.3 MHz SCLK.

see Xilinx doc: ug472_7Series Clocking Resources

----------------------------------------------------------------------------------
-- Company: Trailing Edge Technologies
-- Engineer: Patrick Donaldson
-- 
-- Create Date: 09/15/2021 01:04:20 PM
-- Design Name: 
-- Module Name: CMT_Clk - RTL
--  
-- Project Name: Arty_Master
-- 
-- Target Devices: Artty-35T 
-- Tool Versions: Vivado 2021.2
-- VHDL ver. 2008
--   
-- Dependencies: 
-- 
-- Revision 0.01 - File Created
-- Additional Comments:
--   This instantiates an MMCM (Memory Management Clocking Module)
--  from a CMT module on the device.  This can be done using a 
--  Xilinx Clocking Wizard, but in the spirit of maintaining the 
--  project as close to a true HDL coded design as possible and to 
--  better describe what is actually being implemented. The parameters 
--  of the instantiated MMCM are shown and the process of determining 
--  their values is briefly explained in the code comments.  
----------------------------------------------------------------------------------

library ieee;
use     ieee.std_logic_1164.all;
use     ieee.numeric_std.all;

library unisim;
use     unisim.vcomponents.all;

-- ------------------------------------------------------------------------------------------
-- Entity 
-- ------------------------------------------------------------------------------------------
entity clock is
port 
( 
  clk_in1           : in     std_logic;
  clk_out1          : out    std_logic;
  clk_out2          : out    std_logic;
  reset             : in     std_logic;
  locked            : out    std_logic
);
end clock;

-- ------------------------------------------------------------------------------------------
-- Architecture 
-- ------------------------------------------------------------------------------------------
architecture rtl of clock is

--  MMCME2_ADV
signal clkfbout_clock         : std_logic; 
signal clk_out1_clock         : std_logic; -- 100 MHz
signal clk_out2_clock         : std_logic;

--  BUFG - Feedback
signal clkfbout_buf_clock     : std_logic;



begin
-- ---------------------------------------------------------------------------  
  --  MMCME2_ADV
  
    -- ---------------------------------------------------------------------------  
  --  Instantiate 7-Series MMCME2_ADV Mixed-Mode Clock Manager (MMCM)
  --
  --  For this design the input clock from the CMOD board is 100 MHz which is a 
  -- clock period of 10.00 ns. The other generic parameters  
  -- A Freq FB multiplier of 11.06 and div by 11.06 get's us the 100 MHz clock and a 
  -- div by 20 gets us a 53.3 MHz clock which is the SCLK frequency of the D/A converter
  -- version of this prototype
  --
  -- The Voltage Controlled Oscillator (VCO) in 
  -- the MMCM freq of the Vco needs to operate btw 600 and 1600 MHz. The Vco freq is 
  -- the product input frequency (100MHz) and the feedback out multiplier, M.
  --   Vco = Fin * M => 1,106 MHz
  --   M = Vc0/Fin = 1,106/20 = 53.3 MHz
  -- 
  -- see Xilinx doc: ug472_7Series Clocking Resources
  -- ---------------------------------------------------------------------------
  -- ---------------------------------------------------------------------------
  --
  -- ---------------------------------------------------------------------------  
  --                    *** MMCME2_ADV  ***
  -- ---------------------------------------------------------------------------
  --
  MMCM_ADV_I : MMCME2_ADV
  generic map
  (
    BANDWIDTH            => "OPTIMIZED",
    CLKOUT4_CASCADE      => FALSE,
    COMPENSATION         => "ZHOLD",
    STARTUP_WAIT         => FALSE,
    DIVCLK_DIVIDE        => 1,
    CLKFBOUT_MULT_F      => 11.06,     -- VCO = 11.06*100MHz = 1,106 MHz (must be btw 600 and 1600)
    CLKFBOUT_PHASE       => 0.000,
    CLKFBOUT_USE_FINE_PS => FALSE,
    CLKOUT0_DIVIDE_F     => 11.06,
    CLKOUT0_PHASE        => 0.000,
    CLKOUT0_DUTY_CYCLE   => 0.500,
    CLKOUT0_USE_FINE_PS  => FALSE,
    CLKIN1_PERIOD        => 10.0,
    CLKOUT1_DIVIDE       => 20,            -- div by 20 for 53.3 MHz
    CLKOUT1_DUTY_CYCLE   => 0.500000,
    CLKOUT1_PHASE        => 0.000000,
    CLKOUT1_USE_FINE_PS  => false,
    REF_JITTER1          => 0.010000,
    SS_EN                => "FALSE",
    SS_MODE              => "CENTER_HIGH",
    SS_MOD_PERIOD        => 10000
  )
  port map
  (
    -- Output clocks
    CLKFBOUT             => clkfbout_clock,             -- O
    CLKFBOUTB            => open,                       -- O
    CLKOUT0              => clk_out1_clock,             -- O
    CLKOUT0B             => open,                       -- O
    CLKOUT1              => clk_out2_clock,             -- O
    CLKOUT1B             => open,                       -- O
    CLKOUT2              => open,                       -- O
    CLKOUT2B             => open,                       -- O
    CLKOUT3              => open,                       -- O
    CLKOUT3B             => open,                       -- O
    CLKOUT4              => open,                       -- O
    CLKOUT5              => open,                       -- O
    CLKOUT6              => open,                       -- O
    -- Input clock control
    CLKFBIN              => clkfbout_buf_clock,         -- I
    CLKIN1               => clk_in1,                    -- I External
    CLKIN2               => '0',
    -- Tied to always select the primary input clock
    CLKINSEL             => '1',                        -- I
    -- Ports for dynamic reconfiguration
    DADDR                => (others => '0'),            -- I [6:0]
    DCLK                 => '0',                        -- I
    DEN                  => '0',                        -- I
    DI                   => (others => '0'),            -- I [15:0]
    DO                   => open,                       -- O
    DRDY                 => open,                       -- O
    DWE                  => '0',                        -- I
    -- Ports for dynamic phase shift
    PSCLK                => '0',                        -- I
    PSEN                 => '0',                        -- I
    PSINCDEC             => '0',                        -- I
    PSDONE               => open,
    -- Other control and status signals
    LOCKED               => locked,                     -- O External
    CLKINSTOPPED         => open,                       -- O
    CLKFBSTOPPED         => open,                       -- O
    PWRDWN               => '0',                        -- I
    RST                  => reset                       -- I External
  );

  -- ---------------------------------------------------------------------------  
  --  BUFG - Feedback
  -- ---------------------------------------------------------------------------
  --
  CLKFB_BUFG_I : BUFG
  port map
  (
    O                    => clkfbout_buf_clock,          -- O 
    I                    => clkfbout_clock               -- I
  );

  -- ---------------------------------------------------------------------------  
  --  BUFG - CLKOUT1
  -- ---------------------------------------------------------------------------
  --
  CLKOUT1_BUFG_I : BUFG
  port map
  (
    O                    => clk_out1,
    I                    => clk_out1_clock
  );

end rtl;

Be sure to click on the new file in the Sources Pane and change file type to to VHDL-2008 and the Library to Work. You should periodically select the “Libraries” Tab in the Sources Pane to make sure ALL of the files are VHDL-2008 and in the Work Library.

We should now make a Top Level VHDL file and instantiate the two (2) VHDL files we have just created and instantiate them into the Top Level.

I have gone ahead and instantiated VHDL components that will be covered in the following sections. You can go ahead and create the top level VHDL file Arty_Master.vhd and cut and paste the following code block.

It might be helpful to view the Language Templates from the Flow Navigator Pane and locate the “Device Primitive Instantiation” templates ” under the VHDL folder: IBUF, BUFG, BUFIO and we have already configured an MMCME2_ADV clock component.

Locating VHDL Primitive Instantiation Templates

These other primitives are instantiated in the Top Level project source file below.

----------------------------------------------------------------------------------
-- Company: Trailing Edge Technologies
-- Engineer: Patrick Donaldson
-- 
-- Create Date: 09/15/2021 01:04:20 PM
-- Design Name: 
-- Module Name: Arty_Master_top  
--  
-- Project Name: Arty_Master
-- 
-- Target Devices: Artty-35T 
-- Tool Versions: Vivado 2021.2
-- VHDL ver. 2008
--   
-- Dependencies: 
-- 
-- Revision 0.01 - File Created
-- Additional Comments:
----------------------------------------------------------------------------------
library ieee;
use     ieee.std_logic_1164.all;
use     ieee.numeric_std_unsigned.all;

use     work.common_pkg.all;
use     work.Arty_pkg.all;

library unisim;
use     unisim.vcomponents.all;

-- ----------------------------------------------------------------------------- 
--               ***  Entity  *** 
-- ----------------------------------------------------------------------------- -

entity Arty_Master_top is
generic 
    (
      SIM_BLN                : boolean := false;        -- Set to true in TB to save SIM time
      DSBL_STRTP_MSG_BLN     : boolean := false;        -- Set true in TB to save SIM time
      CLOCK_RATE_I           : integer := 100_000_000;  -- Default to 100MHz
      BAUD_RATE_I            : integer := 115_200       -- 115.2K Baud
    );
 Port ( 
        Clk       : in    std_logic;
        Rst       : in    std_logic;
        --Rx        : in    std_logic;            -- Rx for future UART
        --Tx        : out   std_logic;            -- Tx for future UART
         -- LEDs
    --  Led0_r                 : out   std_logic;
    --  Led0_g                 : out   std_logic;
    --  Led0_b                 : out   std_logic;
    
    --  Led1_r                 : out   std_logic;
    --  Led1_g                 : out   std_logic;
    --  Led1_b                 : out   std_logic;
    
    --  Led2_r                 : out   std_logic;
    --  Led2_g                 : out   std_logic;
    --  Led2_b                 : out   std_logic;
    
    --  Led3_r                 : out   std_logic;
    --  Led3_g                 : out   std_logic;
    --  Led3_b                 : out   std_logic;
    
    --  Led4                   : out   std_logic;
    --  Led5                   : out   std_logic;
    --  Led6                   : out   std_logic;
    --  Led7                   : out   std_logic;
    
  --  Bt                    : in    std_logic_vector(3 downto 0);  -- 4 Push Buttons
  --  Sw                    : in    std_logic_vector(3 downto 0);  -- 4 Slide Switches
         SCLK      : out   std_logic;  -- Audio SCLK

  --
         spiCsL     : out std_logic_vector(1 downto 0);
         spiSclk    : out std_logic;
         spiSdi     : out std_logic;
         spiSdo     : in  std_logic;
        
         rst_out   : out   std_logic;
         Rst_n     : out   std_logic
       );
end Arty_Master_top;

architecture Mapping of Arty_Master_top is
-- ------------------------------------------------------------------------------------------
--                            *** Declarations ***
-- ------------------------------------------------------------------------------------------
-- Functions 
-- -----------------------------------------------------------------------------
-- Functions
-- -----------------------------------------------------------------------------
function debounce_delay return integer is
begin
  -- 5us                                  -- If we set the SIM_BLN to true, we have shorthr 
  if (SIM_BLN) then                       -- Debounce time for sim purposes
    -- 100MHz/1MHz
    return ((CLOCK_RATE_I/1_000_000)*5);
  -- 10ms
  else
    -- 100MHz/100Hz
    return (CLOCK_RATE_I/100);
  end if;
end function; 

--  ------------------------------------------------------------------------
--           CONSTANTS 
--  ------------------------------------------------------------------------
-- 
-- CONSTANTS for Top Level
constant DEBOUNCE_TIME_I    : integer := debounce_delay;  -- Based on SIM_BLN value
constant BRAM_MSG_LEN_I     : integer := 296;             -- Welcome MSG length for UART to Terminal

-- CONSTANTS for SPI Interface
constant DATA_WIDTH_I      : natural:= 8;
constant NUM_SLVS_I        : integer range 1 to 8 := 2;                                           
constant CPHA              : std_logic:= '0';                            
constant CPOL              : std_logic:= '0';                            
constant CLK_PER_R         : real:= 10.0E-9;   -- 10 ns  for 100 MHz Clk 
constant SCLK_PER_R        : real:= 1.0E-6;  -- 1 MHz                   


-- Signals
signal CMT_rst              : std_logic:= '1';  -- it's good practice to initialize reg'd signlals
signal CMT_Locked           : std_logic:= '1';  -- it's good practice to initialize reg'd signlals
signal sys_rst              : std_logic:= '1';  -- active high resets for Xilinx-7 series
signal Rst_ibuf             : std_logic;        -- Rst in from outside world goes throu IBUF prim 

signal en                   : std_logic:= '0';
signal Clk_ibuf             : std_logic;
signal Clk_bufio             : std_logic;

signal Clk_100M             : std_logic;
signal Clk_53p3             : std_logic;

signal freeRunClk :   std_logic:= '0';                                                                                                   
signal chipSel    :   std_logic_vector(1 downto 0);                                                                                      
signal wrEn       :   std_logic;                                                                                                         
signal wrData     :   std_logic_vector(DATA_WIDTH_I-1 downto 0);                                                                         
signal dataSize   :   std_logic_vector(log2(DATA_WIDTH_I)-1 downto 0) := toSlv(DATA_WIDTH_I-1, log2(DATA_WIDTH_I)); -- only 1 bit is '1' 
signal rdEn       :   std_logic;                                                                                                         
signal rdData     :   std_logic_vector(DATA_WIDTH_I-1 downto 0);                                                                         
signal shiftCnt   :   std_logic_vector(bitSize(DATA_WIDTH_I)-1 downto 0);                                                                




begin
 -- IBUF: Single-ended Input Buffer
   --       Artix-7
   -- Xilinx HDL Language Template, version 2021.2
   
   IBUF1_inst : IBUF
   generic map (
      IBUF_LOW_PWR => TRUE, -- Low power (TRUE) vs. performance (FALSE) setting for referenced I/O standards
      IOSTANDARD => "DEFAULT")
   port map (
      O => Clk_ibuf,     -- Buffer output
      I => Clk      -- Buffer input (connect directly to top-level port)
   );
--            
--    Now  put an IBUF on the clk 

  -- BUFIO: Local Clock Buffer for I/O
   --        Artix-7
   -- Xilinx HDL Language Template, version 2021.2

   BUFIO_inst : BUFIO
   port map (
      O => Clk_bufio, -- 1-bit output: Clock output (connect to I/O clock loads).
      I => clk_ibuf  -- 1-bit input: Clock input (connect to an IBUF or BUFMR).
   );

Clk_intI : CMT_Clk
    port map 
    (   
     -- Clock in ports
     clk_in1                   => Clk_bufio,             -- I 
     -- Clock out ports  
     clk_out1                  => Clk_100M,               -- O
     clk_out2                  => Clk_53p3,             -- O
     -- Status and control signals                
     reset                     => CMT_rst,               -- I
     locked                    => CMT_Locked             -- O
    ); 
    
    SCLK <= Clk_53p3;

CMT_rst_int: entity work.CMT_reset
   
    port map 
    ( 
      Clk                      => Clk_bufio,             -- I
      Rst_out                  => CMT_rst                -- O  
    ); 

-- ---------------------------------------------------------------------------  
  --                      Enable
  -- ---------------------------------------------------------------------------
ENABLE_I : entity work.enable
    port map 
    (
      Clk                      => Clk_100M,               -- I              
      Locked                   => CMT_locked,            -- I            
      En                       => en                     -- O
    );
-- ---------------------------------------------------------------------------  
  -- Reset
  -- ---------------------------------------------------------------------------
  --
  
    IBUF2_inst : IBUF
   generic map (
      IBUF_LOW_PWR => TRUE, -- Low power (TRUE) vs. performance (FALSE) setting for referenced I/O standards
      IOSTANDARD => "DEFAULT")
   port map (
      O => Rst_ibuf,     -- Buffer output
      I => Rst      -- Buffer input (connect directly to top-level port)
   );
  RST_I: entity work.debsync
  generic map 
  (
    DEBOUNCE_TIME_I   => DEBOUNCE_TIME_I               
  )
  port map 
  ( 
    Clk                        => Clk_100M,             -- I
    Din                        => Rst_ibuf,             -- I  
    Dout                       => rst_n                -- O  
  ); 
  
  sys_rst <= NOT rst_n;
  rst_out <= sys_rst;

SPI_P: entity work.SpiMaster  
   generic map
     (
      NUM_SLVS_I         => 2,
      DATA_WIDTH_I       => 8,
      CPHA               => '0',
      CPOL               => '0',
      CLK_PER_R          => 10.0,    -- 10 ns
      SCLK_PER_R         => 166.67  -- ns = 6 MHz
      )
   port map
     (
      clk         => Clk_100M,
      rst         => sys_rst,
      freeRunClk  => freeRunClk,
      chipSel     => chipSel,    -- sl[1:0]
      wrEn        => wrEn,
      wrData      => wrData    ,
      dataSize    => dataSize  ,
      rdEn        => rdEn      ,
      rdData      => rdData    ,
      shiftCnt    => shiftCnt  ,
      spiCsL      => spiCsL    ,
      spiSclk     => spiSclk   ,
      spiSdi      => spiSdi    ,
      spiSdo      => spiSdo     
    );
    
end Mapping;

The Project Top Level

As a look ahead I have elaborated the RTL design and have captured the schematic. The modules we have covered are in the yellow circle. This was done as a visual aid.

Elaborated Top Level

So far we have created the components needed to interface out Clocks and reset to the outside world. We can use the “Locked” signal from the CMT t generate a global enable signal and create a debounce filter to debounce the push button used for reset. The Debounce filter also synchronizes the the reset signal to the 100 MHz system clock. This module debsync.vhd, is parameterized so that it can be used to sync slower signals (wrt the 100 MHz clock) to the system clock domain Clk_100M. For the case of debouncing a button, the number of stages is on the order of 1,000 flops.

If the Boolean generic parameter, SIM_BLN is set to “true” the number of flops will be decreased to 50. Refer to the debounce_delay function on line 42 and see how it effects the value of the DEBOUNCE_TIME constant.

The code for debsync.vhd is in the following block. Select add/create new design sources and cut and paste this into a new VHDL file named “debsync”.

----------------------------------------------------------------------------------
-- Company: Trailing Edge Signal Processing
-- Engineer: Patrick Donaldson
-- 
-- Create Date: 12/31/2021 08:51:24 PM
-- Design Name: 
-- Module Name: debsync - rtl
-- Project Name: 
-- Target Devices: 
-- Tool Versions: 
-- Description: 
-- 
-- Dependencies: 
-- 
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
-- 
----------------------------------------------------------------------------------
library ieee;
use     ieee.std_logic_1164.all;
use     ieee.numeric_std.all;

use     work.common_pkg.all;

-- ------------------------------------------------------------------------------------------
-- Entity 
-- ------------------------------------------------------------------------------------------
entity debsync is
generic 
(
  DEBOUNCE_TIME_I     : integer := 10
);
port 
(                   
  Clk                 : in    std_logic;
  Din                 : in    std_logic;
  Dout                : out   std_logic
 );
end debsync;

-- ------------------------------------------------------------------------------------------
-- Architecture 
-- ------------------------------------------------------------------------------------------
architecture rtl of debsync is

-- -----------------------------------------------------------------------------
-- Signals
-- -----------------------------------------------------------------------------
--  Din synchronization
signal sync          : std_logic_vector(2 downto 0) := (others => '0');

signal rise_fall     : std_logic;

signal din_sync      : std_logic;

--  De-bounce counter
signal cnt           : unsigned(Log2(DEBOUNCE_TIME_I)-1 downto 0) := (others => '0');

-- Dout
signal dout_i        : std_logic := '0';



begin

  -- ---------------------------------------------------------------------------  
  --                Data In synchronization 
  -- ---------------------------------------------------------------------------
  -- Synchronize "Din" to "Clk"
  --
  process (Clk) 
  begin
    if rising_edge(Clk) then
      sync    <= sync(1 downto 0) & Din;
    end if;
  end process;

  rise_fall <= sync(1) xor sync(2);
  din_sync  <= sync(2);

  -- ---------------------------------------------------------------------------  
  --  De-bounce counter
  -- ---------------------------------------------------------------------------
  --
  process (Clk) 
  begin
    if rising_edge(Clk) then
      if (rise_fall = '1') then
        cnt <= (others => '0');
      else
        if (cnt = DEBOUNCE_TIME_I) then
          cnt <= (others => '0');
        else
          cnt <= cnt + 1;
        end if;
      end if;
    end if;
  end process;

  -- ---------------------------------------------------------------------------  
  --  Dout
  -- ---------------------------------------------------------------------------
  --
  process (Clk) 
  begin
    if rising_edge(Clk) then
      if (cnt = DEBOUNCE_TIME_I) then
        dout_i <= din_sync; 
      end if;
    end if;
  end process;
    
  -- Connect Output(s)
  Dout <= dout_i;

end rtl;


Notice how the code uses the function Log2(arg :int) from the common_pkg.

The code for the enable module is just a 2-bit shift register with the CMT “Locked” signal as the input.

library ieee;
use     ieee.std_logic_1164.all;
use     ieee.numeric_std.all;

-- ------------------------------------------------------------------------------------------
-- Entity 
-- ------------------------------------------------------------------------------------------
entity enable is
port 
(
  Clk           : in    std_logic;              
  Locked        : in    std_logic;             
  En            : out   std_logic
);
end enable;

-- ------------------------------------------------------------------------------------------
-- Architecture 
-- ------------------------------------------------------------------------------------------
architecture rtl of enable is


-- -----------------------------------------------------------------------------
-- Signals
-- -----------------------------------------------------------------------------
signal locked_sync : unsigned(1 downto 0):= (others => '0');


begin

  -- ---------------------------------------------------------------------------  
  -- Locked Synchronization
  -- ---------------------------------------------------------------------------
  -- Synchronize "Din" to "Clk"
  --
  process (Locked, Clk) 
  begin
    if (Locked = '0') then
      locked_sync <= (others => '0');
    elsif rising_edge(Clk) then
      locked_sync <= locked_sync(0) & Locked;
    end if;
  end process;

  -- Connect Output(s)
  En <= locked_sync(1);

end rtl;

Next we will create the SPI Master interface to the D/A converter and the A/D converter. logic needed to configure the D/A and A/D devices.

We also need to create the audio data interface and the logic to stream stream serial digital audio through this interface. The MCLK from the DAC will be used to clock the A/D circuitry, but we will have to create an SPI interface to communicate and configure these devices.

Designing a Structured VHDL Module Example: The SPI_Master

1           Basic Concepts

The SPI bus specifies four logic signals:

  • SCLK: Serial Clock (output from master)
  • MOSI: Master Out Slave In (data output from master)
  • MISO: Master In Slave Out (data output from slave)
  • CS /SS: Chip/Slave Select (often active low, output from master to indicate that data is being sent)

MOSI on a master connects to MOSI on a slave. MISO on a master connects to MISO on a slave. Slave Select has the same functionality as chip select and is used instead of an addressing concept.

Note: on a slave-only device, MOSI may be labeled as SDI (Serial Data In) and MISO may be labeled as SDO (Serial Data Out)

The signal names above can be used to label both the master and slave device pins as well as the signal lines between them in an unambiguous way, and are the most common in modern products. Pin names are always capitalized e.g. “Chip Select,” not “chip select.”

SPI_Master Interface

A requirement of any SPI_Slave device is that when their SS input is not asserted, the Master In Slave Out (MISO) serial data output line must go into a Hi-Z state so that the selected slave device can drive the MISO input of the SPI_Master device without contention.

The SPI_Master is responsible for selecting only one slave for a transaction. The designer is free to design the sequencing and arbitration of SPI Master to Slave as needed as long as the master follows SPI protocol and the needs of the slave device. The designer needs to know the specifications of the slave device(s) ahead of time to design accordingly.

A general SPI_Master can be designed and then modified as necessary to accommodate the needs of the slave devices.

SPI Timing Waveforms

The