212 lines
6.8 KiB
JavaScript
212 lines
6.8 KiB
JavaScript
/**
|
||
* 将各个节点转换为 elknode 的数据结构
|
||
*/
|
||
|
||
import { DEVICE_MAPPER } from "./yosys";
|
||
|
||
export const LAYOUT_CONSTANT = {
|
||
PORT_WIDTH: 50,
|
||
PORT_HEIGHT: 50,
|
||
INSTANTIATION_WIDTH: 50,
|
||
INSTANTIATION_HEIGHT: 50,
|
||
|
||
};
|
||
|
||
export class ModuleLayout {
|
||
/**
|
||
* @param {string} name
|
||
* @param {YosysNetModule} module
|
||
*/
|
||
constructor(name, module) {
|
||
this.module = module;
|
||
this.name = name;
|
||
|
||
this.initialise();
|
||
}
|
||
|
||
/**
|
||
* @description 初始化,构建信息更加丰富的 RawYosysNet
|
||
*/
|
||
initialise() {
|
||
// 构建模块树
|
||
|
||
|
||
// 构建 wireId 到模块树节点的映射表
|
||
|
||
}
|
||
|
||
/**
|
||
* @description 从 ports 和 netnames 中创建所有变量相关的节点
|
||
* @returns {ElkNode[]}
|
||
*/
|
||
makeNetsElkNodes() {
|
||
const ports = this.module.ports;
|
||
const nets = this.module.netnames;
|
||
|
||
const moduleName = this.name;
|
||
const nodes = [];
|
||
|
||
// 绘制 ports
|
||
for (const [portName, port] of Object.entries(ports)) {
|
||
const portId = ElkNodeId.port(moduleName, portName);
|
||
// TODO: 把当前的 id 绑定到模块树上面
|
||
const node = {
|
||
id: portId,
|
||
width: LAYOUT_CONSTANT.PORT_WIDTH,
|
||
height: LAYOUT_CONSTANT.PORT_HEIGHT,
|
||
layoutOptions: {
|
||
"elk.port.side": port.direction === "input" ? "WEST" : "EAST"
|
||
}
|
||
}
|
||
nodes.push(node);
|
||
}
|
||
|
||
// 非 port 的其他内部变量的节点不需要绘制,因为它们总能被表示为中间变量
|
||
// 它们的操作可以表示为一系列器件的组合或者直连
|
||
|
||
return nodes;
|
||
}
|
||
|
||
/**
|
||
* @description 从 cells 中创建节点
|
||
* @returns {ElkNode[]}
|
||
*/
|
||
makeCellsElkNodes() {
|
||
const cells = this.module.cells;
|
||
const moduleName = this.name;
|
||
const nodes = [];
|
||
for (const [cellName, cell] of Object.entries(cells)) {
|
||
const cellId = ElkNodeId.cell(moduleName, cellName);
|
||
// 查看当前的器件是否为 内置器件
|
||
if (DEVICE_MAPPER[cell.type]) {
|
||
// 是内置器件
|
||
// TODO: 查询内置器件的 size
|
||
const node = {
|
||
id: cellId,
|
||
width: 50,
|
||
height: 50,
|
||
layoutOptions: {
|
||
"elk.port.side": "NORTH"
|
||
}
|
||
};
|
||
nodes.push(node);
|
||
} else {
|
||
// 是例化模块
|
||
const node = {
|
||
id: cellId,
|
||
width: LAYOUT_CONSTANT.INSTANTIATION_WIDTH,
|
||
height: LAYOUT_CONSTANT.INSTANTIATION_HEIGHT,
|
||
layoutOptions: {
|
||
"elk.port.side": "NORTH"
|
||
}
|
||
};
|
||
nodes.push(node);
|
||
}
|
||
}
|
||
|
||
return nodes;
|
||
}
|
||
|
||
/**
|
||
* @description 从 cells.connections 中创建边
|
||
* @returns {[ElkNode[], ElkEdge[]]}
|
||
*/
|
||
makeConnectionElkNodes() {
|
||
const cells = this.module.cells;
|
||
const moduleName = this.name;
|
||
const nodes = [];
|
||
const edges = [];
|
||
|
||
for (const [cellName, cell] of Object.entries(cells)) {
|
||
// 当前器件的 ID
|
||
const cellId = `${moduleName}.${cellName}`;
|
||
|
||
for (const [connectionName, wireIds] of Object.entries(cell.connections)) {
|
||
// 器件当前的端口的 ID,为了区别与外层 module 的端口,起名为 connection
|
||
const connectionId = `${moduleName}.${cellName}.${connectionName}`;
|
||
|
||
// 遍历器件端口的每一个连接点
|
||
// 比如对于端口 input [31:0] data ,它的32个位不一定是完全导向一个变量(虽然我们愿意认为,大部分情况下是这样)
|
||
// 大部分情况下,👇的 wireIds.length 都是 1
|
||
for (let i = 0; i < wireIds.length; ++ i) {
|
||
const bit = wireIds[i];
|
||
// bit 可以是一个 id,也可以是一个常数
|
||
if (typeof bit === 'string') {
|
||
// 常数
|
||
// 如果是常数,需要先创建代表常数的节点,常数一定是器件的输入,而非输出
|
||
const constant = parseInt(bit);
|
||
const constantBitId = ElkNodeId.constantBit(moduleName, cellName, connectionName, i, constant);
|
||
|
||
const node = {
|
||
id: constantBitId,
|
||
width: 50,
|
||
height: 50,
|
||
layoutOptions: {
|
||
"elk.port.side": "WEST"
|
||
}
|
||
};
|
||
|
||
nodes.push(node);
|
||
|
||
// 创建常数到器件的连线
|
||
const edge = {
|
||
id: constantBitId,
|
||
sources: [cellId],
|
||
targets: [node.id]
|
||
};
|
||
|
||
// TODO: 把创建的 edge 关联到模块树上
|
||
edges.push(edge);
|
||
|
||
} else {
|
||
// 如果不是一个常数连接,那么存在两种可能
|
||
// 1. 当前的器件的这个端口和某一个 port 连接
|
||
// 2. 当前的器件的这个端口和另一个器件的一个端口连接
|
||
|
||
|
||
// TODO: 把创建的 edge 关联到模块树上
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return [nodes, edges];
|
||
}
|
||
|
||
|
||
}
|
||
|
||
/**
|
||
* @description 使用 . 把各个部分连接起来
|
||
* @param {string[]} args
|
||
*/
|
||
export function dotConnect(...args) {
|
||
const stringArgs = args.map(arg => arg.toString());
|
||
return stringArgs.join('.');
|
||
}
|
||
|
||
export namespace ElkNodeId {
|
||
export function module(moduleName) {
|
||
return dotConnect(moduleName);
|
||
}
|
||
|
||
export function port(moduleName, portName) {
|
||
return dotConnect(moduleName, portName);
|
||
}
|
||
|
||
export function cell(moduleName, cellName) {
|
||
return dotConnect(moduleName, cellName);
|
||
}
|
||
|
||
export function connection(moduleName, cellName, connectionName) {
|
||
return dotConnect(moduleName, cellName, connectionName);
|
||
}
|
||
|
||
export function constantBit(moduleName, cellName, connectionName, orderId, constant) {
|
||
return dotConnect(moduleName, cellName, connectionName, orderId, 'constant-' + constant);
|
||
}
|
||
|
||
export function commonBit(moduleName, cellName, connectionName, wireId) {
|
||
return dotConnect(moduleName, cellName, connectionName, wireId);
|
||
}
|
||
} |