Koupil jsem novy laptop a zacal zase pracovat na M4 FORTHu. Delam na tom uz nejaky mesic, takze zmen je tolik, ze nevim kde zacit.
Prvne jsem pridal podporu pro argumenty lezici v pameti pro hodne slov. Tzn. misto konstanty je parametr adresa v pameti s danou hodnotou. Jako napriklad PUSH(10) --> PUSH((0x8000)).
--------
Zapracoval jsem znovu na nasobeni konstantou. Pro konstanty vetsi jak 255 to bylo hodne neefektivni. Delal jsem to zpusobem, ze jsem se kouknul co me leze z compileru a porovnal to s tim co bych napsal ja v assembleru a snazil se z toho vytvorit dalsi pravidlo. Skoro vzdy to slo udelat nejak lepe. Vytvoril jsem tak hodne algoritmu/pravidel a vysledky mi makro uklada do promenne. Takze mohu porovnat, podle mych kriterii ktera varianta je "nejlepsi" a tu zvolit. Pak jsem se jednou kouknul co leze z sdcc a neveril svym ocim. Prvne mi prislo ze to maji chybne a pak mi to docvaklo. Moc pekny. Ja to vetsinou resil zpusobem
Kód:
vysledek = 0
x
x *= 2
x *= 2
vysledek += x
x *= 2
vysledek += x
x *= 2
x *= 2
x *= 2
vysledek += x
atd.
Algoritmus vytvari vysledek od nejnizsich jednickovych bitu. To znamena ze jsem pricital k "vysledek" pokazde kdyz bit byl 1. Ale tenhle algoritmus ma nevyhodu od vic jak 2 jednickovych bitu, ze potrebujete mit v HL jak "vysledek" tak "x". Takze hledate pro danou konstantu nejlepsi reseni "ex DE,HL" nebo neco jineho.
sdcc to resi uplne naopak, vytvari vysledek od nejvyssich bitu. Ma "x" v BC a vysledek v "HL"
Kód:
HL = x
HL *= 2
HL *= 2
HL *= 2
HL += x
HL *= 2
HL += x
vysledek = 10011 * x. Algoritmus prvne pricte x a pak ji teprve nasobi dvema. Chytre, ale stale to neni optimalni algoritmus, porad dokazi napsat nejake konstanty vetsi 255 lepe.
Protoze se ve FORTHu jedna o nasobeni 16 bit = 16 bit * 16 bit. Tak pokud nasobime konstantou vetsi jak 8 bit tak lze udelat, pokud to hodne zjednodusim pro vysvetleni neco jako:
vysledek = 110 0001 0011 * x
Kód:
HL = x
HL *= 2
A = L
HL *= 2
A += L
HL *= 2
HL += x
HL *= 2
HL += x
H += A.
Takze ted to mam lepe udelane jak sdcc. .))
-----------
Zapracoval jsem na deleni konstantou. To je ale tak komplikovana zalezitost, ze jsem udelal jen par konstant. NEJDE* udelat udelat univerzalni algoritmus. Je to spis magie, proste zkousite nejake vzorce a triky dokud neudelate neco lepsiho nez mate. To je na dlouhe psani... Na netu se da neco najit, ale skoro vzdy se jedna o neco jako X / 9, kde X < 1440.
*Zatimco ja musim mit platnost v celem oboru 0..65535.
----
Doplnil jsem CASE OF ENDOF ENDCASE. Musel jsem se celkem drzet jak to maji ve FORTHu definovane, ale jedna varianta implementace je celkem pekna. Jedna se o ideu ze testovane hodnoty jdou vetsinou pekne za sebou. Jako 1,2,3,8,9,10.
Takze implementace jde udelat jako
Kód:
HL = x
dec HL
jr nz, next_of
...1
jr end_case
dec HL
jr nz, next_of
...2
jr end_case
dec HL
jr nz, next_of
...3
jr end_case
ld BC,3-8
or A
sbc HL,BC
jr nz, next_of
...8
jr end_case
dec HL
jr nz, next_of
...9
jr end_case
dec HL
jr nz, next_of
...10
jr end_case
---------
Vytvoril jsem fajn bash skript, ktery me dokaze ukazat primo assembler pro dane slovo, nebo vice slov. Takze mohu hned koukat na vysledek, a ne na mnohem mene prehledne makro.
Kód:
dworkin@dw-A15:~/Programovani/ZX/Forth$ ./check_word.sh '_3UDIV'
; vvv
; ^^^
;[35:212] 3/ Variant HL/3 = (HL*257*85) >> 16 = (HL*(1+1/256)*b_0101_0101) >> 8
ld B, H ; 1:4 3/
ld C, L ; 1:4 3/ 1 1x = base
xor A ; 1:4 3/
add HL, HL ; 1:11 3/ 0
adc A, A ; 1:4 3/ *2 AHL = 2x
add HL, HL ; 1:11 3/ 1
adc A, A ; 1:4 3/ *2 AHL = 4x
add HL, BC ; 1:11 3/
adc A, 0x00 ; 2:7 3/ +1 AHL = 5x
add HL, HL ; 1:11 3/ 0
adc A, A ; 1:4 3/ *2 AHL = 10x
add HL, HL ; 1:11 3/ 1
adc A, A ; 1:4 3/ *2 AHL = 20x
add HL, BC ; 1:11 3/
adc A, 0x00 ; 2:7 3/ +1 AHL = 21x
add HL, HL ; 1:11 3/ 0
adc A, A ; 1:4 3/ *2 AHL = 42x
add HL, HL ; 1:11 3/ 1
adc A, A ; 1:4 3/ *2 AHL = 84x
add HL, BC ; 1:11 3/
ld BC, 0x0055 ; 3:10 3/ rounding down constant
adc A, B ; 1:4 3/ +1 AHL = 85x
add HL, BC ; 1:11 3/
adc A, B ; 1:4 3/ +0 AHL = 85x with rounding down constant
ld B, A ; 1:4 3/ (AHL * 257) >> 16 = (AHL0 + 0AHL) >> 16 = AH.L0 + A.HL = A0 + H.L + A.H
ld C, H ; 1:4 3/ BC = "A.H"
add HL, BC ; 1:11 3/ HL = "H.L" + "A.H"
ld L, H ; 1:4 3/
adc A, 0x00 ; 2:7 3/ + carry
ld H, A ; 1:4 3/ HL = HL/3 = HL*(65536/65536)/3 = HL*21845/65536 = (HL*(1+256)*85) >> 16
;[35:212]
Pocita me to i takty a bajty. Takty spravne jen pokud nema kod smycky, ale i tak to nemusim pocitat rucne a hned vidim co je efektivnejsi. Kdyz porovnavam 2 slova a spojenou variantu do jednoho souslovi.
------
FORTH ma vlastni zpusob jak testovat slova.
Kód:
T{ 1 2 SWAP -> 2 1 }T
Cte se to tak ze se neco testuje pred sipkou -> a pak se zasobnik porovna s tim co je za sipkou. Implementoval jsem to, jen trosku jinak nez pres pomaly zasobnik adres.
Kód:
TEST_START PUSH2(1,2) SWAP TEST_EQ PUSH2(2,1) TEST_END
. V podstate se uklada hodnota SP ve slovech TEST_START i TEST_EQ a pak se provadi porovnani hodnot i poctu.
------
Diky tomu jsem mohl provadet nejake klasicke testy a odhalil nejake chyby a narazil na tuhle silnost
BEGIN ... WHILE ... WHILE ... UNTIL ... ELSE ... THEN
https://forth-standard.org/standard/core/WHILECoz neni nikde dokumentovany, ze WHILE se nekdy chova jako IF.
BEGIN ... IF ... WHILE ... UNTIL ... ELSE ... THEN (coz zase nesezere gforth, protoze se krizi BEGIN UNTIL s IF ELSE THEN)
Muselo by se to "korektne" zapsat jako
IF ... BEGIN ... WHILE ... UNTIL ... ELSE ... THEN. Pak tomu rozumi vsichni.
------
Pridal jsem podporu pro praci s cisly typu 16 * 16 = 32 bit nebo 32 / 16 = 16 bit. Takze jsem musel pridat i tisk pro 32 bitove cislo. Neni to uplne nejlepsi, jeste se musim kouknout na jeden algoritmus, zda to nebude v me implementaci rychlejsi/kratsi.
https://my.eng.utah.edu/~nmcdonal/Tutorials/BCDTutorial/BCDConversion.html-------
Narazil jsem u testovani na neprijemnou vec ze FORTH ma "floor" deleni. Ne to na co jste
![Very Happy :D](./images/smilies/icon_e_biggrin.gif)
urcite zvykly z C. Jina receno, pokud je vysledek deleni zaporny. Tak nema zbytek vzdy znamenko cisla co se deli, ale je pokazde kladny a vysledek se stale zaokrouhluje dolu, ne k nule. -7/3 = -3 a zbytek je 2. Uf... Bohuzel jsem nezjistil jak to spocitat jinak nez pres "symetricke" deleni s dodatecnou opravou. Takze mi slova DIV, MOD a DIVMOD vrazi jine hodnoty nez standard.
https://www.nimblemachines.com/symmetric-division-considered-harmful/ S clankem a dalsimi nesouhlasim. Argumentuji tim ze pokud indexuji nejake pole tak nebudu indexovat od nuly, ale od zaporneho cisla a pak mi vysledky bude davat spravne pro indexy jen floor division. Ale proc bych to delal? Naopak me prijde spravne ze se vysledky zrcadli kolem osy Y.
------
No to je asi tak vse s cim jsem se chtel podelit... Pokud vas to jen trchou zajima tak urcite doporucuji to hrani s checkword.sh skriptem. Hodi se to i kdyz potrebujete jen neco pridat do vlastniho programu, jako to nasobeni nebo deleni konstantou.