Frooxius
Založen: 27. 04. 2011 Příspěvky: 73 Bydliště: Kopřivnice
|
Zaslal: 27. duben 2011, 00:54:28 Předmět: AttoWPU - experimentální procesor a programovací jazyk |
|
|
Dobrý den,
chtěl bych vám představit jeden projekt, na kterém nějakou dobu pracuji, ačkoli je to tak trochu spíše bokovka od vývoje her
Oficiální web
Stáhnout
Obsahuje:
Anglickou a českou dokumentaci v PDF
Překladač ("kompilátor") pro jazyk AttoASM, Windows, Linux, konzolová aplikace
Simulátor (či emulátor) pro procesor AttoWPU, Windows, Linux, GUI (Qt)
Logické schéma procesoru v PDF
Ukázkové zdrojové kódy v jazyce AttoASM
Hru Pong napsanou v jazyce AttoASM
Changelog
0.8.01
-kód starající se o emulaci AttoWPU nyní běží v samostatném vlákně - o něco rychlejší (zvláště u multijádrových procesorů), přesnější časování.
-maximální/nelimitovaná rychlost emulace nyní funguje, stačí kliknout na Max, či přetáhnout posuvník úplně doprava
-LED diody a přepínače jsou nyní grafické, namísto radio a checkboxů, takže vypadají lépe
-zachytávání kláves je nyní zcela globální pro celou aplikaci, nezáleží který prvek má keyboard focus
-opraveno padání při zavření dialogu pro načtení zdrojového souboru
-malá aktualizace ve specifikaci ve způsobu ovládání speakeru
-dostupná je česká verze dokumentace, počítejte ale s tím, že nebude aktualizována tak často, jako anglická
0.8.02
-K dispozici binárky pro Linux
-Aktualizován attoassembler kód v emulátoru, což řeší některé pády při překladu zdrojových kódů
Jedná se o experimentální procesor attoWPU ze série WPU (Weird Processing Unit), který se snaží o netradiční a zajímavý přístup ke zpracování strojového kódu a samotnému programování: součástí je i programovací jazyk AttoASM (attoassembler) a CustASM (custom assembler) a s tím související kompilátory, plus simulátor, ve kterém si můžete vytvořené programy vyzkoušet, v plánu je i VHDLko.
Co je to WPU?
Určitě znáte alespoň pojmy CPU a GPU: procesorové jednotky s určitým účelem: CPU (central processing unit) je univerzální procesor schopný zpracovat jakýkoliv typ programu (nebrat doslova), avšak na rozdíl od specializovaných jednotek řadu operací nezvládá dostatečně rychle, takže kupříkladu u grafických operací přijde na řadu GPU: tento procesor je navržen speciálně pro rychlé zpracování grafických operací, ale moc jiného neumí (ačkoli s unified shadery je to už na pováženou, ale to je mimo pointu). Důležité je, že každý z těchto typů procesorových jednotek má svoji vlastní filozofii a účel, tedy něco, co je činí typickými. Čím jsou tedy typické WPU?
WPU znamená Weird Processing Unit (podivná procesorová jednotka) a takové podivné, praštěné a legrační jméno už implikuje, co WPU je: jedná se v podstatě o procesorovou jednotku, která je nějakým způsobem podivná - jiná od běžných konvencí. WPU může být jakýkoliv procesor, který je alespoň z části navržený podivným, nevídaným způsobem, což činí programování pro tento procesor a způsob zpracování strojového kódu vyzývajícím a/nebo zajímavým. Nemusí mít žádný čistě praktický účel a většinou ani mít nebude. Jedná se spíše o způsob "hej, zkusme tohle a uvidíme co to udělá" - čistě experimentální, praštěný a podivný pro zábavu a zvědavost.
To znamená, že WPU je v podstatě jakýkoliv procesor, který se snaží jít za hranice konvencí běžných procesorů. WPU se snaží být více či méně originální a přicházet s novými a zajímavými koncepty, které pomohou stimulovat mysl programátorů netradičním designem a obvykle i netradičním programováním. Mohou být dokonce do jisté míry považovány za druh umění, něco jako "avant-garde procesory".
Co je to AttoWPU?
Zatímco WPU je obecný termín pro jakoukoliv procesorovou jednotku (splňující filozofii WPU), attoWPU je specifický WPU a nejenom to: jedná se o první WPU, vytvořený jako začátek série těchto experimentálních procesorů. Rozhodně se nejedná o nejlepší z nich (už plánuji nějaké mnohem lepší ), ale je to začátek.
AttoWPU je inspirován mikrokódem normálních procesorů a v podstatě staví na myšlence, že můžeme procesory rozdělit na dvě logické části: výpočetní část (provádí všechny výpočty a operace) a řídící část (dekóduje instrukce a říká výpočetním částím co mají dělat). AttoWPU má řídící část zredukovanou na absurdní minimum a vyžaduje od programátora, aby v podstatě vytvořil kód, který bude řídit funkci procesoru použitím tří elementárních instrukcí (attoinstrukcí), každá z nich mění vždy pouze jediný bit. Co se týče výpočetních jednotek, těch má AttoWPU hodně a odvádějí poměrně dost práce za programátora, čímž je programování jednodušší (jestli to chcete ještě více hardcore, počkejte na zeptoWPU ).
To vám v podstatě umožňuje vytvořit konvenční (nebo taky ne) software použitím attoassembleru (programovací jazyk na ještě nižší úrovni než assembler), což je v podstatě forma extrémního/hardcore programování, ale také vám umožňuje pomocí attoassembleru definovat funkci procesoru a nechat jej zpracovávat program na vyšší úrovni s vašimi vlastními instrukcemi, jednoduše řečeno, v podstatě se jedná o procesor, jehož funkci a instrukční sadu si musíte sami naprogramovat. S tím souvisí taky zajímavá vlastnost: neboť je paměť attokódu zapisovatelná, je teoreticky možné vytvořit sebemodifikující procesor.
Další činností spojenou s programováním AttoWPU je optimalizace kódu a komprese (kódu). Strojový kód AttoWPU nabízí obrovský prostor pro optimalizaci (menší a rychlejší kód, což jsou do jisté míry v podstatě stejné parametry) a kompresi (HODNĚ redudance), takže si můžete otestovat, jak dobří programátoři jste v různých vyzývavých úkolech a budoucích soutěžích.
Ve zkratce: AttoWPU má AttoJádro, které je schopné zpracovávat pouze elementární instrukce, nazvané attoinstrukce. AttoWPU má jednu 64 bitovou sběrnici, která je rozdělena na čtyři logické sběrnice: adresní, řídící, datovou a Quick aJump sběrnici. Každá attoinstrukce mění vždy pouze jediný bit této sběrnice. K těmto sběrnicím jsou paralelně připojeny různé jednotky (výpočetní jednotky), takže programátor musí využít těchto sběrnic k ovládání těchto jednotek a výměně dat mezi jednotkami. AttoJádro je pouze schopno měnit jeden bit na jedné z logických sběrnic v každém cyklu, vše ostatní (i (ne)podmíněné skoky) musí být provedeny použitím jednotek. Pro lepší pochopení doporučuji soubor Schematics.pdf v archivu, který je ke stažení níže.
Co to je AttoASM?
AttoASM je programovací jazyk sloužící k vytvoření attokódu - strojového kódu, který je zpracován attojádrem AttoWPU. Umožňuje vytvořit attokód (strojový kód) zapsáním jednotlivých attoinstrukcí, ale nabízí i způsoby, jak programování zjednodušit a odstranit opakující se zdrojový kód. To samé ale nejde říct o strojovém kódu, takže pokud budete chtít optimalizovat výsledný strojový kód, zdrojový kód bude zřejmě složitější, stejně jako jeho tvorba, ale přece jenom se jedná o extrémní/hardcore programování, ne? Budete potřebovat "kompilátor" (attoassembler - tak se správně nazývá překladač) k převedení zdrojového kódu na attokód (strojový kód).
Co je to CustASM?
Protože lze vytvořit attokód, který bude v podstatě definovat procesor - bude dekódovat instrukce a provádět příslušné akce, můžete vytvořit prakticky libovolnou instrukční sadu (na vyšší úrovni než jsou attoinstrukce, nebo taky ne, klidně si napište attokód, který bude zpracovávat attokód ), budete potřebovat nástroj, který vytvoří příslušný strojový kód ze zdrojového kódu s vašimi vlastními mnemotechnickými symboly.
Pro pohodlí, abyste nemuseli sami psát svůj překladač, nebo nějaký hledat, je k dispozici i programovací jazyk CustASM a příslušný překladač (custassembler). Tento jazyk symbolických adres vám v podstatě umožňuje jednoduše nadefinovat své vlastní mnemotechnické zkratky, včetně uspořádání a počtu argumentů a příslušný strojový kód. Custassembler poté použije tyto definice při překladu strojového kódu.
Custassembler prozatím není k dispozici ke stažení, ale bude brzy.
Jak si to můžu vyzkoušet?
Aby si mohl kdokoliv vyzkoušet AttoWPU a programování pro něj, je k dispozici i grafický simulátor, spolu s překladačem (simulátor má však vestavěný překladač, takže stačí načíst zdrojový kód a on se postará o překlad). Samozřejmě byste si měli přečíst dokumentaci (bohužel zatím jen v angličtině, bude ale trochu ořezaná česká verze) a podívat se na ukázkové zdrojové kódy (brzy jich bude více a lépe komentovaných).
Doporučuji zejména Pong.att, což je v podstatě hra Pong napsaná čistě v AttoAssemebleru (AttoASM) a poměrně hojně komentovaná (ono to ani jinak nejde).
Jaký je stav projektu?
Projekt je momentálně ve fázi alfa, takže stále podléhá značnému vývoji, takže očekávávejte bugy, problémy a nedodělané funkce. Naštěstí je procesor, překladač i simulátor funkční do té míry, že si jej lze rozumně vyzkoušet a prezentovat jej
Momentálně věnuji čas i dalšímu WPU ze série, o kterém však zatím nic neřeknu no a samozřejmě mě čeká maturita, takže času tolik není, přesto bych byl rád za nějakou odezvu, tak neváhejte, zkoušejte, experimentujte, programujte a komentujte Jakmile budou nějaké novinky, tak vás upozorním, ideálně sledujte oficiální web http://attowpu.solirax.org
Ukázka zdrojového kódu - hra Pong v jazyce AttoASM:
Psal jsem to samozřejmě já, už jste někdy zkoušeli psát něco ve vlastním programovacím jazyce pro vlastní procesor? Je to zvláštní (možná je to ale moc dlouhé, kdyžtak to smažu, pokud to bude vadit)
kód: |
// Register Memory Allocation
PADDLE0_Y { 0 }
PADDLE1_Y { 1 }
BALL_X { 2 }
BALL_Y { 3 }
SCORE0 { 4 }
SCORE1 { 5 }
BALL_XSPD { 6 }
BALL_YSPD { 7 }
TEMP { 8 }
// For passing an argument to a symbol
ARG0 { 0 }
ARG1 { 0 }
ARG2 { 0 }
ARG3 { 0 }
// Auxiliary
EXE { CTRL+7(2) ! }
WriteTEMP
{
ADDR [02, 8]
CTRL [03, 7]
EXE
}
// Output value from the Register memory at address given by ARG
OutputRegister
{
ADDR [03, 8]
DATA [ARG0]
CTRL [01, 7] // write address
EXE
CTRL [0DH, 7] // output data
EXE
DATA 1(32)
}
// stop register output
StopRegister
{
ADDR [03, 8]
CTRL [0, 7]
EXE
}
OUT2TEMP
{
DATA 1(32)
ADDR [05, 8] CTRL [01, 7] EXE // output OUT
WriteTEMP
ADDR [05, 8] CTRL [0, 7] EXE // stop the OUT output
}
// signed add two values together and store the result back
// ARG1 - first value
// ARG2 - second value
// ARG3 - result (where to write)
SADDStoreBack
{
ARG0 {! ARG1 }
OutputRegister
WriteTemp
ADDR [03, 8] CTRL [0, 7] EXE // stop the register output
ARG0 {! ARG2 }
OutputRegister
// add them together
ADDR [04, 8]
CTRL [09, 7]
EXE
// store the result back
ADDR [03, 8] CTRL [0, 7] EXE // stop the register output
ADDR [03, 8] DATA [ARG3] CTRL [01, 7] EXE // address the proper location in the register memory
DATA 1(32)
ADDR [05, 8] CTRL [1, 7] EXE // output the out
ADDR [03, 8] CTRL [0EH, 7] EXE // write the value
ADDR [05, 8] CTRL [0, 7] EXE // stop the out output
}
// **** KEYBOARD READ ****
ReadKey
{
DATA 1(32) // prepare for data exchange
ADDR [0DH, 8] // address the input controller
CTRL [08, 7] // read the key
EXE
}
StopKey
{
ADDR [0DH, 8] // address the input controller
CTRL [0, 7] // read the key
EXE
}
ProcessAllKeys
{
ARG0 {! PADDLE0_Y }
ARG1 {! 22 } // the W key
ARG2 {! 02 } // subtraction
ProcessKey
ARG0 {! PADDLE0_Y }
ARG1 {! 18 } // the S key
ARG2 {! 01 } // addition
ProcessKey
// second paddle
ARG0 {! PADDLE1_Y }
ARG1 {! 4 } // the E key
ARG2 {! 02 } // subtraction
ProcessKey
ARG0 {! PADDLE1_Y }
ARG1 {! 3 } // the D key
ARG2 {! 01 } // addition
ProcessKey
}
/*
ARGUMENTS:
ARG0 = address at the register memory to process
ARG1 = code of the key
ARG2 = command code for the ALU to calculate new value
*/
ProcessKey
{
// Read the W key
ReadKey
WriteTEMP
StopKey
DATA [ARG1] // code of the W key
// compare them using the ALU
ADDR [04, 8]
CTRL [28H, 7] // test for equality
EXE
// output result from the OUT
ADDR [05, 8]
CTRL [01, 7]
EXE
DATA 1(32)
WriteTEMP // and store it in the temp register
// multiply by two, so it moves by two pixels
ADDR [04, 8]
CTRL [01, 7]
EXE EXE
WriteTEMP
ADDR [05, 8]
CTRL [0, 7]
EXE // stop OUT output
// load the value from the Register memory
// address is prepared in the ARG0
OutputRegister
// calculate new value
ADDR [04, 8]
CTRL [ARG2, 7]
EXE
ADDR [03, 8]
CTRL [0, 7]
EXE // stop register memory outputting
// ---- limit value to min 0, max 127 ----
// copy it from the OUT to the TEMP
DATA 1(32)
ADDR [05, 8]
CTRL [1, 7]
EXE
WriteTemp
ADDR [05, 8]
CTRL [0, 7]
EXE
// maximum
DATA [128-18]
ADDR [04, 8]
CTRL [26H, 7]
EXE
// copy it from the OUT to the TEMP
DATA 1(32)
ADDR [05, 8]
CTRL [1, 7]
EXE
WriteTemp
ADDR [05, 8]
CTRL [0, 7]
EXE
// minimum
DATA [0]
ADDR [04, 8]
CTRL [24H, 7]
EXE
// new position is calculated, now store the value back
DATA 1(32)
ADDR [05, 8]
CTRL [1, 7]
EXE // output calculated value
ADDR [03, 8] // register memory
CTRL [0EH, 7]
EXE // modified value is now written back
// cleanup
ADDR [05, 8]
CTRL [0, 7]
EXE // stop OUT output
}
// DRAWING
// draws the ball at current position - it's calculated automatically
DrawBall
{
// ball is 6x6 px
// get the Y position first and calculate proper address for the LCD
ARG0 {! BALL_Y }
OutputRegister
// write it to the temp
ADDR [02, 8]
CTRL [03, 7]
EXE
// stop register output
ADDR [03, 8]
CTRL [0, 7]
EXE
// multiply by 128
DATA [128]
ADDR [04, 8]
CTRL [03, 7]
EXE
// output the OUT
ADDR [05, 8]
CTRL [01, 7]
EXE
DATA 1(32)
// write it to the temp
WriteTEMP
// stop the OUT output
ADDR [05, 8]
CTRL [0, 7]
EXE
// add the paddle X position to the address
ARG0 {! BALL_X }
OutputRegister
ADDR [04, 8]
CTRL [01, 7]
EXE // add the BALL_X to the address
// OUT now contains the address, where drawing of the ball should start
// stop register output
ADDR [03, 8]
CTRL [0, 7]
EXE
// output the OUT
ADDR [05, 8]
CTRL [01, 7]
EXE
// write the address to the LCD
ADDR [0CH, 8]
CTRL [01, 7]
EXE // write the new address
// write it to the temp too (DrawRowNext requires it)
WriteTEMP
// stop the out output
ADDR [05, 8]
CTRL [0, 7]
EXE
ARG0 {! 00FF0000H }
// draw 6 rows
DrawRowNext DrawRowNext DrawRowNext
DrawRowNext DrawRowNext DrawRowNext
}
// draws paddle at the current position - it needs to be set before this symbol is used
DrawPaddle
{
// paddle is 6x18 px
DATA 1(32)
// store the starting value in the TEMP first
ADDR [0CH, 8]
CTRL [06, 7]
EXE
WriteTemp
// start writing pixels
ADDR [0CH, 8]
CTRL [0, 7]
EXE // stop the data output first
ARG0 {! 00FFFF00H }
DrawRowNext DrawRowNext DrawRowNext DrawRowNext
DrawRowNext DrawRowNext DrawRowNext DrawRowNext
DrawRowNext DrawRowNext DrawRowNext DrawRowNext
DrawRowNext DrawRowNext DrawRowNext DrawRowNext
DrawRowNext DrawRowNext
}
// draw a row of pixels and move to the next one
// color is stored in ARG0
DrawRowNext
{
DATA+8 [ARG0, 24]
// write 6 pixels
ADDR [0CH, 8]
CTRL [03, 7]
CTRL+7(12) !
// move to the next row
DATA [128]
ADDR [04, 8]
CTRL [01, 7]
EXE // add 128 to the value
DATA 1(32)
ADDR [05, 8]
EXE // output it
WriteTemp
ADDR [0CH, 8]
CTRL [01, 7]
EXE // write the new address
ADDR [05, 8]
CTRL [0, 7]
EXE // stop the output from the OUT
}
// write LCD paddle start position
// ARG0 - register address containing the position
// ARG1 - number to add to the start address (used to determine side)
LCDPaddleStart
{
// output the start position from the register memory
OutputRegister
// write it to the temp
ADDR [02, 8]
CTRL [03, 7]
EXE
// stop register output
ADDR [03, 8]
CTRL [0, 7]
EXE
// multiply by 128
DATA [128]
ADDR [04, 8]
CTRL [03, 7]
EXE
// output the OUT
ADDR [05, 8]
CTRL [01, 7]
EXE
DATA 1(32)
// write it to the temp
WriteTEMP
// stop the OUT output
ADDR [05, 8]
CTRL [0, 7]
EXE
// now add the value in ARG1 (horizontal shift)
DATA [ARG1]
ADDR [04, 8]
CTRL [01, 7]
EXE
// output the OUT
ADDR [05, 8]
CTRL [01, 7]
EXE
DATA 1(32)
// write the address to the LCD
ADDR [0CH, 8]
CTRL [01, 7]
EXE
}
UpdateBall
{
// increment/decrement
// add BALL_XSPD to the BALL_X
ARG1 {! BALL_X }
ARG2 {! BALL_XSPD }
ARG3 {! ARG1 }
SADDStoreBack
// add BALL_YSPD to the BALL_Y
ARG1 {! BALL_Y }
ARG2 {! BALL_YSPD }
ARG3 {! ARG1 }
SADDStoreBack
/* **********************
VERTICAL COLLISION
********************** */
DATA [0]
WriteTEMP // temp contains minimal value
ARG0 {! BALL_Y }
OutputRegister
// now compare them
ADDR [04, 8]
CTRL [25H, 7]
EXE // if value in TEMP is larger than BALL_Y, then one will be outputed to the OUT
ADDR [03, 8] CTRL [0, 7] EXE // stop register output
CTRL [01, 7] DATA [TEMP] EXE // address the cell for temporary data
DATA 1(32) ADDR [05, 8] CTRL [01, 7] EXE // output the out
ADDR [03, 8] CTRL [0EH, 7] EXE // write the value
ADDR [05, 8] CTRL [0, 7] EXE // stop the OUT output
DATA [128-6]
WriteTEMP
ARG0 {! BALL_Y }
OutputRegister
// now compare them
ADDR [04, 8]
CTRL [27H, 7]
EXE // if value in TEMP is smaller than BALL_X, then one will be outputed to the OUT
ADDR [03, 8] CTRL [0, 7] EXE // stop register output
// copy OUT to the TEMP
DATA 1(32)
ADDR [05, 8] CTRL [1, 7] EXE // output the OUT
WriteTEMP
ADDR [05, 8] CTRL [0, 7] EXE // stop the OUT output
// output the first value on the bus
ARG0 {! TEMP }
OutputRegister
// now OR them, so 1 is outputted if at one of them is 1, otherwise zero
ADDR [04, 8] CTRL [18H, 7] EXE
ADDR [03, 8] CTRL [0, 7] EXE // stop the register output
// now multiply by -1, so -1 is outputted, when position overflows, zero otherwise
DATA [-1]
WriteTEMP
DATA 1(32)
ADDR [05, 8] CTRL [01, 7] EXE // output the OUT
ADDR [04, 8] CTRL [0BH, 7] EXE // signed multiply
WriteTEMP
ADDR [05, 8] CTRL [0, 7] EXE // stop the OUT output
DATA [1]
ADDR [04, 8] CTRL [29H, 7] EXE // copy 1 to the OUT only if TEMP is zero (so OUT now contains either -1 or 1)
// write back to the TEMP
DATA 1(32)
ADDR [05, 8] CTRL [1, 7] EXE // output the OUT
WriteTEMP
ADDR [05, 8] CTRL [0, 7] EXE // stop the OUT output
// multiply it with the BALL_YSPD
ARG0 {! BALL_YSPD }
OutputRegister
ADDR [04, 8] CTRL [0BH, 7] EXE // multiply them
// store the result back
ADDR [03, 8] CTRL [0, 7] EXE // stop the register output
CTRL [01, 7] DATA [BALL_YSPD] EXE // address the cell with BALL_YSPD, because the new value will be written there
DATA 1(32)
ADDR [05, 8] CTRL [1, 7] EXE // output the OUT, contaning the new value
ADDR [03, 8] CTRL [0EH, 7] EXE // write the value
ADDR [05, 8] CTRL [0, 7] EXE // stop OUT output
// left paddle detection
ARG1 {! PADDLE0_Y }
ARG2 {!
DATA [6]
ADDR [04, 8]
CTRL [27H, 7]
EXE
}
ARG3 {! 1 }
PaddleBounce
// right paddle detection
ARG1 {! PADDLE1_Y }
ARG2 {!
DATA [128-6-6]
ADDR [04, 8]
CTRL [25H, 7]
EXE
}
ARG3 {! -1 }
PaddleBounce
DetectOutside
}
// detect if the ball left the area
DetectOutside
{
// detect left outside
ARG0 {! BALL_X }
OutputRegister
WriteTEMP
StopRegister
DATA [0]
ADDR [04, 8] CTRL [27H, 7] EXE // if ball left on the left, then OUT is 1
OUT2TEMP
// conditional jump
DATA [LEFTLOSE%]
ADDR [04, 8] CTRL [2AH, 7] EXE // if OUT is 1 then OUT will contain address of the LEFTLOSE
DATA [LEFTNORMAL%]
ADDR [04, 8] CTRL [29H, 7] EXE // if OUT is 0 then OUT will contain address of the LEFTNORMAL
// output out
DATA 1(32)
ADDR [05, 8] CTRL [01, 7] EXE // output OUT
ADDR [00, 8] CTRL [01, 7] EXE // write the new address
LEFTLOSE%:
CTRL+7 0 // to be safe
ADDR [05, 8] CTRL [0, 7] EXE // stop OUT output
ResetBall
LeftLoseCode
AJMP [END%, 15]
AJMP+15(2) !
LEFTNORMAL%:
CTRL+7 0 // to be safe
ADDR [05, 8] CTRL [0, 7] EXE // stop OUT output
// detect right outside
ARG0 {! BALL_X }
OutputRegister
WriteTEMP
StopRegister
DATA [128-6]
ADDR [04, 8] CTRL [25H, 7] EXE // if ball left on the right, then OUT is 1
OUT2TEMP
// conditional jump
DATA [RIGHTLOSE%]
ADDR [04, 8] CTRL [2AH, 7] EXE // if OUT is 1 then OUT will contain address of the RIGHTLOSE
DATA [END%]
ADDR [04, 8] CTRL [29H, 7] EXE // if OUT is 0 then OUT will contain address of the END
// output out
DATA 1(32)
ADDR [05, 8] CTRL [01, 7] EXE // output OUT
ADDR [00, 8] CTRL [01, 7] EXE // write the new address
RIGHTLOSE%:
CTRL+7 0 // to be safe
ADDR [05, 8] CTRL [0, 7] EXE // stop OUT output
ResetBall
RightLoseCode
END%:
CTRL+7 0 // to be safe
ADDR [05, 8] CTRL [0, 7] EXE // stop OUT output
}
LeftLoseCode
{
// increment right score
ARG0 {! SCORE1 }
OutputRegister
WriteTEMP
StopRegister
DATA [1] ADDR [04, 8] CTRL [01, 7] EXE // add one
DATA [SCORE1] ADDR [03, 8] CTRL [01, 7] EXE // write the address
ADDR [05, 8] DATA 1(32) CTRL [01, 7] EXE // output OUT
ADDR [03, 8] CTRL [0EH, 7] EXE // write data
ADDR [05, 8] CTRL [0, 7] EXE // stop OUT output
UpdateText
}
RightLoseCode
{
// increment right score
ARG0 {! SCORE0 }
OutputRegister
WriteTEMP
StopRegister
DATA [1] ADDR [04, 8] CTRL [01, 7] EXE // add one
DATA [SCORE0] ADDR [03, 8] CTRL [01, 7] EXE // write the address
ADDR [05, 8] DATA 1(32) CTRL [01, 7] EXE // output OUT
ADDR [03, 8] CTRL [0EH, 7] EXE // write data
ADDR [05, 8] CTRL [0, 7] EXE // stop OUT output
UpdateText
}
UpdateText
{
ADDR [0BH, 8] CTRL [09, 7] EXE // address the text display and reset it first
ARG0 {! strInfo}
CopyStr
ARG0 {! strScore0 }
CopyStr
ARG0 {! SCORE0 }
TwoDigitsFromReg
ARG0 {! endLine }
CopyStr
ARG0 {! strScore1 }
CopyStr
ARG0 {! SCORE1 }
TwoDigitsFromReg
ARG0 {! endLine }
CopyStr
ARG0 {! strInfo2 }
CopyStr
}
// write two digits to the text display from the register memory at address in ARG0
TwoDigitsFromReg
{
// first digit
DATA [10]
WriteTEMP
OutputRegister
ADDR [04, 8] CTRL [05, 7] EXE // divide it by 10
StopRegister
OUT2TEMP
ADDR [04, 8] DATA [30H] CTRL [01, 7] EXE // add the value of '0' to it to produce a digit character
ADDR [05, 8] DATA 1(32) CTRL [01, 7] EXE // output out
ADDR [0BH, 8] CTRL [03, 7] EXE // write the character
ADDR [05, 8] CTRL [0, 7] EXE // stop the OUT output
// second digit
DATA [10]
WriteTEMP
OutputRegister
ADDR [04, 8] CTRL [06, 7] EXE // module it by 10
StopRegister
OUT2TEMP
ADDR [04, 8] DATA [30H] CTRL [01, 7] EXE // add the value of '0' to it to produce a digit character
ADDR [05, 8] DATA 1(32) CTRL [01, 7] EXE // output out
ADDR [0BH, 8] CTRL [03, 7] EXE // write the character
ADDR [05, 8] CTRL [0, 7] EXE // stop the OUT output
}
// copy zero terminated string to the display
// ARG0 - start address in the attocode memory
CopyStr
{
ADDR [01, 8] DATA [ARG0] CTRL [01, 7] EXE // address start of the string
LOOP%:
DATA 0(24)1(8)
ADDR [01, 8] CTRL [03, 7] EXE // output character
// determine if it's a zero - then end the loop
WriteTEMP
ADDR [01, 8] CTRL [0, 7] EXE // stop output
DATA 1(24)
ADDR [04, 8]
DATA [END%] CTRL [29H, 7] EXE // copy the END address if TEMP is zero (zero terminated string)
DATA [CONTINUE%] CTRL [2AH, 7] EXE // copy when TEMP is non-zero (contains character)
// write the address
DATA 1(32)
ADDR [05, 8] CTRL [01, 7] EXE // output the OUT
ADDR [0, 8] CTRL [01, 7] EXE // write new address
CONTINUE%:
CTRL+7 0
ADDR [05, 8] CTRL [0, 7] EXE // stop the OUT output
// copy the character to the text memory
ADDR [02, 8] CTRL [04, 7] EXE // output value from the TEMP (the character)
ADDR [0BH, 8] CTRL [03, 7] EXE // write the character and move to the next one
ADDR [02, 8] CTRL [0, 7] EXE // stop the TEMP output
ADDR [01, 8] CTRL [07, 7] EXE // move to the next character
AJMP [LOOP%, 15] AJMP+15(2) ! // maintain the cycle
END%:
CTRL+7 0
ADDR [05, 8] CTRL [0, 7] EXE // stop the OUT output
}
ResetBall
{
ADDR [03, 8]
CTRL [01, 7] DATA [BALL_X] EXE DATA [64] CTRL [0EH, 7] EXE
ARG0 {! BALL_Y }
OutputRegister
WriteTEMP
StopRegister
ADDR [03, 8] CTRL [01, 7] DATA [BALL_XSPD] EXE // address BALL_XSPD
ADDR [02, 8] CTRL [04, 7] EXE // output TEMP
ADDR [03, 8] DATA 0(31)1 CTRL [0EH, 7] EXE // write the value
ADDR [02, 8] CTRL [0, 7] EXE // stop TEMP output
ADDR [03, 8] CTRL [01, 7] DATA [BALL_YSPD] EXE // address BALL_YSPD
ADDR [02, 8] CTRL [04, 7] EXE // output TEMP
ADDR [03, 8] DATA 0(30)10 CTRL [0EH, 7] EXE // write data
ADDR [02, 8] CTRL [0, 7] EXE // stop TEMP output
ADDR [03, 8] CTRL [01, 7] DATA [BALL_Y] EXE // address BALL_Y
ADDR [02, 8] CTRL [04, 7] EXE // output TEMP
ADDR [03, 8] DATA 1(32) CTRL [0EH, 7] EXE
ADDR [02, 8] CTRL [0, 7] EXE // stop TEMP output
// now alter the BALL_XSPD and BALL_YSPD
ARG0 {! BALL_XSPD }
OutputRegister
WriteTEMP
StopRegister
// copy either 1 or -1
ADDR [04, 8] DATA [1] CTRL [29H, 7] EXE
ADDR [04, 8] DATA [-1] CTRL [2AH, 7] EXE
DATA [BALL_XSPD] ADDR [03, 8] CTRL [01, 7] EXE // address register
ADDR [05, 8] DATA 1(32) CTRL [1, 7] EXE // output out
ADDR [03, 8] CTRL [0EH, 7] EXE // write the new value
ADDR [05, 8] CTRL [0, 7] EXE // stop the OUT output
ARG0 {! BALL_YSPD }
OutputRegister
WriteTEMP
StopRegister
// copy either 1 or -1
ADDR [04, 8] DATA [1] CTRL [29H, 7] EXE
ADDR [04, 8] DATA [-1] CTRL [2AH, 7] EXE
DATA [BALL_YSPD] ADDR [03, 8] CTRL [01, 7] EXE // address register
ADDR [05, 8] DATA 1(32) CTRL [1, 7] EXE // output out
ADDR [03, 8] CTRL [0EH, 7] EXE // write the new value
ADDR [05, 8] CTRL [0, 7] EXE // stop the OUT output
}
// handle boucing from either paddle
// ARG1 - which paddle
// ARG2 - X axis detect code (only set DATA and do ALU stuff, TEMP contains ball X)
// ARG3 - new direction
PaddleBounce
{
// first, calculate if it's in the range of the paddle (below and above paddle's size)
ARG0 {! ARG1 }
OutputRegister
WriteTEMP
StopRegister
DATA [-5]
ADDR [04, 8] CTRL [09H, 7] EXE // subtract 5 from the paddle Y (so it can bounce from the edge)
OUT2TEMP
// TEMP now contains the upper position, now check if it's above ball position
ARG0 {! BALL_Y }
OutputRegister
ADDR [04, 8] CTRL [27H, 7] EXE // check if the BALL_Y is below PADDLE_Y
StopRegister
// store it in TEMP location in the register memory
ADDR [03, 8] DATA [TEMP] CTRL [01, 7] EXE // write the address
DATA 1(32) ADDR [05, 8] CTRL [01, 7] EXE // output OUT
ADDR [03, 8] CTRL [0EH, 7] EXE // the result is now stored
ADDR [05, 8] CTRL [0, 7] EXE // stop OUT output
// BOTTOM OF THE PADDLE
ARG0 {! ARG1 }
OutputRegister
WriteTEMP
StopRegister
DATA [18]
ADDR [04, 8] CTRL [09H, 7] EXE // add 18 to the value (paddle is 18 pixels tall)
OUT2TEMP
// TEMP now contains the bottrom possition, now check if it's below ball position
ARG0 {! BALL_Y }
OutputRegister
ADDR [04, 8] CTRL [25H, 7] EXE
StopRegister
OUT2TEMP
// now AND both these together - they both must be true
ARG0 {! TEMP }
OutputRegister
ADDR [04, 8] CTRL [17H, 7] EXE // Logical AND
StopRegister
// store the result in TEMP location once again, because it will be needed soon
ADDR [03, 8] DATA [TEMP] CTRL [01, 7] EXE // write the address
ADDR [05, 8] CTRL [1, 7] EXE // output out
DATA 1(32)
ADDR [03, 8] CTRL [0EH, 7] EXE // write the value
ADDR [05, 8] CTRL [0, 7] EXE // stop the OUT output
// now detect, if the ball is touching the paddle on the X axis
ARG0 {! BALL_X }
OutputRegister
WriteTemp
StopRegister
ARG2 // detection is handled by an external code
OUT2TEMP
// now AND it with the value in the TEMP, to produce final value, determining whether or not to bounce
ARG0 {! TEMP }
OutputRegister
ADDR [04, 8] CTRL [17H, 7] EXE // AND
StopRegister
OUT2TEMP
// now calculate new BALL_XSPD based on the calculated conditional value
DATA [ARG3]
ADDR [04, 8] CTRL [2AH, 7] EXE // if TEMP is nonzero, copy value from DATA to the OUT
ARG0 {! BALL_XSPD }
OutputRegister
ADDR [04, 8] CTRL [29H, 7] EXE // copy current speed if TEMP is zero (no collision - maintain regular speed)
StopRegister
// write the calculated speed to the BALL_XSPD
ADDR [03, 8] CTRL [1, 7] DATA [BALL_XSPD] EXE // address the propel cell
DATA 1(32) ADDR [05, 8] CTRL [1, 7] EXE // output the OUT
ADDR [03, 8] CTRL [0EH, 7] EXE // write the value
ADDR [05, 8] CTRL [0, 7] EXE // stop the OUT output
}
/* ************************************
PROGRAM START
************************************ */
// INITIALIZE EVERYTHING
0 0(64)
// enable double buffering
ADDR [0CH, 8]
CTRL [0BH, 7]
EXE
ADDR [03, 8] CTRL [01, 7] DATA [PADDLE0_Y] EXE DATA [64-9] CTRL [0EH, 7] EXE
ADDR [03, 8] CTRL [01, 7] DATA [PADDLE1_Y] EXE DATA [64-9] CTRL [0EH, 7] EXE
ADDR [03, 8] CTRL [01, 7] DATA [SCORE0] EXE DATA [0] CTRL [0EH, 7] EXE
ADDR [03, 8] CTRL [01, 7] DATA [SCORE1] EXE DATA [0] CTRL [0EH, 7] EXE
ResetBall
UpdateText
LOOP:
// cleanup after jump
CTRL+7 0
ADDR [0, 8] CTRL [0, 7] EXE
// game logic
UpdateBall
ProcessAllKeys
ARG0 {! PADDLE0_Y }
ARG1 {! 0 }
LCDPaddleStart
DrawPaddle
ARG0 {! PADDLE1_Y }
ARG1 {! 128-6 }
LCDPaddleStart
DrawPaddle
DrawBall
// switch buffer
ADDR [0CH, 8]
CTRL [0CH, 7]
EXE
CTRL [09, 7]
EXE
// long jump
DATA [LOOP]
ADDR [0, 8] CTRL [01, 7] EXE
strInfo:
" attoPong 1.0 " $00
strInfo2:
"Programmed by Tomas \"Frooxius\" Mariancik" $00
strScore0:
" Player 0 score: " $00
strScore1:
" Player 1 score: " $00
endLine:
" " $00
|
Naposledy upravil Frooxius dne 19. květen 2011, 21:37:57, celkově upraveno 3 krát |
|