/** * 将各个节点转换为 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); } }