n-Bit-Schieberegister (Serial Out) in VHDL

Ich erstelle ein n-Bit-Schieberegister. Wenn das Freigabesignal hoch ist, möchte ich, dass das Schieberegister n-mal verschoben wird, unabhängig davon, ob die Freigabe weiterhin hoch oder niedrig ist. Ich habe eine for-Schleife eingefügt, um n-mal innerhalb eines Prozesses zu verschieben. Mein Code ist unten angegeben.

Ich glaube nicht, dass die for-Schleife funktioniert, da die Verschiebung nicht auf n-mal beschränkt ist. Wo gehe ich falsch?

library ieee;
use ieee.std_logic_1164.all;

entity SReg is

  generic (
     n  : integer := 4 
    );

  port(
    clk:              in std_logic; 
    reset:            in std_logic; 
    enable:           in std_logic;    --enables shifting
    parallel_in:      in std_logic_vector(n-1 downto 0);
    s_in:             in std_logic;    --serial input
    s_out:            out std_logic   --serial output
    );
end SReg;

architecture behavioral of SReg is
  signal temp_reg: std_logic_vector(n-1 downto 0) := (Others => '0');
begin
  process (clk,reset)
  begin
    if (reset = '1') then
      temp_reg <= parallel_in;   
    elsif (clk'event and clk='1') then
      if (enable ='1') then
        --shifting n number of bits
        for i in 0 to n-1 loop
          s_out <= temp_reg(0);
          temp_reg <= s_in & temp_reg(n-1 downto 1);
        end loop;
      end if;
    end if;
  end process;
end behavioral;

Antworten (2)

In VHDL wird eine for-Schleife in Nullzeit ausgeführt . Das heißt, anstatt zwischen jeder Iteration einen Taktzyklus zu warten, wird die gesamte Schleife innerhalb eines Taktzyklus durchlaufen, wobei am Ende nur das Endergebnis der Schleife angezeigt wird. Dies ist, was in Ihrem Code passiert. Die gesamte Schleife wird in einem einzigen Taktzyklus ausgeführt.

Was Sie wirklich wollen, ist eine Schleife, bei der jede Iteration an einer neuen Taktflanke auftritt. Dadurch kann s_in in jedem Taktzyklus aus s_out verschoben werden.

Das Durchführen einer Schleife, bei der jede Iteration an einer Taktflanke auftritt, erfordert keinen for-Schleifenbefehl, sondern nutzt stattdessen die Sensitivitätsliste des Prozesses. Hier ist wie:

Jedes Mal, wenn sich eines der Signale auf der Empfindlichkeitsliste (in diesem Fall „clk, reset“) ändert, wird ein Prozess ausgelöst. Das bedeutet, dass der Prozess bereits bei jedem Taktzyklus eine Schleife durchläuft (wenn sich eine Uhr in der Sensitivitätsliste befindet). Sie können dies zu Ihrem Vorteil nutzen, um eine Operation vom Typ for-Schleife auszuführen, bei der jede Iteration der Schleife in einem Taktzyklus erfolgt.

Zuerst brauchen Sie einen Zähler:

process(clk,reset)
    variable shift_counter: integer := 0;
begin

shift_counterverfolgt, wie viele Iterationen (oder Verschiebungen) bisher stattgefunden haben. Sie werden mit vergleichen shift_counter, um n-1zu sehen, ob Sie schon fertig sind.

Als nächstes könnte es eine gute Idee sein, an die Zustände zu denken, in denen sich Ihr Prozess befinden wird. Vielleicht ein Wartezustand, wenn der Prozess nicht wechselt, und ein Wechselstatus, wenn er es ist.

Die Zustandssignaldefinition:

 TYPE POSSIBLE_STATES IS (waiting, shifting);
 signal state : POSSIBLE_STATES;

Im eigentlichen Ablauf:

case state is
   when waiting =>

Okay, was passiert also, wenn wir auf eine Freigabe warten? Es wäre eine gute Idee, alle (gesteuerten) Variablen auf einen bekannten Wert zu setzen. Das bedeutet, dass vielleicht so etwas eine gute Idee ist:

shift_counter := 0;
temp_reg <= parallel_in;
s_out <= '0';

Dies ist nützlich, da Sie dann genau wissen, was Ihre Signalwerte sind, wenn die Freigabe hoch geht. Außerdem können Sie am Ende der Schicht den Zustand wieder auf "Wartend" ändern, um sich wieder für die Freigabe bereit zu machen.

Was wird also einen Zustandswechsel vom Warten zum Verschieben auslösen? Das ist einfach:

if(enable = '1') then
    state <= shifting;
else
    state <= waiting;
end if;

Okay, also nächster Zustand. Verschiebung.

Zuerst wollen wir den Verschiebungszähler inkrementieren und die eigentliche Verschiebung durchführen:

when shifting =>
    shift_counter := shift_counter + 1;
    s_out <= temp_reg(0);
    temp_reg <= s_in & temp_reg(n-1 downto 1);

Und dann auch erkennen, wann das Schalten beendet ist, um den Schaltzustand zu verlassen und zum Warten zurückzukehren:

if (shift_counter >= n-1) then
    state <= waiting;
else
    state <= shifting;
end if; 

Und das ist es!

Beachten Sie im folgenden Codeabschnitt, dass der „Zurücksetzen“-Zustand und der „Warten“-Zustand unterschiedlich sind. Dies ist nützlich, da das asynchrone Zurücksetzen im Allgemeinen nur beim Start auftritt und während dieser Zeit keine Datenverarbeitung erwartet wird. Indem wir den temp_reg <= parallel_inin den Wartezustand (außerhalb des asynchronen Resets) versetzen, ermöglichen wir, dass die Modulsteuerung parallel_inkorrekt startet, ohne dass während des Resets Daten gesendet werden müssen. Außerdem kann jetzt bei Bedarf in den Wartezustand eingetreten werden, ohne dass ein asynchroner Reset durchgeführt werden muss.

Beachten Sie auch, dass ich in meinem Prozess nur 3 Signale (4, die die Variable zählen) und nur diese Signale ansteuere. Wenn ein Signal in einem Prozess getrieben wird, sollte es nirgendwo anders als in diesem Prozess getrieben werden. Nicht außerhalb des Prozesses, nicht in einem anderen Prozess. Ein Signal wird innerhalb eines Prozesses und nur eines Prozesses getrieben. Sie können das Signal an anderen Stellen mit anderen Signalen vergleichen (if-Anweisungen und dergleichen), aber geben Sie dem Signal nirgendwo einen Wert, außer in einem Prozess. Und im Allgemeinen wird es im Rücksetzabschnitt definiert und dann, wo immer es notwendig ist, im eigentlichen Prozess. Aber nur 1 Prozess. Wenn mir das gesagt worden wäre, hätte es mir beim Lernen viel Zeit gespart.

Hier ist der gesamte Code in einem Stück:

library ieee; 

use ieee.std_logic_1164.all;

entity SReg is

generic ( n : integer := 4);

port( clk: in std_logic; 
      reset: in std_logic; 
      enable: in std_logic; --enables shifting 
      parallel_in: in std_logic_vector(n-1 downto 0); 
      s_in: in std_logic; --serial input 
      s_out: out std_logic --serial output

);
end SReg;

architecture behavioral of SReg is

signal temp_reg: std_logic_vector(n-1 downto 0) := (Others => '0');
TYPE POSSIBLE_STATES IS (waiting, shifting);
signal state : POSSIBLE_STATES;
begin

process(clk,reset)
    variable shift_counter: integer := 0;
begin

   if(reset = '1') then
      temp_reg <= (others => '0');   
      state <= waiting;
      shift_counter := 0;
   elsif(clk'event and clk='1') then
        case state is
            when waiting =>
                shift_counter := 0;
                temp_reg <= parallel_in;
                s_out <= '0';
                if(enable = '1') then
                    state <= shifting;
                else
                    state <= waiting;
                end if;
            when shifting =>
                shift_counter := shift_counter + 1;
                s_out <= temp_reg(0);
                temp_reg <= s_in & temp_reg(n-1 downto 1);
                if (shift_counter >= n-1) then
                    state <= waiting;
                else
                    state <= shifting;
                end if; 
        end case;
    end if;
end process;

end behavioral;
Stanri, das hat mich viel gelehrt. Ich danke Ihnen aufrichtig für Ihre Hilfe.
If a signal is driven in one process, it shouldn't be driven anywhere else but that process.Das macht sehr viel Sinn. Allzu oft werde ich mit dem Fehler "mehrere Treiber" konfrontiert! Was genau wird s_inhier gemacht? Dieses Schieberegister scheint die parallelen Daten zwischenzuspeichern und dann seriell herauszuschieben.
@ShashankVM, Im Fall der Frage liefert parallel_in den Rücksetzwert des internen Schieberegisters. Also behielt ich es mit dem gleichen Namen und der gleichen Funktion. Es ist ansonsten unbenutzt, OP muss erklären, wofür es ursprünglich gedacht war. Ich stimme zu, dass ein SISO-Register ansonsten streng seriell sein sollte.
@ShashankVM Ich habe diese Antwort vor 7 Jahren mit der Absicht geschrieben, das Denken hinter der Verwendung einer Zustandsmaschine zur Lösung solcher Probleme zu demonstrieren. Es soll ein allgemeines Konzept aufgezeigt werden. Das OP kann es gerne an seine Bedürfnisse anpassen. Meine Programmierpraktiken haben sich seitdem geändert, und ich bin sicher, dass es mehrere Dinge gibt, die jemand falsch machen könnte, da ich es in meiner Freizeit geschrieben habe und nicht als strenge akademische Übung. Sie können gerne eine Antwort schreiben, wenn Sie meinen, dass meine unzureichend ist.
Bitte lesen Sie eine Diskussion Ihrer Antwort auf Meta electronic.meta.stackexchange.com/q/7298/238188

Die Antwort von @stanri ist beeindruckend gründlich und ziemlich genau ... wenn ich die erste Aussage zusammenfassen / verdeutlichen darf, drückt die "for" -Anweisung in einer HDL einfach "syntaktische Replikation" aus, nicht "sequentielle Ausführung".

Das heißt, es erzeugt einfach mehr Hardwareelemente (Gatter) und informiert den Prozessfluss nicht. Ich würde sagen, die Schleife wird zur Ausarbeitungszeit (Kompilierung) erweitert, nicht dass sie "in Nullzeit ausgeführt" wird, schließlich wird es zur Laufzeit immer noch eine Ausbreitungsverzögerung durch die vom "for"-Konstrukt generierten Elemente geben.

Beginnen Sie nicht mit dem Schreiben von VHDL-Code, beginnen Sie mit dem Zeichnen von Logikschemata (zumindest auf einer gewissen Abstraktionsebene). Letztendlich ist HDL nur eine textbasierte Möglichkeit, den Inhalt von Logikschemata auszudrücken.

Obwohl Menschen unterschiedliche Herangehensweisen an digitales Design haben, ist Ihre Beschreibung der for-Anweisung nicht ganz korrekt. Wenn Sie die Generate-Anweisung betrachten, ist Ihre Beschreibung wahr, aber wenn Sie die Loop-Anweisung innerhalb eines Prozesses betrachten, handelt es sich um sequentiellen Code. Wenn Sie Schleifensignalzuweisungen zum selben Signal enthalten, wird die vorherige Zuweisung durch die nächste überschrieben, aber wenn Sie mit Variablen umgehen, verhält sie sich wie normaler sequentieller Code.