I want to simulate a module that has a simple Addr/Data interface. I want to create a separate module that will act as a transactor for this module. So for example, I can simply call a task in the transactor module that will perform a write of an addr and data. e.g.
Transactor.Write_Task(0, 123); //Writes 123 to Addr 0.
My question is, should I do an include to include the transactor module at the top of the testbench? Or is it a better approach to compile the transactor separately and instantiate it and wire it up, as if it was another unit under test? e.g:
\lib_Local_Bus.Local_Bus_Transactor Transactor
(
.i_LB_Clk(w_LB_Clk),
.o_LB_CS(w_LB_CS),
.o_LB_Wr_Rd_n(w_LB_Wr_Rd_n),
.o_LB_Addr8(w_LB_Addr8),
.o_LB_Wr_Data(w_LB_Wr_Data),
.i_LB_Rd_Data(w_LB_Rd_Data),
.i_LB_Rd_DV(w_LB_Rd_DV)
);
I feel like it's easier to simply include it with an include, but then I need to point to it correctly, which could be a problem. By precompiling it, instantiating it like a UUT, and wiring it up, there's more code, but it is more straightforward as to what is going on.
The classic approach is to treat it as any other module, meaning it lives as its own file and you instantiate the transactor and the DUT together in your top level test bench. This is how most vanilla Verilog environments I have worked on are coded.
The module can either present a queue-based interface to queue transactions into, or a task-based interface which can then be called from your test, e.g.
initial
begin : test_block
Transactor.Send(data1);
->stimulus_complete;
end
Related
There are brief definitions of Queue and other Standard Library Interfaces of Chisel (Decoupled, Valid, etc) in the Cheat-Sheet and a bit more detail in the Chisel Manual. I also found these two answers here at StackOverflow - here and here.
However, neither of these resources explains in the plastic way - and I feel that would help me better understand the purpose of these Interfaces - what do these lines of code synthesize to - what do they look like in actual hardware?
For example, here is a snippet of the FPU code from the package HardFloat:
val input = Decoupled(new DivRecFN_io(expWidth, sigWidth)).flip
where DivRecFN_io is a class as follows:
class DivRecFN_io(expWidth: Int, sigWidth: Int) extends Bundle {
val a = ...
val b = ...
val ...
...
}
What exactly is achieved with the line containing Decouple?
Thank you.
For what it looks like in actual hardware:
The default Chisel util Queue is a standard circular buffer implementation. This means it has a series of registers with an enqueue and dequeue pointer, that move as a result of operations on the queue, checked for fullness and emptiness.
Decoupled wires a DivRecFN Bundle to field named bits and adds ready and valid signals that are typically used to manage flow control for Modules that do not return results within a single cycle. By default DecoupledIO's data fields would be Output. The flip at the end of the line would convert that to Input. Considering a module C which contains the val input and a module P that uses an instance of Module(C), The module C would be consuming the data in the Bundle, the parent of this module P would be producing the data placed in the Bundle. C would assert ready to indicate it is ready for data, and would read/use that data when valid is asserted by P.
The fields in the decoupled Bundle would be
input.ready
input.valid
input.bits.a
input.bits.b
...
I have a hierarchy of modules where I am trying to do a force to get different value at different module interface. I am working on a component whose task is to inject transaction to a module down the hierarchy, bypassing the drives from the modules higher up in the hierarchy. I thought I could use force on the control signals in order to disengage drives from higher up modules and start driving into the module of interest. So I have been trying to see how force will work. The full code is at http://www.edaplayground.com/x/69PB.
In particular, I am trying to understand effect of these two statements within initial block:
force u_DataReceiveTop.u_DataReceiveWrap.DataReceiveIfWrp_inst.valid = 1'b0;
force u_DataReceiveTop.valid = 1'b1;
what I expected the values to be is:
u_DataReceiveTop.u_DataReceiveWrap.DataReceiveIfWrp_inst.valid == 0
u_DataReceiveTop.valid == 1
but I see from waves:
u_DataReceiveTop.u_DataReceiveWrap.DataReceiveIfWrp_inst.valid == 1
u_DataReceiveTop.valid == 1
It is as if the second force statement force u_DataReceiveTop.valid = 1'b1; has propagated down the hierarchy even though there is another force. What is happening here?
A wire in Verilog is a network of drivers and receivers all connected to the same signal. The value of that signal is some resolution function of all the drivers and the type of the wire. When you connect two wires through a port, the two wires get collapsed into a single signal, but you still have two different names for the same signal.
When you use the force statement on a wire, that overrides all the drivers on the network until encountering another force or release statement. In your example, the second force statement replaces the first force. I doesn't matter which hierarchical reference you use in the force because they all refer to the same signal.
If you want the behavior you are expecting, you need to use variables instead of wires. When you connect a variable to a port, SystemVerilog creates an implicit continuous assignment, depending on the direction of the port. SystemVerilog does not allow more than one continuous assignment to a variable, which is why you can't use variables with an inout port. So you will need to be more careful about the port directions then.
I wanted to have a parameterized FIFO instantiation so that I can call a single FIFO instance with change in depth(parameter).
e.g. I have written a code for FIFO with depth as a parameter.
I will know the depth of the FIFO only by the configuration from Microprocessor. Based on the register configuration, can i call this FIFO with variable parameter like value?
integer depth_param;
if(config_reg[1])
depth_param <= 128;
else
depth_param <= 512;
genfifo #(depth_param) (.din (din),.wr(wr)....);
fifo module is:
module gen_fifo #(depth = 128)
( din,wr,rd,clk....);
can you please suggest is there a way I can do this?
This is what the LRM says:
Parameters represent constants; hence, it is illegal to modify their
value at run time. However, module parameters can be modified at
compilation time to have values that are different from those
specified in the declaration assignment. This allows customization of
module instances. A parameter can be modified with the defparam
statement or in the module instance statement. Typical uses of
parameters are to specify delays and width of variables.
'run time' means during simulation, after elaboration. A synthesiser doesn't 'run' anything, but what you're doing is effectively "run time", and so is illegal.
This doesn't mean that you can't do it, though. Pass in your FIFO depth as a module port. I'm assuming that you know how to code a FIFO from first principles. If so, you will normally have a constant for the FIFO size; just replace this constant with the value at the port, and find some way to set the memory size. You'll obviously need to be careful when changing the FIFO size - you may need to reset it, for example. If you don't know how to code a FIFO you should ask with an FPGA or an electronics tag.
I am doing following in my UVM testbench to create seq and start test.
I've some sequences. I'm copying a code snippet from one of the sequences bellow.
Inside body():
`uvm_create_on(my_seq, p_sequencer.my_sequencer)
my_seq.randomize();
`uvm_send(my_seq)
2.In my test, I'm doing following to start a sequence:
task run_phase(uvm_phase phase);
.....
phase.raise_objection(this);
seq.start(env.virtual_sequencer);
phase.drop_objection(this);
endtask
Now, if I do this, the test is starts and ends at zero time. What I mean is, the DUT is not being driven by my sequence. If I make following change then it seems to work fine:
Option1:changing run_phase in test-
task run_phase(uvm_phase phase);
.....
phase.raise_objection(this);
seq.start(env.virtual_sequencer);
#100000; // Adding delay here.
phase.drop_objection(this);
endtask
If I do this then test starts and I can see that DUT is being driven and everything is working as expected. However, test always ends at time 1000000- even though the sequence is not done sending all the transactions to DUT. It's not good as I don't know how long my DUT will take to complete a test. So, I rather tried something like this:
Option 2: Keeping the default code in test (not adding delay in run_phase). Making following change inside body of my_seq:
Inside body():
uvm_test_done.raise_objection(this);
`uvm_create_on(my_seq, p_sequencer.my_sequencer)
my_seq.randomize();
`uvm_send(my_seq)
uvm_test_done.drop_objection(this);
If I do this then it works fine. Is it the proper way of handling objection ? Going back to my original implementation, I assumed that my sequence is blocking. So, whenever I start a sequence in run_phase of test using start(...), it'll be considered as blocking and will wait at that line until sequence is done sending all the transaction. So, I didn't add any delay in my original code.
I think I'm missing something here. Any help will be greatly appreciated.
If you're doing a fork in your main sequence, then its body() task (which is called by start()) won't block. If you need to do a fork...join_none due to some kind of synchronization you need, you should also implement some kind of mechanism to know when the forked off processes terminate so that you can stall body until then. Example:
// inside your main sequence
event forked_finished;
task body();
fork
`uvm_do(some_sub_seq)
-> forked_finished;
join_none
// do some other stuff here in parallel
// make sure that the forked processes also finished
#forked_finished;
endtask
This code here assumes that the forked process finishes after your other code does. In production code you probably wouldn't rely on this assumption and would use a uvm_event to test first if the event already triggered before waiting.
Because body() waits until everything is finished w.r.t. stimulus, then you shouldn't have any problem setting and objection before starting this sequence and the lowering it once it's done.
You really have to consider the semantics of your sequence. Usually I expect a sequence's body to not complete until it is finished. So doing a fork/join_none would be undesirable because the caller of the sequence would have a way of knowing that the sequence has completed. Similar to what you see in your test.
The solution is to not have my_seq::body return until it is complete.
If the caller of my_seq needs to do something in parallel with my_seq, then it should be their responsibility to do the appropriate fork.
I understand that modules are essentially like c++ functions. However, I didn't find something like a main() section that calls those functions. How does it work without a main() section?
Trying to find (or conceptually force) a main() equivalent in HDL is the wrong way to go about learning HDL -- it will prevent you from making progress. For synthesisable descriptions you need to make the leap from sequential thinking (one instruction running after another) to "parallel" thinking (everything is running all the time). Mentally, look at your code from left to right instead of top to bottom, and you may realize that the concept of main() isn't all that meaningful.
In HDL, we don't "call" functions, we instantiate modules and connect their ports to nets; again, you'll need to change your mental view of the process.
Once you get it, it all becomes much smoother...
Keep in mind that the normal use of Verilog is modeling/describing circuits. When you apply power, all the circuits start to run, so you need to write your reset logic to get each piece into a stable, usable operating state. Typically you'll include a reset line and do your initialization in response to that.
Verilog has initial blocks are kinda like main() in C. These are lists of statements that are scheduled to run from time 0. Verilog can have multiple initial blocks though, that are executed concurrently.
always blocks will also work as main() if they've an empty sensitivity list:
always begin // no sensitivity list
s = 4;
#10; // delay statements, or sim will infinite loop
s = 8;
#10;
end