-
-
-
Mirror the unknown
+
+
{{ t('module') }}
+
+
+
+
\ No newline at end of file
diff --git a/src/components/treeview/tree.js b/src/components/treeview/tree.js
new file mode 100644
index 0000000..4051717
--- /dev/null
+++ b/src/components/treeview/tree.js
@@ -0,0 +1,13 @@
+import { ModuleView } from '@/hook/render/yosys';
+import { reactive } from 'vue';
+
+export const treeviewData = reactive({
+ /**
+ * @description 当前的顶层模块数量,内部数据结构是递归的
+ * 大部分情况下,它应该是只有一个元素
+ *
+ * @type {ModuleView[]}
+ */
+ modules: []
+});
+
diff --git a/src/hook/css.js b/src/hook/css.js
index 95a5c14..5a97701 100644
--- a/src/hook/css.js
+++ b/src/hook/css.js
@@ -8,7 +8,7 @@ export function setDefaultCss() {
document.body.style.setProperty('--el-color-primary-light-3', 'var(--main-color)');
document.body.style.setProperty('--el-text-color-secondary', 'var(--foreground)');
document.body.style.setProperty('--el-text-color-regular', 'var(--foreground)');
- document.body.style.setProperty('--el-border-color', 'var(--vscode-focusBorder)');
+ document.body.style.setProperty('--el-border-color', 'var(--main-color)');
document.body.style.setProperty('--el-fill-color-blank', 'var(--sidebar)');
document.body.style.setProperty('--el-fill-color-light', 'var(--vscode-button-hoverBackground)');
document.body.style.setProperty('--el-switch-on-color', 'var(--main-color)');
@@ -16,14 +16,15 @@ export function setDefaultCss() {
document.body.style.setProperty('--el-border-color-light', 'var(--sidebar)');
document.body.style.setProperty('--el-border-color-lighter', 'var(--sidebar)');
document.body.style.setProperty('--el-bg-color-overlay', 'var(--sidebar)');
- document.body.style.setProperty('--el-color-info-light-9', 'var(--vscode-focusBorder)');
+ document.body.style.setProperty('--el-color-info-light-9', 'var(--main-color)');
document.body.style.setProperty('--el-color-info', 'var(--foreground)');
- document.body.style.setProperty('--el-color-info-light-8', 'var(--vscode-focusBorder)');
+ document.body.style.setProperty('--el-color-info-light-8', 'var(--main-color)');
+ document.body.style.setProperty('--el-fill-color-light', 'var(--sidebar-item-selected)');
// document.body.style.setProperty('--el-color-white', 'var(--background)');
// 设置全局宏
- document.body.style.setProperty('--vcd-render-padding', '30px');
+ document.body.style.setProperty('--netlist-render-padding', '30px');
document.body.style.setProperty('--sidebar-width', '330px');
document.body.style.setProperty('--toolbar-height', '60px');
diff --git a/src/hook/global.js b/src/hook/global.js
index f842c60..89d88a5 100644
--- a/src/hook/global.js
+++ b/src/hook/global.js
@@ -25,7 +25,18 @@ export const globalLookup = {
/**
* @type {Avoid}
*/
- Avoid: undefined
+ Avoid: undefined,
+
+ /**
+ * @description 当前仿真结果的顶层模块的名字
+ * @type {string}
+ */
+ topModuleName: '',
+
+ /**
+ * @description 当前选择的实体,可以是 wire,也可以是 cell
+ */
+ currentSelectEntity: undefined
};
function loadSetting() {
diff --git a/src/hook/jsdoc.js b/src/hook/jsdoc.js
index 4f4232d..3c71807 100644
--- a/src/hook/jsdoc.js
+++ b/src/hook/jsdoc.js
@@ -5,6 +5,8 @@
* @property {Record
} [models] 模型定义的映射(仅在使用 `-aig` 选项时存在)。
*/
+import { Module } from "./render/layout";
+
/**
* @typedef {number | string} WireId 信号ID
*/
@@ -252,4 +254,4 @@
/**
* @typedef DragConnection
* @property {d3.Selection} selection
- */
\ No newline at end of file
+ */
diff --git a/src/hook/render/index.js b/src/hook/render/index.js
index 14c0839..143f3e2 100644
--- a/src/hook/render/index.js
+++ b/src/hook/render/index.js
@@ -9,7 +9,8 @@ import { InstantiationRender } from './instantiation';
import { CellRender } from './cell';
import { ConnectionRender } from './connection';
import { WireRender } from './wire';
-import { pinkLog } from '../utils';
+import { pinkLog, redLog } from '../utils';
+import { treeviewData } from '@/components/treeview/tree';
export class NetlistRender {
/**
@@ -82,15 +83,20 @@ export class NetlistRender {
* @type {YosysRawNet}
*/
this.rawNet = rawNet;
+ this.topModuleName = globalLookup.topModuleName;
+ pinkLog(`当前模块 ${this.topModuleName}`);
+
+ /**
+ * @type {Module}
+ */
+ let topModule = undefined;
// 构造模块树
for (const [moduleName, rawModule] of Object.entries(rawNet.modules)) {
- const top = parseInt(rawModule.attributes.top);
- // 一开始只渲染 top 模块,或者告诉我当前的 top 是什么
- if (top) {
- const module = new Module(moduleName, rawModule);
-
+ const module = new Module(moduleName, rawModule);
+ if (moduleName === this.topModuleName) {
// 构造符合 elk 格式的节点数据
+ topModule = module;
const portNodes = module.makeNetsElkNodes();
const cellNodes = module.makeCellsElkNodes();
const [constantNodes, connectionEdges] = module.makeConnectionElkNodes();
@@ -104,6 +110,42 @@ export class NetlistRender {
this.nameToModule.set(moduleName, module);
}
}
+
+ if (topModule === undefined) {
+ redLog(`当前 netlist 中没有找到名为 ${this.topModuleName} 的顶层模块! Digital IDE 无法构建它的 treeview 视图!`);
+ return;
+ }
+
+ // 根据 topModule + this.nameToModule 构建出用于渲染右侧列表的数据结构
+ const nameToModule = this.nameToModule;
+
+ // 记录当前需要不断递归解析成渲染视图的 module 的 name
+ // 从将每一个 module 的 tree 中关于 Cell 的部分完成指向(判断这个 Cell 是不是 例化模块,并找到它们对应的 Module 对象)
+ const moduleStack = [topModule.name];
+
+ while (moduleStack.length > 0) {
+ const moduleName = moduleStack.pop();
+ const currentModule = nameToModule.get(moduleName);
+ // 需要展示的数据:port, cell, (other netnames)
+ const view = currentModule.view;
+ for (const cellName of view.nameToCell.keys()) {
+ const cell = view.nameToCell.get(cellName);
+ const solveInstanceTypes = new Set();
+ if (nameToModule.has(cell.type)) {
+ cell.isInstantiation = true;
+ const instModule = nameToModule.get(cell.type);
+ cell.belongModuleView = instModule.view;
+ solveInstanceTypes.add(cell.type);
+ }
+ for (const instanceBelongModuleName of solveInstanceTypes) {
+ moduleStack.push(instanceBelongModuleName);
+ }
+ }
+ }
+
+ // 将默认的顶层模块的 view 加入响应数组中,响应地去渲染右侧的 view
+ const rootView = topModule.view;
+ treeviewData.modules.push(rootView);
}
/**
diff --git a/src/hook/render/instantiation.js b/src/hook/render/instantiation.js
index 00ac271..ede5c47 100644
--- a/src/hook/render/instantiation.js
+++ b/src/hook/render/instantiation.js
@@ -31,6 +31,7 @@ export class InstantiationRender {
this.data.push({
x: node.x,
y: node.y,
+ name: node.name,
width: node.width,
height: node.height,
fill: 'var(--main-dark-color)',
@@ -39,4 +40,98 @@ export class InstantiationRender {
ry: 3
});
}
+
+ render() {
+ const data = this.data;
+ const rootRender = this.rootRender;
+ const id2manager = this.id2manager;
+ const _this = this;
+
+ let instantiationSelections = this.parentSelection.selectAll('g.instance')
+ .data(data)
+ .enter()
+ .append('g')
+ .attr('class', 'instance')
+ .attr("transform", d => `translate(${d.x}, ${d.y})`);
+
+ let instances = instantiationSelections.append('rect')
+ .attr('width', data => data.width)
+ .attr('height', data => data.height)
+ .attr('fill', d => d.fill);
+
+ let texts = instantiationSelections.append('text')
+ .attr('x', data => data.width / 2) // 文本的 x 坐标(居中)
+ .attr('y', data => data.height / 2) // 文本的 y 坐标(居中)
+ .attr('dominant-baseline', 'middle') // 文本垂直居中
+ .attr('text-anchor', 'middle') // 文本水平居中
+ .attr('fill', 'var(--foreground)') // 文本颜色
+ .attr('font-size', '0')
+ .transition()
+ .duration(1000)
+ .attr('font-size', '15px')
+ .attr('class', 'port-caption')
+ .text(data => data.name); // 设置文本内容
+
+
+ if (globalSetting.renderAnimation) {
+ instances.transition()
+ .duration(1000)
+ .attr('stroke', 'var(--main-color)')
+ .attr('stroke-width', 2)
+ .attr('rx', d => d.rx)
+ .attr('ry', d => d.ry);
+ } else {
+ instances.attr('stroke', 'var(--main-color)')
+ .attr('stroke-width', 2)
+ .attr('rx', d => d.rx)
+ .attr('ry', d => d.ry);
+ }
+
+ instantiationSelections
+ .attr('class', 'grab')
+ .each(function (data) {
+ const portSelection = d3.select(this);
+ const manager = _this.createDataManager(portSelection, data);
+
+ // TODO: 实现拖拽
+ // registerDragEvent(manager, rootRender);
+ });
+
+ this.selections = instantiationSelections;
+ return instantiationSelections;
+ }
+
+ /**
+ *
+ * @param {d3.Selection} selection
+ * @param {BasicD3DataItem} data
+ * @returns {BasicD3ManagmentItem}
+ */
+ createDataManager(selection, data) {
+ const id2manager = this.id2manager;
+ // 创建拖拽上下文
+ const dragContext = {
+ neighbors: [],
+ elkGraph: {
+ // elk 是无状态的,id 取什么名字都行
+ id: 'root',
+ children: [],
+ edges: [],
+ layoutOptions: {}
+ }
+ }
+ const managerItem = {
+ data,
+ selection,
+ type: 'cell',
+ dragContext
+ };
+
+ if (!id2manager.has(data.id)) {
+ id2manager.set(data.id, []);
+ }
+
+ id2manager.get(data.id).push(managerItem);
+ return managerItem;
+ }
}
\ No newline at end of file
diff --git a/src/hook/render/layout.js b/src/hook/render/layout.js
index 4319690..203aa7b 100644
--- a/src/hook/render/layout.js
+++ b/src/hook/render/layout.js
@@ -3,7 +3,7 @@
*/
import { globalLookup } from "../global";
-import { Cell, ModuleTree } from "./yosys";
+import { Cell, ModuleView } from "./yosys";
export const LINE_WIDTH = 2;
@@ -48,9 +48,9 @@ export class Module {
/**
* @description
- * @type {ModuleTree}
+ * @type {ModuleView}
*/
- this.moduleTree = new ModuleTree(name, module);
+ this.view = new ModuleView(name, module);
}
/**
@@ -61,12 +61,13 @@ export class Module {
const nodes = [];
// 绘制 ports
- for (const name of this.moduleTree.nameToPort.keys()) {
- const port = this.moduleTree.nameToPort.get(name);
+ for (const name of this.view.nameToPort.keys()) {
+ const port = this.view.nameToPort.get(name);
if (port.direction === 'input') {
const node = {
id: port.id,
+ name: port.name,
renderName: name,
renderType: 'port',
type: port.direction,
@@ -81,6 +82,7 @@ export class Module {
} else {
const node = {
id: port.id,
+ name: port.name,
renderName: name,
renderType: 'port',
type: port.direction,
@@ -111,8 +113,8 @@ export class Module {
const edges = [];
const skinManager = globalLookup.skinManager;
- for (const name of this.moduleTree.nameToCell.keys()) {
- const cell = this.moduleTree.nameToCell.get(name);
+ for (const name of this.view.nameToCell.keys()) {
+ const cell = this.view.nameToCell.get(name);
const skin = skinManager.querySkin(cell.type);
if (skin) {
@@ -173,6 +175,7 @@ export class Module {
const node = {
id: cell.id,
+ name: cell.name,
renderName: cell.type,
renderType: 'cell',
width,
@@ -207,6 +210,7 @@ export class Module {
const node = {
id: cell.id,
+ name: cell.name,
renderName: cell.type,
renderType: 'cell',
width,
@@ -234,7 +238,7 @@ export class Module {
const nodes = [];
const edges = [];
- const tree = this.moduleTree
+ const tree = this.view
for (const cellName of tree.nameToCell.keys()) {
const cell = tree.nameToCell.get(cellName);
diff --git a/src/hook/render/yosys.js b/src/hook/render/yosys.js
index 5e672f4..2300662 100644
--- a/src/hook/render/yosys.js
+++ b/src/hook/render/yosys.js
@@ -1,8 +1,8 @@
/**
* @description 模块树对象,直接作为 treeview 的渲染视图加入运算
- * 相比于 YosysNetModule, ModuleTree 只关心和渲染有关的那部分变量,且不可被序列化
+ * 相比于 YosysNetModule, ModuleView 只关心和渲染有关的那部分变量,且不可被序列化
*/
-export class ModuleTree {
+export class ModuleView {
/**
* @param {string} name
* @param {YosysNetModule} rawModule
@@ -96,12 +96,12 @@ export class ModuleTree {
export class Port {
/**
* @description port 的抽象
- * @param {ModuleTree} moduleTree
+ * @param {ModuleView} view
* @param {string} name
* @param {YosysPort} rawPort
*/
- constructor(moduleTree, name, rawPort) {
- this.moduleTree = moduleTree;
+ constructor(view, name, rawPort) {
+ this.view = view;
this.name = name;
this.rawPort = rawPort;
}
@@ -115,22 +115,33 @@ export class Port {
}
get id() {
- return dotConnect(this.moduleTree.id, this.name);
+ return dotConnect(this.view.id, this.name);
}
}
export class Cell {
/**
* @description 器件的抽象
- * @param {ModuleTree} moduleTree
+ * @param {ModuleView} view
* @param {string} name
* @param {YosysCell} rawCell
*/
- constructor(moduleTree, name, rawCell) {
- this.moduleTree = moduleTree;
+ constructor(view, name, rawCell) {
+ this.view = view;
this.name = name;
this.rawCell = rawCell;
+ /**
+ * @description 是否为例化模块
+ */
+ this.isInstantiation = false;
+
+ /**
+ * @description 如果是例化模块,它对应的 module 的 tree
+ * @type {ModuleView}
+ */
+ this.belongModuleView = undefined;
+
this._nameToConnection = new Map();
for (const [connectionName, wireIds] of Object.entries(rawCell.connections)) {
@@ -146,12 +157,17 @@ export class Cell {
return this._nameToConnection;
}
+ /**
+ * @description 当前器件的类型
+ * - 如果器件是一个例化模块,那么 type 就是这个例化的 module 的名字,比如 `full_adder_1bit`
+ * - 如果器件是一个基本器件,那么 type 就是 yosys 文档中记录的关于这个器件的名字,比如 $_AND_(与门), $_OR_(或门)
+ */
get type() {
return this.rawCell.type;
}
get id() {
- return dotConnect(this.moduleTree.id, this.name);
+ return dotConnect(this.view.id, this.name);
}
}
diff --git a/src/hook/utils.js b/src/hook/utils.js
index 70c42f2..a7d2627 100644
--- a/src/hook/utils.js
+++ b/src/hook/utils.js
@@ -1,5 +1,10 @@
const pinkLogStyle = 'background-color: #CB81DA; color: white; padding: 3px; border-radius: 3px;';
+const redLogStyle = 'background-color:rgb(227, 91, 49); color: white; padding: 3px; border-radius: 3px;';
export function pinkLog(message) {
console.log('%c' + message, pinkLogStyle);
+}
+
+export function redLog(message) {
+ console.log('%c' + message, redLogStyle);
}
\ No newline at end of file
diff --git a/src/i18n/ar.json b/src/i18n/ar.json
index 44904d9..549915f 100644
--- a/src/i18n/ar.json
+++ b/src/i18n/ar.json
@@ -15,5 +15,6 @@
"usermanual.move-view": "عرض الجوال",
"usermanual.scale-view": "تكبير/تصغير العرض",
"usermanual.scale-view-more": "تكبير العرض (مقياس أكبر)",
- "loading": "جاري التحميل"
+ "loading": "جاري التحميل",
+ "module": "وحدة"
}
\ No newline at end of file
diff --git a/src/i18n/de.json b/src/i18n/de.json
index df284d2..ce9b93e 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -15,5 +15,6 @@
"usermanual.move-view": "Mobile Ansicht",
"usermanual.scale-view": "Ansicht zoomen",
"usermanual.scale-view-more": "Ansicht vergrößern (größerer Maßstab)",
- "loading": "Laden"
+ "loading": "Laden",
+ "module": "Modul"
}
\ No newline at end of file
diff --git a/src/i18n/en.json b/src/i18n/en.json
index cd779fe..2f05218 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -15,5 +15,6 @@
"usermanual.move-view": "Mobile View",
"usermanual.scale-view": "View zoom",
"usermanual.scale-view-more": "View zoom (larger scale)",
- "loading": "loading"
+ "loading": "loading",
+ "module": "Modules"
}
\ No newline at end of file
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index b18ac45..7c4770e 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -15,5 +15,6 @@
"usermanual.move-view": "Vue mobile",
"usermanual.scale-view": "Zoom de la vue",
"usermanual.scale-view-more": "Zoom de la vue (échelle plus grande)",
- "loading": "Chargement"
+ "loading": "Chargement",
+ "module": "Module"
}
\ No newline at end of file
diff --git a/src/i18n/ja.json b/src/i18n/ja.json
index 2ca324d..de503dd 100644
--- a/src/i18n/ja.json
+++ b/src/i18n/ja.json
@@ -15,5 +15,6 @@
"usermanual.move-view": "モバイル表示",
"usermanual.scale-view": "ビューのズーム",
"usermanual.scale-view-more": "ビューのズーム(より大きなスケール)",
- "loading": "読み込み中"
+ "loading": "読み込み中",
+ "module": "モジュール"
}
\ No newline at end of file
diff --git a/src/i18n/ko.json b/src/i18n/ko.json
index 4a2d2e8..7b75ff7 100644
--- a/src/i18n/ko.json
+++ b/src/i18n/ko.json
@@ -15,5 +15,6 @@
"usermanual.move-view": "모바일 보기",
"usermanual.scale-view": "보기 확대/축소",
"usermanual.scale-view-more": "보기 확대 (더 큰 스케일)",
- "loading": "로딩 중"
+ "loading": "로딩 중",
+ "module": "모듈"
}
\ No newline at end of file
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 3765b53..7b3d194 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -15,5 +15,6 @@
"usermanual.move-view": "Мобильный вид",
"usermanual.scale-view": "Масштабирование вида",
"usermanual.scale-view-more": "Масштабирование вида (больший масштаб)",
- "loading": "Загрузка"
+ "loading": "Загрузка",
+ "module": "Модуль"
}
\ No newline at end of file
diff --git a/src/i18n/zh-cn.json b/src/i18n/zh-cn.json
index a2bf7b6..b3c0173 100644
--- a/src/i18n/zh-cn.json
+++ b/src/i18n/zh-cn.json
@@ -15,5 +15,6 @@
"usermanual.move-view": "移动视图",
"usermanual.scale-view": "视图缩放",
"usermanual.scale-view-more": "视图缩放(尺度更大)",
- "loading": "加载中"
+ "loading": "加载中",
+ "module": "模块"
}
\ No newline at end of file
diff --git a/src/i18n/zh-tw.json b/src/i18n/zh-tw.json
index 36d8b00..a16cb1f 100644
--- a/src/i18n/zh-tw.json
+++ b/src/i18n/zh-tw.json
@@ -15,5 +15,6 @@
"usermanual.move-view": "移動視圖",
"usermanual.scale-view": "視圖縮放",
"usermanual.scale-view-more": "視圖縮放(尺度更大)",
- "loading": "加載中"
+ "loading": "加載中",
+ "module": "模塊"
}
\ No newline at end of file