Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

When to use tasks, functions, and modules?

Status
Not open for further replies.

forkconfig

Member level 1
Member level 1
Joined
Jan 28, 2013
Messages
32
Helped
0
Reputation
0
Reaction score
0
Trophy points
1,286
Visit site
Activity points
1,655
I'm trying to wrap my head around when to use tasks, functions, and modules.
In programming languages, I know how to break things up into objects/functions/structs etc, but translating that into hardware is difficult.
I'd appreciate some theory to help me understand.

Also, I have a specific question about a project I'm working on.
I'm trying to make an elevator, just a project I set up for myself to learn, but I am having troubles deciding how to break a specific portion of my code up.
I would appreciate input...
So in the section of the code that is analyzing requests and placing those requests into the correct location within the queue, there is quite a bit of code. I have to address internal requests (meaning requests from inside the elevator) separately from external requests (perhaps from a lobby) and my code is getting messy. I know that modularizing my code is good for verification purposes and readability, however if I do modularize this portion of my code then I have to have interfaces from the queues to both modules. However, I feel like this is making my design worse because of all the added interfaces and thus noise and cross talk I have to worry about. I see 4 options:
1) Just use tasks to improve code readability and loose benefits of modular design
2) Modularize it, the added interfaces are okay (in this case please explain why so I can identify for myself next time)
3) Modularize it, but also put the Queue's in there own module with an arbiter (I don't like this because now I have an arbiter!)
4) Insert a flux capacitor into the system and let R2-D2 do the coding...I thought there was a fourth but I couldn't remember so I made this up :p

Thanks in advance everyone :)
 

If you are coming from a programming background, especially an Object-Oriented one like C++ or Java, think of modules as objects without inheritance. In fact both C++ and Verilog have common roots in an early simulation language called Simula, considered the first object-oriented language.




A Verilog module is a container of behaviors and processes that may represent concurrent activity in hardware. Do not think of it as modular procedural programming; that's what tasks and functions are for. A module represents a physical boundary for hardware design and that boundary is usually based on where there is repetition in the hardware, or a well defined interface by specification.

A task or function is used inside a module to describe the procedural code that represents a process. The key difference between the two is that a task may block and consume time, whereas a function must not block so it may be used as part of an expression.

If you are writing verification code, than typically all of that code goes into one module or one module per DUT interface, since the testbench is really a software program, not hardware. That where methodologies like the UVM can help you write testbenchs with the goal of having modular code. See https://verificationacademy.com/topics/verification-methodology
 

first, task/functions have been lightly used in the past for three main reasons:
1.) Not often useful for a practical solution. Basically, the constructs provide a description of the problem but do not prescribe an efficient hardware construction that can solve it. This limits useful functions. For example, even something as simple as division wouldn't be suitable as a function. The result would need a lot of adders which would result in a design that runs slow (as it must complete in one cycle).

2.) Synthesis tools have often had issues with implementing some functions, or at least implementing them well.

3.) For verilog, the inputs/outputs are fixed-size.


I suggest you try a few simple designs out that make use of tasks/functions to see exactly what is synthesizable and what isn't, as well as how well the design synthesizes. For example, it is possible certain optimization options will give dramatically better results. It is also very likely that some key construction in your task/function will simply throw an error and refuse to synthesize in the current version of your synthesis tool.

The "high-level synthesis" approach may also be of interest if you have access to those tools. In these design flows, you provide a high level algorithm which is similar to a C function with some constraints. You then spend your time instructing the tool on how you want the design to be implemented -- shared math units, independent math units, pipelines, ability to flush, etc...
 

So functions are best if you are re-using a section of code and you need that hardware to execute within 1 clk cycle and it is only returning 1 thing.
Is that right?
You mentioned functions may not block. But I thought you were ONLY allowed to use blocking statements in functions :???: ?
One more question on functions, like tasks they share the same name space as the module they are defined in, correct?

I still don't understand when to use a task or a module.

Also if someone doesn't mind helping me with that specific elevator question that would be great, I'm kinda stuck not sure how to proceed.
 

Think of modules like elements on a cicuit board. So if you think you need a new chip, then it needs to be a module.

Tasks and functions are really just there for code readability. They are often used in testbenches to do repetative, behvioural, actions (for example, a memory read or write cycle). But for synthesis you always need to be aware of the underlying logic, hence why tasks/functions are not always suitable.
 

TrickyDicky, thanks for the reply but that didn't answer my questions.

So functions are best if you are re-using a section of code and you need that hardware to execute within 1 clk cycle and it is only returning 1 thing.
Is that right?
You mentioned functions may not block. But I thought you were ONLY allowed to use blocking statements in functions ?
One more question on functions, like tasks they share the same name space as the module they are defined in, correct?

I still don't understand when to use a task or a module.

Also if someone doesn't mind helping me with that specific elevator question that would be great, I'm kinda stuck not sure how to proceed.
 

You mentioned functions may not block. But I thought you were ONLY allowed to use blocking statements in functions ?
Functions use blocking statements as they represent combinatorial logic, hence this is why they have to finish in 1 clock cycle. What was meant by blocking was that you can't include statements like wait in a function like you can in a task.
 

forkconfig,

You need to explain to us if the code you are writing is to be synthesized into hardware (implemented in an FPGA), or just for verification purposes to help verify yours or someone elses design. How you choose to use the various constructs are different for each purpose. The synthesis modeling guidelines will dictate where you use tasks/functions.

To answer some of your other questions, a blocking assignment statement does not always block, there has to be a blocking delay control inside the assignment.

A = @(posedge clk) B;
next_statement;

The blocking assignment statement above does:
  1. evaluates B
  2. blocks waiting for a posedge of clk
  3. updates A
  4. then executes the next_statement;
A = B;
next_statement;

However, the blocking assignment statement above never blocks and is fine to use in a function
  1. evaluates B
  2. updates A
  3. then executes the next_statement;
A <= @(posedge clk) B;
next_statement;

The non-blocking assignment above never blocks, regardless of whether there is a delay control in the statement or not.
  1. evaluates B
  2. schedules an update to A in the future
  3. then executes the next_statement;
If you are using SystemVerilog, you can put a blocking assignment with no delay control, or a non-blocking assignment inside a function. Verilog does not allow non-blocking assignments in a function.
Yes,tasks and functions share the same namespace where they are defined.
 

Note my code is really rough right now and is messy (my first verilog project).
But my question was referring to the logic that is in charge of handling requests and putting those requests into the queues.
There are two parts to that, requests form inside and requests from outside.
The question is these two parts, do I modularize them, make them tasks, or leave them inside.
My concern with modularizing was the added interfaces in my hardware to access all the queues (as explained in my original question).

I would appreciate any guidance and suggestions, relating to my question and just general hardware and verilog design theory.

Thanks so much.

(by the way I have a few other modules but I didn't include them all)


module elevator (
clk,
en,
rst,
floor_req_flag,
floor_req,
external_req_flag,
external_req,
current_floor,
direction,
elevator_id,
//FIXEME: UPDATE
);

//------------------------------
// INPUT / OUTPUT
//------------------------------
input clk;
input en;
input rst;

input floor_req_flag;
input [7:0] floor_req;
input external_req_flag;
input [21:0] external_req;

output [7:0] current_floor;
output direction;

output elevator_id;

//------------------------------
// WIRE / REG
//------------------------------
wire clk;
reg en;
reg rst;

reg floor_req_flag;
reg [7:0] floor_queue [255:0]; // 0'b00000000 floor is reset state
reg [7:0] external_floor_req;
reg external_req_flag;
reg [21:0] external_req;

reg [7:0] current_floor;
reg direction; // 1 -> up .... 0 -> down

reg [7:0] elevator_id;

reg floor_in_queue;
reg [7:0] tailOfStack;
reg moving_toward_floor;
reg [7:0] distance_to_floor;
integer i, j;
integer timer;


//------------------------------
// INSTANCES
//------------------------------
//FIXME: Include modules

//------------------------------
// TASKS
//------------------------------
task reset ();
begin
en <= 1'b0;

door_open <= 1'b0;
current_floor <= 8'b00000001; // How do I make this dynamic such that if i have 50 floors and 10 elevators I stagger them every 5 floors
direction <= 1'b1;

floor_req_flag <= 1'b0;

elevator_id <= 8'b00000000; // FIXME: Impliment comm prot for initializing if 0

floor_in_queue <= 1'b0;

tailOfStack <= 8'b00000000;
timer <= 32'hFFFF;
moving_toward_floor <= 1'b0;
distance_to_floor <= 8'b00000000;

for (i = 0; i < 255; i = i+1) begin
floor_queue <= 8'b00000000;
end
end

// New Floor Request - From Inside Elevator
task in_elevator_floor_req ();
begin
// Check if floor already in queue
floor_in_queue <= 1'b0; // Reset Signal

for (j = 0; j <= tailOfStack; j = j+1) begin
if (floor_queue[j] == floor_req) begin
floor_in_queue <= 1'b1;
end
end

// if not already in queue, place floor request in correct position within queue
if (floor_in_queue == 1'b0) begin
if (direction == 1'b1) begin
for (j = 0; j <= tailOfStack; j = j+1) begin
if ( (floor_req > current_floor) && (floor_req < floor_queue[j]) ) begin
floor_queue <= { floor_queue[254:j], floor_req, floor_queue[j:0] };
end
else if ( (floor_req < current_floor) && (floor_req > floor_queue[j]) ) begin
floor_queue <= { floor_queue[254:j], floor_req, floor_queue[j:0] };
end
end
end
else if (direction == 0'b1) begin
for (j = 0; j <= tailOfStack; j = j+1) begin
if ( (floor_req < current_floor) && (floor_req > floor_queue[j]) ) begin
floor_queue <= { floor_queue[254:j], floor_req, floor_queue[j:0] };
end
else if ( (floor_req > current_floor) && (floor_req < floor_queue[j]) ) begin
floor_queue <= { floor_queue[254:j], floor_req, floor_queue[j:0] };
end
end
end
end

tailOfStack = tailOfStack + 1;
floor_req_flag <= 0;
end

//------------------------------
// RTL CODE
//------------------------------
// Reset Logic
always @(posedge clk and posedge rst)
begin
reset();
end

// Control Elevator Movement
always @(posedge clk)
begin
if (en == 1'b1 ) begin
// Queue is Empty - Go to default floor
if ( tailOfStack == 8'b00000000) begin
current_floor <= 8'b00000001; // FIXME: Go to default floor
// goToFloor (current_floor, 1'b0);
end
// Queue is NOT Empty - Go to floor at top of stack
else begin
// move (current_floor);
if ( direction == 1'b1 ) begin
current_floor = current_floor + 1;
end
else begin
current_floor = current_floor - 1;
end
end
end // if (en == 1)
end


// Maintain variables after arrival
always @(posedge clk and posedge arrival_flag)
begin
req_flag <= 0;
current_floor <= new_floor;

if (tailOfStack > 0) begin
floor_queue <= { 8'b00000000, floor_queue[255:1] };
tailOfStack <= tailOfStack - 1;

// Manage Direction in which Elevator Moves
if (floor_queue[0] > current_floor) begin
direction <= 1'b1;
end
else begin
direction <= 1'b0;
end
end
end



// New Floor Request - From Inside Elevator
always @(posedge clk and posedge floor_req_flag)
begin
in_elevator_floor_req ();
end


// New Floor Request - From Outside Elevator
always @(posedge clk and posedge external_req_flag)
begin
/*
Read floor request...
Are you moving toward the floor (1 -> yes , 0 -> no) // Note: if all no, then put in queue to service later...early assignment may cause longer wait
How far from the floor are you (consider even if you have to go all the way up and then back down based on current queue)
How many things are already in your queue

Incoming packet will look like this:
7 3 12
|eid|request type|data|

For now we will just use floor request...
7 3 7
|eid|001|floor requested|xxxxx|

Outgoing packet will look like this:
7 1 7 7 = Total: 22 bits
|eid|moving toward floor|distance to floor|items in queue|
*/

// Check if broadcast request
if (external_req[21:15] == 8'b1) begin
// If elevator does not have id, request one ...
if ( elevator_id == 8'b0 ) begin
// FIXME: put code here ...
end
else begin
// Future maintenance commands, such as configuring firmware ...
end
end

// If direct communication, see if it is addressed to me ...
else if (external_req[21:15] == elevator_id) begin
// What kind of request?
// Floor Request
if (external_req[14:12] == 3'b001) begin
external_floor_req <= external_req[11:5];

// Check if queue is empty
if (floor_queue[0] == 8'b00000000) begin
floor_queue[0] <= external_floor_req;
// FIXME: Don't execute rest of code ...
end

// Check if moving towards floor
if ( ((direction == 1) && (current floor < external_floor_req)) || ((direction == 0) && (current floor > external_floor_req)) ) begin
moving_toward_floor <= 1'b1;
end else begin
moving_toward_floor <= 1'b0;
end

// Check distance to floor
if (moving_toward_floor == 1'b1) begin
if (direction == 1'b1) begin
distance_to_floor <= external_floor_req - current_floor;
end else begin
distance_to_floor <= current_floor - external_floor_req;
end
else
if (direction == 1'b1) begin
i = 0;
while (floor_queue[i+1] != 8'b00000000) begin
if ( floor_queue[i+1] > floor_queue) begin
i = i + 1;
end
end
distance_to_floor <= external_floor_req - current_floor;
end else begin
distance_to_floor <= current_floor - external_floor_req;
end





// If you are already going there send a signal ...
for (j = 0; j <= tailOfStack; j = j+1) begin
if (floor_queue[j] == external_floor_req) begin
// send_message() -----> if bus busy, wait...once you gain access to bus write packet
external_req <= {elevator_id, moving_toward_floor,

|eid|moving toward floor|distance to floor|items in queue|

end
end



end // No elses needed for now
end





// if not already in queue, place floor request in correct position within queue
if (floor_in_queue == 1'b0) begin
if (direction == 1'b1) begin
for (j = 0; j <= tailOfStack; j = j+1) begin
if ( (floor_req > current_floor) && (floor_req < floor_queue[j]) ) begin
floor_queue <= { floor_queue[254:j], floor_req, floor_queue[j:0] };
end
else if ( (floor_req < current_floor) && (floor_req > floor_queue[j]) ) begin
floor_queue <= { floor_queue[254:j], floor_req, floor_queue[j:0] };
end
end
end
else if (direction == 0'b1) begin
for (j = 0; j <= tailOfStack; j = j+1) begin
if ( (floor_req < current_floor) && (floor_req > floor_queue[j]) ) begin
floor_queue <= { floor_queue[254:j], floor_req, floor_queue[j:0] };
end
else if ( (floor_req > current_floor) && (floor_req < floor_queue[j]) ) begin
floor_queue <= { floor_queue[254:j], floor_req, floor_queue[j:0] };
end
end
end
end

tailOfStack = tailOfStack + 1;
floor_req_flag <= 0;
end
 

You have been asking about modules.

I presume, dividing the code into functionally structured modules can improve the clarity and readability a lot.
 

I had a very quick look over your code, and I think you are seriously overthinking the problem and are approaching it from a software view. It is is far more complex than it needs to be. As well, I don't think it is synthesizeable as you have not considered what the underlying hardware has to be.

First of all, sending packets and queuing requests is overkill. An elevator is an interrupt driven system. The buttons on each floor could be considered latched interrupts - most floors have an up and down interrupt except the top and bottom floors. And each button press is a simple one bit signal. For example, the elevator wil sit idle (lets say at the ground floor) until it receives an interrupt from the floor elevator buttons. It will begin to travel up to that floor. As it moves up, it will poll its list of floor interrupts to see if a button has been pushed on another floor. If it has, it will check if it is an up or down interrupt. If it is a down interrput, it may bypass the floor entirely or choose to stop, open the door and flash the up light. If it is an up interrupt it will stop and let someone board, and clear the interrupt on that floor. That person will push an floor button inside, which is also a latched interrupt. The elevator knows it must stop at that floor sometime. Either on its way up or way down. The elevator will continue to climb until it runs out of up interrupts (from the floors or the elevator buttons) , then go back down, evaluating whether it must stop at each floor on the way down. It will continue to decend until it runs out of interrupts. Then repeat. There may be other scenarios that require some logic from you. But the long and short of it is you just need a bunch of interrupt inputs and interrupt clear signals to operate on - no packets and no queues.

You have written many for loops that are implementing queing and sorting algorithms. If this is meant to be a behavioural model that is never synthesized, you are probably OK. If you intend to implement it in an FPGA (as other posts imply) then how do you see these for loops being implemented? What type of hardware are you using? For loops are not commonly used in synthesizeable code. And the synthesizer doesn't magically replace the for loops with RAMs or register arrays and add all the read and write strobes and address lines. You have to add them in your module. A sort algorithm on a RAM means sending the address and read signal and a clock to the RAM, storing the data appearing on the RAM's data out bus in another register, having a state machine step to the next address to read and compare the newly read data with your stored data, and then writing one or two pieces of data back to the RAM. You will have to generate the read and write signals for these actions too. Register arrays are a bit simpler but you still will have to describe those steps.

When you write procedural software, you can just write a for loop and the operating system takes care of translating that into machine code in the processor on which the OS resides, and that machine code will initiate memory reads and writes. But you are writing with a Hardware Description Language, not a procedural language, so every line of code you write must describe some hardware. Your sorting algorithms don't describe anything like that.

So if I were you, I would step back and draw out the hardware you will need, then write the code that describes that. I would also simplify it, and create a module that had a bunch of interrupts as the inputs, and outputs which are used to clear these interrupts. Then write a state machine that starts from idle and moves in the direction of the first interrupt it receives, but checking the interrupts associated with each floor as it goes, clearing the ones it has serviced. It will move up until it runs out off interrupts. Then it will move down and stop at each floor with a down request, etc. Repeat as necessary. There may be more complicated situations but you get the idea. I would dump the packet requests, queuing and sorting, at least until you have a better understanding of writing synthesizeable code.

My 2 cents.

r.b.
 
Last edited:
Status
Not open for further replies.

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top