This commit is contained in:
锦恢 2024-12-22 01:26:31 +08:00
parent 3620b3c9f3
commit fb3d481397
7 changed files with 111 additions and 71 deletions

8
package-lock.json generated
View File

@ -14,7 +14,8 @@
"elkjs": "^0.9.3",
"mitt": "^3.0.1",
"vue": "^3.2.13",
"vue-i18n": "10.0.5"
"vue-i18n": "10.0.5",
"web-worker": "^1.3.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",
@ -11345,6 +11346,11 @@
"defaults": "^1.0.3"
}
},
"node_modules/web-worker": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz",
"integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA=="
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",

View File

@ -14,7 +14,8 @@
"elkjs": "^0.9.3",
"mitt": "^3.0.1",
"vue": "^3.2.13",
"vue-i18n": "10.0.5"
"vue-i18n": "10.0.5",
"web-worker": "^1.3.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",

View File

@ -8,6 +8,7 @@ import { useI18n } from 'vue-i18n';
import RightNav from '@/components/right-nav.vue';
import { onMounted } from 'vue';
import { setDefaultCss } from './hook/css';
import { NetlistRender } from './hook/render';
const { t } = useI18n();
@ -18,7 +19,8 @@ onMounted(async () => {
// netlist json
const [ netJson ] = await window.readNetFile();
console.log(netJson);
const render = new NetlistRender(netJson);
await render.createLayout();
});

View File

@ -68,10 +68,15 @@
/**
* @typedef ModuleAttribute
* @property {string} top - 表示该模块是否为顶层模块1 表示是0 表示否
* @property {string} src - 模块定义的源文件和行号
* @property {string} [dynports] - 动态端口标志1 表示是0 表示否
* @property {string} [hdlname] - HDL 名称
* @property {string} [top] 表示该模块是否为顶层模块1 表示是0 表示否
* @property {string} src 模块定义的源文件和行号
* @property {string} [cells_not_processed] 代表当前模块是否没有被处理
*
* 比如对于一个 main.v 而言它内部定义了两个模块 A B如果只综合了 A那么 B 也会出现在 yosys json
* 但是 B 的会额外出现 `cells_not_processed` 这个字段且值为 "00000000000000000000000000000001"代表没有处理
*
* @property {string} [dynports] 动态端口标志1 表示是0 表示否
* @property {string} [hdlname] HDL 名称
*/
/**
@ -142,7 +147,11 @@
/**
* @typedef ElkNode
* @property {string} id - 节点的唯一标识符
* @property {string} id 节点的唯一标识符
* @property {ElkNode[]} children 当前节点的内部
* @property {ElkPort[]} ports 当前节点的端口
* @property {ElkEdge[]} edges 当前节点的连线
* @property {string} type 节点的类型
* @property {number} [x] - 节点的 X 坐标可选由布局算法生成
* @property {number} [y] - 节点的 Y 坐标可选由布局算法生成
* @property {number} [width] - 节点的宽度可选
@ -175,5 +184,5 @@
* @property {string} [elk.direction] - 布局方向可选 "RIGHT", "DOWN"
* @property {number} [elk.spacing.nodeNode] - 节点之间的间距可选
* @property {number} [elk.spacing.edgeNode] - 边与节点之间的间距可选
* @property {string} [elk.port.side] - 端口的位置可选 "NORTH", "SOUTH", "EAST", "WEST"
* @property {'NORTH' | 'SOUTH' | 'EAST' | 'WEST'} [elk.port.side] - 端口的位置可选 "NORTH", "SOUTH", "EAST", "WEST"
*/

View File

@ -1,7 +1,10 @@
import * as d3 from 'd3';
import { ModuleLayout } from './layout';
import ELK from 'elkjs';
class NetlistRender {
import { Module } from './layout';
export class NetlistRender {
/**
*
* @param {YosysRawNet} rawNet
@ -21,6 +24,11 @@ class NetlistRender {
edges: []
};
/**
* @type {Map<string, Module>}
*/
this.nameToModule = new Map();
this.loadYosysRawNet(rawNet);
}
@ -29,11 +37,36 @@ class NetlistRender {
* @param {YosysRawNet} rawNet
*/
loadYosysRawNet(rawNet) {
// 转换为 elkjs 格式的 graph
for (const [moduleName, module] of Object.entries(rawNet.modules)) {
for (const [moduleName, rawModule] of Object.entries(rawNet.modules)) {
const top = parseInt(rawModule.attributes.top);
// 一开始只渲染 top 模块
if (top) {
const module = new Module(moduleName, rawModule);
const portNodes = layout.makeNetsElkNodes();
const cellNodes = layout.makeCellsElkNodes();
const [constantNodes, connectionEdges] = layout.makeConnectionElkNodes();
this.elkGraph.children.push(...portNodes);
this.elkGraph.children.push(...cellNodes);
this.elkGraph.children.push(...constantNodes);
this.elkGraph.edges.push(...connectionEdges);
this.nameToModule.set(moduleName, module);
}
}
}
/**
* @description 根据信息创建布局对象
* @returns {Promise<ElkNode>}
*/
async createLayout() {
const elk = new ELK();
const graph = this.elkGraph;
const layoutGraph = await elk.layout(graph, {});
console.log(layoutGraph);
return layoutGraph
}
}

View File

@ -10,9 +10,19 @@ export const LAYOUT_CONSTANT = {
INSTANTIATION_WIDTH: 50,
INSTANTIATION_HEIGHT: 50,
CONSTANT_WIDTH: 50,
CONSTANT_HEIGHT: 50
CONSTANT_HEIGHT: 50,
CELL_PORT_HEIGHT: 10,
CELL_PORT_WIDTH: 5
};
export const ELK_DIRECTION = {
LEFT: 'WEST',
RIGHT: 'EAST',
TOP: 'NORTH',
BOTTOM: 'SOUTH'
}
function getCellSize(cellType) {
if (CELL_LIBS[cellType]) {
return {
@ -31,7 +41,7 @@ function makeEdgeId(fromId, toId) {
return 'edge-' + fromId + '-' + toId;
}
export class ModuleLayout {
export class Module {
/**
* @param {string} name
* @param {YosysNetModule} module
@ -44,7 +54,7 @@ export class ModuleLayout {
* @description
* @type {ModuleTree}
*/
this.moduleTree = new ModuleTree(name, moduleTree);
this.moduleTree = new ModuleTree(name, module);
}
/**
@ -57,15 +67,11 @@ export class ModuleLayout {
// 绘制 ports
for (const name of this.moduleTree.nameToPort.keys()) {
const port = this.moduleTree.nameToPort.get(name);
const side = port.direction === 'input' ? 'WEST' : 'EAST';
const node = {
id: port.id,
width: LAYOUT_CONSTANT.PORT_WIDTH,
height: LAYOUT_CONSTANT.PORT_HEIGHT,
layoutOptions: {
'elk.port.side': side
}
};
nodes.push(node);
@ -78,23 +84,38 @@ export class ModuleLayout {
}
/**
* @description cells 中创建节点
* @returns {ElkNode[]}
* @description cells器件 中创建节点
* @returns {[ElkNode[], ElkEdge[]]}
*/
makeCellsElkNodes() {
const nodes = [];
const edges = [];
for (const name of this.moduleTree.nameToCell.keys()) {
const cell = this.moduleTree.nameToCell.get(name);
const { height, width } = getCellSize(cell.type);
const side = 'NORTH';
// 创建器件节点的 port, port 和 connection 一一对应
const ports = [];
for (const connectionName of cell.nameToConnection) {
const connection = cell.nameToConnection.get(connectionName);
const portSide = connection.direction === 'input' ? ELK_DIRECTION.LEFT: ELK_DIRECTION.RIGHT;
ports.push({
id: connection.id,
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
properties: {
'port.side': portSide,
'allowNonFlowPortsToSwitchSides': true
}
})
}
const node = {
id: cell.id,
width, height,
layoutOptions: {
'elk.port.side': side
}
width,
height,
ports
}
nodes.push(node);
@ -120,9 +141,6 @@ export class ModuleLayout {
for (const connectionName of cell.nameToConnection.keys()) {
const connection = cell.nameToConnection.get(connectionName);
// 器件当前的端口的 ID为了区别与外层 module 的端口,起名为 connection
const connectionId = connection.id;
// 遍历器件端口的每一个连接点
// 比如对于端口 input [31:0] data 它的32个位不一定是完全导向一个变量虽然我们愿意认为大部分情况下是这样
// 大部分情况下,👇的 wireIds.length 都是 1
@ -185,38 +203,3 @@ export class ModuleLayout {
}
/**
* @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);
}
}

View File

@ -60,8 +60,8 @@ export class ModuleTree {
*/
this._wireIdToConnection = new Map();
for (const [portName, port] of Object.entries(rawModule.ports)) {
const port = new Port(this, portName, port);
for (const [portName, rawPort] of Object.entries(rawModule.ports)) {
const port = new Port(this, portName, rawPort);
this._nameToPort.set(portName, port);
// 构建映射
for (const id of port.bits) {
@ -71,8 +71,8 @@ export class ModuleTree {
}
}
for (const [cellName, cell] of Object.entries(rawModule.cells)) {
const cell = new Cell(this, cellName, cell);
for (const [cellName, rawCell] of Object.entries(rawModule.cells)) {
const cell = new Cell(this, cellName, rawCell);
this._nameToCell.set(cellName, cell);
// 构建映射
for (const connection of cell.nameToConnection.values()) {
@ -181,6 +181,7 @@ export class Cell {
}
}
export class Connection {
/**
* @description connection 的抽象一个 connection 代表一个器件的一个端口是如何与外界连接的
@ -193,6 +194,11 @@ export class Connection {
this.name = name;
this.wireIds = wireIds;
/**
* @type {PortDirection}
*/
this.direction = cell.rawCell.port_directions[name];
/**
* @type {Wire[]}
*/
@ -200,7 +206,7 @@ export class Connection {
for (let i = 0; i < wireIds.length; ++ i) {
const wire = new Wire(this, i, wireIds[i]);
this.wireIds.push(wire);
this.wires.push(wire);
}
}