//http://www.cs.utexas.edu/users/dburger/teaching/cs395t-s08/papers/9_coherence.pdf // // Vivio CF78J directory cache coherency protocol animation // // Copyright © joksimovic.milos@yahoo.com // // // Vivio MSI cache coherency protocol animation // // Copyright © 2002 - 2006 jones@cs.tcd.ie // // This program is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software Foundation; // either version 2 of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software Foundation Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // // 27/08/02 first version [based on Vivio Write-Once and Firefly animations] // 04/12/02 read cache line from memory on a write miss // 03/06/05 corrected URL link to MESIHelp.htm // 23/01/06 Vivio 4.0 // 20/02/06 real x,y event parameters // 07/11/06 added extra wait(1) after getting bus lock // 11/01/07 added Vivio logo #include "icnAllToAll_4.vin" #include "SimpleButton.vin" const LOGICALW = 800; const LOGICALH = 600; setViewport(0, 0, LOGICALW+200, LOGICALH, 1); //setAngle(LOGICALW/2, LOGICALH/2, 180); // upside down!! //setSize(LOGICALW/2, LOGICALH/2); //translate(LOGICALW/4, LOGICALH/4); //setPen(redpen); // // set background // bgbrush = SolidBrush(vellum); setBgBrush(bgbrush); // // fonts // smallFont = Font("Times New Roman", 10, 0); hintFont = Font("Times New Roman", 16, 0); titleFont = Font("Times New Roman", 24, 0); // // title // navybrush = SolidBrush(rgb(0, 0, 102)); title = Rectangle2(0, VCENTRE, 0, navybrush, 5, 5, LOGICALW/15*8.5, 30, whitebrush, titleFont, "CF78J Directory Cache Coherency Protocol"); title.setTxtOff(2, 1); // // simple useage information // str = "Like real hardware, the CPUs can operate\n"; str += "in parallel - try pressing a button on each\n"; str += "CPU \"simultaneously\".\n"; hint = Txt(0, HLEFT | VTOP, 5, 80, blackbrush, hintFont, str); const int NCPU = 4; // number of cpus const int POMERAJ = 0; //90; const int MAX_MSG = 20; // maksimalna velicina fifo const int TICKS = 20; // animation speed const int MSG_TICKS = 60; const icnBrush = red; const int MEMORYID = -1; const int FALSE = 0; const int TRUE = 1; // !=0 const string privateStr = "P"; const string notPrivateStr = "N"; const string validStr = "V"; const string notValidStr = "I"; const string modifiedStr = "M"; const string notModifiedStr = "C"; int wValue = 0; // value written into cache int buglevel = 0; // buglevel [0 = bugfree] int dirtycpu = -1; // indicates CPU supplies dirty data int isshared = 0; // indicates a shared read int watchcnt = 0; // count watch() completions int varwatchcnt = 0; buslock = 0; // bus lock int cpulock[*]; SimpleButton lastButton[*]; // remember last button pressed icn = IcnAllToAll_4(); // interconnection network function resetCpuR (int i); // // vertical double headed bus arrow object // // separate up, down and body objects used to animate bus traffic // class BusArrow(int x, int y, int whead, int wbody, int l, Brush brush) arrow = Polygon(0, AAFILL | ABSOLUTE, 0, blackbrush, x,y, 0, 0, whead,whead, wbody,whead, wbody,l-whead, whead,l-whead, 0,l, -whead,l-whead, -wbody,l-whead, -wbody,whead, -whead,whead); body = Rectangle2(0, 0, 0, brush, x-wbody, y, 2*wbody+1, l); body.setOpacity(0); up = Polygon(0, AAFILL | ABSOLUTE, 0, brush, 0,0, whead,0, 2*whead,whead, 0,whead); up.setOpacity(0); down = Polygon(0, AAFILL | ABSOLUTE, 0, brush, 0,0, 0,0, 2*whead,0, whead,whead); down.setOpacity(0); function reset() arrow.setBrush(blackbrush); up.setOpacity(0); body.setOpacity(0); down.setOpacity(0); arrow.setOpacity(255); end; // // animates up movement on vertical bus arrow // // may be called with ticks = 0 // function moveup(int ticks, int wflag) // // initial positions, size & opacity // up.setPos(x-whead, y+(l-whead)); body.setPos(x-wbody, y+l); body.setSize(2*wbody+1, 0); up.setOpacity(255); body.setOpacity(255); // // final positions, size & opacity // up.setPos(x-whead, y, ticks, 1, 0); body.setPos(x-wbody, y+whead, ticks, 1, 0); body.setSize(2*wbody+1, l+1-whead, ticks, 1, 0); arrow.setOpacity(0, ticks, 1, wflag); end; // // animates down movement on vertical bus arrow // // may be called with ticks = 0 // function movedown(int ticks, int wflag) // // initial positions, size & opacity // down.setPos(x-whead, y); body.setPos(x-wbody, y); body.setSize(2*wbody+1, 0); down.setOpacity(255); body.setOpacity(255); // // final positions, size & opacity // down.setPos(x-whead, y+l-whead, ticks, 1, 0); body.setSize(2*wbody+1, l+1-whead, ticks, 1, 0); arrow.setOpacity(0, ticks, 1, wflag); end; end; class Cache; // forward declaration Cache cache[NCPU]; // cache objects FIFO function getFifo (int id); // // memory object // // contains 4 memory locations a0, a1, a2 & a3 // class Memory(int x, int y) fifo = FIFO (MAX_MSG); r = Rectangle2(0, 0, blackpen, gray192brush, x, y, 150, 100); t = Txt(0, HLEFT | VTOP, x+160, y+40, redbrush, 0, "memory"); for (int i = 0; i < 4; i++) ackCounter[i] = -2; ackMaster[i] = -2; //stale[i] = 0; modified[i] = 0; modifiedR[i] = Rectangle2(0, 0, blackpen, whitebrush, x+5, y+4+i*24, 20+1, 20, blackbrush, 0, modifiedStr); modifiedR[i].setTxtOff(0, 1); present[i][NCPU] = 0; presentR[i] = Rectangle2(0, 0, blackpen, whitebrush, x+25, y+4+i*24, 50+1, 20, blackbrush, 0, "0000"); presentR[i].setTxtOff(0, 1); addrR[i] = Rectangle2(0, 0, blackpen, whitebrush, x+75, y+4+i*24, 30+1, 20, blackbrush, 0, "a%d", i); addrR[i].setTxtOff(0, 1); mem[i] = 0; memR[i] = Rectangle2(0, 0, blackpen, whitebrush, x+105, y+4+i*24, 40+1, 20, blackbrush, 0, "%d", mem[i]); memR[i].setTxtOff(0, 1); end; function repaint (int addr) modifiedR[addr].setTxt ("%s", (modified[addr] == TRUE) ? modifiedStr : notModifiedStr ); presentR[addr].setTxt ("%d%d%d%d", present[addr][0], present[addr][1], present[addr][2], present[addr][3]); memR[addr].setTxt ("%d", mem[addr]); // memR[addr].setTxt ("m: %d p: %d%d%d%d a%d %d", modified[addr], present[addr][0], present[addr][1], present[addr][2], present[addr][3], addr, mem[addr]); // memR[addr].setTxt ("%d m: %d p: %d%d%d%d a%d %d", ackCounter[addr], modified[addr], present[addr][0], present[addr][1], present[addr][2], present[addr][3], addr, mem[addr]); end; function repaintAll () for (int i = 0; i < NCPU; i++) repaint (i); end; end; for (i = 0; i < 4; i++) repaint (i); end; function highlight(int addr, int flag) modifiedR[addr].setBrush((flag) ? greenbrush : whitebrush); presentR[addr].setBrush((flag) ? greenbrush : whitebrush); addrR[addr].setBrush((flag) ? greenbrush : whitebrush); memR[addr].setBrush((flag) ? greenbrush : whitebrush); end; function reset() for (int i = 0; i < 4; i++) highlight(i, 0); end; end; function read (Message msg, int excl); function exclude (Message msg); function write (Message msg); function eject (Message msg); function writeandeject (Message msg); function processMessage(Message msg) if (msg.type == READ) read (msg, FALSE); else if (msg.type == READX) read (msg, TRUE); else if (msg.type == EXCLUDE) exclude (msg); else if (msg.type == WRITE) write (msg); else if (msg.type == EJECT) eject (msg); else if (msg.type == WRITEANDEJECT) writeandeject (msg); end;end;end;end;end;end; end; Message function waitForMessage (int type, int source) m = fifo.get(); while (!((m.type == type) && (m.source == source))) processMessage(m); m = fifo.get(); end; return m; end; Message function waitMessage () m = fifo.get(); processMessage(m); end; function waitAnyMessage() while(1) m = fifo.get(); processMessage(m); end; end; // izvrsava se u konstruktoru // formira novu 'nit' koja ceka poruke i reaguje na njih fork (waitAnyMessage()); // funkcije koje su specificne za protokol function read (Message msg, int excl) int blknum = msg.address; int knum = msg.source; if (ackCounter[blknum] != -2) m = Message (BUSY, MEMORYID, msg.master, blknum); icn.sendMessage (MEMORYID, knum, getFifo(knum), MSG_TICKS, icnBrush, m, 0); return; end; ackCounter[blknum] = 0; ackMaster[blknum] = msg.master; repaint (blknum); for (i=0; i 0) waitMessage(); end; end; present[blknum][knum] = TRUE; modified[blknum] = excl; repaint (blknum); //highlight (blknum, 1); m = Message (READRESPONSE, MEMORYID, msg.master, blknum); m.setValue (mem[blknum]); icn.sendMessage (MEMORYID, knum, getFifo(knum), MSG_TICKS, icnBrush, m, 0); // kraj transakcije error = -1; if (ackCounter[blknum] == 0) ackCounter[blknum] = -2; else cache[error]; // ovo ne bi trebalo da se dogodi end; ackMaster[blknum] = -2; repaint (blknum); end; function exclude (Message msg) int blknum = msg.address; int knum = msg.source; if (ackCounter[blknum] != -2) m = Message (BUSY, MEMORYID, msg.master, blknum); icn.sendMessage (MEMORYID, knum, getFifo(knum), MSG_TICKS, icnBrush, m, 0); return; end; ackCounter[blknum] = 0; ackMaster[blknum] = msg.master; repaint (blknum); for (i=0; i 0) waitMessage(); end; modified[blknum] = TRUE; m = Message (EXCLUDERESPONSE, MEMORYID, msg.master, blknum); icn.sendMessage (MEMORYID, knum, getFifo(knum), MSG_TICKS, icnBrush, m, 0); // kraj transakcije error = -1; if (ackCounter[blknum] == 0) ackCounter[blknum] = -2; else cache[error]; // ovo ne bi trebalo da se dogodi end; ackMaster[blknum] = -2; repaint (blknum); end; function write (Message msg) blknum = msg.address; mem[blknum] = msg.getValue(); repaint (blknum); end; function eject (Message msg) blknum = msg.address; knum = msg.source; if ((ackMaster[blknum] == msg.master) || (ackMaster[blknum] == -2)) if (ackCounter[blknum] > 0) ackCounter[blknum]--; end; present[blknum][knum] = FALSE; repaint (blknum); if (knum == msg.master) m = Message (ACK, MEMORYID, msg.master, blknum); icn.sendMessage (MEMORYID, knum, getFifo(knum), MSG_TICKS, icnBrush, m, 0); end; else m = Message (BUSY, MEMORYID, msg.master, blknum); icn.sendMessage (MEMORYID, knum, getFifo(knum), MSG_TICKS, icnBrush, m, 0); end; end; function writeandeject (Message msg) blknum = msg.address; knum = msg.source; if ((ackMaster[blknum] == msg.master) || (ackMaster[blknum] == -2)) if (ackCounter[blknum] > 0) ackCounter[blknum]--; end; mem[blknum] = msg.getValue(); present[blknum][knum] = FALSE; modified[blknum] = FALSE; repaint (blknum); if (knum == msg.master) m = Message (ACK, MEMORYID, msg.master, blknum); icn.sendMessage (MEMORYID, knum, getFifo(knum), MSG_TICKS, icnBrush, m, 0); end; else m = Message (BUSY, MEMORYID, msg.master, blknum); icn.sendMessage (MEMORYID, knum, getFifo(knum), MSG_TICKS, icnBrush, m, 0); end; end; end; // Memory end // // horizontal double headed bus object // class Bus(int x, int y, int whead, int wbody, int l, Brush brush) arrow = Polygon(0, AAFILL | ABSOLUTE, 0, blackbrush, x,y, 0,0, whead,-whead, whead,-wbody, l-whead,-wbody, l-whead,-whead, l,0, l-whead,whead, l-whead,wbody, whead,wbody, whead,whead); function highlight(int flag) arrow.setBrush((flag) ? brush : blackbrush); end; end; memory = Memory(LOGICALW/2-POMERAJ, 50); int newValue = 0; int function getNewValue() return ++newValue; end; // // cache object // class Cache(int x, int y, int cpu) fifo = FIFO (MAX_MSG); cpuabus = BusArrow(x+45, y+50, 10, 4, 50, bluebrush); cpudbus = BusArrow(x+115, y+50, 10, 4, 50, redbrush); r = Rectangle2(0, 0, blackpen, gray192brush, x,y, 150,50); tx = Txt(0, HLEFT | VTOP, x+130, y+53, redbrush, 0, "cache %d", cpu); for (int i = 0; i < 2; i++) valid[i] = 0;//01 validR[i] = Rectangle2(0, 0, blackpen, whitebrush, x+5, y+6+i*20, 20+1, 17+1, blackbrush, 0, notValidStr); validR[i].setTxtOff(0, 1); private[i] = 0;//01 privateR[i] = Rectangle2(0, 0, blackpen, whitebrush, x+25, y+6+i*20, 20+1, 17+1, blackbrush, 0, notPrivateStr); privateR[i].setTxtOff(0, 1); directory[i] = 0;//02 directoryR[i] = Rectangle2(0, 0, blackpen, whitebrush, x+45, y+6+i*20, 50+1, 17+1); directoryR[i].setTxtOff(0, 1); data[i] = 0; dataR[i] = Rectangle2(0, 0, blackpen, whitebrush, x+95, y+6+i*20, 50+1, 17+1); dataR[i].setTxtOff(0, 1); end; function repaint (int set) validR[set].setTxt ( (valid[set]==TRUE) ? validStr : notValidStr ); privateR[set].setTxt ( (private[set]==TRUE) ? privateStr : notPrivateStr ); directoryR[set].setTxt ("a%d", directory[set]); dataR[set].setTxt ("%d", data[set]); end; function repaintAll () for (int i=0; i<2; i++) repaint (i); end; end; // // highlight cache line // function highlight(int set, int flag) brush = (flag) ? greenbrush : whitebrush; validR[set].setBrush(brush); privateR[set].setBrush(brush); directoryR[set].setBrush(brush); dataR[set].setBrush(brush); end; // // reset cache lines, cpu address and data busses // function reset(int i) cache[i].cpuabus.reset(); cache[i].cpudbus.reset(); cache[i].highlight(0, 0); cache[i].highlight(1, 0); end; // // reset objects at start of a read or write operation // function resetStart() for (int i = 0; i < NCPU; i++) if ((cpulock[i] == 0) || (cpu == i)) reset(i); lastButton[i].setborderpen(blackpen); end; end; end; // // reset objects at end of a read or write operation // function resetEnd() for (int i = 0; i < NCPU; i++) if (cpulock[i] == 0) reset(i); lastButton[i].setborderpen(blackpen); end; end; end; function load(int blknum, int animatecpu); function store (int, int); function update (Message msg); function purge (Message msg); function processMessage(Message msg) if (msg.type == LOAD) load (msg.address, 1); else if (msg.type == STORE) store (msg.address, TRUE); else if (msg.type == UPDATE) update (msg); else if (msg.type == PURGE) purge (msg); end;end;end;end; end; Message function waitForMessage (int type, int source) m = fifo.get(); while (!((m.type == type) && (m.source == source))) processMessage(m); m = fifo.get(); end; return m; end; function waitAnyMessage() while(1) m = fifo.get(); processMessage(m); end; end; // izvrsava se u konstruktoru // formira novu 'nit' koja ceka poruke i reaguje na njih fork (waitAnyMessage()); // ukoliko je blok kesiran, vraca ulaz u kom se nalazi // u suprotnom vraca -1 int function search (int blknum) int set = blknum % 2; if (directory[set] == blknum) return set; else return -1; end; end; // vraca ulaz u koji blok treba da se smesti int function select (int blknum) int set = blknum % 2; return set; end; // funkcije specificne za protokol int function evict (int blknum) kloc = select (blknum); loop = TRUE; while (loop) if (valid[kloc] == TRUE) addr = directory[kloc]; if (private[kloc] == TRUE) //writeandeject m = Message (WRITEANDEJECT, cpu, cpu, addr); m.setValue (data[kloc]); icn.sendMessage (cpu, MEMORYID, memory.fifo, MSG_TICKS, icnBrush, m, 0); else //eject m = Message (EJECT, cpu, cpu, addr); icn.sendMessage (cpu, MEMORYID, memory.fifo, MSG_TICKS, icnBrush, m, 0); end; // procesira sve poruke dok ne dobije BUSY ili ACK // ako dobije BUSY pokusava ponovo, sa ACK nastavlja dalje // moguce je da nakon BUSY stanje vise nije 'V', vec 'I' loop1 = TRUE; while (loop1 == TRUE) m = fifo.get(); if ((m.type == BUSY) && (m.source == MEMORYID)) loop1 = FALSE; loop = TRUE; else if ((m.type == ACK) && (m.source == MEMORYID)) loop1 = FALSE; loop = FALSE; else processMessage (m); end;end; end; else loop = FALSE; end; end; return kloc; end; // // CPU cache read // function load(int blknum, int animatecpu) if (animatecpu) cpuabus.moveup(TICKS, 1); end; int kloc = search (blknum); t = TRUE; if (kloc == -1) t = FALSE; end; if (t == TRUE) t = valid[kloc]; end; // t = FALSE - blok je nevalidan ili je za validan za izbacivanje if (t == FALSE) kloc = evict (blknum); loop = TRUE; while (loop == TRUE) m = Message (READ, cpu, cpu, blknum); icn.sendMessage (cpu, MEMORYID, memory.fifo, MSG_TICKS, icnBrush, m, 0); m = fifo.get(); if ((m.type == BUSY) && (m.source == MEMORYID)) loop = TRUE; else if ((m.type == READRESPONSE) && (m.source == MEMORYID)) loop = FALSE; else processMessage (m); end;end; end; data[kloc] = m.getValue(); valid[kloc] = TRUE; private[kloc] = FALSE; directory[kloc] = blknum; highlight(kloc, 1); repaint (kloc); end; if (animatecpu) cpudbus.movedown(TICKS, 1); end; icn.markirajPoruke (cpu); start (0); icn.ukloniPoruke (cpu, 20); resetCpuR(cpu); cache[cpu].resetEnd(); cpulock[cpu] = 0; end; // // CPU cache write // function store(int blknum, int prviPoziv) int set = select (blknum); int kloc = search (blknum); t = TRUE; if (kloc == -1) t = FALSE; end; if (prviPoziv == TRUE) cpudbus.moveup(TICKS, 0); cpuabus.moveup(TICKS, 1); //highlight(set, 1); end; if (t == TRUE) t = valid[kloc]; end; if (t == FALSE) kloc = evict (blknum); loop = TRUE; while (loop == TRUE) m = Message (READX, cpu, cpu, blknum); icn.sendMessage (cpu, MEMORYID, memory.fifo, MSG_TICKS, icnBrush, m, 0); m = fifo.get(); if ((m.type == BUSY) && (m.source == MEMORYID)) loop = TRUE; else if ((m.type == READRESPONSE) && (m.source == MEMORYID)) loop = FALSE; else processMessage (m); end;end; end; data[kloc] = m.getValue(); valid[kloc] = TRUE; directory[kloc] = blknum; else if (private[kloc] == FALSE) m = Message (EXCLUDE, cpu, cpu, blknum); icn.sendMessage (cpu, MEMORYID, memory.fifo, MSG_TICKS, icnBrush, m, 0); // m = waitForMessage (EXCLUDERESPONSE, MEMORYID); // ceka odgovor od memorije i obradjuje druge zahteve // malo se zakomplikovalo zbog BUSY loop = TRUE; while (loop == TRUE) m = fifo.get(); if ((m.type == BUSY) && (m.source == MEMORYID)) // p1:read --- p0:write + p1:write // p1 poslao EXC, dobije PUR, dobije BUSY, pa salje RDX store (blknum, FALSE); loop = FALSE; else if ((m.type == EXCLUDERESPONSE) && (m.source == MEMORYID)) loop = FALSE; else processMessage (m); end;end; end; end; end; if (prviPoziv == TRUE) private[kloc] = TRUE; data[kloc] = getNewValue(); highlight(set, 1); repaint (kloc); icn.markirajPoruke (cpu); start (0); icn.ukloniPoruke (cpu, 20); resetCpuR(cpu); cache[cpu].resetEnd(); cpulock[cpu] = 0; checkPoint(); end; end; function update (Message msg) blknum = msg.address; int kloc = search (blknum); t = TRUE; if (kloc == -1) t = FALSE; end; if (t == TRUE) if ((valid[kloc] == TRUE) && (private[kloc] == TRUE)) t = TRUE; else t = FALSE; end; end; if (t == TRUE) m = Message (WRITE, cpu, msg.master, blknum); m.setValue (data[kloc]); icn.sendMessage (cpu, MEMORYID, memory.fifo, MSG_TICKS, icnBrush, m, 0); private[kloc] = FALSE; end; repaint (kloc); end; function purge (Message msg) blknum = msg.address; int kloc = search (blknum); t = TRUE; if (kloc == -1) t = FALSE; end; if (t == TRUE) t = valid[kloc]; end; if (t == TRUE) if (private[kloc] == TRUE) //writeandeject m = Message (WRITEANDEJECT, cpu, msg.master, blknum); m.setValue (data[kloc]); icn.sendMessage (cpu, MEMORYID, memory.fifo, MSG_TICKS, icnBrush, m, 0); else //eject m = Message (EJECT, cpu, msg.master, blknum); icn.sendMessage (cpu, MEMORYID, memory.fifo, MSG_TICKS, icnBrush, m, 0); end; valid[kloc] = FALSE; end; repaint (kloc); end; end; // // CPU read button // class readOp(int x, int y, int addr, int cpu, Rectangle r) b = SimpleButton(x, y, 65, 22, bgbrush, gray192brush, blackbrush, 0, "read"); b.button.setTxt("read a%d", addr); when b.button.eventLB(int down, real xx, real yy) if (down == 0 && cpulock[cpu] == 0) cpulock[cpu] = 1; r.setPen(greenpen); cache[cpu].resetStart(); b.setborderpen(redpen); start(1); lastButton[cpu] = b; cache[cpu].fifo.put (Message(LOAD, cpu, -2, addr)); end; end; end; // // CPU write button // class writeOp(int x, int y, int addr, int cpu, Rectangle r) b = SimpleButton(x, y, 65, 22, bgbrush, gray192brush, blackbrush, 0, "write"); b.button.setTxt("write a%d", addr); when b.button.eventLB(int down, real xx, real yy) if (down == 0 && cpulock[cpu] == 0) cpulock[cpu] = 1; r.setPen(greenpen); cache[cpu].resetStart(); b.setborderpen(redpen); start(1); lastButton[cpu] = b; cache[cpu].fifo.put (Message(STORE, cpu, -2, addr)); end; end; end; // // CPU object // class CPU(int x, int y, int cpu) r = Rectangle2(0, 0, blackpen, gray192brush, x, y, 150, 100); t = Txt(0, HLEFT | VTOP, x+130, y-20, redbrush, 0, "CPU %d", cpu); for (int i = 0; i < NCPU; i++) rb[i] = readOp(x+5, y+5+i*23, i, cpu, r); wb[i] = writeOp(x+80, y+5+i*23, i, cpu, r); end; end; CPU cpu[NCPU]; function createCpuAndCache(int x, int y, int i) cpu[i] = CPU(x, y, i); cache[i] = Cache(x, y-100, i); lastButton[i] = cpu[i].rb[0].b; cpulock[i] = 0; end; for (int i=0;i= 0) && (id < NCPU)) return cache[id].fifo; end; return FIFO(5); end; resetButton = SimpleButton(LOGICALW-75, LOGICALH-35, 75, 25, bgbrush, gray192brush, blackbrush, 0, "reset"); when resetButton.button.eventLB(int down, real x, real y) if (down == 0) reset(); end; end; // eof