2025-01-02 04:41:17 +08:00

620 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 将各个节点转换为 elknode 的数据结构
*/
import { globalLookup } from "../global";
import { pinkLog } from "../utils";
import { Cell, dotConnect, ModuleView } from "./yosys";
// 线段的宽度
export const LINE_WIDTH = 2;
// 分组最小 ID
export const MIN_PARTITION_INDEX = 1;
// 分组最大 ID
export const MAX_PARTITION_INDEX = 10000000;
// 每一个例化最多能占据的列数
export const MAX_INSTANCE_COL_NUM = 1000;
// 每一列最多的同类器件数量
export const MAX_SAME_TYPE_INSTANCE_NUM = 5;
// 其他常量
export const LAYOUT_CONSTANT = {
// port
PORT_WIDTH: 50,
PORT_HEIGHT: 25,
// 例化模块这只是最小height 会根据端口数量进行调整)
INSTANTIATION_WIDTH: 80,
INSTANTIATION_HEIGHT: 80,
// port 顶部的空余,一般用来放置标签的
PORT_TOP_MARGIN: 30,
// port 内部的 padding用于绘制
PORT_INNER_PADDING: 20,
// 例化模块上部分的空缺区域
INSTANCE_TITLE_HEIGHT: 10,
INSTANCE_TOP_PADDING: 20,
INSTANCE_LEFT_MARGIN: 10,
INSTANCE_RIGHT_MARGIN: 10,
// 常数
CONSTANT_WIDTH: 50,
CONSTANT_HEIGHT: 30,
// 器件的端口
CELL_PORT_HEIGHT: 1,
CELL_PORT_WIDTH: 1,
CELL_LEFT_MARGIN: 10,
CELL_RIGHT_MARGIN: 10
};
export const ELK_DIRECTION = {
LEFT: 'WEST',
RIGHT: 'EAST',
TOP: 'NORTH',
BOTTOM: 'SOUTH'
}
export function makeEdgeId(fromId, toId) {
return fromId + '-' + toId;
}
export class Module {
/**
* @param {string} name
* @param {YosysNetModule} module
*/
constructor(name, module) {
this.module = module;
this.name = name;
/**
* @description
* @type {ModuleView}
*/
this.view = new ModuleView(name, module);
this.commonPartitionIndex = 10;
this.partitionIndex = 11;
/**
* @description 统计一个例化模块 name 出现的次数,用来分配布局分组索引
* @type {Map<string, number>}
*/
this.instanceNameCounter = new Map();
}
/**
* @description 从 ports 中创建所有变量相关的节点
* @returns {ElkNode[]}
*/
makeNetsElkNodes() {
const nodes = [];
// 绘制 ports
for (const name of this.view.nameToPort.keys()) {
const port = this.view.nameToPort.get(name);
// 根据长度估算 name 的渲染长度
const estimateLength = Math.max(12 * 0.6 * name.length + 10, LAYOUT_CONSTANT.PORT_WIDTH);
if (port.direction === 'input') {
// 为 port 设置连接点
const portConnection = {
id: dotConnect(port.id, '0'),
renderType: 'portConnection',
source: 'port',
// 对于 input 的 port 而言,它的端口只有一个且为输出
direction: 'output',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: estimateLength,
y: LAYOUT_CONSTANT.PORT_HEIGHT / 2
};
const node = {
id: port.id,
name: port.name,
renderName: name,
renderType: 'port',
source: 'port',
// 对于 input 的 port 而言,它的端口只有一个且为输入
direction: 'input',
type: port.direction,
width: estimateLength,
height: LAYOUT_CONSTANT.PORT_HEIGHT,
ports: [portConnection],
layoutOptions: {
'partitioning.partition': MIN_PARTITION_INDEX,
'org.eclipse.elk.portConstraints': 'FIXED_POS'
}
};
nodes.push(node);
} else {
// 为 port 设置连接点
const portConnection = {
id: dotConnect(port.id, '0'),
renderType: 'portConnection',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: 0,
y: LAYOUT_CONSTANT.PORT_HEIGHT / 2
};
const node = {
id: port.id,
name: port.name,
renderName: name,
renderType: 'port',
type: port.direction,
width: estimateLength,
height: LAYOUT_CONSTANT.PORT_HEIGHT,
ports: [portConnection],
layoutOptions: {
'partitioning.partition': MAX_PARTITION_INDEX,
'org.eclipse.elk.portConstraints': 'FIXED_POS'
}
};
nodes.push(node);
}
}
// 非 port 的其他内部变量的节点不需要绘制,因为它们总能被表示为中间变量
// 它们的操作可以表示为一系列器件的组合或者直连
return nodes;
}
/**
* @description 从 cells器件 中创建节点
* @returns {[ElkNode[], ElkEdge[]]}
*/
makeCellsElkNodes() {
const nodes = [];
const edges = [];
const skinManager = globalLookup.skinManager;
for (const name of this.view.nameToCell.keys()) {
const cell = this.view.nameToCell.get(name);
const skin = skinManager.querySkin(cell.type);
if (skin) {
const node = this.makeCellNode(cell, skin);
nodes.push(node);
} else {
const node = this.makeInstanceNode(cell);
nodes.push(node);
}
}
return nodes;
}
/**
* @description 从 cells.connections 中创建边
* @returns {[ElkNode[], ElkEdge[]]}
*/
makeConnectionElkNodes() {
const nodes = [];
// 完成去重
const id2EdgeCount = new Map();
const edges = [];
const tree = this.view;
for (const cellName of tree.nameToCell.keys()) {
const cell = tree.nameToCell.get(cellName);
for (const connectionName of cell.nameToConnection.keys()) {
const connection = cell.nameToConnection.get(connectionName);
// 遍历器件端口的每一个连接点
// 比如对于端口 input [31:0] data 它的32个位不一定是完全导向一个变量虽然我们愿意认为大部分情况下是这样
// 大部分情况下,👇的 wireIds.length 都是 1
for (const wire of connection.wires) {
const wireId = wire.wireId;
const id = wire.id;
if (typeof wireId === 'string') {
// 常数
// 如果是常数,需要先创建代表常数的节点,常数一定是器件的输入,而非输出
// 估算常量的宽度
let renderString = parseInt(wireId, 2);
if (isNaN(renderString)) {
if (wireId.toString().startsWith('x')) {
renderString = `${wireId.toString().length}{1'bx}`;
} else {
renderString = wireId.toString();
}
}
const constantWidth = Math.max(12 * 0.6 * renderString.toString().length + 10, LAYOUT_CONSTANT.CONSTANT_WIDTH);
console.log(constantWidth);
const constantConnection = {
id: dotConnect(id, '0'),
renderType: 'portConnection',
source: 'port',
// 对于 constant 的 port 而言,它的端口只有一个且为输出
direction: 'output',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: constantWidth,
y: LAYOUT_CONSTANT.CONSTANT_HEIGHT / 2
};
const node = {
id,
name: wireId,
renderType: 'constant',
width: constantWidth,
height: LAYOUT_CONSTANT.CONSTANT_HEIGHT,
ports: [constantConnection],
layoutOptions: {
'partitioning.partition': this.commonPartitionIndex,
'org.eclipse.elk.portConstraints': 'FIXED_POS'
}
};
nodes.push(node);
// 创建常数到器件的连线
const edge = {
id: makeEdgeId(cell.id, id),
source: id,
sourcePort: constantConnection.id,
target: cell.id,
targetPort: connection.id
};
edges.push(edge);
} else {
// 如果不是一个常数连接,那么存在两种可能
// 1. 当前的器件的这个端口和某一个 port 连接
// 2. 当前的器件的这个端口和另一个器件的一个端口连接
if (tree.wireIdToPort.has(wireId)) {
// 当前的器件的这个端口和某一个 port 连接
const port = tree.wireIdToPort.get(wireId);
// 器件的端口的方向有比 port 更高的优先级。
// 器件当前的口为 input那么所连接的 port 就必须是 input即便这个 port 是 output
if (connection.direction === 'input') {
const edgeId = makeEdgeId(port.id, connection.id);
if (!id2EdgeCount.has(edgeId)) {
id2EdgeCount.set(edgeId, 0);
const edge = {
// id 遵循 sourcePort-targetPort
id: edgeId,
source: port.id,
sourcePort: dotConnect(port.id, '0'),
target: cell.id,
targetPort: connection.id
};
edges.push(edge);
}
const counter = id2EdgeCount.get(edgeId);
id2EdgeCount.set(edgeId, counter + 1);
} else {
const edgeId = makeEdgeId(connection.id, port.id);
if (!id2EdgeCount.has(edgeId)) {
id2EdgeCount.set(edgeId, 0);
const edge = {
id: edgeId,
source: cell.id,
sourcePort: connection.id,
target: port.id,
targetPort: dotConnect(port.id, '0')
};
edges.push(edge);
}
const counter = id2EdgeCount.get(edgeId);
id2EdgeCount.set(edgeId, counter + 1);
}
} else if (tree.wireIdToConnection.has(wireId)) {
// 当前的器件的这个端口和另一个器件的一个端口连接
const conn = tree.wireIdToConnection.get(wireId);
// 防止查询到自己
if (conn.id === connection.id && conn.cell.id === cell.id) {
continue;
}
if (conn.direction === 'input') {
const edgeId = makeEdgeId(conn.id, connection.id);
if (!id2EdgeCount.has(edgeId)) {
id2EdgeCount.set(edgeId, 0);
const edge = {
// id 遵循 sourcePort-targetPort
id: edgeId,
source: cell.id,
sourcePort: connection.id,
target: conn.cell.id,
targetPort: conn.id
};
edges.push(edge);
}
const counter = id2EdgeCount.get(edgeId);
id2EdgeCount.set(edgeId, counter + 1);
} else {
const edgeId = makeEdgeId(connection.id, conn.id);
if (!id2EdgeCount.has(edgeId)) {
id2EdgeCount.set(edgeId, 0);
const edge = {
id: edgeId,
source: conn.cell.id,
sourcePort: conn.id,
target: cell.id,
targetPort: connection.id
};
edges.push(edge);
}
const counter = id2EdgeCount.get(edgeId);
id2EdgeCount.set(edgeId, counter + 1);
}
}
}
}
}
}
pinkLog('#edge: ' + edges.length);
this.id2EdgeCount = id2EdgeCount;
return [nodes, edges];
}
/**
* @description 制作器件对应的 node
* @param {Cell} cell
* @param {import("../skin").SkinResource} skin
*/
makeCellNode(cell, skin) {
// 内置器件
// 创建器件节点的 port, port 和 connection 一一对应
const meta = skin.meta;
const height = meta.height;
const width = meta.width + LAYOUT_CONSTANT.CELL_LEFT_MARGIN + LAYOUT_CONSTANT.CELL_RIGHT_MARGIN;
const ports = [];
// 统计分配到左右两侧的 port
const leftSideConnections = [];
const rightSideConnections = [];
const topSideConnections = [];
for (const connection of cell.nameToConnection.values()) {
const yOffset = meta.getPortYOffset(connection.name);
if (yOffset === 0) {
topSideConnections.push(connection);
} else if (connection.direction === 'input') {
leftSideConnections.push(connection);
} else {
rightSideConnections.push(connection);
}
}
// 计算上侧的
for (let i = 0; i < topSideConnections.length; ++ i) {
const connection = topSideConnections[i];
const xOffset = meta.getPortXOffset(connection.name);
console.log(xOffset);
ports.push({
id: connection.id,
renderName: connection.name,
renderType: 'cellPort',
direction: 'input',
source: 'cell',
isVertical: true,
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: xOffset,
y: 0
});
}
// 计算左侧的
for (let i = 0; i < leftSideConnections.length; ++ i) {
const connection = leftSideConnections[i];
const yOffset = meta.getPortYOffset(connection.name);
ports.push({
id: connection.id,
renderName: connection.name,
renderType: 'cellPort',
direction: 'input',
source: 'cell',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: 0,
y: yOffset
});
}
// 计算右侧的
for (let i = 0; i < rightSideConnections.length; ++ i) {
const connection = rightSideConnections[i];
const yOffset = meta.getPortYOffset(connection.name);
ports.push({
id: connection.id,
renderName: connection.name,
renderType: 'cellPort',
direction: 'output',
source: 'cell',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: width,
y: yOffset
});
}
const node = {
id: cell.id,
name: cell.name,
renderName: cell.type,
renderType: 'cell',
width,
height,
ports,
layoutOptions: {
'org.eclipse.elk.portConstraints': 'FIXED_POS',
'partitioning.partition': this.commonPartitionIndex
}
};
return node;
}
/**
* @description 制作例化模块对应的 node
* @param {Cell} cell
*/
makeInstanceNode(cell) {
// 统计分配到左右两侧的 port
const leftSideConnections = [];
const rightSideConnections = [];
for (const conn of cell.nameToConnection.values()) {
if (conn.direction === 'input') {
leftSideConnections.push(conn);
} else {
rightSideConnections.push(conn);
}
}
// 根据一侧的 port 数量来决定 height
const maxPortNum = Math.max(leftSideConnections.length, rightSideConnections.length);
const instanceHeight = Math.max(
// 这是例化模块高度的最小值
LAYOUT_CONSTANT.INSTANTIATION_HEIGHT,
// 每一个 connection 占据的空间为:【上方空余区域(用来防止文本的)】 + 【本身的高度】
(LAYOUT_CONSTANT.PORT_TOP_MARGIN + LAYOUT_CONSTANT.CELL_PORT_HEIGHT) * (maxPortNum + 1)
);
// 宽度等于预设宽度 + 两倍的 inner padding + left margin + right margin
const instanceWidth = LAYOUT_CONSTANT.INSTANTIATION_WIDTH +
2 * LAYOUT_CONSTANT.PORT_INNER_PADDING +
LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN +
LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN;
// 创建器件节点的 port, port 和 connection 一一对应
const ports = [];
let inputCount = 0;
let outputCount = 0;
for (const connectionName of cell.nameToConnection.keys()) {
const connection = cell.nameToConnection.get(connectionName);
const portSide = connection.direction === 'input' ? ELK_DIRECTION.LEFT: ELK_DIRECTION.RIGHT;
if (connection.direction === 'input') {
inputCount ++;
const offsetY = inputCount * LAYOUT_CONSTANT.PORT_TOP_MARGIN +
(inputCount - 1) * LAYOUT_CONSTANT.CELL_PORT_HEIGHT +
LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING;
ports.push({
id: connection.id,
renderName: connectionName,
renderType: 'cellPort',
direction: 'input',
source: 'instance',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: 0,
y: offsetY
});
} else {
outputCount ++;
const offsetY = outputCount * LAYOUT_CONSTANT.PORT_TOP_MARGIN +
(outputCount - 1) * LAYOUT_CONSTANT.CELL_PORT_HEIGHT +
LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING;
ports.push({
id: connection.id,
renderName: connectionName,
renderType: 'cellPort',
direction: 'output',
source: 'instance',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: instanceWidth,
y: offsetY
});
}
}
const node = {
id: cell.id,
name: cell.name,
parentName: this.name,
renderName: cell.type,
renderType: 'instance',
width: instanceWidth,
height: instanceHeight + LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING,
ports,
layoutOptions: {
// 强制固定 port 的方向
'org.eclipse.elk.portConstraints': 'FIXED_POS',
// TODO: 同名例化模块对齐
'partitioning.partition': this.commonPartitionIndex
}
};
return node;
}
}
/**
*
* @param {import('../jsdoc').ElkPort} port
* @returns {ConnectionMarginParameter}
*/
export function getMarginParamter(port) {
if (!port) {
// 如果是例化模块,那么当前的 port 就是空
// 因为例化模块内部的变量一定和外部直连,所以例化模块内部是没有 port 的
return {
leftMargin: 0,
rightMargin: 0
}
}
switch (port.source) {
case 'instance':
return {
leftMargin: LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN,
rightMargin: LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN
}
case 'cell':
return {
leftMargin: LAYOUT_CONSTANT.CELL_LEFT_MARGIN,
rightMargin: LAYOUT_CONSTANT.CELL_RIGHT_MARGIN
}
default:
return {
leftMargin: 0,
rightMargin: 0
}
}
}
/**
* @typedef ConnectionMarginParameter
* @property {number} leftMargin
* @property {number} rightMargin
*/