Instantiate Verilog submodule - adumont/hrm-cpu GitHub Wiki
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!
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, 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 = ;
// ---------------------------------------- //
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.
- My scripts depends on HDLParse, you can install it with:
pip3 install --user hdlparse
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...