------------------------------------------------------------------------------- -- -- File: TopCpld.vhd -- Author: Daniel Jepson -- Original Project: N310 -- Date: 24 October 2016 -- ------------------------------------------------------------------------------- -- Copyright 2016-2017 Ettus Research, A National Instruments Company -- SPDX-License-Identifier: GPL-3.0 ------------------------------------------------------------------------------- -- -- Purpose: -- -- Top level file for the Magnesium CPLD. -- -- This file instantiates two SPI slave ports. One slave port comes from the PS -- of the motherboard Zynq. It has three slave select pins, mapped as follows: -- sPlsSpiLe = CPLD Internal Registers -- aPsSpiAddr(0) = LMK Endpoint -- aPsSpiAddr(1) = Phase DAC Endpoint -- -- The other slave port comes from the PL of the motherboard Zynq. It also has -- three slave select pins: -- lPlSpiLe = CPLD Internal Registers -- aPlSpiAddr(0) = TX Lowband LO -- aPlSpiAddr(1) = RX Lowband LO -- -- The final address line for the PL slave is used as a passthrough for the LMK -- SYNC pin. -- -- -- For either SPI interface, the CPLD has internal registers that can be addressed -- whenever the appropriate slave select is driven asserted. These register groups -- are completely independent from one another, meaning the PS SPI interface cannot -- access the PL registers, and vice-versa. -- -- See the register interface XML at the bottom of this file for details on how -- each SPI port is expected to be driven, and for the register maps for the PS -- and PL slaves. -- -- -- BUMPING THE REVISION: -- In PkgSetup the kMinorRev and kMajorRev are defined. Whenever a change -- is made to the CPLD, no matter how small, bump the kMinorRev value. If this change -- breaks compatibility with current HW or SW drivers, increment the kMajorRev value -- and reset the kMinorRev to zero. Similarly, there is a constant to define the build -- code, kBuildCode. Currently this is simply the year, month, day, and hour the CPLD is -- built, but could be user-definable. -- -- ------------------------------------------------------------------------------- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use ieee.math_real.all; library work; use work.PkgMgCpld.all; use work.PkgSetup.all; entity TopCpld is port( -- SPI Port Incoming from FPGA -- PlSpiSck : in std_logic; lPlSpiSdi : in std_logic; lPlSpiSdo : out std_logic; lPlSpiLe : in std_logic; aPlSpiAddr : in std_logic_vector(2 downto 0); -- SPI Port Incoming from PS -- PsSpiSck : in std_logic; sPsSpiSdi : in std_logic; sPsSpiSdo : out std_logic; sPsSpiLe : in std_logic; aPsSpiAddr : in std_logic_vector(1 downto 0); -- ATR bits from FPGA -- aAtrRx1 : in std_logic; aAtrRx2 : in std_logic; aAtrTx1 : in std_logic; aAtrTx2 : in std_logic; -- Ch 1 TX (15 bits) -- aCh1LedTx : out std_logic; aCh1TxPaEn : out std_logic; aCh1TxAmpEn : out std_logic; aCh1TxMixerEn : out std_logic; aCh1TxSw1 : out std_logic_vector(1 downto 0); aCh1TxSw2 : out std_logic_vector(3 downto 0); aCh1TxSw3 : out std_logic; aCh1TxSw4 : out std_logic_vector(1 downto 0); aCh1TxSw5 : out std_logic_vector(1 downto 0); -- Ch 1 RX (29 bits) -- aCh1LedRx : out std_logic; aCh1LedRx2 : out std_logic; aCh1RxAmpEn : out std_logic; aCh1RxMixerEn : out std_logic; aCh1RxLna1En : out std_logic; aCh1RxLna2En : out std_logic; aCh1SwTrx : out std_logic_vector(1 downto 0); aCh1RxSw1 : out std_logic_vector(1 downto 0); aCh1RxSw2 : out std_logic_vector(1 downto 0); aCh1RxSw3 : out std_logic_vector(2 downto 0); aCh1RxSw4 : out std_logic_vector(2 downto 0); aCh1RxSw5 : out std_logic_vector(3 downto 0); aCh1RxSw6 : out std_logic_vector(2 downto 0); aCh1RxSw7 : out std_logic_vector(1 downto 0); aCh1RxSw8 : out std_logic_vector(1 downto 0); -- Ch 2 TX -- aCh2LedTx : out std_logic; aCh2TxPaEn : out std_logic; aCh2TxAmpEn : out std_logic; aCh2TxMixerEn : out std_logic; aCh2TxSw1 : out std_logic_vector(1 downto 0); aCh2TxSw2 : out std_logic_vector(3 downto 0); aCh2TxSw3 : out std_logic; aCh2TxSw4 : out std_logic_vector(1 downto 0); aCh2TxSw5 : out std_logic_vector(1 downto 0); -- Ch 2 RX -- aCh2LedRx : out std_logic; aCh2LedRx2 : out std_logic; aCh2RxAmpEn : out std_logic; aCh2RxMixerEn : out std_logic; aCh2RxLna1En : out std_logic; aCh2RxLna2En : out std_logic; aCh2SwTrx : out std_logic_vector(1 downto 0); aCh2RxSw1 : out std_logic_vector(1 downto 0); aCh2RxSw2 : out std_logic_vector(1 downto 0); aCh2RxSw3 : out std_logic_vector(2 downto 0); aCh2RxSw4 : out std_logic_vector(2 downto 0); aCh2RxSw5 : out std_logic_vector(3 downto 0); aCh2RxSw6 : out std_logic_vector(2 downto 0); aCh2RxSw7 : out std_logic_vector(1 downto 0); aCh2RxSw8 : out std_logic_vector(1 downto 0); -- LMK -- aLmkSpiSdio : out std_logic; aLmkSpiSck : out std_logic; aLmkSpiCs_n : out std_logic; aLmkClkinSel : in std_logic_vector(0 downto 0); -- SDO aLmkSync : out std_logic; -- direct connect to aPlSpiAddr(2) -- Phase DAC -- aDacDin : out std_logic; aDacSync_n : out std_logic; aDacSck : out std_logic; aVcxoCtrl : out std_logic; -- @PS-REG-WR -- RX and TX LOs -- (timed) aLoSpiSync : in std_logic; -- Clock! (unused atm, only for reclocking aRxLoSck : out std_logic; -- the SPI bus if needed for sync) aRxLoDin : out std_logic; aRxLoCs_n : out std_logic; aRxLoMuxOut : in std_logic; aRxLoLockDetect : in std_logic; -- @PS-REG-RD aTxLoSck : out std_logic; aTxLoDin : out std_logic; aTxLoCs_n : out std_logic; aTxLoMuxOut : in std_logic; aTxLoLockDetect : in std_logic; -- @PS-REG-RD -- Mykonos Interface -- aMkReset_n : out std_logic; -- @PS-REG-WR aMkRx1En : out std_logic; aMkRx2En : out std_logic; aMkTx1En : out std_logic; aMkTx2En : out std_logic ); end TopCpld; architecture RTL of TopCpld is -- PS MOSI signal sCpldPsSpiActive : boolean; signal sPsMosiIndex : unsigned(integer(ceil(log2(real(kTotalWidth)))) downto 0); signal sPsMosiBuffer : InterfaceData_t := (others => '0'); signal sPsRd : boolean := false; signal sPsRegAddr : unsigned(kAddrWidth-1 downto 0) := (others => '0'); -- PS MISO signal sPsCpldMiso : std_logic; signal sPsMisoIndex : unsigned(integer(ceil(log2(real(kTotalWidth)))) downto 0); signal sPsMisoBuffer : std_logic_vector(kTotalWidth-1 downto 0); -- PS Register Signals signal aRxLoLockDetect_ms, sRxLoLockDetect, aTxLoLockDetect_ms, sTxLoLockDetect : std_logic := '0'; signal sReset : boolean := false; signal sScratchVal : InterfaceData_t := (others => '0'); signal sVcxoControl : std_logic := '1'; signal sMykonosReset : std_logic := '0'; -- PL MOSI signal lCpldPlSpiActive : boolean; signal lPlMosiIndex : unsigned(integer(ceil(log2(real(kTotalWidth)))) downto 0); signal lPlMosiBuffer : InterfaceData_t := (others => '0'); signal lPlRd : boolean := false; signal lPlRegAddr : unsigned(kAddrWidth-1 downto 0) := (others => '0'); -- PL MISO signal lPlCpldMiso : std_logic; signal lPlMisoIndex : unsigned(integer(ceil(log2(real(kTotalWidth)))) downto 0); signal lPlMisoBuffer : std_logic_vector(kTotalWidth-1 downto 0); -- PL Register Signals signal lScratchVal : InterfaceData_t := (others => '0'); signal lReset : boolean := false; -- See PkgSetup for each Default definition. signal lTxCh1IdleReg : InterfaceData_t := kTxChDefault; signal lTxCh2IdleReg : InterfaceData_t := kTxChDefault; signal lTxCh1TxOnReg : InterfaceData_t := kTxChDefaultRun; signal lTxCh2TxOnReg : InterfaceData_t := kTxChDefaultRun; signal lRxCh1_0IdleReg : InterfaceData_t := kRxChDefault0; signal lRxCh1_1IdleReg : InterfaceData_t := kRxChDefault1; signal lRxCh2_0IdleReg : InterfaceData_t := kRxChDefault0; signal lRxCh2_1IdleReg : InterfaceData_t := kRxChDefault1; signal lRxCh1_0RxOnReg : InterfaceData_t := kRxChDefault0Run; signal lRxCh1_1RxOnReg : InterfaceData_t := kRxChDefault1Run; signal lRxCh2_0RxOnReg : InterfaceData_t := kRxChDefault0Run; signal lRxCh2_1RxOnReg : InterfaceData_t := kRxChDefault1Run; signal lTxCh1 : InterfaceData_t; signal lTxCh2 : InterfaceData_t; signal lRxCh1_0 : InterfaceData_t; signal lRxCh1_1 : InterfaceData_t; signal lRxCh2_0 : InterfaceData_t; signal lRxCh2_1 : InterfaceData_t; begin -- Direct Pass-through Pins : --------------------------------------------------------- -- ------------------------------------------------------------------------------------ -- LMK SYNC assignment for direct passthrough to LMK from SPI Addr line. aLmkSync <= aPlSpiAddr(2); -- PS SPI Interface : ----------------------------------------------------------------- -- Composed of a few modules: -- 1) PsMosiIndex - generate pointer for MOSI Buffer -- 2) PsMosiBuffer - actually implement the buffer, only when the CPLD is targeted. -- 3) PsMosiProcessing - process the MOSI data: sort into Rd/!Wt, Address, and Data. -- This process works on the falling edge of the clock to register the pieces -- of the MOSI packet as they are complete. The final falling edge registers -- the data into the individual registers, so it is critical that the clock idle -- LOW after the transaction is complete such that this final falling edge occurs. -- 4) PsMisoBuffer - generate pointer for the MISO buffer. The buffer itself is -- completely async. -- 5) PsMisoBufferMux - Mux all the register data back into the MISO buffer. -- 6) StatusSynchronizer - double-synchronizers for status bits from the LMK and LOs. -- ------------------------------------------------------------------------------------ -- Decode the PS SPI Address bits... which are actually going to be used as individual -- chip selects coming from the PS. sCpldPsSpiActive <= sPsSpiLe = '0'; aLmkSpiCs_n <= aPsSpiAddr(0); aDacSync_n <= aPsSpiAddr(1); -- Assign the remainder of the SPI lines to the LMK and DAC. aLmkSpiSck <= PsSpiSck; aLmkSpiSdio <= sPsSpiSdi; aDacSck <= PsSpiSck; aDacDin <= sPsSpiSdi; -- Output mux for data back to the FPGA (PS core). The LMK and CPLD are the only -- endpoints that have readback enabled. sPsSpiSdo <= aLmkClkinSel(0) when aPsSpiAddr(0) = '0' else sPsCpldMiso; -- Use the LE signal (Cs_n) as the asynchronous reset to the shift register counter. -- LE will hold the counter in reset until this endpoint is targeted, when it will -- release the reset (long before the clock toggles) and allow the shift operation -- to begin. -- -- !!! SAFE COUNTER STARTUP!!! -- This counter starts safely from reset because the PsSpiSck will not start toggling -- until long after the asynchronous reset (sCpldPsSpiActive) de-asserts. Similarly, -- the reset will only assert long after the last clock edge is received. PsMosiIndex : process(PsSpiSck, sCpldPsSpiActive) begin if not sCpldPsSpiActive then sPsMosiIndex <= (others => '0'); elsif rising_edge(PsSpiSck) then sPsMosiIndex <= sPsMosiIndex + 1; end if; end process PsMosiIndex; -- Shift in SDI (MOSI) data from the PS on the rising edge of the clock. Only use -- synchronous resets from here on out. PsMosiBuffer : process(PsSpiSck) begin if rising_edge(PsSpiSck) then if sReset then sPsMosiBuffer <= (others => '0'); else if sCpldPsSpiActive then sPsMosiBuffer <= sPsMosiBuffer(sPsMosiBuffer'high-1 downto 0) & sPsSpiSdi; -- left shift end if; end if; end if; end process PsMosiBuffer; -- As portions of the command and data packets become available, register them here -- using the falling edge of the PS SPI clock. PsMosiProcessing : process(PsSpiSck) begin if falling_edge(PsSpiSck) then if sReset then -- sReset is intentionally self-clearing. It clears on the first falling edge of -- the next SPI transaction after it is set. Logic on the first rising edge of -- that next transaction is therefore held in reset. This will not matter -- as long as SW follows the recommended reset procedure (writing a '1' to reset -- then writing a '0'), since the first bit of the transaction is '0' for a -- write operation. sReset <= false; sScratchVal <= (others => '0'); sVcxoControl <= '1'; sMykonosReset <= '0'; sPsRd <= false; sPsRegAddr <= (others => '0'); else -- After the first bit is captured, we can determine if it is a write or read. if (sPsMosiIndex = (kRdWtWidth)) then sPsRd <= sPsMosiBuffer(0) = '1'; end if; -- After the entire command word is captured, the address is ready for capture. if (sPsMosiIndex = (kAddrWidth + kRdWtWidth)) then sPsRegAddr <= unsigned(sPsMosiBuffer(kAddrWidth - 1 downto 0)); end if; -- And finally after the entire transaction is complete we can save off the data -- on the final falling edge of the SPI clock into it's appropriate place, based -- off the address value captured above. if (sPsMosiIndex = kTotalWidth) and (not sPsRd) then -- ---------------------------------------------------------------------------- -- Assign writable register values here! -------------------------------------- -- ---------------------------------------------------------------------------- if (sPsRegAddr = kScratch) then sScratchVal <= sPsMosiBuffer; end if; if (sPsRegAddr = kCpldControl) then sReset <= sPsMosiBuffer(kCpldReset) = '1'; end if; if (sPsRegAddr = kLmkControl) then sVcxoControl <= sPsMosiBuffer(kVcxoControl); end if; if (sPsRegAddr = kMykonosControl) then sMykonosReset <= sPsMosiBuffer(kMykonosReset); end if; end if; end if; end if; end process PsMosiProcessing; -- Send MISO back to FPGA (PS) on the falling edge as well. -- -- !!! SAFE COUNTER STARTUP!!! -- This counter starts safely from reset because the PsSpiSck will not start toggling -- until long after the asynchronous reset (sCpldPsSpiActive) de-asserts. Similarly, -- the reset will only assert long after the last clock edge is received. PsMisoBuffer : process(PsSpiSck, sCpldPsSpiActive) begin if not sCpldPsSpiActive then sPsMisoIndex <= to_unsigned(kTotalWidth-1, sPsMisoIndex'length); elsif falling_edge(PsSpiSck) then if sPsMisoIndex > 0 then sPsMisoIndex <= sPsMisoIndex - 1; end if; end if; end process PsMisoBuffer; sPsCpldMiso <= sPsMisoBuffer(to_integer(sPsMisoIndex)); -- Mux the register data from the CPLD back to the FPGA. PsMisoBufferMux : process(sPsRegAddr, sScratchVal, sVcxoControl, sTxLoLockDetect, sRxLoLockDetect, sMykonosReset) begin sPsMisoBuffer <= (others => '0'); case to_integer(sPsRegAddr) is when kSignatureReg => sPsMisoBuffer(kDataWidth-1 downto 0) <= kSignature; when kMinorRevReg => sPsMisoBuffer(kDataWidth-1 downto 0) <= kMinorRev; when kMajorRevReg => sPsMisoBuffer(kDataWidth-1 downto 0) <= kMajorRev; when kBuildCodeLSB => sPsMisoBuffer(kDataWidth-1 downto 0) <= kBuildCode(15 downto 0); when kBuildCodeMSB => sPsMisoBuffer(kDataWidth-1 downto 0) <= kBuildCode(31 downto 16); when kScratch => sPsMisoBuffer(kDataWidth-1 downto 0) <= sScratchVal; when kLmkControl => sPsMisoBuffer(kVcxoControl) <= sVcxoControl; when kLoStatus => sPsMisoBuffer(kTxLoLockDetect) <= sTxLoLockDetect; sPsMisoBuffer(kRxLoLockDetect) <= sRxLoLockDetect; when kMykonosControl => sPsMisoBuffer(kMykonosReset) <= sMykonosReset; when others => sPsMisoBuffer(kDataWidth-1 downto 0) <= (others => '0'); end case; end process PsMisoBufferMux; -- Double-synchronize the async inputs to the PS clock domain. However, this clock -- isn't toggling all the time. Whenever it is toggling, let's capture these bits. StatusSynchronizer : process(PsSpiSck) begin if rising_edge(PsSpiSck) then aRxLoLockDetect_ms <= aRxLoLockDetect; sRxLoLockDetect <= aRxLoLockDetect_ms; aTxLoLockDetect_ms <= aTxLoLockDetect; sTxLoLockDetect <= aTxLoLockDetect_ms; end if; end process; -- PS SPI locals to outputs. aVcxoCtrl <= sVcxoControl; aMkReset_n <= not sMykonosReset; -- PL SPI Interface : ----------------------------------------------------------------- -- Composed of a few modules: -- 1) PlMosiIndex - generate pointer for MOSI Buffer -- 2) PlMosiBuffer - actually implement the buffer, only when the CPLD is targeted. -- 3) PlMosiProcessing - process the MOSI data: sort into Rd/!Wt, Address, and Data. -- This process works on the falling edge of the clock to register the pieces -- of the MOSI packet as they are complete. The final falling edge registers -- the data into the individual registers, so it is critical that the clock idle -- LOW after the transaction is complete such that this final falling edge occurs. -- 4) PlMisoBuffer - generate pointer for the MISO buffer. The buffer itself is -- completely async. -- 5) PlMisoBufferMux - Mux all the register data back into the MISO buffer. -- 6) StatusSynchronizer - double-synchronizers for status bits from the LMK and LOs. -- ------------------------------------------------------------------------------------ -- Decode the PL SPI Address bits... which are actually going to be used as individual -- chip selects coming from the PL. lCpldPlSpiActive <= lPlSpiLe = '0'; aTxLoCs_n <= aPlSpiAddr(0); aRxLoCs_n <= aPlSpiAddr(1); -- Assign the remainder of the SPI lines to the LOs. aRxLoSck <= PlSpiSck; aRxLoDin <= lPlSpiSdi; aTxLoSck <= PlSpiSck; aTxLoDin <= lPlSpiSdi; -- Output mux for data back to the FPGA (PL core). The LMK and CPLD are the only -- endpoints that have readback enabled. lPlSpiSdo <= aTxLoMuxOut when aPlSpiAddr(0) = '0' else aRxLoMuxOut when aPlSpiAddr(1) = '0' else lPlCpldMiso; -- Use the LE signal (Cs_n) as the asynchronous reset to the shift register counter. -- LE will hold the counter in reset until this endpoint is targeted, when it will -- release the reset (long before the clock toggles) and allow the shift operation -- to begin. -- -- !!! SAFE COUNTER STARTUP!!! -- This counter starts safely from reset because the PlSpiSck will not start toggling -- until long after the asynchronous reset (lCpldPlSpiActive) de-asserts. Similarly, -- the reset will only assert long after the last clock edge is received. PlMosiIndex : process(PlSpiSck, lCpldPlSpiActive) begin if not lCpldPlSpiActive then lPlMosiIndex <= (others => '0'); elsif rising_edge(PlSpiSck) then lPlMosiIndex <= lPlMosiIndex + 1; end if; end process PlMosiIndex; -- Shift in SDI (MOSI) data from the PL on the rising edge of the clock. Only use -- synchronous resets from here on out. PlMosiBuffer : process(PlSpiSck) begin if rising_edge(PlSpiSck) then if lReset then lPlMosiBuffer <= (others => '0'); else if lCpldPlSpiActive then lPlMosiBuffer <= lPlMosiBuffer(lPlMosiBuffer'high-1 downto 0) & lPlSpiSdi; -- left shift end if; end if; end if; end process PlMosiBuffer; -- As portions of the command and data packets become available, register them here -- using the falling edge of the PL SPI clock. PlMosiProcessing : process(PlSpiSck) begin if falling_edge(PlSpiSck) then if lReset then -- lReset is intentionally self-clearing. It clears on the first falling edge of -- the next SPI transaction after it is set. Logic on the first rising edge of -- that next transaction is therefore held in reset. This will not matter -- as long as SW follows the recommended reset procedure (writing a '1' to reset -- then writing a '0'), since the first bit of the transaction is '0' for a -- write operation. lReset <= false; lScratchVal <= (others => '0'); lTxCh1IdleReg <= kTxChDefault; lTxCh1TxOnReg <= kTxChDefault; lTxCh2IdleReg <= kTxChDefault; lTxCh2TxOnReg <= kTxChDefault; lRxCh1_0IdleReg <= kRxChDefault0; lRxCh1_1IdleReg <= kRxChDefault1; lRxCh1_0RxOnReg <= kRxChDefault0; lRxCh1_1RxOnReg <= kRxChDefault1; lRxCh2_0IdleReg <= kRxChDefault0; lRxCh2_1IdleReg <= kRxChDefault1; lRxCh2_0RxOnReg <= kRxChDefault0; lRxCh2_1RxOnReg <= kRxChDefault1; lPlRd <= false; lPlRegAddr <= (others => '0'); else -- After the first bit is captured, we can determine if it is a write or read. if (lPlMosiIndex = (kRdWtWidth)) then lPlRd <= lPlMosiBuffer(0) = '1'; end if; -- After the entire command word is captured, the address is ready for capture. if (lPlMosiIndex = (kAddrWidth + kRdWtWidth)) then lPlRegAddr <= unsigned(lPlMosiBuffer(kAddrWidth - 1 downto 0)); end if; -- And finally after the entire transaction is complete we can save off the data -- on the final falling edge of the SPI clock into it's appropriate place, based -- off the address value captured above. if (lPlMosiIndex = kTotalWidth) and (not lPlRd) then -- ---------------------------------------------------------------------------- -- Assign writable register values here! -------------------------------------- -- ---------------------------------------------------------------------------- if (lPlRegAddr = kPlScratch) then lScratchVal <= lPlMosiBuffer; end if; if (lPlRegAddr = kPlCpldControl) then lReset <= lPlMosiBuffer(kCpldReset) = '1'; end if; if (lPlRegAddr = kTxCh1_Idle) then lTxCh1IdleReg <= lPlMosiBuffer; end if; if (lPlRegAddr = kTxCh1_TxOn) then lTxCh1TxOnReg <= lPlMosiBuffer; end if; if (lPlRegAddr = kTxCh2_Idle) then lTxCh2IdleReg <= lPlMosiBuffer; end if; if (lPlRegAddr = kTxCh2_TxOn) then lTxCh2TxOnReg <= lPlMosiBuffer; end if; if (lPlRegAddr = kRxCh1_0_Idle) then lRxCh1_0IdleReg <= lPlMosiBuffer; end if; if (lPlRegAddr = kRxCh1_1_Idle) then lRxCh1_1IdleReg <= lPlMosiBuffer; end if; if (lPlRegAddr = kRxCh1_0_RxOn) then lRxCh1_0RxOnReg <= lPlMosiBuffer; end if; if (lPlRegAddr = kRxCh1_1_RxOn) then lRxCh1_1RxOnReg <= lPlMosiBuffer; end if; if (lPlRegAddr = kRxCh2_0_Idle) then lRxCh2_0IdleReg <= lPlMosiBuffer; end if; if (lPlRegAddr = kRxCh2_1_Idle) then lRxCh2_1IdleReg <= lPlMosiBuffer; end if; if (lPlRegAddr = kRxCh2_0_RxOn) then lRxCh2_0RxOnReg <= lPlMosiBuffer; end if; if (lPlRegAddr = kRxCh2_1_RxOn) then lRxCh2_1RxOnReg <= lPlMosiBuffer; end if; end if; end if; end if; end process PlMosiProcessing; -- Send MISO back to FPGA (PL) on the falling edge as well. -- -- !!! SAFE COUNTER STARTUP!!! -- This counter starts safely from reset because the PlSpiSck will not start toggling -- until long after the asynchronous reset (lCpldPlSpiActive) de-asserts. Similarly, -- the reset will only assert long after the last clock edge is received. PlMisoBuffer : process(PlSpiSck, lCpldPlSpiActive) begin if not lCpldPlSpiActive then lPlMisoIndex <= to_unsigned(kTotalWidth-1, lPlMisoIndex'length); elsif falling_edge(PlSpiSck) then if lPlMisoIndex > 0 then lPlMisoIndex <= lPlMisoIndex - 1; end if; end if; end process PlMisoBuffer; lPlCpldMiso <= lPlMisoBuffer(to_integer(lPlMisoIndex)); -- Mux the register data from the CPLD back to the FPGA. PlMisoBufferMux : process(lPlRegAddr, lScratchVal, lTxCh1IdleReg, lTxCh1TxOnReg, lTxCh2IdleReg, lTxCh2TxOnReg, lRxCh1_0IdleReg, lRxCh1_1IdleReg, lRxCh1_0RxOnReg, lRxCh1_1RxOnReg, lRxCh2_0IdleReg, lRxCh2_1IdleReg, lRxCh2_0RxOnReg, lRxCh2_1RxOnReg) begin lPlMisoBuffer <= (others => '0'); case to_integer(lPlRegAddr) is when kPlScratch => lPlMisoBuffer(kDataWidth-1 downto 0) <= lScratchVal; when kTxCh1_Idle => lPlMisoBuffer(kDataWidth-1 downto 0) <= lTxCh1IdleReg; when kTxCh1_TxOn => lPlMisoBuffer(kDataWidth-1 downto 0) <= lTxCh1TxOnReg; when kTxCh2_Idle => lPlMisoBuffer(kDataWidth-1 downto 0) <= lTxCh2IdleReg; when kTxCh2_TxOn => lPlMisoBuffer(kDataWidth-1 downto 0) <= lTxCh2TxOnReg; when kRxCh1_0_Idle => lPlMisoBuffer(kDataWidth-1 downto 0) <= lRxCh1_0IdleReg; when kRxCh1_1_Idle => lPlMisoBuffer(kDataWidth-1 downto 0) <= lRxCh1_1IdleReg; when kRxCh1_0_RxOn => lPlMisoBuffer(kDataWidth-1 downto 0) <= lRxCh1_0RxOnReg; when kRxCh1_1_RxOn => lPlMisoBuffer(kDataWidth-1 downto 0) <= lRxCh1_1RxOnReg; when kRxCh2_0_Idle => lPlMisoBuffer(kDataWidth-1 downto 0) <= lRxCh2_0IdleReg; when kRxCh2_1_Idle => lPlMisoBuffer(kDataWidth-1 downto 0) <= lRxCh2_1IdleReg; when kRxCh2_0_RxOn => lPlMisoBuffer(kDataWidth-1 downto 0) <= lRxCh2_0RxOnReg; when kRxCh2_1_RxOn => lPlMisoBuffer(kDataWidth-1 downto 0) <= lRxCh2_1RxOnReg; when others => lPlMisoBuffer(kDataWidth-1 downto 0) <= (others => '0'); end case; end process PlMisoBufferMux; -- Use the ATR bits to mux the output values. lTxCh1 <= lTxCh1IdleReg when aAtrTx1 = '0' else lTxCh1TxOnReg; lTxCh2 <= lTxCh2IdleReg when aAtrTx2 = '0' else lTxCh2TxOnReg; lRxCh1_0 <= lRxCh1_0IdleReg when aAtrRx1 = '0' else lRxCh1_0RxOnReg; lRxCh1_1 <= lRxCh1_1IdleReg when aAtrRx1 = '0' else lRxCh1_1RxOnReg; lRxCh2_0 <= lRxCh2_0IdleReg when aAtrRx2 = '0' else lRxCh2_0RxOnReg; lRxCh2_1 <= lRxCh2_1IdleReg when aAtrRx2 = '0' else lRxCh2_1RxOnReg; -- PL SPI locals to outputs. All the register values are set for Channel 1. Channel 2 -- values are mixed around here in order for the same register settings to work for -- Channel 1 and Channel 2, even though Ch2 switch configuration is different in HW. aCh1LedTx <= lTxCh1(kCh1TxLed); aCh1TxPaEn <= lTxCh1(kCh1TxPaEn); aCh1TxAmpEn <= lTxCh1(kCh1TxAmpEn); aCh1TxMixerEn <= lTxCh1(kCh1TxMixerEn); aCh1TxSw1 <= lTxCh1(kCh1TxSw1Msb downto kCh1TxSw1); aCh1TxSw2 <= lTxCh1(kCh1TxSw2Msb downto kCh1TxSw2); aCh1TxSw3 <= lTxCh1(kCh1TxSw3); aCh1TxSw4 <= "01" when lTxCh1(kCh1TxLowbandMixerPathSelect) = '1' else "10"; aCh1TxSw5 <= "10" when lTxCh1(kCh1TxLowbandMixerPathSelect) = '1' else "01"; aCh1SwTrx <= lTxCh1(kCh1SwTrxMsb downto kCh1SwTrx); aMkTx1En <= lTxCh1(kCh1MykEnTx); aCh2LedTx <= lTxCh2(kCh1TxLed); aCh2TxPaEn <= lTxCh2(kCh1TxPaEn); aCh2TxAmpEn <= lTxCh2(kCh1TxAmpEn); aCh2TxMixerEn <= lTxCh2(kCh1TxMixerEn); aCh2TxSw1 <= lTxCh2(kCh1TxSw1Msb downto kCh1TxSw1); aCh2TxSw2 <= Tx2Switch2Mod(lTxCh2(kCh1TxSw2Msb downto kCh1TxSw2)); aCh2TxSw3 <= lTxCh2(kCh1TxSw3); aCh2TxSw4 <= "10" when lTxCh2(kCh1TxLowbandMixerPathSelect) = '1' else "01"; aCh2TxSw5 <= "01" when lTxCh2(kCh1TxLowbandMixerPathSelect) = '1' else "10"; aCh2SwTrx <= Tx2TrxMod(lTxCh2(kCh1SwTrxMsb downto kCh1SwTrx)); aMkTx2En <= lTxCh2(kCh1MykEnTx); aCh1RxSw1 <= lRxCh1_0(kCh1RxSw1Msb downto kCh1RxSw1); aCh1RxSw2 <= lRxCh1_0(kCh1RxSw2Msb downto kCh1RxSw2); aCh1RxSw3 <= lRxCh1_0(kCh1RxSw3Msb downto kCh1RxSw3); aCh1RxSw4 <= lRxCh1_0(kCh1RxSw4Msb downto kCh1RxSw4); aCh1RxSw5 <= lRxCh1_0(kCh1RxSw5Msb downto kCh1RxSw5); aCh1RxSw6 <= lRxCh1_1(kCh1RxSw6Msb downto kCh1RxSw6); aCh1RxSw7 <= "01" when lRxCh1_1(kCh1RxLowbandMixerPathSelect) = '1' else "10"; aCh1RxSw8 <= "01" when lRxCh1_1(kCh1RxLowbandMixerPathSelect) = '1' else "10"; aCh1LedRx <= lRxCh1_1(kCh1RxLed) and not lTxCh1(kCh1TxLed); aCh1LedRx2 <= lRxCh1_1(kCh1Rx2Led); aCh1RxAmpEn <= lRxCh1_1(kCh1RxAmpEn); aCh1RxMixerEn <= lRxCh1_1(kCh1RxMixerEn); aCh1RxLna1En <= lRxCh1_1(kCh1RxLna1En); aCh1RxLna2En <= lRxCh1_1(kCh1RxLna2En); aMkRx1En <= lRxCh1_1(kCh1MykEnRx); aCh2RxSw1 <= Rx2Switch1Mod(lRxCh2_0(kCh1RxSw1Msb downto kCh1RxSw1)); aCh2RxSw2 <= Rx2Switch2Mod(lRxCh2_0(kCh1RxSw2Msb downto kCh1RxSw2)); aCh2RxSw3 <= Rx2Switch3Mod(lRxCh2_0(kCh1RxSw3Msb downto kCh1RxSw3)); aCh2RxSw4 <= Rx2Switch4Mod(lRxCh2_0(kCh1RxSw4Msb downto kCh1RxSw4)); aCh2RxSw5 <= Rx2Switch5Mod(lRxCh2_0(kCh1RxSw5Msb downto kCh1RxSw5)); aCh2RxSw6 <= Rx2Switch6Mod(lRxCh2_1(kCh1RxSw6Msb downto kCh1RxSw6)); aCh2RxSw7 <= "10" when lRxCh2_1(kCh1RxLowbandMixerPathSelect) = '1' else "01"; aCh2RxSw8 <= "10" when lRxCh2_1(kCh1RxLowbandMixerPathSelect) = '1' else "01"; aCh2LedRx <= lRxCh2_1(kCh1RxLed) and not lTxCh2(kCh1TxLed); aCh2LedRx2 <= lRxCh2_1(kCh1Rx2Led); aCh2RxAmpEn <= lRxCh2_1(kCh1RxAmpEn); aCh2RxMixerEn <= lRxCh2_1(kCh1RxMixerEn); aCh2RxLna1En <= lRxCh2_1(kCh1RxLna1En); aCh2RxLna2En <= lRxCh2_1(kCh1RxLna2En); aMkRx2En <= lRxCh2_1(kCh1MykEnRx); end RTL; --XmlParse xml_on -- -- -- -- -- -- These registers are accessed via the PS SPI interface to the CPLD. They are all -- internal to the CPLD. The SPI format is 24 bits total. On MOSI, shift (msb first) -- Rd/!Wt | Addr(6:0) | Data(15:0) (lsb). The SPI clock {b}MUST{/b} idle LOW before -- and after the transaction. CPOL=CPHA=0. To access these registers, use the chip -- select line named "CPLD-PS-SPI-SLE-33" as an active-low select. -- -- -- -- -- This register contains the device signature. -- -- -- -- Represents the product family name/number. This field reads back as -- 0xCAFE. -- -- -- -- -- -- -- This register contains the device revision numeric code. -- -- -- -- Contains minor revision code (0,1,2,...). -- -- -- -- -- -- -- This register contains the major revision value. -- -- -- -- Contains major revision code. -- -- -- -- -- -- -- Build code... right now it's the date it was built. LSB in this register. -- -- -- -- Contains build code hour code. -- -- -- -- -- Contains build code day code. -- -- -- -- -- -- -- Build code... right now it's the date it was built. MSB in this register. -- -- -- -- Contains build code month code. -- -- -- -- -- Contains build code revision year code. -- -- -- -- -- -- -- -- -- -- Contains scratch value for testing. The state of this register has -- no effect on any other operation in the CPLD. -- -- -- -- -- -- -- -- -- -- -- Asserting this bit resets all the CPLD logic. -- This reset will return all registers on the PS SPI interface to their default -- state! To use this reset correctly, first write CpldReset to '1', then write -- it to '0'. Registers will be reset on the _falling_ edge of CpldReset. -- -- -- -- -- -- -- -- -- -- -- Setting this bit to '0' will allow the Phase DAC to exclusively control the -- VCXO voltage. Defaults to '1', which allows the Phase DAC to adjust the -- voltage (but the LMK still has control as well). -- -- -- -- -- -- -- -- -- -- Live lock detect status from the RX LO. -- -- -- -- -- Live lock detect status from the TX LO. -- -- -- -- -- -- -- -- -- -- Drives the Mykonos hard reset line. Defaults to de-asserted. Write a '1' to -- assert the reset, and a '0' to de-assert. -- -- -- -- -- -- -- -- -- -- -- -- These registers are accessed via the PL SPI interface to the CPLD. They are all -- internal to the CPLD. The SPI format is 24 bits total. On MOSI, shift (msb first) -- Rd/!Wt | Addr(6:0) | Data(15:0) (lsb). The SPI clock {b}MUST{/b} idle LOW before -- and after the transaction. CPOL=CPHA=0. To access these registers, use the chip -- select line named "CPLD-PL-SPI-LE-25" as an active-low select. {br}{br} -- -- The ATR bits ultimately control which of these registers actually control -- the RF front end. -- -- -- -- -- -- Contains scratch value for testing. The state of this register has no effect -- on any other operation in the CPLD. -- -- -- -- -- -- -- -- -- -- Asserting this bit resets all the CPLD logic on the PL SPI interface. -- This reset will return all registers to their default state! To use this -- reset correctly, first write PlCpldReset to '1', then write it to '0'. -- Registers will be reset on the _falling_ edge of PlCpldReset. -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- Load this register with the front-end configuration for channel TX 1 when the -- ATR bits are configured: TX = 0, RX = don't-care. -- -- -- -- Controls Switch 1. Filter bank receive switch. -- -- -- -- -- Controls Switch 2. Filter bank distribution switch. -- -- -- -- -- Controls Switch 3. Bypasses the filter bank and PA, or doesn't. -- -- -- -- -- Controls Switches 4 and 5. Write a '1' to select the Lowband Mixer path. -- Writing '0' will select the bypass path around the mixer. Default is '0'. Note: -- Individual control over these switches was removed as an optimization to -- allow all TX controls to fit in one 16 bit register. -- -- -- -- -- Write a '1' to enable the lowband mixer. Note that Ch1TxLowbandMixerPathSelect -- must be properly configured to select the mixer path. -- -- -- -- -- Write a '1' to enable the TX path Amp in between TX switches 3 and 4. The path -- (from Mykonos) is: TxSw4 -> Amp -> DSA -> TxSw3. -- -- -- -- -- Write a '1' to enable the TX path PA in between TX switches 2 and 3. -- -- -- -- -- TRX switch control. -- -- -- -- -- Red/Green combo LED for the TRX channel. -- -- -- -- -- Drives the Mykonos input port TX1_ENABLE. -- -- -- -- -- -- -- Load this register with the front-end configuration for channel TX 1 when the -- ATR bits are configured: TX = 1, RX = don't-care. The bitfields are the same -- as for the Tx1_Off register. -- -- -- -- -- -- Load this register with the front-end configuration for channel TX 2 when the -- ATR bits are configured: TX = 0, RX = don't-care. The bitfields are the same -- as for the Tx1_Off register. -- -- -- -- -- -- Load this register with the front-end configuration for channel TX 2 when the -- ATR bits are configured: TX = 1, RX = don't-care. The bitfields are the same -- as for the Tx1_Off register. -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- Load this register with the front-end configuration for channel RX 1 when the -- ATR bits are configured: TX = don't-care, RX = 0. -- -- -- -- Controls Switch 1. Selects between the cal, bypass, RX2, and TRX paths. -- -- -- -- -- Controls Switch 2. First filter switch. Selects between bypass path and -- the upper/lower filter banks. -- -- -- -- -- Controls Switch 3. Lower filter bank transmit switch. -- -- -- -- -- Controls Switch 4. Upper filter bank receive switch. -- -- -- -- -- Controls Switch 5. Lower filter bank receive switch. -- -- -- -- -- -- -- Load this register with the front-end configuration for channel RX 1 when the -- ATR bits are configured: TX = don't-care, RX = 0. -- -- -- -- Controls Switch 6. Selects between the upper and lower filter banks and -- bypass path. -- -- -- -- -- Controls Switches 7 and 8. Write a '1' to select the Lowband Mixer path. -- Writing '0' will select the bypass path around the mixer. Default is '0'. Note: -- Individual control over these switches was removed as an optimization to -- allow all TX controls to fit in one 16 bit register... so the same was done -- for the RX path for continuity. -- -- -- -- -- Write a '1' to enable the lowband mixer. Note that Ch1RxLowbandMixerPathSelect -- must be properly configured to select the mixer path. -- -- -- -- -- Write a '1' to enable the RX path Amp directly before the Mykonos inputs. -- -- -- -- -- Write a '1' to enable the RX path LNA1 between RxSw4 and RxSw6. -- -- -- -- -- Write a '1' to enable the RX path LNA2 between RxSw5 and RxSw6. -- -- -- -- -- Green LED for RX2 channel. -- -- -- -- -- Red/Green combo LED for the TRX channel. -- -- -- -- -- Drives the Mykonos input port RX1_ENABLE. -- -- -- -- -- -- -- Load this register with the front-end configuration for channel RX 1 when the -- ATR bits are configured: TX = don't-care, RX = 1. The bitfields are the same -- as for the RxCh1_0_Idle register. -- -- -- -- -- -- Load this register with the front-end configuration for channel RX 1 when the -- ATR bits are configured: TX = don't-care, RX = 1. The bitfields are the same -- as for the RxCh1_1_Idle register. -- -- -- -- -- -- Load this register with the front-end configuration for channel RX 2 when the -- ATR bits are configured: TX = don't-care, RX = 0. The bitfields are the same -- as for the RxCh1_0_Idle register. -- -- -- -- -- -- Load this register with the front-end configuration for channel RX 2 when the -- ATR bits are configured: TX = don't-care, RX = 0. The bitfields are the same -- as for the RxCh1_1_Idle register. -- -- -- -- -- -- Load this register with the front-end configuration for channel RX 2 when the -- ATR bits are configured: TX = don't-care, RX = 1. The bitfields are the same -- as for the RxCh1_0_Idle register. -- -- -- -- -- -- Load this register with the front-end configuration for channel RX 2 when the -- ATR bits are configured: TX = don't-care, RX = 1. The bitfields are the same -- as for the RxCh1_1_Idle register. -- -- -- -- -- -- --XmlParse xml_off