Instantiate Verilog submodule - adumont/hrm-cpu GitHub Wiki

Instantiate Verilog submodule

I've always thought it was a pain to instantiate a submodule within another verilog module: you have to indicate every single port name from the submodule, indicate which wire you will connect it to... And what about the naming of all those wires? Do you name them from the point of view of the outter module or from the point of view of the submodules, or you pick whatever existing wire you already have defined in the outter module?

I personally couldn't find any systematic method regarding how to handle this in a mechanical way. So I came up with my own way!

The principles behind it

For every module M we will instantiate, we will declare new wires for all ports of M. The name of these wires will follow these rules:

  • Input/Output wires will have their names prefixed with i_/o_,
  • Followed by the name of the instance of the module,
  • Followed by the port name

For example, let's consider a module which declaration would look like this:

module ALU (
        input  wire        [2:0] aluCtl,
        input  wire signed [7:0] inR,
        input  wire signed [7:0] inM,
        output reg  signed [7:0] aluOut,
        output reg               flag
    );
    [...]
endmodule

We'd like to instantiate it once in a new top module. Let's call alu0 this new instance of ALU.

First, we'll start by declaring new wires:

wire        [2:0] i_alu0_aluCtl;
wire signed [7:0] i_alu0_inR;
wire signed [7:0] i_alu0_inM;
wire signed [7:0] o_alu0_aluOut;
wire              o_alu0_flag;

Then, we instantiate the module:

ALU alu0 (
   //---- input ports ----
   .aluCtl(i_alu0_aluCtl),
   .inR   (i_alu0_inR   ),
   .inM   (i_alu0_inM   ),
   //---- output ports ----
   .aluOut(o_alu0_aluOut),
   .flag  (o_alu0_flag  )
);

Finally, we just have to assign an existing wire to all of the input ports of our instance: we can either using an input port of the outter module or an output port of another submodule.

// Connect Inputs:
assign i_alu0_aluCtl = cu_aluCtl ;
assign i_alu0_inR    = R_value ;
assign i_alu0_inM    = mem_M ;

I have put this method in practice (manually) when connecting all the submodules in my HRM CPU microprocessor design. And frankly it helped me a lot to follow such a systematic method. Also it makes the design easier much to follow and understand, and I feel it minimizes the probability to make errors!

Why do it manually? -- Just don't !

Why do it manually, when you can automate it, right?... After finding a (working) Verilog parser, I wrote a simple python script that will prepare the instantiation sentences from a module source (verilog file), ready to be copy-pasted into our outter module!

See the example:

$ ./vhelper.py -f ALU.v -n alu0

// ---------------------------------------- //
// alu0 (ALU)
//

wire        [2:0] i_alu0_aluCtl;
wire signed [7:0] i_alu0_inR;
wire signed [7:0] i_alu0_inM;
wire signed [7:0] o_alu0_aluOut;
wire              o_alu0_flag;

ALU alu0 (
   //---- input ports ----
   .aluCtl(i_alu0_aluCtl),
   .inR   (i_alu0_inR   ),
   .inM   (i_alu0_inM   ),
   //---- output ports ----
   .aluOut(o_alu0_aluOut),
   .flag  (o_alu0_flag  )
);
// Connect Inputs:
assign i_alu0_aluCtl =  ;
assign i_alu0_inR    =  ;
assign i_alu0_inM    =  ;
// ---------------------------------------- //

Parameters

My vhelper scripts also supports parameters being passed to the submodule. For example, this module have two parameters and:

// ---------------------------------------- //
// rom (PROG)
//

wire [7:0] i_rom_Addr;
wire       i_rom_clk;
wire       o_rom_Data;

PROG rom (
   //---- input ports ----
   .Addr(i_rom_Addr),
   .clk (i_rom_clk ),
   //---- output ports ----
   .Data(o_rom_Data)
);
// Define Parameters:
// defparam rom.PROGRAM = ;
// defparam rom.SIZE = ;
// Connect Inputs:
assign i_rom_Addr =  ;
assign i_rom_clk  =  ;
// ---------------------------------------- //

Notice the two lines that were added before the input port connexions to allow us to optionally specify a value to each parameter:

// defparam rom.PROGRAM = ;
// defparam rom.SIZE = ;

Uncomment these lines as necesarry.

Notes

  • My scripts depends on HDLParse, you can install it with: pip3 install --user hdlparse

Help with instantiation as code blocks in IceStudio

When importing a verilog module as a new code block in IceStudio, you need to manually specify the input/output ports (with corresponding width and range) and parameters. Also, if you module use any output reg declaration, you'll need to manually add new reg <portname> declarations at the top of the code block.

When you have more than a couple ports, it can become a real pain if done manually. My vhelper.py script helps with that (script is here).

Example of an FSM module with 8 input ports, and 15 output ports. We invoque this functionality of vhelper.py with the -i flag (i for IceStudio):

$ ./vhelper.py -f ControlUnit.v -i

PARAMETERS:
  

INPUT PORTS:

  INSTR[7:0], inEmpty, outFull, debug, nxtInstr, busy, clk, i_rst

OUTPUT PORTS:

  wIR, muxR[1:0], wR, srcA, wM, wAR, aluCtl[2:0], wPC, rIn, wO, ijump, branch, rst, halt, enT

Add to beginning of code block:

reg wIR;
reg [1:0] muxR;
reg wR;
reg srcA;
reg wM;
reg wAR;
reg [2:0] aluCtl;
reg wPC;
reg rIn;
reg wO;
reg ijump;
reg branch;
reg rst;
reg halt;
reg enT;

Notice how it prepares the parameters, input and output port list, ready to copy/paste them in the IceStudio module ports dialog. No typing necesarry, no time lost, no errors!

I'm thinking it could also help with the creation of the input/output pins...

⚠️ **GitHub.com Fallback** ⚠️