0
\$\begingroup\$

Hello I'll be brief because my English isn't good thank you for your patience

I'm working on a system that drives this memory:

module Buffer_m #(
parameter Word=8,
parameter bits_Buffer_address)
(
    input  logic Clk,
    input  logic [bits_Buffer_address-1:0] Address_R,
    input  logic [bits_Buffer_address-1:0] Address_W,
    input  logic R,
    input  logic W,
    input  logic [Word-1:0] Data_buffer_in,
    output logic [Word-1:0] Data_buffer_out
);

localparam  Buffer_deep = 1 << bits_Buffer_address;
logic [Word-1:0] Buffer [Buffer_deep-1:0];

always_ff @(posedge Clk)
begin
    if (R) begin
        Data_buffer_out<=Buffer[Address_R];
    end
    else Data_buffer_out<= '0;
    if (W) begin
        Buffer[Address_W]<=Data_buffer_in;
    end
end

endmodule // Buffer

and give these results enter image description here Image 1: correct working

I think this is a normal behaviour but when I drive it with my module a couple of FSMs ,(not showing all, It's 277 lines ) I don't think this is a problem with the logic but with syntaxis or other thing.

assign Address_R = Reg_address_R_Buffer;
assign Address_W = Reg_address_W_Buffer;

...
assign R           = R_Data           || R_Bus;
assign W           = W_Data           || W_Bus;
...

...
always_ff @ (posedge Clk) begin
    if ( Reset || Address_W_0 ) begin
        Reg_address_W_Buffer<='b0;
    end else begin
        Reg_address_W_Buffer <= Reg_address_W_Buffer + Address_W_1;
    end
end

//FSM
always_ff @(posedge Clk) begin
    if (Reset) State_data_in <= 2'd0;
    else State_data_in <= Next_data_in;
end

...

always_comb begin

    ...
    W_Data           = 1'b0;

    case (State_data_in)

        2'd0:
        begin
            ...
        end
        2'd1:
        begin
            ...
            W_Data           = 1'b1;
            ...
        end

and the same way for R,

I hope this is enough code......

and that give me the same waveforms for the inputs but the outputs are wrong

enter image description here Image 2: incorrect working

As you see it doesn't save the first byte, I think it's a race condition but I'm not so sure.

------------------------------------------------------EDIT---------------------------------------------------------

I ran it in Synopsys VCS and gave me different results than image 2

enter image description here Image 3:incorrect working in VCS

I don't know why but modelsim delayed W one clock cycle, so I'll continue with VCS

As @Oldfart pointed there is a race condition when (R && W && Address_R==Address_W) but the problem persists even with R=0

enter image description here Image 4:Still not working with R=0

I think it could be that buffer reads W before my module update it at the posedge but I am not sure.

Thank you for your time.

\$\endgroup\$
  • \$\begingroup\$ My guess is that the arrival of the inputs (e.g. R, W, Data_buffer_in) are the cause of the incorrect behavior. If the drivers are from tb then you can shift them in time. If they are from a clocked process you should check that they do not use blocking assignments (which can lead to race conditions) \$\endgroup\$ – HKOB Aug 27 '18 at 23:41
  • \$\begingroup\$ I am using non-blocking assignments in the memory and blocking in the FSM that drives it, as I understand this should avoid the race. \$\endgroup\$ – Autoreiv Aug 28 '18 at 15:32
  • \$\begingroup\$ I am pretty sure that causes race conditions when you use blocking assignments in one clocked proceas and use the output in another clocked process. \$\endgroup\$ – HKOB Aug 28 '18 at 16:14
0
\$\begingroup\$

You do not show all your code but basically:

if (R) begin
    Data_buffer_out<=Buffer[Address_R];
end
else Data_buffer_out<= '0;
if (W) begin
    Buffer[Address_W]<=Data_buffer_in;
end

That code by itself is a race condition if R and W are both active and Address_R equals Address_W.

It is not bad code, because that is how all dual-ported memories in ASICs++ work. There are two solutions to the problem:

  • Make sure it never happens.

  • Catch/recognize the condition and deal with it.

The former is the case if you e.g. have a FIFO. You normally do not read from a FIFO until after it has been written. In all the years I have worked with dual-ported memories this has been the case in 99% of the code.

The latter requires you to compare the addresses and 'do something'. e.g. if you see a read and write-clash you could ignore the read result and return the write data. You get some code in along the lines of:

if (R && W && Address_R==Address_W)
begin
   <do something special>
else
   <normal action> 

In reality it is a bit more complex and the actual solution depends on if you want to have, read-after-write (Read returns the data being written) or write-after-read (The old data is returned, the new data is written)

++Many FPGA vendors allow you to specify the read-after-write or write-after-read behavior using a parameter when you instance their dual-ported memory block.

\$\endgroup\$
  • \$\begingroup\$ you are correct, there is a race condition in (R && W && Address_R==Address_W) but even with R=0 the problem persists \$\endgroup\$ – Autoreiv Aug 27 '18 at 16:12
  • \$\begingroup\$ I recently jumped into systemverilog and I think the code-bit looks fine with regards to race conditions? The statements are non-blocking so inputs are read at posedge clk and outputs are updated after. As far as I've understood the language. Could you please point to a resource if I am wrong? \$\endgroup\$ – HKOB Aug 27 '18 at 16:22
  • \$\begingroup\$ Can the RHS be read after LHS update in this code-bit? \$\endgroup\$ – HKOB Aug 27 '18 at 16:24
0
\$\begingroup\$

Considering your last comment I think your race problem is caused by blocking statements in another clocked FSM.

Consider the following code:

always @(posedge clk) addr = addr + 1;   // proc1 (blocking statement)
always @(posedge clk) data <= mem[addr]; // proc2 (non-blocking statement)

The simulator will evaluate the two processes in no defined order. In this case there are two options:

Let's assume addr starts at 0 in both cases.

  1. First proc1 is evaluated; the RHS is evaluated to 1 and then the LHS (addr) is updated immediately to 1. Next proc2 is evaluated next and the RHS is evaluated to mem[1].

  2. First proc2 is evaluated, and the RHS is evaluated to mem[0]. Next proc1 is evaluated; RHS is evaluated to 1 and then the LHS is updated immediately to 1.

In both cases after the two processes are evaluated (and all other processes triggered by posedge clk) the LHS (data) are updated with the RHS expressions as they were evaluated earlier. In the 1st option data gets mem[1], and in the second data gets mem[0].

The important thing here is that data reads from a different memory address depending on an undefined order of execution.

My current personal motto is to not use blocking statements in clocked processes, except to variables which are declared within the process (scope local to the process). I am however new to SV so I'm not fully sure how well the latter works with synthesis, but it seems to be no problem for simulation.

\$\endgroup\$
  • \$\begingroup\$ I make the FSMs the way SNUG-2003 recommend with non-blocking for the next state logic and blocking for the output logic do you think this can create the conflict? sunburst-design.com/papers/… \$\endgroup\$ – Autoreiv Aug 28 '18 at 20:58
  • \$\begingroup\$ It is basically blocking statements that are in processes sensitive to the clock that you should look for. In modelsim/questa there is a way to expand time to see the steps that occur around the clock edge. It may be helpful for debugging this. That and checking the drivers. Also some formal tools can warn about code that might cause race problems. \$\endgroup\$ – HKOB Aug 28 '18 at 21:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.