Forum: FPGA, VHDL & Co. Zugriff auf Speicher


von Tom (Gast)


Lesenswert?

Hallo,

ich bekomme alle x ns zwei STD_LOGIC_VECTOREN als Eingangssignal mit 
jeweils einer Größe von y Bit und möchte diese zeitgleich (parallel) in 
einen Speicher oder Register schreiben? Nach z Taktzyklen ist der 
Speichervorgang vorbei und beginnt nach einigen us erneut.

Welche Form von Speicher ist für eine solche Prozedur am geeignetesten? 
Register / Blockram? Der Speicher müsste auch wieder parallel ausgelesen 
werden, um Beispielsweise das erste 8 Bit Speicherelement mit dem 
Zweiten 8 Bit Speicherelement im ersten Takt und das dritte Element mit 
dem 4 Element im 2 Takt etc. pp zu vergleichen?


Gruß,

von Duke Scarring (Gast)


Lesenswert?

Da Du für x, y und z keine konkreten Zahlen nennst, kann man Dir auch 
nicht wirklich helfen.

Tom schrieb:
> Der Speicher müsste auch wieder parallel ausgelesen
> werden, um Beispielsweise das erste 8 Bit Speicherelement mit dem
> Zweiten 8 Bit Speicherelement im ersten Takt und das dritte Element mit
> dem 4 Element im 2 Takt etc. pp zu vergleichen?
Vielleicht helfen Dir dabei zwei Speicher, die abwechselnd beschrieben 
werden?

Duke

von Tom (Gast)


Lesenswert?

Hi Duke,

in einem Takt bekomme ich zwei mal 8 Bit Signale des Typs 
STD_LOGIC_VECTOR. Nach 1000 Takten also 2000 Signalwerte. Diese 2000 
Werte bilden meine Gesamtinformation.

Ich möchte also mit jedem Takt die zwei 8 Bit Signale in einem Speicher 
/ Register speichern, sodass mir nach 1000 Takten die kompletten 
Informationen in meinem Speicher vorliegen.

Also irgendwie in der Art:

"schreibe die 8 Bit des signal's A im Takt 1  an adresse "00000000" in 
und
 schreibe die 8 Bit des signal's B im Takt 1  an adresse "00000001"

schreibe die 8 Bit des signal's A im Takt 2  an adresse "00000010" in 
und
schreibe die 8 Bit des signal's B im Takt 2  an adresse "00000011"

.
.
.

von Duke Scarring (Gast)


Lesenswert?

Tom schrieb:
> Welche Form von Speicher ist für eine solche Prozedur am geeignetesten?

Tom schrieb:
> Diese 2000
> Werte bilden meine Gesamtinformation.
Also 16 kBit.
Dafür reicht der interne Speicher im FPGA, bei Xilinx BRAM genannt.

Duke

von Tom (Gast)


Lesenswert?

Dank Dir, Duke

von Tom (Gast)


Lesenswert?

Wenn ich mit Hilfe des Core Generators von Xilinx die Block RAM Funktion 
erstelle, kommt für diese Aufgabe doch nur der True Dual Port Ram in 
Frage, oder?

Beide Eingänge benötigen einen gemeinsamen Takt (Common Clock).

Was ich noch nicht so richtig verstanden habe, ist, wie das mit dem 
Write Enable funktionert. (Use Byte Write Enable mit ByteSize 8 und 9 
Bits).
Als Algorithmus würde ich "Minumum Area" nehmen.

Sollte als erstes geschrieben oder gelesen werden? Da vorher noch nichts 
im Ram drin steht, würde ich schreiben sagen.

Und ein enable Pin macht in diesem Fall ja auch Sinn.


Fragen über Fragen ;-)

Vielen Dank und schönes Wochenende,

Tom

von Uwe (Gast)


Lesenswert?

> Sollte als erstes geschrieben oder gelesen werden?
Was denkst du denn was drinne steht wenn du noch nichts reingeschrieben 
hast ? Macht das Sinn für dich ? Erst ein Blatt Papier lesen und dann 
erst was draufschreiben ?

von Tom (Gast)


Lesenswert?

Uwe schrieb:
> Was denkst du denn was drinne steht wenn du noch nichts reingeschrieben
> hast ? Macht das Sinn für dich ? Erst ein Blatt Papier lesen und dann
> erst was draufschreiben ?

klar, da gebe ich Dir recht, wie ich es auch schon geschrieben hatte. 
Jedoch wusste ich nicht, ob evtl. noch mehr dahinter steckt.

von Lattice User (Gast)


Lesenswert?

Mach den Speicher 16 bit breit, da brauchst du keinen Dualport RAM, 
ausser du möchtest schreiben und lesen entkoppeln.

von Tom (Gast)


Lesenswert?

Lattice User schrieb:
> Mach den Speicher 16 bit breit, da brauchst du keinen Dualport RAM,
> ausser du möchtest schreiben und lesen entkoppeln.

Und ich kann trotzdem zwei Adressen gleichzeitig beschreiben und beim 
Lesen zwei Adressen gleichenzeitig auslesen? Denn wenn ich im CoreGen 
nur SinglePort Ram auswähle, habe ich auch nur einen Eingang und einen 
Ausgang.

Meinst Du damit, dass ich dann die 2 * 8 Bit Werte mit dem '&' Operator 
verknüpfe und diese dann als 1 Signal in den Ram schreibe?

Später sollte es evtl. auf eine Größe von 4096 * 12 Bit Werten 
vergrößtert werden.

Lässt sich das alles mit einem Takt realisieren, denn in der nächsten 
aktiven Taktflanke liegen zwei neue Signale an A und B an?

Welchen Nachteil hat DualPort Ram für diese Anwendung gegenüber dem 
SinglePort? Deutlich mehr Ressourcenverbrauch?

Empfiehlt es sich, den Ram mit Hilfe des CoreGen zu realisieren oder 
beispielsweise mit so einer einfachen Implementrierung?

http://www.doulos.com/knowhow/vhdl_designers_guide/models/simple_ram_model/

Was den Coregenerator betrifft, bin ich dann an die jeweils vorher 
definierte Hardware gebunden?



Was würde ich nur ohne das super Forum machen ;-)

Vielen Dank,

Tom

von Duke Scarring (Gast)


Lesenswert?

Tom schrieb:
> Lattice User schrieb:
>> Mach den Speicher 16 bit breit, da brauchst du keinen Dualport RAM,
>> ausser du möchtest schreiben und lesen entkoppeln.
>
> Und ich kann trotzdem zwei Adressen gleichzeitig beschreiben und beim
> Lesen zwei Adressen gleichenzeitig auslesen?
Klar, wenn Deine Daten auf der gleichen Adresse liegen.

> Denn wenn ich im CoreGen
> nur SinglePort Ram auswähle, habe ich auch nur einen Eingang und einen
> Ausgang.
Logisch.


> Meinst Du damit, dass ich dann die 2 * 8 Bit Werte mit dem '&' Operator
> verknüpfe und diese dann als 1 Signal in den Ram schreibe?
Ganz genau.

> Später sollte es evtl. auf eine Größe von 4096 * 12 Bit Werten
> vergrößtert werden.
Schau ins Datenblatt, welche Speicheranordnungen unterstützt werden.

> Lässt sich das alles mit einem Takt realisieren, denn in der nächsten
> aktiven Taktflanke liegen zwei neue Signale an A und B an?
Ja.

> Welchen Nachteil hat DualPort Ram für diese Anwendung gegenüber dem
> SinglePort? Deutlich mehr Ressourcenverbrauch?
Nein. Kein Nachteil. Die zwei Ports sind sowieso vorhanden. Aber wenn 
man nur Singleport braucht, sieht der Code mit Singleport einfacher aus, 
als mit Dualport-BRAM.

> Empfiehlt es sich, den Ram mit Hilfe des CoreGen zu realisieren oder
> beispielsweise mit so einer einfachen Implementrierung?
CoreGen hab ich für BRAMs noch nie verwendet. Der macht die Sache m.E. 
nur unnötig kompliziert und unportabel.

> http://www.doulos.com/knowhow/vhdl_designers_guide...
Ja, das ist gut. Das steht (leider mit veralteten Synopsis-Libs) auch im 
XST USer Guide.

> Was den Coregenerator betrifft, bin ich dann an die jeweils vorher
> definierte Hardware gebunden?
Jain.

Duke

von tzu (Gast)


Lesenswert?

Du brauchst doch gar kein solches Modul einbinden, mMn nur umständlich.

(ohne Gewähr)
1
type t_bram is array(0 to 4095) of std_logic_vector(23 downto 0);
2
signal bram : t_bram;
3
4
signal write_address : integer range 0 to 4095 := 0;
5
...
6
process (clk)
7
begin
8
   if rising_edge(clk) then  
9
10
      if write_enable='1' then
11
         write_address <= write_address + 1;
12
         bram(write_address) <= Data_in1 & Data_in2
13
      end if;
14
      ...
15
      data_out1 <= bram(read_adress1/2)(11 downto 0); 
16
      data_out2 <= bram(read_adress2/2)(23 downto 12);
17
18
   end if;
19
end process;


Nur so als Beispiel.
Du kannst mit dieser Beischreibungsform beliebig viele Leseports an ein 
solches Ram hängen, die Synthese findet selbst raus wie sie das sinnvoll 
in HW abbilden kann.(z.b. Daten mehrfach speichern)

Klar sollte man vor Augen haben was dabei passiert, aber das muss man so 
oder so.

Coregen für solche Sachen ist absoluter Overkill.

von Tom (Gast)


Lesenswert?

Hey, vielen Dank für die super Hilfe. Das werde ich ausprobieren.

Hoffentlich werde ich eines Tages auch mal so viel Helfen können ;-)

Gruß,

Tom

von Tom (Gast)


Lesenswert?

Eine (vielleicht dumme) Frage habe ich bezüglich des BRAMS noch. BRAM 
steht doch für Block RAM. Bezieht sich das BRAM auf einen im FPGA 
bezogenen Speicher (gibt es auch FPGA's ohne BRAM?) oder wird nur der 
Aufbau des Speichers in Form von Blöcken so bezeichnet und BRAM würde 
sich immer implementieren lassen?

von tzu (Gast)


Lesenswert?

In dem Beispiel bezieht hab ich einfach irgenteinen Namen gegeben.

Typischerweise versteht man unter BRAM schon einen konkreten Speichertyp 
im FPGA(Blockram).

Wer der Synthese vertraut, wird aber feststellen das bei halbwegs 
geeineter Schreibweise die Tools anhand von gewählter Speichertiefe und 
Breite schon von alleine feststellen welcher Speicher sinnvoll zu 
verwenden ist.

Andere Typen selbst auswählen kann man aber auch ganz einfach im Code 
über attribute (unterscheiden sich ggf je nach Hersteller).
Macht aber fast nur dann Sinn wenn die Ressourcen(BRAMs oder LUTs oder 
FFs) knapp werden.

von tzu (Gast)


Lesenswert?

Vergessen:

Bei dem Beispiel oben ist es ein 24 Bit mal 4096 Ram. Das würde in 
Blockram bei den meisten neuen FPGAs genau ein BRAM ergeben bzw eins pro 
frei adressierbarem Leseport.

In FFs wären das 98304FF, das baut die Synthese nur wenns ganz übel 
hingeschrieben ist.

Als distributed Ram (in LUTs, falls unterstützt) sind das je nach 
Lutgröße 1500 bis 6000 Luts, was auch schon sehr viel ist.


Deshalb lege ich jetzt mal meine Hand dafür ins Feuer das die aktuellen 
Tools von Synopsys, Xilinx und Altera dort allesamt selbst die 
Entscheidung treffen werden daraus Blockram zu bauen solange welcher 
Verfügbar ist.

Nimmst du die Speichertiefe z.b. auf 16 runter, dann wird in den 
allermeisten Fällen hingegen Distributed Ram rauskommen.

Probiers einfach mal, du siehst im Report ja was er draus macht.

von Tom (Gast)


Lesenswert?

Super, vielen Dank für die tolle Erklärung.

Der RAM sieht bei mir jetzt folgendermaßen aus:
1
library IEEE;
2
use IEEE.STD_LOGIC_1164.ALL;
3
use ieee.numeric_std.all;
4
5
6
entity BRAM is
7
port(
8
clk    : IN std_logic;
9
data_a : IN std_logic_vector(11 downto 0);
10
data_b : IN std_logic_vector(11 downto 0);
11
write_enable :  IN std_logic;
12
data_out_a: OUT std_logic_vector(11 downto 0);
13
data_out_b: OUT std_logic_vector(11 downto 0)
14
);
15
end BRAM;
16
17
18
architecture Behavioral of BRAM is
19
type t_bram is array(0 to 4095) of std_logic_vector(23 downto 0);
20
signal bram : t_bram;
21
22
signal write_address : integer range 0 to 4095 := 0;
23
signal read_address : integer range 0 to 4095 := 0;
24
25
26
begin
27
28
29
30
process (clk)
31
begin
32
   if rising_edge(clk) then  
33
34
      if write_enable='1' then
35
         write_address <= write_address + 1;
36
         bram(write_address) <= data_a & data_b;
37
      end if;    
38
39
     
40
       data_out_a <= bram(write_address)(11 downto 0); 
41
       data_out_b <= bram(write_address)(23 downto 12);
42
43
44
  end if;    
45
end process;
46
47
48
49
end Behavioral;

Wenn write_enable jetzt nun 1 ist, wird mit jeden Takt der Wert von 
data_a und data_b hintereinander gehangen in den Speicher an 
n-ter(write_address) Zeile gespeichert.

Sobald write_enable wieder null wird, sind somit alle Werte im Ram 
gespeichert, wenn ich das richtig verstanden habe.

Was ich noch nicht ganz verstanden habe ist, was genau mit
1
      data_out1 <= bram(read_adress1/2)(11 downto 0); 
2
      data_out2 <= bram(read_adress2/2)(23 downto 12);

Muss read_adress nicht bei data_out1 und data_out2 nicht dieselbe sein?



Ich habe mir nun mal eine TB geschrieben, um den Eingängen mal ein paar 
Werte zuzuweisen.
1
LIBRARY ieee;
2
USE ieee.std_logic_1164.ALL;
3
Use ieee.numeric_std.all;
4
 
5
6
 
7
ENTITY tb_BRAM_24x4096 IS
8
END tb_BRAM_24x4096;
9
 
10
ARCHITECTURE behavior OF tb_BRAM_24x4096 IS 
11
 
12
    -- Component Declaration for the Unit Under Test (UUT)
13
 
14
    COMPONENT BRAM
15
    PORT(
16
         clk : IN  std_logic;
17
         data_a : IN  std_logic_vector(11 downto 0);
18
         data_b : IN  std_logic_vector(11 downto 0);
19
         write_enable : IN  std_logic;
20
         data_out_a : OUT  std_logic_vector(11 downto 0);
21
         data_out_b : OUT  std_logic_vector(11 downto 0)
22
        );
23
    END COMPONENT;
24
    
25
26
   --Inputs
27
   signal clk : std_logic := '0';
28
   signal data_a : std_logic_vector(11 downto 0) := (others => '0');
29
   signal data_b : std_logic_vector(11 downto 0) := (others => '0');
30
   signal write_enable : std_logic := '0';
31
32
   --Outputs
33
   signal data_out_a : std_logic_vector(11 downto 0);
34
   signal data_out_b : std_logic_vector(11 downto 0);
35
36
   -- Clock period definitions
37
   constant clk_period : time := 25 ns;
38
 
39
BEGIN
40
 
41
  -- Instantiate the Unit Under Test (UUT)
42
   uut: BRAM PORT MAP (
43
          clk => clk,
44
          data_a => data_a,
45
          data_b => data_b,
46
          write_enable => write_enable,
47
          data_out_a => data_out_a,
48
          data_out_b => data_out_b
49
        );
50
51
   -- Clock process definitions
52
   clk_process :process
53
   begin
54
    clk <= '0';
55
    wait for clk_period/2;
56
    clk <= '1';
57
    wait for clk_period/2;
58
   end process;
59
 
60
61
   
62
   stim_proc: process
63
   begin    
64
65
66
67
68
data_a <= std_logic_vector(to_unsigned(268,12)) after 200 ns, 
69
std_logic_vector(to_unsigned(578,12)) after 225 ns,
70
std_logic_vector(to_unsigned(898,12)) after 250 ns,
71
std_logic_vector(to_unsigned(0,12)) after 275 ns;
72
73
74
data_b <= std_logic_vector(to_unsigned(268,12)) after 200 ns, 
75
std_logic_vector(to_unsigned(578,12)) after 225 ns,
76
std_logic_vector(to_unsigned(898,12)) after 250 ns,
77
std_logic_vector(to_unsigned(0,12)) after 275 ns;
78
79
80
81
82
write_enable <= '0', '1' after 200 ns, '0' after 275 ns;
83
84
85
86
87
88
89
90
91
      wait;
92
   end process;
93
94
END;

Leider waren die Werte für data_out_a und data_out b null.
Liegt das daran, dass ich hier versuche gleichzeitig zu schreiben und zu 
lesen?

Wie kann ich nun sehen, was in den Speicher geschrieben wurde? Muss ich 
dann nachdem der Speicher beschrieben wurde und der write_enable wieder 
null auslesen?

Wenn ich von aussen, also von einer übergeordneten Ebene den Speicher 
auslesen möchte, benötige ich dafür nicht auch ein read_enable?



Sorry für die vielen Fragen und Danke nochmal für die tolle Hilfe!

von Tom (Gast)


Lesenswert?

Ich habe es nun mit einem zusätzlichen enable Eingang gelöst. Wenn 
enable und write enable = '1' sind, dann wird geschrieben, wenn enable 1 
und write enable 0 ist, wird gelesen.

von Tom (Gast)


Lesenswert?

Sind die Adressen bei Xilinx BRAM'S immer vom Typ Integer?


Ist die 1. Adresse eines Xilinx Block RAM's  mit Null definiert, oder 
mit einer 1?

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.