I write ldpc_if.sv and ldpc_transaction.sv as follows.
"ldpc_if.sv"
interface ldpc_if#(parameter COLS=9216, parameter ROWS=1024) (input clk, input reset);
logic [COLS-ROWS-1:0] en_enq_data;
logic en_enq_valid;
logic en_enq_ready;
logic [ROWS-1:0] en_deq_data;
logic en_deq_valid;
logic en_deq_ready;
logic [COLS-1:0] de_enq_data;
logic de_enq_valid;
logic de_enq_ready;
logic [COLS-1:0] de_deq_data;
logic de_deq_valid;
logic de_deq_ready;
endinterface
"ldpc_transaction.sv"
class ldpc_transaction#(parameter WIDTH=8192) extends uvm_sequence_item;
rand bit [WIDTH-1:0] data;
bit [8191:0] encode_data_in;
bit [1023:0] encode_data_out;
bit [9215:0] decode_data;
`uvm_object_utils(ldpc_transaction)
function new(string name = "ldpc_transaction");
super.new();
endfunction
endclass
And I write ldpc_monitor.sv to monitor interface.
task ldpc_monitor::collect_one_pkt(ldpc_transaction tr);
while(1) begin
#(posedge vif.clk);
if(vif.en_enq_valid && vif.en_enq_ready) break;
end
tr.encode_data_in <= vif.en_enq_data;
while(1) begin
#(posedge vif.clk)
if(vif.en_deq_valid && vif.en_deq_ready) break;
end
tr.encode_data_out <= vif.en_deq_data;
while(1)begin
#(posedge vif.clk)
if(vif.de_deq_valid && vif.de_deq_ready)begin
break;
end
end
tr.decode_data <= vif.de_deq_data;
$display("tr.decode_data = %0h", tr.decode_data);
$display("vif.de_deq_data = %0h", vif.de_deq_data);
endtask
vcs compiles all files successfully. However, tr.decode_data is always displayed as zero. But, vif.de_deq_data is correct. Why is vif.de_deq_data not assigned to tr.decode_data.
It's because the $display you've used to display your transaction is blocking. Conversely, you've used an non-blocking assignment to set tr.decode_data.
Thus, your $display statement actually gets executed before your assignment. Getting a 0 is just an artefact of your simulator - could be any random stuff in the memory assigned to that variable (though most simulators just reset to 0).
Quick search revealed this useful example which illustrates exactly your problem.
https://verificationguide.com/systemverilog/systemverilog-nonblocking-assignment/
Related
I need to bypass programming a bunch of registers in different blocks, the basic infrastructure is something like shown below. This gives me two types of errors:
Dynamic type in non-procedural context
Illegal reference in force/proc assign
Both of these are for line:
force top.design0.register_block.in = in;
Is there any quick solution short of writing an FSM that goes over all register_values?
logic [31:0] register_values[2:0] = {'habcd, 'hbcde, 'hcdef };
class Injector;
task automatic run();
foreach (register_values[i]) force_reg(register_values[i]);
endtask
task automatic force_reg(input logic [31:0] in);
#(negedge top.design0.register_block.clk);
force top.design0.register_block.in = in;
#(negedge top.design0.register_block.clk);
endtask
endclass
module register_block(input logic clk,
input logic[31:0] in);
endmodule
task force_registers();
Injector injector = new();
injector.run();
endtask
module design(input logic clk);
logic[31:0] in;
register_block register_block(clk, in);
endmodule
module top();
logic clk;
design design0(clk);
initial force_registers();
initial begin
clk = 0;
forever #10 clk = ~clk;
end
initial #200 $finish;
endmodule
Tried the tasks without the 'automatic' but that gives same error.
Tasks defined in class are automatic by default. The tool complains about in which is a task argument. Since the task is automatic, the argument variable is considered to be automatic as well.
The way around it is to declare the task static:
task static force_reg(input logic [31:0] in);
Assuming, that there is only a single instance of the task 'run' at any time, it should work.
I do have a problem understanding how always_ff works in a way of creating a mesh of logic gates.
What do I mean ? When I use always_comb like here :
module gray_koder_dekoder(i_data, i_oper, o_code);
parameter LEN = 4;
input logic [LEN-1:0] i_data;
input logic i_oper;
output logic [LEN-1:0] o_code;
int i;
always_comb
begin
o_code = '0;
i = LEN-1;
if (i_oper == 1'b1) // 1'b1 - operacja
begin // kodowania
o_code = i_data ^ (i_data >> 1);
end
else // dla kazdej innej wartosci
begin // realizuj dokodowanie
o_code = i_data;
for (i=LEN-1; i>0; i=i-1)
begin
o_code[i-1] = o_code[i]
^ i_data[i-1];
end
end
end
endmodule
So how do I see it.
At the beggining the program sees that output is 0000,
now if the i_oper is equal 1 so the input is 1 then it checks changes the o_code to i_data ^ (i_data >> 1) so now the program want's to do combination of logic gates for this operation but if the i_oper is equal 0 then the program makes another set of logic gates to get different o_code.
So the always_comb gives the final result for every bite in the i_data that results in o_code.
So my teacher said that always_comb is "blocking" but always_ff is not "blocking" I don't get it ...
So the always_ff doesn't give the final result of logic gates for the input to get a specific output ?
Another example of always_comb :
module gray_dekoder (i_gray, o_data);
parameter LEN = 4;
input logic [LEN-1:0] i_gray;
output logic [LEN-1:0] o_data;
always_comb
begin
o_data = i_gray;
for (int i=LEN-1; i>0; i=i-1)
o_data[i-1] = o_data[i] ^ i_gray[i-1];
end
endmodule
So at the beggining the program sees that the output is 0000 so it will make a set of logic combination to have 0 at the end. Then he sees loop for that modifies the output so the program checks every bit of the input like bit nr 3 then nr 2 then nr 1 etc. and creates for every input specific output so now the output is not 0000 anymore but set of instructions that modifies the output made from loop "for"
so the always comb gives a final result from the analizing the whole code from top to bottom of "always_comb" and creates a set of instructions/set of logic gates that helps it. Because always_comb overwrites the previous instructions like 0000 it was a basic instruction but then was overwrited by the loop "for"
But maybe I think wrongly because if instruction doesn't overwrite the 0000 instruction like here :
module replace(i_a, i_b, o_replaced, o_error);
parameter BITS = 4;
input logic signed [BITS-1:0] i_a, i_b;
output logic signed [BITS-1:0] o_replaced;
output logic o_error;
int i;
always_comb
begin
o_replaced = '0;
if(i_b < 0 || i_b > BITS)
begin
o_error = 1;
o_replaced = 'x;
end
else
begin
i = i_b;
o_replaced = i_a;
o_replaced[i-1] = 1;
o_error = 0;
end
end
endmodule
I have here 0000 output that isn't overwrited for "else" So I don't know what happens then.
I think of always_comb as an "final result" that gives a set of instructions how to create logic gates. But the final result is at the end, so if something changes then the beggining result doesn't matter "overwrited" but with if loop it doesn't work with my mind set.
So always_ff I heard that it doesn't give a final result that it can stop at any point not like in always_comb that the program analysis from top to bottom.
Verilog is designed to represent behavior of hardware and it is not a regular programming language. It operates different semantics.
At a very top glance, hardware consists of combinational logic and flops (and latches). From the other point of view hardware is a set of parallel functions which are synchronized across design by clocks. This means that at a clock edge a lot of hardware devices start working in parallel and they should produce results by the next clock edge. Those results could be used by other functions at the next clock cycle.
Roughly, combinational logic defines a function, flops provide synchronization.
In verilog all those devices are described with always blocks. TUse of edges, e.g., #(posedge clk) provides synchronization points and usually defines flops in the code. A simple function and a flop look like the following.
// combinational logic
always #* // you can use always_comb instead
val = in1 & in2; // a combinational function
// flop
always #(posedge clk) // you can use always_ff instead
out <= val; // synchronization
So, in the example val is calculated by the combinational function and its synchronous out is made available to other functions to be used by a flop. You can see progression of clocks and results in a waveform.
So, this is what always_ff is doing, just providing synchronization and expressing flops for synthesis.
In general, always, always_comb, always_ff and always_latch are identical. The last three are system verilog blocks and just provide additional hints to the compiler which can run additional checks on them. I intentionally used just always blocks in my example to show that. There are some other conditions which need to be programmed to cleanly express the intention. So, your assertion about different working of always_ff has no base. It works the same as other always blocks.
What I think confuses you, is use of blocking (=) and non-blocking (<=) assignments. It does not matter for synthesis which one you use, but it matters for simulation. The difference is described and numerous documents and examples. To understand it properly you need to look into verilog simulation scheduling semantics.
But the rule of thumb is that you should use non-blocking assignments (<=) in flops and use blocking (=) in combinational logic. In flops '<=' allows simulating real behavior of flops. Remember that hardware is a massively-parallel evaluation engine. Consider the following example:
always_ff #(posedge clk) begin
out1 <= in1;
out2 <= out1;
end
The above example defines at least two flops working at posedge clk. out1 and out2 must be synchronized at this clock. It means, the flops have to catch values which existed before the edge and present them after the edge. So, for out1 the value existed before the edge is in1, evaluated by a combinational logic. What would be the value of out2? Which value existed before the edge? Apparently, the value of the out1 before it gets changed to the new value of in1.
clk ___|---|___|---|___
in1 0
out1 x 0 << new value of in1
out2 x << old value of ou1
.
in1 1 .
out1 . 1 << new value of in1
out2 . 0 << old value of ou1
So, after evaluation of the block, the at the first edge the value of out2 will be 'x' (previous value of out1), at the second clock edge it will finally get value of 'in1' as it existed at the previous clock cycle.
I hope it would make your understanding a bit better.
I am facing a puzzling behavior of Systemverilog 'assert' when trying to verify a flip-flop had sampled the input value on a rising edge. It seems that the assert will pass if I add an arbitrarily small delay AFTER the rising edge, but not right on it. However if I add an 'always' block that prints the value of the output at the rising edge time, it will show the correct value. BTW and 'if' comparison behaves like 'assert'. I am using Modelsim as my simulator. Below is my code with inline comments to illustrate the problem. Please also look at the image in the link for further illustration.
Thanks.
`timescale 1ns/1ns
module example_t(input logic clk, input logic resetb, input logic selector, output logic result);
always_ff # (posedge clk or ~resetb)
if(~resetb)
result <= 0;
else
result <= selector ? 1'b1 : 1'b0;
endmodule
module tst();
logic clk;
logic resetb;
logic selector;
logic result;
example_t example(.clk(clk), .resetb(resetb), .selector(selector), .result(result));
//Simulate a clock
always
#10 clk = ~clk;
initial
begin
clk = 0;
resetb = 0; //Reset
selector = 0;
#20
#(posedge clk);
resetb = 1;
#20
#(posedge clk);
selector = 1;
#20 //**At this time we are at 70ns**
#(posedge clk); //**We are already at the posedge, but adding just in case**
assert(result == 1'b1); //**This will fail, meaning result[enter image description here][1]==0 at 70ns**
end
always # (result)
begin
$display("result=%h time=%t",result, $realtime); //**This will print the value at 70ns as 1**
end
endmodule
You have a couple of problems in the code.
The problem is a race on the selector (and on resetb). You modify the signals with blocking assignments at the same simulation cycle as you use them. Depending on simulator or simulation conditions the update can happen before or after its use. As a result, synopsys and cadence have result ready at time 50, making assertion happy. On the other hand, mentor an aldera make result ready at 70 only and cause assertion failure at this time. You can avoid this issue and use nbas and make every compiler to have results ready at 70.
There is a slightly different source of race which involves the immeditate assertion in the initial block. The conditional expression in it will be executed as a part of the procedural block flow in active region, before non-blocking assignments. It still sees the old value of the result and is evaluated to false.
The way around it is to set 'selector' and resetb way before the posedge. The following will make everyone happy. The result will be ready at 50 and assertion at 70 will be happy as well.
initial
begin
clk = 0;
resetb = 0; //Reset
selector = 0;
#15
resetb = 1; // set it before the next posedge
#(posedge clk); // wait for posedge
#10
selector = 1; // set it before the next edge
#20 //**At this time we are at 70ns**
#(posedge clk); //**We are already at the posedge, but adding just in case**
assert(result == 1'b1) $info("Assertion is ok at %0t", $realtime); //**This will fail, meaning result[enter image description here][1]==0 at 70ns**
#20 $finish;
end
My question emphases the amend of the struct's element!
struct packed {
logic word;
logic [31:0] test;
} a;
logic [32:0] a_input;
logic a_ff;
always_latch begin
if (enable) begin
a = a_input; // map the bus `a_input` to the struct `a`
a.test = a.test[1:0]; // change the `test` child
end
end
enable and a_input are flip-flops on the same clock (then can reach the latch at different moment in physical hardware)
a is the modified comb/latch version of a_input
Vivado doesn't synthetize this as a latch.
I want to change only the a.test, a_input isn't a struct then I can't use a_input.test. Then that code describes well what I want to do.
How can I get a latch?
Edit: I can use a mix of always_comb, always_ff and assign.
struct packed {
logic word;
logic [31:0] test;
} a, a_comb;
logic [32:0] a_input;
logic a_ff;
always_ff #(posedge clk)
if (enable) begin
a_ff <= a_comb;
end
always_comb begin
a_comb = a_input; // map the bus `a_input` to the struct `a_comb`
a_comb.test = a_comb.test[1:0]; // change the `test` child
end
assign a = (enable)? a_comb: a_ff;
I'd like to avoid these extra lines and temporary logics, it should be possible using a simple always_latch.
Edit #2:
I really want to amend only the test element of my struct, and let all the other element being assigned from a_input.
If it was a FF, it'd do:
always_ff #(posedge clk)
if (enable) begin
a <= a_input; // map the bus `a_input` to the struct `a`
a.test <= a.test[1:0]; // change the `test` child
end
end
I want to convert that logic to a latch instead of FF.
First make sure a simple latch will synthesize as a latch. If it doesn't, then that a separate problem and you will need to figure out; check your synthesizer version and target device.
always_latch
if (enable)
a <= a_input;
I'm not sure why your latch does not work. Latches should be kept simple to prevent unintended complex feedback. Your code looks simple enough as the only thing it is doing beyond a basic D-latch is mask some bits.
Assuming the basic latch synthesizes correctly, you can do combinational logic before or after the latch. Ex:
always_comb begin
a_comb = a_input;
a_comb.test = a_comb.test[1:0];
end
always_latch
if (enable)
a <= a_comb;
or:
always_latch
if (enable)
a_lat <= a_input;
always_comb begin
a = a_lat;
a.test = a.test[1:0];
end
If you are only masking bits, you should be able to use:
always_latch
if (enable)
a <= a_input & 32'h100000003;
If the above three doesn't work, but a simple latch does work properly, then it might be optimization related. In this case make sure a_input and enable are FF and are functionally toggleable (ie not optimized away due do something else upstream). Also check how a is begin used down stream.
I tried this code in vivado, it able to synthesize it to LDCE latch .
module latch (
input logic a_input,enable,
output logic a
);
always_latch if(enable) a = a_input;
endmodule
A latch is effectively a level sensitive register rather than edge sensitive. The driver of enable isn't shown, so that may be getting driven to 0 by Vivado. A quick example, if you are trying to make a latch the following would should do it.
always#(enable) begin
a = enable ? a_input : a;
end
Generally, we tend to want to avoid latches, but if you are dead set on making one, you can start by getting rid of the posedge clk from the sensitivity list and changing it to *. That would mean that on any change of the signals, you would get a re-evaluation of the block. The fact that enable would control loading of the a_ff term while high, and then hold the value while low, should be seen as a latch provided that enable is driven from some source and not optimized to a constant 0 state because it is a undriven signal.
I have two 2-bit inputs and an output of 1-bit. So what I'm trying to do is code a next state value with taking AND of the two inputs, then use non-blocking <= to assign that value into register xy_r, which is now two D-flipflops. So I am seeking to get always the XOR-value of the xy_r previous clock edge as my output xor_out. I suppose that the way below is not the right one?
It worked in the simulation but then again in RTL Synthesis I didn't end up with an XOR gate but a third flip-flop so it seems like xor_out is treated as a register.
I suppose I can't use assign outside of the if-else-statements because in that case the output wouldn't follow the xy_r previous state but the present.
Can you please assist me how to solve this issue, if there is a somewhat simple way.
module 2ffsxor
(input logic clk,
input logic rst_n,
input logic [1:0] x_in,
input logic [1:0] y_in,
output logic xor_out
);
logic [1:0] xy;
logic [1:0] xy_r;
always # (posedge clk or negedge rst_n)
begin
if (rst_n == '0)
xy_r <= '0;
else
begin
xy = x_in & y_in;
xy_r <= xy;
xor_out = xy_r[0] ^ xy_r[1];
end
end
endmodule
And here's a schematic what it should be:
Schematic 2ffsxor
Synthesis treats this code as if you had wrote
begin
xy_r <= x_in & y_in;
xor_out <= xy_r[0] ^ xy_r[1];
end
That's two stages of registers. But since you assigned xor_out with a blocking assignment, you have a simulation race condition for any process that tries to read xor_out. The race would be from the result from the previous values of xy_r and the result before that!
If you use an continuous assign statement outside this always block, any process reading xor_out on the clock edge will see the result from the previous state of xy_r.
But why not write this as on register:
always # (posedge clk or negedge rst_n)
begin
if (rst_n == '0)
xor_out <= '0;
else
begin
xy = x_in & y_in;
xor_out <= xy[0] ^ xy[1];
end
end