This VHDL module receives parallel data as input at it outputs the data in serial format.
This VHDL module receives parallel data from the data_in bus when load is asserted. One clock after load is de-asserted, the data is serially transmitted out on the data_out line, MSB first, and valid is also asserted. The frame signal is asserted together with the end of the transmission (i.e. when the LSB is transmitted).
A busy signal informs the host on the parallel side that a transmission is ongoing.
I posted a previous version of this block on Reddit and received extensive remarks and help from user @asp_digital. The resulting code, generic and with lots of comments, is presented below:
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity par2ser is port ( clk : in std_logic; -- inputs data_in : in std_logic_vector; load : in std_logic; -- outputs data_out : out std_logic; busy : out std_logic := '0'; frame : out std_logic := '0' ); end par2ser; architecture rtl of par2ser is signal cnt : natural range 0 to data_in'left; signal reg : std_logic_vector (data_in'left downto 0) := (others => '0'); -- normalize the unconstrained input alias data_in_norm : std_logic_vector(data_in'length - 1 downto 0) is data_in; -- normalized unconstrained input begin par2ser_pr : process (clk) begin if (rising_edge(clk)) then -- the bit counter, for framing, always counts down to zero and then stops. -- The counter is preset when the parallel register is loaded. BitCounter : if cnt > 0 then cnt <= cnt - 1; end if BitCounter; -- busy flag should be cleared when not shifting. -- It gets set immediately on load, overriding this. -- Busy can be cleared on the last clock of the current data transmision EndBusy : if cnt = 1 then busy <= '0'; end if EndBusy; ParallelReg : if (load = '1') then reg <= data_in_norm; -- load parallel register cnt <= data_in_norm'left; -- size of the parallel register busy <= '1'; -- ensure immediate busy flag end if ParallelReg; -- frame strobe goes true when the count reaches zero, and is cleared right away. -- Let's take advantage of pipelining. AssertFrameStrobe : if cnt = 1 then frame <= '1'; else frame <= '0'; end if AssertFrameStrobe; end if; end process par2ser_pr; data_out <= reg(cnt); end rtl;
The par2ser_pr process handles the load signal and updates the internal counter cnt to check when a transaction is complete. The process frame_pr is used to generate a single clock pulse when cnt achieves the value zero, indicating the end of the transaction.
In the waveforms below, several packets are sent for two instantiations of the parallel to serial converter (each one with different width):
In the first instantiation of the module, the input and output signals are shown separately. The second instantiation signals summary is shown after the last divider.
This implementation of the block generates a mux on line 57. As proposed by the same user, @asp_digital, I attach below an implementation that uses a shift register to convert the data from parallel to serial format:
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity par2ser_sr is port ( clk : in std_logic; -- inputs data_in : in std_logic_vector; load : in std_logic; -- outputs data_out : out std_logic; busy : out std_logic := '0'; frame : out std_logic := '0' ); end par2ser_sr; architecture rtl of par2ser_sr is signal cnt : natural range 0 to data_in'left; signal reg : std_logic_vector (data_in'left downto 0) := (others => '0'); begin par2ser_pr : process (clk) begin if (rising_edge(clk)) then -- the bit counter, for framing, always counts down to zero and then stops. -- The counter is preset when the parallel register is loaded. BitCounter : if cnt > 0 then cnt <= cnt - 1; reg <= reg(data_in'left-1 downto 0) & '0'; end if BitCounter; -- busy flag should be cleared when not shifting. -- It gets set immediately on load, overriding this. EndBusy : if cnt = 0 then busy <= '0'; end if EndBusy; ParallelReg : if (load = '1') then reg <= data_in; -- load parallel register cnt <= data_in'left; -- size of the parallel register busy <= '1'; -- ensure immediate busy flag end if ParallelReg; -- frame strobe goes true when the count reaches zero, and is cleared right away. -- Let's take advantage of pipelining. AssertFrameStrobe : if cnt = 1 then frame <= '1'; else frame <= '0'; end if AssertFrameStrobe; end if; end process par2ser_pr; data_out <= reg(data_in'left); end rtl;
As proposed exercises:
- Write a testbench for the par2ser_sr module
- Synthesize several sizes of the two blocks and compare which implementation takes fewer resources and can be implemented using a faster clock, the mux-based implementation, or the shit-register one.
The source codes (for mux base and for shift_register based implementations), testbench, and Vivado waveform file for simulation are available on GitHub.