--
Radio-Frequency Detector Module (A3038DM) Firmware, Toplevel Unit -- Compile with Synplify Pro. The message decoder fails if compiled with the Lattice synthesizer. -- Version A01, 01-JAN-21, Translate message decoder from ABEL to VHDL. Add code to flash indicator lights when -- receiving and message ready. -- Version A02, 12-JAN-21, Switch to 80 MHz clock input. -- Version A03, 16-APR-21. Separate detector from main code. Detector provides message_id as an integer and -- message_data as a standard logic vector. We confirm that message_id is being received correctly with a -- lamp dedicated to one particular channel. -- Version 04, 19-APR-21. Add Data Upstream Bus (DUB), Data Downstream Bus (DDB), and Detector Control Bus (DCB). -- and implement the global flags the detector modules share for power measurement and readout. We find that the -- message detector does not work when we compile with the Lattice synthesizer. We must use the Synplify Pro -- synthesizer. We add the message fifo and write messages into it. Our red lamp, LED2, indicates that the -- fifo is full -- Version 05, 20-APR-21. Complete a draft of the entire functionality, including storing incoming messages in -- the FIFO, reading them out, and arranging the output bytes over the daisy-chained eight-bit detector module -- bus. We have the FIFO implemented in LUTs. It is 16 thirty-two-bit message records, each containing an ID, -- HI and LO bytes, and power byte. -- Version 06, 10-MAY-21. Improve indicator implementation for HIDE and SHOW. Use rising edge of CK to clock -- message writes to FIFO as well as reads from, thus allowing us 25 ns from a write to the next read rather -- than 12.5 ns. Implement power display look-up table in distributed ROM. Discover that we are not clocking -- the final bit out of the ADC, so correct power readout and check response with transmitter and looking at -- duty cycle of the power indicator lamp. Assign lamps with constants and make the blue lamp the power -- indicator, because we find it is much more visible than the white. Changed the board power indicator to -- a run indicator that reduces the intensity of the green lamp at the same time as requiring the clocks to -- turn on. Enhance the red error lamp so it gives a short flash for FIFO full erro and a longer flash for -- empty error. We have 10 kHz SLWCK and 10 Hz LCK. We update the decoder, correcting its reset behavior -- and removing the CNT signal. Our message recorder resets the decoder after it stores a message, regardless -- of whether its decoder had received the message or not. Add a 100-ms timer to the power indicator so that -- it turns off in the absence of new samples. -- Version 07, 31-MAY-21. Update power indication only when at least one detector receives a complete message. -- Remove the expiration of power measurement to simplify the code while lookig for bugs. Synchronize the -- control signals with respect to the rising edge of CK rather than FCK to remove timing and metastability -- problems we observed through examination of the power readout state machine. Increase hysteresis on -- tri-state inputs. Add power calibration parameter that we subtract from the raw power measurement. For now -- this value is zero. Remove 80 MHz clock and instead use DMCK with PLL to make 40 MHz. We add a PLL lock -- error to the possible sources of DMERR. -- Version 08, 15-JUN-21. Assign test pin outputs. Improve power indicator map. Fix vulnerability of state -- machines to glitches in GRCV and GINC that occur when we assert outputs as soon as we see either signal -- unasserted. In particular, when a test point is asserted on !GINC, we can induce a spike in GINC. -- Global Constantslibrary ieee; library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity main is port ( Q : in std_logic; -- Comparator Output from Detector SDO : in std_logic; -- Serial Data Out from ADC SCK : inout std_logic; -- Serial Clock for ADC NCS : inout std_logic; -- Negated Chip Select for ADC DSD : in std_logic; -- Data Strobe Downstream ddb : out std_logic_vector(7 downto 0); -- Data Downstream Bus DSU : out std_logic; -- Data Strobe Upstream dub : in std_logic_vector(7 downto 0); -- Data Upstream Bus RESET : in std_logic; -- Reset from master (DC0) DRC : in std_logic; -- Detector Readout Complete from master (DC1) GERR : inout std_logic; -- Global detector module error flag (DC2) GINC : inout std_logic; -- Global incoming message flag (DC3) GRCV : inout std_logic; -- Global message received flag (DC4) HIDE : in std_logic; -- Turn off indicator lamps (DC5) SHOW : in std_logic; -- Turn on indicator lamps (DC6) DMCK : in std_logic; -- Detector Module Clock (DC7) LED : out std_logic_vector(5 downto 1); -- Indictors Lamps TP : out std_logic_vector(4 downto 1) -- Test Points ); constant power_calibration : integer := 0; constant run_led_num : integer := 1; constant err_led_num : integer := 2; constant inc_led_num : integer := 3; constant rcv_led_num : integer := 4; constant pwr_led_num : integer := 5; end; architecture behavior of main is -- Attributes to guide the compiler. attribute syn_keep : boolean; attribute nomerge : string; -- Detector Control Bus Signals signal DMERR : std_logic; -- Detector module error, to master. signal DSD_sync, GINC_sync, GRCV_sync, DRC_sync : std_logic; -- Synchronized inputs. -- Message Decoder Signals signal CONTINUE : std_logic; -- Tell message detector to continue detection. signal RSTDCR : std_logic; -- Reset the decoder. signal INCOMING : std_logic; -- The message detector is receiving a message. signal RECEIVED : std_logic; -- The message detector has received a complete message. signal message_id : integer range 0 to 255; -- id of received message. signal message_data : std_logic_vector (15 downto 0); -- contents of received message. -- Clock and Timing Signals. signal CK : std_logic; -- Decoder Clock 40 MHz signal IOCK : std_logic; -- Input-Output Clock 20 MHz signal SLWCK : std_logic; -- Slow Clock 10 kHz signal LCK : std_logic; -- Lamp Clock 10 Hz signal LOCK : std_logic; -- PLL Lock signal clock_divA, clock_divB : std_logic_vector(7 downto 0); -- ADC Signals signal pwr, pwr_rcv, pwr_intensity : std_logic_vector(7 downto 0); signal PRDY : boolean; -- Functions and Procedures function to_std_logic (v: boolean) return std_ulogic is begin if v then return('1'); else return('0'); end if; end function; -- Message FIFO signal received_record, recalled_record : std_logic_vector(31 downto 0); signal FIFO_EMPTY, FIFO_FULL, WRMSG, RDMSG : std_logic; signal FIFO_EMPTY_ERR, FIFO_FULL_ERR : boolean; begin -- The message decoder runs off 40 MHz, but we bring 80 MHz into the logic -- from an 80 MHz oscillator so as to avoid harmonics of the oscillator -- frequency in the 900-930 MHz band. Here we create CK, our 40-MHz clock, -- by dividing the Fast Clock. Clock : entity P3038DM_PLL port map ( CLKI => DMCK, CLKOP => CK, LOCK => LOCK ); -- Synchronize control inputs with respect to the rising edge of CK. We also -- generate IOCK at 20 MHz. Synchronizer : process (CK) is begin if rising_edge(CK) then DSD_sync <= DSD; GINC_sync <= GINC; GRCV_sync <= GRCV; DRC_sync <= DRC; IOCK <= to_std_logic(IOCK = '0'); end if; end process; -- We use the lamp clock to provide human-visible signalling with lamps. -- We divide CK by 256 twice to get 610 Hz. We divide by 61 to get 10 Hz -- to control lamp signals. We divide 40 MHz by 256 once to get 156 kHz, -- then divide by 16 to get 10 kHz. Slow_Clocks : process (CK) is variable div1,div2 : integer range 0 to 63; begin if rising_edge(CK) then clock_divA <= std_logic_vector(unsigned(clock_divA)+1); end if; if rising_edge(clock_divA(7)) then clock_divB <= std_logic_vector(unsigned(clock_divB) + 1); if div1 = 15 then div1 := 0; else div1 := div1 + 1; end if; SLWCK <= to_std_logic(div1 < 9); end if; if rising_edge(clock_divB(7)) then if div2 = 60 then div2 := 0; else div2 := div2 + 1; end if; LCK <= to_std_logic(div2 < 30); end if; end process; -- The Message Decoder watches the incoming logic sequence and looks for -- messages. When it sees an incoming message, it asserts INCOMING. When -- it sees a complete message, it asserts RECEIVED. It provides the message -- id and data for storage, and waits until it receives RST before -- clearing its id and data values and looking for the next message. If -- it receives RST at any other time, it returns to its reast state, waiting -- for a new message to arrive. Decoder : entity message_decoder port map ( CK => CK,-- Clock, 40.000 MHz Q => Q, -- Demodulator Output RST => RSTDCR, -- Reset the Decoder INCOMING => INCOMING, -- Message Incoming RECEIVED => RECEIVED, -- Message Received message_id => message_id, -- Eight-bit message ID message_data => message_data -- Message Data ); -- The Detector Control process operates the various detector control -- lines that are shared by all detector modules and the master. In -- particular, there are three global shared lines that each detector -- can assert or leave tri-state. These are the global error, incoming, -- and received lines. Detector_Control : process (CK) is begin if rising_edge(CK) then if DMERR = '1' then GERR <= '1'; else GERR <= 'Z'; end if; if INCOMING = '1' then GINC <= '1'; else GINC <= 'Z'; end if; if RECEIVED = '1' then GRCV <= '1'; else GRCV <= 'Z'; end if; end if; end process; -- The Message FIFO receives new 32-bit message records and allows the -- them to be read out later by the detector module daisy chain. The FIFO -- writes occur on the falling edge of CK. We asser WrEn on the prior -- rising edge, which allows 12.5 ns (half of 40-MHz period) for the FIFO -- to prepare for WrClock. The logic chip requires 11.4 ns setup. On the -- read, we set up RdEn on the falling edge and read on the rising edge -- of CK, which again allows 12.5 ns setup where 11.7 ns is required. Message_FIFO : entity P3038DM_FIFO port map ( Data => received_record, WrClock => CK, RdClock => CK, WrEn => WRMSG, RdEn => RDMSG, Reset => RESET, RPReset => RESET, Q => recalled_record, Empty => FIFO_EMPTY, Full => FIFO_FULL ); -- We sample the power detector output and read out the ADC when we -- see Global Incoming (GINC) asserted. We acquire the power detector -- output voltage as soon as we see GINC asserted, by driving the ADC -- chip select input low (asserting !CS). We then wait until GRCV is -- asserted, at which time it is safe to read out the ADC without disturbing -- message reception. We generate SCK at 20 MHz and read out three dummy -- bits, eight data bits, and three terminating bits. The ADC transitions -- to input tracking after the third terminating bit. Once the readout is -- complete, we wait for GINC to be unasserted, and then wait a little -- longer to make sure that GINC has settled. The GINC line is a global -- signal with pull-down. It takes some time to settle back to zero, and -- transitions in nearby logic signals can cause it to glitch back up. -- After the wait, we return to the rest state. The PRDY flag indicates that -- a new power measurement is ready, and will be asserted until the power -- readout returns to rest. Power_Readout : process (IOCK) is constant pwr_end : integer := 31; constant wait_end : integer := 37; variable state, next_state : integer range 0 to 63; begin if RESET = '1' then state := 0; SCK <= '1'; NCS <= '1'; pwr <= "00000000"; PRDY <= false; elsif rising_edge(IOCK) then next_state := state; if state = 0 then if (GINC_sync = '1') then next_state := 1; else next_state := 0; end if; elsif state = 1 then if (GINC_sync = '0') then next_state := 0; elsif (GRCV_sync = '1') then next_state := 2; else next_state := 1; end if; elsif state = pwr_end then if (GINC_sync = '0') then next_state := state + 1; else next_state := pwr_end; end if; elsif state = wait_end then next_state := 0; else next_state := state + 1; end if; case state is when 0 => SCK <= '1'; NCS <= '1'; -- Idle when 1 => SCK <= '1'; NCS <= '0'; -- Acquire input voltage when 2 => SCK <= '0'; NCS <= '0'; -- Initiate conversion when 3 => SCK <= '1'; NCS <= '0'; when 4 => SCK <= '0'; NCS <= '0'; -- Continue conversion when 5 => SCK <= '1'; NCS <= '0'; when 6 => SCK <= '0'; NCS <= '0'; -- Complete conversion when 7 => SCK <= '1'; NCS <= '0'; when 8 => SCK <= '0'; NCS <= '0'; -- Bit 7 when 9 => SCK <= '1'; NCS <= '0'; when 10 => SCK <= '0'; NCS <= '0'; -- Bit 6 when 11 => SCK <= '1'; NCS <= '0'; when 12 => SCK <= '0'; NCS <= '0'; -- Bit 5 when 13 => SCK <= '1'; NCS <= '0'; when 14 => SCK <= '0'; NCS <= '0'; -- Bit 4 when 15 => SCK <= '1'; NCS <= '0'; when 16 => SCK <= '0'; NCS <= '0'; -- Bit 3 when 17 => SCK <= '1'; NCS <= '0'; when 18 => SCK <= '0'; NCS <= '0'; -- Bit 2 when 19 => SCK <= '1'; NCS <= '0'; when 20 => SCK <= '0'; NCS <= '0'; -- Bit 1 when 21 => SCK <= '1'; NCS <= '0'; when 22 => SCK <= '0'; NCS <= '0'; -- Bit 0 when 23 => SCK <= '1'; NCS <= '0'; when 24 => SCK <= '0'; NCS <= '0'; -- Set up next cycle when 25 => SCK <= '1'; NCS <= '0'; when 26 => SCK <= '0'; NCS <= '0'; -- Set up next cycle when 27 => SCK <= '1'; NCS <= '0'; when 28 => SCK <= '0'; NCS <= '0'; -- Track input when 29 => SCK <= '1'; NCS <= '0'; when 30 => SCK <= '1'; NCS <= '1'; -- Terminate cycle when others => SCK <= '1'; NCS <= '1'; -- Idle end case; case state is when 9 | 11 | 13 | 15 | 17 | 19 | 21 | 23 => pwr(7 downto 1) <= pwr(6 downto 0); pwr(0) <= SDO; when others => pwr <= pwr; end case; PRDY <= (state >= pwr_end); state := next_state; end if; end process; -- The Message Recorder saves 32-bit message records in the FIFO. The -- top eight bits are the message id, the next sixteen are the message -- data, and the final eight are the power. If this detector did not -- receive the message, the id and data are all zero. Message_Recorder : process (CK,RESET) is variable state, next_state : integer range 0 to 3; begin if RESET = '1' then state := 0; RSTDCR <= '1'; pwr_rcv <= "00000000"; elsif rising_edge(CK) then next_state := state; RSTDCR <= '0'; WRMSG <= '0'; case state is when 0 => if (GRCV_sync = '1') and PRDY then next_state := 1; end if; when 1 => next_state := 2; WRMSG <= '1'; pwr_rcv <= pwr; when 2 => RSTDCR <= '1'; if GRCV_sync = '0' then next_state := 3; else next_state := 2; end if; when 3 => RSTDCR <= '1'; next_state := 0; end case; state := next_state; end if; if RECEIVED = '1' then received_record(31 downto 24) <= std_logic_vector(to_unsigned(message_id,8)); received_record(23 downto 8) <= message_data; else received_record(31 downto 24) <= "00000000"; received_record(23 downto 8) <= "0000000000000000"; end if; received_record(7 downto 0) <= pwr; end process; -- The Message Reader responds to the readout process on the detector module -- daisy chain. From its rest state, it reads a message from the FIFO after -- seeing Data Strobe Downstream (DSD) from the module closer to the master. -- All modules are receiving the same DSD and reading their records of the -- same message in resonse to the same DSD pulse. The first DSD cycle gives -- the detectors a chance to establish if they are the highest-power receiver -- in comparison to all upstream modules, and we set saved_local_best to mark -- this comparison. The next DSD cycle invites the highest-power receivers to -- send their message_id downstream. Other detectors relay the upstream data -- to their downstream. The next DSD cycle invites the highest-power receivers -- to do the same for the top eight data bits. The next cycle is for the bottom -- eight data bits. The cycle after that is blocked from passing upstream: each -- detector prevents DSD from being sent to DSU, and responds to the cycle -- itself, with its own power measurement. Thereafter, it permits all subsequent -- DSD cycles to propagate upstream, and forwards the upstream data to the -- downstream data. In this way the master receives the id, high data, low data, -- and then power from the first to the last module in the daisy chain. The master -- asserte Detector Read Complete (DRC) to send all Message Readers back to their -- rest state, ready to read out the next message from the FIFO. Message_Reader : process (CK,RESET) is variable state, next_state : integer range 0 to 31; variable recalled_id,recalled_pwr,upstream_value : integer range 0 to 255; variable recalled_hi,recalled_lo : std_logic_vector(7 downto 0); variable ddb_select : integer range 0 to 7; constant select_zero : integer := 0; constant select_dub : integer := 1; constant select_pwr : integer := 2; constant select_hi : integer := 3; constant select_lo : integer := 4; constant select_id : integer := 5; constant max_pwr : integer := 6; variable local_best, local_best_saved : boolean; variable ds_forward : boolean; begin recalled_id := to_integer(unsigned(recalled_record(31 downto 24))); recalled_hi := recalled_record(23 downto 16); recalled_lo := recalled_record(15 downto 8); recalled_pwr := to_integer(unsigned(recalled_record(7 downto 0))); upstream_value := to_integer(unsigned(dub)); local_best := (upstream_value <= recalled_pwr) and (recalled_id /= 0); if (RESET = '1') or (DRC_sync = '1') then state := 0; RDMSG <= '0'; elsif rising_edge(CK) then next_state := state; RDMSG <= '0'; case state is when 0 => if DSD_sync = '1' then next_state := 1; end if; local_best_saved := false; ddb_select := select_zero; ds_forward := true; when 1 => next_state := 2; ddb_select := select_zero; ds_forward := true; RDMSG <= '1'; when 2 => if DSD_sync = '0' then next_state := 3; end if; local_best_saved := local_best; ddb_select := max_pwr; ds_forward := true; when 3 => if DSD_sync = '1' then next_state := 4; end if; ddb_select := select_zero; ds_forward := true; when 4 => if DSD_sync = '0' then next_state := 5; end if; if local_best_saved then ddb_select := select_id; else ddb_select := select_dub; end if; ds_forward := true; when 5 => if DSD_sync = '1' then next_state := 6; end if; ddb_select := select_zero; ds_forward := true; when 6 => if DSD_sync = '0' then next_state := 7; end if; if local_best_saved then ddb_select := select_hi; else ddb_select := select_dub; end if; ds_forward := true; when 7 => if DSD_sync = '1' then next_state := 8; end if; ddb_select := select_zero; ds_forward := true; when 8 => if DSD_sync = '0' then next_state := 9; end if; if local_best_saved then ddb_select := select_lo; else ddb_select := select_dub; end if; ds_forward := true; when 9 => if DSD_sync = '1' then next_state := 10; end if; ddb_select := select_zero; ds_forward := false; when 10 => if DSD_sync = '0' then next_state := 11; end if; ddb_select := select_pwr; ds_forward := false; when 11 => if DSD_sync = '1' then next_state := 12; end if; ddb_select := select_zero; ds_forward := true; when 12 => ddb_select := select_dub; ds_forward := true; when others => next_state := 0; ds_forward := true; end case; state := next_state; end if; case ddb_select is when select_zero => ddb <= "00000000"; when select_dub => ddb <= dub; when select_pwr => ddb <= recalled_record(7 downto 0); when select_hi => ddb <= recalled_hi; when select_lo => ddb <= recalled_lo; when select_id => ddb <= recalled_record(31 downto 24); when max_pwr => if local_best then ddb <= recalled_record(7 downto 0); else ddb <= dub; end if; when others => ddb <= "00000000"; end case; if ds_forward then DSU <= DSD_sync; else DSU <= '0'; end if; end process; -- The error detector looks for overflow or empty FIFO conflicts. Error_Detection : process (CK,RESET) is begin if RESET = '1' then FIFO_FULL_ERR <= false; FIFO_EMPTY_ERR <= false; elsif rising_edge(CK) then if (WRMSG = '1') and (FIFO_FULL = '1') then FIFO_FULL_ERR <= true; end if; if (RDMSG = '1') and (FIFO_EMPTY = '1') then FIFO_EMPTY_ERR <= true; end if; end if; DMERR <= to_std_logic(FIFO_EMPTY_ERR or FIFO_FULL_ERR or (LOCK = '0')); end process; -- The run indicator glows steady if power is on, logic is programmed, -- and the clocks are running. Run_Indicator : process (SLWCK) is variable counter : integer range 0 to 15; variable run_led_on : boolean; begin if RESET = '1' then counter := 0; run_led_on := false; elsif rising_edge(SLWCK) then counter := counter +1; end if; run_led_on := (counter rem 4) = 3; if HIDE = '1' then run_led_on := false; end if; if SHOW = '1' then run_led_on := true; end if; LED(run_led_num) <= to_std_logic(run_led_on); end process; -- The status indicator is off when all is well, and flashes -- to indicate error conditions. Status_Indicator : process (LCK) is variable counter : integer range 0 to 15; variable err_led_on : boolean; begin if rising_edge(LCK) then counter := counter + 1; err_led_on := false; end if; err_led_on := (FIFO_FULL_ERR and (counter >= 0) and (counter <= 1)) or (FIFO_EMPTY_ERR and (counter >= 4) and (counter <= 11)) or (LOCK = '0'); if HIDE = '1' then err_led_on := false; end if; if SHOW = '1' then err_led_on := true; end if; LED(err_led_num) <= to_std_logic(err_led_on); end process; -- Indoming LED indicates message is being decoded now. Incoming_Indicator : process (CK) is variable counter : integer range 0 to 65535; constant pulse_length : integer := 32768; begin if rising_edge(CK) then if SHOW = '1' then LED(inc_led_num) <= '1'; elsif HIDE = '1' then LED(inc_led_num) <= '0'; elsif (counter = 0) then if (INCOMING = '1') then counter := 1; else counter := 0; end if; LED(inc_led_num) <='0'; else if (counter = pulse_length) then counter := 0; else counter := counter + 1; end if; LED(inc_led_num) <= '1'; end if; end if; end process; -- Received LED indicates complete message received. Received_Indicator : process (CK) is variable counter : integer range 0 to 65535; constant pulse_length : integer := 32768; begin if rising_edge(CK) then if SHOW = '1' then LED(rcv_led_num) <= '1'; elsif HIDE = '1' then LED(rcv_led_num) <= '0'; elsif (counter = 0) then if (RECEIVED = '1') then counter := 1; else counter := 0; end if; LED(rcv_led_num) <= '0'; else if (counter = pulse_length) then counter := 0; else counter := counter + 1; end if; LED(rcv_led_num) <= '1'; end if; end if; end process; -- Power Indicator Look-Up Table. Maps detector power values to display -- intensity values. Power_Map : entity P3038DM_PWMAP port map ( Address => pwr_rcv, Q => pwr_intensity ); -- Power LED brightness proportional to the log of most recetnly-received -- message's power. We use the slow clock to generate a lamp signal with -- duty cycle proportional to log power. Power_Indicator : process (SLWCK) is variable counter : integer range 0 to 255; begin if rising_edge(SLWCK) then if SHOW = '1' then LED(pwr_led_num) <= '1'; elsif HIDE = '1' then LED(pwr_led_num) <= '0'; elsif counter < to_integer(unsigned(pwr_intensity)) then LED(pwr_led_num) <= '1'; else LED(pwr_led_num) <= '0'; end if; counter := counter + 1; end if; end process; TP(1) <= SDO; TP(2) <= WRMSG; TP(3) <= RDMSG; TP(4) <= DSD; end behavior;