Názory k článku Vývoj pro ZX Spectrum: dokončení realizace příkazu PLOT

  • Článek je starý, nové názory již nelze přidávat.
  • 28. 3. 2023 1:29

    _dw

    V kapitole 13. se popisuje samomodifikujici se kod (mimochodem, odkaz vede na jeste nenahrany soubor) a me v hlave hned vyskakuji vykricniky, ze zapominame vratit rutinu do puvodni hodnoty. Ted to funguje jen proto, ze hlavni smycka je nekonecna. Jinak pri opakovanem volani si to bude drzet ten nop...

    Osobne bych to napsal tak, ze tu prepisovaci cast bych dal pred tu rutinu a rutina by se pak dala volat na puvodni odkaz, s tim ze je uz "zadratovana" na nejakou hodnotu (vyhoda oproti "rom" variante s parametrem navic je, ze neztracime rychlost a uvolnime si jeden registr, za cenu toho ze to predratovani je drazsi) a nebo tesne pred ni s dalsim parametrem, ktery by ji prvne nastavil.
    Pokud menime jen jeden bajt tak bych volil instrukci "ld (adresa),A ; 3:13"

    A nacteni "kodu instukce" do A je u nop snadne pres xor A a u zbyvajicich staci zjistit jak vypada prvni bajt instrukce "ld A,n".

    Kod bude pak o jeden bajt delsi, a pokud nam to vadi tak to muzeme jeste zkratit tak ze to opakovane volani dame primo do te rutiny. Tim usetrime 3 bajty.

    fill_in_screen:
            ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami
            ;
            ; vstupy:
            ; žádné
            ld de, SCREEN_ADR         ; 3:10     adresa pro vykreslení prvního bloku znaků
            db 0x3E                   ; 2:7      ld A, n
            cpl                       ;          ld A, cpl
            call draw_ascii_table_init; 3:17     vykreslení 96 znaků
    
            xor  A                    ; 1:4      ld A, nop
            call draw_ascii_table_init; 3:17     vykreslení 96 znaků
            ret                       ; 1:10     návrat z podprogramu
    
    
    draw_ascii_table_init:
            ld (inv_instruction_adr),A; 3:13     adresa bajtu v paměti, který budeme modifikovat
    draw_ascii_table_2x:
            call draw_ascii_table     ; 3:17     vykreslení 2*96 znaků
    draw_ascii_table:
    ...
  • 28. 3. 2023 2:06

    _dw

    Jeste lepsi by asi bylo:

    fill_in_screen:
            ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami
            ;
            ; vstupy:
            ; žádné
            ld de, SCREEN_ADR         ; 3:10     adresa pro vykreslení prvního bloku znaků
    
            call draw_ascii_table_inv ; 3:17     vykreslení 2*96 znaků
    
            xor  A                    ; 1:4      ld A, nop
            call draw_ascii_table_init; 3:17     vykreslení 2*96 znaků
            ret                       ; 1:10     návrat z podprogramu
    
    draw_ascii_table_inv:
            db 0x3E                   ; 2:7      ld A, n
            cpl                       ;          ld A, cpl
    draw_ascii_table_init:
            ld (inv_instruction_adr),A; 3:13     adresa bajtu v paměti, který budeme modifikovat
            call draw_ascii_table     ; 3:17     vykreslení 96 znaků
    draw_ascii_table:
  • 28. 3. 2023 12:41

    radioing

    A jeste
    call draw_ascii_ta­ble_init; 3:17 vykreslení 2*96 znaků
    ret ; 1:10 návrat z podprogramu

    ->
    jmp draw_ascii_ta­ble_init; 3:? vykreslení 2*96 znaků

  • 28. 3. 2023 10:50

    _dw

    Pro zmenu 1 bajtu mas pro zapis jen 3 moznosti:

    db 0x3E, 0x2F  ; 2:7    ld A,cpl
    db 0x3E        ; 2:7    ld A,cpl
    cpl
    ld A, 0x2F     ; 2:7    ld A,cpl

    a "cpl" je tady promenna, takze jsem zvolil tu, kde se to tabulky kodu nemusis divat pokud si jednou zapamatujes to "ld A,n".

    Pro 2 bajty je nejlepsi pouzit HL, jen si dat pozor ze Z80 ma "ld (adr),HL" 2x. Jednu tribajtovou za 16 taktu a druhou, jakou maji ostatni registrove pary DE a BC, ctyrbajtovou za 20 taktu.

  • 28. 3. 2023 1:38

    _dw
    plot_over:
            ; varianta podprogramu pro vykreslení pixelu inverzní barvou
            ;
            ; parametry:
            ; B - x-ová souřadnice (v pixelech)
            ; C - y-ová souřadnice (v pixelech)
            call calc_pixel_address  ; výpočet adresy pixelu
            call calc_pixel_value    ; výpočet ukládané hodnoty
            ld d, (hl)               ; přečíst původní hodnotu osmice pixelů
            xor d                    ; použít vypočtenou masku pro vynulování jediného bitu
            ld (hl), a               ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění)
            ret                      ; návrat z podprogramu
    plot_over:
            ; varianta podprogramu pro vykreslení pixelu inverzní barvou
            ;
            ; parametry:
            ; B - x-ová souřadnice (v pixelech)
            ; C - y-ová souřadnice (v pixelech)
            call calc_pixel_address  ; výpočet adresy pixelu
            call calc_pixel_value    ; výpočet ukládané hodnoty
    plot_over_self:
            xor   (hl)               ; přečíst původní hodnotu osmice pixelů
                                     ; použít vypočtenou masku pro vynulování jediného bitu
            ld (hl), a               ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění)
            ret                      ; návrat z podprogramu
  • 28. 3. 2023 8:16

    atarist

    Se samomodifikujicim se kodem se dalo hrat i jeste pozdeji v dobach DOSu, kdy se v realnem rezimu moc nehralo na kontrolu zapisu do code segmentu. Potom uz to v chranenem rezimu bylo horsi, ale porad jeste nejaka ta zmena navratove adresy na zasobniku byla mozna :) Vlastne nevim, jestli to jeste dneska jde, modifikovat navratovou adresu na zasobniku (assembler na x86/64 mi pripadne nudny :)

  • 28. 3. 2023 8:34

    Ondřej Novák

    Samomodifikující kód je problém jakmile máte paralelismus (vlákna). Naštěstí, některé jazyky mají šablony (a taky máme o dost víc paměti), takže programátor to napíše jednou a překladač vygeneruje všechny varianty kódu, a je schopen to i zoptimalizovat.

  • 28. 3. 2023 9:02

    atarist

    jj jasne. hlavne ale jsou text segmenty (logicky) RO+executable, takze tam zapis nejde v runtime provadet. a to je dobre, akorat to uz neni takova zabava na low level.

  • 28. 3. 2023 10:07

    Calculon

    Tohle je legrace v Go, když někdo z řetězce udělá pomocí unsafe řez bajtů a pak se do něj pokusí něco zapsat. Ten převod přes unsafe je i v stdlib. Zápisy už ne :-)

  • 28. 3. 2023 12:41

    Calculon

    To s tím IMHO přímo nesouvisí, třeba v Rustu se “mut” u řetězce typu &str týká přiřaditelnosti jiné hodnoty do proměnné, konstantní řetězce jsou taky v segmentu paměti, do kterého nelze zapisovat.

  • 28. 3. 2023 10:05

    Calculon

    Tohle jsem nikdy neměl rád ani na Z80, měnit přímo kód, ale se zásobníkem se blblo často, na Z80 šel například jednoduše napsat preemptivní multitasking (přes přerušení a přepínání zásobníků).

  • 28. 3. 2023 10:54

    _dw

    V DOSu si uz musel hlidat vzdalenost meneneho kodu od prave beziciho kodu a ta se lisila podle procesoru pokud vim. Takze ten kod mohl bezet po testovani na tvem CPU, ale jak to bezi jinde nebo v budoucnu si nevedel.

  • 28. 3. 2023 12:07

    Pavel Tišnovský
    Zlatý podporovatel

    ano v ramci toho vstupniho instrukcniho bufferu. Potom to nekdy okolo 486 opravili tak, aby se to chovalo stejne, tj. slo vlastne menit i to, co bylo uz nacteno z RAM do bufferu v CPU (ted nevim, jestli tomu uz tehdy rikali fronta instrukci, nebo jen buffer instrukci, je to davno)

  • 28. 3. 2023 12:37

    Ondřej Novák

    Jasně, změna kódu se neprojeví v instrukční frontě, a ta je různě dlouhá na různých CPU. Některé operace (přepínání režimu CPU např, stránkování), také často vyžadovali exekuci serializační instrukce, která by vyprázdnila instrukční frontu a načetla by instrukce podle nového režimu. Serializace je přitom tak drahá, že samomodifikující kód ztrácí na efektivitě

  • 28. 3. 2023 14:20

    tzl

    Samomodifikující se kód v zásadě je i cokoliv co generuje nativní kód ze zdrojáku za běhu, od jednoduchých toy forthů po slušná prostředí jako sbcl, a je to víc práce než na osmibitech.

    U toy forthu co jsem si zkoušel psát to znamenalo po každém definovaném slově synchronizovat code a data, a snad taky vypnout ochranu na úrovni binárky. A mám pocit že na Armu ty bariéry byly ještě víc pruda.

    Ale jde to, jen to už není rychlejší s tím vším flushováním a predikcemi. I když pořád asi může mít smysl vygenerovat nativní kód pro konkrétní situaci, pokud bude běžet dost dlouho beze změn.

  • 28. 3. 2023 19:32

    _dw

    Kdyz uz se tu bavime i o ne Z80 procesorech, mam tu offtopic otazku.

    Nevite nekdo jak spravne pouzivat instrukci FWAIT?

    Mam kod, ktery dokaze emulovat sin a cos na koprocesoru 8087 jen pri pouziti tangenty, ktere jako jedinou umi. Ale 8087 nevlastnim a i kdybych ho mel tak si nejsem jisty zda nektere varianty treba nebezi v 99% pripadu spravne a...

    Nebyl jsem to schopen pochopit z tech par stripku informaci co jsem na netu nasel.
    Zkousel jsem se i divat co me vygeneruje borland pascal pri spravnych direktivach, ale on tam hazel WAIT uplne vsude.

    https://github.com/DW0RKiN/FSINCOS-i287

    je tam i pokus o test v cecku.

  • 28. 3. 2023 20:02

    Pavel Tišnovský
    Zlatý podporovatel

    tyjo pro 8087 jsem kdysi neco psaval, musel bych dohledat. Ale pokud si dobre pamatuju, musis prakticky davat FWAIT pred kazdou 8087 instrukci, protoze provadeni tech instrukci bezi paralelne k CPU a trva dyl (i nekolik desitek taktu). A hlavni CPU nema jak zjistit, ze uz se dopocitalo, tak FWAIT ceka na nejaky signal, ze je jako fakt dokonceno.

    Jako pokud mas jistotu, ze semtam jednou za 100 taktu zavolas FPU instrukci, tak FWAIT neni nutnej, protoze ten FPU to stihne dopocitat, ale u obecnyho kodu to fakt jak assemblery tak i prekladace strkaji vsude.

    (ono je to tedy interne jeste slozitejsi, protoze FPU nemuze jen tak sledovat datovou sbernici, jestli pro nej jde instrukce, protoze uz 8086 mela prefetch)

  • 28. 3. 2023 20:06

    Pavel Tišnovský
    Zlatý podporovatel

    jeste doplnim (ale to asi uz vis), ze pro 287 uz neni FWAIT potreba. A zrovna jsem dohledal, ze FPATAN trva az 540 taktu, takze ta zminena stovka byla hoooodne optimisticka :)

  • 28. 3. 2023 20:40

    _dw

    Diky za odpoved.

    Jen pro ujasneni, ten wait ma jit fakt pred kazdkou instrukci pro koprocesor? I kdyz si nepredavam data (vysledek) mezi CPU? Jen nasleduji po sobe instrukce matematickeho koprocesoru? Proste dokud pocita, tak mu nesmim predlozit zadnou dalsi instrukci? Protoze stale pocita? Nema zadny vlastni buffer?.. nebo se nestopne provadeni programu jako u normalnich instrukci? hmm...

    Vim, ze tam byla nejaka chyba v hw navrhu.

    Mam to teda chapat, ze CPU cte instrukce a narazi na instrukci pro koprocesor a posle mu ho a pokracuje dal a koprocesor pokud je volny tak to zpracuje nebo se to ignoruje, blokne cely program atd.

  • 28. 3. 2023 21:28

    kvr kvr

    IMHO je to přesně naopak - FWAIT má být *před* instrukcí, která čte výsledek z koprocesoru (ať už přes pamět nebo exceptions). Kromě toho by CPU neměl přepisovat data, které FPU může v té chvíli ještě číst (případně by rovněž vyžadovalo FWAIT - to by ale bylo neefektivní). Viz například https://web.itu.edu.tr/kesgin/mul06/intel/instr/wait_fwait.html .

    Kód, který běží jenom na FPU, synchronizovaný být nemusí, do doby, než se CPU snaží načíst výsledek. Pokud je FPU zahlcen, tak další instrukce (pro FPU) musí počkat, ale to platí i dnes. Jak reagovalo 8086 v případě zahlcení jednoho proudu, netuším.

  • 28. 3. 2023 21:42

    Pavel Tišnovský
    Zlatý podporovatel

    "Jak reagovalo 8086 v případě zahlcení jednoho proudu, netuším." 8087 by mel v tomto pripade vystavit signal BUSY, ktery 8086 muze cist pres pin TEST prave ve FWAIT https://i.imgur.com/jNRiukR.jpg

  • 28. 3. 2023 21:38

    Pavel Tišnovský
    Zlatý podporovatel

    Asi nejlepsi popis je tady https://www.ques10.com/p/10902/explain-interfacing-of-8087-co-processor-with-80-1/ viz bod cislo 12. To "posilani instrukci" do FPU neni az tak uplne pravda, protoze 8086/8088 sice generuje adresy pro adresovou sbernici a cte si opkody po datove sbernici, ale FPU tento "bzukot" sleduje a ty opkody si taky bere a dekoduje, jestli to neni nejaka ESC-instrukce pro nej. Pro nejake instrukce dokonce od CPU prevezme sbernice a resi si svoje DMA (DMA z pohledu CPU). Tj. CPU nijak aktivne nezjistuje, ze ma ESC-instrukci a neposila ji do FPU, tu instrukci proste jen ingoruje - NOPuje ji.

    Potom pro vetsi zmateni se sice mluvi o "frontach" (queue) na strane FPU, ale to je jen (AFAIK) pro jednu instrukci - tam se rozlisuje, jestli je fronta prazdna, plna, cte se prvni bajt s opkodem nebo uz bajty s parametry.

  • 28. 3. 2023 22:42

    _dw

    Me ten bod 10. nedava smysl:

    Once 8087 gets its operand, it begins processing by making the BUSY output high. This BUSY output is connected to the TEST input of the microprocessor. Now 8087 execute its instruction and 8086 moves ahead with its next instruction. Hence multiprocessing takes place.

    Je tam hw... ach jo, rika to ze FPU nacte instrukci a nastavi nejaky signal na nejakou hodnotu a to je propojene na vstup neceho jineho co se pouziva pro WAIT. FPU vykona instrukci a teprve pak CPU se posune dal a tvrdi ze tohle je multiprocesing. Uhm.

    Zato ten bod 12 tvrdi ze WAIT je potreba jen pro vysledek FPU pokud to chapu taky dobre.

  • 28. 3. 2023 23:11

    kvr kvr

    > FPU vykona instrukci a teprve pak CPU se posune dal a tvrdi ze tohle je multiprocesing.

    Ne. FPU začne vykonávat instrukci a nastaví BUSY signál a vypne BUSY až když svou práci dokončí. CPU se posouvá dál hned a může mezitím zpracovávat svoje vlastní instrukce. Až když narazí na WAIT, tak musí počkat na vypnutí BUSY. Takže skutečně v té době mezí první instrukcí a WAIT pracují paralelně.

  • 29. 3. 2023 1:25

    _dw

    Pokud ale FPU nema v podstate bufer tak se to trosku zjednodusuje (rozchazeni buferu pokud bezi kazdy paralelne), ale co se teda presne deje a jak se zase sejdou?

    pocatek
    strasne pomala instrukce FPU
    instrukce cpu / fpu jeste pocita
    instrukce cpu /fpu jeste pocita
    instrukce fpu pro ulozeni vysledku ale fpu jeste pocita prvni fpu instrukci
    instrukce cpu / fpu stale pocita
    wait
    ted se jako sejdou ale fpu preskocilo svoji jednu instrukci???
    instrukce cpu nad vysledkem

    A mohl bych pridat jeste scenar kdy fpu je busy a prichazi dalsi instrukce fpu co ale potrebuje pomoct s vypoctem adresy (no vlastne je to scenar nahore, protoze fpu uklada)

    A jak muze bezet

    ;THE NEXT 3 INSTRUCTIONS DO ALL THE HARD WORK
    ADD-LOOP:FADD     DWORD PTR [BX] ; 105 clocks  DWORD=> SINGLE PRECISION
             ADD      BX,4           ;  4 clock        READY FOR NEXT ELEMENT
             LOOP     ADD_LOOP       ; 17 clock

    Kdyz mezitim co FPU pocita prvni FADD tak cpu uz je u cca pateho FADD? To CPU pocital adresy v druhe, tretim a ctvrtem pripade zbytecne? To by dost zrychlilo algoritmus.. :D ale vysledek by nestal ani za tu 1/5 casu co to spocitalo.

    Kdyby to bylo

    add_loop
    fwait (poprve ceka testuje zbytecne)
    fadd ...
    add bx,4
    loop

    fwait (cekani na posledni fadd)
    ulozit vysledek z fpu
    fwait (cekani na ulozeni vysledku)
    zobrazit ho

    tak by me to davalo smysl.

  • 29. 3. 2023 4:20

    kvr kvr

    Předpokládám, že se to bude lišit dle implementace, ale IMHO byla FPU spíš napůl něco jako ALU než plnohodnotné CPU - třeba parsování nejspíš musí proběhnout kvůli proměnlivé délce už na CPU a stejně tak adresa operandů se předává z CPU registrů.

    Pokud je instrukční buffer velikosti 0, tak každá další instrukce, je-li FPU ještě aktivní, skončí zablokováním CPU, dokud se FPU opět neuvolní. Což by řešilo problémy výše jako kalkulaci adresy operandu na základně aktuálních hodnot registrů apod.

    Tohle je spíš moje naivní pochopení z hlediska SW a můžu se plést. Modernější CPU už měly superscalar a out-of-order, takže tyhle věci řešily efektivněji, včetně většího bufferu, kolize mezi registry atd. V obou případech ale FPU nemohlo pochopitelně přeskakovat instrukce, maximálně jeden z nich nic nedělal, bo čekal na výsledek toho druhého - to je ale pořád běžné i u moderních CPU.

    Pro ten příklad bude nejspíš dostatečné:

    fld0
    add_loop:
    fadd.f st(0), [bx]
    add bx, 4
    loop
    fstp.f [sp]
    fwait // sync before result use
    mov ax and bx, [sp] // or whatever other use

  • 29. 3. 2023 4:59

    kvr kvr

    Teď jsem si ještě důkladněji přečetl citaci z té knihy níže a defakto potvrzuje, co jsem napsal: pouze jedna běžící instrukce (zajištěno CPU/FPU), kód musí hlídat pomocí FWAIT jenom situace, kdy nastává kolize v paměti a CPU si mezitím může měnit registry jak chce. I ten cyklus je defakto stejný. IMHO je to docela logické.

    Otázka je, na co to potřebuješ - máš ještě někde 8088? :-D

  • 29. 3. 2023 10:54

    _dw

    OMG!
    Myslim ze jsem to teda konecne pochopil.
    Takze mezi FPU instrukcemi neni poreba zadny WAIT, ale blokne to nezavisle provadeni CPU.
    Konecne ma logiku proc autor v jedne knize pise neco co jsem spatne pochopil a pak ukazuje kod bez wait.
    WAIT teda ani neni potreba, mohu si stopnout po odeslani vysledku pro CPU (eh spatna formulace) CPU tim, ze za tu instrukci jeste nez si vysledek CPU vyzvedne strcim dalsi FPU instrukci.
    Asi to bude diky prefixu delsi instrukce, ale muze to byt zacatek nejakeho dalsiho vypoctu.

    PS: Nepotrebuji to, jen jsem mel kod pro ten vypocet sinu a cosinu na 8087, kterou kupodivu neimplementuje, ale jen tangent a prislo me to nemozne z toho vytahnout a bral jsem to jako hadanku, matematickou, jak funguje matematicky koprocesor, jeho zasobnik atd. A za 5 let konecne zjistim tu nejtezsi cast kodu... jak spravne napsat WAIT.

    A to tu padly jeste informace jeste co si muze CPU nezavisle dovolit a na co nesmi sahat. Moc pekne. Dekuji. A sorry za spam.

  • 30. 3. 2023 14:44

    _dw

    Myslel jsem si ze tomu uz rozumim, ale kdyz jsem se znovu dival na zdroje z noveho uhlu pohledu tak jsem narazil u knihy
    8087 Applications Programming IBM-PC and Other PCs od Richarda Startzeho na vetu:

    The assembler automatically places a WAIT instruction in front of every 8087 instruction.

    To by melo znamenat ze pred FPU instrukci se nedava WAIT, aby CPU pockalo ze FPU je volne a mohly spolecne nacist dalsi instrukci jen proto, ze to WAIT vlozi do binarky pred KAZDOU FPU instrukci prekladac sam...
    To je presny opak nez jsem si myslel a navic uplne necekana vec ze se me bude zdrojak v asembleru prekladat jinak nez ho napisi (pokud zvolim spravny prekladac), a nanestesti to s timhle pristupem dava vsechno smysl taky. Na kod z knihy se uz divat nemuzu, ten me uz nic nenapovi, ani jiny zdrojak...

    Hledal jsem dal a narazil na "cerstvy" clanek https://www.os2museum.com/wp/learn-something-old-every-day-part-vii-8087-intricacies/ o detekci zda je 8087 pritomen. To me dalo odpoved co se stane kdyz na ciste 8086 napisete wait, proste se blokne, bude sice vykonavat preruseni, ale pokud jsme to necekali tak se z toho asi ndostane.

    Hledal jsem dal a ve vlakne http://computer-programming-forum.com/46-asm/e6d7eab90bbcb1c2.htm jsou odpovedi

    There is no need to code the FWAIT as the assembler does that
    automatically (at least TASM does) if the co-processor is 8087. The
    co-processors from 287 on do not need it and so it one puts ".286" at
    the top of the program no FWAITs are generated by the assembler.

    Osmo

    Several have already answered this question, but none (IMO) have
    given you a COMPLETE answer.
    In the old days when the 8087 chip was a seperate chip from the
    interger processor, the Wait operation was used to syncronize the
    two processors. The assembler supported two forms of most of the
    8087 instructions: the normal ones that began with an 'F' and the
    others that began with an 'FN'. Those with an 'F' automatically
    generated the necessary Wait operations for you. If a co-processor
    instruction was executed without a Wait and the 8087 was not finished
    with the previous instruction, that instruction would be aborted and
    the new one started. A prime example of a time when you would not
    want to wait for the last instruction to finish is when you are about
    to initialize the co-processor, so they had an FNINIT instruction
    for that purpose (what do you care if it is done if you are about to
    clear his brain anyway?). Other instructions without wait include:
    FNSTENV FNSTCW FNSTSW FNSAVE FNCLEX FNDISI FNENI
    There are other times when executing a floating point instruction
    without the Wait is useful (to gain maximum speed). For example let's
    say you start one floating point operation and then do some stuff in
    the x86. Now if you KNOW that there has been enough time elapsed for
    the x87 to have completed the task, there is no need to waste the extra
    cycles on the Wait. Most assemblers don't give you this level of
    control however. :( I wrote one a long time ago, but haven't used
    it in years.
    I haven't done any floating point since the old days so I'm not an
    expert on the intergrated co-processor designs, buy it is my
    understanding that the Wait is not necessary with these newer systems.
    I would hope if this is the case that the assemblers would omit them
    if told it was assembling for one of these modern machines.

    Jim Neil

    A zde je kod co jsem dal do TASM 4

    .model TINY
    .code
    .8087
    
    org 100h
    
    ;----------------- Program Entry point --------------------------
    ; input st = rad, rad >= 0
    ; output st = cos(rad), st(1) = sin(rad)
    SINCOS:
        PUSH    AX                   ;
        FLD     PiDiv4_80bit         ; pi/4, x
        FXCH                         ; x, pi/4
        FPREM                        ; x mod pi/4, pi/4
        FSTSW   Status               ; FPU Status Word => AX
                                     ; Status Word:   B,  C3, top, top, top,  C2,  C1,  C0, ...
                                     ;        Flag:  SF,  ZF, res,  AF, res,  PF, res,  CF
        WAIT                         ;
        MOV     AX,word ptr [Status]
        MOV     AL, AH               ;
        SAHF                         ; AH->Flag, ZF = C3
        JNZ     zero_c3              ;
        XOR     AL, 255              ; C3 xor C1, C3 xor C0
    zero_c3:
    
        TEST    AH, 2                ; C1?
        JZ  without_complement
                                     ; tan ( pi/2 - x ) = sin ( pi/2 - x ) / cos ( pi/2 -x ) = cos ( x ) / sin ( x ) = 1 / tan ( x )
        FSUBR   st, st(1)         ; y = pi/4 - (x mod pi/4), pi/4
    without_complement:
    
        FSTP    st(1)                ; y
        FPTAN                        ; 1, tan(y)     0 <= y <= Pi/4
    
        FLD     st(1)                ; tan, 1, tan
        FMUL    st, st               ; tan^2, 1, tan
        FADD    st, st(1)            ; 1+tan^2, 1, tan
        FSQRT                        ; (1+tan^2)^0.5, 1, tan
        FXCH    st(2)                ; tan, 1, (1+tan^2)^0.5
    
        TEST    AL, 2                ; C3 xor C1
        JZ      swap_sincos
        FXCH                         ; 1, tan, (1+tan^2)^0.5
    swap_sincos:
    
        FDIV    st, st(2)            ; sin, 1, (1+tan^2)^0.5
        SAHF                         ; AH->Flag, CF = C0
        JNC     plus_sin
        FCHS                         ; -sin, 1, (1+tan^2)^0.5
    plus_sin:
        FXCH    st(2)                ; (1+tan^2)^0.5, 1, +-sin
        FDIVP   st(1), st            ; cos, +-sin
    
        TEST    AL, 1                ; C3 xor C0
        JZ      plus_cos
        FCHS                         ; -cos, +-sin
    plus_cos:
    
        POP     AX                   ;
        RET
    
    
        PiDiv4_80bit    dt  3ffec90fdaa22168c235h
    ; Uninitialized data.
        Status label Word
        org $+2
    
    END SINCOS

    A tohle me vylezlo

    0x0000000000000000:  50             push   ax
    0x0000000000000001:  9B             wait
    0x0000000000000002:  DB 2E 58 01    fld    xword ptr [0x158]
    0x0000000000000006:  9B             wait
    0x0000000000000007:  D9 C9          fxch   st(1)
    0x0000000000000009:  9B             wait
    0x000000000000000a:  D9 F8          fprem
    0x000000000000000c:  9B             wait
    0x000000000000000d:  DD 3E 62 01    fnstsw dword ptr [0x162]
    0x0000000000000011:  9B             wait
    0x0000000000000012:  A1 62 01       mov    ax, word ptr [0x162]
    0x0000000000000015:  8A C4          mov    al, ah
    0x0000000000000017:  9E             sahf
    0x0000000000000018:  75 02          jne    0x1c
    0x000000000000001a:  34 FF          xor    al, 0xff
    0x000000000000001c:  F6 C4 02       test   ah, 2
    0x000000000000001f:  74 03          je     0x24
    0x0000000000000021:  9B             wait
    0x0000000000000022:  D8 E9          fsubr  st(1)
    0x0000000000000024:  9B             wait
    0x0000000000000025:  DD D9          fstp   st(1)
    0x0000000000000027:  9B             wait
    0x0000000000000028:  D9 F2          fptan
    0x000000000000002a:  9B             wait
    0x000000000000002b:  D9 C1          fld    st(1)
    0x000000000000002d:  9B             wait
    0x000000000000002e:  D8 C8          fmul   st(0)
    0x0000000000000030:  9B             wait
    0x0000000000000031:  D8 C1          fadd   st(1)
    0x0000000000000033:  9B             wait
    0x0000000000000034:  D9 FA          fsqrt
    0x0000000000000036:  9B             wait
    0x0000000000000037:  D9 CA          fxch   st(2)
    0x0000000000000039:  A8 02          test   al, 2
    0x000000000000003b:  74 03          je     0x40
    0x000000000000003d:  9B             wait
    0x000000000000003e:  D9 C9          fxch   st(1)
    0x0000000000000040:  9B             wait
    0x0000000000000041:  D8 F2          fdiv   st(2)
    0x0000000000000043:  9E             sahf
    0x0000000000000044:  73 03          jae    0x49
    0x0000000000000046:  9B             wait
    0x0000000000000047:  D9 E0          fchs
    0x0000000000000049:  9B             wait
    0x000000000000004a:  D9 CA          fxch   st(2)
    0x000000000000004c:  9B             wait
    0x000000000000004d:  DE F9          fdivp  st(1)
    0x000000000000004f:  A8 01          test   al, 1
    0x0000000000000051:  74 03          je     0x56
    0x0000000000000053:  9B             wait
    0x0000000000000054:  D9 E0          fchs
    0x0000000000000056:  58             pop    ax
    0x0000000000000057:  C3             ret
    0x0000000000000058:  35 C2 68       xor    ax, 0x68c2
    0x000000000000005b:  21 A2 DA 0F    and    word ptr [bp + si + 0xfda], sp
    0x000000000000005f:  C9             leave

    509bdb2e58019­bd9c99bd9f89bdd3e62
    019ba162018ac­49e750234fff6c40274
    039bd8e99bddd­99bd9f29bd9c19bd8c8
    9bd8c19bd9fa9­bd9caa80274039bd9­c9
    9bd8f29e73039­bd9e09bd9ca9bdef9a8
    0174039bd9e05­8c335c26821a2da0fc9
    fe3f
    https://shell-storm.org/online/Online-Assembler-and-Disassembler/

    Pokud to drobne upravim pro 286 (nactu status rovnou do AX, ne pres pamet, jinak to pry 8087 neumi)

    50db2e4201d9c­9d9f89bdfe09b8ac49e
    750234fff6c40­27402d8e9ddd9d9f2d9
    c1d8c8d8c1d9f­ad9caa8027402d9c9­d8
    f29e7302d9e0d­9cadef9a8017402­d9e0
    58c335c26821a­2da0fc9fe3f

    0x0000000000000000:  50             push   ax
    0x0000000000000001:  DB 2E 42 01    fld    xword ptr [0x142]
    0x0000000000000005:  D9 C9          fxch   st(1)
    0x0000000000000007:  D9 F8          fprem
    0x0000000000000009:  9B             wait
    0x000000000000000a:  DF E0          fnstsw ax
    0x000000000000000c:  9B             wait
    0x000000000000000d:  8A C4          mov    al, ah
    0x000000000000000f:  9E             sahf
    0x0000000000000010:  75 02          jne    0x14
    0x0000000000000012:  34 FF          xor    al, 0xff
    0x0000000000000014:  F6 C4 02       test   ah, 2
    0x0000000000000017:  74 02          je     0x1b
    0x0000000000000019:  D8 E9          fsubr  st(1)
    0x000000000000001b:  DD D9          fstp   st(1)
    0x000000000000001d:  D9 F2          fptan
    0x000000000000001f:  D9 C1          fld    st(1)
    0x0000000000000021:  D8 C8          fmul   st(0)
    0x0000000000000023:  D8 C1          fadd   st(1)
    0x0000000000000025:  D9 FA          fsqrt
    0x0000000000000027:  D9 CA          fxch   st(2)
    0x0000000000000029:  A8 02          test   al, 2
    0x000000000000002b:  74 02          je     0x2f
    0x000000000000002d:  D9 C9          fxch   st(1)
    0x000000000000002f:  D8 F2          fdiv   st(2)
    0x0000000000000031:  9E             sahf
    0x0000000000000032:  73 02          jae    0x36
    0x0000000000000034:  D9 E0          fchs
    0x0000000000000036:  D9 CA          fxch   st(2)
    0x0000000000000038:  DE F9          fdivp  st(1)
    0x000000000000003a:  A8 01          test   al, 1
    0x000000000000003c:  74 02          je     0x40
    0x000000000000003e:  D9 E0          fchs
    0x0000000000000040:  58             pop    ax
    0x0000000000000041:  C3             ret
    0x0000000000000042:  35 C2 68       xor    ax, 0x68c2
    0x0000000000000045:  21 A2 DA 0F    and    word ptr [bp + si + 0xfda], sp
    0x0000000000000049:  C9             leave

    Tak me to udela jeden wait navic pred FNSTSW, ale to jen proto, ze jsem mel puvodne FSTSW (mozna je ta instrukce jen symbolicka spojenim techto dvou, nevim)

  • 30. 3. 2023 14:47

    _dw

    Mimochodem se me sptane prelozila ta 10 bajtova hodnota, chybi nejvyssi 2 bajty.
    Asi jsem to mel napsat jako deset db a neresit.

    30. 3. 2023, 14:50 editováno autorem komentáře

  • 30. 3. 2023 14:52

    Pavel Tišnovský
    Zlatý podporovatel

    takze TL;DR; TASM v rezimu .8087 vklada WAIT vsude, kdezto pro 80287 a vys uz ne. Hele to si otestuju, protoze TASM tady mam v DOSBoxu :)

    Teoreticky, kdybys moc chtel, tak zkusim i Turbo Pascaly nejake starsi verze v 8087 rezimu a porovnat.

  • 12. 4. 2023 14:52

    PaJaSoft

    Ahoj všeci,
    omlouvám se, ale jsem poněkud staršího data vydání (**), proto bych možná k tomuto tématu měl co říci. Na druhou stranu lovím z opravdu dlouhodobé paměti a tak moje vývody nijak neozdrojuji (až se přestěhuji, mohu dát do placu nějakou literaturu k tomuto) za což se omlouvám (v tom případě doporučuji domluvit se s českou komunitou lidí, kteří tento HW mají dosud v provozuschopném stavu).

    TL;DR;: Komunikace s I8087 silně připomínala v tehdejší době obvyklý a obdobný přístup pro LPT port. V zásadě - buď bylo zařízení (v tomto případě FPU) BUSY a tak na nic nového nereagovalo (*) a mohlo dojít ke ztrátě dat nebo bylo "free" a reagovalo dle dokumentace.

    Ten FWAIT měl dvě funkce:
    1) zastavit CPU do okamžiku, kdy FPU bude schopno komunikace (a je jedno proč bylo v daném okamžiku BUSY) - tedy v reálu to FPU udělalo NOOP nebo okamžitě povolilo načítání dalšího OPu z paměti pro CPU/FPU
    2) zastavit CPU do okamžiku, kdy FPU dokončí poslední operaci a tedy jedině poté má smysl se zabývat výsledkem FPU procesu

    To vysvětluje, proč je v tom takový chaos - typické použití bylo - pokud program věděl s 100% jistotou, že doposud FPU nepoužil, pak mohl hrnout na FPU první instrukci bez předchozího FWAITu (předpokládejme, že po RESET a POSTu BIOSu do běhu této instrukce se FPU stihl inicializovat). Pokud to však jistota nebyla, tak FWAIT byla nejlevnější cesta, jak to "neřešit" a v případě potřeby počkat. Vzhledem k tomu, že i v dřevních dobách měl i ASM překladač něco jako "knihovny sub-rutin" - tak jednoduše neměl informaci, kdy se FPU poprvné použilo (což by za určitých okolností šlo zjistit, ale vede to na obarvený graf a to jak víme bez run-time analýzy cest v tomto případě to není triviální zjistit) a proto to tam vkládal pro I8087 explicitně *před* každou FPU instrukcí (což pro slušnější překladače šlo řídit na cmdline). Mám za to, že I80287 to změnila právě v tom smyslu, že další OP pro FPU v případě BUSY na FPU zablokoval i CPU do uvolnění BUSY FPU (což bylo legální použití, nebylo povinností si výsledek z FPU po každé vyzvednout, šel klidně přepsat a nebo první instrukce FPU přenastavila do výchozího stavu a teprve výsledek 2. instrukce nás zajímal zpět v CPU), pro I8087 hrozila ztráta viz (*).

    Jako stěžejní to však bylo pro případ #2 - vzhledem k architektuře jste neměli nikdy jistotu, že se paralelní běh CPU a FPU opravdu "potká" tak, jak jste jako programátor zamýšleli a třeba na takty (z dokumentace) spočetli (ono v tom manuálu bylo taky dost chyb a nepřesností a někdy i revizní změny bez propisu do tištěné dokumentace, Gopher, ani WWW neexistoval...). Jediný způsob jak jsem si jako programátor mohl být jist, že jsem in-sync mezi CPU a FPU bylo právě to *explicitní* použití FWAIT, který vedle na NOOP pokud obě jednotky byly cajk nebo na blokování CPU dokud nebylo dosaženo definovaného stavu FPU. A pokud si dobře pamatuju, šlo se dostat i do stavu infinite waiting...:-)

    A ještě bych zmínil třetí věc, která tu zatím nezazněla - socket pro I8087, I80287 a I80387 byl samostatný a obvykle neosazený z důvodů ekonomických. Myslím, že až I80486 v sobě kombinoval neoddělitelně CPU+FPU, což zase vedlo k problémům, že tu byli třetí výrobci CPU - klonu I80486 - kteří své zboží malovali jako CPU I80486, ale ve skutečnosti FPU (~I80487) zapoměli přibalit do výbavy... (myslím, že např. takový Cyrix např. Cx486SLC, což navíc nebyl ani plnohodnotný klon I80486 bez FPU, alébrž I80386 a to dokonce jen SX (poloviční datová sběrnice) - to jen, aby došlo ještě k lepšímu zmatení jazyků a naivních oveček). Sám jsem si I80387 pro své I80386DX kupoval a osobně osazoval do patice na MB o pár let později... Řešení dvě - buď programátor situaci detekoval předem a v případě nepřítomnosti FPU zvolil jinou impl., vlastní emulaci či "FPU not found error" nebo se prostě spoléhal, že si potřebný runtime environment vyřeší ten, kdo kód spustil. Jenže to vedlo k jedné věci - co dělat v případě, že instrukční fronta narazí na FPU instrukci (pro kterou nám chybí zpracovatel = FPU). Inu jak bylo v tehdejší době zvykem vyvoláme přerušení Invalid instruction - a jak asi tušíte, na to mohl nasadit "hook" SW implementace takové FPU instrukce, což se přesně tak dělo a dělalo. SW implementace těchto instrukcí byl pěkně mastně drahý kus SW na tu dobu...

    Z předchozího odstavce by vám měl být také jasný závěr, že FWAIT je naprosto esenciální a nezbytná výbava, protože vaše teoretické výsledky počítání taktů z dokumentace Intelu vám v daném reálném exekučním prostředí byly naprosto k ničemu, záleželo totiž na tom, zda-li byl I8087 (a vyšší) opravdu přítomen a pokud nikoli, na vlastní implementaci té konkrétní FPU instrukce tím konkrétním FPU emulátorem... - ASM programátor se nemohl spolehnout na nic (a to ani na znalost SW implementace v konkrétním FPU emulátoru, protože CPU a obsluha přerušení např. (co takový keyboard)).

    Kódování zdar!
    PaJaSoft

    (*) v případě LPT došlo prostě ke ztrátě/ignora­ci/zahození dat, které jste na LPT (tiskárnu) poslali a vše se úspěšně odignorovalo až do okamžiku, kdy bylo BUSY dole - jinými slovy, pro správnou komunikaci se testoval "zákmit" ACK *i* BUSY, vím, že spousta ovladačů to dělala "optimalizovaně" a blbě, což za určité konstelace hvězd se neprojevilo, ale správně to nebylo. V případě FPU si nejsem jist, zda-li (nepřesná) Intel dokumentace explicitně říkala, že výsledek je definovaný jako X, nebo prostě obligátní N/A, takže bylo na programátorovi, aby s FPU komunikoval dle "kontraktu" a nic jiného nebylo validní/povolené(= definované)/správ­né.

    (**) ano, opravdu osobně pamatuji práci na stroji s Hercules gr. kartou, oranžovým či zelený skutečným CRT "monitorem", případně Merkur televizí, který i Linux kernel v0.9 honil na 2MiB RAM, případně si vybíral, zda-li v kernelu bude mít TCP/IP stack nebo tu trochu paměti neoželí a použije ji pro "vývoj/ladění" non-network kódu atd.

    12. 4. 2023, 14:56 editováno autorem komentáře

  • 12. 4. 2023 17:04

    Pavel Tišnovský
    Zlatý podporovatel

    díky moc za doplnění!

    Jo to zmatení pro 486 bylo velké. Hlavně ty čipy byly vlastně všechny DX, ale pokud zrovna mat. koprocesor v testech ve výrobě nefungoval, udělala se z toho přepálením jednoho bitu SX a bylo to (proč ne, když to zvyšovalo výtěžnost).

  • 29. 3. 2023 18:24

    kvr kvr

    > WAIT teda ani neni potreba, mohu si stopnout po odeslani vysledku pro CPU (eh spatna formulace) CPU tim, ze za tu instrukci jeste nez si vysledek CPU vyzvedne strcim dalsi FPU instrukci.

    Tohle bych nedělal - to bude spoléhat na instrukční buffer délky 0, což je implementační detail, který nemusí být pravda (i když tedy asi je u prvních procesorů). On ten FWAIT tak drahý není a za "běžné" implementace stejně doba čekání na výpočet bude dramaticky vyšší než jedna instrukce navíc.

    Jinak ok.

  • 28. 3. 2023 21:53

    _dw

    Zda se ze ne vsechny informace o 8087 jsou ztraceny.
    Nasel jsem ted knihu http://vtda.org/books/Computing/Programming/8087ApplicationsProgrammingIBM-PC_OtherPCs_RichardStartz.pdf

    A ta uvadi:
    Co-processor Organization

    The 8087 is designed as a co-processor for the 8088 CPU. Both th_e 8087
    and 8088 "look" at each instruction fetched from memory. The 8087 acts
    on its own instructions and ignores those belonging to the 8088. When
    the 8088 sees an 8087 ins-truetion, which is an 8088 ESCape instruction,
    it calculates the address of any data referenced by the instruction and
    reads-but ignores- one byte of data from this address. Otherwise, the
    8088 treats the 8087 instruction as a null operation. The 8087 copies the
    addLess calculated by the 8088 and uses it to store or fetch data to and
    from memory. In this way, the co-processor design allows the 8087 and
    the 8088 to execute simultaneously, considerably enhancing total system
    performance.
    To ensure properly coordinated parallel operation, 8087/8088 programs
    must follow the following synchronization rules:
    • The 8088 must not change a memory location referenced by an 8087
    instruction until the 8087 is finished. The 8088 is free to change its
    own internal registers and flags.
    • A second 8087 instruction must not be fetched until the current
    operation is complete. (Under special circumstances it is possible to
    safely violate this rule, but such circumstances do not generally occur
    in application programs.)
    Synchronization, obedience to both rules, is achieved through judicious use of the 8088 WAIT instruction. The WAIT instruction tells the
    8088 to suspend processing until the TEST line becomes active. (The 8088
    checks the TEST line status once every microsecond.) When the 8087
    begins an instruction, it sets the TEST line to inactive. It then resets the
    TEST line to active when the instruction is complete.
    The programmer has responsibility fo seeing that the first rule is
    obeyed. To ensure synchronization, code an FWAIT instruction after an
    8087 instruction and before an 8088 instruction whenever the two instructions access the same memory location. (Except that the FWAIT may
    be omitted if neither instruction changes the memory location.) FW AIT
    generates an 8088 WAIT instruction. (Use of the mnemonic "FWAIT,"
    for "floating wait," is a software convention.) FWAIT holds the 8088 until
    the 8087 operation is complete, thus preventing violation of the first rule.
    Responsibility for implementing the second rule is left to the assembler.
    The assembler automatically places a WAIT instruction in front of every
    8087 instruction. Thus the 8088 will suspend processing and not fetch
    another 8087 instruction so long as a previous 8087 instruction is still
    being executed.
    Programs violating either of the two rules will have unpredictable results. Possible outcomes include the computer coming to a dead halt (if
    you are lucky), and having random numbers presented as final results
    (if you are not so lucky).

    A prvni priklad je:
    str 60-61 (pdf)
    str 50-51 (book)

    Our First Program-Adding Up An Array of Numbers

    Our first program is picked to show off the speed and ease in using the
    8087. This program runs about 200 times faster than an equivalent BASIC
    program without the 8087!

    To write a complete 8087 program, we need a number of details that
    we haven't covered. For example, we really ought to specify how the
    routine gets its arguments from BASIC. In the interest of preserving
    everyone's sanity, we are going to cheat just this once by leaving out
    some details. Therefore, the program below won't run as it stands. (The
    program appears in full in Chapter 9.)

    We assume that, elsewhere in the program, someone has already defined a single precision array named ARRAY. Our task is to add up the
    numbers stored in ARRAY and place a single precision result in a variable
    named DSUM. The integer variable N has the number of elements in
    ARRAY. (ARRAY goes from ARRAY(0) to ARRAY(N-1)). A fragment of
    a BASIC program to do the job follows:

    10 DEFDBL D
    20 DEFINT I
    30 DSUM=0
    40 FOR I=O TO N-1
    50 DSUM=DSUM+ARRAY(IJ
    60 NEXT I

    Notice that we collected the sum in a double precision variable to ensure
    getting at least single precision accuracy for the final answer.

    Our 8087 code appears below. The program assumes that ARRAY is
    an array of single precision memory locations, that N holds a non-negative integer, and that DSUM is a double precision memory location.
    6 a Simple Instruction Set 51
    Everything on a line after a semicolon is a comment. We have used
    comments to number the lines and mark each instruction as either an
    8088 or an 8087 instruction.

                   MOV CX,N          ;1 {8088}
                   FLDZ              ;2 {8087}
                   JCXZ  DONE_ADDING ;3 {8088}
                   MOV   BX, 0       ;4 {8088}
    LOOP_TOP:      FADD  ARRAY[BX]   ;5 {8087}
                   ADD   BX,4        ;6 {8088}
                   LOOP  LOOP_TOP    ;7 {8088}
    DONE_ADDING:   FSTP  DSUM        ;8 {8087}

    Doplneny kod z kapitly 9.

    book str 86.
    
    ;SUBROUTINE SUM(ARRAY,N,DSUM)
    ; ASSUMPTIONS: ARRAY IS A SINGLE PRECISION ARRAY OF LENGTH N
    ;              N IS AN INTEGER
    ;              DSUM IS DOUBLE PRECISION
    
    
             PUBLIC   SUM
    CSEG     SEGMENT  'CODE'
             ASSUME   cs:CSEG
             PROC     FAR
             PUSH     BP
             MOV      BP,SP
             MOV      BX,[BP]+10     ; BX=ADDR(ARRAY)
             MOV      SI,[BP]+8      ; SI=ADDR(N)
             MOV      CX,[SI]        ; CX=N
    ;
    ;NOW ALL SET UP TO GO
             FLDZ                    ; INITIALIZE ST=0
             CMP      CX,OH          ; HOPE N > 0
             JLE   DONE
    ;THE NEXT 3 INSTRUCTIONS DO ALL THE HARD WORK
    ADD-LOOP:FADD     DWORD PTR [BX] ;DWORD=> SINGLE PRECISION
             ADD      BX,4           ;READY FOR NEXT ELEMENT
             LOOP     ADD_LOOP
    DONE:
    ;
    ;NOW FILE ANSWER BACK IN DSUM
             MOV      DI,[BP]+6      ;DI=ADDR(DSUM)
             FSTP  QWORD PTR [DI]    ;QWORD=> DOUBLE PRECISION
                                     ; DSUM IS NOW PUT AWAY
             POP   BP
             FWAIT                   ;BE SURE 8087 IS DONE
             RET
    SUM      ENDP
    CSEG     ENDS
             END

    Takze dava wait jen pri cekani CPU na vysledek.

  • 28. 3. 2023 22:26

    _dw

    A aby to nebylo malo tak jsem se ted dival na takty "tech 3 nepracovitejsich instrukci"

    ;THE NEXT 3 INSTRUCTIONS DO ALL THE HARD WORK
    ADD-LOOP:FADD     DWORD PTR [BX] ; 25 microsecond/105 clocks  DWORD=> SINGLE PRECISION
             ADD      BX,4           ;  4 clock        READY FOR NEXT ELEMENT
             LOOP     ADD_LOOP       ; 17 clock

    A nejak me to nesedi. Mozna jen tim ze 8087 je v knize v ms a zbytek v taktech. .)
    Jedine co vim, ze oba musi bezet na stejne frekvenci kvuli shodnemu cteni.

    Tak jiny zdroj uvadi FADD DWORD na 105 clocks
    FADD [DWord] 105
    https://web.archive.org/web/20180104181527/http://wiretap.area.com/Gopher/Library/Techdoc/Cpu/coproc.txt

    Takze program na nic neceka i kdyz je FPU brzda, divne...