diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 020d55031f1d78efc29fd39501635359263e4d6e..c4f6de98797b8dda368c9de896b58e9d342ad3ea 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -2,7 +2,8 @@ <nav> <ul> + <li><a href="./mips-data-dependency">Data Dependencies (🚧)</a></li> <li><a href="./lru">LRU</a></li> - <li><a href="./scoreboard">Scoreboard</a></li> + <li><a href="./scoreboard">Scoreboard (🚧</a></li> </ul> </nav> diff --git a/src/routes/mips-data-dependency/+page.svelte b/src/routes/mips-data-dependency/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..cfd91ae416610d453deda46a9aa990a6dd20090f --- /dev/null +++ b/src/routes/mips-data-dependency/+page.svelte @@ -0,0 +1,215 @@ +<script> + import { generateInstructions, hasWarHazard } from './hazards'; + + import Operand from './operand.svelte'; + + /** @type {{instruction: string, operands: string[]}[]} */ + let hazardousInstructions = []; + + /** @type {[number,number][]} */ + let currentlyMarked = []; + $: notTwoSelected = currentlyMarked.length !== 2; + + /** @type {string[]} */ + let hazardsFound = []; + + /** @type {[number,number][]} */ + let colorings = []; + + /** + * @param instructionIndex {number} + * @param operandIndex {number} + */ + function handleOpClick(instructionIndex, operandIndex) { + if (currentlyMarked.some(([li, oi]) => li === instructionIndex && oi === operandIndex)) { + currentlyMarked = currentlyMarked.filter( + ([li, oi]) => !(li === instructionIndex && oi === operandIndex) + ); + } else { + if (currentlyMarked.length >= 2) { + return; + } + currentlyMarked = [...currentlyMarked, [instructionIndex, operandIndex]]; + } + hazardousInstructions = hazardousInstructions; + } + + function handleWarClick() { + const [firstMarker, secondMarker] = currentlyMarked.sort((a, b) => a[0] - b[0]); + const firstInstruction = hazardousInstructions[firstMarker[0]]; + const secondInstruction = hazardousInstructions[secondMarker[0]]; + const isWar = hasWarHazard( + firstInstruction, + firstMarker[1], + secondInstruction, + secondMarker[1] + ); + + if (isWar) { + currentlyMarked = []; + + hazardsFound = [ + ...hazardsFound, + 'WAR: ' + + stringifyInstruction(firstInstruction) + + ' -> ' + + stringifyInstruction(secondInstruction) + ]; + colorings = [...colorings, firstMarker, secondMarker]; + + hazardousInstructions = hazardousInstructions; + } else { + alert('Computer says: No WAR'); + } + } + + function handleRawClick() {} + + function handleWawClick() {} + + /** + * @param instruction {{ + * instruction: string; + * operands: string[]; + * }} + */ + function stringifyInstruction(instruction) { + return `${instruction.instruction} ${instruction.operands.join(',')}`; + } + + /** + * @param instrIndex {number} + * @param operandIndex {number} + */ + function getOperandColor(instrIndex, operandIndex) { + if (colorings.some(([ii, oi]) => ii === instrIndex && oi === operandIndex)) { + return 'red'; + } else { + return null; + } + } +</script> + +<h1>Data Dependencies</h1> + +<fieldset> + <details> + <summary>What is this all about?</summary> + <p> + While theoretically, CPU instructions follow each other back-to-back, one per clock cycle, in + reality varying instructions tend take a varying amount of time to complete. + <br /> + Consider this code: + </p> + <pre>ADD.D F3;F1;F2 ; F3 = F1+F2<br />ADD.D F4;F1;F3 ; F4 = F1+F3</pre> + <p> + The second instruction uses the result of the first instruction (<code>F3</code>) as one if + its operands. To be able to do so, the first <code>ADD.D</code> instruction must have finished + writing it's result into register <code>F3</code>. If the second instruction is executed + before the first finished, it may use an old value of <code>F3</code>, resulting in a wrong + result. + </p> + <p> + This kind of dependency is called <em>RAW</em> (Read After Write). If not considered, it can + become a <em>Data Hazard</em>. To avoid that, CPUs employ <em>Scoreboarding</em> or + <em>Tomasulos Algorithm</em>. + </p> + + <hr /> + <pre>ADD.D F3;F1;F2 ; F3 = F1+F2<br />ADD.D F2;F5;F6 ; F2 = F5+F6</pre> + <p> + In this example, the second instruction writes to <code>F2</code>, which is an operand for the + first instruction. If executed without care, this write might happen before the first + instruction had time to read it first, again resulting in incorrect calculation. A + <em>WAR</em> (Write After Read) hazard occured. + </p> + + <hr /> + + <pre>MUL.D F3;F1;F2 ; F3 = F1+F2<br />ADD.D F3;F5;F6 ; F3 = F5+F6</pre> + <p> + The last data hazard to mention is <em>WAW</em> (Write After Write). If instructions are + executed out of order, instruction two may write to <code>F3</code> before the first one. As a + result, the first instructions writes its result last, leaving the final output wrong. + </p> + </details> +</fieldset> +<br /> + +<div class="quiz"> + <p>1. Mark two operands between which a data hazard exists:</p> + {#each hazardousInstructions as hazardInstr, lineIndex} + <p> + <code class="line-number">{lineIndex + 1}</code> + <code style="instruction-line"> + {hazardInstr.instruction} + <span class="operands-list"> + {#each hazardInstr.operands as operand, operandIndex} + <Operand + marked={currentlyMarked.some(([li, oi]) => li === lineIndex && oi === operandIndex)} + op={operand} + color={getOperandColor(lineIndex, operandIndex)} + on:clicked={() => handleOpClick(lineIndex, operandIndex)} + /> + <span>,</span> + {/each} + </span> + </code> + </p> + {:else} + <button + style:width="16rem" + style:height="13rem" + on:click={() => (hazardousInstructions = generateInstructions())} + > + Generate exercise + </button> + {/each} + + <p>2. Select which hazard it is exactly:</p> + + <div class="button-group"> + <button disabled={true} on:click={handleRawClick}>RAW</button> + <button disabled={notTwoSelected} on:click={handleWarClick}>WAR</button> + <button disabled={true} on:click={handleWawClick}>WAW</button> + </div> + <p>3. Repeat, until there are no more data hazards</p> + <ul> + {#each hazardsFound as hazard} + <li> + {hazard} + </li> + {/each} + </ul> +</div> + +<style> + code.line-number { + color: gray; + margin-right: 0.5rem; + padding-right: 0.5rem; + border-right: solid 1px gray; + } + div.quiz code { + font-size: x-large; + } + div.quiz button { + font-size: large; + width: 5rem; + height: 2rem; + } + div.button-group { + display: flex; + flex-direction: row; + gap: 0.5rem; + } + span.operands-list { + margin-left: 1rem; + display: inline-flex; + gap: 0.5rem; + } + /* Hide the last , */ + span.operands-list span:last-child { + display: none; + } +</style> diff --git a/src/routes/mips-data-dependency/hazards.js b/src/routes/mips-data-dependency/hazards.js new file mode 100644 index 0000000000000000000000000000000000000000..c38b5b89028e543471b4b72adb808c5d9a29a75b --- /dev/null +++ b/src/routes/mips-data-dependency/hazards.js @@ -0,0 +1,31 @@ +const possibleInstructions = ['ADD.D', 'MUL.D']; +const possibleRegisters = ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8']; + +/** @param arr {string[]} */ +function randomFrom(arr) { + const randomIndex = Math.floor(Math.random() * arr.length); + return arr[randomIndex]; +} + +/** + * @returns {{instruction: string, operands: string[]}[]} + */ +export function generateInstructions() { + return new Array(5).fill(null).map(() => ({ + instruction: randomFrom(possibleInstructions), + operands: new Array(3).fill(null).map(() => randomFrom(possibleRegisters)) + })); +} + +/** + * + * @param {{instruction: string, operands: string[]}} instr1 + * @param {number} selectedOp1 + * @param {{instruction: string, operands: string[]}} instr2 + * @param {number} selectedOp2 + * @returns {boolean} + */ +export function hasWarHazard(instr1, selectedOp1, instr2, selectedOp2) { + const registerWrittenTo = instr2.operands[selectedOp2]; + return instr1.operands[1] === registerWrittenTo || instr1.operands[2] === registerWrittenTo; +} diff --git a/src/routes/mips-data-dependency/operand.svelte b/src/routes/mips-data-dependency/operand.svelte new file mode 100644 index 0000000000000000000000000000000000000000..e674bd1c22e389b46a5e1760543742284f677f88 --- /dev/null +++ b/src/routes/mips-data-dependency/operand.svelte @@ -0,0 +1,34 @@ +<script> + import { createEventDispatcher } from 'svelte'; + const dispatch = createEventDispatcher(); + + /** @type {string} */ + export let op; + /** @type {boolean} */ + export let marked = false; + /** @type {string | null} */ + export let color = null; +</script> + +<span style:color style:border-color={color} class:marked on:click={() => dispatch('clicked', op)}> + {op} +</span> + +<style> + span { + border-width: 1px; + border-color: lightgray; + border-style: dashed; + padding-left: 0.4rem; + padding-right: 0.4rem; + } + span.marked { + font-weight: bold; + border-color: grey; + } + span:hover { + border-style: solid; + font-weight: bolder; + cursor: pointer; + } +</style>