1

I'm currently working on a pipelined MIPS cpu using Icarus Verilog and have come across some very strange behaviour when using an if statement within an always_ff loop. I'm currently testing this implementation of a PC block:

module PC (

    input logic clk,
    input logic rst,

    input logic[31:0] PC_JVal,
    input logic jump_en,
    input logic branch_en,
    input logic PC_Stall,

    output logic [31:0] PC_Out,
    output logic fetch_stall,

    output logic active,
    output logic [2:0] check

);

// Active is completely dependent on the value of the PC.

// JUMP_EN --> PC = JVAL
// BRANCH_EN --> PC = PC + JVAL
// PC_Stall --> PC = PC

reg [31:0] PC;
logic [31:0] branchSignExt = (PC_JVal[15] == 1) ? {16'hFFFF, PC_JVal[15:0]} : {16'h0000, PC_JVal[15:0]};
logic start;

assign fetch_stall = PC_Stall;
assign active = (PC != 0) ? 1 : 0;

assign PC_Out = (active == 0) ? 0 : ( (PC_Stall == 1) ? PC + 4 : ( (jump_en == 1) ? PC_JVal : ( (branch_en == 1) ? PC + branchSignExt : PC + 4 ) ) );

initial begin

    PC = 0;
    start = 0;

    check = 0;

end

always_ff @ (posedge clk) begin

    check[1] <= ~check[1];

    if (rst) begin
        start <= 1;
    end 
    else if (active) begin

        
        
        if (PC_Stall) begin
            PC <= PC;
            check[0] <= ~check[0];
        end
        else if (jump_en) begin
            PC <= PC_JVal;
        end
        else if (branch_en) begin
            PC <= PC + branchSignExt;
        end
        else begin
            PC <= PC + 4;
        end
        
    end

end

always_ff @ (negedge rst) begin
    if (start) begin
        PC <= 32'hBFBFFFFC;
        start <= 0;
    end
end

endmodule

And am running the following testbench:

module PC_TB ();

    logic clk;
    logic rst;

    logic[31:0] PC_JVal;
    logic jump_en;
    logic branch_en;
    logic PC_Stall;

    logic [31:0] PC_Out;
    logic fetch_stall;
    logic active;
    logic [2:0] check;

    initial begin

        $dumpfile("PC_TB.vcd");
        $dumpvars(0, PC_TB);

        clk = 0;
        jump_en = 0;
        PC_Stall = 0;
        branch_en = 0;
        rst = 0;
        
        repeat(100) begin
            #50; clk = ~clk;
        end

        $fatal(1, "Timeout");

    end

    initial begin
        
        @ (posedge clk);
        @ (posedge clk);
        @ (posedge clk);
        rst = 1;
        @ (posedge clk);
        @ (posedge clk);
        @ (posedge clk);
         rst = 0;
        @ (posedge clk);
        @ (posedge clk);
         @ (posedge clk);
        PC_Stall = 1;
        @ (posedge clk);
        PC_Stall = 0;
        @ (posedge clk);
        
        @ (posedge clk);
        
        

    end

    PC PC(.clk(clk), .rst(rst), .PC_JVal(PC_JVal), .jump_en(jump_en), .branch_en(branch_en), .PC_Stall(PC_Stall), .PC_Out(PC_Out), .fetch_stall(fetch_stall), .active(active), .check(check));

endmodule

The issue I'm having is that how the if statement checking for PC_Stall is evaluated seems to alternate between clock cycles and I have no clue why.

I get the following VCD output when running it with the test bench as is (not the desired output), with the PC Stall not really happening (the PC value should remain for 2 cycles, but here it is only for one.)

Stall lasts 1 Cycle

Then by just shifting the point at which the PC_Stall is asserted forward by one cycle, results in Stall lasting 3 cycles, even though its only asserted for 1.

Stall lasts 3 cycles

I've been really stuck on this and genuinely have no idea what is wrong, and I would appreciate the help.

toolic
  • 57,801
  • 17
  • 75
  • 117
Mineglitch
  • 11
  • 3

2 Answers2

1

iverilog does not have very good support for SystemVerilog features yet. If you compile your code on other simulators, such as VCS on edaplayground, you will get compile errors. For example:

Error-[ICPD] Illegal combination of drivers
  Illegal combination of procedural drivers
  Variable "check" is driven by an invalid combination of procedural drivers. 
  Variables written on left-hand of "always_ff" cannot be written to by any 
  other processes, including other "always_ff" processes.
  This variable is declared at : logic [2:0] check;
  The first driver is at : always_ff @(posedge clk) begin
  check[1] <= (~check[1]);
   ...
  The second driver is at : check = 0;

You must fix all such errors.

Note, several simulators are available on edaplayground if you sign up for a free account.

toolic
  • 57,801
  • 17
  • 75
  • 117
-1

So it appears to be a compiler issue regarding how conditionals are treated when both inputs to said conditionals change and the conditionals themselves are executed on a positive clock edge.

The issue was fixed by adding a small delay just before said conditional, to give the values time to update or something, not sure and this seems like quite a botched solution, it works though.

Mineglitch
  • 11
  • 3
  • It may add sanity to your debug, if the testbench were modified to assure synchronous behavior between the testbench driving signals and the dut which is intended to capture the old value at the rising edge. One way of doing this is to use a SV clocking block. Another way is to use #something delays after the @(posedge clk) blocking time. A 3rd way would be to drive on the negedge and sample on the rising. (or vice-versa) Adding delays to an RTL model is not the way to go, it will not work in a synthesis flow. Also think you should do what toolic suggested. – Mikef Feb 01 '22 at 21:08