mikrocontroller.net

Forum: FPGA, VHDL & Co. VHDL - Takt für verschiedene CPU-Komponenten verzögern


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich versuche gerade eine Ein-Register-CPU in VHDL zu erstellen. Ich 
möchte gerne in einem Takt-Zyklus Daten aus dem Akkumulator im RAM 
platzieren.

Bei steigender Flanke wird die Instruktion, auf die der Program Counter 
zeigt, auf die Leitung gelegt.
Ebenfalls bei steigender Flanke liest der Decoder die aktuell anliegende 
Instruktion ein, verarbeitet sie und setzt entsprechende Steuersignale. 
Dabei wird dem Akkumulator mitgeteilt, dass er den Inhalt auf die 
Leitung zum RAM legen muss. Ebenso wird das "Write-Enable" für den RAM 
gesetzt.
Bei fallender Flanke liest der RAM die Steuersignale ein und speichert 
ggf. den anliegenden Wert. Ebenso wird zuletzt der Program Counter 
hochgezählt.

Allerdings passiert das ja im FPGA mehr oder weniger gleichzeitig, 
obwohl es in dieser Reihenfolge passieren muss. Gibt es eine 
Möglichkeit, den Takt für die verschiedenen Komponenten zu verzögern? 
Macht das überhaupt Sinn?
Falls nein - wie würde man das sonst lösen?

: Bearbeitet durch User
Autor: Strubi (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Such doch mal nach "MIPS pipelining", da findest du eine Menge 
Unterrichtsmaterial. Vom MIPS-Konzept kann man im allgemeinen nicht 
allzuweit abweichen, was FPGA-CPUs angeht, die üblichen 
Implementierungen sind deswegen mehr oder weniger alle MIPS-ähnlich.

Grad mal selber gegoogelt, da gibts recht witzige Interpretationen:

https://cs.stanford.edu/people/eroberts/courses/soco/projects/risc/pipelining/

Aber mindestens zwei Register werden's dann schon, zum Akku brauchst du 
ja noch einen Adress-Zeiger.

Autor: Markus F. (mfro)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:
> Falls nein - wie würde man das sonst lösen?

State Machine.

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Strubi schrieb:
> Such doch mal nach "MIPS pipelining", da findest du eine Menge
> Unterrichtsmaterial.

Also komplett ohne Pipeline kommt man in VHDL nicht aus (auch wenn es in 
echter Hardware ohne Pipeline funktoniert)?

Strubi schrieb:
> zum Akku brauchst du
> ja noch einen Adress-Zeiger.

Meinst du damit den Program-Counter?

Autor: Lothar M. (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Max M. schrieb:
> Also komplett ohne Pipeline kommt man in VHDL nicht aus (auch wenn es in
> echter Hardware ohne Pipeline funktoniert)?
Mit VHDL beschreibst du "echte Hardware"...

Wenn du "in echter Hardware" mit steigenden und fallenden Flanken eines 
Taktes arbeitest, dann verdoppelst du lediglich die Taktfrequenz.
Wenn du mit phasenverschobenen Takten arbeitest, dann vervielfachst du 
die Taktfrequenz auf den Kehrwert der kürzesten Phasenverschiebung.

Echte synthetisierbare und portierbare CPUs arbeiten heute deshalb 
intern einfach gleich mit ein und dem selben vervielfachen Takt.

Und natürlich wird in echter Hardware heutzutage Pipelining zur 
Beschleunigung genommen. Denn dann arbeitet der Prozessor gleichzeitig 
mit relativ niedriger Taktfrequenz (z.B. 100MHz) an z.B. 5 Befehlen und 
ist damit so schnell wie deine CPU mit 500MHz.

Autor: Weltbester FPGA-Pongo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lothar M. schrieb:
> Und natürlich wird in echter Hardware heutzutage Pipelining zur
> Beschleunigung genommen.

Das ist aber nicht eigentlich das, was wir im FPGA mit pipeling 
bezeichnen. Das ist eher ein Verschachteltes Arbeiten wie ein 
DDR-Controller und aus Sicht der Datenzeit paralleles Arbeiten, weil in 
einem äussere Takt mehr als eine Aktion geschieht. Es braucht dazu auch 
parallele Hardware.

Pipelining passiert nicht auf paralleler Hardware sondern auf 
sequenzieller.

Autor: Strubi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Also komplett ohne Pipeline kommt man in VHDL nicht aus (auch wenn es in
> echter Hardware ohne Pipeline funktoniert)?

Doch, kommst du schon, wenn du oben erwähnte State-Machine 
implementierst. Dann liegt aber unter Umständen recht viel Logik brach. 
Dir ist auch sicher klar, dass Fetch, Decode, Execute und Write-Back 
nicht in einem Taktzyklus funktionieren kann (was die genau bedeuten, 
findest du typischerweise in der Literatur).
Du kannst natürlich schon Logik bauen, die gleich ein angelegtes Datum 
mit einem Wert irgendwie verwurstet, und im nächsten Takt ausgibt, dann 
handelst du dir aber massiv komplexe Logik und einen Flaschenhals ein, 
der dir den Systemtakt nach unten zwingt. Sowohl im FPGA als auch in 
realer HW.

>
> Strubi schrieb:
>> zum Akku brauchst du
>> ja noch einen Adress-Zeiger.
>
> Meinst du damit den Program-Counter?

Nee, der ist ja nur beim Fetch relevant. Du willst ja irgendwo anders 
Daten holen/speichern, dafür brauchste den Zeiger. Dafür kannst du 
natürlich ein (virtuelles) "top of stack" register (der Inhalt der 
Adresse, die im Stack-Pointer steht) verwenden. Dann hast du eine 
Stack-Machine, was in der Implementation fast auf dasselbe rausläuft wie 
eine Ein-Register-CPU.

Lothar M. schrieb:
> Denn dann arbeitet der Prozessor gleichzeitig
> mit relativ niedriger Taktfrequenz (z.B. 100MHz) an z.B. 5 Befehlen und
> ist damit so schnell wie deine CPU mit 500MHz.

Das könnte sich missverstehen lassen, d.h. beisst sich mit echt 
paralleler Ausführung, also als solcher definierter Opcodes (wie bei 
manchen DSPs), oder der 'superscalaren' Variante, die unter Umständen 
mehrere sequenzielle Opcodes gleichzeitig abarbeiten kann.
Die Pipeline definiert ja eigentlich im einfachsten Fall nur eine 
Verkettung der atomaren Funktionen Fetch/Decode, Execute, usw. Sobald 
was parallel verarbeitet wird, gabelt sich ne Pipe oder es gibt mehrere 
vernetzte Instanzen davon, das wär etwas kompliziert fürn Anfang..

Das Ding mit der dreckigen Wäsche aus dem obigen Link beschreibt's 
eigentlich recht nett. Oder die Heinzelmännchen, die den Eimer 
weiterreichen..

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Strubi schrieb:
> Doch, kommst du schon, wenn du oben erwähnte State-Machine
> implementierst.

Im Studium haben wir das Signal mit UND und ODER Gattern verzögert, ich 
weiß nicht, in wiefern das mit VHDL möglich ist? Die CPU, die ich 
versuche zu bauen, gibt es tatsächlich schon
(http://pdf.eepw.com. 
cn/420090717/0c6b3d3419f57d26d6057c8fac26482f.pdf).
Da sehe ich keinen Hinweis auf Pipeline nur "Full CMOS static design".

Strubi schrieb:
> Du willst ja irgendwo anders
> Daten holen/speichern

Das sind in dem Fall tatsächlich 30 General Purpose Registers, die als 
RAM verwendet werden. Die 512 Words an Flash bilde ich in einem Array 
ab:
  type ROM_type is array (0 to 511) of unsigned(10 downto 0);
  constant rom_data: ROM_type := (0 => "10010000110", 1 => "11110100000"....);

: Bearbeitet durch User
Autor: Lothar M. (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:
> Im Studium haben wir das Signal mit UND und ODER Gattern verzögert
Ja, das ist die lange Schreibweise für "Murks".

>  ich weiß nicht, in wiefern das mit VHDL möglich ist?
Es ist möglich. Aber du bekommst das nicht reproduzierbar in ein FPGA. 
Und darum geht es letztendlich doch?

> Da sehe ich keinen Hinweis auf Pipeline nur "Full CMOS static design".
Ein vollstatisches Design darf aber keine solchen hingebastelten Timings 
enthalten. Und der von dir verlinkte Prozessor hat, soweit ich das sehe, 
sowas auch nicht drin.
Das ist ein vollkommen simples alltägliches synchrones Design.

Als Denkanstoß: letztendlich ist dieser Prozessor nur eine FSM, die vom 
ROM gesteuert wird.

: Bearbeitet durch Moderator
Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lothar M. schrieb:
> Und darum geht es letztendlich doch?

Jop

Lothar M. schrieb:
> Ein vollstatisches Design darf aber keine solchen hingebastelten Timings
> enthalten.

Bekommt jedes Register dann seinen separaten Time-Slot? Ist der 
grundlegende Ansatz mit Instruktions-Register, Decoder + Flags setzen, 
Akkumulator einstellen und RAM lesen / schreiben richtig?

Lothar M. schrieb:
> letztendlich ist dieser Prozessor nur eine FSM, die vom
> ROM gesteuert wird.

Ist FSM = Stack Machine? Meinst du so etwas z.B. für 5+10*3?
PUSH 10
PUSH 3
MUL
PUSH 5
ADD

Auf den ersten Blick erkenne ich da keine Analogien zu dem Befehlssatz 
der CPU, die ich nachbauen möchte.

: Bearbeitet durch User
Autor: Strubi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
FSM = Finite State Machine

Hab mir jetzt deine Wunsch-CPU nicht angesehen, aber wenn Du mehr als 
ein Register hast, ist es keine Stack-Maschine.
Ich gehe schwer davon aus, dass diese CPU irgend eine Art Pipelining 
implementiert. Das ist heute eine Selbstverständlichkeit, die kaum im 
Datenblatt erwähnt wird. Wie gesagt, du lässt sonst Logik u.U. zeitweise 
brachliegen oder es wird irgendwas gemultiplext (wegen potentieller 
Logik-Verstopfung eine ungeliebter Effekt). Siehe z.B. Zealot/ZPU. Dafür 
ist es einfach zu implementieren, da kaum Konflikte (siehe "MIPS 
Hazards") auftreten können.

Fang doch einfach mal an die 'Stages' (Befehls-Verarbeitungsstufen?) 
gemäss deinem Ansatz als einfache FSM zu implementieren. Nächste Übung: 
Implementiere eine Pipeline, identifiziere Konflikte, wie Klassiker: 
Gerade (im vorherigen Zyklus) geschriebenen Wert zurücklesen.

Autor: Max M. (maxmicr)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Strubi schrieb:
> Dafür
> ist es einfach zu implementieren, da kaum Konflikte (siehe "MIPS
> Hazards") auftreten können.

Das ist erstmal mein Ziel: Die CPU so einfach wie möglich implementiert 
bekommen, ohne Pipelining-Mechanismen.

Ich hab gerade ein paar seltsame Effekte während der Simulation.

Ich hab dem Ablauf nun in Zustände aufgeteilt, der aktuelle Zustand wird 
in einem separaten Register verwaltet.

Zustand *0*: Der Program Counter passt seinen Wert an
Zustand *1*: Der passende Befehl wird geladen
Zustand *2*: Befehl wird decodiert und Steuersignale werden gesetzt
Zustand *3*: Daten werden ggf. aus dem RAM geladen
Zustand *4*: Der Akkumulator (bzw. die ALU) führt Berechnungen durch
Zustand *5*: Daten werden ggf. ins RAM zurückgeschrieben (vom Akku aus)
Zustand *5*: Der Stack speichert ggf. die Rücksprungadresse, falls ein 
JUMP vorliegt

Mein Program Counter:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
USE IEEE.numeric_std.all;  

entity Program_Counter is

  port (
  
    c0: in std_logic;
    pc: out unsigned(8 downto 0);
    is_jump: in std_logic;
    jump_addr: in unsigned(8 downto 0);
    is_ret: in std_logic;
    ret_addr: in unsigned(8 downto 0);
    state: in unsigned(2 downto 0)
    
  );
  
end Program_Counter;

architecture Behavioral of Program_Counter is

  signal pc_int : unsigned(8 downto 0) := "000000000";
  
  begin
  process(c0, state)
  begin
  
    if(falling_edge(c0) and state = "000") then
      if(is_jump = '1') then
        pc_int <= jump_addr;
      elsif(is_ret = '1') then
        pc_int <= ret_addr;
      else
        pc_int <= pc_int + 1;
      end if;
      pc <= pc_int;
    end if;
  
  end process;

end Behavioral;

Die Leitung pc ist mit der Top-Level-Entity verbunden.

Der Akkumulator:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
USE ieee.numeric_std.ALL;  

entity W_Reg is

  port (

    c0: in std_logic;
    read_w: in std_logic;
    write_w: in std_logic;
    place_immediate: in std_logic;
    
    is_add: in std_logic;
    
    reg_write_data: out unsigned(3 downto 0);
    reg_read_data: in unsigned(3 downto 0);
    
    immediate: in unsigned(3 downto 0);
    w_reg_top: out unsigned(3 downto 0);
    state: in unsigned(2 downto 0)
  );
  
  
end W_Reg;

architecture Behavioral of W_Reg is

  signal w_content: unsigned(3 downto 0) := "0000";

  begin
  process(c0, state)
  begin
  
    if(falling_edge(c0) and state = "100") then
      if(read_w = '1') then
        reg_write_data <= w_content;
      elsif(write_w = '1') then
        if(place_immediate = '1') then
          w_content <= immediate;
        elsif(is_add = '1') then
          w_content <= w_content + reg_read_data;
        else
          w_content <= reg_read_data;
        end if;
      end if;
    
      w_reg_top <= w_content;
      
    end if;
  
  end process;
  
end Behavioral;

Auch hier ist die Leitung w_reg_top mit der Top-Leve-Entity verbunden.

Der in der angehängten Simulation durchgeführte Befehl lädt den Wert 
"0x1" in den Akkumulator.

Nun das seltsame Verhalten: Der Wert des Program Counters wird 
flankengenau geupdated, der Inhalt vom W-Register allerdings nicht, 
sonder erst zum nächsten Befehl (wenn dieser den entsprechenden Zustand 
erreicht hat). Ich würde also erwarten, dass w_reg_top in der 
Simulation den Wert 1 hat.
Warum ist das so?

: Bearbeitet durch User
Autor: Achim S. (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Max M. schrieb:
> Nun das seltsame Verhalten: Der Wert des Program Counters wird
> flankengenau geupdated, der Inhalt vom W-Register allerdings nicht,
> sonder erst zum nächsten Befehl (wenn dieser den entsprechenden Zustand
> erreicht hat)

ohne deinen Code vollständig durchgegangen zu sein: in einem Taktzyklus 
weißt du w_content einen neuen Inhalt zu. Diese Zuweisung wird aber erst 
zum Ende des Prozesses wirksam.

Deine zusätzliche Zuweisung

   w_reg_top <= w_content

bringt w_reg_top auf den Wert, den w_content zu Beginn des Prozesses 
hatte (nicht auf den Wert, der während des Prozesses zugewiesen wurde). 
Der neue Wert von w_content macht sich erst im folgenden Taktzyklus 
bemerkbar.

Autor: Lothar M. (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:
> Der Akkumulator:
...ist normalerweise nicht getaktet, sondern pure und reinste 
Kombinatorik. Durch deine Takterei hast du zusätzlich eine Stufe 
Flipflops eingefügt und das gefunden, was man Latency nennt: deine 
Ergebnisse werden um 1 Takt "verschoben".

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lothar M. schrieb:
> deine
> Ergebnisse werden um 1 Takt "verschoben".

Achso, beim Program-Counter ist das deswegen nicht, da ich eins 
hochzähle und mit 0 beginne.

Achim S. schrieb:
> bringt w_reg_top auf den Wert, den w_content zu Beginn des Prozesses
> hatte (nicht auf den Wert, der während des Prozesses zugewiesen wurde).
> Der neue Wert von w_content macht sich erst im folgenden Taktzyklus
> bemerkbar.

Danke dir, das erklärt auch, warum mein Jump nicht funktioniert :(
Wie löst man das?

Lothar M. schrieb:
> ist normalerweise nicht getaktet, sondern pure und reinste
> Kombinatorik.

Wie funktioniert das denn, wenn ich noch Daten aus dem RAM holen muss?
Im Diagramm der CPU, die ich nachbauen möchte, sind Akkumulator und ALU 
quasi eine Einheit (sind in einem Block zusammen gefasst)

Autor: Achim S. (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Max M. schrieb:
> Danke dir, das erklärt auch, warum mein Jump nicht funktioniert :(
> Wie löst man das?

Soll der Wert von w_content immer in w_reg_top geschrieben werden? Dann 
mach diese Zuweisung w_reg_top <= w_content einfach außerhalb des 
(getakteten) Prozesses. Damit gibt es dann keinen unerwünschten 
Taktzyklus Latenz.

Autor: Lothar M. (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:
> Akkumulator und ALU quasi eine Einheit
Der Akku ist lediglich ein getaktetes Register mit einigen Bits Breite. 
Er ist der Lieferant eines Operators und "zugleich" der Speicher des 
Ergebnisses. "Zugleich" in Anführungszeichen, weil dazwischen eine 
Taktflanke kommt.

Du solltest dir mal aufzeichnen, welche Teile in diesem Prozessor 
speichern müssen und welche Teile nur rechnen oder kombinatorische 
Funktionen abhandeln. Dann siehst du, dass die ALU keinen Takt braucht, 
der Akku aber schon. Und der Programmcounter natürlich auch.

Wie gesagt: VHDL ist eine Beschreibungssprache und wenn du etwas 
beschreiben willst, dann musst du dir vorher ein Bild davon machen 
(können).

: Bearbeitet durch Moderator
Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Achim S. schrieb:
> Damit gibt es dann keinen unerwünschten
> Taktzyklus Latenz.

Wie löse ich das mit dem Jump? Der tritt auch um einen Takt verzögert 
auf:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
USE IEEE.numeric_std.all;  

entity Program_Counter is

  port (
  
    c0: in std_logic;
    pc: out unsigned(8 downto 0);
    is_jump: in std_logic;
    jump_addr: in unsigned(8 downto 0);
    is_ret: in std_logic;
    ret_addr: in unsigned(8 downto 0);
    state: in unsigned(2 downto 0)
    
  );
  
end Program_Counter;

architecture Behavioral of Program_Counter is

  signal pc_int : unsigned(8 downto 0) := "000000000";
  
  begin
  process(c0, state)
  begin
  
    if(falling_edge(c0) and state = "000") then
      if(is_jump = '1') then
        pc_int <= jump_addr;
      elsif(is_ret = '1') then
        pc_int <= ret_addr;
      else
        pc_int <= pc_int + 1;
      end if;
      pc <= pc_int;
    end if;
  
  end process;

end Behavioral;

Achim S. schrieb:
> Damit gibt es dann keinen unerwünschten
> Taktzyklus Latenz.

Danke, das hat geholfen!

Lothar M. schrieb:
> Dann siehst du, dass die ALU keinen Takt braucht,
> der Akku aber schon.

Wie würde ich denn hier den add-Befehl aus dem Prozess raus bekommen? Es 
scheint, als könnte man ein if nur innerhalb eines Prozesses 
verwenden:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
USE ieee.numeric_std.ALL;  

entity W_Reg is

  port (

    c0: in std_logic;
    read_w: in std_logic;
    write_w: in std_logic;
    place_immediate: in std_logic;
    
    is_add: in std_logic;
    
    reg_write_data: out unsigned(3 downto 0);
    reg_read_data: in unsigned(3 downto 0);
    
    immediate: in unsigned(3 downto 0);
    w_reg_top: out unsigned(3 downto 0);
    state: in unsigned(2 downto 0)
  );
  
  
end W_Reg;

architecture Behavioral of W_Reg is

  signal w_content: unsigned(3 downto 0) := "0000";

  begin
  process(c0, state, reg_read_data, immediate, read_w, write_w)
  begin
  
    if(falling_edge(c0) and state = "100") then
      if(read_w = '1') then
        reg_write_data <= w_content;
      elsif(write_w = '1') then
        if(place_immediate = '1') then
          w_content <= immediate;
        elsif(is_add = '1') then
          w_content <= w_content + reg_read_data;
        else
          w_content <= reg_read_data;
        end if;
      end if;
      
    end if;
  
  end process;

  w_reg_top <= w_content;
  
end Behavioral;

: Bearbeitet durch User
Autor: Achim S. (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Max M. schrieb:
> Wie löse ich das mit dem Jump? Der tritt auch um einen Takt verzögert
> auf:

Na komm, die Transferleistung ist jetzt nicht so gewaltig: du musst halt 
auch das
       pc <= pc_int;
aus dem getakteten Prozess herausziehen um einen Takt weniger Latenz zu 
bekommen.

Max M. schrieb:
> Es
> scheint, als könnte man ein if nur innerhalb eines Prozesses
> verwenden:

vielleicht hilft dir "when"

http://www.ics.uci.edu/~jmoorkan/vhdlref/cond_s_a.html

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Achim S. schrieb:
> Na komm, die Transferleistung ist jetzt nicht so gewaltig: du musst halt
> auch das
>        pc <= pc_int;
> aus dem getakteten Prozess herausziehen um einen Takt weniger Latenz zu
> bekommen.

Da hätte ich tatsächlich auch selbe drauf kommen können, danke dir.

In der Simulation hängt es allerdings nun davon ab, ob die erste Flanke 
fallend oder steigend ist. Handelt es sich um eine fallende Flanke wird 
der PC direkt auf 1 gesetzt und somit ein Befehl übersprungen.
Wenn die CPU mal im FPGA läuft, kann ich mir ja nicht aussuchen, ob die 
erste Flanke fallend oder steigend sein wird (oder?). Wie läuft das dann 
ab?

Bei meinem Stack für die Rücksprungadressen lässt sich das Problem mit 
der Verzögerung nicht so einfach lösen (glaub ich), da hier noch ein 
Index-Wert drann hängt:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
USE IEEE.numeric_std.all;  

entity Stack is

  port (
  
    c0: in std_logic;
    
    is_ret: in std_logic;
    is_jump: in std_logic;
    pc: in unsigned(8 downto 0);
    ret_addr: out unsigned(8 downto 0);
    
    state: in unsigned(2 downto 0);
    
    lowest_stack: out unsigned(8 downto 0)
    
  );
  
end Stack;

architecture Behavioral of Stack is

  type two_level is array (0 to 1) of unsigned (8 downto 0);
  signal stack_int: two_level;
  
  signal idx : unsigned(0 downto 0);
  
  begin
  process(c0, state, is_ret, is_jump, idx)
  begin
  
    if(falling_edge(c0) and state = "101") then
      if(is_ret = '1') then
        ret_addr <= stack_int(to_integer(unsigned(idx)));
        if(idx = "1") then
          idx <= "0";
        end if;
      elsif(is_jump = '1') then
        stack_int(to_integer(unsigned(idx))) <= pc + 1;
        if(idx = "0") then
          idx <= "1";
        end if;
      end if;
    end if;
  
  end process;
  
  lowest_stack <= stack_int(to_integer(unsigned(idx)));
  
end Behavioral;

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Okay, es geht tatsächlich so einfach:
architecture Behavioral of Stack is

  type two_level is array (0 to 1) of unsigned (8 downto 0);
  signal stack_int: two_level;
  
  signal idx : unsigned(0 downto 0);
  
  begin
  process(c0, state, is_ret, is_jump, idx)
  begin
  
    if(falling_edge(c0) and state = "101") then
      if(is_ret = '1') then
        if(idx = "1") then
          idx <= "0";
        end if;
      elsif(is_jump = '1') then
        stack_int(to_integer(unsigned(idx))) <= pc + 1;
        if(idx = "0") then
          idx <= "1";
        end if;
      end if;
    end if;
  
  end process;
  
  ret_addr <= stack_int(to_integer(unsigned(idx)));
  lowest_stack <= stack_int(to_integer(unsigned(idx)));
  
end Behavioral;

Irgendwie hab ich das Gefühl, ich baue nur um ein Problem außen rum und 
irgendwann stürzt das Konstrukt zusammen.

Autor: Lothar M. (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
2 lesenswert
nicht lesenswert
Max M. schrieb:
> if(idx = "1") then
> if(idx = "0") then
Du kannst diese beiden if-Abfragen einfach weglassen, denn in realer 
Hardware kann idx nur '0' oder '1' sein. Denk mal kurz drüber nach, was 
wäre, wenn im ersten Fall idx schon '0' wäre, oder im zweiten Fall schon 
'1'. Richtig: dann würe idx im ersten Fall wieder '0' und im zweiten 
Fall wieder '1'.

Langer Rede kurzer Sinn: der Synthesizer wird diese if-Abfragen eh' 
umstandslos wegoptimieren... ;-)

Max M. schrieb:
> signal idx : unsigned(0 downto 0);
Warum sooooo umständlich? Sieh mal nach, wie unsigned-Vektoren definiert 
sind: ein unsigend-Vektor mit der Breite 1 Bit ist ein simpler 
std_logic...

Siehe dort in der Zeile direkt nach dem Copyright:
https://standards.ieee.org/downloads/1076/1076.2-1996/numeric_std.vhdl

Wenn du damit aber die Wege für einen späteren idx bereithalten willst, 
warum machst du den idx dann nicht gleich zum Integer? dann sparst du 
dir das Herumkonvertieren beim Arrayzugriff.

Am leichtesten lässt sich VHDL Code schreiben und lesen, wenn man sich 
vorneweg Gedanken zu den verendeten Datentypen macht...


Und dann die Sesitivliste: da ist viel zu viel drin. Das kommt auch von 
der ungewöhnlichen Schreibweise für das Enable. Du verwendest den zwar 
funktionierenden aber unüblichen zweiten Fall von dort:
http://www.lothar-miller.de/s9y/archives/1-Clock-Enable-in-einer-ISE-VHDL-Beschreibung.html
Mein Tipp: mach es wie der Rest der Welt. In die Sensitivliste eines 
getakteten Prozesses kommt nur der Takt (und nötigenfalls bestens noch 
der asynchrone Reset).

Mein Vorschlag zu deinem Code wäre dann sowas in diese Richtung:
  type two_level is array (0 to 1) of unsigned (8 downto 0);
  signal stack_int: two_level;
  signal idx : integer;
  begin

  process(c0)
  begin 
    if falling_edge(c0 then
      if state="101" then
        if is_ret='1' then
          if idx=1 then 
            idx <= 0;
          end if;
        elsif is_jump='1' then
          stack_int(idx) <= pc + 1;
          if idx=0 then 
            idx <= 1;
          end if;
        end if;
      end if;
    end if;
  end process;

Autor: Max M. (maxmicr)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Danke für deine hilfreichen Tipps, das ist mein erstes Projekt bzw. 
meine ersten Gehversuche mit VHDL, von dem her ist jeder Hinweis gerne 
gesehen.

Lothar M. schrieb:
> Warum sooooo umständlich?

Ja, das frag ich mich nun auch

Lothar M. schrieb:
> Mein Tipp: mach es wie der Rest der Welt. In die Sensitivliste eines
> getakteten Prozesses kommt nur der Takt (und nötigenfalls bestens noch
> der asynchrone Reset).

Okay, ich dachte, da muss alles rein, auf das im folgenden Prozess 
lesend zugegriffen wird?

Ich hätte noch kurz zwei Fragen zum Instruction Set der CPU:

1. Der Befehl "LDR R, t" hat zwei Parameter. R steht einmal für das 
General Register (also quasi das RAM) und t gibt an, ob der Inhalt von 
R in das Working Register oder in ein General Register geschrieben 
werden soll.
Nun frage ich mich: Wie soll hier ein t=1 (= General Register) 
funktionieren? Ich hab nur 5 Bits, und die geben bereits an, aus welchem 
Register Daten geladen werden. Die einzige Möglichkeit, die mir 
einfällt, ist das in das selbe Register wieder zurückgeschrieben wird 
(was etwas sinnlos wäre)?

2. RTWI popt die oberste Adresse vom 2-level Stack, allerdings sehe 
ich keinen Befehl, der einen push durchführt?
Der einzige unbedingte Sprung ist JUMP, da steht aber nix von Stack in 
der Beschreibung.
Der kleinste PIC10F200 hat zwei Befehle für unbedingte Sprünge, einmal 
goto und call. Die Verwendung von goto:
main: nop
 goto main

während call eine Unterfunktion aufruft (also ein push quasi):
main: nop
 call func
 goto main

func: nop
 retlw 0

Ein Befehl scheint da bei der MDT90P01 zu fehlen oder mir ist die 
Funktionsweise nicht ganz klar.

: Bearbeitet durch User
Autor: Lothar M. (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:
> Lothar M. schrieb:
>> Mein Tipp: mach es wie der Rest der Welt. In die Sensitivliste eines
>> getakteten Prozesses kommt nur der Takt (und nötigenfalls bestens noch
>> der asynchrone Reset).
>
> Okay, ich dachte, da muss alles rein, auf das im folgenden Prozess
> lesend zugegriffen wird?
Es muss alles rein, was eine Neuberechnung des Prozesses nötig macht, 
weil sich durch die Änderung dieses Signals in der Simulation die 
Resultate des Prozesses ändern würden. Und bei einem getakteten Prozess 
können sich Resultate nur mit einer Taktänderung ergeben.

Dem Synthesizer ist die Sensitivliste vollkommen schnuppe! Er erweitert 
fehlende Signale oder er ignoriert sie wie er es braucht. Und meldet 
dann bestenfalls, dass die Simulation nicht mehr zur Realität passt.

Autor: Max M. (maxmicr)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:
> Der einzige unbedingte Sprung ist JUMP, da steht aber nix von Stack in
> der Beschreibung.

Etwas seltsame Lösung: Ein Flag im Status Register gibt an, ob es sich 
um einen JUMP oder einen CALL handelt. Da war anscheinend die Wortbreite 
zu kurz für einen weiteren Befehl.

Lothar M. schrieb:
> Es muss alles rein, was eine Neuberechnung des Prozesses nötig macht,
> weil sich durch die Änderung dieses Signals in der Simulation die
> Resultate des Prozesses ändern würden. Und bei einem getakteten Prozess
> können sich Resultate nur mit einer Taktänderung ergeben.

Alles klar, vielen Dank!

: Bearbeitet durch User
Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hm, ich scheitere daran, dieses Design vernünftig in meinen Nachbau zu 
implementieren.
Aktuell führt eine Leitung SCALL vom RAM zum Stack (der dann bestimmt, 
ob die Adresse des JUMP-Befehls abgelegt wird oder nicht).
Wie schaffe ich es nun, dieses Bit im RAM beim nächsten JUMP-Befehl auf 
0 zu setzen (so wie es die Beschreibung vorsieht)?
Prinzipiell erkennt der Program-Counter ob es sich beim aktuellen Befehl 
um einen Jump handelt, aber der hat keine Verbindung zum RAM.

: Bearbeitet durch User

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [vhdl]VHDL-Code[/vhdl]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.