Forum: FPGA, VHDL & Co. VHDL: umständlicher Code?


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.
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Angehängte Dateien:

Bewertung
1 lesenswert
nicht lesenswert
Versuche gerade, einen RAM-Block hier einzubinden, und die jeweils 
gelesenen Daten dann seriell aus einem Schieberegister rauszutakten.

Mein erster Versuch hatte da zwei Prozesse, einen für die Behandlung 
eines Taktzählers, der einerseits die RAM-Adressen zählt und 
andererseits die Serialisierung der RAM-Ansteuersignale erledigt. Wenn 
ein neues Byte gelesen werden kann, wird dieses in das 
Ausgabe-Schieberegister gepackt, welches dann von einem zweiten Prozess 
mit jedem Taktschritt rausgeschoben wird.

Hier hat mir der Simulator dann diverse Us und Xs im Schieberegister 
gezeigt, obwohl der Reset am Anfang ja eigentlich das shiftregister 
sauber ausnullen sollte.

Packe ich alles Handling in ein relativ umständlich anmutendes 
case-Statement in einem Prozess, funktioniert es wie gewünscht.

Wie würde man das logisch trennen, ohne in die Undefinierbarkeiten 
meines ersten Versuchs zu laufen?

(ps: Der RAM-Inhalt ist erstmal komplett durch die Initialisierungsdatei 
bestimmt. Die Schreib-Seite wäre erst später dran.)

: Bearbeitet durch Moderator
von Tobias B. (Firma: www.elpra.de) (ttobsen) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
In der ersten Variante wird in zwei unterschiedlichen Prozessen in 
"shiftreg" geschrieben. Das ist nicht erlaubt und erzeugt ein undefined 
fuer eben diesen Vektor.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Im Prinzip ist mir das schon klar. Nur: wie würde man das besser (und 
vor allem schöner) anstellen?

Diesen "case-Klumpen" in der zweiten Version finde ich irgendwie 
hässlich und unübersichtlich, auch wenn er formal korrekt ist.

: Bearbeitet durch Moderator
von Tobias B. (Firma: www.elpra.de) (ttobsen) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Also im ersten Schritt wuerde ich den Prozess in eine eigene Entity 
packen und Speicher und Controller trennen. Aber das ist ja erstmal 
nicht das Thema. ;-)

Erstmal finde ich die case Statements nicht wirklich unuebersichtlich. 
Liesst sich wie eine Art FSM was erstmal leicht verstaendlich ist. Von 
daher kannst auch gleich eine saubere FSM definieren.

Den Counter von 0 bis 127 wuerde ich dazu in einen extra Prozess machen 
und dann beim Ueberlauf ein enable Flag setzen, welches den Prozess mit 
der FSM anstoesst. Dort hast du dann wenn ich das richtig sehe die 3 
States: Wait, Set RAM Adress und Read RAM.

Wenn du allen deinen case Statements (ausser der 5) noch ein ram_en <= 
'0' spendierst, dann siehst du auch das die alle eine ziemlich grosse 
Schnittmenge haben und sich zusammenfassen lassen.

Ob das am Ende Uebersichtlicher ist, ist allerdings stark 
geschmacksabhaengig. Gerne mache ich da morgen mal ein Beispiel dazu, 
jetzt ists mir dafuer allerdings doch etwas zu spaet. :-(

von Gustl B. (-gb-)


Bewertung
0 lesenswert
nicht lesenswert
Das ist quasi ein klassisches Ding das man irgendwann mal baut. Z. B. 
wenn man Daten über UART ausgeben möchte die man vorher im BRAM abgelegt 
hat.

Bei mir hat die UART Komponente ein Start Signal und ein Busy. Und 
sobald Busy '0' ist, dann werden neue Daten aus dem RAM gelesen, an den 
Dateneingang angelegt und das Start Signal auf '1' gelegt.
Dann läuft der UART los und setzt sofort Busy auf '1' wodurch schon im 
nächsten Takt auch von aussen das Start wieder auf '0' gesetzt wird, das 
liegt also nur für einen Takt auf '1'.

: Bearbeitet durch User
von Christoph M. (mchris)


Bewertung
0 lesenswert
nicht lesenswert
>Das ist quasi ein klassisches Ding das man irgendwann mal baut. Z. B.
>wenn man Daten über UART ausgeben möchte die man vorher im BRAM abgelegt
>hat.

Ja, das kann man oft gebrauchen. Das hatte ich hier mal als erstes 
Bastelprojekt versucht, wobei das Pollin-Board ein externes RAM hat:

Beitrag "Logic Analyser mit Pollin CPLD Board"

Die Kombination aus Schnittstelle, Speicher und Steuerlogik könnte man 
als Standard-Template gebrauchen.
Mittlerweile bin ich aber der Meinung, dass eine SPI statt asynchroner 
serielle Schnittstelle sinnvoller ist, da man höhere Datenübertragungen 
schafft.
Zur Not kann man immer noch irgend eine Arduino-Variante als Seriel-SPI 
Adapter nehmen, die sind teilweise auch nicht mehr viel teurer als ein 
USB-Seriel-Converter.
Vielleicht sollte man mal ein Gemeinschaftsprojekt für so ein Template 
starten.

von Markus F. (mfro)


Bewertung
1 lesenswert
nicht lesenswert
das hier:
      case ctr mod 8 is
      when 4 =>
        ram_addr <= std_logic_vector(to_unsigned(ctr / 8, 4));
        wave <= shiftreg(7);
        shiftreg(7 downto 1) <= shiftreg(6 downto 0);
      when 5 =>
        ram_en <= '1';
        wave <= shiftreg(7);
        shiftreg(7 downto 1) <= shiftreg(6 downto 0);
      when 6 =>
        ram_en <= '0';
        wave <= shiftreg(7);
        shiftreg(7 downto 1) <= shiftreg(6 downto 0);
      when 7 =>
        shiftreg <= ram_data;
        wave <= ram_data(7);
      when others =>
        wave <= shiftreg(7);
        shiftreg(7 downto 1) <= shiftreg(6 downto 0);
      end case;



kann man (funktionsgleich) auch so schreiben:
    wave <= shiftreg(7);
    shiftreg(7 downto 1) <= shiftreg(6 downto 0);
    
    case ctr mod 8 is
        when 4 =>
            ram_addr <= std_logic_vector(to_unsigned(ctr / 8, 4));
        when 5 =>
            ram_en <= '1';
        when 6 =>
            ram_en <= '0';
        when 7 =>
            shiftreg <= ram_data;
            wave <= ram_data(7);
        when others =>
            null;
    end case;

indem man alles, was redundant ist, aus dem case rauszieht und nur die 
Ausnahmen drin läßt. Da muss man sich als "normaler" Programmierer (wo 
"falsche" Zuweisungen, die man anschliessend korrigiert, meist mit 
Performanceeinbussen bestraft werden) erst mal dran gewöhnen.

Der Synthese ist das wurscht.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Ihr habt ja lustige Arbeitszeiten. ;-)

Danke mal für alle Tipps. Dass man das modularisieren kann, ist 
natürlich ein anderes Thema, das hier war ja nur ein runtergebrochenes 
Minimalbeispiel, damit ich den RAM überhaupt erstmal in der Simulation 
gehandhabt bekomme, und all meine Us und Xs los werde.

Markus F. schrieb:
> kann man (funktionsgleich) auch so schreiben:

Das war jetzt genau das, was ich gesucht habe, danke für diesen Hinweis.

Ich habe mich zwar im Großen und Ganzen ganz gut dran gewöhnen können, 
dass bei so einer Hardwarebeschreibung einiges anders ist als beim 
Programmieren, aber manche Dinge sind da eben nicht so offensichtlich.

von Lothar M. (lkmiller) (Moderator) Benutzerseite


Bewertung
1 lesenswert
nicht lesenswert
Jörg W. schrieb:
> aber manche Dinge sind da eben nicht so offensichtlich.
Man darf sich auch durch syntaktische Änlichkeiten nicht verleiten 
lassen, irgendwelche Kenntnisse oder Erfahrungen aus der 
SW-Programmierung ohne vorherige Tauglichkeitsprüfung anzuwenden. Ganz 
offensichtlich ist das bei der for-Schleife, die etwas völlig anderes 
macht, als ein SW-Programmierer erwarten würde.

Markus F. schrieb:
> indem man alles, was redundant ist, aus dem case rauszieht und nur die
> Ausnahmen drin läßt.
Denn das sehr angenehme Verhalten von Signalen in Prozessen ist:
1. sie behalten ihren Eingangswert bis zum Prozessende(*). Somit ist in 
einem Prozess völlig egal, an welcher Stelle ein Signal abgefragt 
wird.
2. sie übernehmen erst am Prozessende(*) den zuletzt zugewiesenen Wert. 
Man kann ihnen also "vorher" oder "zwischendurch" alle möglichen Werte 
zuweisen.

Diese beiden Prozesse machen/ergeben also genau das selbe:
signal a,b,c,d : std_logic;
:
  erster: process (clk) is 
  begin
    if rising_edge(clk) then
      b <= a;
      c <= b;
      d <= c xor b;
    end if;
  end process;
:
  zweiter: process (clk) is 
  begin
    if rising_edge(clk) then
      d <= c xor b;
      c <= b;
      b <= a;
    end if;
  end process;
Denn wenn man die beiden obigen "Regeln" anwendet, dann sieht man, dass 
hier die Reihenfolge der Zuweisungen völlig uninteressant ist.



(*)oder eben explizit bis zum nächsten wait, denn jede Sensitivliste 
eines Prozesses kann durch ein "wait on" samt Signalliste ersetzt 
werden:
  process is
  begin
    wait on clk, reset;
    if reset = '0' then
      :
    elsif rising_edge(clk) then
      :
    end if;
  end process;

: Bearbeitet durch Moderator
von Gustl B. (-gb-)


Bewertung
0 lesenswert
nicht lesenswert
So, hier mal ein Minimalbeispiel.

Das hat einen RAM, der wird getaktet gelesen und es hat ein 
Schieberegister das je 10 Bits wie bei einem UART mit Start- und Stopbit 
herausschiebt, hier mit 4 MBaud.

Ich habe das hier in zwei Prozesse unterteilt und zwar deshalb, weil 
"man üblicherweise" den zweiten unteren Prozess als eigene Komponente 
baut. Da kann man problemlos die UART Komponente von Lothar verwenden 
und eben die Ports passend anbinden.

Hier wie es ohne Lothars UART aussieht:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity RAM_SR is Port(
  CLK: in std_logic;
  TXD: out std_logic);
end RAM_SR;

architecture Behavioral of RAM_SR is

type Array16x8 is array(0 to 15) of std_logic_vector(7 downto 0);
signal RAM: Array16x8:=(x"00",x"01",x"02",x"03",x"04",x"05",x"06",x"07",x"08",x"09",x"0A",x"0B",x"0C",x"0D",x"0E",x"0F");
signal RAM_Address: unsigned(3 downto 0):=(others => '0');
signal RAM_Data: std_logic_vector(7 downto 0):=(others => '0');

signal TX_Register: std_logic_vector(9 downto 0):=(others => '0');
signal TX_Bit_Address: integer range 0 to 9:=9;
signal TX_Busy: std_logic:='0';
signal TX_Start: std_logic:='0';
signal TX_Data: std_logic_vector(7 downto 0):=(others => '0');
signal TX_Bd_counter: integer range 0 to 24 := 24; -- 4 MBd@100MHz

begin

process begin
  wait until rising_edge(CLK);
  RAM_Data <= RAM(to_integer(RAM_Address));
  TX_Start <= '0';
  if TX_Busy = '0' then
    RAM_Address <= RAM_Address +1;
    TX_Start <= '1';
    TX_Data <= RAM_Data;
  end if;
end process;

process begin
  wait until rising_edge(CLK);
  if TX_Bd_counter < 24 then
    TX_Bd_counter <= TX_Bd_counter +1;
  else
    if TX_Bit_Address < 9 then
      TX_Bit_Address <= TX_Bit_Address +1;
      TX_Bd_counter <= 0;
    else
      if TX_Start = '1' then
        TX_Bit_Address <= 0;
        TX_Bd_counter <= 0;
        TX_Register <= "1" & TX_Data & "0";
      end if;
    end if;
  end if;
end process;

TX_Busy <= '0' when TX_Start = '0' and TX_Bit_Address = 9 and TX_Bd_counter = 24 else '1';
TXD <= TX_Register(TX_Bit_Address);

end Behavioral;

Und hier die Variante mit Lothars UART von 
http://www.lothar-miller.de/s9y/categories/42-RS232 :
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity RAM_SR is Port(
  CLK: in std_logic;
  TXD: out std_logic);
end RAM_SR;

architecture Behavioral of RAM_SR is

component RS232 is
    Generic(
  Quarz_Taktfrequenz : integer   := 50000000;  -- Hertz 
  Baudrate           : integer   :=  9600);      -- Bits/Sec)
    Port(
  RXD      : in   STD_LOGIC;
  RX_Data  : out  STD_LOGIC_VECTOR (7 downto 0);
  RX_Busy  : out  STD_LOGIC;
  TXD      : out  STD_LOGIC;
  TX_Data  : in   STD_LOGIC_VECTOR (7 downto 0);
  TX_Start : in   STD_LOGIC;
  TX_Busy  : out  STD_LOGIC;
  CLK      : in   STD_LOGIC);
end component;

type ARR4x8 is array(0 to 15) of std_logic_vector(7 downto 0);
signal RAM: ARR4x8:=(x"00",x"01",x"02",x"03",x"04",x"05",x"06",x"07",x"08",x"09",x"0A",x"0B",x"0C",x"0D",x"0E",x"0F");
signal RAM_Address: unsigned(3 downto 0):=(others => '0');
signal RAM_Data: std_logic_vector(7 downto 0):=(others => '0');

signal TX_Busy: std_logic:='0';
signal TX_Start: std_logic:='0';
signal TX_Data: std_logic_vector(7 downto 0):=(others => '0');

begin

process begin
  wait until rising_edge(CLK);
  RAM_Data <= RAM(to_integer(RAM_Address));
  TX_Start <= '0';
  if TX_Busy = '0' then
    RAM_Address <= RAM_Address +1;
    TX_Start <= '1';
    TX_Data <= RAM_Data;
  end if;
end process;

uart: RS232
    Generic map(
  Quarz_Taktfrequenz => 100000000,
  Baudrate           => 4000000)
    Port map(
  RXD      => '1',
  RX_Data  => open,
  RX_Busy  => open,
  TXD      => TXD,
  TX_Data  => TX_Data,
  TX_Start => TX_Start,
  TX_Busy  => TX_Busy,
  CLK      => CLK);

end Behavioral;

: Bearbeitet durch User
von Markus F. (mfro)


Bewertung
0 lesenswert
nicht lesenswert
Jörg W. schrieb:
> Ihr habt ja lustige Arbeitszeiten. ;-)

Emergency call um halb drei. Dann erst mal zwei Tassen Kaffee, um 
erstmal das Problem zu verstehen. Dann mal eben die Welt retten ;).

Da geht so ein "VHDL-Problemchen" fast nebenbei.

Danach braucht man nicht mehr ins Bett. Mach' ich jetzt.

von Lothar M. (lkmiller) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Jörg W. schrieb:
> Hier hat mir der Simulator dann diverse Us und Xs im Schieberegister
> gezeigt, obwohl der Reset am Anfang ja eigentlich das shiftregister
> sauber ausnullen sollte.
Du treibst da ein Signal aus mehreren Quellen. Denn ein Prozess 
beschreibt Hardware. Ein zweiter Prozess beschreibt ebenfalls Hardware. 
Und wenn beide Ptozesse schreibend auf das selbe (Schiebe-)Register 
zugreifen wollen, dann rumpelts:
  process(clk, reset) is
  begin
    if reset = '0' then
      shiftreg <= "00000000";
      :
    elsif rising_edge(clk) then
      :
      when 7 => shiftreg <= ram_data;
      :
    end if;
  end process;

  process(clk, reset, shiftreg) is
  begin
    :
    elsif rising_edge(clk) then
      : 
      shiftreg(7 downto 1) <= shiftreg(6 downto 0);
    end if;
  end process;
Der Synthesizer nennt das "multiple drivers". Und keine Sorge: das ist 
ein beliebter Fehler, da muss jeder mal durch  ;-)

Noch eine Silbe zur Sensitivliste:
process(clk, reset, shiftreg) is ...
shiftreg ist hier unnötig, weil die Neuberechung des Prozesses nur bei 
einer Änderung von clk oder reset nötig ist. Und in der Realität ist es 
ja so, dass einem D-FF zum Glück nur die Taktflanke interessiert, aber 
nicht, ob der Dateneingang herumzappelt.

Noch eine weitere Silbe zur Sensitivliste:
diese Liste interessiert nur den Simulator. Der Synthesizer schert sich 
nicht darum, sondern meldet dir einfach, wenn ein Signal fehlt und meint 
dazu lapidar, dass deine Simulation Käse ist: "simulation will not match 
synthesis result".

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Lothar M. schrieb:

> Der Synthesizer nennt das "multiple drivers". Und keine Sorge: das ist
> ein beliebter Fehler, da muss jeder mal durch  ;-)

Ja, im Prinzip war mir das dann schon klar geworden, dass „man das nicht 
macht“. Das war ja nur der Versuch, dass der hineschriebene Code etwas 
schöner aussah als das, was ich danach dann fabriziert habe.

Mit Markus' Implementierungsvorschlag ist es ja dann beides, technisch 
in Ordnung und übersichtlich aussehend.

> Noch eine Silbe zur Sensitivliste:
> process(clk, reset, shiftreg) is ...
> shiftreg ist hier unnötig, weil die Neuberechung des Prozesses nur bei
> einer Änderung von clk oder reset nötig ist.

An irgendeiner Stelle hatte ich in der Simulation unerwartete Effekte, 
als ich mal ein Signal da nicht hinein geschrieben habe. Weiß aber nicht 
mehr ganz den Zusammenhang.

> Noch eine weitere Silbe zur Sensitivliste:
> diese Liste interessiert nur den Simulator.

OK.

von Lothar M. (lkmiller) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Jörg W. schrieb:
>> shiftreg ist hier unnötig, weil die Neuberechung des Prozesses nur bei
>> einer Änderung von clk oder reset nötig ist.
> An irgendeiner Stelle hatte ich in der Simulation unerwartete Effekte,
> als ich mal ein Signal da nicht hinein geschrieben habe. Weiß aber nicht
> mehr ganz den Zusammenhang.
Vermutlich war das ein kombinatorischer Prozess. Dessen Ergebnis hängt 
natürlich von allen Signalen ab, die auf der rechten Seite eines 
Zuweisungsoperators stehen.
Ein UND-Gatter muss sofort neu berechnet werden, wenn sich eines der 
Signale ändert:
  erster: process (a,b,c) is 
  begin
       d <= c and b and a;
  end process;

-- das würde man natürlich eher ohne Prozess als nebenläufige Anweisung schreiben:

  d <= c and b and a;

(das würde man natürlich normalerweise ohne Prozesspipapo als 
nebenläufige Anweisung schreiben)

Wenn der Wert des UND-Gatters aber hinterher auf ein Register geht, dann 
ist das Ergebnis nur vom Takt abhängig. Und demzufolge gehört in einen 
getakteten Prozess nur der Takt (und bestenfalls noch der asynchrone 
Reset):
  erster: process (clk) is 
  begin
    if rising_edge(clk) then
      d <= c and b and a;
    end if;
  end process;

-- auch das könnte man natürlich als nebenläufige Anweisung schreiben:

  d <= c and b and a when rising_edge(clk);


: Bearbeitet durch Moderator
von Markus F. (mfro)


Bewertung
0 lesenswert
nicht lesenswert
Lothar M. schrieb:
> Ganz
> offensichtlich ist das bei der for-Schleife, die etwas völlig anderes
> macht, als ein SW-Programmierer erwarten würde.

Da bin ich übrigens weiterhin anderer Meinung.

Die for-Schleife macht exakt genau das, was ein SW-Programmierer 
erwarten würde. Nur eben innerhalb der Randbedingungen, die ein 
HW-Design bzw. dessen Synthese vorgibt.

Wenn ich eine for-Schleife in einen Prozess packe, der alles, was drin 
steht, innerhalb eines Taktes ausführen muss, dann bleibt der Synthese 
gar nichts anderes übrig, als die Effekte der for-Schleife während der 
Synthese "vorzuberechnen" und die Resultate zu hinterlegen.

Das ist praktisch exakt dasselbe, was z.B. in C++ bei "Template 
Metaprogramming (TMP)" bzw. "constexpr" gesteuert bzw. steuerbar und bei 
jedem optimierenden C-Compiler "hinter den Kulissen" während des 
Compilerlaufs (z.B. als loop unrolling) passiert.

In VHDL heisst das "static" (vereinfacht: "rechne alles aus, was Du zur 
Compile-Zeit ausrechnen kannst und was übrig bleibt ist eine 
unveränderliche Logik, die in einem Schritt abgearbeitet werden kann 
oder ein Fehler" und hat mit dem C/C++ static Begriff nicht das 
Geringste zu tun) und ein Muss, um aus einem dynamischen Programm ein 
statisches Schaltnetz aufzubauen. In C++ TMP/constexpr ist das praktisch 
exakt identisch (mit dem Unterschied, dass der Compiler am Ende meist 
doch noch in "klassisches Verhalten" zurückfallen darf, wenn das 
Konstrukt "über seinen Verstand" geht).

Wer (als "normaler Programmierer") das verinnerlicht hat, muss sich 
nicht mehr jede for-Schleife extra angucken.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Markus F. schrieb:
> Wer (als "normaler Programmierer") das verinnerlicht hat, muss sich
> nicht mehr jede for-Schleife extra angucken.

Da ich ursprünglich aus der Hardware komme und mir vorstellen kann, wie 
Hardware funktioniert, wäre ich mit sowas wie einer for-Schleife in 
einer HDL sowieso außerordentlich skeptisch. :)

von Lothar M. (lkmiller) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Markus F. schrieb:
> Die for-Schleife macht exakt genau das, was ein SW-Programmierer
> erwarten würde.
Der normale durchschnittliche SW-Programmierer erwartet eben nicht, dass 
sein Compiler versucht, alle 100 Schleifendurchläufe gleichzeitig 
auszuführen.

> was übrig bleibt ist eine unveränderliche Logik, die in einem Schritt
> abgearbeitet werden kann
Genau das ist eine übliche for-Schleife in einem Programm für einen 
normalen Programmierer eben nicht. Normale SW-Programmierer jammern ja 
wie im Beitrag "Re: Entprellen (kein AVR)" schon 
darüber, wenn ihre untätige, leere for-Schleife (zu Recht) wegoptimiert 
wird.

> Wer (als "normaler Programmierer") das verinnerlicht hat, muss sich
> nicht mehr jede for-Schleife extra angucken.
Wer sich mit dieser Problematik ausgiebig beschäftigt hat, ist eben 
schon einen Schritt weiter als der "normale" Programmierer. Ich habe da 
hin- und wieder mit Studenten zu tun, die das erste mal mit einer VHDL 
konfrontiert werden und zuvor SW programmiert haben. Die allermeisten 
werden von der for-Schleife überrascht und zeigen sich verwundert. Denn 
sie wollen erst mal wie zuvor gelernt z.B. einfach zur Implementierung 
einer seriellen Schnitte 10 Bits nacheinander in einer for-Schleife 
einlesen.
Erst nach der entsprechenden Aufklärung wird ihnen klar, wie sie die 
Arbeit anzugehen haben.

: Bearbeitet durch Moderator

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.

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