VHDL can implement procedures. Even if the VHDL is a hardware description language, it can define procedures like a common programming language.
When we use a procedure, we always need to take in mind how the procedure code is translated in hardware implementation.
In this post, we will address the classical use of a procedure. It is a software approach, not a hardware approach.
First of all, as a golden rule, we need to take in mind what my professor of computer science used to repeat to me during the lessons at university:
When you need to write twice a piece of code, write a procedure or function.
Well, also in VHDL we can use the same approach.
Let’s see how to declare a procedure in VHDL.
VHDL Procedure declaration syntax
A procedure in VHDL is a subprogram. In VHDL there are 2 types of subprogram:
- Procedure
- Function
Differences between procedures and functions are basically:
- Procedure can return more than one argument, can have input parameters, output parameters, and inout parameters.
- Function always returns just one. In a function, all parameters are input parameters.
What we are going to say for the procedure is valid also for the functions.
A procedure can be either:
- Concurrent
- Sequential
The concurrent procedure exists outside of a process statement or another subprogram.
The sequential procedure exists only in a process statement or another subprogram statement.
All statements inside of a subprogram are sequential by definition. Since a process is a sequential statement, the same statements that exist in a process statement can be used in a subprogram, including WAIT statements.
The main differences between a procedure and function are that a procedure can exist as a separate statement in an architecture or process, a function is usually used in an assignment statement or expression.
Procedures can be defined in a VHDL package. The TextIO package is an example where we can find all the procedures (and functions) we can use to manage FILE in VHDL.
Another example is the numeric_std package where we can find all the procedures and functions for signed and unsigned number handling.
Coming back to the procedure declaration, the BNF of the VHDL procedure declaration is:
procedure procedure_name [ ( actual_parameter_part ) ] is [variable declaration] | [sub_program declaration] Begin [procedure body] End [procedure_name]; actual_parameter_part::= [variable variable_name : in|out|inout variable_type]| [constant constant_name : in|out|inout constant _type]| [signal signal_name : in|out|inout signal _type] procedure_call ::= procedure_name [ ( actual_parameter_part ) ] procedure_call_statement ::= [ label : ] procedure_call ;
Ok, I know that it is quite complicated to understand how to deal with the procedure only with the BNF declaration. I think an example worth thousands of BNF declaration.
BUS access simulation using VHDL
As example, we will see how to implement a procedure to drive a simple BUS interface signals for a test bench.
The BUS interface is composed by
- csb: chip select (active low)
- wrb: write strobe (active low)
- rdb: read strobe (active low)
- addr: address bus
- data_in: input data to the interface
- data_out: output data from the interface
the interface is designed synchronous to a system clock. Of course, we can design an asynchronous interface using the same procedure strategy.
Take in mind that this VHDL code for the procedure simulating a BUS interface can be used only in a test bench to emulate the BUS interface behavior. This VHDL code cannot be synthesized. The synthesizer will rise a compile error if you try to synthesize.
The result of the simulation is reported in Figure 1
Figure 1 VHDL procedure BUS simulation wave
The first BUS transaction is relative to a bus-write. In this procedure implementation, the bus-write-cycle is four clock cycles. The bus write signal became one clock cycle after the chip select signal.
Address and data bus change on chip select low. In this implementation, the write cycle takes 5 clock cycles.
The read cycle starts with chip select low and read signal low. In this implementation, the read cycle takes 9 clock cycles. The address bus changes on the csb/rdb signal. The evidence of data read is given by the transcript in Figure 2
Figure 2 VHDL procedure BUS simulation transcript
Is it possible to change all this values depending on the simulation needs. Let’s see a possible implementation of a VHDL test bench procedure for BUS read and BUS write access.
BUS access VHDL procedure
A possible implementation of a BUS writes and read access VHDL procedure is reported below.
p_control : process procedure wait_clk(signal i_clk : in std_logic; val : in integer) is begin for i in 1 to val loop wait until rising_edge(i_clk); end loop; end procedure wait_clk; procedure bus_write( i_addr : in std_logic_vector(31 downto 0); i_data : in std_logic_vector(31 downto 0); signal i_clk : in std_logic; signal o_csb : out std_logic; signal o_wrb : out std_logic; signal o_rdb : out std_logic; signal o_addr : out std_logic_vector(31 downto 0); signal o_data : out std_logic_vector(31 downto 0); log_on : in boolean ) is variable L : line; variable t : time; begin wait_clk(i_clk, 1); t := now; o_csb <= '0'; o_wrb <= '1'; o_rdb <= '1'; o_addr <= i_addr; o_data <= i_data; wait_clk(i_clk, 1); o_csb <= '0'; o_wrb <= '0'; o_rdb <= '1'; wait_clk(i_clk, 3); o_csb <= '0'; o_wrb <= '1'; wait_clk(i_clk, 1); o_csb <= '1'; o_wrb <= '1'; write(L,now, justified => right, field=>20,unit=>ns); write(L, string'(" << WRITE BUS ")); write(L,now, justified => right, field=>10,unit=>ns); write(L, string'(" ADDR 0x")); hwrite(L,i_addr, justified => left, field=>8); write(L, string'(" DATA 0x")); hwrite(L,i_data, justified => left, field=>8); write(L, string'(" @ ")); write(L,now, justified => right, field=>10,unit=>ns); write(L, string'(" >>")); if(log_on) then writeline(output,L); end if; wait_clk(i_clk, 2); end procedure bus_write; procedure bus_read( i_addr : in std_logic_vector(31 downto 0); signal i_clk : in std_logic; signal o_csb : out std_logic; signal o_wrb : out std_logic; signal o_rdb : out std_logic; signal o_addr : out std_logic_vector(31 downto 0); signal i_data : in std_logic_vector(31 downto 0); log_on : in boolean ) is variable L : line; variable t : time; variable v_data : std_logic_vector(31 downto 0); begin wait_clk(i_clk, 1); t := now; o_csb <= '0'; o_wrb <= '1'; o_rdb <= '0'; o_addr <= i_addr; wait_clk(i_clk, 9); o_csb <= '1'; o_wrb <= '1'; o_rdb <= '1'; v_data := i_data; write(L,now, justified => right, field=>20,unit=>ns); write(L, string'(" << READ BUS ")); write(L,now, justified => right, field=>10,unit=>ns); write(L, string'(" ADDR 0x")); hwrite(L,i_addr, justified => left, field=>8); write(L, string'(" DATA 0x")); hwrite(L,v_data, justified => left, field=>8); write(L, string'(" @ ")); write(L,now, justified => right, field=>10,unit=>ns); write(L, string'(" >>")); if(log_on) then writeline(output,L); end if; wait_clk(i_clk, 2); end procedure bus_read; begin bus_write( X"11223344", X"AABBCCDD", clk , csb , wrb , rdb , addr , data , true ); bus_read( X"55667788", clk , csb , wrb , rdb , addr , data_out , true ); wait; end process p_control;
The procedures are implemented inside the sequential VHDL code relative to the “p_process” process. You know that we can use a VHDL package to write all our test bench functions.
In this example, we are using the procedure “wait_clk” to implement a clock cycles delay inside the main procedure.
You can modify the VHDL code for write and read bus access emulation very easily. In this synchronous implementation you can modify the clock delays simply modifying the delay value during the call of the wait_clk procedure.
If you want to implement an asynchronous based bus access, just use an asynchronous delay procedure implementation instead of the synchronous wait_clk procedure.
Conclusion
In this post, we saw how to implement a VHDL test bench for BUS access simulation.
As you can see, the procedure approach in the test bench design makes the VHDL test bench implementation straightforward.
Moreover, using the procedure approach, we can easily modify the delay implementation. In this example we are using the synchronous delay approach: we call the wait_clk procedure that implement a clock cycles delay. If we modify the wait_clk procedure using an asynchronous delay approach, the main read and write procedures structure remain the same, modifying only the way of the delay implementation.
References
[1] VHDL cookbook
[2] VHDL Wikipedia
It is even harder to understand when you use the name actual_parameter_part to refer to the formal parameters.
Hi, if you check the example it should be clear