Introduction to OpenMPW

Questions, suggested revisions, or other communications can be sent to @Cra2yPierr0t

It’s been a while since I read the introductory article I wrote before, but the writing was messy, the content was old, and it was done like the end of the world, so I’m writing this in preparation for 90nm.

Want to make your own LSI? Let’s make it. Skywater opened its PDK to the public, and OpenLANE, an OSS GDSII compiler, was born. And Efabless, funded by Google, has started the Open MPW Shuttle Program, a program that lets you build LSIs for free. Now is your chance to create your own LSI. Build the chip you want to build, on Google’s dime!

OpenMPW Overview

OpenMPW (Open Multi Project Wafer) is a shuttle program created by Google’s investment in Efabless, and its home page includes the following statement.

The shuttle provides opportunities for designers to experiment and push the state-of-the-art without having to reconcile the risk associated with the cost of fabrication. The shuttle program is open to anyone, provided that their project is fully open source and meets the other program requirements. Costs for fabrication, packaging, evaluation boards and shipping are covered by Google for this program.

Source: https://efabless.com/open_shuttle_program

So it’s a program where you can make your own semiconductors completely free of charge as long as you makes your design open source. This is hot, you have to join, Google is the best! I will follow you for the rest of my life.

That said, not all submitted designs will be made into LSI, and several conditions exist.

I know you want to make your own HDL into LSI right now, but there is a bit much that needs to be done, so let’s do it little by little.

Next, we will go into an overview of each tool, which you may skip if you already know and have installed it.

OpenLANE Overview

OpenLANE is an OSS RTL-to-GDSII compiler, which is made by combining about 20 OSS.

When this GDSII is sent to a fab, it will build an LSI.

OpenLANE provides a Docker container, and it is recommended to install OpenLANE using Docker. It is possible to install OpenLANE without using Docker container, but you should stay away from it because it says “It is more complex” or something like that.

environment building

Build an environment to install OpenLANE; supports Linux/M1 Mac/Intel Mac/Win.

Minimum requirements are here

The following commands are targeted at archlinux, but can be used on any OS with pip, virtualenv and docker.

Installing pip and virtualenv

sudo pacman -S python-virtualenv python-pip

Installing docker

sudo pacman -S docker

Add yourself to the docker group. This is very bad for security. I tried using podman and other ways to get around this, but in the end it was impossible in my environment, so I gave up. If you can work around this, please let me know.

sudo usermod -a -G docker $(whoami)

Restart once.

Start the Docker daemon

systemctl start docker

Installing OpenLANE

Next, install OpenLANE, but install caravel first because it is more convenient to install via caravel, which is described later.

Download caravel first

git clone https://github.com/efabless/caravel_user_project.git

Then create a dependencies directory, a directory to install OpenLANE and PDK, and set environment variables.

cd caravel_user_project
mkdir dependencies
export OPENLANE_ROOT=$(pwd)/dependencies/openlane_src
export PDK_ROOT=$(pwd)/dependencies/pdks

The PDK contains blueprints for OR circuits, AND circuits, FF, and so on.

Start installation with make setup.

make setup

The environment variables OPENLANE_ROOT and PDK_ROOT are important; if they are not set, neither OpenLANE nor Caravel will work.

Checking the operation of OpenLANE

You can enter the OpenLANE directory and use make to check the operation.

cd $OPENLANE_ROOT
make test

If “Basic test passed” is displayed, the installation has been completed without any problems.

If you want to build a design under design/ that is already prepared, you can make mount and then use . /flow.tcl -design <directory name>.

The following is a command to build APU as a trial.

cd $OPENLANE_ROOT
make mount
./flow.tcl -design APU

The GDSII generated when the design was built directly using OpenLANE is OpenLANE/designs/APU/runs/run_<date>/results/final/gds and the layout can be viewed using klayout.

klayout APU.gds

This image hides the decap cells to give a sense of the circuit being generated and is slightly different from what is actually seen.

Configuration of OpenLANE

When using OpenLANE, it is necessary to write a configuration file for OpenLANE according to the design as well as writing HDL.

Let’s look at the configuration file for the default design. The default designs are located under OpenLANE/designs/ which includes usb, picorv32a, etc. config.tcl in those directories is the OpenLANE configuration file.

The configuration file can be set either in the tcl file config.tcl or in the json file config.json.

A basic description of how to write a configuration file can be found here, but will be explained later.

https://openlane.readthedocs.io/en/latest/docs/source/hardening_macros.html

A list of available variables can be found here, and the essential variables will be explained later.

https://openlane.readthedocs.io/en/latest/configuration/README.html

Caravel Overview

Caravel, which has been mentioned many times before, is an OpenMPW semiconductor design template. Caravel is a template for OpenMPW semiconductor design, which allows you to build, precheck, and testbench your design with a single template.

Caravel is divided into Management Area and User Project Area. The Management Area is an area that cannot be changed and consists of a lightweight RISC-V core and various IOs. The User Project Area is an area that can be freely modified, where developers place their designs.

The size of the Management Area is 2.920um x 3.520um, which is roughly 10mm.

There are 38 GPIO lines extending out from the User Project Area, from which you can directly access your design. The Management Area is connected to the RISC-V core via a 32-bit wishbone interface and 128-bit Logic Analyzer lines, allowing programs running on the RISC-V core to access the RISC-V core via MMIO.

Installing Caravel

If you have already installed Caravel by installing OpenLANE above, you do not need to do this step. It is better not to do it.

However, if you want to delete OpenLANE, pdk, Caravel, and everything else and install the OSS you need with OpenMPW again, this is a quick way to do it.

Download Caravel

git clone https://github.com/efabless/caravel_user_project.git

Create a dependencies directory, a directory to install OpenLANE and PDK, and set environment variables.

cd caravel_user_project
mkdir dependencies
export OPENLANE_ROOT=$(pwd)/dependencies/openlane_src
export PDK_ROOT=$(pwd)/dependencies/pdks

Start installation.

make setup

Checking that Caravel is working

You can go under caravel_user_project and build the design provided as an example using make.

make user_proj_example

GDSII files are generated under gds/.

Caravel File Structure

To use Caravel, it is sufficient to know how to use the following four directories.

A description of all Caravel directories can be found here.

https://caravel-harness.readthedocs.io/en/latest/getting-started.html#required-directory-structure

In Caravel, the design build is divided into two stages. The first stage is the build of your Verilog, and the second stage is the build of the wrapper. In OpenMPW, the wrapper is submitted.

The wrapper here is a design that contains one or more of your designs and is named user_project_wrapper.

The built user_project_wrapper usually looks like the image below, with the design inserted into the wrapper.

This image also hides the decap cells to give the feeling that a circuit is being generated.

The developer edits two OpenLANE configuration files, one for own design and one for user_project_wrapper. The latter one is only slightly edited, so don’t worry about it.

Since user_project_wrapper contains user_proj_example immediately after installation of Caravel, building user_proj_wrapper after building user_project_wrapper will result in GDSII will be generated as shown in the image above.

make user_proj_example
make user_project_wrapper

klayout gds/user_project_wrapper.gds

Caravel Documentation

The Caravel documentation can be found here and will be explained in detail later.

https://caravel-harness.readthedocs.io/en/latest/index.html#

Make your design LSI with OpenMPW!

From here, I will explain how to build your own design in earnest and submit it to OpenMPW. Note that we will not explain how to write Verilog, so please prepare your own design.

Here is what you need. The above three have already been done in the previous chapter.

1. prepare the design you want to bake

First, let’s prepare your design. We will use Ethernet MAC as an example.

https://github.com/Cra2yPierr0t/Vthernet-SoC/tree/main/verilog/rtl/Vthernet_MAC

I made it for you.

2. create an interface for Caravel in your own design.

If you connect your design to Caravel’s MGMT Core, you will be able to set registers and send and receive data. Also, if you connect your design to an external pin, you can directly send and receive data to and from the outside.

This section describes what you need to do to make your design compatible with Caravel.

There are two ways to connect to the Management Core: Wishbone and Logic Analyzer. There is one way to connect to the outside world, named mprj_io.

To summarize.

Signal Name Bus Width Direction
Wishbone 32bit user_design <-> Management Core
Logic Analyzer 128bit user_design <-> Management Core
mprj_io 38bit user_design <-> Outside

Difference between Wishbone and Logic Analyzer

I mentioned that there are two ways to communicate between your design and MGMT Core: Wishbone (32 bits wide) and Logic Analyzer (128 bits wide). So, here is a quick overview of the two.

Wishbone is a communication protocol, a friend of AXI4 and AMBA, which allows read and write operations by manipulating 10 types of signal lines according to the protocol. In Caravel, addresses from 0x30000000 to 0x80000000-1 are reserved for the Wishbone and can be used freely within this range.

Next is Logic Analyzer, which is just a signal line name. The bus width is 128 bits, four times wider than the Wishbone, and there are no separate data lines or address lines, only three types of signal lines. From MGMT Core, 128 bits are divided into four parts, 0x250000000, 0x250000004, 0x250000008, and 0x25000000c, which are visible as four registers.

Logic Analyzer is easier to implement in HDL, but only four addresses can be allocated. Wishbone, on the other hand, is a bit more complicated than Logic Analyzer in terms of simplicity, but it can handle a wider range of addresses. Well, which one is better suited for which application, so this section describes how to use both.

Creating a Wishbone interface

This section explains how to create a mechanism to read data from and write data to registers in Wishbone. By creating this interface, the RISC-V processor will be able to send and receive data to and from your design via MMIO.

The signal lines of Wishbone are as follows.

Signal Name Direction Function
wb_clk_i MGMT Core -> user_design Clock input from the master. The value is read at the timing of this rising edge.
wb_rst_i MGMT Core -> user_design Reset signal.
wbs_stb_i MGMT Core -> user_design Strobe input. Indicates that a slave is selected.
wbs_cyc_i MGMT Core -> user_design Cycle Input. Indicates that a valid bus cycle is in progress.
wbs_we_i MGMT Core -> user_design Write enable input. 1 for Write, 0 for Read.
wbs_sel_i[3:0] MGMT Core -> user_design Selector input. Indicates which bytes of wbs_dat_i[31:0] are valid. 8-bit granularity.
wbs_dat_i[31:0] MGMT Core -> user_design Data input.
wbs_adr_i[31:0] MGMT Core -> user_design Address input.
wbs_ack_o MGMT Core <- user_design Acknowledge output. 1 indicates the end of a normal bus cycle.
wbs_dat_o[31:0] MGMT Core <- user_design Data output.

There are 10 signal lines, which may scare those who have not dealt with communication protocols such as AXI4 and AMBA very much, but it is very simple to handle. First, the waveforms of READ and WRITE are shown below. This was borrowed from the Wishbone specification.

Waveform of READ READ is an operation that sends data to the MGMT Core (MGMT Core is the Master) and takes two cycles to complete.

Waveform of WRITE WRITE is an operation to receive data from the MGMT Core and is completed in two cycles as well. The protocol is explained below.

First of all, there are three signal lines, adr_o, dat_i, and dat_o, which you should know as a prerequisite. Each adr_o is an address line, dat_i is data to MGMT Core (for READ), and dat_o is data from MGMT Core (for WRITE).

The first two to look at in the first cycle are stb_o and cyc_o. If these two are present, it means that you have been selected and communication has been initiated. Next, if we_o is standing, it means that WRITE is performed in that cycle, and if not, READ is performed. If WRITE, adr_o and dat_o are valid values, and the value of dat_o should be placed at the destination specified by adr_o. If READ, adr_o is the address of the desired data.

The first thing to look at in the second cycle is dat_i and ack_i. The dat_i is used only in the case of READ, and the data specified by adr_o is input here. The ack_i is used for both READ and WRITE, and indicates the completion of communication by standing up in this cycle.

I’ve explained it in detail, but you can see it by looking at the waveforms. Also, wbs_sel_i is byte-enabled.

The specs say all sorts of things about pipelined transactions and such, but Caravel probably doesn’t do consecutive transactions, and the transactions are supposed to be spaced apart from each other. At least that was the case in the simulation.

The following is a Wishbone interface created by the author for reference in implementation. (License free!)

module wb_interface #(
    parameter TEST_CSR0 = 32'h3000_0000,
    parameter TEST_CSR1 = 32'h3000_0004,
    parameter TEST_CSR2 = 32'h3000_0008
)(
    // wishbone signals
    input   wire        wb_clk_i,
    input   wire        wb_rst_i,
    input   wire        wbs_stb_i,
    input   wire        wbs_cyc_i,
    input   wire        wbs_we_i,
    input   wire [3:0]  wbs_sel_i,
    input   wire [31:0] wbs_dat_i,
    input   wire [31:0] wbs_adr_i,
    output  reg         wbs_ack_o,
    output  reg  [31:0] wbs_dat_o,
    // CSRs
    output  reg  [31:0] test_csr0,
    output  reg  [31:0] test_csr1,
    output  reg  [31:0] test_csr2
);

    localparam WB_IDLE  = 2'b00;
    localparam WB_READ  = 2'b01;
    localparam WB_WRITE = 2'b10;

    reg [1:0] wb_state;

    always @(posedge wb_clk_i) begin
        if(wb_rst_i) begin
            wb_state    <= WB_IDLE;
            wbs_ack_o   <= 1'b0;
        end else begin
            case(wb_state)
                WB_IDLE : begin
                    wbs_ack_o   <= 1'b0;
                    if(wbs_stb_i && wbs_cyc_i) begin
                        if(wbs_we_i) begin
                            wb_state <= WB_WRITE;
                        end else begin
                            wb_state <= WB_READ;
                        end
                    end
                end
                WB_READ : begin
                    wb_state    <= WB_IDLE;
                    wbs_ack_o   <= 1'b1;
                    case(wbs_adr_i)
                        TEST_CSR0 : begin
                            wbs_dat_o[7:0]   <= wbs_sel_i[0] ? test_csr0[7:0] : 8'h00;
                            wbs_dat_o[15:8]  <= wbs_sel_i[1] ? test_csr0[15:0] : 8'h00;
                            wbs_dat_o[23:16] <= wbs_sel_i[2] ? test_csr0[23:16] : 8'h00;
                            wbs_dat_o[31:24] <= wbs_sel_i[3] ? test_csr0[31:24] : 8'h00;
                        end
                        TEST_CSR1 : begin
                            wbs_dat_o[7:0]   <= wbs_sel_i[0] ? test_csr1[7:0] : 8'h00;
                            wbs_dat_o[15:8]  <= wbs_sel_i[1] ? test_csr1[15:0] : 8'h00;
                            wbs_dat_o[23:16] <= wbs_sel_i[2] ? test_csr1[23:16] : 8'h00;
                            wbs_dat_o[31:24] <= wbs_sel_i[3] ? test_csr1[31:24] : 8'h00;
                        end
                        TEST_CSR2 : begin
                            wbs_dat_o[7:0]   <= wbs_sel_i[0] ? test_csr2[7:0] : 8'h00;
                            wbs_dat_o[15:8]  <= wbs_sel_i[1] ? test_csr2[15:0] : 8'h00;
                            wbs_dat_o[23:16] <= wbs_sel_i[2] ? test_csr2[23:16] : 8'h00;
                            wbs_dat_o[31:24] <= wbs_sel_i[3] ? test_csr2[31:24] : 8'h00;
                        end
                        default   : begin
                            wbs_dat_o <= 32'h0000_0000;
                        end
                    endcase
                end
                WB_WRITE : begin
                    wb_state    <= WB_IDLE;
                    wbs_ack_o   <= 1'b1;
                    case(wbs_adr_i)
                        TEST_CSR0 : begin
                            test_csr0[7:0]   <= wbs_sel_i[0] ? wbs_dat_i[7:0] : 8'h00;
                            test_csr0[15:8]  <= wbs_sel_i[1] ? wbs_dat_i[15:8] : 8'h00;
                            test_csr0[23:16] <= wbs_sel_i[2] ? wbs_dat_i[23:16] : 8'h00;
                            test_csr0[31:24] <= wbs_sel_i[3] ? wbs_dat_i[31:24] : 8'h00;
                        end
                        TEST_CSR1 : begin
                            test_csr1[7:0]   <= wbs_sel_i[0] ? wbs_dat_i[7:0] : 8'h00;
                            test_csr1[15:8]  <= wbs_sel_i[1] ? wbs_dat_i[15:8] : 8'h00;
                            test_csr1[23:16] <= wbs_sel_i[2] ? wbs_dat_i[23:16] : 8'h00;
                            test_csr1[31:24] <= wbs_sel_i[3] ? wbs_dat_i[31:24] : 8'h00;
                        end
                        TEST_CSR2 : begin
                            test_csr2[7:0]   <= wbs_sel_i[0] ? wbs_dat_i[7:0] : 8'h00;
                            test_csr2[15:8]  <= wbs_sel_i[1] ? wbs_dat_i[15:8] : 8'h00;
                            test_csr2[23:16] <= wbs_sel_i[2] ? wbs_dat_i[23:16] : 8'h00;
                            test_csr2[31:24] <= wbs_sel_i[3] ? wbs_dat_i[31:24] : 8'h00;
                        end
                    endcase
                end
            endcase
        end
    end

endmodule

It will probably work.

Handling Logic Analyzer

Logic Analyzer can receive and send 128-bit data. The following three types of signal lines are used in Logic Analyzer.

Signal Name Direction Function
la_data_in MGMT Core -> user_design Data input
la_data_out MGMT Core <- user_diesgn Data output
la_oenb MGMT Core -> user_deisgn data output enable bar

la_data_in is the data input, where data from the Logic Analyzer comes through. The la_data_out is the data output, and data to the Logic Analyzer is sent here. Of note is la_oenb. This is a signal that indicates whether la_data_in or la_data_out is enabled, and is input from the MGMT Core. In other words, the MGMT Core determines which line of the 128-bit Logic Analyzer is for input and which line is for output through the MMIO.

Since la_data_oenb is a data output enable bar, the output (la_data_out) is enabled when 0 and the input (la_data_in) when 1. Easy to remember as 0utput and 1nput.

The following is an example of using Logic Analyzer for reference in implementation.

assign la_data_out[31:0] = test_signals;
assign w_super_data = (&la_oenb[63:32]) ? la_data_in[63:32] : 32'h0000_0000;

It is rather easy to handle. Incidentally, the & in the second line is a reduction operator.

Handling mprj_io

By using mprj_io, you can directly exchange 38-bit signals between your design and the outside world. The following three types of signal lines are handled by mprj_io.

Signal Name Direction Function
io_in MGMT Core -> user_design Data input
io_out MGMT Core <- user_diesgn Data output
io_oenb MGMT Core <- user_deisgn data output enable bar

It is very similar to Logic Analyzer, but note the io_oenb. The direction of the signal is different from Logic Analyzer, and it is an output from your design. Therefore, you can decide which line to use for output and which line to use for input in your design.

Since io_oenb is a data output enable bar, output (io_out) is enabled when it is 0 and input (io_in) when it is 1. It is easy to remember them as 0utput and 1nput.

You need to configure various settings on the firmware side, but in your design, you only need to manipulate the value of io_oenb.

The following is an example of using mprj_io, and I hope it will be helpful for your implementation.

assign io_enb[31:0]  = 32'hffff_ffff;
assign io_enb[32]    = 1'b1;
assign io_enb[34:33] = 2'b00;
assign data_in = io_in[31:0];
assign hard_reset = io_in[32];
assign io_out[34:33] = led[1:0];

And so on. It is probably appropriate to use assign or always for input to io_out.

How to handle interrupts

Caravel can apply interrupts to MGMT Core from its own design. For interrupts, a 3-bit wide signal line of user_irq[2:0] is used. An interrupt is generated when any one of these 3 bits stands. When an interrupt occurs, the program counter of MGMT Core is set to 0x00000000.

Therefore, the interrupt handler should be placed at 0x00000000 in memory.

You need to set the CSR of MGMT Core in firmware to enable interrupts, but in your own design, you only need to set user_irq when you want to interrupt.

This video is very helpful; Interrupt!

https://www.youtube.com/watch?v=pPgnVBguNW8

3. convert your own design to GDSII

Once your design is ready for Caravel, convert it to GDSII. This operation is called “Hardening”. Also, the hardened design is called a macro.

Writing a configuration file

First, make sure that the Verilog file of your design is under verilog/rtl. If not, bring it.

Next, create a directory under openlane to store the configuration files for OpenLANE. The name of this directory should be the same as the name of the top level module.

mkdir openlane/<user design top module>

The configuration file is placed in the directory created above, but it is very hard to write from scratch. Therefore, copy and edit the configuration file for user_proj_example, which is provided as an example.

Copying the configuration file

cd openlane/<user design top module>
cp ../user_proj_example/config.tcl .

From here, I will explain the parts of the config.tcl that you copied and should edit.

First of all, tcl itself is sufficient for OpenLANE if you remember the following syntax.

set ::env(VAR_NAME) value

This sets the value value to a variable named VAR_NAME. Hereafter, “variable” refers to this VAR_NAME and similar ones.

The first two variables to change in config.tcl are DESIGN_NAME and VERILOG_FILES. The DESIGN_NAME is literally the name of the design, and should be the same name as the top-level module. VERILOG_FILES is the path to the Verilog file to be synthesized.

An example of how to write it is shown below. Multiple Verilog files can be specified in VERILOG_FILES.

set ::env(DESIGN_NAME) user_design_top_module

set ::env(VERILOG_FILES) "\
    $::env(CARAVEL_ROOT)/verilog/rtl/defines.v \
    $script_dir/../../verilog/rtl/user_design_top_module.v \
    $script_dir/../../verilog/rtl/user_design_sub_module.v"

There is a mysterious variable $script_dir, which is the location of the config.tcl you are currently reading. It is very likely that $script_dir will disappear and be changed to $DESIGN_DIR in the future.

The next three variables to change are CLOCK_PORT, CLOCK_NET, and CLOCK_PERIOD, which are related to the clock. The CLOCK_PORT specifies the port used for the clock input of the design, and the CLOCK_NET I honestly don’t know. The documentation says that CLOCK_PORT is for STA and CLOCK_NET is for CTS, but I think it’s probably OK to set the same value.

Set the clock period in nanoseconds in CLOCK_PERIOD. If you set 10, the clock period is 100 MHz.

An example is shown below. I would like someone to tell me about CLOCK_NET.

set ::env(CLOCK_PORT) "wb_clk_i"
set ::env(CLOCK_NET) $::env(CLOCK_PORT)
set ::env(CLOCK_PERIOD) "10"

The next thing to change is DIE_AREA. This sets the size of the macro in micrometers. The order of the settings is x0 y0 x1 y1. If you set "0 0 200 100", the size will be a rectangle of 200umx100um. The coordinates are the same as in the first quadrant.

You can use any combination of coordinates at first, and seek the best combination with PL_TARGET_DENSITY described later.

An example is shown below.

set ::env(DIE_AREA) "0 0 900 600"

The next change is in FP_PIN_ORDER_CFG, which sets the direction from which pins will be placed in the macro. However, since OpenLANE will do it by itself without setting it, please remove it by commenting it out or deleting the whole line.

The commented out example is shown below.

#set ::env(FP_PIN_ORDER_CFG) $script_dir/pin_order.cfg

If you need it, please set it by yourself.

The last thing to change is PL_TARGET_DENSITY. This determines how densely the area of the DIE_AREA setting will be placed. The value can be a real number between 0 and 1. If you set an extreme value between 0 and 1, OpenLANE will tell you the best value, so you can use that as a reference.

An example is shown below.

set ::env(PL_TARGET_DENSITY) 0.2

The following table summarizes the variables that should be changed.

Variable name Function Remarks
DESIGN_NAME specifies a top-level module  
VERILOG_FILES Specify the Verilog files  
CLOCK_PORT Specify the clock port  
CLOCK_NET Mystery Mystery
CLOCK_PERIOD Specify the clock period in nanoseconds
DIE_AREA Specify the size of the macro in micrometers
x0 y0 x1 y1
FP_PIN_ORDER_CFG Specify the direction of the pin delete
PL_TARGET_DENSITY Specify the placement density 0~1

Generate GDSII

Once the configuration file is complete, the next step is to generate GDSII. The build is started by running make <design_name> under caravel_user_project/.

make <user design top module>

The generated GDSII exists in openlane/<user design top module>/runs/run_<date>/results/final/gds and the layout can be viewed using klayout.

klayout <user design top module>.gds

Cute.

4. add your own design to user_project_wrapper

Simply put your own module in verilog/rtl/user_project_wrapper.v instead of user_proj_example. Note that it is not recommended to set up logic such as AND and NOT in user_project_wrapper, as it may cause unexpected errors.

5. check the operation by simulation

Once you have connected your design to Caravel, the next step is to check its operation in simulation.

In order to run the simulation, it is necessary to write a few settings and a test bench for the firmware to be run on the MGMT Core and what signals to input to io_in, etc. I will explain in turn.

Installing the simulation environment

First, install the environment for simulation with this command.

make simenv

Specify the file to use

Just as you set the path to the Verilog files for your design in OpenLANE to config.tcl, in simulation, you need to tell the simulation environment which files to use.

There are three configuration files that need to be edited.

First, regarding includes.rtl.caravel_user_project, add the path to the Verilog files you need for your design, including the user_project_wrapper.

An example is given below.

-v $(USER_PROJECT_VERILOG)/rtl/user_project_wrapper.v	     
-v $(USER_PROJECT_VERILOG)/rtl/user_design_top_module.v
-v $(USER_PROJECT_VERILOG)/rtl/user_design_sub_module.v

Next, for includes.gl.caravel_user_project, add the path to the Verilog files generated in verilog/gl/. The files in verilog/gl/ are automatically generated and are a collection of multiple Verilog files.

An example is shown below.

-v $(USER_PROJECT_VERILOG)/gl/user_project_wrapper.v
-v $(USER_PROJECT_VERILOG)/gl/user_design_top_module.v

Finally, for includes.gl+sdf.caravel_user_project, add the path to the file under verilog/gl/ as well as includes.gl.caravel_user_project.

An example is shown below.

$USER_PROJECT_VERILOG/gl/user_project_wrapper.v	     
$USER_PROJECT_VERILOG/gl/user_design_top_module.v

The setup is now complete.

Writing the firmware

The firmware can be written in C. Caravel will pull a Docker container containing the RISCV toolchain, so you can run the make command to compile the firmware or whatever.

First, create a directory for the simulation in verilog/dv.

mkdir verilog/dv/<sim_name>

Store the firmware and test bench in this directory.

The name of the firmware should be <sim_name>.c. Hereafter, we will explain how to write the firmware.

First, include two files.

#include <defs.h>
#include <stub.c>

Various global variables and constants are defined in this defs.h.

https://github.com/efabless/caravel/blob/main/verilog/dv/caravel/defs.h

The rest of the program is written in the main function, but you may define functions and split files as in ordinary C language.

The configuration part of CSR should be written as follows.

reg_mprj_io_0   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_1   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_2   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_3   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_4   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_5   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_6   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_7   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_33  = GPIO_MODE_USER_STD_OUTPUT;
reg_mprj_io_34  = GPIO_MODE_USER_STD_OUTPUT;

reg_mprj_xfer = 1;
while (reg_mprj_xfer == 1);

In the above example, mprj_io[7:0] is set as input and mprj_io[34:33] as output. The part of reg_mprj_xfer, while after that is the part where the settings are applied, and when the settings are applied, it exits from the while statement. The mechanism here is unknown.

GPIO_MODE_USER_STD_INPUT_PULLDOWN is a constant that sets mprj_io to the input of pulldown, and GPIO_MODE_USER_STD_OUTPUT to the output.

Other constants such as GPIO_MODE_MGMT_STD_OUTPUT and GPIO_MODE_MGMT_STD_INPUT_PULLDOWN can be set, in which case signals can be sent and received between the MGMT Core and GPIO.

Basically, you can write the setting before while(reg_mprj_xfer == 1) and the processing after that.

Write a test bench

Since it is very troublesome to write a testbench from scratch, an existing testbench is copied and pasted and used. As an example, we use io_ports/io_ports_tb.v as a starting point.

Copy Test Bench

cp ./dv/io_ports/io_ports_tb.v ./dv/<sim_name>/<sim_name>_tb.v

In the following, we will make the necessary configuration changes.

First, change the name of the test bench.

module <sim_name>_tb;

Below that is a spooky area surrounded by ifdef ENABLE_SDF, but there is no need to edit this area.

Next, change the target and name of the waveform file.

initial begin
    $dumpfile("<sim_name>.vcd");
    $dumpvars(0, <sim_name>_tb);
    ...

Next, rename the file to be loaded into spiflash at the bottom of the file.

spiflash #(
    .FILENAME("<sim_name>.hex")
) spiflash (
...

That’s all for the setup. After this is done, write your own signals in the initial statement starting at line 162. https://github.com/efabless/caravel_user_project/blob/main/verilog/dv/io_ports/io_ports_tb.v#L162

An example is given below. Don’t worry about the ifdef GL in the original file. Just remember $finish;.

initial begin
    mprj_io[7:0] <= 8'h55;
    # 100
    mprj_io[7:0] <= 8'haa;
    # 100
    wait(mprj_io[34:33] == 2'b11);
    $finish;
end

Copy the Makefile

Copy the Makefile, no need to edit the contents.

cp ./dv/io_ports/Makefile ./dv/<sim_name>/

Run a simulation

Now you are ready to run the simulation. Run the following command in caravel_user_project/ to start the simulation.

make verify-<sim_name>-rtl

Good luck with the debugging.

6. Configure OpenLANE

First, edit config.tcl in user_project_wrapper/.

The variables to be changed in config.tcl are as follows.

Variable name Function Remarks
CLOCK_NET mystery may be the same as CLOCK_PORT
CLOCK_PERIOD Specify the clock period in nanoseconds
FP_PDN_MACRO_HOOKS explicit power connection to the macro  
VERILOG_FILES same as CLOCK_PORT  
VERILOG_FILES_BLACKBOX Verilog file specification for the macro  
EXTRA_LEFS_LEFS Specify the LEF file to be used  
EXTRA_GDS_FILES Specify GDS file to be used  

Change CLOCK_NET

The default is mprj.clk, so change it to the same as CLOCK_PORT or other appropriate one.

set ::env(CLOCK_NET) $::env(CLOCK_PORT)

Change CLOCK_PERIOD

Set this to your preferred operating clock period.

Change FP_PDN_MACRO_HOCKS

Change FP_PDN_MACRO_HOCKS to your own macro name, since the default is mprj, which is the power supply connection to the macro.

set ::env(FP_PDN_MACRO_HOOKS) "\
	<user design top module> vccd1 vssd1 vccd1 vssd1"

Change VERILOG_FILES_BLACKBOX

Change to the path of the top module of your design. Do not make any changes to the define.v path.

set ::env(VERILOG_FILES_BLACKBOX) "\
	$::env(CARAVEL_ROOT)/verilog/rtl/defines.v \
	$script_dir/../../verilog/rtl/<user design top module>.v"

Change EXTRA_LEFS

Set EXTRA_LEFS to the path of the LEF file of your design, which is automatically generated in caravel_user_project/lef/.

set ::env(EXTRA_LEFS) "\
	$script_dir/../../lef/<user design top module>.lef"

Change EXTRA_GDS_FILES

Set EXTRA_GDS_FILES to the path of the GDSII files of your design that are automatically generated in caravel_user_project/gds/.

set ::env(EXTRA_GDS_FILES) "\
	$script_dir/../../gds/<user design top module>.gds"

Editing macro.cfg

After completing the configuration of config.tcl, edit macro.cfg at the end.

The default is to specify the location of the instance named mprj, but change this to the specification of the location of the instance of your macro. The format is instance_name X_pos Y_pos Orientation.

An example is shown below.

<user design instance> 100 100 N

Multiple instance locations can be specified in this macro.cfg.

After completing the above settings, finally build the wrapper.

7. Generate the layout

Execute the following command in caravel_user_project/ to start the wrapper build.

make user_project_wrapper

When this succeeds, user_project_wrapper.gds will have been generated in gds/.

8. run precheck

Install Docker image for precheck

make precheck

run precheck.

make run-precheck

Make sure there are no problems with the precheck.

Congratulations!!! Your design is now complete! Now all you have to do is follow the instructions in Efabless to register your repository. Let’s hope you win the lottery!

Submit your repository here.

https://efabless.com/open_shuttle_program

P.S.

If you know of any recommended books, papers or documents on computer architecture, HPC, FPGA, digital circuit design, etc., please let me know and I will weep for joy.