Skip to content
Snippets Groups Projects
Verified Commit 8e12849f authored by Jonas Zohren's avatar Jonas Zohren :speech_balloon:
Browse files

Add basic data hazard utility

parent b95c5de5
No related branches found
No related tags found
No related merge requests found
Pipeline #98597 failed
......@@ -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>
<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>
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;
}
<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>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment