10 Commits

Author SHA1 Message Date
4918399325 feat:新增复制点赞点踩功能
- 优化输入框占据不满整个大框的问题
- 优化点赞点踩复制的tooltip显示被遮挡的问题
2025-12-16 16:22:07 +08:00
92f6639741 feat:对话界面将输入框固定在底部 2025-12-16 15:19:33 +08:00
07bb2d46b0 feat:实现一键优化的前端效果 2025-12-16 14:53:46 +08:00
e42514fe95 feat:实现添加上下文的样式 2025-12-16 09:17:07 +08:00
4e4e33d136 feat:模型切换的样式定义 2025-12-15 18:21:17 +08:00
e541b85005 feat:将调用node.js的fs模块改为vscode官方的API
- 这样可以避免用户本地没有node环境导致插件无法运行的原因
2025-12-15 17:29:15 +08:00
c77187eec1 feat:订阅之后开启vscode就自动打开聊天面板 2025-12-15 17:14:36 +08:00
a1a526bb98 feat:搭建本地存储会话历史的框架
- 将会话历史存储在C:\Users\admin\.iccoder文件下
- 在里面又会创建多个文件夹进行存储
2025-12-15 15:19:36 +08:00
ab6d257df2 feat:接入波形查看器的工具
- 生成VCD文件后,就自动打开波形查看的工具显示波形
2025-12-15 11:51:35 +08:00
22b9a0ed13 feat:接入iverilog工具
- 将iverilog可以随着插件的下载而下载
- 用户输入自然语言就可以控制生成对应的VCD文件
2025-12-15 11:09:03 +08:00
84 changed files with 4477 additions and 224 deletions

View File

@ -1,14 +0,0 @@
.vscode/**
.vscode-test/**
out/**
node_modules/**
src/**
.gitignore
.yarnrc
webpack.config.js
vsc-extension-quickstart.md
**/tsconfig.json
**/eslint.config.mjs
**/*.map
**/*.ts
**/.vscode-test.*

284
IVERILOG_INTEGRATION.md Normal file
View File

@ -0,0 +1,284 @@
# Iverilog 集成完成报告
## 概述
已成功将 Icarus Verilog (iverilog) 工具集成到 IC Coder 插件中,用户可以通过简单的命令生成 VCD 波形文件。
## 完成的功能
### 1. 核心功能模块
**文件**: `src/utils/iverilogRunner.ts`
实现了以下功能:
- ✅ Verilog 项目文件完整性检查
- ✅ 自动查找顶层模块和 testbench 文件
- ✅ iverilog 编译功能
- ✅ vvp 仿真运行
- ✅ VCD 文件生成
- ✅ 路径空格问题处理(使用 spawn 代替 exec
- ✅ 环境变量配置
### 2. 用户界面集成
**文件**: `src/utils/messageHandler.ts`
- ✅ 命令解析:支持多种 VCD 生成命令
- "生成 VCD"
- "创建 VCD"
- "运行仿真"
- "执行仿真"
- "iverilog"
- "生成波形"
- "仿真生成"
- ✅ 实时反馈:
- 项目检查进度
- 编译状态
- 仿真输出
- 错误信息
- ✅ 用户交互:
- 成功后提示打开 VCD 文件
- 详细的错误提示和解决建议
### 3. Iverilog 工具打包
**目录**: `tools/iverilog/`
已成功复制以下文件到插件包:
**bin/ 目录** (10 个文件,约 4 MB):
- `iverilog.exe` - Verilog 编译器
- `vvp.exe` - Verilog 仿真器
- 所有必需的 DLL 文件
**lib/ 目录** (44 个文件,约 21 MB):
- 所有 `.vpi` 库文件
- 所有 `.tgt` 目标文件
- 所有 `.conf` 配置文件
- `include/` 头文件目录
**总大小**: 约 25 MB
### 4. 文档和示例
创建了完整的文档:
1. **README.md** - 工具说明和使用指南
2. **INSTALL.md** - 安装和配置说明
3. **DOWNLOAD_INSTRUCTIONS.md** - 下载和部署指南
4. **examples/** - 测试示例项目
- `counter.v` - 4 位计数器模块
- `counter_tb.v` - 测试平台
- `README.md` - 示例说明
### 5. 自动化脚本
-`copy-iverilog.ps1` - PowerShell 自动复制脚本
-`copy-iverilog.bat` - Windows 批处理启动器
## 技术实现细节
### 路径空格处理
使用 `child_process.spawn` 代替 `child_process.exec`,完美解决了路径中包含空格的问题:
```typescript
function execCommand(
command: string,
args: string[],
options: { cwd: string; env?: any }
): Promise<{ stdout: string; stderr: string }>
```
### 环境变量配置
设置 `IVERILOG_ROOT` 环境变量,确保 iverilog 能找到库文件:
```typescript
const env = {
...process.env,
IVERILOG_ROOT: path.join(extensionPath, "tools", "iverilog"),
};
```
### 文件检查逻辑
智能识别项目文件:
- 自动查找所有 `.v``.sv` 文件
- 识别 testbench 文件(文件名包含 `tb``test`,或包含 `$dumpfile`
- 识别顶层模块(非 testbench 的 module 定义)
## 使用方法
### 1. 准备项目
确保项目包含:
- 至少一个 Verilog 模块文件(`.v``.sv`
- 一个 testbench 文件,包含:
```verilog
initial begin
$dumpfile("output.vcd");
$dumpvars(0, module_name);
// ... 测试代码 ...
$finish;
end
```
### 2. 生成 VCD
在 IC Coder 插件中输入任一命令:
- `生成 VCD`
- `运行仿真`
- `生成波形`
### 3. 查看结果
- VCD 文件保存在项目根目录:`output.vcd`
- 可以使用 GTKWave 等工具查看波形
## 测试示例
提供了完整的测试示例:
**位置**: `tools/iverilog/examples/`
**运行测试**:
1. 在 VS Code 中打开 `examples` 目录
2. 打开 IC Coder 插件
3. 输入 "生成 VCD"
4. 查看生成的 `output.vcd` 文件
## 文件清单
### 新增文件
```
src/utils/iverilogRunner.ts # 核心功能模块
tools/iverilog/
├── bin/ # 可执行文件 (10 个文件)
│ ├── iverilog.exe
│ ├── vvp.exe
│ └── *.dll
├── lib/ # 库文件 (44 个文件)
│ ├── ivl/
│ │ ├── *.vpi
│ │ ├── *.tgt
│ │ └── *.conf
│ └── include/
├── examples/ # 测试示例
│ ├── counter.v
│ ├── counter_tb.v
│ └── README.md
├── README.md # 使用说明
├── INSTALL.md # 安装指南
├── DOWNLOAD_INSTRUCTIONS.md # 下载说明
├── copy-iverilog.ps1 # 自动复制脚本
└── copy-iverilog.bat # 批处理启动器
```
### 修改文件
```
src/utils/messageHandler.ts # 添加 VCD 生成命令处理
src/panels/ICHelperPanel.ts # 传递 extensionPath 参数
package.json # 添加 tools 目录到打包列表
```
## 版本信息
- **Icarus Verilog**: v12.0 (devel) (s20150603-1539-g2693dd32b)
- **平台**: Windows x64
- **许可证**: GPL v2+
## 已知问题和限制
### 1. 路径空格问题 ✅ 已解决
- 使用 `spawn` 代替 `exec` 完美解决
### 2. 平台支持
- 当前仅包含 Windows x64 版本的 iverilog
- macOS 和 Linux 用户需要自行安装 iverilog
### 3. 文件大小
- 插件包增加约 25 MB
- 建议在发布时说明文件大小
## 后续优化建议
### 1. 多平台支持
- 为 macOS 和 Linux 提供对应的 iverilog 二进制文件
- 根据平台自动选择对应的可执行文件
### 2. 配置选项
- 允许用户配置 VCD 文件输出路径
- 允许用户配置仿真参数
### 3. 高级功能
- 支持 SystemVerilog
- 支持多个 testbench 选择
- 集成波形查看器
### 4. 错误处理
- 更详细的编译错误提示
- 语法错误定位
- 常见问题自动修复建议
## 测试清单
- ✅ 编译成功(无 TypeScript 错误)
- ✅ iverilog 工具已打包25 MB
- ✅ 路径空格问题已解决
- ✅ 环境变量配置正确
- ✅ 文档完整
- ✅ 示例项目可用
- ⏳ 实际运行测试(需要在 VS Code 中测试)
## 部署步骤
1. **确认文件完整**
```bash
ls -lh "D:/IC Coder Plugin/ic-coder/tools/iverilog/bin"
ls -lh "D:/IC Coder Plugin/ic-coder/tools/iverilog/lib"
```
2. **编译插件**
```bash
cd "D:/IC Coder Plugin/ic-coder"
pnpm run compile
```
3. **打包插件**
```bash
pnpm run package
```
4. **测试插件**
- 在 VS Code 中按 F5 启动调试
- 打开 `tools/iverilog/examples` 目录
- 测试 VCD 生成功能
5. **发布插件**
- 确保 `package.json` 中的 `files` 字段包含 `tools`
- 使用 `vsce package` 打包
- 发布到 VS Code Marketplace
## 总结
**所有功能已完成并集成**
- Iverilog 工具已成功打包到插件中25 MB
- 用户下载插件后即可直接使用,无需额外安装
- 支持多种命令触发 VCD 生成
- 提供完整的文档和示例
- 解决了路径空格等技术问题
- 代码编译成功,无错误
**下一步**: 在 VS Code 中实际测试插件功能,验证 VCD 生成流程。
---
**创建时间**: 2025-12-15
**版本**: 1.0
**状态**: ✅ 完成

View File

@ -1,71 +1,24 @@
# ic-coder README
# IC Coder Plugin
This is the README for your extension "ic-coder". After writing up a brief description, we recommend including the following sections.
IC Coder 是一个面向 Verilog/FPGA 开发的智能辅助插件。
## Features
## 功能特性
Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file.
- Verilog 代码智能生成
- 文件操作支持(创建、读取、修改、删除)
- 集成 iverilog 仿真工具
- VCD 波形文件生成
- 智能对话助手
For example if there is an image subfolder under your extension project workspace:
## 使用说明
\!\[feature X\]\(images/feature-x.png\)
安装插件后,点击侧边栏的 IC Coder 图标即可开始使用。
> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow.
## 系统要求
## Requirements
- VS Code 1.107.0 或更高版本
- 插件已内置 iverilog 工具(Windows 平台)
If you have any requirements or dependencies, add a section describing those and how to install and configure them.
## 许可证
## Extension Settings
Include if your extension adds any VS Code settings through the `contributes.configuration` extension point.
For example:
This extension contributes the following settings:
* `myExtension.enable`: Enable/disable this extension.
* `myExtension.thing`: Set to `blah` to do something.
## Known Issues
Calling out known issues can help limit users opening duplicate issues against your extension.
## Release Notes
Users appreciate release notes as you update your extension.
### 1.0.0
Initial release of ...
### 1.0.1
Fixed issue #.
### 1.1.0
Added features X, Y, and Z.
---
## Following extension guidelines
Ensure that you've read through the extensions guidelines and follow the best practices for creating your extension.
* [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines)
## Working with Markdown
You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts:
* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux).
* Toggle preview (`Shift+Cmd+V` on macOS or `Shift+Ctrl+V` on Windows and Linux).
* Press `Ctrl+Space` (Windows, Linux, macOS) to see a list of Markdown snippets.
## For more information
* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown)
* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/)
**Enjoy!**
MIT

View File

@ -0,0 +1,279 @@
# IC Coder 插件 - 会话历史管理功能实现文档
## 概述
本次更新为 IC Coder 插件添加了完整的会话历史管理功能实现了基于任务Task的对话历史持久化存储采用 LangChain4j 兼容的消息格式。
## 核心功能
### 1. 会话历史管理系统
#### 存储架构
- **存储位置**: `~/.iccoder/projects/{项目路径编码}/{taskId}/`
- **项目路径编码规则**:
- 移除冒号 `:`
- 将斜杠 `/` 和反斜杠 `\` 替换为 `--`
- 示例: `C:\Users\admin\Documents\Project``C--Users-admin-Documents-Project`
#### 文件结构
每个任务目录包含三个文件:
```
~/.iccoder/projects/{编码后的项目路径}/{taskId}/
├── meta.json # 任务元数据
├── conversation.json # 对话历史
└── conversation_meta.jsonl # 对话轮次元数据JSONL格式
```
### 2. 新增类型定义 (`src/types/chatHistory.ts`)
#### 消息类型
- **MessageType 枚举**: `SYSTEM`, `USER`, `AI`, `TOOL_EXECUTION_RESULT`
- **消息接口**:
- `SystemMessage`: 系统消息
- `UserMessage`: 用户消息(支持多内容块)
- `AiMessage`: AI 回复(支持工具调用和思考过程)
- `ToolExecutionResultMessage`: 工具执行结果
#### 元数据结构
- **TaskMeta**: 任务元数据
- 任务 ID、名称、项目路径
- 创建/更新时间
- Token 使用统计
- **ConversationMeta**: 对话轮次元数据
- 轮次 ID、时间戳
- Token 使用量
- 模型信息、响应耗时
### 3. ChatHistoryManager 单例类 (`src/utils/chatHistoryManager.ts`)
#### 核心方法
**任务管理**:
- `createTask(projectPath, taskName)`: 创建新任务
- `switchTask(projectPath, taskId)`: 切换到指定任务
- `listProjectTasks(projectPath)`: 列出项目所有任务
- `getCurrentTaskSession()`: 获取当前任务完整会话
**消息记录**:
- `addUserMessage(text)`: 添加用户消息
- `addAiMessage(text, toolRequests?)`: 添加 AI 消息
- `addSystemMessage(text)`: 添加系统消息
- `recordTurnMeta(turnId, usage, model, duration)`: 记录对话轮次元数据
**自动化功能**:
- 自动创建存储目录
- 首次使用时自动创建默认任务
- 自动更新任务时间戳和 Token 统计
### 4. 集成到消息处理流程 (`src/utils/messageHandler.ts`)
#### 修改点
-`handleUserMessage()` 中自动记录用户消息
- 在所有 AI 回复点自动记录 AI 消息
- 支持文件操作、VCD 生成等场景的历史记录
#### 记录场景
- 普通对话消息
- 文件操作(创建、删除、读取、更新、重命名、替换)
- VCD 文件生成流程
- 错误消息
### 5. 插件初始化 (`src/extension.ts`)
- 导入 `ChatHistoryManager`
- 插件激活时自动初始化历史管理器
- 为后续命令实现预留接口(已在 `package.json` 中定义)
## 预留命令(待实现)
`package.json` 中已定义以下命令,但在 `extension.ts` 中暂时注释:
- `ic-coder.viewHistory`: 查看会话历史
- `ic-coder.newSession`: 新建会话
- `ic-coder.exportSession`: 导出当前会话
- `ic-coder.deleteSession`: 删除会话
- `ic-coder.clearHistory`: 清空会话历史
- `ic-coder.searchSession`: 搜索会话
## 技术特点
1. **单例模式**: 确保全局唯一的历史管理器实例
2. **自动初始化**: 首次使用时自动创建必要的目录和文件
3. **LangChain4j 兼容**: 消息格式与 LangChain4j 保持一致
4. **跨平台支持**: 使用 VSCode API 处理文件系统,支持 Windows/Linux/macOS
5. **错误处理**: 完善的异常捕获和错误提示
6. **增量更新**: 使用 JSONL 格式追加对话元数据,避免重写整个文件
## 使用示例
```typescript
// 获取历史管理器实例
const historyManager = ChatHistoryManager.getInstance();
// 创建新任务
await historyManager.createTask('/path/to/project', '实现计数器模块');
// 记录对话
await historyManager.addUserMessage('帮我创建一个计数器');
await historyManager.addAiMessage('好的,我来帮你创建计数器模块...');
// 记录轮次元数据
await historyManager.recordTurnMeta(1, {
inputTokens: 100,
outputTokens: 200,
totalTokens: 300
}, 'gpt-4', 2.5);
// 获取当前会话
const session = await historyManager.getCurrentTaskSession();
```
## 数据格式示例
### meta.json
```json
{
"taskId": "task_20231215_abc123",
"taskName": "实现计数器模块",
"projectPath": "D:\\ICCoderPlugin\\ic-coder",
"createdAt": "2023-12-15T10:30:00.000Z",
"updatedAt": "2023-12-15T11:45:00.000Z",
"stats": {
"credits": 0,
"totalTokens": 5000,
"inputTokens": 2000,
"outputTokens": 3000
}
}
```
### conversation.json
```json
[
{
"type": "USER",
"contents": [
{
"type": "TEXT",
"text": "帮我创建一个计数器"
}
]
},
{
"type": "AI",
"text": "好的,我来帮你创建计数器模块..."
}
]
```
### conversation_meta.jsonl
```jsonl
{"turnId":1,"timestamp":"2023-12-15T10:30:00.000Z","usage":{"inputTokens":100,"outputTokens":200,"totalTokens":300},"model":"gpt-4","duration":2.5}
{"turnId":2,"timestamp":"2023-12-15T10:32:00.000Z","usage":{"inputTokens":150,"outputTokens":250,"totalTokens":400},"model":"gpt-4","duration":3.2}
```
## 文件修改清单
### 新增文件
1. `src/types/chatHistory.ts` - 类型定义文件
2. `src/utils/chatHistoryManager.ts` - 会话历史管理器
### 修改文件
1. `src/extension.ts` - 导入 ChatHistoryManager
2. `src/utils/messageHandler.ts` - 集成历史记录功能
3. `package.json` - 定义会话管理相关命令
## 关键代码位置
### 用户消息记录
- 文件: `src/utils/messageHandler.ts:29-30`
- 代码: `await historyManager.addUserMessage(text);`
### AI 消息记录
- 文件: `src/utils/messageHandler.ts:54`
- 代码: `await historyManager.addAiMessage(reply);`
### 文件操作历史记录
- 文件: `src/utils/messageHandler.ts:194, 207, 217, 227, 243, 263`
- 各种文件操作后都会记录 AI 消息
### 任务自动创建
- 文件: `src/utils/chatHistoryManager.ts:269-279`
- 方法: `ensureCurrentTask()`
## 后续开发建议
1. **实现预留的会话管理命令**
- 查看历史:展示任务列表和对话内容
- 新建会话:创建新任务并切换
- 导出会话:支持 Markdown、JSON 等格式
- 删除会话:删除指定任务目录
- 清空历史:清空所有会话数据
- 搜索会话:按关键词搜索对话内容
2. **增强功能**
- 添加会话统计和可视化面板
- 支持多任务并行管理
- 实现会话备份和恢复功能
- 添加会话标签和分类
- 支持会话合并和拆分
3. **性能优化**
- 实现会话数据的懒加载
- 添加缓存机制减少文件读写
- 优化大型会话的加载速度
4. **用户体验**
- 添加会话切换的快捷键
- 在状态栏显示当前任务信息
- 提供会话导入功能
- 支持会话模板
## 测试建议
1. **单元测试**
- 测试项目路径编码/解码
- 测试任务创建和切换
- 测试消息添加和读取
- 测试元数据更新
2. **集成测试**
- 测试完整的对话流程
- 测试文件操作的历史记录
- 测试跨会话切换
- 测试异常情况处理
3. **性能测试**
- 测试大量消息的读写性能
- 测试多任务并发访问
- 测试长时间运行的稳定性
## 注意事项
1. **数据安全**
- 会话数据存储在用户主目录,确保权限正确
- 敏感信息不应记录到历史中
- 定期清理过期会话数据
2. **兼容性**
- 确保跨平台路径处理正确
- 注意文件编码问题(统一使用 UTF-8
- 保持与 LangChain4j 格式的兼容性
3. **错误处理**
- 文件系统操作都应有异常捕获
- 提供友好的错误提示
- 记录详细的错误日志
## 版本信息
- **功能版本**: v1.0.0
- **实现日期**: 2025-12-15
- **兼容版本**: VSCode ^1.107.0
- **依赖**: 无额外依赖,仅使用 VSCode API
## 参考资料
- [LangChain4j 消息格式](https://docs.langchain4j.dev/)
- [VSCode 文件系统 API](https://code.visualstudio.com/api/references/vscode-api#FileSystem)
- [JSONL 格式规范](http://jsonlines.org/)

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
media/vcdrom/vcd.wasm Normal file

Binary file not shown.

13
media/vcdrom/vcdrom.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@
"description": "Agentic Verilog Coding Platform for Real-World FPGAs",
"version": "0.0.2",
"engines": {
"vscode": "^1.106.3"
"vscode": "^1.107.0"
},
"icon": "media/图案(方底).png",
"categories": [
@ -37,6 +37,41 @@
"command": "ic-coder.openChat",
"title": "IC Coder: 打开聊天",
"category": "IC Coder"
},
{
"command": "ic-coder.openVCDViewer",
"title": "打开 VCD 波形查看器",
"category": "IC Coder"
},
{
"command": "ic-coder.viewHistory",
"title": "查看会话历史",
"category": "IC Coder"
},
{
"command": "ic-coder.newSession",
"title": "新建会话",
"category": "IC Coder"
},
{
"command": "ic-coder.exportSession",
"title": "导出当前会话",
"category": "IC Coder"
},
{
"command": "ic-coder.deleteSession",
"title": "删除会话",
"category": "IC Coder"
},
{
"command": "ic-coder.clearHistory",
"title": "清空会话历史",
"category": "IC Coder"
},
{
"command": "ic-coder.searchSession",
"title": "搜索会话",
"category": "IC Coder"
}
],
"viewsContainers": {
@ -71,16 +106,30 @@
"build": "pnpm run compile"
},
"devDependencies": {
"@types/vscode": "^1.107.0",
"@types/mocha": "^10.0.10",
"@types/node": "22.x",
"typescript-eslint": "^8.48.1",
"eslint": "^9.39.1",
"typescript": "^5.9.3",
"ts-loader": "^9.5.4",
"webpack": "^5.103.0",
"webpack-cli": "^6.0.1",
"@types/vscode": "^1.107.0",
"@vscode/test-cli": "^0.0.12",
"@vscode/test-electron": "^2.5.2"
"@vscode/test-electron": "^2.5.2",
"eslint": "^9.39.1",
"ts-loader": "^9.5.4",
"typescript": "^5.9.3",
"typescript-eslint": "^8.48.1",
"webpack": "^5.103.0",
"webpack-cli": "^6.0.1"
},
"files": [
"dist",
"media",
"tools"
],
"dependencies": {
"@wavedrom/doppler": "^1.14.0",
"iconv-lite": "^0.7.1",
"onml": "^2.1.0",
"style-mod": "^4.1.3",
"vcd-stream": "^1.5.0",
"vcdrom": "^1.6.0",
"waveql": "^1.9.0"
}
}

245
pnpm-lock.yaml generated
View File

@ -7,6 +7,28 @@ settings:
importers:
.:
dependencies:
'@wavedrom/doppler':
specifier: ^1.14.0
version: 1.14.0
iconv-lite:
specifier: ^0.7.1
version: 0.7.1
onml:
specifier: ^2.1.0
version: 2.1.0
style-mod:
specifier: ^4.1.3
version: 4.1.3
vcd-stream:
specifier: ^1.5.0
version: 1.5.0
vcdrom:
specifier: ^1.6.0
version: 1.6.0
waveql:
specifier: ^1.9.0
version: 1.9.0
devDependencies:
'@types/mocha':
specifier: ^10.0.10
@ -48,6 +70,21 @@ packages:
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
'@codemirror/autocomplete@6.18.6':
resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==}
'@codemirror/commands@6.8.1':
resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==}
'@codemirror/language@6.11.2':
resolution: {integrity: sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==}
'@codemirror/state@6.4.1':
resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==}
'@codemirror/view@6.35.2':
resolution: {integrity: sha512-u04R04XFCYCNaHoNRr37WUUAfnxKPwPdqV+370NiO6i85qB1J/qCD/WbbMJsyJfRWhXIJXAe2BG/oTzAggqv4A==}
'@discoveryjs/json-ext@0.6.3':
resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==}
engines: {node: '>=14.17.0'}
@ -130,10 +167,22 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@lezer/common@1.4.0':
resolution: {integrity: sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==}
'@lezer/highlight@1.2.1':
resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==}
'@lezer/lr@1.4.5':
resolution: {integrity: sha512-/YTRKP5yPPSo1xImYQk7AZZMAgap0kegzqCSYHjAL9x1AZ0ZQW+IpcEzMKagCsbTsLnVeWkxYrCNeXG8xEPrjg==}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@types/debug@4.1.5':
resolution: {integrity: sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==}
'@types/eslint-scope@3.7.7':
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
@ -226,6 +275,9 @@ packages:
resolution: {integrity: sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==}
engines: {node: '>=16'}
'@wavedrom/doppler@1.14.0':
resolution: {integrity: sha512-LYzI70g30txDVHQcMvvJ1pnazdhWIOB6lnZUoya11+54lVgYdHn/lZJW8td7u0phHDwr7+WzV1Eqo/9Y2EHX9Q==}
'@webassemblyjs/ast@1.14.1':
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
@ -375,6 +427,12 @@ packages:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
binary-search@1.3.6:
resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==}
bindings@1.5.0:
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
@ -483,6 +541,14 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@ -503,6 +569,10 @@ packages:
resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==}
engines: {node: '>=0.3.1'}
dot-prop@6.0.1:
resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==}
engines: {node: '>=10'}
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@ -621,6 +691,9 @@ packages:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
file-uri-to-path@1.0.0:
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@ -709,6 +782,10 @@ packages:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
iconv-lite@0.7.1:
resolution: {integrity: sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==}
engines: {node: '>=0.10.0'}
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@ -768,6 +845,10 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
is-obj@2.0.0:
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
engines: {node: '>=8'}
is-path-inside@3.0.3:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
engines: {node: '>=8'}
@ -857,6 +938,15 @@ packages:
lie@3.3.0:
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
llparse-builder@1.5.2:
resolution: {integrity: sha512-i862UNC3YUEdlfK/NUCJxlKjtWjgAI9AJXDRgjcfRHfwFt4Sf8eFPTRsc91/2R9MBZ0kyFdfhi8SVhMsZf1gNQ==}
llparse-frontend@3.0.0:
resolution: {integrity: sha512-G/o0Po2C+G5OtP8MJeQDjDf5qwDxcO7K6x4r6jqGsJwxk7yblbJnRqpmye7G/lZ8dD0Hv5neY4/KB5BhDmEc9Q==}
llparse@7.3.0:
resolution: {integrity: sha512-9/CMwjsfGsvA6WuXFSxswpDxuZy5BgAeMnFxQYpDChC5rRrCxeRLyKl8djwMz7ZUN41QKfF/zYtZvL1PrDpVHw==}
loader-runner@4.3.1:
resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==}
engines: {node: '>=6.11.5'}
@ -869,6 +959,10 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lodash.get@4.4.2:
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
@ -942,6 +1036,9 @@ packages:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'}
onml@2.1.0:
resolution: {integrity: sha512-fvaSZRzprpwLFge/mcwE0CItfniNisVNamDdMK1FQUjh4ArQZ8ZWSkDaJbZc3XaANKZHq0xIa8NJpZ2HSe3oXA==}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@ -1074,6 +1171,12 @@ packages:
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
sax@1.4.3:
resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==}
schema-utils@4.3.3:
resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==}
engines: {node: '>= 10.13.0'}
@ -1147,6 +1250,9 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
style-mod@4.1.3:
resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==}
supports-color@10.2.2:
resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
engines: {node: '>=18'}
@ -1248,10 +1354,23 @@ packages:
resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}
engines: {node: '>=10.12.0'}
vcd-stream@1.5.0:
resolution: {integrity: sha512-w7IR4CuUv1KbpDjMu6YW6yRufK5qMTGAbc7ATbjfComVEq4lwSu+nJsFH2lGAsa9g+c3zzYoSDfazuy629eFnA==}
vcdrom@1.6.0:
resolution: {integrity: sha512-xvhbBOHrJb2zDhHTrdFjpOb2PWgcqqHh80YaRoVFFzYVtjrSGmj0mVsqxS5Tm1Gd9hoomdarQCaYJ+nMGWAM2A==}
w3c-keyname@2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
watchpack@2.4.4:
resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==}
engines: {node: '>=10.13.0'}
waveql@1.9.0:
resolution: {integrity: sha512-rvli279IWUpDz+iesmrWk5tUwIAcSoy3Svvjw4rcXLPAWwRkCI7YUl7JJn10Az9QhahXk2tmo4lGD/7pPhWg8Q==}
engines: {node: '>=12'}
webpack-cli@6.0.1:
resolution: {integrity: sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==}
engines: {node: '>=18.12.0'}
@ -1331,6 +1450,37 @@ snapshots:
'@bcoe/v8-coverage@1.0.2': {}
'@codemirror/autocomplete@6.18.6':
dependencies:
'@codemirror/language': 6.11.2
'@codemirror/state': 6.4.1
'@codemirror/view': 6.35.2
'@lezer/common': 1.4.0
'@codemirror/commands@6.8.1':
dependencies:
'@codemirror/language': 6.11.2
'@codemirror/state': 6.4.1
'@codemirror/view': 6.35.2
'@lezer/common': 1.4.0
'@codemirror/language@6.11.2':
dependencies:
'@codemirror/state': 6.4.1
'@codemirror/view': 6.35.2
'@lezer/common': 1.4.0
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.5
style-mod: 4.1.3
'@codemirror/state@6.4.1': {}
'@codemirror/view@6.35.2':
dependencies:
'@codemirror/state': 6.4.1
style-mod: 4.1.3
w3c-keyname: 2.2.8
'@discoveryjs/json-ext@0.6.3': {}
'@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)':
@ -1420,9 +1570,21 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@lezer/common@1.4.0': {}
'@lezer/highlight@1.2.1':
dependencies:
'@lezer/common': 1.4.0
'@lezer/lr@1.4.5':
dependencies:
'@lezer/common': 1.4.0
'@pkgjs/parseargs@0.11.0':
optional: true
'@types/debug@4.1.5': {}
'@types/eslint-scope@3.7.7':
dependencies:
'@types/eslint': 9.6.1
@ -1562,6 +1724,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@wavedrom/doppler@1.14.0':
dependencies:
lodash.get: 4.4.2
onml: 2.1.0
w3c-keyname: 2.2.8
'@webassemblyjs/ast@1.14.1':
dependencies:
'@webassemblyjs/helper-numbers': 1.13.2
@ -1715,6 +1883,12 @@ snapshots:
binary-extensions@2.3.0: {}
binary-search@1.3.6: {}
bindings@1.5.0:
dependencies:
file-uri-to-path: 1.0.0
brace-expansion@1.1.12:
dependencies:
balanced-match: 1.0.2
@ -1827,6 +2001,10 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
debug@3.2.7:
dependencies:
ms: 2.1.3
debug@4.4.3(supports-color@8.1.1):
dependencies:
ms: 2.1.3
@ -1839,6 +2017,10 @@ snapshots:
diff@7.0.0: {}
dot-prop@6.0.1:
dependencies:
is-obj: 2.0.0
eastasianwidth@0.2.0: {}
electron-to-chromium@1.5.267: {}
@ -1955,6 +2137,8 @@ snapshots:
dependencies:
flat-cache: 4.0.1
file-uri-to-path@1.0.0: {}
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@ -2039,6 +2223,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
iconv-lite@0.7.1:
dependencies:
safer-buffer: 2.1.2
ignore@5.3.2: {}
ignore@7.0.5: {}
@ -2081,6 +2269,8 @@ snapshots:
is-number@7.0.0: {}
is-obj@2.0.0: {}
is-path-inside@3.0.3: {}
is-plain-obj@2.1.0: {}
@ -2162,6 +2352,28 @@ snapshots:
dependencies:
immediate: 3.0.6
llparse-builder@1.5.2:
dependencies:
'@types/debug': 4.1.5
binary-search: 1.3.6
debug: 4.4.3(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
llparse-frontend@3.0.0:
dependencies:
debug: 3.2.7
llparse-builder: 1.5.2
transitivePeerDependencies:
- supports-color
llparse@7.3.0:
dependencies:
debug: 4.4.3(supports-color@8.1.1)
llparse-frontend: 3.0.0
transitivePeerDependencies:
- supports-color
loader-runner@4.3.1: {}
locate-path@5.0.0:
@ -2172,6 +2384,8 @@ snapshots:
dependencies:
p-locate: 5.0.0
lodash.get@4.4.2: {}
lodash.merge@4.6.2: {}
log-symbols@4.1.0:
@ -2253,6 +2467,10 @@ snapshots:
dependencies:
mimic-function: 5.0.1
onml@2.1.0:
dependencies:
sax: 1.4.3
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@ -2378,6 +2596,10 @@ snapshots:
safe-buffer@5.2.1: {}
safer-buffer@2.1.2: {}
sax@1.4.3: {}
schema-utils@4.3.3:
dependencies:
'@types/json-schema': 7.0.15
@ -2448,6 +2670,8 @@ snapshots:
strip-json-comments@3.1.1: {}
style-mod@4.1.3: {}
supports-color@10.2.2: {}
supports-color@7.2.0:
@ -2544,11 +2768,32 @@ snapshots:
'@types/istanbul-lib-coverage': 2.0.6
convert-source-map: 2.0.0
vcd-stream@1.5.0:
dependencies:
bindings: 1.5.0
dot-prop: 6.0.1
llparse: 7.3.0
transitivePeerDependencies:
- supports-color
vcdrom@1.6.0: {}
w3c-keyname@2.2.8: {}
watchpack@2.4.4:
dependencies:
glob-to-regexp: 0.4.1
graceful-fs: 4.2.11
waveql@1.9.0:
dependencies:
'@codemirror/autocomplete': 6.18.6
'@codemirror/commands': 6.8.1
'@codemirror/language': 6.11.2
'@codemirror/state': 6.4.1
'@codemirror/view': 6.35.2
'@lezer/highlight': 1.2.1
webpack-cli@6.0.1(webpack@5.103.0):
dependencies:
'@discoveryjs/json-ext': 0.6.3

View File

@ -1,10 +1,15 @@
import * as vscode from "vscode";
import { ICViewProvider } from "./views/ICViewProvider";
import { showICHelperPanel } from "./panels/ICHelperPanel";
import { VCDViewerPanel } from "./panels/VCDViewerPanel";
import { ChatHistoryManager } from "./utils/chatHistoryManager";
export function activate(context: vscode.ExtensionContext) {
console.log("🎉 IC Coder 插件已激活!");
// 自动打开聊天面板
vscode.commands.executeCommand("ic-coder.openChat");
// 注册命令:打开助手面板
const openPanelCommand = vscode.commands.registerCommand(
"ic-coder.openPanel",
@ -21,6 +26,81 @@ export function activate(context: vscode.ExtensionContext) {
}
);
// 注册命令:打开 VCD 波形查看器
const openVCDViewerCommand = vscode.commands.registerCommand(
"ic-coder.openVCDViewer",
async (vcdFilePath?: string) => {
if (!vcdFilePath) {
// 如果没有提供文件路径,让用户选择 VCD 文件
const fileUri = await vscode.window.showOpenDialog({
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
filters: {
"VCD 文件": ["vcd"],
"所有文件": ["*"],
},
title: "选择 VCD 文件",
});
if (fileUri && fileUri[0]) {
vcdFilePath = fileUri[0].fsPath;
} else {
return;
}
}
VCDViewerPanel.createOrShow(context.extensionUri, vcdFilePath);
}
);
// 注册命令:查看会话历史
// TODO: 这些命令需要根据新的任务架构重新实现
// 暂时注释掉,等待重新实现
/*
const viewHistoryCommand = vscode.commands.registerCommand(
"ic-coder.viewHistory",
() => {
vscode.window.showInformationMessage("此功能正在重构中,敬请期待");
}
);
const newSessionCommand = vscode.commands.registerCommand(
"ic-coder.newSession",
() => {
vscode.window.showInformationMessage("此功能正在重构中,敬请期待");
}
);
const exportSessionCommand = vscode.commands.registerCommand(
"ic-coder.exportSession",
() => {
vscode.window.showInformationMessage("此功能正在重构中,敬请期待");
}
);
const deleteSessionCommand = vscode.commands.registerCommand(
"ic-coder.deleteSession",
() => {
vscode.window.showInformationMessage("此功能正在重构中,敬请期待");
}
);
const clearHistoryCommand = vscode.commands.registerCommand(
"ic-coder.clearHistory",
() => {
vscode.window.showInformationMessage("此功能正在重构中,敬请期待");
}
);
const searchSessionCommand = vscode.commands.registerCommand(
"ic-coder.searchSession",
() => {
vscode.window.showInformationMessage("此功能正在重构中,敬请期待");
}
);
*/
// 注册侧边栏视图
const viewProvider = new ICViewProvider(context.extensionUri);
const viewRegistration = vscode.window.registerWebviewViewProvider(
@ -32,6 +112,14 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
openPanelCommand,
openChatCommand,
openVCDViewerCommand,
// TODO: 等待重新实现这些命令
// viewHistoryCommand,
// newSessionCommand,
// exportSessionCommand,
// deleteSessionCommand,
// clearHistoryCommand,
// searchSessionCommand,
viewRegistration
);
}

View File

@ -41,7 +41,7 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
(message) => {
switch (message.command) {
case "sendMessage":
handleUserMessage(panel, message.text);
handleUserMessage(panel, message.text, context.extensionPath);
break;
case "readFile":
handleReadFile(panel, message.filePath);

View File

@ -0,0 +1,352 @@
import * as vscode from "vscode";
import * as path from "path";
import * as fs from "fs";
/**
* VCD 波形查看器面板
*/
export class VCDViewerPanel {
public static currentPanel: VCDViewerPanel | undefined;
private readonly _panel: vscode.WebviewPanel;
private readonly _extensionUri: vscode.Uri;
private _disposables: vscode.Disposable[] = [];
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
this._panel = panel;
this._extensionUri = extensionUri;
// 设置初始 HTML 内容
this._panel.webview.html = this._getLoadingHtml();
// 监听面板关闭事件
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
// 监听来自 webview 的消息
this._panel.webview.onDidReceiveMessage(
(message) => {
switch (message.command) {
case "loadVCD":
if (message.filePath) {
this.loadVCDFile(message.filePath);
}
break;
}
},
null,
this._disposables
);
}
/**
* 创建或显示 VCD 查看器面板
*/
public static createOrShow(extensionUri: vscode.Uri, vcdFilePath?: string) {
const column = vscode.ViewColumn.One;
// 如果已经有面板打开,则显示它
if (VCDViewerPanel.currentPanel) {
VCDViewerPanel.currentPanel._panel.reveal(column);
if (vcdFilePath) {
VCDViewerPanel.currentPanel.loadVCDFile(vcdFilePath);
}
return;
}
// 创建新面板
const panel = vscode.window.createWebviewPanel(
"vcdViewer",
"VCD 波形查看器",
column,
{
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [extensionUri],
}
);
VCDViewerPanel.currentPanel = new VCDViewerPanel(panel, extensionUri);
// 如果提供了 VCD 文件路径,加载它
if (vcdFilePath) {
VCDViewerPanel.currentPanel.loadVCDFile(vcdFilePath);
}
}
/**
* 加载 VCD 文件
*/
public loadVCDFile(vcdFilePath: string) {
try {
// 检查文件是否存在
if (!fs.existsSync(vcdFilePath)) {
vscode.window.showErrorMessage(`VCD 文件不存在: ${vcdFilePath}`);
return;
}
// 更新面板标题
const fileName = path.basename(vcdFilePath);
this._panel.title = `VCD 波形查看器 - ${fileName}`;
// 设置 HTML 内容
this._panel.webview.html = this._getWebviewContent(vcdFilePath);
} catch (error) {
vscode.window.showErrorMessage(
`加载 VCD 文件失败: ${error instanceof Error ? error.message : "未知错误"}`
);
}
}
/**
* 清理资源
*/
public dispose() {
VCDViewerPanel.currentPanel = undefined;
this._panel.dispose();
while (this._disposables.length) {
const disposable = this._disposables.pop();
if (disposable) {
disposable.dispose();
}
}
}
/**
* 获取加载中的 HTML
*/
private _getLoadingHtml(): string {
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VCD 波形查看器</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
font-family: var(--vscode-font-family);
color: var(--vscode-foreground);
background-color: var(--vscode-editor-background);
}
.loading {
text-align: center;
}
.spinner {
border: 4px solid var(--vscode-progressBar-background);
border-top: 4px solid var(--vscode-progressBar-foreground);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="loading">
<div class="spinner"></div>
<p>正在加载 VCD 波形查看器...</p>
</div>
</body>
</html>`;
}
/**
* 获取 Webview 的 HTML 内容
*/
private _getWebviewContent(vcdFilePath: string): string {
// 获取资源 URI
const vcdromJsUri = this._panel.webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "vcdrom", "vcdrom.js")
);
const vcdWasmUri = this._panel.webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "vcdrom", "vcd.wasm")
);
const fontRegularUri = this._panel.webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "vcdrom", "IosevkaDrom-Regular.woff2")
);
const fontObliqueUri = this._panel.webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "vcdrom", "IosevkaDrom-Oblique.woff2")
);
const fontItalicUri = this._panel.webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "vcdrom", "IosevkaDrom-Italic.woff2")
);
// 读取 VCD 文件内容并转换为 base64
const vcdContent = fs.readFileSync(vcdFilePath, "utf-8");
const vcdBase64 = Buffer.from(vcdContent).toString("base64");
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${this._panel.webview.cspSource}; style-src 'unsafe-inline' ${this._panel.webview.cspSource}; script-src 'unsafe-inline' 'unsafe-eval' ${this._panel.webview.cspSource}; img-src ${this._panel.webview.cspSource} data:; connect-src ${this._panel.webview.cspSource};">
<title>VCD 波形查看器</title>
<style>
@font-face {
font-family: 'Iosevka Drom Web';
font-display: swap;
font-weight: 400;
font-stretch: normal;
font-style: normal;
src: url('${fontRegularUri}') format('woff2');
}
@font-face {
font-family: 'Iosevka Drom Web';
font-display: swap;
font-weight: 400;
font-stretch: normal;
font-style: oblique;
src: url('${fontObliqueUri}') format('woff2');
}
@font-face {
font-family: 'Iosevka Drom Web';
font-display: swap;
font-weight: 400;
font-stretch: normal;
font-style: italic;
src: url('${fontItalicUri}') format('woff2');
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Iosevka Drom Web', monospace;
color: var(--vscode-foreground);
background-color: var(--vscode-editor-background);
overflow: hidden;
}
#waveform-container {
width: 100vw;
height: 100vh;
overflow: auto;
}
#waveform1 {
width: 100%;
height: 100%;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
flex-direction: column;
}
.spinner {
border: 4px solid var(--vscode-progressBar-background);
border-top: 4px solid var(--vscode-progressBar-foreground);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error-message {
padding: 20px;
color: var(--vscode-errorForeground);
background-color: var(--vscode-inputValidation-errorBackground);
border: 1px solid var(--vscode-inputValidation-errorBorder);
border-radius: 4px;
margin: 20px;
}
</style>
<script src="${vcdromJsUri}"></script>
</head>
<body>
<div id="waveform-container">
<div class="loading">
<div class="spinner"></div>
<p>正在加载 VCD 波形...</p>
</div>
<div id="waveform1"></div>
</div>
<script>
(async function() {
try {
// 设置 WASM 文件路径
window.wasmBinaryFile = '${vcdWasmUri}';
// 解码 base64 VCD 内容
const vcdBase64 = '${vcdBase64}';
const vcdContent = atob(vcdBase64);
// 隐藏加载提示
document.querySelector('.loading').style.display = 'none';
// 创建一个函数来提供 VCD 数据流
const vcdProvider = async (handler) => {
// 将 VCD 内容转换为 Uint8Array
const encoder = new TextEncoder();
const vcdData = encoder.encode(vcdContent);
// 创建一个 ReadableStream reader
const stream = new ReadableStream({
start(controller) {
controller.enqueue(vcdData);
controller.close();
}
});
const reader = stream.getReader();
// 调用 handler 并传递 reader
await handler([{
key: 'local',
value: 'waveform.vcd',
format: 'raw',
baseName: 'waveform.vcd',
ext: 'vcd',
reader: reader
}]);
};
// 初始化 VCDrom使用函数回调方式
if (typeof VCDrom === 'function') {
await VCDrom('waveform1', vcdProvider);
} else {
throw new Error('VCDrom 未正确加载');
}
} catch (error) {
console.error('加载 VCD 波形失败:', error);
document.getElementById('waveform-container').innerHTML =
'<div class="error-message">' +
'<h3>❌ 加载 VCD 波形失败</h3>' +
'<p>' + error.message + '</p>' +
'<p style="margin-top: 10px;">请确保 VCD 文件格式正确。</p>' +
'<pre style="margin-top: 10px; padding: 10px; background: rgba(0,0,0,0.1); overflow: auto;">' + error.stack + '</pre>' +
'</div>';
}
})();
</script>
</body>
</html>`;
}
}

115
src/types/chatHistory.ts Normal file
View File

@ -0,0 +1,115 @@
/**
* 消息类型枚举(对应 LangChain4j 格式)
*/
export enum MessageType {
SYSTEM = "SYSTEM",
USER = "USER",
AI = "AI",
TOOL_EXECUTION_RESULT = "TOOL_EXECUTION_RESULT"
}
/**
* 工具执行请求
*/
export interface ToolExecutionRequest {
id: string;
name: string;
arguments: string; // JSON字符串
}
/**
* 消息内容用于USER消息
*/
export interface MessageContent {
type: "TEXT";
text: string;
}
/**
* 基础消息接口
*/
export interface BaseMessage {
type: MessageType;
}
/**
* 系统消息
*/
export interface SystemMessage extends BaseMessage {
type: MessageType.SYSTEM;
text: string;
}
/**
* 用户消息
*/
export interface UserMessage extends BaseMessage {
type: MessageType.USER;
contents: MessageContent[];
}
/**
* AI消息
*/
export interface AiMessage extends BaseMessage {
type: MessageType.AI;
text?: string;
toolExecutionRequests?: ToolExecutionRequest[];
thinking?: string;
}
/**
* 工具执行结果消息
*/
export interface ToolExecutionResultMessage extends BaseMessage {
type: MessageType.TOOL_EXECUTION_RESULT;
id: string;
toolName: string;
text: string; // JSON字符串
}
/**
* 联合消息类型
*/
export type ChatMessage = SystemMessage | UserMessage | AiMessage | ToolExecutionResultMessage;
/**
* 对话轮次元数据
*/
export interface ConversationMeta {
turnId: number;
timestamp: string; // ISO 8601格式
usage?: {
inputTokens?: number;
outputTokens?: number;
totalTokens?: number;
};
model?: string;
duration?: number; // 响应耗时(秒)
}
/**
* 任务元数据meta.json
*/
export interface TaskMeta {
taskId: string;
taskName: string;
projectPath: string;
createdAt: string; // ISO 8601格式
updatedAt: string; // ISO 8601格式
stats: {
credits: number;
totalTokens: number;
inputTokens: number;
outputTokens: number;
};
}
/**
* 任务会话(包含所有相关数据)
*/
export interface TaskSession {
meta: TaskMeta;
messages: ChatMessage[]; // conversation.json的内容
conversationMeta: ConversationMeta[]; // conversation_meta.jsonl的内容
}

View File

@ -0,0 +1,497 @@
import * as vscode from 'vscode';
import * as path from 'path';
import {
ChatMessage,
TaskMeta,
TaskSession,
ConversationMeta,
MessageType,
UserMessage,
AiMessage,
SystemMessage
} from '../types/chatHistory';
/**
* 会话历史管理器
* 按照设计文档实现:~/.iccoder/projects/{项目路径编码}/{taskId}/
*/
export class ChatHistoryManager {
private static instance: ChatHistoryManager;
private baseDir: string; // ~/.iccoder
private currentTaskId: string | null = null;
private currentProjectPath: string | null = null;
private constructor() {
// 设置存储路径: ~/.iccoder
const userHome = process.env.USERPROFILE || process.env.HOME || '';
this.baseDir = path.join(userHome, '.iccoder');
this.ensureBaseDir();
}
/**
* 项目路径编码
* 规则:
* - 替换 \ 和 / 为 --
* - 替换 : 为空
* 例如C:\Users\admin\Documents\Project -> C--Users-admin-Documents-Project
*/
private encodeProjectPath(projectPath: string): string {
return projectPath
.replace(/:/g, '') // 移除冒号
.replace(/[/\\]/g, '--'); // 替换斜杠为 --
}
/**
* 生成任务ID
* 格式task_{date}_{sequence}
*/
private generateTaskId(): string {
const date = new Date().toISOString().split('T')[0].replace(/-/g, '');
const sequence = Math.random().toString(36).substr(2, 6);
return `task_${date}_${sequence}`;
}
/**
* 获取任务目录路径
*/
private getTaskDir(projectPath: string, taskId: string): string {
const encodedPath = this.encodeProjectPath(projectPath);
return path.join(this.baseDir, 'projects', encodedPath, taskId);
}
/**
* 确保基础目录存在
*/
private async ensureBaseDir(): Promise<void> {
try {
const uri = vscode.Uri.file(this.baseDir);
try {
await vscode.workspace.fs.stat(uri);
} catch {
// 目录不存在,创建它
await vscode.workspace.fs.createDirectory(uri);
console.log(`创建存储目录: ${this.baseDir}`);
}
} catch (error) {
console.error("创建存储目录失败:", error);
vscode.window.showErrorMessage("创建会话历史存储目录失败");
}
}
/**
* 确保任务目录存在
*/
private async ensureTaskDir(taskDir: string): Promise<void> {
try {
const uri = vscode.Uri.file(taskDir);
try {
await vscode.workspace.fs.stat(uri);
} catch {
// 目录不存在,创建它
await vscode.workspace.fs.createDirectory(uri);
console.log(`创建任务目录: ${taskDir}`);
}
} catch (error) {
console.error("创建任务目录失败:", error);
throw error;
}
}
/**
* 获取单例实例
*/
public static getInstance(): ChatHistoryManager {
if (!ChatHistoryManager.instance) {
ChatHistoryManager.instance = new ChatHistoryManager();
}
return ChatHistoryManager.instance;
}
/**
* 创建新任务
*/
public async createTask(projectPath: string, taskName: string): Promise<TaskMeta> {
const taskId = this.generateTaskId();
const now = new Date().toISOString();
const meta: TaskMeta = {
taskId,
taskName,
projectPath,
createdAt: now,
updatedAt: now,
stats: {
credits: 0,
totalTokens: 0,
inputTokens: 0,
outputTokens: 0
}
};
this.currentTaskId = taskId;
this.currentProjectPath = projectPath;
// 创建任务目录
const taskDir = this.getTaskDir(projectPath, taskId);
await this.ensureTaskDir(taskDir);
// 保存 meta.json
await this.saveTaskMeta(meta);
// 初始化空的 conversation.json
await this.saveConversation([]);
return meta;
}
/**
* 保存任务元数据
*/
private async saveTaskMeta(meta: TaskMeta): Promise<void> {
if (!this.currentTaskId || !this.currentProjectPath) {
throw new Error("没有当前任务");
}
const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId);
const metaPath = path.join(taskDir, 'meta.json');
try {
const uri = vscode.Uri.file(metaPath);
const content = Buffer.from(JSON.stringify(meta, null, 2), 'utf-8');
await vscode.workspace.fs.writeFile(uri, content);
} catch (error) {
console.error("保存任务元数据失败:", error);
throw error;
}
}
/**
* 加载任务元数据
*/
private async loadTaskMeta(): Promise<TaskMeta | null> {
if (!this.currentTaskId || !this.currentProjectPath) {
return null;
}
const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId);
const metaPath = path.join(taskDir, 'meta.json');
try {
const uri = vscode.Uri.file(metaPath);
const content = await vscode.workspace.fs.readFile(uri);
const data = Buffer.from(content).toString('utf-8');
return JSON.parse(data);
} catch (error) {
// 文件不存在或读取失败
return null;
}
}
/**
* 保存对话历史conversation.json
*/
private async saveConversation(messages: ChatMessage[]): Promise<void> {
if (!this.currentTaskId || !this.currentProjectPath) {
throw new Error("没有当前任务");
}
const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId);
const conversationPath = path.join(taskDir, 'conversation.json');
try {
const uri = vscode.Uri.file(conversationPath);
const content = Buffer.from(JSON.stringify(messages, null, 2), 'utf-8');
await vscode.workspace.fs.writeFile(uri, content);
} catch (error) {
console.error("保存对话历史失败:", error);
throw error;
}
}
/**
* 加载对话历史
*/
private async loadConversation(): Promise<ChatMessage[]> {
if (!this.currentTaskId || !this.currentProjectPath) {
return [];
}
const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId);
const conversationPath = path.join(taskDir, 'conversation.json');
try {
const uri = vscode.Uri.file(conversationPath);
const content = await vscode.workspace.fs.readFile(uri);
const data = Buffer.from(content).toString('utf-8');
return JSON.parse(data);
} catch (error) {
// 文件不存在或读取失败
return [];
}
}
/**
* 追加对话元数据conversation_meta.jsonl
*/
private async appendConversationMeta(meta: ConversationMeta): Promise<void> {
if (!this.currentTaskId || !this.currentProjectPath) {
throw new Error("没有当前任务");
}
const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId);
const metaPath = path.join(taskDir, 'conversation_meta.jsonl');
try {
const uri = vscode.Uri.file(metaPath);
const line = JSON.stringify(meta) + '\n';
// 读取现有内容
let existingContent = '';
try {
const content = await vscode.workspace.fs.readFile(uri);
existingContent = Buffer.from(content).toString('utf-8');
} catch {
// 文件不存在,忽略错误
}
// 追加新内容
const newContent = existingContent + line;
await vscode.workspace.fs.writeFile(uri, Buffer.from(newContent, 'utf-8'));
} catch (error) {
console.error("追加对话元数据失败:", error);
throw error;
}
}
/**
* 确保有当前任务,如果没有则自动创建
*/
private async ensureCurrentTask(): Promise<void> {
if (!this.currentTaskId || !this.currentProjectPath) {
// 获取当前工作区路径
const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
if (workspacePath) {
await this.createTask(workspacePath, "默认任务");
} else {
throw new Error("没有打开的工作区,无法创建任务");
}
}
}
/**
* 添加用户消息
*/
public async addUserMessage(text: string): Promise<void> {
await this.ensureCurrentTask();
const messages = await this.loadConversation();
const userMessage: UserMessage = {
type: MessageType.USER,
contents: [{ type: "TEXT", text }]
};
messages.push(userMessage);
await this.saveConversation(messages);
// 更新任务元数据
await this.updateTaskTimestamp();
}
/**
* 添加AI消息
*/
public async addAiMessage(text: string, toolRequests?: any[]): Promise<void> {
await this.ensureCurrentTask();
const messages = await this.loadConversation();
const aiMessage: AiMessage = {
type: MessageType.AI,
text,
toolExecutionRequests: toolRequests
};
messages.push(aiMessage);
await this.saveConversation(messages);
// 更新任务元数据
await this.updateTaskTimestamp();
}
/**
* 添加系统消息
*/
public async addSystemMessage(text: string): Promise<void> {
await this.ensureCurrentTask();
const messages = await this.loadConversation();
const systemMessage: SystemMessage = {
type: MessageType.SYSTEM,
text
};
messages.push(systemMessage);
await this.saveConversation(messages);
}
/**
* 记录对话轮次元数据
*/
public async recordTurnMeta(
turnId: number,
usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number },
model?: string,
duration?: number
): Promise<void> {
const meta: ConversationMeta = {
turnId,
timestamp: new Date().toISOString(),
usage,
model,
duration
};
await this.appendConversationMeta(meta);
// 更新任务统计
if (usage) {
await this.updateTaskStats(usage);
}
}
/**
* 更新任务时间戳
*/
private async updateTaskTimestamp(): Promise<void> {
const meta = await this.loadTaskMeta();
if (meta) {
meta.updatedAt = new Date().toISOString();
await this.saveTaskMeta(meta);
}
}
/**
* 更新任务统计
*/
private async updateTaskStats(usage: { inputTokens?: number; outputTokens?: number; totalTokens?: number }): Promise<void> {
const meta = await this.loadTaskMeta();
if (meta) {
meta.stats.inputTokens += usage.inputTokens || 0;
meta.stats.outputTokens += usage.outputTokens || 0;
meta.stats.totalTokens += usage.totalTokens || 0;
meta.updatedAt = new Date().toISOString();
await this.saveTaskMeta(meta);
}
}
/**
* 获取当前任务会话
*/
public async getCurrentTaskSession(): Promise<TaskSession | null> {
const meta = await this.loadTaskMeta();
if (!meta) {
return null;
}
const messages = await this.loadConversation();
const conversationMeta = await this.loadConversationMeta();
return {
meta,
messages,
conversationMeta
};
}
/**
* 加载对话元数据
*/
private async loadConversationMeta(): Promise<ConversationMeta[]> {
if (!this.currentTaskId || !this.currentProjectPath) {
return [];
}
const taskDir = this.getTaskDir(this.currentProjectPath, this.currentTaskId);
const metaPath = path.join(taskDir, 'conversation_meta.jsonl');
try {
const uri = vscode.Uri.file(metaPath);
const content = await vscode.workspace.fs.readFile(uri);
const data = Buffer.from(content).toString('utf-8');
return data
.split('\n')
.filter(line => line.trim())
.map(line => JSON.parse(line));
} catch (error) {
// 文件不存在或读取失败
return [];
}
}
/**
* 列出项目的所有任务
*/
public async listProjectTasks(projectPath: string): Promise<TaskMeta[]> {
const encodedPath = this.encodeProjectPath(projectPath);
const projectDir = path.join(this.baseDir, 'projects', encodedPath);
try {
const uri = vscode.Uri.file(projectDir);
const entries = await vscode.workspace.fs.readDirectory(uri);
const tasks: TaskMeta[] = [];
for (const [taskId, type] of entries) {
if (type === vscode.FileType.Directory) {
const metaPath = path.join(projectDir, taskId, 'meta.json');
try {
const metaUri = vscode.Uri.file(metaPath);
const content = await vscode.workspace.fs.readFile(metaUri);
const data = Buffer.from(content).toString('utf-8');
tasks.push(JSON.parse(data));
} catch (error) {
console.error(`加载任务 ${taskId} 失败:`, error);
}
}
}
return tasks.sort((a, b) =>
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
);
} catch (error) {
// 目录不存在
return [];
}
}
/**
* 切换到指定任务
*/
public async switchTask(projectPath: string, taskId: string): Promise<boolean> {
const taskDir = this.getTaskDir(projectPath, taskId);
const metaPath = path.join(taskDir, 'meta.json');
try {
const uri = vscode.Uri.file(metaPath);
await vscode.workspace.fs.stat(uri);
this.currentProjectPath = projectPath;
this.currentTaskId = taskId;
return true;
} catch {
return false;
}
}
/**
* 获取当前任务ID
*/
public getCurrentTaskId(): string | null {
return this.currentTaskId;
}
/**
* 获取基础目录
*/
public getBaseDir(): string {
return this.baseDir;
}
}

View File

@ -1,5 +1,4 @@
import * as vscode from "vscode";
import * as fs from "fs";
import * as path from "path";
/**
@ -21,21 +20,31 @@ export async function createFile(
}
}
const fileUri = vscode.Uri.file(absolutePath);
// 检测文件是否已存在
if (fs.existsSync(absolutePath)) {
try {
await vscode.workspace.fs.stat(fileUri);
throw new Error(`文件已存在: ${absolutePath}`);
} catch (error: any) {
// 如果文件不存在,继续创建
if (error.code !== 'FileNotFound') {
throw error;
}
}
// 确保目录存在
const dirPath = path.dirname(absolutePath);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, {
recursive: true,
});
const dirUri = vscode.Uri.file(dirPath);
try {
await vscode.workspace.fs.stat(dirUri);
} catch {
await vscode.workspace.fs.createDirectory(dirUri);
}
// 创建文件
fs.writeFileSync(absolutePath, content, "utf-8");
const contentBytes = Buffer.from(content, "utf-8");
await vscode.workspace.fs.writeFile(fileUri, contentBytes);
} catch (error) {
throw error;
}
@ -62,14 +71,17 @@ export async function createOrOverwriteFile(
// 确保目录存在
const dirPath = path.dirname(absolutePath);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, {
recursive: true,
});
const dirUri = vscode.Uri.file(dirPath);
try {
await vscode.workspace.fs.stat(dirUri);
} catch {
await vscode.workspace.fs.createDirectory(dirUri);
}
// 创建或覆盖文件
fs.writeFileSync(absolutePath, content, "utf-8");
const fileUri = vscode.Uri.file(absolutePath);
const contentBytes = Buffer.from(content, "utf-8");
await vscode.workspace.fs.writeFile(fileUri, contentBytes);
} catch (error) {
throw error;
}
@ -91,18 +103,25 @@ export async function createDirectory(dirPath: string): Promise<void> {
}
}
const dirUri = vscode.Uri.file(absolutePath);
// 检测创建目录是否存在
if (fs.existsSync(absolutePath)) {
const state = fs.statSync(absolutePath);
if (state.isDirectory()) {
try {
const stat = await vscode.workspace.fs.stat(dirUri);
if (stat.type === vscode.FileType.Directory) {
throw new Error(`目录已存在: ${absolutePath}`);
} else {
throw new Error(`路径已存在且不是目录: ${absolutePath}`);
}
} catch (error: any) {
// 如果目录不存在,继续创建
if (error.code !== 'FileNotFound') {
throw error;
}
}
// 创建目录
fs.mkdirSync(absolutePath, { recursive: true });
await vscode.workspace.fs.createDirectory(dirUri);
} catch (error) {
throw error;
}
@ -146,19 +165,20 @@ export async function deleteFile(filePath: string): Promise<void> {
}
}
const fileUri = vscode.Uri.file(absolutePath);
// 检查文件是否存在
if (!fs.existsSync(absolutePath)) {
try {
const stat = await vscode.workspace.fs.stat(fileUri);
if (stat.type !== vscode.FileType.File) {
throw new Error(`路径不是文件: ${absolutePath}`);
}
} catch (error) {
throw new Error(`文件不存在: ${absolutePath}`);
}
// 检查是否是文件
const stats = fs.statSync(absolutePath);
if (!stats.isFile()) {
throw new Error(`路径不是文件: ${absolutePath}`);
}
// 删除文件
fs.unlinkSync(absolutePath);
await vscode.workspace.fs.delete(fileUri);
} catch (error) {
throw error;
}
@ -181,19 +201,21 @@ export async function updateFile(
}
}
const fileUri = vscode.Uri.file(absolutePath);
// 检查文件是否存在
if (!fs.existsSync(absolutePath)) {
try {
const stat = await vscode.workspace.fs.stat(fileUri);
if (stat.type !== vscode.FileType.File) {
throw new Error(`路径不是文件: ${absolutePath}`);
}
} catch (error) {
throw new Error(`文件不存在: ${absolutePath}`);
}
// 检查是否是文件内容
const state = fs.statSync(absolutePath);
if (!state.isFile()) {
throw new Error(`路径不是文件: ${absolutePath}`);
}
// 修改文件内容
fs.writeFileSync(absolutePath, content, "utf-8");
const contentBytes = Buffer.from(content, "utf-8");
await vscode.workspace.fs.writeFile(fileUri, contentBytes);
} catch (error) {
throw error;
}
@ -218,13 +240,21 @@ export async function appendToFile(
}
}
// 检查文件是否存在
if (!fs.existsSync(absolutePath)) {
const fileUri = vscode.Uri.file(absolutePath);
// 检查文件是否存在并读取原内容
let existingContent = "";
try {
const contentBytes = await vscode.workspace.fs.readFile(fileUri);
existingContent = Buffer.from(contentBytes).toString("utf-8");
} catch (error) {
throw new Error(`文件不存在: ${absolutePath}`);
}
// 追加内容
fs.appendFileSync(absolutePath, content, "utf-8");
const newContent = existingContent + content;
const contentBytes = Buffer.from(newContent, "utf-8");
await vscode.workspace.fs.writeFile(fileUri, contentBytes);
} catch (error) {
throw error;
}
@ -248,13 +278,16 @@ export async function replaceFile(
}
}
// 检查文件是否存在
if (!fs.existsSync(absolutePath)) {
throw new Error(`文件不存在: ${absolutePath}`);
}
const fileUri = vscode.Uri.file(absolutePath);
// 读取文件内容
const fileContent = fs.readFileSync(absolutePath, "utf-8");
let fileContent: string;
try {
const contentBytes = await vscode.workspace.fs.readFile(fileUri);
fileContent = Buffer.from(contentBytes).toString("utf-8");
} catch (error) {
throw new Error(`文件不存在: ${absolutePath}`);
}
// 转义特殊字符,将字符串作为字面量处理
const escapeRegExp = (str: string) => {
@ -276,7 +309,8 @@ export async function replaceFile(
}
// 写回文件
fs.writeFileSync(absolutePath, newContent, "utf-8");
const contentBytes = Buffer.from(newContent, "utf-8");
await vscode.workspace.fs.writeFile(fileUri, contentBytes);
} catch (error) {
throw error;
}
@ -300,20 +334,26 @@ export async function insertAtLine(
}
}
// 检查文件是否存在
if (!fs.existsSync(absolutePath)) {
const fileUri = vscode.Uri.file(absolutePath);
// 读取文件内容
let fileContent: string;
try {
const contentBytes = await vscode.workspace.fs.readFile(fileUri);
fileContent = Buffer.from(contentBytes).toString("utf-8");
} catch (error) {
throw new Error(`文件不存在: ${absolutePath}`);
}
// 读取文件内容
const fileContent = fs.readFileSync(absolutePath, "utf-8");
const lines = fileContent.split("\n");
// 插入内容
lines.splice(lineNumber, 0, content);
// 写回文件
fs.writeFileSync(absolutePath, lines.join("\n"), "utf-8");
const newContent = lines.join("\n");
const newContentBytes = Buffer.from(newContent, "utf-8");
await vscode.workspace.fs.writeFile(fileUri, newContentBytes);
} catch (error) {
throw error;
}
@ -345,24 +385,41 @@ export async function renameFile(
throw new Error("没有打开的工作区,无法重命名相对路径的文件");
}
const oldUri = vscode.Uri.file(absoluteOldPath);
const newUri = vscode.Uri.file(absoluteNewPath);
// 检查原文件是否存在
if (!fs.existsSync(absoluteOldPath)) {
try {
await vscode.workspace.fs.stat(oldUri);
} catch (error) {
throw new Error(`文件不存在: ${absoluteOldPath}`);
}
// 检查新文件名是否已存在
if (fs.existsSync(absoluteNewPath)) {
try {
await vscode.workspace.fs.stat(newUri);
throw new Error(`目标文件已存在: ${absoluteNewPath}`);
} catch (error: any) {
// 如果文件不存在,继续重命名
if (error.code !== 'FileNotFound' && !error.message.includes('目标文件已存在')) {
throw error;
}
if (error.message.includes('目标文件已存在')) {
throw error;
}
}
// 确保目标目录存在
const newDir = path.dirname(absoluteNewPath);
if (!fs.existsSync(newDir)) {
fs.mkdirSync(newDir, { recursive: true });
const newDirUri = vscode.Uri.file(newDir);
try {
await vscode.workspace.fs.stat(newDirUri);
} catch {
await vscode.workspace.fs.createDirectory(newDirUri);
}
// 重命名文件
fs.renameSync(absoluteOldPath, absoluteNewPath);
await vscode.workspace.fs.rename(oldUri, newUri, { overwrite: false });
} catch (error) {
throw error;
}

415
src/utils/iverilogRunner.ts Normal file
View File

@ -0,0 +1,415 @@
import * as vscode from "vscode";
import * as path from "path";
import { spawn } from "child_process";
import { promisify } from "util";
// 使用 spawn 来执行命令,正确处理路径空格
function execCommand(
command: string,
args: string[],
options: { cwd: string; env?: any }
): Promise<{ stdout: string; stderr: string }> {
return new Promise((resolve, reject) => {
// 在 Windows 上,如果路径包含空格,不使用 shell直接用 spawn
// spawn 在不使用 shell 时会正确处理带空格的路径
const child = spawn(command, args, {
cwd: options.cwd,
env: options.env || process.env,
windowsHide: true,
shell: false, // 不使用 shell让 spawn 直接执行
});
let stdout = "";
let stderr = "";
// 在 Windows 上使用 GBK 编码解码输出
const encoding = process.platform === 'win32' ? 'gbk' : 'utf8';
child.stdout.on("data", (data) => {
try {
// 尝试使用 iconv-lite 解码(如果可用)
const iconv = require('iconv-lite');
stdout += iconv.decode(data, encoding);
} catch {
// 如果 iconv-lite 不可用,使用默认解码
stdout += data.toString('utf8');
}
});
child.stderr.on("data", (data) => {
try {
const iconv = require('iconv-lite');
stderr += iconv.decode(data, encoding);
} catch {
stderr += data.toString('utf8');
}
});
child.on("close", (code) => {
if (code === 0) {
resolve({ stdout, stderr });
} else {
const error: any = new Error(`Command failed with exit code ${code}`);
error.stdout = stdout;
error.stderr = stderr;
error.code = code;
reject(error);
}
});
child.on("error", (error) => {
reject(error);
});
});
}
/**
* Verilog 项目文件检查结果
*/
export interface VerilogProjectCheck {
isComplete: boolean;
hasTopModule: boolean;
hasTestbench: boolean;
topModuleFile?: string;
testbenchFile?: string;
allVerilogFiles: string[];
missingFiles: string[];
errors: string[];
}
/**
* VCD 生成结果
*/
export interface VCDGenerationResult {
success: boolean;
vcdFilePath?: string;
outputPath?: string;
message: string;
stdout?: string;
stderr?: string;
}
/**
* 检查项目中的 Verilog 文件完整性
*/
export async function checkVerilogProject(
projectPath: string
): Promise<VerilogProjectCheck> {
const result: VerilogProjectCheck = {
isComplete: false,
hasTopModule: false,
hasTestbench: false,
allVerilogFiles: [],
missingFiles: [],
errors: [],
};
try {
// 检查项目路径是否存在
const projectUri = vscode.Uri.file(projectPath);
try {
await vscode.workspace.fs.stat(projectUri);
} catch (error) {
result.errors.push(`项目路径不存在: ${projectPath}`);
return result;
}
// 查找所有 Verilog 文件 (.v, .sv)
const verilogFiles = await findVerilogFiles(projectPath);
result.allVerilogFiles = verilogFiles;
if (verilogFiles.length === 0) {
result.errors.push("项目中没有找到 Verilog 文件 (.v 或 .sv)");
return result;
}
// 分析文件内容,查找 top module 和 testbench
for (const file of verilogFiles) {
const fileUri = vscode.Uri.file(file);
const contentBytes = await vscode.workspace.fs.readFile(fileUri);
const content = Buffer.from(contentBytes).toString("utf-8");
const fileName = path.basename(file).toLowerCase();
// 检查是否是 testbench 文件
if (
fileName.includes("tb") ||
fileName.includes("test") ||
content.includes("$dumpfile") ||
content.includes("$dumpvars")
) {
result.hasTestbench = true;
result.testbenchFile = file;
}
// 检查是否包含 module 定义(可能是 top module
if (content.match(/module\s+\w+/)) {
if (!result.hasTopModule && !fileName.includes("tb")) {
result.hasTopModule = true;
result.topModuleFile = file;
}
}
}
// 检查完整性
if (!result.hasTopModule) {
result.missingFiles.push("缺少顶层模块文件");
}
if (!result.hasTestbench) {
result.missingFiles.push("缺少 testbench 文件");
}
result.isComplete = result.hasTopModule && result.hasTestbench;
return result;
} catch (error) {
result.errors.push(
`检查项目时出错: ${error instanceof Error ? error.message : "未知错误"}`
);
return result;
}
}
/**
* 递归查找目录下所有 Verilog 文件
*/
async function findVerilogFiles(dir: string): Promise<string[]> {
const verilogFiles: string[] = [];
async function searchDir(currentDir: string) {
const dirUri = vscode.Uri.file(currentDir);
const entries = await vscode.workspace.fs.readDirectory(dirUri);
for (const [fileName, fileType] of entries) {
const filePath = path.join(currentDir, fileName);
if (fileType === vscode.FileType.Directory) {
// 跳过 node_modules 等目录
if (!fileName.startsWith(".") && fileName !== "node_modules") {
await searchDir(filePath);
}
} else if (fileType === vscode.FileType.File) {
const ext = path.extname(fileName).toLowerCase();
if (ext === ".v" || ext === ".sv") {
verilogFiles.push(filePath);
}
}
}
}
await searchDir(dir);
return verilogFiles;
}
/**
* 获取 iverilog 可执行文件路径
*/
async function getIverilogPath(extensionPath: string): Promise<string> {
const platform = process.platform;
let iverilogBin = "";
if (platform === "win32") {
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog.exe");
} else if (platform === "darwin") {
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog");
} else {
// Linux
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog");
}
// 如果插件包中没有,尝试使用系统安装的 iverilog
const iverilogUri = vscode.Uri.file(iverilogBin);
try {
await vscode.workspace.fs.stat(iverilogUri);
return iverilogBin;
} catch {
return "iverilog"; // 使用系统 PATH 中的 iverilog
}
}
/**
* 获取 vvp 可执行文件路径
*/
async function getVvpPath(extensionPath: string): Promise<string> {
const platform = process.platform;
let vvpBin = "";
if (platform === "win32") {
vvpBin = path.join(extensionPath, "tools", "iverilog", "bin", "vvp.exe");
} else if (platform === "darwin") {
vvpBin = path.join(extensionPath, "tools", "iverilog", "bin", "vvp");
} else {
// Linux
vvpBin = path.join(extensionPath, "tools", "iverilog", "bin", "vvp");
}
// 如果插件包中没有,尝试使用系统安装的 vvp
const vvpUri = vscode.Uri.file(vvpBin);
try {
await vscode.workspace.fs.stat(vvpUri);
return vvpBin;
} catch {
return "vvp"; // 使用系统 PATH 中的 vvp
}
}
/**
* 运行 iverilog 生成 VCD 文件
*/
export async function generateVCD(
projectPath: string,
extensionPath: string
): Promise<VCDGenerationResult> {
try {
// 1. 检查项目完整性
const projectCheck = await checkVerilogProject(projectPath);
if (!projectCheck.isComplete) {
return {
success: false,
message: `项目文件不完整:\n${projectCheck.missingFiles.join("\n")}\n${projectCheck.errors.join("\n")}`,
};
}
// 2. 获取 iverilog 和 vvp 路径
const iverilogPath = await getIverilogPath(extensionPath);
const vvpPath = await getVvpPath(extensionPath);
// 3. 准备输出路径
const outputDir = projectPath;
const outputFile = path.join(outputDir, "simulation.vvp");
// 4. 设置环境变量,确保 iverilog 能找到库文件
const libPath = path.join(extensionPath, "tools", "iverilog", "lib");
const env = {
...process.env,
IVERILOG_ROOT: path.join(extensionPath, "tools", "iverilog"),
};
// 5. 构建 iverilog 编译参数
const compileArgs = ["-o", outputFile, ...projectCheck.allVerilogFiles];
console.log("执行编译命令:", iverilogPath, compileArgs.join(" "));
console.log("IVERILOG_ROOT:", env.IVERILOG_ROOT);
// 6. 执行编译
let compileResult;
try {
compileResult = await execCommand(iverilogPath, compileArgs, {
cwd: projectPath,
env: env,
});
} catch (error: any) {
return {
success: false,
message: `iverilog 编译失败:\n${error.message}`,
stderr: error.stderr,
stdout: error.stdout,
};
}
// 7. 执行仿真生成 VCD
const simArgs = [outputFile];
console.log("执行仿真命令:", vvpPath, simArgs.join(" "));
let simResult;
try {
simResult = await execCommand(vvpPath, simArgs, {
cwd: projectPath,
env: env,
});
} catch (error: any) {
return {
success: false,
message: `VVP 仿真失败:\n${error.message}`,
stderr: error.stderr,
stdout: error.stdout,
};
}
// 8. 查找生成的 VCD 文件
const projectUri = vscode.Uri.file(projectPath);
const entries = await vscode.workspace.fs.readDirectory(projectUri);
const vcdFiles = entries
.filter(([fileName, fileType]) => fileType === vscode.FileType.File && fileName.endsWith('.vcd'))
.map(([fileName]) => fileName);
if (vcdFiles.length === 0) {
return {
success: false,
message: "VCD 文件未生成。请确保 testbench 中包含 $dumpfile 和 $dumpvars 语句。",
stdout: simResult.stdout,
};
}
// 使用找到的第一个 VCD 文件
const vcdFile = path.join(projectPath, vcdFiles[0]);
// 9. 清理中间文件
try {
const outputUri = vscode.Uri.file(outputFile);
await vscode.workspace.fs.stat(outputUri);
await vscode.workspace.fs.delete(outputUri);
} catch (error) {
// 忽略清理错误
}
return {
success: true,
vcdFilePath: vcdFile,
outputPath: projectPath,
message: `VCD 文件生成成功!\n路径: ${vcdFile}`,
stdout: simResult.stdout,
};
} catch (error) {
return {
success: false,
message: `生成 VCD 文件时出错: ${error instanceof Error ? error.message : "未知错误"}`,
};
}
}
/**
* 检查 iverilog 是否可用
*/
export async function checkIverilogAvailable(
extensionPath: string
): Promise<{ available: boolean; version?: string; message: string }> {
try {
const iverilogPath = await getIverilogPath(extensionPath);
// 检查文件是否存在
const iverilogUri = vscode.Uri.file(iverilogPath);
try {
await vscode.workspace.fs.stat(iverilogUri);
} catch (error) {
return {
available: false,
message: `iverilog 不可用。未找到文件: ${iverilogPath}`,
};
}
// 设置环境变量
const env = {
...process.env,
IVERILOG_ROOT: path.join(extensionPath, "tools", "iverilog"),
};
const result = await execCommand(iverilogPath, ["-V"], {
cwd: path.dirname(iverilogPath),
env: env,
});
const version = result.stdout.trim().split("\n")[0];
return {
available: true,
version: version,
message: `iverilog 可用: ${version}`,
};
} catch (error: any) {
return {
available: false,
message: `iverilog 执行失败: ${error.message}\n${error.stderr || ""}`,
};
}
}

View File

@ -8,16 +8,33 @@ import {
renameFile,
replaceFile,
} from "./createFiles";
import {
generateVCD,
checkVerilogProject,
checkIverilogAvailable,
} from "./iverilogRunner";
import { ChatHistoryManager } from "./chatHistoryManager";
/**
* 处理用户消息
*/
export async function handleUserMessage(
panel: vscode.WebviewPanel,
text: string
text: string,
extensionPath?: string
) {
console.log("收到用户消息:", text);
// 记录用户消息到历史
const historyManager = ChatHistoryManager.getInstance();
await historyManager.addUserMessage(text);
// 检查是否是 VCD 生成命令
if (isVCDGenerationCommand(text)) {
await handleVCDGeneration(panel, extensionPath || "");
return;
}
// 检查是否是文件操作命令
const fileOperation = parseFileOperation(text);
@ -32,6 +49,10 @@ export async function handleUserMessage(
// 普通消息处理
console.log("作为普通消息处理");
const reply = getMockReply(text);
// 记录助手回复到历史
await historyManager.addAiMessage(reply);
setTimeout(() => {
panel.webview.postMessage({
command: "receiveMessage",
@ -154,28 +175,36 @@ async function handleFileOperation(
replaceText?: string;
}
) {
const historyManager = ChatHistoryManager.getInstance();
try {
let responseText = "";
switch (operation.type) {
case "create":
await createFile(operation.filePath, operation.content || "");
responseText = `✅ 文件创建成功: ${operation.filePath}`;
panel.webview.postMessage({
command: "receiveMessage",
text: `✅ 文件创建成功: ${operation.filePath}`,
text: responseText,
});
vscode.window.showInformationMessage(
`文件创建成功: ${operation.filePath}`
);
await historyManager.addAiMessage(responseText);
break;
case "delete":
await deleteFile(operation.filePath);
responseText = `✅ 文件删除成功: ${operation.filePath}`;
panel.webview.postMessage({
command: "receiveMessage",
text: `✅ 文件删除成功: ${operation.filePath}`,
text: responseText,
});
vscode.window.showInformationMessage(
`文件删除成功: ${operation.filePath}`
);
await historyManager.addAiMessage(responseText);
break;
case "read":
@ -185,6 +214,7 @@ async function handleFileOperation(
content: content,
filePath: operation.filePath,
});
await historyManager.addAiMessage(`读取文件: ${operation.filePath}`);
break;
case "update":
@ -194,6 +224,7 @@ async function handleFileOperation(
content: currentContent,
filePath: operation.filePath,
});
await historyManager.addAiMessage(`编辑文件: ${operation.filePath}`);
break;
case "rename":
@ -201,13 +232,15 @@ async function handleFileOperation(
throw new Error("缺少新文件名");
}
await renameFile(operation.filePath, operation.newPath);
responseText = `✅ 文件重命名成功: ${operation.filePath}${operation.newPath}`;
panel.webview.postMessage({
command: "receiveMessage",
text: `✅ 文件重命名成功: ${operation.filePath}${operation.newPath}`,
text: responseText,
});
vscode.window.showInformationMessage(
`文件重命名成功: ${operation.filePath}${operation.newPath}`
);
await historyManager.addAiMessage(responseText);
break;
case "replace":
@ -219,13 +252,15 @@ async function handleFileOperation(
operation.searchText,
operation.replaceText
);
responseText = `✅ 文件内容替换成功: ${operation.filePath}`;
panel.webview.postMessage({
command: "receiveMessage",
text: `✅ 文件内容替换成功: ${operation.filePath}`,
text: responseText,
});
vscode.window.showInformationMessage(
`文件内容替换成功: ${operation.filePath}`
);
await historyManager.addAiMessage(responseText);
break;
}
} catch (error) {
@ -235,6 +270,7 @@ async function handleFileOperation(
text: `${errorMsg}`,
});
vscode.window.showErrorMessage(errorMsg);
await historyManager.addAiMessage(`${errorMsg}`);
}
}
@ -466,3 +502,158 @@ export function insertCodeToEditor(code: string) {
vscode.window.showWarningMessage("请先打开一个编辑器");
}
}
/**
* 检查是否是 VCD 生成命令
*/
function isVCDGenerationCommand(text: string): boolean {
const lowerText = text.toLowerCase().trim();
// 匹配各种 VCD 生成命令
const vcdPatterns = [
/生成\s*vcd/,
/创建\s*vcd/,
/运行\s*仿真/,
/执行\s*仿真/,
/iverilog/,
/生成\s*波形/,
/仿真\s*生成/,
];
return vcdPatterns.some((pattern) => pattern.test(lowerText));
}
/**
* 处理 VCD 生成请求
*/
async function handleVCDGeneration(
panel: vscode.WebviewPanel,
extensionPath: string
) {
try {
// 获取当前工作区路径
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
panel.webview.postMessage({
command: "receiveMessage",
text: "❌ 请先打开一个工作区文件夹",
});
vscode.window.showErrorMessage("请先打开一个工作区文件夹");
return;
}
const projectPath = workspaceFolders[0].uri.fsPath;
// 发送开始消息
panel.webview.postMessage({
command: "receiveMessage",
text: "🔍 正在检查项目文件...",
});
// 1. 检查 iverilog 是否可用
const iverilogCheck = await checkIverilogAvailable(extensionPath);
if (!iverilogCheck.available) {
panel.webview.postMessage({
command: "receiveMessage",
text: `${iverilogCheck.message}\n\n请参考插件文档安装 iverilog 工具。`,
});
vscode.window.showErrorMessage(iverilogCheck.message);
return;
}
// 2. 检查项目文件完整性
const projectCheck = await checkVerilogProject(projectPath);
if (!projectCheck.isComplete) {
let errorMsg = "❌ 项目文件不完整:\n\n";
if (projectCheck.allVerilogFiles.length === 0) {
errorMsg += "• 未找到任何 Verilog 文件 (.v 或 .sv)\n";
} else {
errorMsg += `• 找到 ${projectCheck.allVerilogFiles.length} 个 Verilog 文件\n`;
if (!projectCheck.hasTopModule) {
errorMsg += "• ❌ 缺少顶层模块文件\n";
} else {
errorMsg += `• ✅ 顶层模块: ${projectCheck.topModuleFile}\n`;
}
if (!projectCheck.hasTestbench) {
errorMsg += "• ❌ 缺少 testbench 文件\n";
errorMsg += "\n提示: testbench 文件应包含 $dumpfile 和 $dumpvars 语句来生成 VCD 文件。\n";
} else {
errorMsg += `• ✅ Testbench: ${projectCheck.testbenchFile}\n`;
}
}
if (projectCheck.errors.length > 0) {
errorMsg += "\n错误信息:\n" + projectCheck.errors.join("\n");
}
panel.webview.postMessage({
command: "receiveMessage",
text: errorMsg,
});
vscode.window.showWarningMessage("项目文件不完整,无法生成 VCD");
return;
}
// 3. 显示项目检查结果
panel.webview.postMessage({
command: "receiveMessage",
text: `✅ 项目检查通过!\n\n找到 ${projectCheck.allVerilogFiles.length} 个 Verilog 文件\n• 顶层模块: ${projectCheck.topModuleFile}\n• Testbench: ${projectCheck.testbenchFile}\n\n🚀 开始编译和仿真...`,
});
// 4. 生成 VCD 文件
const result = await generateVCD(projectPath, extensionPath);
if (result.success) {
let successMsg = `${result.message}`;
if (result.stdout) {
successMsg += `\n\n仿真输出:\n${result.stdout}`;
}
panel.webview.postMessage({
command: "receiveMessage",
text: successMsg,
});
// 自动打开 VCD 波形查看器
if (result.vcdFilePath) {
vscode.commands.executeCommand("ic-coder.openVCDViewer", result.vcdFilePath);
vscode.window.showInformationMessage(
`VCD 文件生成成功,已自动打开波形查看器`
);
}
} else {
let errorMsg = `${result.message}`;
if (result.stderr) {
errorMsg += `\n\n错误输出:\n${result.stderr}`;
}
if (result.stdout) {
errorMsg += `\n\n标准输出:\n${result.stdout}`;
}
panel.webview.postMessage({
command: "receiveMessage",
text: errorMsg,
});
vscode.window.showErrorMessage("VCD 文件生成失败");
}
} catch (error) {
const errorMsg = `❌ 生成 VCD 文件时出错: ${
error instanceof Error ? error.message : "未知错误"
}`;
panel.webview.postMessage({
command: "receiveMessage",
text: errorMsg,
});
vscode.window.showErrorMessage(errorMsg);
}
}

View File

@ -1,5 +1,4 @@
import * as vscode from "vscode";
import * as fs from "fs";
import * as path from "path";
/**
@ -16,19 +15,21 @@ export async function readFileContent(filePath: string): Promise<string> {
}
}
// 检查文件是否存在
if (!fs.existsSync(absolutePath)) {
const fileUri = vscode.Uri.file(absolutePath);
// 检查文件是否存在并获取文件类型
try {
const stat = await vscode.workspace.fs.stat(fileUri);
if (stat.type !== vscode.FileType.File) {
throw new Error(`路径不是文件: ${absolutePath}`);
}
} catch (error) {
throw new Error(`文件不存在: ${absolutePath}`);
}
// 检查是否是文件
const stats = fs.statSync(absolutePath);
if (!stats.isFile()) {
throw new Error(`路径不是文件: ${absolutePath}`);
}
// 读取文件内容
const content = fs.readFileSync(absolutePath, "utf-8");
const contentBytes = await vscode.workspace.fs.readFile(fileUri);
const content = Buffer.from(contentBytes).toString("utf-8");
return content;
} catch (error) {
throw error;
@ -76,36 +77,38 @@ export async function readDirectory(
}
}
const dirUri = vscode.Uri.file(absolutePath);
// 检查目录是否存在
if (!fs.existsSync(absolutePath)) {
try {
const stat = await vscode.workspace.fs.stat(dirUri);
if (stat.type !== vscode.FileType.Directory) {
throw new Error(`路径不是目录: ${absolutePath}`);
}
} catch (error) {
throw new Error(`目录不存在: ${absolutePath}`);
}
const stats = fs.statSync(absolutePath);
if (!stats.isDirectory()) {
throw new Error(`路径不是目录: ${absolutePath}`);
}
// 读取目录内容
const files = fs.readdirSync(absolutePath);
const entries = await vscode.workspace.fs.readDirectory(dirUri);
const results = [];
for (const file of files) {
const filePath = path.join(absolutePath, file);
const fileStats = fs.statSync(filePath);
if (fileStats.isFile()) {
for (const [fileName, fileType] of entries) {
if (fileType === vscode.FileType.File) {
// 如果指定了扩展名过滤
if (extensions && extensions.length > 0) {
const ext = path.extname(file);
const ext = path.extname(fileName);
if (!extensions.includes(ext)) {
continue;
}
}
try {
const content = fs.readFileSync(filePath, "utf-8");
results.push({ path: file, content });
const filePath = path.join(absolutePath, fileName);
const fileUri = vscode.Uri.file(filePath);
const contentBytes = await vscode.workspace.fs.readFile(fileUri);
const content = Buffer.from(contentBytes).toString("utf-8");
results.push({ path: fileName, content });
} catch (error) {
// 跳过无法读取的文件
continue;
@ -122,13 +125,13 @@ export async function readDirectory(
/**
* 获取文件信息
*/
export function getFileInfo(filePath: string): {
export async function getFileInfo(filePath: string): Promise<{
exists: boolean;
isFile: boolean;
isDirectory: boolean;
size?: number;
extension?: string;
} {
}> {
try {
let absolutePath = filePath;
if (!path.isAbsolute(filePath)) {
@ -138,22 +141,24 @@ export function getFileInfo(filePath: string): {
}
}
if (!fs.existsSync(absolutePath)) {
const fileUri = vscode.Uri.file(absolutePath);
try {
const stat = await vscode.workspace.fs.stat(fileUri);
return {
exists: true,
isFile: stat.type === vscode.FileType.File,
isDirectory: stat.type === vscode.FileType.Directory,
size: stat.size,
extension: path.extname(absolutePath),
};
} catch (error) {
return {
exists: false,
isFile: false,
isDirectory: false,
};
}
const stats = fs.statSync(absolutePath);
return {
exists: true,
isFile: stats.isFile(),
isDirectory: stats.isDirectory(),
size: stats.size,
extension: path.extname(absolutePath),
};
} catch (error) {
return {
exists: false,

View File

@ -16,6 +16,7 @@ export function getWebviewContent(iconUri?: string): string {
margin: 0;
padding: 20px;
height: 100vh;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
@ -33,45 +34,254 @@ export function getWebviewContent(iconUri?: string): string {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.messages {
flex: 1;
overflow-y: auto;
margin-bottom: 15px;
min-height: 0;
}
.message {
margin-bottom: 12px;
padding: 10px 15px;
border-radius: 8px;
max-width: 80%;
}
.user-message {
padding: 10px 15px;
border-radius: 8px;
background: var(--vscode-button-secondaryBackground);
border: 1px solid var(--vscode-input-border);
margin-left: auto;
width: fit-content;
max-width: 80%;
}
.bot-message {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
margin-right: auto;
padding: 0;
text-align: left;
color: var(--vscode-foreground);
max-width: 100%;
position: relative;
}
.message-actions {
display: flex;
gap: 8px;
margin-top: 12px;
margin-left: 10px;
opacity: 0.85;
transition: opacity 0.2s ease;
}
.message-actions:hover {
opacity: 1;
}
.action-btn {
background: transparent;
border: none;
cursor: pointer;
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
color: var(--vscode-foreground);
opacity: 0.9;
transition: opacity 0.2s ease;
position: relative;
}
.action-btn:hover {
opacity: 1;
}
.action-btn svg {
width: 14px;
height: 14px;
}
.action-btn.active {
color: var(--vscode-button-background);
opacity: 1;
}
.action-btn .action-tooltip {
visibility: hidden;
width: auto;
background: #1e1e1e;
color: #ffffff;
text-align: center;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.2);
padding: 4px 8px;
position: absolute;
z-index: 1000;
bottom: 125%;
left: 50%;
transform: translateX(-50%) translateY(5px);
opacity: 0;
transition: all 0.2s ease;
font-size: 12px;
white-space: nowrap;
pointer-events: none;
}
.action-btn .action-tooltip::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #1e1e1e transparent transparent transparent;
}
.action-btn .action-tooltip::before {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -6px;
border-width: 6px;
border-style: solid;
border-color: rgba(255, 255, 255, 0.2) transparent transparent transparent;
z-index: -1;
}
.action-btn:hover .action-tooltip {
visibility: visible;
opacity: 1;
transform: translateX(-50%) translateY(0);
}
.input-area {
border-top: 1px solid var(--vscode-panel-border);
padding-top: 15px;
flex-shrink: 0;
}
.input-group {
display: flex;
flex-direction: column;
gap: 10px;
background: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border);
border-radius: 8px;
padding: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 2px 6px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.input-group:hover {
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2), 0 3px 8px rgba(0, 0, 0, 0.15);
}
.input-group:focus-within {
border-color: var(--vscode-focusBorder);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25), 0 3px 10px rgba(0, 0, 0, 0.2);
}
.input-wrapper {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
}
.input-bottom-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.mode-selector {
display: flex;
align-items: center;
position: relative;
}
.input-actions {
display: flex;
align-items: center;
gap: 10px;
}
.mode-selector select {
padding: 2px 4px;
background: transparent;
color: var(--vscode-foreground);
border: none;
cursor: pointer;
font-size: 12px;
outline: none;
}
.mode-selector select:hover {
background: var(--vscode-list-hoverBackground);
}
/* Tooltip 样式 */
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: auto;
background: #1e1e1e;
color: #ffffff;
text-align: center;
border-radius: 6px;
padding: 6px 12px;
position: absolute;
z-index: 1000;
bottom: 150%;
left: 50%;
transform: translateX(-50%) translateY(10px);
opacity: 0;
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
font-size: 12px;
font-weight: 500;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6), 0 2px 4px rgba(0, 0, 0, 0.3);
white-space: nowrap;
letter-spacing: 0.3px;
}
.tooltip .tooltiptext::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -6px;
border-width: 6px;
border-style: solid;
border-color: #1e1e1e transparent transparent transparent;
}
.tooltip .tooltiptext::before {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -7px;
border-width: 7px;
border-style: solid;
border-color: rgba(255, 255, 255, 0.2) transparent transparent transparent;
z-index: -1;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
transform: translateX(-50%) translateY(0);
}
textarea {
flex: 1;
width: 100%;
padding: 10px;
background: var(--vscode-input-background);
background: transparent;
color: var(--vscode-input-foreground);
border: 1px solid var(--vscode-input-border);
border: none;
border-radius: 4px;
font-family: inherit;
resize: none;
min-height: 40px;
max-height: 200px;
outline: none;
box-sizing: border-box;
overflow-y: auto;
line-height: 1.5;
}
/* 简洁的滚动条样式 */
textarea::-webkit-scrollbar {
width: 8px;
}
textarea::-webkit-scrollbar-track {
background: transparent;
}
textarea::-webkit-scrollbar-thumb {
background: rgba(128, 128, 128, 0.5);
border-radius: 4px;
}
textarea::-webkit-scrollbar-button {
display: none;
}
button {
padding: 0 20px;
@ -81,11 +291,36 @@ export function getWebviewContent(iconUri?: string): string {
border-radius: 4px;
cursor: pointer;
}
.optimize-button {
padding: 8px;
background: transparent;
color: var(--vscode-foreground);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.2s ease;
width: 32px;
height: 32px;
}
.optimize-button:hover {
opacity: 0.7;
}
.optimize-button svg {
width: 16px;
height: 16px;
}
.optimize-button-wrapper {
display: flex;
align-items: flex-end;
}
.quick-actions {
display: flex;
gap: 8px;
margin-top: 10px;
flex-wrap: wrap;
flex-shrink: 0;
}
.quick-btn {
padding: 6px 12px;
@ -97,11 +332,12 @@ export function getWebviewContent(iconUri?: string): string {
font-size: 12px;
}
.file-reader-section {
margin-bottom: 20px;
margin-bottom: 15px;
padding: 15px;
background: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border);
border-radius: 8px;
flex-shrink: 0;
}
.file-reader-section h3 {
margin: 0 0 10px 0;
@ -126,7 +362,7 @@ export function getWebviewContent(iconUri?: string): string {
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 4px;
max-height: 300px;
max-height: 120px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
@ -146,12 +382,13 @@ export function getWebviewContent(iconUri?: string): string {
margin-top: 10px;
}
.file-editor-section {
margin-bottom: 20px;
margin-bottom: 15px;
padding: 15px;
background: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border);
border-radius: 8px;
display: none;
flex-shrink: 0;
}
.file-editor-section.active {
display: block;
@ -177,6 +414,130 @@ export function getWebviewContent(iconUri?: string): string {
gap: 10px;
margin-top: 10px;
}
/* 上下文显示样式 */
.context-display {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.context-info {
display: flex;
align-items: center;
gap: 6px;
height: 40px;
background: transparent;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
color: var(--vscode-foreground);
transition: opacity 0.3s ease;
box-shadow: none;
position: relative;
overflow: hidden;
cursor: pointer;
}
.context-info:hover {
opacity: 0.8;
}
.database-icon {
display: flex;
align-items: center;
justify-content: center;
width: 12px;
height: 12px;
position: relative;
}
.db-svg {
width: 100%;
height: 100%;
}
.db-body {
fill: #ffffff;
}
.db-fill {
fill: #409eff;
transition: all 0.3s ease;
}
.context-percentage {
font-size: 14px;
font-weight: 500;
color: var(--vscode-foreground);
text-align: right;
}
/* 上下文信息弹窗样式 */
.context-panel {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
margin-bottom: 8px;
z-index: 1000;
animation: fadeInUp 0.2s ease-out;
display: none;
}
.context-panel.active {
display: block;
}
.context-panel::after {
content: "";
position: absolute;
bottom: -6px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid #ffffff;
}
.context-panel-content {
background: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
backdrop-filter: blur(10px);
min-width: 160px;
}
.context-info-text {
font-size: 12px;
color: #374151;
text-align: center;
margin-bottom: 8px;
white-space: nowrap;
}
.compress-button {
width: 100%;
background: linear-gradient(145deg, #3b82f6 0%, #1d4ed8 100%);
border: 1px solid rgba(59, 130, 246, 0.3);
border-radius: 6px;
color: white;
font-size: 12px;
font-weight: 500;
padding: 6px 12px;
cursor: pointer;
transition: all 0.2s ease;
}
.compress-button:hover {
background: linear-gradient(145deg, #2563eb 0%, #1e40af 100%);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
.compress-button:active {
transform: translateY(0);
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateX(-50%) translateY(10px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
</style>
</head>
<body>
@ -188,32 +549,31 @@ export function getWebviewContent(iconUri?: string): string {
<p>专注于真实FPGA研发的Verilog智能体编程平台</p>
</div>
<div class="file-reader-section">
<h3>📁 文件读取</h3>
<div class="file-input-group">
<input
type="text"
id="filePathInput"
placeholder="输入文件路径(相对或绝对路径)..."
/>
<button onclick="readFile()">读取文件</button>
</div>
<div id="fileContent" class="file-content empty">
文件内容将在这里显示...
</div>
<div id="errorMessage" style="display: none;"></div>
</div>
<div id="fileEditorSection" class="file-editor-section">
<h3>✏️ 编辑文件: <span id="editingFileName"></span></h3>
<textarea id="fileEditorTextarea" class="file-editor-textarea"></textarea>
<div class="editor-actions">
<button onclick="saveFile()">保存修改</button>
<button onclick="cancelEdit()" style="background: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground);">取消</button>
</div>
</div>
<div class="chat-container">
<div class="file-reader-section">
<h3>📁 文件读取</h3>
<div class="file-input-group">
<input
type="text"
id="filePathInput"
placeholder="输入文件路径(相对或绝对路径)..."
/>
<button onclick="readFile()">读取文件</button>
</div>
<div id="fileContent" class="file-content empty">
文件内容将在这里显示...
</div>
<div id="errorMessage" style="display: none;"></div>
</div>
<div id="fileEditorSection" class="file-editor-section">
<h3>✏️ 编辑文件: <span id="editingFileName"></span></h3>
<textarea id="fileEditorTextarea" class="file-editor-textarea"></textarea>
<div class="editor-actions">
<button onclick="saveFile()">保存修改</button>
<button onclick="cancelEdit()" style="background: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground);">取消</button>
</div>
</div>
<div id="messages" class="messages">
<div class="message bot-message">
👋 你好!我是 IC Coder 助手,可以帮你生成代码、回答问题。
@ -228,12 +588,72 @@ export function getWebviewContent(iconUri?: string): string {
<div class="input-area">
<div class="input-group">
<textarea
id="messageInput"
placeholder="输入您的问题..."
onkeydown="if(event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); sendMessage(); }"
></textarea>
<button onclick="sendMessage()">发送</button>
<div class="input-wrapper">
<textarea
id="messageInput"
placeholder="输入您的问题..."
onkeydown="if(event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); sendMessage(); }"
></textarea>
<div class="input-bottom-row">
<div class="mode-selector">
<div class="tooltip">
<select id="modeSelect">
<option value="agent" selected>Agent</option>
<option value="ask">Ask</option>
<option value="plan">Plan</option>
</select>
<span class="tooltiptext">切换模型</span>
</div>
</div>
<div class="input-actions">
<!-- 上下文显示 -->
<div class="context-display">
<div class="context-info" onclick="toggleContextPanel()">
<div class="database-icon">
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" class="db-svg">
<!-- 数据库容器主体 - 底层灰色 -->
<path d="M870.4 57.6C780.8 19.2 652.8 0 512 0 371.2 0 243.2 19.2 153.6 57.6 51.2 102.4 0 153.6 0 211.2l0 595.2c0 57.6 51.2 115.2 153.6 153.6C243.2 1004.8 371.2 1024 512 1024c140.8 0 268.8-19.2 358.4-57.6 96-38.4 153.6-96 153.6-153.6L1024 211.2C1024 153.6 972.8 102.4 870.4 57.6L870.4 57.6zM812.8 320C729.6 352 614.4 364.8 512 364.8 403.2 364.8 294.4 352 211.2 320 115.2 294.4 70.4 256 70.4 211.2c0-38.4 51.2-76.8 140.8-108.8C294.4 76.8 403.2 64 512 64c102.4 0 217.6 19.2 300.8 44.8 89.6 32 140.8 70.4 140.8 108.8C953.6 256 908.8 294.4 812.8 320L812.8 320zM819.2 505.6C736 531.2 620.8 550.4 512 550.4c-108.8 0-217.6-19.2-307.2-44.8C115.2 473.6 64 435.2 64 396.8L64 326.4C128 352 172.8 384 243.2 396.8 326.4 416 416 428.8 512 428.8c96 0 185.6-12.8 268.8-32C851.2 384 896 352 960 326.4l0 76.8C960 435.2 908.8 473.6 819.2 505.6L819.2 505.6zM819.2 710.4c-83.2 25.6-198.4 44.8-307.2 44.8-108.8 0-217.6-19.2-307.2-44.8C115.2 684.8 64 646.4 64 601.6L64 505.6c64 32 108.8 57.6 179.2 76.8C326.4 601.6 416 614.4 512 614.4c96 0 185.6-12.8 268.8-32C851.2 563.2 896 537.6 960 505.6l0 96C960 646.4 908.8 684.8 819.2 710.4L819.2 710.4zM512 960c-108.8 0-217.6-19.2-307.2-44.8C115.2 889.6 64 851.2 64 812.8l0-96c64 32 108.8 57.6 179.2 76.8 76.8 19.2 172.8 32 262.4 32 96 0 185.6-12.8 268.8-32 76.8-19.2 121.6-44.8 185.6-76.8l0 96c0 38.4-51.2 76.8-140.8 108.8C736 947.2 614.4 960 512 960L512 960z" fill="#94a3b8" class="db-body"/>
<!-- 填充进度效果 - 从下往上填充蓝色 -->
<defs>
<mask id="fill-mask">
<rect x="0" y="0" width="1024" height="1024" id="fillRect" fill="white"/>
</mask>
</defs>
<g mask="url(#fill-mask)">
<path d="M870.4 57.6C780.8 19.2 652.8 0 512 0 371.2 0 243.2 19.2 153.6 57.6 51.2 102.4 0 153.6 0 211.2l0 595.2c0 57.6 51.2 115.2 153.6 153.6C243.2 1004.8 371.2 1024 512 1024c140.8 0 268.8-19.2 358.4-57.6 96-38.4 153.6-96 153.6-153.6L1024 211.2C1024 153.6 972.8 102.4 870.4 57.6L870.4 57.6zM812.8 320C729.6 352 614.4 364.8 512 364.8 403.2 364.8 294.4 352 211.2 320 115.2 294.4 70.4 256 70.4 211.2c0-38.4 51.2-76.8 140.8-108.8C294.4 76.8 403.2 64 512 64c102.4 0 217.6 19.2 300.8 44.8 89.6 32 140.8 70.4 140.8 108.8C953.6 256 908.8 294.4 812.8 320L812.8 320zM819.2 505.6C736 531.2 620.8 550.4 512 550.4c-108.8 0-217.6-19.2-307.2-44.8C115.2 473.6 64 435.2 64 396.8L64 326.4C128 352 172.8 384 243.2 396.8 326.4 416 416 428.8 512 428.8c96 0 185.6-12.8 268.8-32C851.2 384 896 352 960 326.4l0 76.8C960 435.2 908.8 473.6 819.2 505.6L819.2 505.6zM819.2 710.4c-83.2 25.6-198.4 44.8-307.2 44.8-108.8 0-217.6-19.2-307.2-44.8C115.2 684.8 64 646.4 64 601.6L64 505.6c64 32 108.8 57.6 179.2 76.8C326.4 601.6 416 614.4 512 614.4c96 0 185.6-12.8 268.8-32C851.2 563.2 896 537.6 960 505.6l0 96C960 646.4 908.8 684.8 819.2 710.4L819.2 710.4zM512 960c-108.8 0-217.6-19.2-307.2-44.8C115.2 889.6 64 851.2 64 812.8l0-96c64 32 108.8 57.6 179.2 76.8 76.8 19.2 172.8 32 262.4 32 96 0 185.6-12.8 268.8-32 76.8-19.2 121.6-44.8 185.6-76.8l0 96c0 38.4-51.2 76.8-140.8 108.8C736 947.2 614.4 960 512 960L512 960z" fill="#409eff" class="db-fill"/>
</g>
</svg>
</div>
<span class="context-percentage" id="contextPercentage">0%</span>
</div>
<!-- 上下文信息弹窗 -->
<div id="contextPanel" class="context-panel">
<div class="context-panel-content">
<div class="context-info-text" id="contextInfoText">
0k / 200k 已用上下文
</div>
<button class="compress-button" onclick="compressConversation()">
压缩会话
</button>
</div>
</div>
</div>
<!-- 一键优化按钮 -->
<div class="tooltip">
<button id="optimizeButton" class="optimize-button" onclick="handleOptimize()">
<svg t="1765867478136" id="optimizeIcon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2314"><path d="M490.048929 399.773864c7.042381-21.120144 36.85976-21.120144 43.902142 0l41.273372 123.957105A184.967743 184.967743 0 0 0 692.274156 640.713687l123.890111 41.273373c21.119144 7.042381 21.119144 36.85976 0 43.902141L692.207161 767.162574A184.967743 184.967743 0 0 0 575.224443 884.212286l-41.273372 123.890111A23.09997 23.09997 0 0 1 512 1024c-9.983123 0-18.838344-6.409437-21.951071-15.897603L448.775557 884.145292A184.946745 184.946745 0 0 0 331.792839 767.162574L207.836733 725.889201A23.09997 23.09997 0 0 1 191.93813 703.93813c0-9.983123 6.409437-18.838344 15.897603-21.95107l123.957106-41.273373A184.946745 184.946745 0 0 0 448.775557 523.730969zM242.840657 73.466543A13.888779 13.888779 0 0 1 256.022498 63.94338c5.987474 0 11.299007 3.839663 13.182841 9.523163l24.767824 74.360464a111.070238 111.070238 0 0 0 70.19983 70.20083l74.360464 24.767824A13.888779 13.888779 0 0 1 448.05662 255.977502c0 5.987474-3.839663 11.299007-9.523163 13.182841l-74.360464 24.767823a110.947249 110.947249 0 0 0-70.20083 70.199831l-24.767824 74.360464A13.888779 13.888779 0 0 1 256.022498 448.011624a13.888779 13.888779 0 0 1-13.182841-9.523163l-24.767823-74.360464a110.947249 110.947249 0 0 0-70.199831-70.20083l-74.360464-24.767824A13.888779 13.888779 0 0 1 63.988376 255.977502c0-5.987474 3.839663-11.299007 9.523163-13.182841l74.360464-24.767824a110.947249 110.947249 0 0 0 70.20083-70.19983zM695.213897 6.335443a9.283184 9.283184 0 0 1 17.538459 0L729.260905 55.86509a73.889506 73.889506 0 0 0 46.843883 46.843883l49.530646 16.509549a9.283184 9.283184 0 0 1 0 17.538458L776.106787 153.266529a73.9585 73.9585 0 0 0-46.843882 46.843883l-16.509549 49.530647a9.283184 9.283184 0 0 1-17.538459 0L678.705348 200.112412a73.9585 73.9585 0 0 0-46.843883-46.843883l-49.468652-16.509549a9.283184 9.283184 0 0 1 0-17.538458l49.535646-16.509549a73.897505 73.897505 0 0 0 46.842883-46.843883L695.213897 6.397438z m0 0" p-id="2315" fill="#409eff"></path></svg>
</button>
<span class="tooltiptext" id="optimizeTooltip">一键优化</span>
</div>
<button onclick="sendMessage()">发送</button>
</div>
</div>
</div>
</div>
</div>
</div>
@ -242,6 +662,7 @@ export function getWebviewContent(iconUri?: string): string {
const vscode = acquireVsCodeApi();
const messagesEl = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
const modeSelect = document.getElementById('modeSelect');
const filePathInput = document.getElementById('filePathInput');
const fileContentEl = document.getElementById('fileContent');
const errorMessageEl = document.getElementById('errorMessage');
@ -250,15 +671,69 @@ export function getWebviewContent(iconUri?: string): string {
const editingFileName = document.getElementById('editingFileName');
let currentEditingFile = null;
let originalText = ''; // 保存原始文本用于撤回
let isOptimized = false; // 标记是否已优化
function sendMessage() {
const text = messageInput.value.trim();
if (!text) return;
const mode = modeSelect.value;
addMessage(text, 'user');
vscode.postMessage({ command: 'sendMessage', text: text });
vscode.postMessage({ command: 'sendMessage', text: text, mode: mode });
messageInput.value = '';
autoResizeTextarea(); // 重置输入框高度
messageInput.focus();
// 重置优化状态
resetOptimizeButton();
}
function handleOptimize() {
if (isOptimized) {
// 撤回操作
messageInput.value = originalText;
resetOptimizeButton();
} else {
// 优化操作
originalText = messageInput.value; // 保存原始文本
// 使用死数据替换输入框内容
const optimizedTexts = [
'请帮我优化这段代码,提高性能和可读性',
'请分析这个问题并给出最佳解决方案',
'请帮我重构这段代码,使其更加简洁高效',
'请检查代码中的潜在问题并提供改进建议'
];
const randomText = optimizedTexts[Math.floor(Math.random() * optimizedTexts.length)];
messageInput.value = randomText;
// 切换到撤回状态
isOptimized = true;
updateOptimizeButton();
}
messageInput.focus();
}
function updateOptimizeButton() {
const optimizeIcon = document.getElementById('optimizeIcon');
const optimizeTooltip = document.getElementById('optimizeTooltip');
// 切换为撤回图标
optimizeIcon.innerHTML = '<path d="M581.056 288.32H232.96l108.352-102.208c15.552-15.744 19.456-31.104 4.16-46.208-16.064-15.872-32.576-15.808-48.64 0l-145.92 144.768c-8.768 8.832-23.488 20.608-22.08 32.448l0.64 4.8-0.64 4.864c-1.344 11.776 6.4 18.24 14.848 26.816l147.648 145.216c16.064 15.808 38.08 20.992 54.144 5.12 15.296-15.104 3.84-38.208-11.328-53.504L233.152 353.6 581.056 352c126.464 0 250.944 111.488 250.944 236.16C832 712.832 707.52 832 581.056 832H246.4c-22.592 0-29.696 9.6-29.696 32.256s7.04 31.744 29.696 31.744H581.12C755.136 896 896 757.696 896 588.16c0-169.408-140.8-299.84-314.944-299.84z" fill="currentColor"/><path d="M323.392 192a32 32 0 1 1 0-64 32 32 0 0 1 0 64zM320.192 514.048a32 32 0 1 1 0-64 32 32 0 0 1 0 64zM237.824 896a32 32 0 1 1 0-64 32 32 0 0 1 0 64z" fill="currentColor"/>';
optimizeTooltip.textContent = '撤回';
}
function resetOptimizeButton() {
const optimizeIcon = document.getElementById('optimizeIcon');
const optimizeTooltip = document.getElementById('optimizeTooltip');
// 切换回优化图标(星星图标)
optimizeIcon.innerHTML = '<path d="M490.048929 399.773864c7.042381-21.120144 36.85976-21.120144 43.902142 0l41.273372 123.957105A184.967743 184.967743 0 0 0 692.274156 640.713687l123.890111 41.273373c21.119144 7.042381 21.119144 36.85976 0 43.902141L692.207161 767.162574A184.967743 184.967743 0 0 0 575.224443 884.212286l-41.273372 123.890111A23.09997 23.09997 0 0 1 512 1024c-9.983123 0-18.838344-6.409437-21.951071-15.897603L448.775557 884.145292A184.946745 184.946745 0 0 0 331.792839 767.162574L207.836733 725.889201A23.09997 23.09997 0 0 1 191.93813 703.93813c0-9.983123 6.409437-18.838344 15.897603-21.95107l123.957106-41.273373A184.946745 184.946745 0 0 0 448.775557 523.730969zM242.840657 73.466543A13.888779 13.888779 0 0 1 256.022498 63.94338c5.987474 0 11.299007 3.839663 13.182841 9.523163l24.767824 74.360464a111.070238 111.070238 0 0 0 70.19983 70.20083l74.360464 24.767824A13.888779 13.888779 0 0 1 448.05662 255.977502c0 5.987474-3.839663 11.299007-9.523163 13.182841l-74.360464 24.767823a110.947249 110.947249 0 0 0-70.20083 70.199831l-24.767824 74.360464A13.888779 13.888779 0 0 1 256.022498 448.011624a13.888779 13.888779 0 0 1-13.182841-9.523163l-24.767823-74.360464a110.947249 110.947249 0 0 0-70.199831-70.20083l-74.360464-24.767824A13.888779 13.888779 0 0 1 63.988376 255.977502c0-5.987474 3.839663-11.299007 9.523163-13.182841l74.360464-24.767824a110.947249 110.947249 0 0 0 70.20083-70.19983zM695.213897 6.335443a9.283184 9.283184 0 0 1 17.538459 0L729.260905 55.86509a73.889506 73.889506 0 0 0 46.843883 46.843883l49.530646 16.509549a9.283184 9.283184 0 0 1 0 17.538458L776.106787 153.266529a73.9585 73.9585 0 0 0-46.843882 46.843883l-16.509549 49.530647a9.283184 9.283184 0 0 1-17.538459 0L678.705348 200.112412a73.9585 73.9585 0 0 0-46.843883-46.843883l-49.468652-16.509549a9.283184 9.283184 0 0 1 0-17.538458l49.535646-16.509549a73.897505 73.897505 0 0 0 46.842883-46.843883L695.213897 6.397438z m0 0" fill="#409eff"/>';
optimizeTooltip.textContent = '一键优化';
isOptimized = false;
originalText = '';
}
function readFile() {
@ -309,11 +784,80 @@ export function getWebviewContent(iconUri?: string): string {
function addMessage(text, sender) {
const div = document.createElement('div');
div.className = \`message \${sender}-message\`;
div.textContent = text;
if (sender === 'bot') {
// 创建消息内容
const messageContent = document.createElement('div');
messageContent.textContent = text;
div.appendChild(messageContent);
// 创建操作按钮容器
const actionsDiv = document.createElement('div');
actionsDiv.className = 'message-actions';
// 复制按钮
const copyBtn = document.createElement('button');
copyBtn.className = 'action-btn';
copyBtn.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M761.088 715.3152a38.7072 38.7072 0 0 1 0-77.4144 37.4272 37.4272 0 0 0 37.4272-37.4272V265.0112a37.4272 37.4272 0 0 0-37.4272-37.4272H425.6256a37.4272 37.4272 0 0 0-37.4272 37.4272 38.7072 38.7072 0 1 1-77.4144 0 115.0976 115.0976 0 0 1 114.8416-114.8416h335.4624a115.0976 115.0976 0 0 1 114.8416 114.8416v335.4624a115.0976 115.0976 0 0 1-114.8416 114.8416z" fill="currentColor"/><path d="M589.4656 883.0976H268.1856a121.1392 121.1392 0 0 1-121.2928-121.2928v-322.56a121.1392 121.1392 0 0 1 121.2928-121.344h321.28a121.1392 121.1392 0 0 1 121.2928 121.2928v322.56c1.28 67.1232-54.1696 121.344-121.2928 121.344zM268.1856 395.3152a43.52 43.52 0 0 0-43.8784 43.8784v322.56a43.52 43.52 0 0 0 43.8784 43.8784h321.28a43.52 43.52 0 0 0 43.8784-43.8784v-322.56a43.52 43.52 0 0 0-43.8784-43.8784z" fill="currentColor"/></svg><span class="action-tooltip">复制</span>\`;
copyBtn.onclick = () => copyMessage(text, copyBtn);
// 点赞按钮
const likeBtn = document.createElement('button');
likeBtn.className = 'action-btn';
likeBtn.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M923.5 411.2c-28.6-33.9-72.1-53-116.4-51.1h-68c6.4-31.6 10.1-63.9 11.2-96v-0.8c-0.5-60.9-18.7-112-51.2-144-22.6-22.2-50.8-33.7-81.5-33.3-38.3 0-69.1 11.5-91.7 34.2-26.5 26.5-39.9 66.8-39.8 119.6 0.1 40.1-19.4 83.4-52.1 115.9-32 31.8-71.7 49.3-111.8 49.3H295.6c-3 0-6 0.3-8.9 0.8v-1.2H140.8c-39.7 0-72.2 32.5-72.2 72.2v392.9c0 39.7 32.5 72.2 72.2 72.2h146.8v-0.6c2.9 0.4 5.9 0.7 8.9 0.7h464.7c33.3-0.8 65.6-13 91.1-34.4s43.1-51.1 49.6-83.8l52.3-289.1c9.4-43.4-2.1-89.6-30.7-123.5zM147.7 843.7v-344c0-9 7.3-16.3 16.3-16.3h70.4V860H164c-9 0-16.3-7.3-16.3-16.3z m726.4-324.9l-0.2 0.6-51.7 290.3c-6.7 29.1-32.3 50.2-62.2 51.3l-4.9 0.2-0.4 0.3h-440V486h7.3c61.4 0 121-25.7 168.1-72.4 48.6-48.2 76.5-111.7 76.5-174.2-0.1-31.5 4.9-51.8 15.3-62.2 7.4-7.4 18.7-10.8 35.8-10.8h0.2c9-0.1 17.4 3.6 24.9 11 16.3 16.2 25.7 47.3 25.8 85.4-1.2 41.8-7.9 83.3-19.9 123.4l-21.6 54.3h181.5c24.5-0.6 48.2 8.9 65.1 26.1 16.9 17.2 25.3 40.8 23 64.9z" fill="currentColor"/></svg><span class="action-tooltip">点赞</span>\`;
likeBtn.onclick = () => toggleLike(likeBtn);
// 点踩按钮
const dislikeBtn = document.createElement('button');
dislikeBtn.className = 'action-btn';
dislikeBtn.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M360 640c60.992 40.107 88 87.381 88 149.333 0 5.611 0.427 12.864 1.579 21.462a174.933 174.933 0 0 0 11.2 42.666c19.584 48.192 60.864 83.008 119.018 85.12 28.843 2.56 60.886-3.584 91.414-25.045 58.709-41.237 81.706-117.248 69.973-230.87h48.15V640v42.667c17.173 0 38.4-2.475 61.823-10.667 66.304-23.125 107.627-84.117 84.928-162.816l-53.674-254.55a213.333 213.333 0 0 0-208.747-169.3H207.019a85.333 85.333 0 0 0-85.078 78.783l-29.546 384A85.333 85.333 0 0 0 177.493 640H360z m-61.333-109.333v24H177.493l29.526-384h91.648v360z m85.333-360h289.664a128 128 0 0 1 125.227 101.589l54.442 258.07c21.334 67.007-64 67.007-64 67.007H640c64 277.334-54.613 256-54.613 256-52.054 0-52.054-64-52.054-64 0-92.8-43.264-167.082-129.77-222.805A42.667 42.667 0 0 1 384 530.667v-360z" fill="currentColor"/></svg><span class="action-tooltip">点踩</span>\`;
dislikeBtn.onclick = () => toggleDislike(dislikeBtn);
actionsDiv.appendChild(copyBtn);
actionsDiv.appendChild(likeBtn);
actionsDiv.appendChild(dislikeBtn);
div.appendChild(actionsDiv);
} else {
div.textContent = text;
}
messagesEl.appendChild(div);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
function copyMessage(text, button) {
navigator.clipboard.writeText(text).then(() => {
const originalHTML = button.innerHTML;
button.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474c-6.1-7.7-15.3-12.2-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1 0.4-12.8-6.3-12.8z" fill="currentColor"/></svg>\`;
setTimeout(() => {
button.innerHTML = originalHTML;
}, 2000);
});
}
function toggleLike(button) {
const isActive = button.classList.contains('active');
// 移除所有同级按钮的 active 状态
const parent = button.parentElement;
parent.querySelectorAll('.action-btn').forEach(btn => btn.classList.remove('active'));
if (!isActive) {
button.classList.add('active');
}
}
function toggleDislike(button) {
const isActive = button.classList.contains('active');
// 移除所有同级按钮的 active 状态
const parent = button.parentElement;
parent.querySelectorAll('.action-btn').forEach(btn => btn.classList.remove('active'));
if (!isActive) {
button.classList.add('active');
}
}
function openFileEditor(filePath, content) {
currentEditingFile = filePath;
editingFileName.textContent = filePath;
@ -350,6 +894,59 @@ export function getWebviewContent(iconUri?: string): string {
editingFileName.textContent = '';
}
// 上下文面板相关函数
function toggleContextPanel() {
const contextPanel = document.getElementById('contextPanel');
if (contextPanel.classList.contains('active')) {
contextPanel.classList.remove('active');
} else {
contextPanel.classList.add('active');
}
}
function compressConversation() {
// 发送压缩会话请求
vscode.postMessage({ command: 'compressConversation' });
addMessage('正在压缩会话...', 'bot');
// 关闭面板
const contextPanel = document.getElementById('contextPanel');
contextPanel.classList.remove('active');
}
function updateContextDisplay(currentTokens, maxTokens) {
const percentage = Math.min(Math.round((currentTokens / maxTokens) * 100), 100);
// 更新百分比显示
document.getElementById('contextPercentage').textContent = percentage + '%';
// 更新详细信息
const currentK = Math.round((currentTokens / 1000) * 10) / 10;
const maxK = Math.round(maxTokens / 1000);
document.getElementById('contextInfoText').textContent = \`\${currentK}k / \${maxK}k 已用上下文\`;
// 更新SVG填充效果从下往上填充
const fillRect = document.getElementById('fillRect');
if (fillRect) {
const fillHeight = (1024 * percentage) / 100;
const fillY = 1024 - fillHeight;
fillRect.setAttribute('y', fillY.toString());
fillRect.setAttribute('height', fillHeight.toString());
}
}
// 点击外部关闭上下文面板
document.addEventListener('click', (event) => {
const contextDisplay = document.querySelector('.context-display');
const contextPanel = document.getElementById('contextPanel');
if (contextPanel && contextPanel.classList.contains('active')) {
if (!contextDisplay.contains(event.target)) {
contextPanel.classList.remove('active');
}
}
});
window.addEventListener('message', event => {
const message = event.data;
@ -382,6 +979,18 @@ export function getWebviewContent(iconUri?: string): string {
}
});
// 自动调整 textarea 高度
function autoResizeTextarea() {
messageInput.style.height = 'auto';
messageInput.style.height = messageInput.scrollHeight + 'px';
}
// 监听输入事件,自动调整高度
messageInput.addEventListener('input', autoResizeTextarea);
// 初始化时调整一次高度
autoResizeTextarea();
messageInput.focus();
</script>
</body>

View File

@ -0,0 +1,183 @@
# Iverilog 工具下载和安装说明
## 重要提示
由于网络限制,需要手动下载 iverilog 工具并放置到插件包中。
## 快速开始
### 步骤 1下载 Iverilog
**Windows x64 用户:**
1. 访问官方下载页面http://bleyer.org/icarus/
2. 下载文件:`iverilog-v12-20220611-x64_setup.exe` (约 18.2 MB)
3. 保存到任意位置
**备用下载地址:**
- GitHub Releases: https://github.com/steveicarus/iverilog/releases
- SourceForge: https://sourceforge.net/projects/iverilog/
### 步骤 2安装到临时目录
1. 运行下载的安装程序 `iverilog-v12-20220611-x64_setup.exe`
2. 选择安装路径(建议:`C:\iverilog-temp`
3. 完成安装
### 步骤 3复制文件到插件目录
**需要复制的文件:**
#### A. 可执行文件(从 `C:\iverilog-temp\bin\` 复制到 `tools\iverilog\bin\`
```
iverilog.exe # Verilog 编译器
vvp.exe # Verilog 仿真器
```
#### B. 依赖的 DLL 文件(从 `C:\iverilog-temp\bin\` 复制到 `tools\iverilog\bin\`
```
libgcc_s_seh-1.dll
libwinpthread-1.dll
libstdc++-6.dll
```
如果运行时提示缺少其他 DLL也需要从安装目录复制。
#### C. 库文件目录(重要!)
将整个 `C:\iverilog-temp\lib\` 目录复制到 `tools\iverilog\lib\`
最终目录结构:
```
tools\iverilog\lib\ivl\
├── system.vpi
├── v2005_math.vpi
├── vhdl_sys.vpi
└── ... (其他 .vpi 和 .vpl 文件)
```
### 步骤 4验证安装
在命令行中运行:
```bash
cd "D:\IC Coder Plugin\ic-coder\tools\iverilog\bin"
.\iverilog.exe -V
```
应该看到版本信息:
```
Icarus Verilog version 12.0 (stable) (s20220611-xxx)
```
### 步骤 5清理
安装完成后,可以:
1. 卸载临时安装的 iverilog通过控制面板
2. 删除临时安装目录 `C:\iverilog-temp`
## 最终目录结构
```
D:\IC Coder Plugin\ic-coder\
└── tools\
└── iverilog\
├── bin\
│ ├── iverilog.exe
│ ├── vvp.exe
│ ├── libgcc_s_seh-1.dll
│ ├── libwinpthread-1.dll
│ └── libstdc++-6.dll
├── lib\
│ └── ivl\
│ ├── system.vpi
│ ├── v2005_math.vpi
│ └── ... (其他库文件)
├── README.md
├── INSTALL.md
└── DOWNLOAD_INSTRUCTIONS.md (本文件)
```
## 文件大小参考
- `iverilog.exe`: ~2 MB
- `vvp.exe`: ~1 MB
- DLL 文件: ~1-2 MB
- lib/ivl/ 目录: ~3-5 MB
- **总计**: 约 7-10 MB
## 自动化脚本(可选)
如果你已经安装了 iverilog可以使用以下 PowerShell 脚本自动复制文件:
```powershell
# 设置路径
$iverilogInstallPath = "C:\iverilog" # 修改为你的安装路径
$pluginToolsPath = "D:\IC Coder Plugin\ic-coder\tools\iverilog"
# 创建目录
New-Item -ItemType Directory -Force -Path "$pluginToolsPath\bin"
New-Item -ItemType Directory -Force -Path "$pluginToolsPath\lib"
# 复制可执行文件
Copy-Item "$iverilogInstallPath\bin\iverilog.exe" "$pluginToolsPath\bin\"
Copy-Item "$iverilogInstallPath\bin\vvp.exe" "$pluginToolsPath\bin\"
# 复制 DLL 文件
Copy-Item "$iverilogInstallPath\bin\*.dll" "$pluginToolsPath\bin\"
# 复制库文件
Copy-Item "$iverilogInstallPath\lib\*" "$pluginToolsPath\lib\" -Recurse
Write-Host "复制完成!"
```
保存为 `copy-iverilog.ps1` 并在 PowerShell 中运行。
## 许可证信息
Icarus Verilog 使用 **GPL v2+** 许可证,允许自由分发。
- 官方网站http://iverilog.icarus.com/
- GitHubhttps://github.com/steveicarus/iverilog
- 许可证文本https://github.com/steveicarus/iverilog/blob/master/COPYING
## 需要帮助?
如果遇到问题,请:
1. 检查是否复制了所有必需的文件
2. 确认 DLL 文件都在 bin 目录中
3. 验证 lib/ivl 目录包含所有 .vpi 文件
4. 查看插件的错误日志
## 其他平台
### macOS
```bash
# 使用 Homebrew 安装
brew install icarus-verilog
# 复制文件到插件目录
cp /usr/local/bin/iverilog "tools/iverilog/bin/"
cp /usr/local/bin/vvp "tools/iverilog/bin/"
cp -r /usr/local/lib/ivl "tools/iverilog/lib/"
```
### Linux
```bash
# Ubuntu/Debian
sudo apt-get install iverilog
# 复制文件到插件目录
cp /usr/bin/iverilog "tools/iverilog/bin/"
cp /usr/bin/vvp "tools/iverilog/bin/"
cp -r /usr/lib/ivl "tools/iverilog/lib/"
```
---
**注意**:完成上述步骤后,插件即可使用内置的 iverilog 工具进行 Verilog 编译和 VCD 文件生成。

131
tools/iverilog/INSTALL.md Normal file
View File

@ -0,0 +1,131 @@
# Iverilog 工具安装指南
## 自动安装(推荐)
插件会自动使用打包在 `tools/iverilog/bin/` 目录中的 iverilog 工具。
## 手动安装步骤
如果需要手动更新或安装 iverilog 工具,请按照以下步骤操作:
### Windows x64
1. **下载安装包**
- 访问http://bleyer.org/icarus/
- 下载:`iverilog-v12-20220611-x64_setup.exe` (18.2MB)
- 或直接下载http://bleyer.org/icarus/iverilog-v12-20220611-x64_setup.exe
2. **安装到临时目录**
- 运行安装程序
- 选择安装路径(例如:`C:\iverilog-temp`
- 完成安装
3. **复制必要文件**
将以下文件从安装目录复制到 `tools/iverilog/bin/`
**核心可执行文件:**
- `iverilog.exe` - Verilog 编译器
- `vvp.exe` - Verilog 仿真器
**依赖的 DLL 文件:**
- `libgcc_s_seh-1.dll`
- `libwinpthread-1.dll`
- `libstdc++-6.dll`
- 以及其他可能需要的 DLL 文件
**库文件目录(重要):**
- 复制整个 `lib/ivl/` 目录到 `tools/iverilog/lib/ivl/`
- 这个目录包含 iverilog 的标准库和模块
4. **目录结构**
最终的目录结构应该是:
```
tools/
└── iverilog/
├── bin/
│ ├── iverilog.exe
│ ├── vvp.exe
│ ├── libgcc_s_seh-1.dll
│ ├── libwinpthread-1.dll
│ └── libstdc++-6.dll
├── lib/
│ └── ivl/
│ ├── system.vpi
│ ├── v2005_math.vpi
│ └── ... (其他库文件)
└── README.md
```
5. **清理**
- 可以卸载临时安装的 iverilog
- 删除临时安装目录
### 验证安装
在命令行中运行:
```bash
cd "D:\IC Coder Plugin\ic-coder\tools\iverilog\bin"
.\iverilog.exe -V
```
应该看到类似输出:
```
Icarus Verilog version 12.0 (stable) (s20220611-xxx)
```
## 文件清单
### 必需文件(约 5-10 MB
**bin/ 目录:**
- iverilog.exe (~2MB)
- vvp.exe (~1MB)
- libgcc_s_seh-1.dll
- libwinpthread-1.dll
- libstdc++-6.dll
**lib/ivl/ 目录:**
- system.vpi
- v2005_math.vpi
- vhdl_sys.vpi
- vhdl_textio.vpi
- va_math.vpi
- 以及其他 .vpi 和 .vpl 文件
## 许可证
Icarus Verilog 使用 GPL v2+ 许可证。
- 官方网站http://iverilog.icarus.com/
- 源代码https://github.com/steveicarus/iverilog
- 许可证https://github.com/steveicarus/iverilog/blob/master/COPYING
## 注意事项
1. **文件大小**:完整的 iverilog 工具约 5-10 MB不包含 GTKWave
2. **依赖项**:确保复制所有必需的 DLL 文件
3. **库文件**lib/ivl/ 目录是必需的,包含 Verilog 标准库
4. **版本**:推荐使用 v12.0 或更高版本
## 故障排除
### 问题:运行时提示缺少 DLL
**解决方案**
- 使用 Dependency Walker 或 Dependencies.exe 检查缺少的 DLL
- 从 iverilog 安装目录复制缺少的 DLL 到 bin/ 目录
### 问题:编译时提示找不到标准库
**解决方案**
- 确保 lib/ivl/ 目录存在且包含所有 .vpi 文件
- 检查 iverilog 是否能找到库文件路径
### 问题vvp 运行失败
**解决方案**
- 确保 vvp.exe 和相关 DLL 都在 bin/ 目录中
- 检查是否有权限问题

247
tools/iverilog/README.md Normal file
View File

@ -0,0 +1,247 @@
# Iverilog 工具集成说明
本插件**已集成** Icarus Verilog (iverilog) 工具,用于 Verilog 代码的编译和仿真,以及 VCD 波形文件的生成。
## 目录结构
```
tools/
└── iverilog/
├── bin/ # 可执行文件目录(已包含 iverilog 工具)
│ ├── iverilog.exe # Verilog 编译器 (Windows)
│ ├── vvp.exe # Verilog 仿真器 (Windows)
│ └── ... # 其他依赖文件
└── README.md # 本说明文件
```
## 工具说明
插件包中**已包含** Windows x64 版本的 iverilog 工具,无需额外安装。
- **版本**: Icarus Verilog 11.0 (或更高)
- **平台**: Windows x64
- **许可证**: GPL v2+
### Windows 安装
1. 访问 Icarus Verilog 官方网站http://bleyer.org/icarus/
2. 下载最新的 Windows 安装包例如iverilog-v11-20210204-x64_setup.exe
3. 运行安装程序,按照提示完成安装
4. 安装完成后,将以下文件复制到插件的 `tools/iverilog/bin/` 目录:
- `iverilog.exe`
- `vvp.exe`
- 以及相关的 DLL 文件(如果有)
**或者**,将 iverilog 安装目录添加到系统 PATH 环境变量中,插件会自动使用系统安装的版本。
### macOS 安装
使用 Homebrew 安装:
```bash
brew install icarus-verilog
```
安装完成后iverilog 会自动添加到系统 PATH 中,插件可以直接使用。
**可选**:如果想将 iverilog 打包到插件中,可以将以下文件复制到 `tools/iverilog/bin/`
- `/usr/local/bin/iverilog`
- `/usr/local/bin/vvp`
### Linux 安装
#### Ubuntu/Debian
```bash
sudo apt-get update
sudo apt-get install iverilog
```
#### Fedora/CentOS
```bash
sudo yum install iverilog
```
#### Arch Linux
```bash
sudo pacman -S iverilog
```
安装完成后iverilog 会自动添加到系统 PATH 中,插件可以直接使用。
## 使用方法
### 1. 准备 Verilog 项目
确保您的项目包含以下文件:
- **顶层模块文件**:包含您的设计代码(例如:`counter.v`
- **Testbench 文件**:包含测试代码,文件名通常包含 `tb``test`(例如:`counter_tb.v`
### 2. Testbench 要求
为了生成 VCD 波形文件,您的 testbench 必须包含以下语句:
```verilog
module counter_tb;
// ... 信号声明 ...
initial begin
// 生成 VCD 文件
$dumpfile("output.vcd"); // 指定 VCD 文件名
$dumpvars(0, counter_tb); // 记录所有信号
// ... 测试代码 ...
#1000 $finish; // 结束仿真
end
// ... 其他测试代码 ...
endmodule
```
### 3. 生成 VCD 文件
在 IC Coder 插件的聊天界面中,输入以下任一命令:
- `生成 VCD`
- `创建 VCD`
- `运行仿真`
- `执行仿真`
- `生成波形`
插件会自动:
1. 检查项目文件完整性
2. 使用 iverilog 编译所有 Verilog 文件
3. 使用 vvp 运行仿真
4. 在项目根目录生成 `output.vcd` 文件
### 4. 查看波形
生成的 VCD 文件可以使用以下工具查看:
- **GTKWave**(推荐):开源波形查看器
- Windows: http://gtkwave.sourceforge.net/
- macOS: `brew install gtkwave`
- Linux: `sudo apt-get install gtkwave`
- **其他工具**
- ModelSim
- Vivado
- 在线 VCD 查看器
## 示例项目
### counter.v顶层模块
```verilog
module counter (
input clk,
input rst_n,
output reg [3:0] count
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
count <= 4'b0000;
else
count <= count + 1;
end
endmodule
```
### counter_tb.vTestbench
```verilog
`timescale 1ns/1ps
module counter_tb;
reg clk;
reg rst_n;
wire [3:0] count;
// 实例化被测模块
counter uut (
.clk(clk),
.rst_n(rst_n),
.count(count)
);
// 生成时钟信号
initial begin
clk = 0;
forever #5 clk = ~clk; // 10ns 周期
end
// 测试序列
initial begin
// 生成 VCD 文件
$dumpfile("output.vcd");
$dumpvars(0, counter_tb);
// 初始化
rst_n = 0;
#20;
// 释放复位
rst_n = 1;
#200;
// 结束仿真
$finish;
end
// 监控输出
initial begin
$monitor("Time=%0t rst_n=%b count=%d", $time, rst_n, count);
end
endmodule
```
## 故障排除
### 问题:提示 "iverilog 不可用"
**解决方案**
1. 确认已安装 iverilog
2. 检查 iverilog 是否在系统 PATH 中:
- Windows: 在命令提示符中运行 `iverilog -V`
- macOS/Linux: 在终端中运行 `iverilog -V`
3. 或者将 iverilog 可执行文件复制到 `tools/iverilog/bin/` 目录
### 问题:提示 "项目文件不完整"
**解决方案**
1. 确保项目中至少有一个 `.v``.sv` 文件
2. 确保有 testbench 文件(文件名包含 `tb``test`
3. 确保 testbench 中包含 `$dumpfile``$dumpvars` 语句
### 问题:编译失败
**解决方案**
1. 检查 Verilog 代码语法错误
2. 查看错误输出信息
3. 确保所有模块都正确实例化
### 问题VCD 文件未生成
**解决方案**
1. 确保 testbench 中包含 `$dumpfile("output.vcd")` 语句
2. 确保 testbench 中包含 `$dumpvars` 语句
3. 确保仿真运行了足够的时间(使用 `#时间 $finish;`
## 版本信息
- 推荐 Icarus Verilog 版本v11.0 或更高
- 支持的 Verilog 标准Verilog-1995, Verilog-2001, Verilog-2005, SystemVerilog (部分)
## 相关链接
- Icarus Verilog 官网http://iverilog.icarus.com/
- GTKWave 官网http://gtkwave.sourceforge.net/
- Verilog 教程https://www.asic-world.com/verilog/
## 许可证
Icarus Verilog 是开源软件,遵循 GPL 许可证。

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tools/iverilog/bin/vvp.exe Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,20 @@
@echo off
chcp 65001 >nul
echo =====================================
echo Iverilog 文件复制脚本
echo =====================================
echo.
REM 检查 PowerShell 是否可用
where powershell >nul 2>&1
if %errorlevel% neq 0 (
echo 错误: 未找到 PowerShell
pause
exit /b 1
)
REM 运行 PowerShell 脚本
powershell -ExecutionPolicy Bypass -File "%~dp0copy-iverilog.ps1"
echo.
pause

View File

@ -0,0 +1,136 @@
# Iverilog 文件自动复制脚本
# 用于将已安装的 iverilog 复制到插件目录
param(
[string]$IverilogPath = "C:\iverilog"
)
# 设置插件工具路径
$PluginPath = Split-Path -Parent $PSScriptRoot
$ToolsPath = Join-Path $PSScriptRoot ""
Write-Host "=====================================" -ForegroundColor Cyan
Write-Host "Iverilog 文件复制脚本" -ForegroundColor Cyan
Write-Host "=====================================" -ForegroundColor Cyan
Write-Host ""
# 检查源路径是否存在
if (-not (Test-Path $IverilogPath)) {
Write-Host "错误: 找不到 iverilog 安装目录: $IverilogPath" -ForegroundColor Red
Write-Host ""
Write-Host "请指定正确的 iverilog 安装路径,例如:" -ForegroundColor Yellow
Write-Host " .\copy-iverilog.ps1 -IverilogPath 'C:\Program Files\iverilog'" -ForegroundColor Yellow
Write-Host ""
# 尝试查找常见安装位置
$CommonPaths = @(
"C:\iverilog",
"C:\Program Files\iverilog",
"C:\Program Files (x86)\iverilog",
"$env:LOCALAPPDATA\iverilog"
)
Write-Host "正在搜索常见安装位置..." -ForegroundColor Yellow
foreach ($path in $CommonPaths) {
if (Test-Path $path) {
Write-Host "找到: $path" -ForegroundColor Green
$IverilogPath = $path
break
}
}
if (-not (Test-Path $IverilogPath)) {
Write-Host "未找到 iverilog 安装目录" -ForegroundColor Red
exit 1
}
}
Write-Host "源路径: $IverilogPath" -ForegroundColor Green
Write-Host "目标路径: $ToolsPath" -ForegroundColor Green
Write-Host ""
# 创建目标目录
Write-Host "创建目录结构..." -ForegroundColor Cyan
New-Item -ItemType Directory -Force -Path "$ToolsPath\bin" | Out-Null
New-Item -ItemType Directory -Force -Path "$ToolsPath\lib" | Out-Null
# 复制可执行文件
Write-Host "复制可执行文件..." -ForegroundColor Cyan
$executables = @("iverilog.exe", "vvp.exe")
foreach ($exe in $executables) {
$sourcePath = Join-Path "$IverilogPath\bin" $exe
if (Test-Path $sourcePath) {
Copy-Item $sourcePath "$ToolsPath\bin\" -Force
Write-Host "$exe" -ForegroundColor Green
} else {
Write-Host " ✗ 未找到 $exe" -ForegroundColor Red
}
}
# 复制 DLL 文件
Write-Host "复制 DLL 文件..." -ForegroundColor Cyan
$dlls = Get-ChildItem "$IverilogPath\bin\*.dll" -ErrorAction SilentlyContinue
if ($dlls) {
foreach ($dll in $dlls) {
Copy-Item $dll.FullName "$ToolsPath\bin\" -Force
Write-Host "$($dll.Name)" -ForegroundColor Green
}
} else {
Write-Host " ! 未找到 DLL 文件" -ForegroundColor Yellow
}
# 复制库文件
Write-Host "复制库文件..." -ForegroundColor Cyan
$libPath = Join-Path $IverilogPath "lib"
if (Test-Path $libPath) {
Copy-Item "$libPath\*" "$ToolsPath\lib\" -Recurse -Force
# 统计复制的文件
$vpiFiles = Get-ChildItem "$ToolsPath\lib\ivl\*.vpi" -ErrorAction SilentlyContinue
$vplFiles = Get-ChildItem "$ToolsPath\lib\ivl\*.vpl" -ErrorAction SilentlyContinue
Write-Host " ✓ 复制了 $($vpiFiles.Count) 个 .vpi 文件" -ForegroundColor Green
Write-Host " ✓ 复制了 $($vplFiles.Count) 个 .vpl 文件" -ForegroundColor Green
} else {
Write-Host " ✗ 未找到 lib 目录" -ForegroundColor Red
}
Write-Host ""
Write-Host "=====================================" -ForegroundColor Cyan
Write-Host "验证安装..." -ForegroundColor Cyan
Write-Host "=====================================" -ForegroundColor Cyan
# 验证 iverilog
$iverilogExe = Join-Path "$ToolsPath\bin" "iverilog.exe"
if (Test-Path $iverilogExe) {
Write-Host ""
Write-Host "运行 iverilog -V:" -ForegroundColor Yellow
& $iverilogExe -V
Write-Host ""
} else {
Write-Host "错误: iverilog.exe 未找到" -ForegroundColor Red
}
# 显示文件大小统计
Write-Host "=====================================" -ForegroundColor Cyan
Write-Host "文件大小统计" -ForegroundColor Cyan
Write-Host "=====================================" -ForegroundColor Cyan
$binSize = (Get-ChildItem "$ToolsPath\bin" -Recurse -File | Measure-Object -Property Length -Sum).Sum
$libSize = (Get-ChildItem "$ToolsPath\lib" -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
$totalSize = $binSize + $libSize
Write-Host "bin/ 目录: $([math]::Round($binSize / 1MB, 2)) MB" -ForegroundColor Green
Write-Host "lib/ 目录: $([math]::Round($libSize / 1MB, 2)) MB" -ForegroundColor Green
Write-Host "总计: $([math]::Round($totalSize / 1MB, 2)) MB" -ForegroundColor Cyan
Write-Host ""
Write-Host "=====================================" -ForegroundColor Cyan
Write-Host "复制完成!" -ForegroundColor Green
Write-Host "=====================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "现在可以使用插件的 VCD 生成功能了。" -ForegroundColor Green
Write-Host ""

View File

@ -0,0 +1,134 @@
# Iverilog 测试示例
这个目录包含一个简单的 Verilog 项目示例,用于测试 IC Coder 插件的 VCD 生成功能。
## 文件说明
- **counter.v** - 一个简单的 4 位计数器模块
- **counter_tb.v** - 计数器的测试平台testbench
## 功能说明
### counter.v顶层模块
这是一个同步复位的 4 位计数器:
- **输入**
- `clk` - 时钟信号
- `rst_n` - 低电平有效的复位信号
- **输出**
- `count[3:0]` - 4 位计数值0-15
### counter_tb.v测试平台
测试平台包含:
- 时钟生成器100MHz周期 10ns
- 复位序列(初始复位 20ns
- VCD 波形文件生成
- 信号监控和显示
## 使用方法
### 方法 1使用 IC Coder 插件(推荐)
1. 在 VS Code 中打开这个 `examples` 目录作为工作区
2. 打开 IC Coder 插件面板
3. 在聊天框中输入以下任一命令:
- `生成 VCD`
- `运行仿真`
- `生成波形`
4. 插件会自动编译并运行仿真,生成 `output.vcd` 文件
### 方法 2手动运行用于测试
在命令行中执行:
```bash
# 进入示例目录
cd "D:\IC Coder Plugin\ic-coder\tools\iverilog\examples"
# 编译 Verilog 文件
"D:\IC Coder Plugin\ic-coder\tools\iverilog\bin\iverilog.exe" -o simulation.vvp counter.v counter_tb.v
# 运行仿真
"D:\IC Coder Plugin\ic-coder\tools\iverilog\bin\vvp.exe" simulation.vvp
# 查看生成的 VCD 文件
ls -lh output.vcd
```
## 预期输出
### 控制台输出
```
Time=0 ns, rst_n=0, count=0 (0x0)
Time=20 ns, rst_n=1, count=0 (0x0)
Time=25 ns, rst_n=1, count=1 (0x1)
Time=35 ns, rst_n=1, count=2 (0x2)
Time=45 ns, rst_n=1, count=3 (0x3)
...
Time=215 ns, rst_n=1, count=15 (0xf)
Final count value: 15
```
### 生成的文件
- **simulation.vvp** - 编译后的中间文件(可以删除)
- **output.vcd** - VCD 波形文件(约 2-5 KB
## 查看波形
使用 GTKWave 查看生成的 VCD 文件:
```bash
gtkwave output.vcd
```
在 GTKWave 中:
1. 在左侧 SST 窗口选择 `counter_tb`
2. 将信号 `clk``rst_n``count[3:0]` 拖到波形窗口
3. 点击 "Zoom Fit" 查看完整波形
## 波形说明
你应该能看到:
- **clk** - 规则的时钟信号10ns 周期)
- **rst_n** - 初始 20ns 为低电平,然后保持高电平
- **count** - 从 0 开始递增到 15 的计数值
## 故障排除
### 问题:编译失败
**检查**
- 确认 iverilog.exe 在 `tools/iverilog/bin/` 目录中
- 确认所有 DLL 文件都已复制
### 问题VCD 文件未生成
**检查**
- testbench 中是否包含 `$dumpfile("output.vcd");`
- testbench 中是否包含 `$dumpvars(0, counter_tb);`
- 仿真是否正常结束(有 `$finish;`
### 问题:路径错误
**解决**
- 确保在正确的工作区目录中运行
- 检查路径中是否有特殊字符
## 扩展练习
你可以修改这个示例来学习:
1. **修改计数器位宽**:将 4 位改为 8 位
2. **添加使能信号**:只在使能时计数
3. **添加加载功能**:可以加载初始值
4. **创建递减计数器**:向下计数
5. **添加溢出标志**:计数到最大值时输出标志
## 参考资料
- Verilog 语法https://www.asic-world.com/verilog/
- Icarus Verilog 文档http://iverilog.icarus.com/
- GTKWave 使用http://gtkwave.sourceforge.net/

View File

@ -0,0 +1,17 @@
// counter.v - 简单的 4 位计数器模块
module counter (
input wire clk,
input wire rst_n,
output reg [3:0] count
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
count <= 4'b0000;
end else begin
count <= count + 1;
end
end
endmodule

View File

@ -0,0 +1,54 @@
// counter_tb.v - 计数器测试平台
`timescale 1ns/1ps
module counter_tb;
// 信号声明
reg clk;
reg rst_n;
wire [3:0] count;
// 实例化被测模块
counter uut (
.clk(clk),
.rst_n(rst_n),
.count(count)
);
// 生成时钟信号 (10ns 周期 = 100MHz)
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 测试序列
initial begin
// 生成 VCD 波形文件
$dumpfile("output.vcd");
$dumpvars(0, counter_tb);
// 初始化信号
rst_n = 0;
// 等待 20ns 后释放复位
#20;
rst_n = 1;
// 运行 200ns 让计数器计数
#200;
// 显示最终计数值
$display("Final count value: %d", count);
// 结束仿真
$finish;
end
// 监控输出变化
initial begin
$monitor("Time=%0t ns, rst_n=%b, count=%d (0x%h)",
$time, rst_n, count, count);
end
endmodule

View File

@ -0,0 +1,43 @@
// Mathematical and physical constants
`ifdef CONSTANTS_VAMS
`else
`define CONSTANTS_VAMS 1
// M_ is a mathematical constant
`define M_E 2.7182818284590452354
`define M_LOG2E 1.4426950408889634074
`define M_LOG10E 0.43429448190325182765
`define M_LN2 0.69314718055994530942
`define M_LN10 2.30258509299404568402
`define M_PI 3.14159265358979323846
`define M_TWO_PI 6.28318530717958647693
`define M_PI_2 1.57079632679489661923
`define M_PI_4 0.78539816339744830962
`define M_1_PI 0.31830988618379067154
`define M_2_PI 0.63661977236758134308
`define M_2_SQRTPI 1.12837916709551257390
`define M_SQRT2 1.41421356237309504880
`define M_SQRT1_2 0.70710678118654752440
/*
* Do we need these? For now they are not available.
*
// The following constants have been taken from http://physics.nist.gov
// P_ is a physical constant
// charge of electron in coulombs
`define P_Q 1.602176462e-19
// speed of light in vacuum in meters/sec
`define P_C 2.99792458e8
// Boltzmann's constant in joules/kelvin
`define P_K 1.3806503e-23
// Planck's constant in joules*sec
`define P_H 6.62606876e-34
// permittivity of vacuum in farads/meter
`define P_EPS0 8.854187817e-12
// permeability of vacuum in henrys/meter
`define P_U0 (4.0e-7 * `M_PI)
// zero celsius in kelvin
`define P_CELSIUS0 273.15
*/
`endif

View File

@ -0,0 +1,72 @@
// Standard definitions for Verilog-AMS
`ifdef DISCIPLINES_VAMS
`else
`define DISCIPLINES_VAMS 1
discipline \logic ;
domain discrete;
enddiscipline
discipline ddiscrete;
domain discrete;
enddiscipline
nature Current;
units = "A";
access = I;
idt_nature = Charge;
`ifdef CURRENT_ABSTOL
abstol = `CURRENT_ABSTOL
`else
abstol = 1e-12;
`endif
endnature
nature Charge;
units = "coul";
access = Q;
ddt_nature = Current;
`ifdef CHARGE_ABSTOL
abstol = `CHARGE_ABSTOL;
`else
abstol = 1e-14;
`endif
endnature
nature Voltage;
units = "V";
access = V;
idt_nature = Flux;
`ifdef VOLTAGE_ABSTOL
abstol = `VOLTAGE_ABSTOL;
`else
abstol = 1e-6;
`endif
endnature
nature Flux;
units = "Wb";
access = Phi;
ddt_nature = Voltage;
`ifdef FLUX_ABSTOL
abstol = `flux_ABSTOL;
`else
abstol = 1e-9;
`endif
endnature
discipline electrical;
potential Voltage;
flow Current;
enddiscipline
discipline voltage;
potential Voltage;
enddiscipline
discipline current;
flow Current;
enddiscipline
`endif // !`ifdef DISCIPLINES_VAMS

View File

@ -0,0 +1,6 @@
functor:synth2
functor:synth
functor:syn-rules
functor:cprop
functor:nodangle
flag:DLL=blif.tgt

View File

@ -0,0 +1,6 @@
functor:synth2
functor:synth
functor:syn-rules
functor:cprop
functor:nodangle
flag:DLL=blif.tgt

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,4 @@
functor:synth2
functor:synth
functor:syn-rules
flag:DLL=null.tgt

View File

@ -0,0 +1 @@
flag:DLL=null.tgt

Binary file not shown.

View File

@ -0,0 +1,6 @@
functor:synth2
functor:synth
functor:syn-rules
functor:cprop
functor:nodangle
flag:DLL=pcb.tgt

View File

@ -0,0 +1,3 @@
functor:cprop
functor:nodangle
flag:DLL=pcb.tgt

Binary file not shown.

View File

@ -0,0 +1,6 @@
functor:synth2
functor:synth
functor:syn-rules
functor:cprop
functor:nodangle
flag:DLL=sizer.tgt

View File

@ -0,0 +1,6 @@
functor:synth2
functor:synth
functor:syn-rules
functor:cprop
functor:nodangle
flag:DLL=sizer.tgt

Binary file not shown.

View File

@ -0,0 +1,6 @@
functor:synth2
functor:synth
functor:syn-rules
functor:cprop
functor:nodangle
flag:DLL=stub.tgt

View File

@ -0,0 +1,3 @@
functor:cprop
functor:nodangle
flag:DLL=stub.tgt

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,7 @@
functor:synth2
functor:synth
functor:syn-rules
functor:cprop
functor:nodangle
-t:dll
flag:DLL=vhdl.tgt

View File

@ -0,0 +1,4 @@
functor:cprop
functor:nodangle
flag:DLL=vhdl.tgt
flag:DISABLE_CONCATZ_GENERATION=true

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,7 @@
functor:synth2
functor:synth
functor:syn-rules
functor:nodangle
functor:exposenodes
flag:DLL=vlog95.tgt
flag:DISABLE_CONCATZ_GENERATION=true

View File

@ -0,0 +1,2 @@
flag:DLL=vlog95.tgt
flag:DISABLE_CONCATZ_GENERATION=true

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,7 @@
functor:synth2
functor:synth
functor:syn-rules
functor:cprop
functor:nodangle
flag:DLL=vvp.tgt
flag:VVP_EXECUTABLE=/c/Source/iverilog-install/bin/vvp

View File

@ -0,0 +1,4 @@
functor:cprop
functor:nodangle
flag:DLL=vvp.tgt
flag:VVP_EXECUTABLE=/c/Source/iverilog-install/bin/vvp

Binary file not shown.

Binary file not shown.