Search code examples
verilogfpgacpu-architectureintel-fpgaquartus

Iteration limit when implementing a multicycled processor


I'm trying to implement a simple multicycle processor and I ran into some problems that I don't seem to be getting through. The code is below. I'm just experimenting right now to get this flowing. When I'm done, I'll begin implementing instructions and ALU. However, I'm stuck at this point. In the code below, I'm aware that data_memory is never used (I'll get to there if I can resolve this), some inputs and outputs are also not used for now, x1 and x2 are just variables I created to see what's really going on. What's in definitions.v file is self-evident.

I'm using Altera Quartus 15.1 with Verilog2001. This code compiles fine excepts some warnings due to unused stuff but when I try to simulate it with a clock period of 20ns it gives an error saying that "Error (suppressible): (vsim-3601) Iteration limit 5000 reached at time 100 ns". It also says this is suppressible but I don't know how to suppress either.

I looked up for this error and I learned that this is happening because at some point the code goes into an infinite loop. I tried to solve this by creating another variable ok. A cycle will start by setting ok to 0 and after microoperations for that cycle are done, I set ok to 1. So the cycle will not change at an improper time (it's like locking the cycle). Unfortunately, this resulted with the same error.

I tried another flow, too. Instead of cycle and next_cycle, I created one variable for cycle. At every rising edge of clock, I checked the current state and did things accordingly, then set the cycle for next step. Example:

always @ (posedge clk) begin
    case (cycle)
        3'b000: begin
            MAR <= PC;
            cycle <= 3'b001;
            ire <= 1'b1;
            x2 <= 2'b00; 
        3'b001: begin
            ...
            ...

This also compiles fine, and can be simulated without error! However, is not functioning correctly, giving weird(or unexpected) results. I find other approach more intuitive. So I will try to make it work.

How can I resolve/implement this?

`include "definitions.v"

module controller(
    input                           clk,
    input                           nres,
    output reg                      ire,
    output reg                      dwe,
    output reg                      dre,
    output reg  [1:0]               x2,
    output reg  [`IADR_WIDTH-1:0]   i_address,
    output reg  [`DADR_WIDTH-1:0]   d_address,
    output reg  [`DATA_WIDTH-1:0]   data_out);

    reg [2:0] cycle = 3'b000;
    reg [2:0] next_cycle;

    reg [`IADR_WIDTH-1:0] PC  = 6'b000000;
    reg [`INST_WIDTH-1:0] IR  = 12'b00000_0000000;
    reg [`DADR_WIDTH-1:0] MAR = 6'b000000;        
    reg [4:0]             OPC = 5'b00000;

    wire [`DATA_WIDTH-1:0] data_in;
    wire [`INST_WIDTH-1:0] instruction;

    reg [1:0] x1;

    data_memory dmem        (   .clk        (clk),
                                .dwe        (dwe),
                                .dre        (dre),
                                .nres       (nres),
                                .d_address  (d_address),
                                .d_data     (data_out),
                                .d_q        (data_in));

    instruction_memory imem (   .clk        (clk),
                                .ire        (ire),
                                .i_address  (i_address),
                                .i_q        (instruction));

    reg ok = 1;

    always @ (posedge clk) begin
        cycle = (ok) ? next_cycle : cycle;
    end

    always @ (cycle) begin
        case (cycle)
            3'b000: begin
                ok = 0;
                MAR = PC;
                next_cycle = 3'b001;
                ire = 1'b1;
                x2 = 2'b00;
                ok = 1;
            end
            3'b001: begin
                ok = 0;
                i_address = MAR;
                IR = instruction;
                ire = 1'b0;
                next_cycle = 3'b010;
                x2 = 2'b01;
                ok = 1;
            end
            3'b010: begin
                ok = 0;
                OPC = IR;
                next_cycle = 3'b011;
                x2 = 2'b10;
                ok = 1;
            end
            3'b011: begin
                ok = 0;
                if (OPC==5'b01011)  x1 = 2'b11;
                PC = PC + 1;
                next_cycle = 3'b000;
                x2 = 2'b11;
                ok = 1;
            end
        endcase    
    end

endmodule

Solution

  • When we write always @(signal) in verilog, a specified sensitivity list, the logic is triggered on a change in that signal. This can lead to misunderstanding of how hardware actually works. The only hardware we have that changes on an edge is a flip-flop and you need to specify the posedge or negedge keyword for that.

    When always @(signal) is synthesised you actually get a combinatorial block, which has the effect of behaving like always @(*). This is an automatic sensitivity list.

    So from the comments we will look at this small section of code:

     always @ (*) begin
        case (cycle)
          3'b011: begin
            ok = 0;
            if (OPC==5'b01011)  x1 = 2'b11;
            PC = PC + 1;
            next_cycle = 3'b000;
            x2 = 2'b11;
            ok = 1;
    

    This a combinatorial block, triggered in the simulator when anything which can effect he output changes. Most signals are assigned to static signals, or other known values with out loops.

    PC = PC +1;
    

    The above line though updates the value of PC, this new value of PC should trigger the combinatorial block to be re-evaluated, hitting the PC increment again, etc. This all happens inside the delta cycle of the simulator.

    With hardware description languages (HDLs) like Verilog we have to remember that we are describing parallel statements, not serially executed lines of code.