speccy.pl
Facebook Like


SPECCY.PL

[SPECCY.PL PARTY 2023.1]

[WIKI SPECCY.PL]
Polecamy

KOMITET SPOŁECZNY KRONIKA POLSKIEJ DEMOSCENY
PIXEL HEAVEN 2023
AYGOR
Forum ZX Spectrum
Zawartość panelu chwilowo niedostępna
Archiwum plików ZX Spectrum
Nawigacja
Zróbmy sobie układ... cześć 3.

Streszczenie poprzednich odcinków

...z niewielkim rozszerzeniem.
  • Zmieniamy sposób myślenia – opisujemy zachowanie układu, jego reakcję na zmiany sygnałów. Nie piszemy programu według którego ma działać.

  • Nie lenimy się – tam gdzie ma to sens, zawsze podajemy liczby wraz z ich ‘szerokością’ w bitach:
    8’h3f = 8’b00111111 = 8’d63
    (albo spędzamy długi czas na szukaniu błędu)

  • Nie żałujmy nawiasów.

  • Sygnały i magistrale tworzymy przez:
    wire sygnal;
    wire my_xor = we_a ^ we_b;
    wire enabled = ((nOE == 0)&&(nCE==0));
    wire [7:0] bus = {busHi[3:0], busLo[3:0]} ;
    Oprócz wire zsyntetyzować powinny się co najmniej tri, tri0, tri1, wand, wor (o tym poniżej).

  • Rejestry tworzymy przez:
    reg jedenbit;
    reg [3:0] rejestr4bit;

  • Mała pamięć:
    reg [7:0] mem16B [15:0]; // jak duży jest Twój CPLD? :>

  • Sygnałom możemy przypisać konkretną wartość przez assign:
    assign my_or = bus[0] | bus[1] | bus[2];
    assign output = (nOE == 0) ? rejestr4bit[0] : 1’bz;

  • Rejestrom przypisujemy wartości w bloku always :
    Asynchronicznie:
    always @(x or y) rejestr4bit[0] = x&y;
    always@(*) rejestr4bit[1] = x|y;

    Gdy sygnał sygnal zmienia wartość 0->1:
    always@(posedge sygnal) begin rejestr4bit[2] <= ~rejestr4bit[2]; rejestr4bit[3] <= ~rejestr4bit[3]; end
    (zauważyliście różne operatory przypisania?)

  • Odróżniamy operatory działające na bitach (np. &,|,~) od operatorów działających na wartościach logicznych (np. &&, ||, !)

  • Pamiętamy, że do danego rejestru wartość można przypisać tylko w jednym bloku always.

Lektura do poduszki : Verilog cheat Sheet

ZZZZZZZZ

z’ oznacza w verilogu stan wysokiej impedancji. Gdy przypiszemy do wyjścia wartość ‘z’ - odcinamy je od świata zewnętrznego. Słowem – kluczem jest tutaj ‘wyjście’. Jeśli projekt ma zawierać sygnał / magistralę trójstanową wewnątrz układu, system syntezy odniesie się do tego ze zrozumieniem, przemieli, wyświetli nawet schematy zawierające we właściwych miejscach bufory trójstanowe…, po czym zmieni wszystko na układ logiczny (o czym się można przekonać dopiero analizując log z syntezy ) :)
Stety-niestety synteza układów trójstanowych nie lubi..
(Lubiłaby gdyby mogła. Bufory trójstanowe są jednak powiązane z wyjściami fizycznymi układu i nie ma możliwości przekierowania ich wyjść do wewnątrz.)
Jeśli więc chcemy w układzie zastosować trójstanową magistralę - bo przykładowo tworzymy układ w którym do emulowanego CPU chcemy podłączyć RAM, ROM oraz trzy interfejsy (z góry uprzedzam, że przeciętny CPLD jest za mały, by to wszystko w środku pomieścić, tu już potrzebne jest FPGA) to można użyć magistrali tri (która jest tożsama z wire, ale ładniej się nazywa), tri1 (to samo, lecz już z ‘podłączonym’ układem pullup) lub wand.
W pierwszym przypadku (wire/tri/tri1) pamiętamy, by sygnały podłączone do magistrali miały w stanie nieaktywnym wartość ‘bzzzzz...’, w drugim - ‘b111111…’. W pierwszym przypadku synteza będzie mocno kombinowała co z tym fantem zrobić, w drugim dostaje od nas małą podpowiedź.
NB do danego sygnału / magistrali typu wire / tri można kilkukrotnie przypisać wartość przez assign (lub podpiąć do niej różne moduły) tylko i wyłącznie jeśli przypisywane wartości będą przyjmowały, w którejś chwili wartość ‘z’ (można ją np. zadeklarować jako ‘wire [7:0] bus_d = 8'bzzzzzzzz;’ :)
Ta sama zasada dotyczy także sygnałów/magistral typu tri0/wor (pulldown/wired-or).

Na głęboką wodę.

Przykład bardziej skomplikowanego układu, i równocześnie przykład zastosowania tych elementów kodu, które nie zostały jeszcze wspomniane (np. IF lub CASE).
Dwa słowa wyjaśnienia – jak już pisałem wcześniej, przyjęło się, że ‘programując’ CPLD/FPGA za stan aktywny uważa się zawsze stan wysoki. Aby było full profeska podręcznikowo poprawnie, powinienem wszystkie sygnały procesora, które są aktywne w stanie niskim (nRD, nRW, nMREQ itd.) na samym początku zanegować i dalej już korzystać z sygnałów odwróconych. Nie potrafię. Te sygnały zawsze będą dla mnie aktywne w zerze i kropka. Niestety, powoduje to nieco zamieszania w kodzie, za które przepraszam.
module ti_cpld_a(
        input [15:0] bus_a,
        input [5:0]  ti_in,
        input nRD,
        input nWR,
        input nMREQ,
        input nIORQ,
        input nM1,
        
        inout  [7:0] bus_d, // tu coś nowego - port dwukierunkowy
        
        output nRAMCE,
        output nROMCE,
        output ROMCS,
        output reg [5:0] ti_out
    );

initial
    ti_out = 6'b000000;

// M397 składa się z dwóch niezależnych bloków funkcjonalnych. 
// Jeden obsługuje port I/O, drugi zarządza pamięcią.

// IO - port 0xef = 0b11101111

    // true jeśli komputer odczytuje port 0xef
    wire ioRead = ((nRD == 0) && (nIORQ == 0) && ( bus_a[7:0] == 8'hef)); 
    // true jeśli komputer pisze do portu 0xef
    wire ioWrite = ((nWR == 0) && (nIORQ == 0) && ( bus_a[7:0] == 8'hef)); 

    // JAK działa interface?
    // Jeśli wartość sygnału ioRead jest 'prawdą',
    // to wystaw na szynę danych komputera wartość odczytaną z portu interfejsu.
    // W przeciwnym przypadku, szyna danych przechodzi w stan wysoki.
    assign bus_d[7:0] = (ioRead)?{1'b0,ti_in[5],1'b0, ti_in[4:0] } : 8'bzzzzzzzz;

    // Jeśli zmieni się wartość sygnału ioWrite i jest ona 'prawdą',
    // to wyślij do stacji wartość z szyny danych.
    // W przeciwnym przypadku, pozostaw  bez zmian.
    // można tak :
    // assign ti_out[5:0] = (ioWrite)?{ bus_d[6],bus_d[4:0] }:ti_out[5:0];
    // lub tak
    always@(*)
        if (ioWrite) ti_out[5:0] = { bus_d[6],bus_d[4:0] };
    // oba rozwiązania generują ostrzeżenie o utworzonym zatrzasku.
    
// PAMIĘĆ - adresy 0x00, 0x08, 0x604
    
    // mPaged = 1 - podmieniliśmy pamięci na te z interfejsu.
    reg mPaged = 0;
    // Trzeba odłączyć oryginalny ROM
    assign ROMCS  = mPaged;
    // jeśli pierwsze 8k pamięci - aktywujemy ROM
    assign nROMCE = !((bus_a[15:13] == 3'b000) && mPaged);
    // jeśli drugie 8k pamięci - aktywujemy RAM
    assign nRAMCE = !((bus_a[15:13] == 3'b001) && mPaged);
    
    // true jeśli komputer odczytuje kod rozkazu
    wire mRead = ((nRD == 0) && (nMREQ == 0) && (nM1 == 0)); 
    // Jeśli odczytywany jest kod rozkazu spod adresu 0 lub 8 - przełączamy pamięci.
    // jeśli odczytywany jest kod rozkazu spod adresu 0x604 - załączamy oryginalny ROM.
    always@(*)
        if (mRead) begin
            if    ( (bus_a[15:0] == 16'h0000)||(bus_a[15:0] == 16'h0008)) mPaged = 1;
            else if (bus_a[15:0] == 16'h0604) mPaged = 0;
        end
   // Ten kod także wygeneruje ostrzeżenie o zatrzasku. Uroki układów asynchronicznych.
    
    // I to wszystko. Jak widać definicja interfejsu składa się głównie z komentarzy :)
endmodule

W załączonym pliku znajdują się również kody dwóch innych rozwiązań, nieco inaczej opisujących działanie układu – co widać na poniższej symulacji:




Czy te układy będą działać poprawnie po ‘wypaleniu’ w układzie scalonym?
Niespodzianka – nie wiadomo.
Głównie dlatego, że wykonana symulacja była jedynie zgrubna (nie starałem się dokładnie zasymulować czasów operacji zapisu/odczytu procesora).
A także dlatego, że definitywną odpowiedź może dać jedynie zmontowanie i podłączenie fizycznego układu - i patrzenie co wybuchło (a co nie :)

Co dalej.

Wszystko.
Pragnąłem pokazać jak wygląda proces tworzenia wsadu do CPLD bez zagłębiania się zbytnio w szczegóły, tak by czytelnik mógł ocenić, czy jest to coś z czym pragnie się bliżej zapoznać. W artykule z perfidną premedytacją pominąłem wszystko, co dotyczy zaimplementowania wygenerowanego kodu w fizycznym układzie, trochę bardziej skomplikowanej gramatyki, większości problemów, które można napotkać na każdym kroku, oraz np. kwestię dlaczego nie powinno się tworzyć układów asynchronicznych i arcyważną sprawę przypisania blokującego i nieblokującego1 ( a także teorię układów cyfrowych, algebrę Boole’a, maszyny stanu, wyścigi, metastabilność, typy wyjść, czasówkę, problemy z modelowaniem sferycznych owiec, wejścia dedykowane, makrocele, routing sygnałów i zapewne całą masę innych rzeczy, o których nie mam pojęcia, bo jeszcze się z nimi nie zetknąłem :)

Jeśli po przeczytaniu całości uznasz, że warto zgłębić temat – tematy te same się pojawią we właściwym czasie.

Natomiast jeśli po przeczytaniu całości uznasz że gdzieś plotłem bzdury, nie wahaj się wysłać sprostowania.

1)Nie żartuję. To jest ważne.
Autor: steev

Licencja Creative Commons Artykuł autorstwa steev został wydany na licencji Creative Commons Uznanie autorstwa - Użycie niekomercyjne - Bez utworów zależnych 4.0 Międzynarodowe License.W oparciu o utwór dostępny pod adresem http://speccy.pl/articles.php?article_id=56

Brak komentarzy. Może czas dodać swój?
Dodaj komentarz
Zaloguj się, aby móc dodać komentarz.
Oceny
Tylko zarejestrowani użytkownicy mogą oceniać zawartość strony
Zaloguj się , żeby móc zagłosować.

Brak ocen. Może czas dodać swoją?