Wie sende ich ein Paket alle n Taktzyklen in Verilog?

Ich bin ziemlich neu bei Verilog und im Allgemeinen Digital Design. Ich arbeite an einem Projekt, das eine Zustandsmaschine hat. Das Modul empfängt in einem bestimmten Zustand ein Leseanforderungspaket von einem anderen Modul, und ich muss die erforderliche Leseantwortdatengröße decodieren und so viele Datenbytes in der Nutzlast zurücksenden. Jetzt muss ich dies alle n Taktzyklen tun.

Ich finde es schwierig zu verstehen, wo meine Zählung aktualisiert werden soll und wo ihr der nächste Zählwert zugewiesen werden soll? Hier ist die allgemeine Struktur meines Codes.

    always@(posedge clk_i or negedge resetn_i) begin
    if (resetn_i == 1'b0) begin
        reg_read_en         <= 1'b0;
        reg_write_en        <= 1'b0;
        count               <= 3'b111;
        next_count          <= 3'b111;  
        state               <= S_INITIAL;

    end else begin
        state               <= next_state;
        count               <= next_count;
    end

Dies ist der sequentielle Block. Jetzt in meinem Kombinationsblock

always@(*) begin

    next_state  = state;
    next_count  = count;
    .......
    ........
    M_PASSIVE:begin                         
                        if(count == 3'b000)
                        begin
                           //Update some registers with new data values
                        end else begin
                        next_count = count - 3'b001;
                        end

Ich kann nicht verstehen, ob dies auf die erforderliche Anzahl von Zyklen wartet und dann die Reg-Werte aktualisiert, oder mir fehlt hier etwas.

Ich würde mich sehr über etwas Klarheit darüber freuen.

PS Verzeihen Sie mir, wenn ich einige grundlegende Aspekte des Verhaltensdesigns in Bezug auf den Unterschied zwischen sequentiellen und kombinatorischen Blöcken vermisse.

Hier ist ein vollständiger Code dessen, was ich versuche zu tun. Im Wesentlichen möchte ich ein Paket lesen, Pakete lesen, 7 oder 8 Taktzyklen oder N clk-Zyklen warten und dann eine Leseantwort mit den Daten in den Registern senden, die den Ausgangsports kontinuierlich zugewiesen sind.

    always@(posedge clk_i or negedge resetn_i) begin
    if (resetn_i == 1'b0) begin
        reg_read_en         <= 1'b0;
        reg_write_en        <= 1'b0;
        count           <= 3'b111;
        state           <= S_INITIAL;
    end else begin
        state           <= next_state;
        count           <= next_count;
    end 
end 

always@(*) begin
    next_state  = state;
    next_count  = count;
    case(state)
        S_INITIAL: begin //not enabled
            if(enable_i == 1'b1) begin                  
                next_state = S_ENABLED;
            end
            reg_read_en         <= 1'b0;
            reg_write_en        <= 1'b0;
        end 

        S_ENABLED: begin
            if(enable_i == 1'b0) begin
                next_state = S_INITIAL;                 
            end

            case(mode_i)
                M_PASSIVE:begin
                    reg_read_en =   dnoc_packet_ready;
                    //pkt_type  =   dnoc_packet_pld_header[]
                    if(dnoc_packet_ready)
                    begin
                        reg_read_en = 1'b0;
                        if(count == 3'b000)
                        begin
                        reg_write_en    =   1'b1; 
                        read_resp_tx_data_size = 3'b001;     
                        read_resp_tx_qpe_dest  = 6'b000010;      
                        read_resp_tx_mod_dest   = 5'b00000;
                        read_resp_tx_c_routing  = 0;
                        read_resp_tx_pld_header = 17'b00100101000000000;
                        read_resp_tx_pld_address    = 32'h0001;   
                        read_resp_tx_pld_data   = 256;
                                                        //count = 3'b111; 
                        reg_write_en    =   1'b0;
                        end
                        next_count = count - 3'b001;
                    end
                end
Wenn mehr Informationen über den spezifischen Kontext benötigt werden, fragen Sie bitte nach.
In manchen Fällen ist es notwendig, die Zustandsmaschine auf diese Weise in zwei Teile zu teilen, aber es macht die Dinge hier zu kompliziert, besonders da Sie neu sind. Schauen Sie sich diese Antwort an, die ich geschrieben habe, sie enthält ein Beispiel für eine vollständige Zustandsmaschine mit Zähler: electronic.stackexchange.com/questions/57035/… Ich empfehle dringend, kombinatorische Always-Blöcke zu vermeiden (die mit dem (*) in der Empfindlichkeit Liste), bis Sie sich daran gewöhnt haben, nur mit sequentiellen Blöcken zu codieren und ein Gefühl für "diesen Code -> dieses Verhalten" bekommen
@stanri Vielen Dank für die Antwort. Ich werde die von Ihnen erwähnte Antwort durchgehen. Ich werde hier zurückschreiben, wenn ich weitere Erläuterungen benötige.
@stanri Wenn Sie sagen, dass ein Signal nur von einem richtigen Prozess angesteuert werden muss, meinen Sie Reg oder Wire?
Mir ist klar, dass der Link, den ich Ihnen gegeben habe, VHDL war, aber das Prinzip gilt immer noch. regs werden in sequentiellen Prozessblöcken verwendet, also spreche ich von a regin Verilog. (Übrigens, ist SystemVerilog eine Option für Sie zum Lernen? SystemVerilog hebt unter anderem die Registrierungs-/Kabelunterscheidung auf.)
Es ist eine Option, aber leider nicht für das Projekt, an dem ich arbeite.
Ich stehe auch vor dem Dilemma, wie ich die Schreibfreigabe hoch halten kann, bis die Zählung auf Null erreicht ist.
Warum haben Sie reg_write_en zweimal im if(dnoc_packet_ready)-Block? Was versuchst du zu erreichen?
Möchten Sie, dass reg_write_enable die ganze Zeit hoch ist, während es heruntergezählt wird (die ganze Zeit von 7 bis 0) oder nur für den einen Taktzyklus, in dem es bei 0 ist?

Antworten (2)

Es gibt ein paar Dinge, auf die Sie in Ihrem Code hinweisen sollten.

1) Sie fahren die Signale reg_read_enund reg_write_enin beiden Blöcken immer.

always@(posedge clk_i or negedge resetn_i) begin
if (resetn_i == 1'b0) begin
    reg_read_en         <= 1'b0; <=
    reg_write_en        <= 1'b0; <=
    count               <= 3'b111;
    next_count          <= 3'b111;  
    state               <= S_INITIAL;

end else begin
    state               <= next_state;
    count               <= next_count;
end

Und

always@(*) begin
next_state  = state;
next_count  = count;
case(state)
    S_INITIAL: begin //not enabled
        if(enable_i == 1'b1) begin                  
            next_state = S_ENABLED;
        end
        reg_read_en         <= 1'b0; <==
        reg_write_en        <= 1'b0; <==
    end 

Dies ist nicht synthetisierbar und würde wahrscheinlich auch von einigen Simulationstools nicht zugelassen.

2) Sie schließen Latches in Ihren kombinatorischen Blöcken ab:

always@(*) begin
next_state  = state;
next_count  = count;
case(state)
    S_INITIAL: begin //not enabled
        if(enable_i == 1'b1) begin         <==         
            next_state = S_ENABLED;        <==
        end                                <==
        reg_read_en         <= 1'b0;
        reg_write_en        <= 1'b0;
    end 

Die markierte Aussage bedarf in jedem Fall ifeiner Entsprechung . elseSie müssen alle möglichen Optionen abdecken.

Aus diesem Grund habe ich in meinem Kommentar vorgeschlagen, dass Sie einen einzigen sequentiellen Block verwenden, der diese beiden Probleme behebt und alles viel einfacher zu verfolgen macht.

Hier ist ein Beispiel für die Kombination:

always@(posedge clk_i or negedge resetn_i) begin
  if (resetn_i == 1'b0) begin
     reg_read_en              <= 1'b0;
     reg_write_en             <= 1'b0;
     count                    <= 3'b111;
     state                    <= S_INITIAL;

     // I was trained to give everything a reset value
     // But that's not really necessary and is more a matter of style
     // as long as it gets a value when it needs one.
     // including this here for completeness
     read_resp_tx_data_size   <= 3'b000;
     read_resp_tx_qpe_dest    <= 6'b000000;
     read_resp_tx_mod_dest    <= 5'b00000;
     read_resp_tx_c_routing   <= 0;
     read_resp_tx_pld_header  <= 17'b00000000000000000;
     read_resp_tx_pld_address <= 32'h0000;
     read_resp_tx_pld_data    <= 0;

  end
  else begin

     case(state)
       S_INITIAL: begin //not enabled
          if(enable_i == 1'b1) begin
             state <= S_ENABLED;

             // set count to the initial value here
             // this way, you re-set it every time you switch to enabled
             count      <= 3'b111;


             // If there's anything else you want to setup, here's a good place to do it...

          end
          else begin
             state <= S_INITIAL;

          end

          reg_read_en  <= 1'b0;
          reg_write_en <= 1'b0;

       end

       S_ENABLED: begin
          if(enable_i == 1'b0) begin
             state <= S_INITIAL;

             // here's where you would clear any signals that you want to clear before going back to the initial state


          end
          else begin
             state <= S_ENABLED;

          end

          case(mode_i)
            M_PASSIVE:begin
               reg_read_en <= dnoc_packet_ready;

               //pkt_type  =   dnoc_packet_pld_header[]
               if(dnoc_packet_ready) begin
                  reg_read_en <= 1'b0;

                  if(count == 3'b000) begin
                     reg_write_en             <= 1'b1;
                     read_resp_tx_data_size   <= 3'b001;
                     read_resp_tx_qpe_dest    <= 6'b000010;
                     read_resp_tx_mod_dest    <= 5'b00000;
                     read_resp_tx_c_routing   <= 0;
                     read_resp_tx_pld_header  <= 17'b00100101000000000;
                     read_resp_tx_pld_address <= 32'h0001;
                     read_resp_tx_pld_data    <= 256;

                     // you can reset count here to 7 if you want it to start again
                     count                    <= 3'b111;


                  end
                  else begin

                     // this will make reg_write_en valid only when count is 0
                     // and it will be disabled the rest of the time.
                     // if you want it valid all the time, you can set this to 1 and clear it elsewhere.
                     reg_write_en <= 1'b0;



                     // this stops the counter from underflowing
                     // otherwise, when it's 0 it will underflow to 111.
                     // Unless that's what you want, of course.
                     count        <= count - 3'b001;

                  end

               end
            end
            // other cases...
          endcase

       endcase
  end
end
Es gibt abgeleitete Latches (wie alle read_resp_tx_*), aber next_statekeiner davon. Beachten Sie, dass das next_state = state;einen Standardwert bereitstellt und damit die Bedingungen für den Synthesizer erfüllt, um es als kombinatorische Logik zu sehen. Ein Latch wird abgeleitet, wenn es mindestens einen Pfad gibt, dem die Variable nicht innerhalb des gesamten always @*Blocks zugewiesen wird.
Danke @Greg für die ausführliche Antwort. Ich werde das in meiner Simulation ausprobieren und sehen, wie es funktioniert. Ich habe noch eine Frage. Nehmen wir an, ich halte das am Laufen und sende alle N Taktzyklen Daten. Wenn ich die tx_pld-Adresse und die Daten in der if-Anweisung ändere, wo die Zählung durchfällt, würde sie sich in einem einzigen Taktzyklus widerspiegeln? Ist es das, was posedge clk an einer bestimmten clokc-Kante bedeutet?
@Greg guter Punkt, ich habe diese Zeile verpasst.
Danke @Greg für die Hilfe. Für mich geht das. Noch wichtiger ist, vielen Dank für die schöne Erklärung des Konzepts der abgeleiteten Latches und wie man sie vermeidet.
Nur um die Diskussion zu ergänzen, ich möchte eine Verzögerung von einem Taktzyklus, wenn ich mein Schreibfreigabesignal aktiviere, um es zu löschen. Wie kann ich das im Kombinationsblock machen? Ich habe einige Nachforschungen angestellt und dies kann durch die Einführung eines Dummy-Zustands erfolgen, zu dem ich springen und dann zu meinem ursprünglichen Zustand zurückkehren kann. Würde dies eine Verzögerung von einem Taktzyklus einführen? Wie im M_PASSIVE-Fall, wenn ich ein Paket erhalte, möchte ich es in meinen FIFO zurückschreiben, und daher muss die Schreibfreigabe für genau einen Taktzyklus hoch sein S_ONECLK_DELAY: next_state <= S_ENABLED;. Aber schreiben enb ist immer hoch.. Übersehe ich etwas?

Du bist ganz in der Nähe. Der Fehler ist, dass 'next_count' nur im kombinatorischen Always @(*) -Block aktualisiert wird und 'count' nur im Register/getakteten Teil immer @(posedge clk_i ... aktualisiert wird.

Entfernen Sie also next_count <= 3'b111;

Ich kann den Rest Ihres Codes nicht sehen, also fangen Sie damit an.

Sie haben nicht gesagt, wie viele Zyklen Sie warten müssen: 7 oder 8? Ich werde ehrlich sein. Meistens schreibe ich Code, der mich zu 99 % weiterbringt. Wenn die Daten nach 7 oder 8 Zyklen gespeichert sind, sind Details so einfach zu lösen, dass ich oft erst die Simulation laufen lasse und dann eine +1 oder -1 Korrektur mache.


Nachbearbeitung nach neuem Code:
Ich kann Ihre Zustandsmaschine nicht für Sie debuggen, aber ich kann einige Bemerkungen machen. Sie setzen reg_write_en und löschen es einige Zeilen später wieder.

Ich gehe davon aus, dass dies wirklich in der Hardware funktionieren soll, nicht nur in der Simulation. Wenn dies der Fall ist, erzeugt Ihr Code viele Latches. Sie werden darüber erst benachrichtigt, wenn Sie den Code synthetisieren. Zum Beispiel diese:

read_resp_tx_qpe_dest      
read_resp_tx_mod_dest  
read_resp_tx_c_routing 
reg_read_en
reg_write_en

In Ihrem kombinatorischen Abschnitt immer @(*) müssen Sie jeder Variablen in jedem Zustand einen Wert zuweisen. Wenn nicht, bekommst du Riegel. Eine Möglichkeit, dies zu tun, ist das, was Sie mit next_state und next_count tun : Legen Sie oben einen Standardwert fest. Aber dann ist es auch eine gute Praxis zu dokumentieren, dass es sich um einen Standardwert handelt, der später geändert wird.

// Defaults:
next_state  = state;
next_count  = count;
reg_write_en = 1'b0; 
reg_read_en = 1'b0;

Die ersten beiden müssen gleich bleiben, wenn sie nicht berührt werden.
Die anderen beiden sind Beispiele für Variablen, die nur in einem oder zwei Zuständen gesetzt sind und überall sonst Null sind.
Was Ihre 'read_resp_tx ...'-Variablen betrifft: Ich weiß nicht, was sonst noch mit ihnen gemacht wird, also müssen Sie entscheiden, wie Sie damit umgehen.

Wie Sie Ihren Code in einen Register- und einen kombinatorischen Abschnitt aufteilen: Das liegt bei Ihnen. Womit Sie sich wohlfühlen. Ich neige dazu, es oft zu tun, es sei denn, es ist eine triviale Zustandsmaschine. Ich habe Code von einer sehr großen IP-Firma gesehen, die Millionen von CPU-Kernen auf der ganzen Welt bereitstellt und sie überall verwenden . Ich vermute, dass es in ihrem Coding Style Guide obligatorisch ist.

Danke für die Antwort. Ich habe das next_count <= 3'b111aus dem sequentiellen Block entfernt. Ich habe meine Frage so bearbeitet, dass sie den vollständigen Code dessen enthält, was ich versuche zu tun. So können Sie besser verstehen, wie Regs aktualisiert werden.
Ich habe festgestellt, dass die Aufteilung der Register/kombinatorischen Zustandsmaschine nützlich ist, wenn Sie Zustandsübergänge erkennen und abarbeiten möchten if ((state != next_state) && (next_state = MY_STATE)). Das bedeutet, dass Sie statt der Bedingung, die die Zustandsänderung auslöst, die Zustandsänderung selbst erkennen , wodurch Sie die Zustandsänderungsbedingungen nur an einer Stelle ändern können. Register-Only-Zustandsmaschinen erschweren dies erheblich. Im Nur-Register-Fall müssen Sie den Zustand registrieren und ausführen state != state_z, was um 1cc vom Übergang verzögert wird.
Aber im Fall des OP müssen sie keine Zustandsübergänge abarbeiten und sie sind Anfänger, daher ist eine Nur-Register-Methode viel sinnvoller (keine abgeleiteten Latches usw.).