Skip to content

Commit 390b47d

Browse files
Nakshatra SharmaNakshatra Sharma
authored andcommitted
feat: Add Parity Generator component
- Added ParityGenerator.js with XOR-based even parity logic - Added ParityGenerator.svg icon matching project style - Registered component in metadata.ts under Misc category - Added unit tests for parity calculations Closes #586
1 parent 5fb1a1b commit 390b47d

File tree

5 files changed

+263
-1
lines changed

5 files changed

+263
-1
lines changed

src/assets/img/ParityGenerator.svg

Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { describe, it, expect, vi, beforeEach } from 'vitest';
5+
6+
vi.mock('../src/themer/themer', () => ({
7+
colors: {
8+
'stroke': '#000000',
9+
'fill': '#FFFFFF',
10+
'hover_select': '#CCCCCC',
11+
'text': '#000000'
12+
},
13+
updateThemeForStyle: vi.fn(),
14+
getThemeCardSvg: vi.fn()
15+
}));
16+
17+
vi.mock('../src/engine', () => ({
18+
scheduleUpdate: vi.fn(),
19+
wireToBeCheckedSet: vi.fn(),
20+
updateCanvasSet: vi.fn(),
21+
forceResetNodesSet: vi.fn(),
22+
updateSimulationSet: vi.fn(),
23+
renderCanvas: vi.fn(),
24+
canvasMessageData: {},
25+
errorDetectedGet: vi.fn().mockReturnValue(false),
26+
errorDetectedSet: vi.fn()
27+
}));
28+
29+
vi.mock('../src/ux', () => ({
30+
fillSubcircuitElements: vi.fn()
31+
}));
32+
33+
vi.mock('../src/modules', () => ({
34+
changeInputSize: vi.fn(),
35+
default: {}
36+
}));
37+
38+
vi.mock('../src/subcircuit', () => ({
39+
default: class SubCircuit {}
40+
}));
41+
42+
vi.mock('../src/data/load', () => ({
43+
loadScope: vi.fn(),
44+
default: vi.fn()
45+
}));
46+
47+
vi.mock('../src/data', () => ({
48+
default: {
49+
save: vi.fn(),
50+
load: vi.fn(),
51+
createSaveAsImgPrompt: vi.fn(),
52+
clearProject: vi.fn(),
53+
newProject: vi.fn(),
54+
saveOffline: vi.fn(),
55+
createOpenLocalPrompt: vi.fn(),
56+
recoverProject: vi.fn(),
57+
createSubCircuitPrompt: vi.fn(),
58+
createCombinationalAnalysisPrompt: vi.fn(),
59+
fullViewOption: vi.fn(),
60+
colorThemes: vi.fn(),
61+
showTourGuide: vi.fn(),
62+
newVerilogModule: vi.fn(),
63+
generateVerilog: vi.fn(),
64+
bitconverter: vi.fn(),
65+
createNewCircuitScope: vi.fn(),
66+
customShortcut: vi.fn(),
67+
ExportProject: {},
68+
ImportProject: {}
69+
}
70+
}));
71+
72+
vi.mock('../src/combinationalAnalysis', () => ({
73+
performCombinationalAnalysis: vi.fn(),
74+
GenerateCircuit: vi.fn(),
75+
createBooleanPrompt: vi.fn()
76+
}));
77+
78+
vi.mock('../src/layoutMode', () => ({
79+
layoutModeGet: vi.fn().mockReturnValue(false),
80+
layoutModeSet: vi.fn(),
81+
tempBuffer: {},
82+
determineLabel: vi.fn(),
83+
paneLayout: vi.fn(),
84+
layoutUpdate: vi.fn()
85+
}));
86+
87+
import ParityGenerator from '../src/modules/ParityGenerator';
88+
import { simulationArea } from '../src/simulationArea';
89+
90+
describe('ParityGenerator', () => {
91+
let parityGen;
92+
93+
beforeEach(() => {
94+
global.globalScope = {
95+
ParityGenerator: [],
96+
scale: 1,
97+
ox: 0,
98+
oy: 0,
99+
allNodes: [],
100+
nodes: []
101+
};
102+
103+
simulationArea.simulationQueue = {
104+
add: vi.fn()
105+
};
106+
simulationArea.context = {
107+
beginPath: vi.fn(),
108+
fillStyle: '',
109+
textAlign: '',
110+
fill: vi.fn(),
111+
stroke: vi.fn(),
112+
moveTo: vi.fn(),
113+
lineTo: vi.fn(),
114+
bezierCurveTo: vi.fn(),
115+
closePath: vi.fn(),
116+
arc: vi.fn(),
117+
fillText: vi.fn(),
118+
measureText: vi.fn(() => ({ width: 0 }))
119+
};
120+
});
121+
122+
it('should initialize with 3 inputs by default', () => {
123+
parityGen = new ParityGenerator(0, 0, global.globalScope, 'RIGHT', 3);
124+
expect(parityGen.inputSize).toBe(3);
125+
expect(parityGen.inp.length).toBe(3);
126+
});
127+
128+
it('should calculate parity correctly (Odd 1s -> 1)', () => {
129+
parityGen = new ParityGenerator(0, 0, global.globalScope, 'RIGHT', 3);
130+
parityGen.inp[0].value = 1;
131+
parityGen.inp[1].value = 0;
132+
parityGen.inp[2].value = 0;
133+
parityGen.resolve();
134+
expect(parityGen.output1.value).toBe(1);
135+
});
136+
137+
it('should calculate parity correctly (Even 1s -> 0)', () => {
138+
parityGen = new ParityGenerator(0, 0, global.globalScope, 'RIGHT', 3);
139+
parityGen.inp[0].value = 1;
140+
parityGen.inp[1].value = 1;
141+
parityGen.inp[2].value = 0;
142+
parityGen.resolve();
143+
expect(parityGen.output1.value).toBe(0);
144+
});
145+
146+
it('should work with 4 inputs', () => {
147+
parityGen = new ParityGenerator(0, 0, global.globalScope, 'RIGHT', 4);
148+
expect(parityGen.inputSize).toBe(4);
149+
expect(parityGen.inp.length).toBe(4);
150+
151+
parityGen.inp[0].value = 1;
152+
parityGen.inp[1].value = 1;
153+
parityGen.inp[2].value = 1;
154+
parityGen.inp[3].value = 0;
155+
parityGen.resolve();
156+
expect(parityGen.output1.value).toBe(1);
157+
158+
parityGen.inp[3].value = 1;
159+
parityGen.resolve();
160+
expect(parityGen.output1.value).toBe(0);
161+
});
162+
});

src/simulator/src/metadata.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ export const circuitElementList = [
6060
"Dlatch",
6161
"TB_Input",
6262
"TB_Output",
63-
"ForceGate"
63+
"ForceGate",
64+
"ParityGenerator"
6465
]
6566
const annotationList = ["Text", "Rectangle", "Arrow", "ImageAnnotation"]
6667
export const moduleList = [...circuitElementList, ...annotationList]
@@ -171,6 +172,7 @@ export const elementHierarchy: Record<string, NameLabel[]> = {
171172
],
172173
"Misc": [
173174
{ name: "TwoComplement", label: "Two Complement" },
175+
{ name: "ParityGenerator", label: "Parity Generator" },
174176
{ name: "Flag", label: "Flag" },
175177
{ name: "Splitter", label: "Splitter" },
176178
{ name: "Adder", label: "Adder" },

src/simulator/src/moduleSetup.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import TwoComplement from './modules/TwoComplement'
4343
import VariableLed from './modules/VariableLed'
4444
import XnorGate from './modules/XnorGate'
4545
import XorGate from './modules/XorGate'
46+
import ParityGenerator from './modules/ParityGenerator'
4647
import Clock from './sequential/Clock'
4748
import DflipFlop from './sequential/DflipFlop'
4849
import Dlatch from './sequential/Dlatch'
@@ -72,6 +73,7 @@ export default function setupModules() {
7273
Counter,
7374
Multiplexer,
7475
XorGate,
76+
ParityGenerator,
7577
XnorGate,
7678
SevenSegDisplay,
7779
SixteenSegDisplay,
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import CircuitElement from '../circuitElement'
2+
import Node, { findNode } from '../node'
3+
import { simulationArea } from '../simulationArea'
4+
import { fillText } from '../canvasApi'
5+
import { changeInputSize } from '../modules'
6+
import { gateGenerateVerilog } from '../utils'
7+
import { colors } from '../themer/themer'
8+
9+
export default class ParityGenerator extends CircuitElement {
10+
constructor(
11+
x,
12+
y,
13+
scope = globalScope,
14+
dir = 'RIGHT',
15+
inputs = 3,
16+
bitWidth = 1
17+
) {
18+
super(x, y, scope, dir, bitWidth)
19+
this.rectangleObject = true
20+
this.setDimensions(20, 20)
21+
this.inp = []
22+
this.inputSize = inputs
23+
if (inputs % 2 === 1) {
24+
for (let i = 0; i < inputs / 2 - 1; i++) {
25+
const a = new Node(-20, -10 * (i + 1), 0, this)
26+
this.inp.push(a)
27+
}
28+
let a = new Node(-20, 0, 0, this)
29+
this.inp.push(a)
30+
for (let i = inputs / 2 + 1; i < inputs; i++) {
31+
a = new Node(-20, 10 * (i + 1 - inputs / 2 - 1), 0, this)
32+
this.inp.push(a)
33+
}
34+
} else {
35+
for (let i = 0; i < inputs / 2; i++) {
36+
const a = new Node(-20, -10 * (i + 1), 0, this)
37+
this.inp.push(a)
38+
}
39+
for (let i = inputs / 2; i < inputs; i++) {
40+
const a = new Node(-20, 10 * (i + 1 - inputs / 2), 0, this)
41+
this.inp.push(a)
42+
}
43+
}
44+
this.output1 = new Node(20, 0, 1, this)
45+
}
46+
47+
customSave() {
48+
const data = {
49+
constructorParamaters: [
50+
this.direction,
51+
this.inputSize,
52+
this.bitWidth,
53+
],
54+
nodes: {
55+
inp: this.inp.map(findNode),
56+
output1: findNode(this.output1),
57+
},
58+
}
59+
return data
60+
}
61+
62+
resolve() {
63+
let result = this.inp[0].value || 0
64+
if (this.isResolvable() === false) {
65+
return
66+
}
67+
for (let i = 1; i < this.inputSize; i++)
68+
result ^= this.inp[i].value || 0
69+
this.output1.value = result
70+
simulationArea.simulationQueue.add(this.output1)
71+
}
72+
73+
customDraw() {
74+
var ctx = simulationArea.context
75+
ctx.beginPath()
76+
ctx.fillStyle = colors['text']
77+
ctx.textAlign = 'center'
78+
fillText(ctx, 'Parity', this.x, this.y + 2, 10)
79+
ctx.fill()
80+
}
81+
82+
generateVerilog() {
83+
return gateGenerateVerilog.call(this, '^')
84+
}
85+
}
86+
87+
ParityGenerator.prototype.tooltipText = 'Parity Generator: Outputs 1 if odd number of 1s.'
88+
ParityGenerator.prototype.alwaysResolve = true
89+
ParityGenerator.prototype.changeInputSize = changeInputSize
90+
ParityGenerator.prototype.verilogType = 'xor'
91+
ParityGenerator.prototype.objectType = 'ParityGenerator'

0 commit comments

Comments
 (0)