Skip to content

Commit

Permalink
fix timestamps on verilator waveform (#616)
Browse files Browse the repository at this point in the history
* Add MAX_SIM_TIME parameter to simulation

Added parameter MAX_SIM_TIME to `make run-app-verilator`,
which sets the maximum simulation run length in clock cycles
via the `+max_sim_time=` command line argument.
This allows terminating the simulation prematurely in case something
goes wrong and the program stalls, and still get a VCD waveform.
This parameter has been added through a generic `SIM_ARGS` variable
so that further parameters can easily be added in the future.

Example: `make run-app-verilator MAX_SIM_TIME=1000000`

Additionally, modified tb_top.cpp so that setting a `+max_sim_time=`
argument still terminates the simulation on program termination,
rather than continuing to run until the time has elapsed
(after all, it's a *max* simulation time).

If the simulation terminates before the program finishes, it exits
with the special return code `2` to distinguish it from an error.

* Verilator sim: fix timing; add time suffixes

The waveform.vcd file Verilator generates does not dump the correct
timestamps, and just increases the simulation timer by 1 (ps) on every
clock edge, resulting in a clock period of 2ps (500GHz).
Fix that by defining a clock frequency in XHEEP_CmdLineOptions.hh
and incrementing by an amount based on that frequency.
The resulting waveform file has the correct timing.

Also, runCycles(n) was running for n clock edges (i.e. n/2 cycles)
rather than n cycles.  Fixed.

Additionally, add the possibility of adding suffixes to +max_sim_time
to specify time rather than clock cycles:  s, ms, us, ns, ps.
With no suffix, use clock cycles.

* Docs: Describe +firmware, +max_sim_time

Added "Simulation parameters" section to Simulate.md describing the
`+firmware` and `+max_sim_time` plusargs, and also mention the
`MAX_SIM_TIME` Makefile variable for Verilator simulation.

---------

Co-authored-by: Javier Mora <[email protected]>
  • Loading branch information
cousteaulecommandant and Javier Mora authored Jan 10, 2025
1 parent c0567bd commit d32a0cb
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 24 deletions.
13 changes: 10 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ SOURCE ?= $(".")
# Simulation engines options are verilator (default) and questasim
SIMULATOR ?= verilator

# SIM_ARGS: Additional simulation arguments for run-app-verilator based on input parameters:
# - MAX_SIM_TIME: Maximum simulation time in clock cycles (unlimited if not provided)
SIM_ARGS += $(if $(MAX_SIM_TIME),+max_sim_time=$(MAX_SIM_TIME))

# Timeout for simulation, default 120
TIMEOUT ?= 120

Expand Down Expand Up @@ -212,8 +216,9 @@ xcelium-sim:
run-helloworld: mcu-gen verilator-sim
$(MAKE) -C sw PROJECT=hello_world TARGET=$(TARGET) LINKER=$(LINKER) COMPILER=$(COMPILER) COMPILER_PREFIX=$(COMPILER_PREFIX) ARCH=$(ARCH);
cd ./build/openhwgroup.org_systems_core-v-mini-mcu_0/sim-verilator; \
./Vtestharness +firmware=../../../sw/build/main.hex; \
./Vtestharness +firmware=../../../sw/build/main.hex $(SIM_ARGS); \
cat uart0.log; \
echo '<end of uart0.log>'; \
cd ../../..;

## Generates the build output for freertos blinky application
Expand All @@ -222,16 +227,18 @@ run-helloworld: mcu-gen verilator-sim
run-blinkyfreertos: mcu-gen verilator-sim
$(MAKE) -C sw PROJECT=example_freertos_blinky TARGET=$(TARGET) LINKER=$(LINKER) COMPILER=$(COMPILER) COMPILER_PREFIX=$(COMPILER_PREFIX) ARCH=$(ARCH);
cd ./build/openhwgroup.org_systems_core-v-mini-mcu_0/sim-verilator; \
./Vtestharness +firmware=../../../sw/build/main.hex; \
./Vtestharness +firmware=../../../sw/build/main.hex $(SIM_ARGS); \
cat uart0.log; \
echo '<end of uart0.log>'; \
cd ../../..;

## First builds the app and then uses verilator to simulate the HW model and run the FW
## UART Dumping in uart0.log to show recollected results
run-app-verilator: app
cd ./build/openhwgroup.org_systems_core-v-mini-mcu_0/sim-verilator; \
./Vtestharness +firmware=../../../sw/build/main.hex; \
./Vtestharness +firmware=../../../sw/build/main.hex $(SIM_ARGS); \
cat uart0.log; \
echo '<end of uart0.log>'; \
cd ../../..;

## Simulate all the apps present in the repo
Expand Down
23 changes: 22 additions & 1 deletion docs/source/How_to/Simulate.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,27 @@ and type to run your compiled software:
make run PLUSARGS="c firmware=../../../sw/build/main.hex"
```

## Simulation parameters

You may pass additional simulation parameters to the generated simulation executable, in the form of *plusargs*: `+<parameter>=<value>`.

- `+firmware=<path>`:
Loads the hex file specified in `<path>` into memory.
This allows you to run a compiled executable directly, as if it were already written in memory since the beginning of the simulation.
For example, `./Vtestharness +firmware=../../../sw/build/main.hex` will launch the Verilator simulation and instruct it to load the compiled application executable into memory and run it.

- `+max_sim_time=<time>`:
Runs the simulation for a maximum of `<time>` clock cycles.
This is useful in case your application gets stuck in a certain point and never finishes; this parameter will force the simulation to terminate after a certain time so that you can later analyze the generated `waveform.vcd` file.
In that case, the simulation executable will exit with a return code of 2, indicating premature termination.
If this parameter is not provided, the simulation will run until the program finishes (the `main()` function ends).

Alternatively, you may add a time suffix (`s`, `ms`, `us`, `ns`, `ps`) to run the simulation until the specified simulation time has been reached (1 clock cycle = 10ns).
This is, `+max_sim_time=750us` is the same as `+max_sim_time=75000`.
(Note that there's no space between the number and the unit, and that fractional values are not supported.)

If you're launching the Verilator simulation via `make`, you may pass this parameter via the `MAX_SIM_TIME=` command line argument, e.g. `make run-helloworld MAX_SIM_TIME=750us`.

## UART DPI

To simulate the UART, we use the LowRISC OpenTitan [UART DPI](https://github.com/lowRISC/opentitan/tree/master/hw/dv/dpi/uartdpi).
Expand All @@ -157,4 +178,4 @@ For example, to see the "hello world!" output of the Verilator simulation:
cd ./build/openhwgroup.org_systems_core-v-mini-mcu_0/sim-verilator
./Vtestharness +firmware=../../../sw/build/main.hex
cat uart0.log
```
```
19 changes: 15 additions & 4 deletions tb/XHEEP_CmdLineOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,30 @@ std::string XHEEP_CmdLineOptions::get_firmware()
}


unsigned int XHEEP_CmdLineOptions::get_max_sim_time(bool& run_all)
unsigned long long XHEEP_CmdLineOptions::get_max_sim_time(bool& run_all)
{

std::string arg_max_sim_time = this->getCmdOption(this->argc, this->argv, "+max_sim_time=");
unsigned int max_sim_time;
unsigned long long max_sim_time;

max_sim_time = 0;
if(arg_max_sim_time.empty()){
std::cout<<"[TESTBENCH]: No Max time specified"<<std::endl;
run_all = true;
} else {
max_sim_time = stoi(arg_max_sim_time);
std::cout<<"[TESTBENCH]: Max Times is "<<max_sim_time<<std::endl;
size_t u;
max_sim_time = stoull(arg_max_sim_time, &u);
if(u == arg_max_sim_time.length()) max_sim_time *= CLK_PERIOD_ps; // no suffix: clock cycles
else if(arg_max_sim_time[u] == 'p') max_sim_time *= 1; // "p" or "ps" suffix: picoseconds
else if(arg_max_sim_time[u] == 'n') max_sim_time *= 1000; // "n" or "ns" suffix: nanoseconds
else if(arg_max_sim_time[u] == 'u') max_sim_time *= 1000000; // "u" or "us" suffix: microseconds
else if(arg_max_sim_time[u] == 'm') max_sim_time *= 1000000000; // "m" or "ms" suffix: milliseconds
else if(arg_max_sim_time[u] == 's') max_sim_time *= 1000000000000; // "s" suffix: seconds
else {
std::cout<<"[TESTBENCH]: ERROR: Unsupported suffix '"<<arg_max_sim_time.substr(u)<<"' for +max_sim_time"<<std::endl;
exit(EXIT_FAILURE);
}
std::cout<<"[TESTBENCH]: Max sim time is "<<(max_sim_time/CLK_PERIOD_ps)<<" clock cycles"<<std::endl;
}

return max_sim_time;
Expand Down
5 changes: 4 additions & 1 deletion tb/XHEEP_CmdLineOptions.hh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

#include <iostream>

#define CLK_FREQUENCY_kHz (100*1000)
#define CLK_PERIOD_ps (1000*1000*1000 / CLK_FREQUENCY_kHz)

class XHEEP_CmdLineOptions // declare Calculator class
{

Expand All @@ -12,7 +15,7 @@ class XHEEP_CmdLineOptions // declare Calculator class
std::string getCmdOption(int argc, char* argv[], const std::string& option); // get options from cmd lines
bool get_use_openocd();
std::string get_firmware();
unsigned int get_max_sim_time(bool& run_all);
unsigned long long get_max_sim_time(bool& run_all);
unsigned int get_boot_sel();
int argc;
char** argv;
Expand Down
7 changes: 3 additions & 4 deletions tb/tb_sc_top.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ sc_event obi_new_req;
#include "systemc_tb/MainMemory.h"


#define CLK_PERIOD 10

SC_MODULE(external_memory)
{
MemoryRequest *memory_request;
Expand Down Expand Up @@ -184,7 +182,8 @@ int sc_main (int argc, char * argv[])
{

std::string firmware;
unsigned int max_sim_time, boot_sel, exit_val;
unsigned long long max_sim_time;
unsigned int boot_sel, exit_val;
bool use_openocd;
bool run_all = false;
Verilated::commandArgs(argc, argv);
Expand Down Expand Up @@ -217,7 +216,7 @@ int sc_main (int argc, char * argv[])
}

// generate clock, twice the speed as we generate it by dividing it by 2
sc_clock clock_sig("clock", CLK_PERIOD>>1, SC_NS, 0.5);
sc_clock clock_sig("clock", CLK_PERIOD_ps/2, SC_PS, 0.5);

Vtestharness dut("TOP");
testbench tb("testbench");
Expand Down
29 changes: 18 additions & 11 deletions tb/tb_top.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,20 @@
vluint64_t sim_time = 0;

void runCycles(unsigned int ncycles, Vtestharness *dut, VerilatedFstC *m_trace){
for(unsigned int i = 0; i < ncycles; i++) {
for(unsigned int i = 0; i < 2*ncycles; i++) {
sim_time += CLK_PERIOD_ps/2;
dut->clk_i ^= 1;
dut->eval();
m_trace->dump(sim_time);
sim_time++;
}
}

int main (int argc, char * argv[])
{

std::string firmware;
unsigned int max_sim_time, boot_sel, exit_val;
vluint64_t max_sim_time;
unsigned int boot_sel, exit_val;
bool use_openocd;
bool run_all = false;

Expand Down Expand Up @@ -80,17 +81,16 @@ int main (int argc, char * argv[])

dut->eval();
m_trace->dump(sim_time);
sim_time++;

dut->rst_ni = 1;
//this creates the negedge
runCycles(50, dut, m_trace);
runCycles(20, dut, m_trace);
dut->rst_ni = 0;
runCycles(50, dut, m_trace);
runCycles(40, dut, m_trace);


dut->rst_ni = 1;
runCycles(20, dut, m_trace);
runCycles(40, dut, m_trace);
std::cout<<"Reset Released"<< std::endl;

//dont need to exit from boot loop if using OpenOCD or Boot from Flash
Expand All @@ -106,22 +106,29 @@ int main (int argc, char * argv[])
}

if(run_all==false) {
runCycles(max_sim_time, dut, m_trace);
while(dut->exit_valid_o!=1 && sim_time<max_sim_time) {
runCycles(100, dut, m_trace);
}
} else {
while(dut->exit_valid_o!=1) {
runCycles(500, dut, m_trace);
runCycles(100, dut, m_trace);
}
}

if(dut->exit_valid_o==1) {
std::cout<<"Program Finished with value "<<dut->exit_value_o<<std::endl;
exit_val = EXIT_SUCCESS;
} else exit_val = EXIT_FAILURE;
} else {
std::cout<<"Simulation was terminated before program finished"<<std::endl;
exit_val = 2; // exit 2 to indicate successful run but premature termination
}

std::cout<<"Simulation finished after "<<(sim_time/CLK_PERIOD_ps)<<" clock cycles"<<std::endl;

m_trace->close();
delete dut;
delete cmd_lines_options;

exit(exit_val);

}
}

0 comments on commit d32a0cb

Please sign in to comment.