Square waveform generator

On the following three-part tutorial, a square waveform generator is presented.
The requirements for the project are to generate a sequence of square waveforms with different frequencies. For each frequency a number of cycles is generated (different for each one). For each frequency, a distinct duty cycle is also defined.

In this implementation the frequency is defined in Hz., and the active high time in ns. The VHDL code does not validate the parameters, i.e, if the active high time for any frequency is longer than its period, the output will be always '1' for that frequency. For each frequency, a number of cycles is defined.

This project was born over a discussion in Xilinx forums. Once I did the project for a specific configuration I started thinking about a way to make a generic solution, and this tutorial tries to reflect the design process of this small project.

The code is presented below. Three different frequencies FREQ1..3 are defined for this example, 242KHz, 23kHz and 57kHz. Notice that each frequency is defined in Hz.

The active high time for each frequency is defined in the ACT_HIGH1..3 constants, and the number of cycles for each frequency with NCYCLES1..3

There are some calculations involved for CNTn and CNT_HIn. I will comment on them on the third part of the tutorial.


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use work.top_pkg.all;

-- Uncomment the following library declaration if instantiating
-- any Xilinx leaf cells in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity top is
  Port ( 
    clk       : in STD_LOGIC;
    reset     : in STD_LOGIC;
    data_out  : out STD_LOGIC
  );
end top;

architecture Behavioral of top is
  -- Used for frequency generation
constant FREQ1    : natural := 242720;            -- in Hz
constant FREQ2    : natural := 23600;             
constant FREQ3    : natural := 57400;
constant CNT1     : natural := (10*SYS_FREQ/FREQ1 + 5)/10;
constant CNT2     : natural := (10*SYS_FREQ/FREQ2 + 5)/10;
constant CNT3     : natural := (10*SYS_FREQ/FREQ3 + 5)/10;

-- Used for duty cycle of output signal
constant ACT_HIGH1 : natural := 1200;              -- in ns
constant ACT_HIGH2 : natural := 15000;             -- in ns
constant ACT_HIGH3 : natural := 9000;              -- in ns
constant CNT_H1   : natural := (ACT_HIGH1*SYS_FREQ+5e8) / 1e9;
constant CNT_H2   : natural := (ACT_HIGH2*SYS_FREQ+5e8) / 1e9;
constant CNT_H3   : natural := (ACT_HIGH3*SYS_FREQ+5e8) / 1e9;
constant NCYCLES1 : natural := 3;
constant NCYCLES2 : natural := 1;
constant NCYCLES3 : natural := 2;
    
  signal   freq_cnt : unsigned(15 downto 0);
  signal   act_cnt  : unsigned(15 downto 0);
  signal   cyc_cnt  : unsigned(15 downto 0);
  
  type sm_type is (idle, cyc1, cyc2, cyc3);
  signal   state    : sm_type;  
begin
    seq_pr : process(clk)
    begin
      if (rising_edge(clk)) then
        if (reset = '1') then
          state    <= idle;
          data_out <= '0';
        else  
          case(state) is
            when idle =>
              freq_cnt <= to_unsigned(CNT1-1, 16); 
              act_cnt  <= to_unsigned(CNT_H1-1, 16); 
              cyc_cnt  <= to_unsigned(NCYCLES1-1, 16);          
              data_out <= '1';
              state    <= cyc1;  
            when cyc1 =>
              -- Active high timing
              if (act_cnt = 0) then
                data_out <= '0';
              else
                act_cnt  <= act_cnt - 1;  
              end if;
              
              -- Frequency and cycles                
              if (freq_cnt = 0) then                
                -- check number of cycles for this frequency
                data_out <= '1';
                if (cyc_cnt = 0) then
                  freq_cnt <= to_unsigned(CNT2-1, 16);          
                  act_cnt  <= to_unsigned(CNT_H2-1, 16);          
                  cyc_cnt  <= to_unsigned(NCYCLES2-1, 16);          
                  state    <= cyc2;
                else   
                  cyc_cnt  <= cyc_cnt - 1;              
                  freq_cnt <= to_unsigned(CNT1-1, 16);          
                  act_cnt  <= to_unsigned(CNT_H1-1, 16);          
                end if;
              else     
                freq_cnt <= freq_cnt - 1;
              end if;  
              
            when cyc2 =>
              -- Active high timing
              if (act_cnt = 0) then
                data_out <= '0';
              else
                act_cnt  <= act_cnt - 1;  
              end if;
              
              -- Frequency and cycles                
              if (freq_cnt = 0) then                
                -- check number of cycles for this frequency
                data_out <= '1';
                if (cyc_cnt = 0) then
                  freq_cnt <= to_unsigned(CNT3-1, 16);          
                  act_cnt  <= to_unsigned(CNT_H3-1, 16);          
                  cyc_cnt  <= to_unsigned(NCYCLES3-1, 16);          
                  state    <= cyc3;
                else   
                  cyc_cnt  <= cyc_cnt - 1;              
                  freq_cnt <= to_unsigned(CNT2-1, 16);          
                  act_cnt  <= to_unsigned(CNT_H2-1, 16);          
                end if;
              else     
                freq_cnt <= freq_cnt - 1;
              end if;  
            when cyc3 =>
              -- Active high timing
              if (act_cnt = 0) then
                data_out <= '0';
              else
                act_cnt  <= act_cnt - 1;  
              end if;
            
              if (freq_cnt = 0) then                
                -- check number of cycles for this frequency
                data_out <= '1';
                if (cyc_cnt = 0) then
                  freq_cnt <= to_unsigned(CNT1-1, 16);          
                  act_cnt  <= to_unsigned(CNT_H1-1, 16);          
                  cyc_cnt  <= to_unsigned(NCYCLES1-1, 16);          
                  state    <= cyc1;
                else   
                  cyc_cnt  <= cyc_cnt - 1;              
                  freq_cnt <= to_unsigned(CNT3-1, 16);          
                  act_cnt  <= to_unsigned(CNT_H3-1, 16);          
                end if;
              else     
                freq_cnt <= freq_cnt - 1;
              end if;  
            when others =>
              state <= idle;
          end case;
        end if;
      end if;  
    end process seq_pr;           

end Behavioral;


The core of the logic is a state machine. For each state, cyc1..3, the corresponding frequency is
generated. Three counters are used:

  • cyc_cnt, to keep track of the number of cycles, 
  • act_cnt, to take care of the duty cycle of the waveform (how long it is '1', or  active)
  • and freq_cnt, to generate the frequency. 


What I really don't like of this example is the repetition of a block of code for each state. There are several ways to solve this repetition (which is error prone). The one I selected will be presented on the next part of the tutorial.


This is the waveform generated on the simulation:


It can be appreciated the number of cycles (3, 1, 2) generated for each frequency. Three cycles are generated for the fastest frequency, two cycles for the mid frequency, and one cycle for the slowest frequency.

As always, the source code and test bench is available at Github

Comments

Popular posts from this blog

Xilinx AXI Stream tutorial - Part 1

Analysis, elaboration and synthesis