更新架构,进行依赖注入

This commit is contained in:
锦恢 2024-12-28 01:37:58 +08:00
parent eabdba2602
commit 0aa38f5133
24 changed files with 476 additions and 60 deletions

View File

@ -23,7 +23,8 @@
const skinBinary = await r2.arrayBuffer();
return [ netJson, skinBinary ];
}
window.avoidWasm = 'avoid.wasm';
window.moduleName = 'half_adder';
// window.avoidWasm = 'avoid.wasm';
</script>
</head>

View File

@ -37,8 +37,8 @@ body::-webkit-scrollbar {
* hr {
border: none;
background-color: var(--vscode-focusBorder);
height: 2px;
background-color: var(--main-color);
height: 1.5px;
width: 95%;
}
/*

View File

@ -35,6 +35,8 @@ onMounted(async () => {
text: t('loading'),
background: 'rgba(0, 0, 0, 0.7)'
});
globalLookup.topModuleName = window.moduleName;
// netlist json
const [ netJson, skinBinary ] = await window.readNetFile();

View File

@ -1,7 +1,7 @@
<template>
<div class="vcd-right-nav">
<div class="netlist-right-nav">
<div class="vcd-function-panel">
<div class="netlist-function-panel">
<TreeView
v-show="controlPanel.currentIndex === 0"></TreeView>
<Setting
@ -10,17 +10,17 @@
v-show="controlPanel.currentIndex === 2"></About>
</div>
<div class="vcd-function-option">
<div class="vcd-control-panel-wrapper">
<div class="vcd-control-panel-icon digital-ide-icon" />
<div class="netlist-function-option">
<div class="netlist-control-panel-wrapper">
<div class="netlist-control-panel-icon digital-ide-icon" />
</div>
<hr>
<div class="vcd-control-panel-wrapper"
<div class="netlist-control-panel-wrapper"
v-for="(section, index) of controlPanel.sections" :key="index"
@click="controlPanel.click(index)"
>
<div :class="controlPanel.currentIndex === index ? 'vcd-control-panel-active': ''"><span
class="vcd-control-panel-icon"
<div :class="controlPanel.currentIndex === index ? 'netlist-control-panel-active': ''"><span
class="netlist-control-panel-icon"
:class="section.iconClass"
></span></div>
</div>
@ -51,7 +51,7 @@ emitter.on('right-nav', index => {
</script>
<style>
.vcd-right-nav {
.netlist-right-nav {
display: flex;
position: fixed;
top: 0;
@ -59,21 +59,21 @@ emitter.on('right-nav', index => {
z-index: 230;
}
.vcd-function-panel {
.netlist-function-panel {
display: flex;
background-color: var(--sidebar);
height: 100vh;
box-shadow: var(--gray-box-shadow-1);
}
.vcd-function-option {
.netlist-function-option {
width: fit-content;
height: 100vh;
background-color: var(--sidebar);
box-shadow: var(--gray-box-shadow-1);
}
.vcd-control-panel-wrapper {
.netlist-control-panel-wrapper {
width: var(--right-nav-width);
height: var(--right-nav-width);
display: flex;
@ -84,17 +84,17 @@ emitter.on('right-nav', index => {
color: var(--vscode-foreground);
}
.vcd-control-panel-wrapper:hover {
.netlist-control-panel-wrapper:hover {
color: var(--vscode-icon-foreground);
}
.vcd-control-panel-icon {
.netlist-control-panel-icon {
width: 45px;
height: 45px;
font-size: 30px;
}
.vcd-control-panel-active {
.netlist-control-panel-active {
color: var(--main-color);
}
</style>

View File

@ -1,20 +1,69 @@
<template>
<div class="netlist-tree-view">
<div class="search-nothing">
<span class="iconfont icon-empty"></span>
<span>Mirror the unknown</span>
<div class="netlist-module-info">
<div class="netlist-signal-title">{{ t('module') }}</div>
<hr>
<el-scrollbar height="86vh" style="padding-right: 7px;">
<modules
v-for="mod of treeviewData.modules"
:key="mod.name"
:module="mod"
></modules>
</el-scrollbar>
</div>
</div>
</template>
<script setup>
import { defineComponent } from 'vue';
import modules from './modules.vue';
import { treeviewData } from './tree';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
defineComponent({ name: 'tree-view' });
</script>
<style>
.netlist-module-info {
padding-right: 5px;
}
.netlist-signal-title {
margin-left: 5px;
height: 2.5vh;
}
.icon-wave-square {
color: var(--signal-default-color);
}
.icon-brackets {
color: #75BEDF;
}
.icon-register {
color: #885dff;
}
.icon-integer {
color: #C76B3C;
}
.icon-string {
color: #8CC265;
}
.icon-real {
color: #5fb4d8;
}
.icon-task {
color: orange;
}
.search-nothing {
height: 40vh;
width: 100%;

View File

@ -0,0 +1,166 @@
<template>
<div class="module">
<div @click="clickItem()" class="netlist-treeview-item">
<span class="module-tag-status" @click.stop="expandManage.click">
<div :class="expandManage.expandTagClass"></div>
</span>
<span :class="`iconfont ${makeIconClass(module)}`"></span>
&ensp;{{ module.name }}
</div>
<!-- 渲染这个 module scope 有的组件 -->
<div v-if="ports.length > 0 || cells.length > 0" class="netlist-subtree-wrapper">
<div style="width: 20px;"></div>
<div style="width: 100%;">
<!-- ports -->
<div v-for="port in ports" :key="port.name" class="netlist-treeview-item">
<span class="module-tag-status">
</span>
<span class="iconfont icon-wave-square"></span>
&ensp;{{ port.name }}
</div>
<!-- 例化模块 -->
<modules
v-for="(cell, index) in cells"
:module="cell.view"
:key="index"
></modules>
</div>
</div>
</div>
</template>
<script setup>
/* eslint-disable */
import { ModuleView } from '@/hook/render/yosys';
import { defineComponent, reactive } from 'vue';
defineComponent({ name: 'modules' });
const props = defineProps({
module: {
type: ModuleView,
required: true
}
});
const module = props.module;
// scope
const ports = [];
const cells = [];
// ports & modules
for (const portName of module.nameToPort.keys()) {
ports.push({
name: portName
});
}
for (const cellName of module.nameToCell.keys()) {
const cell = module.nameToCell.get(cellName);
if (cell.view === module) {
//
continue;
}
if (cell.isInstantiation) {
cells.push({
name: cellName,
view: cell.belongModuleView
});
}
}
function clickItem() {
}
function getExpandStatus() {
if (ports.length === 0 && cells.length === 0) {
return false;
}
return true;
}
const expandManage = reactive({
expanded: getExpandStatus(),
expandTagClass: ports.length === 0 && cells.length === 0 ? '' : 'collapse-tag',
click() {
this.expanded = !this.expanded;
if (this.expandTagClass) {
this.expandTagClass = this.expanded ? 'expand-tag' : 'collapse-tag';
}
}
});
function makeIconClass() {
return 'icon-memory-chip';
}
</script>
<style>
.icon-memory-chip {
color: #FF7043;
}
.module {
text-align: left;
}
.netlist-subtree-wrapper {
display: flex;
width: 100%;
}
.netlist-treeview-item {
color: var(--vscode-foreground);
cursor: pointer;
display: flex;
align-items: center;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 0.75rem;
}
.netlist-treeview-item::selection {
background: none;
}
.netlist-treeview-item:hover {
background-color: var(--sidebar-item-selected);
border-radius: .3em;
}
.netlist-treeview-selected {
color: #ddd;
background-color: var(--button-active) !important;
border-radius: .3em;
}
.module-tag-status {
width: 23px;
height: 23px;
align-items: center;
justify-content: space-around;
display: flex;
}
.expand-tag {
height: 5px;
width: 5px;
border-top: solid 1.7px var(--vscode-foreground);
border-left: solid 1.7px var(--vscode-foreground);
transform: rotate(225deg);
}
.collapse-tag {
height: 5px;
width: 5px;
border-top: solid 1.7px var(--vscode-foreground);
border-left: solid 1.7px var(--vscode-foreground);
transform: rotate(135deg);
}
</style>

View File

@ -0,0 +1,13 @@
import { ModuleView } from '@/hook/render/yosys';
import { reactive } from 'vue';
export const treeviewData = reactive({
/**
* @description 当前的顶层模块数量内部数据结构是递归的
* 大部分情况下它应该是只有一个元素
*
* @type {ModuleView[]}
*/
modules: []
});

View File

@ -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');

View File

@ -25,7 +25,18 @@ export const globalLookup = {
/**
* @type {Avoid}
*/
Avoid: undefined
Avoid: undefined,
/**
* @description 当前仿真结果的顶层模块的名字
* @type {string}
*/
topModuleName: '',
/**
* @description 当前选择的实体可以是 wire也可以是 cell
*/
currentSelectEntity: undefined
};
function loadSetting() {

View File

@ -5,6 +5,8 @@
* @property {Record<string, YosysModel>} [models] 模型定义的映射仅在使用 `-aig` 选项时存在
*/
import { Module } from "./render/layout";
/**
* @typedef {number | string} WireId 信号ID
*/
@ -252,4 +254,4 @@
/**
* @typedef DragConnection
* @property {d3.Selection} selection
*/
*/

View File

@ -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);
}
/**

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -15,5 +15,6 @@
"usermanual.move-view": "عرض الجوال",
"usermanual.scale-view": "تكبير/تصغير العرض",
"usermanual.scale-view-more": "تكبير العرض (مقياس أكبر)",
"loading": "جاري التحميل"
"loading": "جاري التحميل",
"module": "وحدة"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -15,5 +15,6 @@
"usermanual.move-view": "モバイル表示",
"usermanual.scale-view": "ビューのズーム",
"usermanual.scale-view-more": "ビューのズーム(より大きなスケール)",
"loading": "読み込み中"
"loading": "読み込み中",
"module": "モジュール"
}

View File

@ -15,5 +15,6 @@
"usermanual.move-view": "모바일 보기",
"usermanual.scale-view": "보기 확대/축소",
"usermanual.scale-view-more": "보기 확대 (더 큰 스케일)",
"loading": "로딩 중"
"loading": "로딩 중",
"module": "모듈"
}

View File

@ -15,5 +15,6 @@
"usermanual.move-view": "Мобильный вид",
"usermanual.scale-view": "Масштабирование вида",
"usermanual.scale-view-more": "Масштабирование вида (больший масштаб)",
"loading": "Загрузка"
"loading": "Загрузка",
"module": "Модуль"
}

View File

@ -15,5 +15,6 @@
"usermanual.move-view": "移动视图",
"usermanual.scale-view": "视图缩放",
"usermanual.scale-view-more": "视图缩放(尺度更大)",
"loading": "加载中"
"loading": "加载中",
"module": "模块"
}

View File

@ -15,5 +15,6 @@
"usermanual.move-view": "移動視圖",
"usermanual.scale-view": "視圖縮放",
"usermanual.scale-view-more": "視圖縮放(尺度更大)",
"loading": "加載中"
"loading": "加載中",
"module": "模塊"
}