Updated
This commit is contained in:
@@ -145,3 +145,87 @@ assert(instancesWithoutElements.includes('component_1:'));
|
||||
|
||||
const multiPortComponentPorts = helpers.buildPageComponentPorts(null, elementNodes);
|
||||
assert.deepStrictEqual(multiPortComponentPorts.in0, { x: 10, y: 20, a: 180, width: 0.7 });
|
||||
|
||||
const technologyManifest = {
|
||||
defaults: { xsection: 'strip', width: 0.45, radius: 10, routing_type: 'euler_bend' },
|
||||
xsections: {
|
||||
strip: { family: 'optical', default_width: 0.45 },
|
||||
rib_low: { family: 'optical', default_width: 0.5 },
|
||||
metal_1: { family: 'electrical', default_width: 5 },
|
||||
metal_2: { family: 'electrical', default_width: 6 }
|
||||
},
|
||||
routing_types: ['euler_bend', 'standard_bend']
|
||||
};
|
||||
|
||||
const routeDefaults = helpers.createRouteSettings(technologyManifest);
|
||||
assert.deepStrictEqual(routeDefaults, {
|
||||
xsection: 'strip',
|
||||
family: 'optical',
|
||||
width: 0.45,
|
||||
radius: 10,
|
||||
routing_type: 'euler_bend',
|
||||
widthEdited: false
|
||||
});
|
||||
|
||||
const metalRoute = helpers.updateRouteXsection(routeDefaults, 'metal_1', technologyManifest);
|
||||
assert.strictEqual(metalRoute.family, 'electrical');
|
||||
assert.strictEqual(metalRoute.width, 5);
|
||||
|
||||
const manuallyEditedWidth = helpers.updateRouteField(routeDefaults, 'width', 0.62, technologyManifest);
|
||||
const changedXsection = helpers.updateRouteXsection(manuallyEditedWidth, 'rib_low', technologyManifest);
|
||||
assert.strictEqual(changedXsection.width, 0.62);
|
||||
assert.strictEqual(changedXsection.family, 'optical');
|
||||
|
||||
const styledStrip = helpers.routeStyleForSettings({ xsection: 'strip', family: 'optical' }, false);
|
||||
const styledMetal = helpers.routeStyleForSettings({ xsection: 'metal_1', family: 'electrical' }, true);
|
||||
assert.notStrictEqual(styledStrip.style.stroke, styledMetal.style.stroke);
|
||||
assert(styledMetal.style.strokeDasharray, 'electrical routes should use a visibly different line treatment');
|
||||
assert(styledMetal.style.strokeWidth > styledStrip.style.strokeWidth);
|
||||
|
||||
const routeYaml = helpers.buildBundlesYaml({
|
||||
nodes: [
|
||||
{ id: 'a', data: { componentDisplayName: 'inst_a' } },
|
||||
{ id: 'b', data: { componentDisplayName: 'inst_b' } }
|
||||
],
|
||||
edges: [{
|
||||
id: 'edge-a-b',
|
||||
source: 'a',
|
||||
target: 'b',
|
||||
sourceHandle: 'out',
|
||||
targetHandle: 'in',
|
||||
data: { route: { xsection: 'metal_1', family: 'electrical', width: 5, radius: 20, routing_type: 'standard_bend' } }
|
||||
}]
|
||||
}, technologyManifest);
|
||||
assert(routeYaml.includes('xsection: metal_1'));
|
||||
assert(routeYaml.includes('family: electrical'));
|
||||
assert(routeYaml.includes('radius: 20'));
|
||||
assert(routeYaml.includes('routing_type: standard_bend'));
|
||||
|
||||
const edgeA = {
|
||||
id: 'edge-a-b',
|
||||
source: 'a',
|
||||
target: 'b',
|
||||
data: { route: { family: 'optical' } }
|
||||
};
|
||||
const edgeB = {
|
||||
id: 'edge-c-d',
|
||||
source: 'c',
|
||||
target: 'd',
|
||||
data: { route: { family: 'optical' } }
|
||||
};
|
||||
const edgeC = {
|
||||
id: 'edge-e-f',
|
||||
source: 'e',
|
||||
target: 'f',
|
||||
data: { route: { family: 'electrical' } }
|
||||
};
|
||||
const crossingNodes = {
|
||||
a: { position: { x: 0, y: 0 } },
|
||||
b: { position: { x: 100, y: 100 } },
|
||||
c: { position: { x: 0, y: 100 } },
|
||||
d: { position: { x: 100, y: 0 } },
|
||||
e: { position: { x: 0, y: 100 } },
|
||||
f: { position: { x: 100, y: 0 } }
|
||||
};
|
||||
assert.strictEqual(helpers.findSameFamilyRouteCrossing(edgeB, [edgeA], crossingNodes).conflictEdge.id, 'edge-a-b');
|
||||
assert.strictEqual(helpers.findSameFamilyRouteCrossing(edgeC, [edgeA], crossingNodes), null);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const backendDir = path.join(root, 'backend');
|
||||
const serverPy = fs.readFileSync(path.join(backendDir, 'server.py'), 'utf8');
|
||||
|
||||
assert(
|
||||
fs.existsSync(path.join(backendDir, 'layout_preview.py')),
|
||||
'backend/layout_preview.py should generate SVG previews from saved layout YAML'
|
||||
);
|
||||
assert(
|
||||
fs.existsSync(path.join(backendDir, 'pdk_registry.py')),
|
||||
'backend/pdk_registry.py should resolve public PDK YAML/GDS assets'
|
||||
);
|
||||
assert(
|
||||
fs.existsSync(path.join(backendDir, 'gds_builder.py')),
|
||||
'backend/gds_builder.py should build hierarchical GDS from saved project YAML'
|
||||
);
|
||||
assert(
|
||||
serverPy.includes('create_layout_svg_from_gds'),
|
||||
'save-layout route should create a GDS-derived layout SVG preview'
|
||||
);
|
||||
assert(
|
||||
serverPy.includes('svg_url'),
|
||||
'save-layout response should include an svg_url for the new layout tab'
|
||||
);
|
||||
assert(
|
||||
serverPy.includes("@app.route('/api/projects/<project_name>/cells/<cell_name>/layout.svg')"),
|
||||
'server should expose a route for saved cell SVG previews'
|
||||
);
|
||||
assert(
|
||||
serverPy.includes("@app.route('/api/build-gds'"),
|
||||
'server should expose a Build GDS API route'
|
||||
);
|
||||
assert(
|
||||
serverPy.includes("@app.route('/api/technologies/<foundry>/<technology>/manifest'"),
|
||||
'server should expose a technology manifest API route'
|
||||
);
|
||||
assert(
|
||||
fs.existsSync(path.join(backendDir, 'technology_manifest.py')),
|
||||
'backend/technology_manifest.py should read generated technology manifests'
|
||||
);
|
||||
|
||||
const techManifestPath = path.join(root, 'mxpic', 'PDKs', 'Silterra', 'EMO1_2ML_CU_Al_RDL', 'technology.yml');
|
||||
assert(
|
||||
fs.existsSync(techManifestPath),
|
||||
'Silterra technology.yml should be generated into the EDA PDK folder'
|
||||
);
|
||||
const techManifest = fs.readFileSync(techManifestPath, 'utf8');
|
||||
for (const xsection of ['strip', 'rib_low', 'metal_1', 'metal_2']) {
|
||||
assert(techManifest.includes(`${xsection}:`), `technology.yml should include ${xsection}`);
|
||||
}
|
||||
assert(techManifest.includes('family: optical'), 'technology.yml should classify optical xsections');
|
||||
assert(techManifest.includes('family: electrical'), 'technology.yml should classify electrical xsections');
|
||||
|
||||
const layoutPreviewPy = fs.readFileSync(path.join(backendDir, 'layout_preview.py'), 'utf8');
|
||||
assert(
|
||||
layoutPreviewPy.includes('read_gds') || layoutPreviewPy.includes('load_gds'),
|
||||
'layout_preview.py should load public _BB.gds geometry, not draw only schematic boxes'
|
||||
);
|
||||
assert(
|
||||
layoutPreviewPy.includes('_BB.gds') || layoutPreviewPy.includes('gds_path'),
|
||||
'layout_preview.py should resolve public GDS assets for placed components'
|
||||
);
|
||||
@@ -0,0 +1,75 @@
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const canvasHtml = fs.readFileSync(path.join(root, 'frontend', 'canvas.html'), 'utf8');
|
||||
|
||||
assert(
|
||||
canvasHtml.includes('Build GDS'),
|
||||
'Project Tree header should include a Build GDS button'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('/api/build-gds'),
|
||||
'Build GDS button should call the backend build-gds API'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes(':layout'),
|
||||
'Build Layout should open an SVG preview tab named like canvas_1:layout'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('svg_url'),
|
||||
'Build Layout should use the backend svg_url response'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('layoutPreview'),
|
||||
'canvas pages should support a layoutPreview tab type'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('LayoutSvgPreview'),
|
||||
'layout preview tabs should use the auto-scaling SVG viewer'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('layoutScale'),
|
||||
'layout SVG preview should expose an editable scale value'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('objectFit: \'contain\''),
|
||||
'100% layout preview scale should fit the full SVG within the screen'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('className="build-gds-btn"'),
|
||||
'Build GDS should use a dedicated polished button class'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('buildGdsBusy'),
|
||||
'Build GDS should expose an in-progress state to prevent duplicate requests'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('Build GDS network error'),
|
||||
'Build GDS fetch failures should produce a specific network diagnostic'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('className="build-layout-btn"'),
|
||||
'Build Layout should use the polished primary action class'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('Route Editor'),
|
||||
'Selecting an edge should expose a route editor'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('selectedEdge'),
|
||||
'canvas should track selected edges separately from selected nodes'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('technologyManifest'),
|
||||
'canvas should load the selected technology manifest'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('standard_bend'),
|
||||
'route editor should offer standard_bend as a routing type'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('findSameFamilyRouteCrossing'),
|
||||
'canvas should validate same-family route crossings'
|
||||
);
|
||||
Reference in New Issue
Block a user