Testbench for a reaction timer design VHDL - vhdl

I have to test using modelsim this component:
COMPONENT part5
PORT ( CLOCK_50,KEY0,KEY3 : IN STD_LOGIC;
SW: IN STD_LOGIC_VECTOR (7 DOWNTO 0);
HEX3,HEX2,HEX1,HEX0: OUT STD_LOGIC_VECTOR (6 DOWNTO 0);
LEDR : OUT STD_LOGIC_VECTOR (1 DOWNTO 0)
);
END COMPONENT;
It will be implemented on altera DE2.
It is supposed to work with the clock (CLOCK_50),after KEY0 goes to logic level 1, counting clock periods till it reach the numeber inserted on SW(7 DOWNTO 0),at this point it turns on a red led : LEDR. From the time LEDR is ON the four hexadecimal displays (HEX0, HEX1,HEX2 and HEX3) start to count at 1 ms interval. I have to push a button KEY3 (on the DE2 board) as quickly as possible till I reach the value rappresented in SW(7 DOWNTO 0) : the red light turns off and displays stop counting.
I tried with this:
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;`
ENTITY tb_part5 IS
END ENTITY;
ARCHITECTURE beha OF tb_part5 IS
COMPONENT part5
PORT ( CLOCK_50,KEY0,KEY3 : IN STD_LOGIC;
SW: IN STD_LOGIC_VECTOR (7 DOWNTO 0);
HEX3,HEX2,HEX1,HEX0: OUT STD_LOGIC_VECTOR (6 DOWNTO 0);
LEDR : OUT STD_LOGIC_VECTOR (1 DOWNTO 0)
);
END COMPONENT;
SIGNAL clk,key,rst: STD_LOGIC:='0';--inputs
SIGNAL switch: STD_LOGIC_VECTOR (7 DOWNTO 0);
SIGNAL led,led1: STD_LOGIC;--outputs
SIGNAL dec0,dec1,dec2,dec3 : STD_LOGIC_VECTOR (6 DOWNTO 0);
BEGIN
switch<="00001010";
PROCESS --clock
BEGIN
clk<='1' AFTER 10 ns ;
clk<='0' AFTER 20 ns ;
END PROCESS;
PROCESS --reset
BEGIN
rst<='0';
WAIT FOR 20 ns;
rst<='1';
WAIT;
END PROCESS;
PROCESS
BEGIN
IF led='1' THEN
key<= '1';
WAIT FOR 20 ns;
key<='0';
WAIT FOR 20 ns;
ELSE
key<='0';
END IF;
END PROCESS;
DUT : part5 PORT MAP (CLOCK_50=>clk, KEY0=>rst,KEY3=>key,SW=>switch,HEX3=>dec3,HEX2=>dec2,HEX1=>dec1,HEX0=>dec0,LEDR(0)=>led,LEDR(1)=>led1);
END beha;
But the simulation don't show any results. I'm not very good at testbenches, I really want to understand how they works, specially with the generation of the clock, and insertion of wave vectors! Maybe I've could explain my doubts better, but if someone could show me a testbench example for beginners it would be very helpful!
Thanks

A beginners architecture for test bench can be really simple. You can test a lot of components with just 5 process (including clk and reset process). When you create a test bench you will usually generate at least one clock and a reset for your design under test (DUT). For those process you can keep the following and this should work for all single clock design (does not matter if reset is synchronous or asynchronous). Then you will create a stimulus process. This process will allow you to generate data for your DUT (you will affect signals connected to your DUT). This process can define the end of your simulation. You can set some signals and wait for 100000 ns if you want to test a sequence without modify signals (DUT inputs).
After generate stimulus you can launch simulation and manually verify your DUT outputs but this in not the best way to do (maybe in your case but not in a bigger design). The easiest way to control the integrity of your outputs is to generate a reference. This reference is the expected reaction of your design. E.g : if you want to realize a design that wait 100 clock cycles. You will create a reference signal of the output but you do not have to use VHDL that you can synthesize. You have access to all VHDL function (wait for, wait until, etc...).
At the end you will have the last process, the checker. This one will compare dut outputs and reference to define if there is some errors or not in your design.
Do not forget to put a wait statement in all process (depending on end_sim_s per exemple) to stop your simulation when everthing has been simulated
Here is an empty test bench structure :
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity tb_part5 is
port (
-- no IO for test bench
);
end entity;
architecture beha of tb_part5 is
---------------
-- Constants --
---------------
constant CLOCK_PERIOD : time := 10 ns; -- e.g.
------------------------
-- Test bench signals --
------------------------
signal clk_sti : std_logic := '0';
signal rst_sti : std_logic := '1'; -- !!! activ high !!!
-- end of sim flag
signal end_sim_s : boolean := false;
begin
----------------------
-- Clock generation --
----------------------
process
begin
clk_sti <= '1';
wait for CLOCK_PERIOD/2;
clk_sti <= '0';
wait for CLOCK_PERIOD/2;
if end_sim_s = true then
wait; -- end of simulation
end if;
end process;
--------------------
-- Reset sequence --
--------------------
process
begin
rst <= '1';
wait for 2*CLOCK_PERIOD;
rst <= '0';
wait;
end process;
----------------------
-- Stimulus process --
----------------------
process
begin
-- default values for DUT inputs
-- wait end of reset sequence
wait until (rst_sti = '0');
-- do something
-- end of simulation
end_sim_s <= true;
wait;
end process;
-----------------------
-- Reference process --
-----------------------
-------------------
-- Check process --
-------------------
-----------------------
-- DUT instanciation --
-----------------------
end beha;
For your test bench I propose you the following architecture. But you have to be conscious your test bench does not verify anything.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity tb_part5 is
port (
-- no IO in test bench
);
end entity;
architecture beha of tb_part5 is
---------------
-- Constants --
---------------
constant CLOCK_PERIOD : time := 10 ns; -- e.g.
-----------------------
-- Internals signals --
-----------------------
signal clk, key, rst : std_logic := '0'; --inputs
signal switch : std_logic_vector (7 downto 0);
signal led, led1 : std_logic; --outputs
signal dec0, dec1, dec2, dec3 : std_logic_vector (6 downto 0);
-- test bench signals
signal end_sim_s : boolean := false;
begin
switch <= "00001010";
----------------------
-- Clock generation --
----------------------
process
begin
clk <= '0';
wait for CLOCK_PERIOD/2;
clk <= '1';
wait for CLOCK_PERIOD/2;
if end_sim_s = true then
wait; -- end of simulation
end if;
end process;
--------------------
-- Reset sequence --
--------------------
process
begin
-- TIPS : if you want to be more efficient you should us a norm to
-- define your signal. A reset signal activ low can be called nRst for
-- exemple. Maybe actually you have an activ low reset but maybe not.
-- This exemple show a reset activ low sequence
rst <= '0';
wait for 2*CLOCK_PERIOD;
rst <= '1';
wait;
end process;
---------------------
-- Your TEST bench --
---------------------
-- this part do the same thing that you were asking.
process
begin
-- the if statement in the previous version is not a good thing to do.
-- in fact, you want your process to wait until an event.
-- initial state (default value)
key <= '0';
-- wait until the end of reset sequence (just in case)
wait until (rst = '1'); -- e.g.
-- wait until DUT assert led
wait until (led = '1'); -- e.g.
-- start your sequence
key <= '1';
wait for 20 ns;
key <= '0';
wait for 20 ns;
-- here you have 2 choices. let the process iterate a second time or just
-- end the simulation at this moment
-- stop here
-- notify the others process the end of simulation
end_sim_s <= true;
-- block process
wait;
end process;
-----------------------
-- DUT instanciation --
-----------------------
DUT : part5 port map (
CLOCK_50 => clk,
KEY0 => rst,
KEY3 => key,
SW => switch,
HEX3 => dec3,
HEX2 => dec2,
HEX1 => dec1,
HEX0 => dec0,
LEDR(0) => led,
LEDR(1) => led1
);
end beha;
Hope this will help you.
Regards.
Mike

Related

Entity has no architecture problem in Modelsim

I'm new to FPGA design using VHDL and I'm stucked in a problem of testbench simulation: each time I try to simulate my model (which testbench was given by the testbench writer) I get the following error that's driving me crazy:
Error: (vsim-3173) Entity >'C:/intelFPGA_lite/progetto_dsp/simulation/modelsim/rtl_work.progetto_dsp_top_vhd_t>st' has no architecture.
My model is a simple Phase Frequency Detector (PFD) and all I'd like is to simulate its behavior at different input signals.
Following I post my testbench code, the only thing that I changed from the auto-generated code is the addition of the A and B signals (which I made like clock signals with different frequency).
-- Generated on "05/02/2021 16:58:30"
-- Vhdl Test Bench template for design : progetto_dsp_top
--
-- Simulation tool : ModelSim-Altera (VHDL)
--
LIBRARY ieee;
USE ieee.std_logic_1164.all;
ENTITY progetto_dsp_top_vhd_tst IS
END progetto_dsp_top_vhd_tst;
ARCHITECTURE progetto_dsp_top_arch OF progetto_dsp_top_vhd_tst IS
-- constants
constant period_A : time := 10 ps;
constant period_B : time := 20 ps;
-- signals
SIGNAL A : STD_LOGIC := '0';
SIGNAL B : STD_LOGIC := '0';
SIGNAL High : STD_LOGIC : '1';
SIGNAL QA : STD_LOGIC;
SIGNAL QB : STD_LOGIC;
COMPONENT progetto_dsp_top
PORT (
A : IN STD_LOGIC;
B : IN STD_LOGIC;
High : IN STD_LOGIC;
QA : OUT STD_LOGIC;
QB : OUT STD_LOGIC
);
END COMPONENT;
BEGIN
--signal A generation
process
begin
A <= '1';
wait for period_A/2;
A <= '0';
wait for period_A/2;
if end_sim_s = true then
wait; -- end of simulation
end if;
end process;
signal B generation
process
begin
B <= '1';
wait for period_B/2;
B <= '0';
wait for period_B/2;
if end_sim_s = true then
wait; -- end of simulation
end if;
end process;
i1 : progetto_dsp_top
PORT MAP (
-- list connections between master ports and signals
A => A,
B => B,
High => High,
QA => QA,
QB => QB
);
init : PROCESS
-- variable declarations
BEGIN
-- code that executes only once
WAIT;
END PROCESS init;
always : PROCESS
-- optional sensitivity list
-- ( )
-- variable declarations
BEGIN
-- code executes for every event on sensitivity list
WAIT;
END PROCESS always;
END progetto_dsp_top_arch;

VHDL counter simulated using a test bench giving 'Uninitialized' for the output, how is this resolved?

Below is a counter that is designed to represent an 8 bit binary number with 8 LEDs, it is being simulated using a test bench, however when running the simulation the output simply shows UU for the led.
Here is the main entity that I wish to test:
use IEEE.STD_LOGIC_1164.all;
use IEEE.STD_LOGIC_unsigned.all;
entity Lab_3_Source_File is
generic(N_BITS : integer := 8);
port(
btnd : in STD_LOGIC ;
clk : in STD_LOGIC;
led : out STD_LOGIC_VECTOR(7 downto 0)
);
end Lab_3_Source_File;
architecture counter of Lab_3_Source_File is
signal count: STD_LOGIC_VECTOR(7 downto 0);
begin
process(clk, btnd)
begin
if btnd = '1' then
count <= (others => '0');
elsif rising_edge(clk) then
count <= count + 1;
end if;
end process;
led <= count;
end counter;
Here is the test bench that I have tried to map to the main entity:
use IEEE.STD_LOGIC_1164.ALL;
entity Count_TestBench is
end Count_TestBench;
architecture Behavioral of Count_TestBench is
signal btnd, clk : STD_LOGIC;
signal led : STD_LOGIC_VECTOR(7 downto 0);
begin
UUT : entity work.Lab_3_Source_File port map (btnd => btnd,clk => clk,led => led);
process
begin
btnd<='1';
wait for 1 ns;
btnd<='0';
led<= (others => '0');
for i in 1 to 100 loop
clk<='1';
wait for 10 ns;
clk<='0';
wait for 10 ns;
led<=led;
end loop;
end process;
end Behavioral;
Please could somebody help me understand how to enable the simulation to display the led output incrementing?
EDIT:
Set btnd to 1 with a 1ns wait in the test bench to initialise the led, following the answer from mkrieger1, the led output is still at U following this change.
count is not initialized inside Lab_3_Source_File until btnd is set to '1', which it isn't in the testbench.
Since the led output is driven by count, it is also uninitialized. The uninitialized value of the led output of Lab_3_Source_File is then assigned to the led signal in the testbench.
So, to fix this, you need to set btnd to '1' once for a non-zero duration in the testbench, before setting it to '0' again (otherwise led is held at "00000000" constantly).

Stop VHDL simulation with wait statements

When testing a simple counter implementation, the VHDL simulation is not exiting the simulation. My intention is to stop both concurrent processes using the shared variable changed by the main process. But the main process is not stopping the clock process.
My counter implementation is:
entity dff is
port(
direction, reset, clk, load : in std_logic;
din : in std_logic_vector(3 downto 0);
dout : out std_logic_vector(3 downto 0));
end dff;
architecture behav of dff is
signal temp : std_logic_vector(3 downto 0);
begin
process(clk, reset)
begin
if (reset='1') then
temp <= "0000";
elsif rising_edge(clk) then
if (load='1') then
temp <= din;
else
if (direction='0') then
temp <= std_logic_vector(unsigned(temp) + 1);
else
temp <= std_logic_vector(unsigned(temp) - 1);
end if;
end if;
end if;
dout <= temp;
end process;
end behav;
And my testbench:
architecture behav of test_tb is
component dff port(
direction, reset, clk, load : in std_logic;
din : in std_logic_vector(3 downto 0);
dout : out std_logic_vector(3 downto 0));
end component;
signal direction, reset, clk, load : std_logic := '1';
signal din, dout : std_logic_vector(3 downto 0) := x"7";
shared variable simend : boolean := false;
begin
clkk : process
begin
if simend=false then
clk <= not clk after 50 ns;
else
wait;
end if;
end process clkk;
uut : dff port map(
direction, reset, clk, load, din, dout);
stim : process
begin
reset <= '0';
wait for 1 us;
load <= '0';
wait for 2 us;
direction <= '0';
wait for 2 us;
load <= '1';
wait for 1 us;
reset <= '1';
wait for 0.5 us;
simend := true;
wait;
end process stim;
end behav;
An alternative way to end the simulation if you have a VHDL2008-compliant simulator is to:
use std.env.stop;
you can then end the simulation by calling stop:
stop;
This seems to me to be more elegant than waiting for a lack of clock transitions to cause the simulator iteration limit to be reached.
I would code your clock generator more like this:
clkk : process
begin
while simend=false loop
clk <= not clk;
wait for 50 ns;
end loop;
wait;
end process clkk;
It is possible to execute your clkk process without ever executing a wait statement. (The line clk <= not clk after 50 ns does not wait or block - <= is a non-blocking assignment.) Therefore, you have an infinite loop that will never stop. You can see this by running this example on EDA Playground where the simulation time never advances and, because the maximum runtime on EDA Playground is 1 minute, times out after 1 minute.
Also, I would recommend not using a shared variable for simend. Instead, why not use a signal? You code would not even be compiable in VHDL-2000 onwards, because after VHDL-2000, shared variables had to be protected types. You can see that a warning is produced on EDA Playground unless you set the option to compile VHDL-93. Compiling for VHDL-93, would prevent you using the stop (or finish) procedures.
I agree with #scary_jeff, std.env.stop is a great answer here. If I am just calling it in one place, my preference is to leave off the package reference and just call it:
std.env.stop;
In the event you are stuck with an older simulator, you can use
report "Just Kidding. Test Done." severity failure ;
OTOH if you need to coordinate ending a simulation between multiple processes and add a watch dog timer to your simulation run, you might consider the procedure Osvvm.TbUtilPkg.WaitForBarrier. It is used as shown below. The first call to WaitForBarrier(TestDone, 5 ms) will wake up in 5 ms in the event that TestDone does not happen before then and stop the simulation at that time.
signal TestDone : integer_barrier := 1 ;
ControlProc : process
begin
-- initialize test
SetAlertLogName("Uart1_Rx") ;
. . .
WaitForBarrier(TestDone, 5 ms) ; -- control process uses timeout
AlertIf(now >= 5 ms, "Test finished due to Time Out") ;
ReportAlerts ;
std.env.stop ;
end process ControlProc ;
CpuProc : process
begin
InitDut(. . . )_;
Toggle(CpuReady) ;
-- run numerous Cpu test transactions
. . .
WaitForBarrier(TestDone) ;
wait ;
end process CpuProc ;
UartTxProc : process
Begin
WaitForToggle(CpuReady) ;
-- run numerous Uart Transmit test transactions
. . .
WaitForBarrier(TestDone) ;
wait ;
end process UartTxProc ;
. . .
You can find the OSVVM library at both osvvm.org and on github. There is also a complete user guide in for this package in the download.

Read, then write RAM VHDL

in VHDL all the code lines are executed in a parallel way, since its a machine.
i want to create this RAM that reads a certain register from a ram block to the output and only 'afterwards' writes to the same register the input. my code goes like this:
architecture Behavioral of RAM is
type ram_t is array (0 to numOfRegs-1) of std_logic_vector (rLength-1 downto 0);
signal ram_s: ram_t;
signal loc : integer;
begin
process(clk)
begin
if(rising_edge(clk)) then
if(we='1') then
dataout <= ram_s(loc); -- reads the 'old' data to the output
ram_s(loc) <= datain; -- writes the 'new' data to the RAM
loc <= conv_integer(addr);
end if;
end if;
end process;
end Behavioral;
there is a similar case presented
here.
so I'd like to ask, is my code works fine or is there need for tweaking like putting a delay of half clock cycle, and if so, how to implement it.
I'm very new to VHDL thanks for your patience and help.
ive add a testbench simulation below . as can be seen the dataout isnt working at all.
Your question doesn't present a Minimal, Verifiable and Complete example, lacking the ability to replicate your results.
One of the consequences of this is that answers can be ambiguous should there be one or more causes of the problem in portions of your code not shown.
Brian's comment that you aren't reading data when we is invalid is poignant and would be responsible for 'U's in the clock cycle left of your yellow marker in your waveform.
There's also the issue with loc being a signal. Signals are scheduled for update, and no update occurs while any process that is scheduled to resume in the current simulation cycle has not been resumed and suspended.
This means the integer version of your address is delayed and won't be seen in the process until the next rising edge.
Fixing loc by making it a variable as an alternative to pipelining datain and moving the dataout assignment are accomplished in the following changes to your RAM process:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all; -- standard package
entity ram is
generic (
ADDRLENGTH: natural := 8;
RLENGTH: natural := 16;
NUMOFREGS: natural := 256
);
port (
clk: in std_logic;
we: in std_logic;
addr: in std_logic_vector (ADDRLENGTH - 1 downto 0);
datain: in std_logic_vector (RLENGTH - 1 downto 0);
dataout: out std_logic_vector (RLENGTH - 1 downto 0)
);
end entity;
architecture behavioral of ram is
type ram_t is array (0 to NUMOFREGS - 1) of
std_logic_vector (RLENGTH - 1 downto 0);
signal ram_s: ram_t;
-- signal loc: integer; -- USE VARIABLE in process instead
begin
process(clk)
variable loc: integer; -- MAKE loc variable so it's immediately available
begin
if rising_edge(clk) then
loc := to_integer(unsigned(addr)); -- MOVED so READ works
if we = '1' then
-- dataout <= ram_s(loc); -- reads the 'old' data to the output
ram_s(loc) <= datain; -- writes the 'new' data to the ram
-- loc <= conv_integer(addr);
end if;
dataout <= ram_s(loc); -- MOVED reads the 'old' data to the output
end if;
end process;
end architecture behavioral;
There's also the liberty of filling in the entity declaration and converting from conv_integer using Synopsys's package std_logic_arith to to_integer in the IEEE's numeric_std package. With a -2008 compliant tool chain you could instead use IEEE's package numeric_std_unsigned and do away with the type conversion to unsigned.
Because the ram_test testbench was also not supplied a testbench was written to replicate your waveform display image:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity ram_tb is
end entity;
architecture foo of ram_tb is
constant ADDRLENGTH: natural := 8;
constant RLENGTH: natural := 16;
constant NUMOFREGS: natural := 256;
signal clk: std_logic := '0';
signal we: std_logic := '1';
signal addr: std_logic_vector (ADDRLENGTH - 1 downto 0);
signal datain: std_logic_vector (RLENGTH - 1 downto 0);
signal dataout: std_logic_vector (RLENGTH - 1 downto 0);
begin
DUT:
entity work.ram
generic map (
ADDRLENGTH => ADDRLENGTH,
RLENGTH => RLENGTH,
NUMOFREGS => NUMOFREGS
)
port map (
clk => clk,
we => we,
addr => addr,
datain => datain,
dataout => dataout
);
CLOCK:
process
begin
if now = 500 ps then
wait for 200 ps;
else
wait for 100 ps;
end if;
clk <= not clk;
if now >= 1100 ps then
wait;
end if;
end process;
STIMULI:
process
begin
for i in 0 to 2 loop
addr <= std_logic_vector(to_unsigned (i, ADDRLENGTH));
case i is
when 0 =>
datain <= x"00FF";
when 1 =>
datain <= x"FF00";
when 2 =>
datain <= x"FFFF";
end case;
wait until falling_edge(clk);
if i = 1 then
we <= '0';
end if;
end loop;
for i in 1 to 2 loop
addr <= std_logic_vector(to_unsigned (i, ADDRLENGTH));
case i is
when 1 =>
datain <= x"FF00";
when 2 =>
datain <= x"FFFF";
end case;
wait until falling_edge(clk);
end loop;
wait;
end process;
end architecture;
And this produced:
Where the one written address that is subsequently read shows the correct data.
The simulator used does not present non-signals in a waveform dump (bounds in declarations are required to be static) and rst is not found in the portion of your design specification provided.
As noted previously there is no guarantee there isn't another issue with portions of your design specification or testbench not provided in your question.
The testbench shown is by no means comprehensive.

Ring Oscillator

I'm having some trouble implementing a ring oscillator. I don't care about it working on an FPGA. I only want to simulate using Xilinx ISE. Is the code below acceptable? I also addded the test bench. Thanks!
Code
library ieee;
use ieee.std_logic_1164.all;
-- 5 Ring Oscillator
entity ring_osc is
port (ro_en : in std_logic;
delay : in time;
ro_out : out std_logic);
end ring_osc;
architecture behavioral of ring_osc is
signal gate_out : std_logic_vector(5 downto 0) := (others => '0');
begin
process
begin
gate_out(0) <= ro_en and gate_out(5);
wait for delay;
gate_out(1) <= not(gate_out(0));
wait for delay;
gate_out(2) <= not(gate_out(1));
wait for delay;
gate_out(3) <= not(gate_out(2));
wait for delay;
gate_out(4) <= not(gate_out(3));
wait for delay;
gate_out(5) <= not(gate_out(4));
wait for delay;
ro_out <= gate_out(5);
end process;
end behavioral;
Test Bench
library ieee;
use ieee.std_logic_1164.all;
entity ring_osc_tb is
end ring_osc_tb;
architecture behavior of ring_osc_tb is
-- component declaration for the unit under test (uut)
component ring_osc
port (ro_en : in std_logic;
delay : in time;
ro_out : out std_logic);
end component;
-- Inputs
signal ro_en : std_logic := '0';
signal delay : time := 0.5 ns;
-- Outputs
signal ro_out : std_logic;
signal clk : std_logic := '0';
constant clk_period : time := 10 ns;
begin
-- instantiate the unit under test (uut)
uut: ring_osc port map (
ro_en => ro_en,
delay => delay,
ro_out => ro_out
);
-- clock process definitions
clk_process :process
begin
clk <= '0';
wait for clk_period/2;
clk <= '1';
wait for clk_period/2;
end process;
-- stimulus process
stim_proc: process
begin
ro_en <= '1';
delay <= 0.5 ns;
wait for 10*clk_period;
delay <= 1 ns;
wait for 5*clk_period;
assert false report "End of Simulation" severity failure;
end process;
end;
The process with sequential wait does not describe the concurrent nature of the gates in the Ring oscillator, since execution is suspended for delay time at each wait, which is not the way a real word design operates.
A description with concurrent evaluation of all the gates can be:
gate_out(0) <= ro_en and gate_out(5) after delay;
inv_g : for i in 1 to gate_out'high generate
gate_out(i) <= not gate_out(i - 1) after delay;
end generate;
ro_out <= gate_out(5);
This is for simulation only, as also noted in the question, due to the inherent loop nature of a ring oscillator.
Using test bench, with added disable at start:
-- Disable at start
ro_en <= '0';
delay <= 0.5 ns;
wait for 10 * 0.5 ns;
Then the resulting waveform is:

Resources