Introduction

In my first blog post about CXXRTL, I talked about how CXXRTL is just a Yosys backend, and how it can simulate anything that has been converted from some source input format (Verilog, blif, VHDL, SystemVerilog) to Yosys’ internal RTLIL format.

Most people think of Yosys as a tool to synthesize Verilog, and that’s definitely the dominant use case. But in the past years, significant progress has been made in integrating open source VHDL compiler gHDL into Yosys as well.

The result of this effort is ghdl-yosys-plugin. It’s not part of the main Yosys GitHub repo due to differing open source licenses, but a stand-alone Yosys plugin that has its own GitHub project.

I spent the first 10 years of my career writing VHDL (and, not knowing any better, I was a big fan of it), but after moving to the US West Coast, I’ve been a happy Verilog user. And I want to keep it that way! But I thought it’d be fun to convert theory into practise, and see how far gHDL and the gHDL Yosys plugin have progressed, and if it was possible to simulate a trivial VHDL design with Yosys and CXXRTL.

Taking things a step further, I also tried to run a Verilog/VHDL cosimulation, where one part of the design is written in Verilog, and another in VHDL.

The details are below, but the executive summary can be short: everything worked as it should.

Installing a Yosys + gHDL Combo

If you’ve already compiled Yosys in the past, installing gHDL and the plug-in is easy. On an Ubuntu 20.4 system, it took less than 20 min, and almost all of that was just compilation time.

Here are the steps:

  • Build and install the latest version of Yosys

    The Yosys project describes very well how to compile.

  • Build and install gHDL

    • Install gnat

      GNAT is the GNU ADA compiler. VHDL constructs are based on ADA language constructs, and a large part of gHDL is written in ADA.

      On my system, it was as simple as running sudo apt install gnat-9

    • Clone ghdl repo and compile

        git clone https://github.com/ghdl/ghdl.git
        cd ghdl
        ./configure --prefix=/opt/ghdl
        make install
      

      I install most experimental tools in the /opt directory. By default, everything gets installed in /usr/local/ instead.

    • Add the gHDL binary to your PATH

      I added the following line to ~/.profile:

        export PATH=/opt/ghdl/bin:$PATH
      
  • Build Install ghdl-yosys-plugin

      git clone https://github.com/ghdl/ghdl-yosys-plugin.git
      cd ghdl-yosys-plugin
      # GHDL needs to point to the executable, not the installation path!
      make GHDL=/opt/ghdl/bin/ghdl
      sudo make GHDL=/opt/ghdl/bin/ghdl install
    

    When the yosys binary is in your PATH, the last line above will copy the ghdl plugin to the /usr/local/share/yosys/plugins/ directory.

And that’s all there is to it!

Compiling and Simulating Your First VHDL Code with Yosys

The canonical way to process VHDL code has 2 major steps:

  • Analyze the VHDL code

    This parses the VHDL code, does a bunch of syntactic and semantic checks, and stores the analyzed design objects in a library. The default library is the “work” library.

  • Elaborate the analyzed design

    During this step, the various analyzed design objects are merged together, conditional code generates are executed, interconnections are verified etc.

Verilog has the same steps, but they’re usually not made explicity.

When using gHDL in combination with Yosys, the regular ghdl command (outside of Yosys) is used to analyze all the VHDL code into a standard gHDL library. And the ghdl command inside Yosys, which was added through the plugin, is used to elaborate the design and convert it to RTLIL.

Once available in RTLIL format, running a CXXRTL simulation is no different for VHDL than for a Verilog design.

I converted my trivial blink_basic CXXRTL example to VHDL. You can find it in the blink_basic_vhdl directory.

The design is just an LED connected to a bit of a counter:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity blink is
    port (
        clk     : in std_logic;
        led     : out std_logic
    );
end blink;

architecture RTL of blink is
    signal counter          : unsigned(11 downto 0) := "000000000000";
begin

    process(clk) begin
        if rising_edge(clk) then
            counter         <= counter + 1;
        end if;
    end process;

    led <= counter(7);

end RTL; 

Analyze the VHDL

ghdl analyze blink_basic.vhdl

If all goes well, ghdl won’t print anything on the screen, but you’ll notice that a work-obj93.cf file was created. That’s the work library with the analyzed design.

Create the CXXRTL simulation model with Yosys

Load the design into Yosys, elaborate and converted it RTLIL with the new ghdl command, and create a CXXRTL simulation file:

yosys -m ghdl "ghdl blink; write_cxxrtl blink.cpp

The -m ghdl part tells Yosys to load the ghdl plugin.

Compile the design and testbench into an executable

clang++ -g -O3 -std=c++14 -I `yosys-config --datdir`/include main.cpp  -o blink

Run the simulation

~/projects/cxxrtl_eval/blink_basic_vhdl$ ./blink 
cycle 128 - led: 1, counter: 129
cycle 256 - led: 0, counter: 257
cycle 384 - led: 1, counter: 385
cycle 512 - led: 0, counter: 513
cycle 640 - led: 1, counter: 641
cycle 768 - led: 0, counter: 769
cycle 896 - led: 1, counter: 897

Whenever I’m dealing with projects that require tying together different tools, I expect a lot of hassle with compilation and integration issues, but there’s was none of that here: everything just worked™.

Cosimulating a VHDL RISC-V CPU inside a Verilog SOC

If you already use gHDL to simulate your pure VHDL designs, there’s really no need to use CXXRTL unless you need on one of its unique capabilities.

But here’s something that no open source simulator has done before. Something that has been reserved for expensive proprietary simulators for decades: cosimulation of mixed Verilog/VHDL designs!

I stand corrected about the claim above: it’s a pretty complicated hack compared to the seamless VHDL/Verilog cosimulation that’s possible with CXXRTL, but Robert Ou cobbled a mixed Verilog VHDL simulation together with ghdl, Icarus Verilog, and some VPI glue.

To demonstate this, I started with my original RISC-V design that I used to benchmark CXXRTL and to demonstrate simulation save/restore checkpoints.

It’s originally written in SpinalHDL, but SpinalHDL generated a Verilog file that can be found here in my repo as well.

Here’s what I did:

  • Load the full original Verilog design (which includes a VexRiscv CPU) into Yosys
  • Remove the VexRiscv module from the design database
  • Compile a VHDL-based RISC-V CPU with gHDL
  • Write a Verilog wrapper that glues the new RISC-V CPU to the external interface of the VexRiscv
  • Simulate this Frankenstein design with CXXRTL

You can find everything in the rpu_vhdl directory of my cxxrtl_eval GitHub repo.

./run.sh is all you need to compile and simulate.

VectorBlox RISC-V CPU

VectorBlox was a startup that designed the open source ORCA RISC-V CPU. They were acquired by Microchip, but there are still some cloned GitHub repos out there with the source code. I tried hard to use this CPU because it has a similar pipeline and interfaces as a VexRiscv, but I’m pretty sure I ran into a pipelining bug in their load/store unit.

RPU RISC-V CPU

I then switched to Colin Riley’s (aka @domipheus) RPU. Much like the ubiquitous picorv32, it’s a very slow RISC-V implementation that only executes one instructions at a time instead of processing multiple instructions in different pipeline stages.

You can find the RPU repo here.

Instead of a split instruction and data bus, it only has a single memory bus for both, which is just fine for my usage.

Analyze the VHDL

This is just an extension of the earlier example.

Note that I’m using VHDL-2008 (the default is VHDL-93). This is not necessary for this design, but it was necessary to analyze the ORCA design. If you later run the ghdl command instead Yosys, make sure to specify the matching standard version as well.

RPU=./RPU/vhdl/

OPTIONS="--std=08 -fsynopsys"

ghdl -a $OPTIONS $RPU/constants.vhd $RPU/alu_int32_div.vhd \
                 $RPU/control_unit.vhd $RPU/csr_unit.vhd \
                 $RPU/lint_unit.vhd $RPU/mem_controller.vhd \
                 $RPU/pc_unit.vhd $RPU/register_set.vhd \
                 $RPU/unit_alu_RV32_I.vhd $RPU/unit_decoder_RV32I.vhd \
                 $RPU/core.vhd

VexRiscv_wrapper.v

The wrapper design is simple, with a minor twist.

The SOC design expects an instruction memory bus and a data memory bus. The RPU has only one bus, so I’m tying that interface to the data bus of the SOC, and strap the instruction bus to idle.

Most RISC-V CPUs handle byte to 32-bit word alignment inside the CPU core, but the RPU does not. So the following adaption logic was required in the wrapper:

  always @(*) begin
      MEM_I_data    = dBus_rsp_data;        // Default to avoid a latch

      case(MEM_O_byteEnable)
          2'b00: begin
              // Byte access
              MEM_I_data    = (dBus_rsp_data >> (MEM_O_addr[1:0] * 8)) & 32'hff;
          end
          2'b01: begin
              // HalfWord access
              MEM_I_data    = (dBus_rsp_data >> (MEM_O_addr[1] * 16)) & 32'hffff;
          end
          2'b10: begin
              MEM_I_data    = dBus_rsp_data;
          end
      endcase
  end

I didn’t wire up an interrupts, etc. since this is just a basic proof-of-concept example.

Processing in Yosys

Yosys ties everyting together:

# Load the Verilog design with the VexRiscv CPU
read_verilog ../spinal/ExampleTop.sim.v
# Remove the VexRiscv module from the design database
delete VexRiscv
# Load a replacement VexRiscv design that instantiates the RPU
read_verilog VexRiscv_wrapper.v
# Elaborate the RPU (named "core") to RTLIL
ghdl --std=08 core
# Bring together all design modules into 1 hierarchical design
hierarchy -check -top ExampleTop
# Create CXXRTL simulation model
write_cxxrtl -Og ExampleTop.sim.cpp

Compile testbench and design model to an executable

I’m using exactly the same testbench code as the one for my CXXRTL benchmark:

clang++-9 -g -O3 -I`yosys-config --datdir`/include  \
        -DEXAMPLE_TOP=\"ExampleTop.sim.cpp\" 
        -std=c++14 -I../lib main.cpp ../lib/cxxrtl_lib.cpp 
        -o tb

The result is a ./tb executable binary.

Simulate

> ./tb 2 waves.vcd

UART TX: 
UART TX: 

UART TX: H
UART TX: e
UART TX: l
UART TX: l
UART TX: o
UART TX:  
UART TX: W
UART TX: o
UART TX: r
UART TX: l
UART TX: d
UART TX: !
UART TX: 
UART TX: 

led_red: 1 0
led_red: 0 1
led_green: 1
led_green: 0
led_blue: 1
led_red: 1 1
led_blue: 0
led_red: 0 2
led_green: 1
led_green: 0

The RPU only executes about 1 instruction every 5 clock cycles whereas the VexRiscv needs only about 1.1 clock cycles per instructions, but other than that, everything runs just the same.

Waveforms

For this run, I dumped waveforms, so you can use gtkwave on this mixed Verilog/VHDL design.

In the screenshot below, you can see the memory bus of the RPU from the VHDL part of the design, and the APB bus of the UART which is written in Verilog:

Mixed Verilog/VHDL VCD Waveforms

Limitations

I didn’t run into major issues, but there are some things to be aware of.

  • VHDL assert statements

    gHDL converts VHDL assert statements into RTLIL $assert cells. These are used by Yosys for formal property checking, but CXXRTL doesn’t know what to do with it.

    The current work-around is to just comment out these statements (RPU doesn’t have them, ORCA does.)

    Ideally, the ghdl plugin should have an option to ignore assert statements, or CXXRTL needs to find a way to deal with them. (E.g. ignore them too, or even add simulation-time assertion checking!)

  • VHDL-specific simulation behavior is dropped

    VHDL has numerical types that abort a simulation when there is a range overflow.

    Since Yosys is ultimately a synthesis tool (in the case of CXXRTL, it synthesizes to C++), these kind of change are dropped. Don’t use gHDL and CXXRTL to verify that range constraints are met during simulation.

    A useful potential enhancement of ghdl plugin would be automatically insert range checking assertions for all limited range numerical types.

  • VHDL Records etc

    I didn’t try this myself, but the ghdl plugin flattens complex types into a single vector.

    This can make debugging waveforms very hard.

  • Case-sensitivity

    VHDL is case insensitive. Verilog is case sensitive. Yosys is case sensitive as well.

    This is something to keep in mind when you run into naming issues. (I didn’t.)

Conclusion

Mixed VHDL/Verilog can now be done with pure open source tools, and it works well!

References