Retrasos y/o cómo ciclar manualmente el reloj en un bucle al construir el banco de pruebas Verilog para probar FSM para la conversión de microcódigo/ROM

Estoy trabajando en un proyecto en el que necesito convertir una máquina de estados finitos codificada en Verilog en una ROM. Para hacer esto, necesito crear un archivo de memoria para la versión ROM del FSM que almacena el estado siguiente y los valores de salida en las direcciones compuestas por el estado actual y los valores de entrada. Mi idea de cómo construir este archivo fue tomar el FSM en cuestión y agregar la entrada "estado_actual" y las salidas "estado_siguiente", y luego hacer una pequeña modificación en el FSM para que los use. Sin embargo, he tenido varios problemas con este tesbench que parece que no puedo resolver, así que pensé en armar un fsm más pequeño para mostrar lo que estoy haciendo y, con suerte, obtener una idea de lo que estoy haciendo incorrectamente. Creo que mi problema principal es que la señal del reloj no parece activarse en absoluto (al menos cuando depuro, el bloque que debería activarse en el reloj posedge no parece activarse nunca). El FSM en cuestión que estoy tratando de modelar tiene algo así como 15 bits de estado actual y de entrada, y 76 bits de estado siguiente y de salida, así que en lugar de usar el código, acabo de juntar este FSM más pequeño (obtengo el mismo comportamiento por lo que el problema no es específico de la máquina de estado y probablemente se deba más a mi mal banco de pruebas).

Así que aquí está la unidad FSM bajo prueba. Es un FSM básico que saqué de un viejo libro de texto:

    module mealy_traditional(input wire clk, 
                        input wire reset, 
                        input wire a, 
                        input wire b, 
                        output wire y);
    
    //Symbollic State Definition
    localparam [1:0]    state0 = 2'b00, 
                        state1 = 2'b01, 
                        state2 = 2'b10;
    //signal declaration
    reg [1:0]   stateCurrent, 
                stateNext;
    
    //state register
    always @(posedge clk, posedge reset)
        if(reset)
            stateCurrent <= state0;
        else
            stateCurrent <= stateNext;
    
    //_______________________________________________________Next State Logic
    always @* begin
        stateNext = stateCurrent
        //______________________________________________________START_CASE
        case(stateCurrent)
            //___________________________________________________state0
            state0:
                if(a)
                    if(b)
                        stateNext = state2;
                    else
                        stateNext = state1;
                else
                    stateNext = state0;
            //___________________________________________________state1
            state1:
                if(a)
                    stateNext = state0;
                else
                    stateNext = state1;
            //___________________________________________________state2
            state2: stateNext = state0;
            //__________________________________________________default
            default: stateNext = state0;
        endcase
    end
    //Mealy Output Logic, function of both current state and current inputs
    assign y = (stateCurrent == state0) & a & b;
endmodule

Y aquí está el mismo FSM modificado (creo) para aceptar el estado actual como entrada y asignar la siguiente salida de estado a una salida de cable.

    module mealy_traditional(input wire clk, 
                        input wire reset, 
                        input wire [1:0] state_current, 
                        input wire a, 
                        input wire b, 
                        output wire [1:0] state_next, 
                        output wire y);
    
    //Symbollic State Definition
    localparam [1:0]    state0 = 2'b00, 
                        state1 = 2'b01, 
                        state2 = 2'b10;
    //signal declaration
    reg [1:0]   stateCurrent, 
                stateNext;
    
    //state register
    always @(posedge clk, posedge reset)
        if(reset)
            stateCurrent <= state0;
        else
            stateCurrent <= state_current;
    
    //_______________________________________________________Next State Logic
    always @* begin
        stateNext = stateCurrent;
        //______________________________________________________START_CASE
        case(stateCurrent)
            //___________________________________________________state0
            state0:
                if(a)
                    if(b)
                        stateNext = state2;
                    else
                        stateNext = state1;
                else
                    stateNext = state0;
            //___________________________________________________state1
            state1:
                if(a)
                    stateNext = state0;
                else
                    stateNext = state1;
            //___________________________________________________state2
            state2: stateNext = state0;
            //__________________________________________________default
            default: stateNext = state0;
        endcase
    end
    //Mealy Output Logic, function of both current state and current inputs
    assign y = (stateCurrent == state0) & a & b;
    assign state_next = stateNext;
endmodule

Así que aquí es donde seguramente radica el problema, el banco de pruebas. Ahora sé que, dadas las entradas limitadas de este FSM, sería muy fácil presionar manualmente el fsm con cada combinación de entrada y mostrar la salida, sin embargo, obviamente, con el otro FSM que tiene cerca de 100 bits de IO, esa no es realmente una opción. . Por lo tanto, decidí crear un bucle for para iterar a través de todas las posibles combinaciones de bits de entrada y luego generar las salidas para guardarlas en el archivo de memoria. El problema con el que me encuentro es que no parece que la entrada del reloj cicle en absoluto. Actualmente estoy probando el siguiente código de banco de pruebas

`timescale 1ns / 1ns
module mealy_traditional_tb;
    reg             clk = 1'b0;
    reg             reset = 1'b0;
    reg [3:0]       in;
    wire [2:0]      out;
    integer         i = 0;
    integer         maxValue = 16;


    //Instantiation
    mealy_traditional 
        uut(.clk(clk), 
            .reset(reset),
            .state_current(in[3:2]),
            .a(in[1]), 
            .b(in[0]), 
            .state_next(out[2:1]),
            .y(out[0]));
    initial begin
        
        reset = 1;
        #10
        reset = 0;
        #10
        for(i = 0; i < maxValue; i = i + 1) begin
            in = i;
            clk = 1;
            clk = 0;
            clk = 1;
            clk = 0;
            $display("%b : %b", in, out);
        end
        $finish;
    end
endmodule

Inicialmente había intentado crear un ciclo de reloj independiente mediante el uso de un

always #10 clk = ~clk;

Y luego poner retrasos dentro del bucle for, que a verilog NO le gustó (ni siquiera compilaba, arrojaba errores de sintaxis).

Entonces, tal como está ahora, este código de banco de pruebas se compila y el simulador se activa, sin embargo, está bastante claro que no se accede a la rama else de la actualización del registro de estado en el FSM, ya que cuando configuro puntos de interrupción allí, nunca se activan y la salida de la máquina de estado parece ser solo el estado de referencia de un reinicio del sistema. (como sigue)

# 0000 : 00x
# 0001 : 00x
# 0010 : 00x
# 0011 : 00x
# 0100 : 00x
# 0101 : 00x
# 0110 : 00x
# 0111 : 00x
# 1000 : 00x
# 1001 : 00x
# 1010 : 00x
# 1011 : 00x
# 1100 : 00x
# 1101 : 00x
# 1110 : 00x
# 1111 : 00x

Hubiera pensado que forzar manualmente el ciclo del reloj en el bucle for habría hecho que la máquina de estados finitos se actualizara, pero parece que no. ¿Hay alguna manera de asegurarme de que el ciclo del reloj se active de esa manera?

Respuestas (1)

Sí, el problema está en el código de su banco de pruebas. Siempre es una buena idea mostrar el tiempo de simulación cuando valora, $displayya que ayuda a depurar su problema. Si lo hace, muestra que toda su salida se produce al mismo tiempo (20ns):

              20 0000 : 00x
              20 0001 : 00x
              20 0010 : 00x
              20 0011 : 00x
              20 0100 : 00x
              20 0101 : 00x
              20 0110 : 00x
              20 0111 : 00x
              20 1000 : 00x
              20 1001 : 00x
              20 1010 : 00x
              20 1011 : 00x
              20 1100 : 00x
              20 1101 : 00x
              20 1110 : 00x
              20 1111 : 00x

La primera columna es el tiempo de simulación. El problema es que no transcurre tiempo dentro del forbucle. Una forma de solucionarlo es agregar #retrasos en el ciclo de la siguiente manera:

initial begin
    reset = 1;
    #10
    reset = 0;
    #10
    for(i = 0; i < maxValue; i = i + 1) begin
        in = i;
        #5 clk = 1;
        #5 clk = 0;
        #5 clk = 1;
        #5 clk = 0;
        $display($time,, "%b : %b", in, out);
    end
    $finish;
end

Esto imprime:

          40 0000 : 000
          60 0001 : 000
          80 0010 : 010
         100 0011 : 101
         120 0100 : 010
         140 0101 : 010
         160 0110 : 000
         180 0111 : 000
         200 1000 : 000
         220 1001 : 000
         240 1010 : 000
         260 1011 : 000
         280 1100 : 000
         300 1101 : 000
         320 1110 : 000
         340 1111 : 000

Como puede ver, el tiempo de simulación y outestán cambiando de valor, y outya no tiene incógnitas ( x).


Aquí hay otra forma de escribir el código del banco de pruebas. Utiliza el alwaysbloque más tradicional para el reloj, que estaba tratando de hacer funcionar. También asegura que la entrada esté sincronizada con el reloj.

initial begin
    $monitor($time,, "%b : %b", in, out);
    reset = 1;
    #10
    reset = 0;
    #10
    for (i = 0; i < maxValue; i = i + 1) begin
        @(posedge clk) in <= i;
    end
    $finish;
end

always #10 clk = ~clk;