This commit is contained in:
锦恢 2024-12-23 00:04:11 +08:00
parent d1ab16c5d2
commit 4586b5b19c
6 changed files with 87 additions and 25 deletions

13
design/netlist.drawio Normal file
View File

@ -0,0 +1,13 @@
<mxfile host="65bd71144e">
<diagram id="SfLhgSYCHkdHWK2D13we" name="第 1 页">
<mxGraphModel dx="588" dy="624" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#6a00ff;fontColor=#ffffff;strokeColor=#3700CC;" vertex="1" parent="1">
<mxGeometry x="240" y="290" width="120" height="60" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@ -1,7 +1,6 @@
<template> <template>
<!-- 渲染区域 --> <!-- 渲染区域 -->
<!-- <Render></Render> --> <Render></Render>
<div id="netlist"></div>
<!-- 右侧工具合集 --> <!-- 右侧工具合集 -->
<RightNav></RightNav> <RightNav></RightNav>
</template> </template>

View File

@ -17,5 +17,8 @@ defineComponent({ name: 'netlist-render' });
height: 100vh; height: 100vh;
width: calc(100vw - var(--right-nav-width)); width: calc(100vw - var(--right-nav-width));
overflow: hidden; overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
} }
</style> </style>

View File

@ -148,6 +148,8 @@
/** /**
* @typedef ElkNode * @typedef ElkNode
* @property {string} id 节点的唯一标识符 * @property {string} id 节点的唯一标识符
* @property {string} [renderName] node 展示的名字布局算法前生成
* @property {'port' | 'cell' | 'cellPort'} [renderType] 渲染的类型
* @property {ElkNode[]} children 当前节点的内部 * @property {ElkNode[]} children 当前节点的内部
* @property {ElkPort[]} ports 当前节点的端口 * @property {ElkPort[]} ports 当前节点的端口
* @property {ElkEdge[]} edges 当前节点的连线 * @property {ElkEdge[]} edges 当前节点的连线
@ -188,8 +190,9 @@
/** /**
* @typedef ElkSection * @typedef ElkSection
* @property {ElkPoint} startPoint * @property {ElkPoint} startPoint 起点
* @property {ElkPoint} endPoint * @property {ElkPoint} endPoint 终点
* @property {ElkPoint[]} bendPoints 中间经过的点
* @property {string} id edge id * @property {string} id edge id
* @property {string} incomingShape source 实体的 id * @property {string} incomingShape source 实体的 id
* @property {string} outgoingShape target 实体的 id * @property {string} outgoingShape target 实体的 id

View File

@ -25,6 +25,10 @@ export class NetlistRender {
*/ */
this.elkGraph = { this.elkGraph = {
id: 'root', id: 'root',
layoutOptions: {
'elk.algorithm': 'layered',
'layered.nodePlacement.strategy': 'SIDE_BASED'
},
children: [], children: [],
edges: [] edges: []
}; };
@ -67,10 +71,7 @@ export class NetlistRender {
* @returns {Promise<ElkNode>} * @returns {Promise<ElkNode>}
*/ */
async createLayout() { async createLayout() {
const elk = new ELK({ const elk = new ELK();
// 分层算法,使得生成的边和坐标轴平行
algorithm: 'layered'
});
const graph = this.elkGraph; const graph = this.elkGraph;
const layoutGraph = await elk.layout(graph); const layoutGraph = await elk.layout(graph);
console.log(layoutGraph); console.log(layoutGraph);
@ -93,8 +94,8 @@ export class NetlistRender {
// 遍历计算布局进行创建 // 遍历计算布局进行创建
const svg = d3.select(container).append('svg') const svg = d3.select(container).append('svg')
.attr('width', this.renderWidth) .attr('width', virtualWidth)
.attr('height', this.renderHeight); .attr('height', virtualHeight);
await this.renderEntity(svg, computedLayout, ratio); await this.renderEntity(svg, computedLayout, ratio);
await this.renderLine(svg, computedLayout, ratio); await this.renderLine(svg, computedLayout, ratio);
@ -116,13 +117,16 @@ export class NetlistRender {
// 生成用于绘制的 d3 数据结构 // 生成用于绘制的 d3 数据结构
const squares = []; const squares = [];
for (const node of computedLayout.children) { for (const node of computedLayout.children) {
squares.push({ squares.push({
x: node.x, x: node.x,
y: node.y, y: node.y,
width: node.width, width: node.width,
height: node.height, height: node.height,
fill: 'green' fill: '#2D323B',
text: node.renderName,
rx: 3,
ry: 3
}); });
// 如果存在 port绘制 port // 如果存在 port绘制 port
@ -132,7 +136,10 @@ export class NetlistRender {
y: cellPort.y + node.y, y: cellPort.y + node.y,
width: cellPort.width, width: cellPort.width,
height: cellPort.height, height: cellPort.height,
fill: 'blue' fill: '#CB81DA',
text: '',
rx: 0,
ry: 0
}); });
} }
} }
@ -146,8 +153,23 @@ export class NetlistRender {
.attr('width', data => data.width) .attr('width', data => data.width)
.attr('height', data => data.height) .attr('height', data => data.height)
.attr('fill', d => d.fill) .attr('fill', d => d.fill)
.attr('stroke', 'white') .attr('stroke', '#CB81DA')
.attr('stroke-width', 2); .attr('stroke-width', 2)
.attr('rx', d => d.rx)
.attr('ry', d => d.ry);
svg.selectAll('text')
.data(squares)
.enter()
.append('text')
.attr('x', data => data.x + data.width / 2) // 文本的 x 坐标(居中)
.attr('y', data => data.y + data.height / 2) // 文本的 y 坐标(居中)
.attr('dominant-baseline', 'middle') // 文本垂直居中
.attr('text-anchor', 'middle') // 文本水平居中
.attr('fill', 'white') // 文本颜色
.attr('font-size', '12px')
.text(data => data.text); // 设置文本内容
} }
/** /**
@ -160,14 +182,23 @@ export class NetlistRender {
const lines = []; const lines = [];
for (const edge of computedLayout.edges) { for (const edge of computedLayout.edges) {
for (const section of edge.sections || []) { for (const section of edge.sections || []) {
lines.push({ const points = [];
x1: section.startPoint.x, points.push(section.startPoint);
y1: section.startPoint.y, for (const point of section.bendPoints || []) {
x2: section.endPoint.x, points.push(point);
y2: section.endPoint.y, }
strokeWidth: 2, points.push(section.endPoint);
color: 'white'
}); for (let i = 0; i < points.length - 1; ++ i) {
lines.push({
x1: points[i].x,
y1: points[i].y,
x2: points[i + 1].x,
y2: points[i + 1].y,
strokeWidth: 2,
color: 'var(--foreground)'
});
}
} }
} }
@ -180,6 +211,6 @@ export class NetlistRender {
.attr('x2', data => data.x2) .attr('x2', data => data.x2)
.attr('y2', data => data.y2) .attr('y2', data => data.y2)
.attr('stroke-width', data => data.strokeWidth) .attr('stroke-width', data => data.strokeWidth)
.attr('stroke', data => data.color) .attr('stroke', data => data.color);
} }
} }

View File

@ -6,7 +6,7 @@ import { CELL_LIBS, ModuleTree } from "./yosys";
export const LAYOUT_CONSTANT = { export const LAYOUT_CONSTANT = {
PORT_WIDTH: 50, PORT_WIDTH: 50,
PORT_HEIGHT: 50, PORT_HEIGHT: 20,
INSTANTIATION_WIDTH: 50, INSTANTIATION_WIDTH: 50,
INSTANTIATION_HEIGHT: 50, INSTANTIATION_HEIGHT: 50,
CONSTANT_WIDTH: 50, CONSTANT_WIDTH: 50,
@ -67,11 +67,19 @@ export class Module {
// 绘制 ports // 绘制 ports
for (const name of this.moduleTree.nameToPort.keys()) { for (const name of this.moduleTree.nameToPort.keys()) {
const port = this.moduleTree.nameToPort.get(name); const port = this.moduleTree.nameToPort.get(name);
const direction = port.direction === 'input' ? 'LEFT': 'RIGHT';
const node = { const node = {
id: port.id, id: port.id,
renderName: name,
renderType: 'port',
type: port.direction,
width: LAYOUT_CONSTANT.PORT_WIDTH, width: LAYOUT_CONSTANT.PORT_WIDTH,
height: LAYOUT_CONSTANT.PORT_HEIGHT, height: LAYOUT_CONSTANT.PORT_HEIGHT,
properties: {
'elk.layered.nodePlacement.side': direction
}
}; };
nodes.push(node); nodes.push(node);
@ -103,6 +111,8 @@ export class Module {
const portSide = connection.direction === 'input' ? ELK_DIRECTION.LEFT: ELK_DIRECTION.RIGHT; const portSide = connection.direction === 'input' ? ELK_DIRECTION.LEFT: ELK_DIRECTION.RIGHT;
ports.push({ ports.push({
id: connection.id, id: connection.id,
renderName: connectionName,
renderType: 'cellPort',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
properties: { properties: {
@ -113,6 +123,8 @@ export class Module {
const node = { const node = {
id: cell.id, id: cell.id,
renderName: cell.type,
renderType: 'cell',
width, width,
height, height,
ports, ports,
@ -156,6 +168,7 @@ export class Module {
// 如果是常数,需要先创建代表常数的节点,常数一定是器件的输入,而非输出 // 如果是常数,需要先创建代表常数的节点,常数一定是器件的输入,而非输出
const node = { const node = {
id, id,
name: wireId,
width: LAYOUT_CONSTANT.CONSTANT_WIDTH, width: LAYOUT_CONSTANT.CONSTANT_WIDTH,
height: LAYOUT_CONSTANT.CONSTANT_HEIGHT, height: LAYOUT_CONSTANT.CONSTANT_HEIGHT,
layoutOptions: { layoutOptions: {