1
\$\begingroup\$

enter image description here

How do I synchronize this system? The data valid at the input indicates when the data is valid at the input. Similarly the data out valid indicates when the output data is valid. Both data_in_valid and data_out_valid remain high for one clock cycle.

This is to be done without a FIFO.

What is required is I am writing data at 100 MHz and want to read it at 30 MHz at the output. The signal valid_in is respect of the input clock and valid_out is respect to output clk.

clkA and clkB are both inputs. Data_in and Data_out is 1 byte written and read respectively. It is required to write data at clkA when valid_in is high and read data at clkB when valid_out is high. What I had answered was with the use of 2 stage synchronizer (running at clkB) for both data_in and valid_in and the outputs of the synchronizers wold be valid_out and data_out respectively.

\$\endgroup\$
  • \$\begingroup\$ We would need a little more detail on what you are trying to achieve here. But based on a single block that I am seeing, the system is already synchronous as there must be only a single CLK source to the system. \$\endgroup\$ – user50745 Aug 10 '14 at 13:04
  • \$\begingroup\$ I was asked about it in an interview.The clkA is the write clk while clkB is the read clk. I think there is a need for synchronizers here. As there are two clock sources the system is not synchronous \$\endgroup\$ – user22348 Aug 10 '14 at 13:15
  • \$\begingroup\$ Looks like you need a latch. I don't think synchronize is the right word. I think of synchronizing as getting some random input (say a button push) to turn on with the system clock. But I'm not a digital guy, and so I may be wrong. \$\endgroup\$ – George Herold Aug 10 '14 at 14:18
  • 2
    \$\begingroup\$ How can it ever be synchronous - you have clocks running at different speeds. End of story. \$\endgroup\$ – Andy aka Aug 10 '14 at 15:58
  • \$\begingroup\$ Are the clock signals inputs or outputs? \$\endgroup\$ – The Photon Aug 10 '14 at 17:54
1
\$\begingroup\$

I think I'd have answered this interview question the same way you did. I believe the interviewer's requirement "to be done without a FIFO" was because a FIFO buffer is a valid, practical way to solve the problem of multiple clock domains -- but it can be done without the head/tail logic of a complete FIFO in many cases. And in the context of a job interview, simply instantiating a standard module doesn't demonstrate that you understand how to approach FPGA / HDL design. (I've interviewed candidates who couldn't even manage that small task.)

Passing data between different clock domains is usually done with three stages of flip-flops. The first stage is in the source clock domain (clkA), and the second and third stage flip-flops are in the receiver clock domain (clkB). The setup time of the second stage flip-flop is sometimes violated because the clocks are not synchronous, so the third-stage flip-flop is used to clean up the timing. Since there is a delay, the data_valid signal is passed in parallel with the data.

module SyncExample (
    input   wire            clkA,
    input   wire    [7:0]   Data_in,        // in clkA clock domain
    input   wire            Data_valid,     // in clkA clock domain
    input   wire            clkB,
    output  reg     [7:0]   Data_out,       // in clkB clock domain
    output  reg             Data_out_valid  // in clkB clock domain
    )

// First stage pipeline registers the clkA clock domain signals.
// pipeline_1_valid is set by Data_valid and remains set 
// until cleared by pipeline_1_valid_clear acknowledge from clkB domain.
reg [7:0] pipeline_1_data;
reg       pipeline_1_valid;
wire      pipeline_1_valid_clear;
initial begin
    pipeline_1_data <= 0;
    pipeline_1_valid <= 0;
end
always @(posedge clkA) begin
    if (Data_valid) begin
        // capture pipeline_1_data only when Data_in is valid
        pipeline_1_data <= Data_in;
    end
    // keep pipeline_1_valid set after Data_valid, until pipeline_1_valid_clear.
    pipeline_1_valid <= (Data_valid | (pipeline_1_valid & ~pipeline_1_valid_clear));
end

// Second stage pipeline registers the clkB clock domain signals.
// Because clkA and clkB are asynchronous clock domains, 
// setup time cannot be guaranteed for this stage.
// The previous pipeline_1 stage holds its data valid for
// more than one clkA cycle, to help achieve clkB setup requirement.
reg [7:0] pipeline_2_data;
reg       pipeline_2_valid;
initial begin
    pipeline_2_data <= 0;
    pipeline_2_valid <= 0;
end
always @(posedge clkB) begin
    pipeline_2_data <= pipeline_1_data;
    pipeline_2_valid <= pipeline_1_valid;
end

// Third stage pipeline registers the clkB clock domain signals.
initial begin
    Data_out <= 0;
    Data_out_valid <= 0;
end
always @(posedge clkB) begin
    Data_out <= pipeline_2_data;
    Data_out_valid <= pipeline_2_valid;
end

// pipeline_1_valid_clear timing feedback signals when the data-valid signal
// has propagated through all stages.
// For this simple example, we assume data_out is captured as soon as it is valid.
// A practical application should instead drive this with a read_data_out command.
assign pipeline_1_valid_clear = Data_out_valid;

endmodule;

You can also find similar example code in Xilinx ISE Language Templates under Verilog | Synthesis Constructs | Coding Examples | Misc | Asynchronous Input Synchronization.

edit: Added pipeline_1_valid_clear signal and set/clear behavior to meet the slower clock domain's minimum pulse width requirement. Capture pipeline_1_data only when Data_in is valid.

\$\endgroup\$
  • \$\begingroup\$ The data_in_valid is a single clock wide signal in the clkA domain. when we move from a fast clock domain to a slow clock domain we require the signal to be asserted for atleast 1+1/2 cycle in the clkB domain to be detected properly so that it gets sampled atleast by two edges.This is not being achieved in your code. I think this was what the interviewer expected from me and i had no idea about it \$\endgroup\$ – user22348 Aug 12 '14 at 11:12
  • \$\begingroup\$ Good point - sync from faster clock domain needs the first stage to latch (set) to meet the slower clock domain minimum pulse width requirement. Let me fix this up tonight... \$\endgroup\$ – MarkU Aug 12 '14 at 19:08
  • \$\begingroup\$ Sorry but this design does not work as well. \$\endgroup\$ – Carter Feb 5 '15 at 8:46
0
\$\begingroup\$

What we know - the data in and the data out are in serial form, the data is 8 bits and the clocks are asynchronous and different (unrelated) frequencies. Also we aren't allowed to use FIFO.

I don't think synchronize is the correct terminology. Its more like a data buffering circuit, taking fast serial data in, storing it and clocking it out serially at a lower rate (e.g hard disk write, printer etc.). Given that it was an interview question then no great circuit detail could be expected.

enter image description here

The block digram shows the sort of elements I would expect in such a system. Counters would be needed to ensure all 8 bits were accounted for and a small amount of glue logic (gates) to operate read/write etc. Additionally RAM could be added between the SIPO and the PISO devices to improve throughput.

\$\endgroup\$
0
\$\begingroup\$

Here's how I understand this should work. I knocked this up and tested in Xilinx ISim.

This synchronises the valid flag across the domains, holding it in the A domain until it is seen to arrive in the B domain, and using edge detection in the B domain to regenerate a single cycle strobe.

The data bus is registered from A into B domain when we know it has been stable for several clock cycles (in both domains) so is safe to use like this.

This design could have things like a ready flag added, etc.

bus_sync.vhd:

-- CDC bus synchroniser
-- Depends on bus A being at least ~1.5x faster than bus B

library ieee;
use ieee.std_logic_1164.all;

entity bus_sync is
port (
    data_a  : in  std_logic_vector(7 downto 0);
    valid_a : in  std_logic;
    clk_a       : in  std_logic;
    data_b  : out std_logic_vector(7 downto 0);
    valid_b : out std_logic;
    clk_b       : in  std_logic
);
end bus_sync;

architecture behavioral of bus_sync is

    signal data_a_reg, data_b_reg : std_logic_vector(7 downto 0);
    signal valid_a_hold: std_logic := '0';
    signal valid_ack_a_sync, valid_ack_a: std_logic;
    signal valid_b_sync, valid_b_in, valid_b_last: std_logic;
    signal valid_b_reg: std_logic;

begin

    bus_in_sync_proc: process (clk_a)
    begin
        if rising_edge(clk_a) then
            if valid_a = '1' then
                data_a_reg <= data_a;
                valid_a_hold <= '1';
            elsif valid_ack_a = '1' then
                -- b domain has seen the valid strobe. We can deassert.
                valid_a_hold <= '0';
            end if;
        end if;
    end process;

    valid_sync_ack: process (clk_a)
    begin
        -- synchronise valid_b back into A domain as an 'ack'
        -- (could just keep valid_a_hold asserted for a number of clocks)
        -- domain A clocks 3x faster than domain B so we will not miss it.
        if rising_edge(clk_a) then
            valid_ack_a_sync <= valid_b_in; -- avoid metastability
            valid_ack_a <= valid_ack_a_sync;
        end if;
    end process;

    b_sync_proc: process (clk_b)
    begin
        if rising_edge(clk_b) then
            -- synchronise valid flag into b domain through 2 DFFs
            valid_b_sync <= valid_a_hold;
            valid_b_in <= valid_b_sync;
            -- and one more for edge detection
            valid_b_last <= valid_b_in;

            if valid_b_last = '0' and valid_b_in = '1' then
                -- valid strobe arriving in b domain
                -- registered data is stable in data_a_reg
                data_b_reg <= data_a_reg;
                valid_b_reg <= '1';
            else
                valid_b_reg <= '0';
            end if;
        end if;
    end process;

    data_b <= data_b_reg;
    valid_b <= valid_b_reg;

end behavioral;

an example testbench:

library ieee;
use ieee.std_logic_1164.all;

entity bus_sync_tb is
end bus_sync_tb;

architecture behavior of bus_sync_tb is 

    -- component declaration for the unit under test (uut)
    component bus_sync
    port(
         data_a : in  std_logic_vector(7 downto 0);
         valid_a : in  std_logic;
         clk_a : in  std_logic;
         data_b : out  std_logic_vector(7 downto 0);
         valid_b : out  std_logic;
         clk_b : in  std_logic
        );
    end component;

   --inputs
   signal data_a : std_logic_vector(7 downto 0) := (others => '0');
   signal valid_a : std_logic := '0';
   signal clk_a : std_logic := '0';
   signal clk_b : std_logic := '0';

    --outputs
   signal data_b : std_logic_vector(7 downto 0);
   signal valid_b : std_logic;

   -- clock period definitions
   constant clk_a_period : time := 10 ns;
   constant clk_b_period : time := 33.333 ns;

begin

    -- instantiate the unit under test (uut)
    uut: bus_sync port map (
        data_a => data_a,
        valid_a => valid_a,
        clk_a => clk_a,
        data_b => data_b,
        valid_b => valid_b,
        clk_b => clk_b
    );

   -- clock process definitions
   clk_a_process :process
   begin
        clk_a <= '0';
        wait for clk_a_period/2;
        clk_a <= '1';
        wait for clk_a_period/2;
   end process;

   clk_b_process :process
   begin
        clk_b <= '0';
        wait for clk_b_period/2;
        clk_b <= '1';
        wait for clk_b_period/2;
   end process;


   -- stimulus process
   stim_proc: process
   begin
      wait for clk_a_period*10;

      -- change testbench inputs on falling edge of clk.
        -- makes the ordering easier to see in wave view.
        wait until clk_a = '0';
        data_a <= x"5a";
        valid_a <= '1';
        wait until clk_a = '0';
        data_a <= (others => '0');
        valid_a <= '0';

        -- must not send more data through too soon!

        wait for clk_b_period*10;

        -- halt the simulation the only way I know how in VHDL:
        assert false report "TEST COMPLETE OK" severity failure;

   end process;

end;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.