🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] > # 简介 近段时间做项目,涉及到一些传感器数据的采集,比如温度传感器`DHT11`,这种东西使用FPGA来做,为了实现他的时序,如果自己写的话那是真的不容易,但是对于项目来说,这个东西有需要做,怎么办?于是在FPGA或者CPLD中做一个占用资源可控,且能在各个平台下移植的可编程状态机就进入了我的视野。 说时迟那时快,花了一天时间写了一个简单的8位mcu,在功能上仅仅只有简单的输入输出功能,加减法,逻辑运算,支持跳转,调用,比较等指令。 > # 主要实现功能 目前该mcu的主要情况如下: * 内部16个寄存器,s0 ~ sF * 设计一个深度为31的程序指针栈,用于支持CALL命令 * 支持8位加减法运算,通过设计的标志位可以实现16为,32位的加减法运算 * 支持逻辑运算,AND,OR,XOR * 有一个中断输入口 * 可在mcu外部使用逻辑资源扩展中断--利用标志位的方式 从实现功能上来看,似乎比较少,但是我们依旧可以使用这些有限的命令实现我们的初衷,就是实现与一些简单传感器的交互,甚至可以实现I2C,SPI的通信接口。 在指令上为了不用自己开发将汇编翻译为机器指令的工具,这里直接和xilinx的picoblaze的指令保持一致,换句话说,使用xilinx的工具可以将我们的汇编代码翻译成支持这个mini-mcu的机器指令。 > # 有什么特有的特性 在一些资源有限的CPLD上,完全实现一个mcu是不现实,为了契合我的初衷,并且不让资源被浪费太多,我字节写了脚本,可以根据写的汇编代码,将汇编代码中使用到的命令提取出来,并会直接将`mini-mcu`与这些命令有关的部分保留,**裁剪掉其他没有用到的部分**,但是输入输出部分将会一直保留。另外由于没有实现其他一些复杂的指令,**这些指令都是在两个时钟周期中完成的**,也就是说基于这个特性,可以实现精确的时钟延时。 > # 环境准备 由于自己适应了linux的环境,所以实现的脚本都是使用`bash`写的,包括裁剪mcu,如果不会linux指令也没事,只不过不能自动化的生成这些东西,需要自己手动来,包括裁剪mcu,当然这种情况下,你可以不裁剪。 **说一说在为了支持linux指令,在windows系统上应该怎么准备环境** ## step1:安装cygwin以支持bash脚本 Cygwin就是一个windows软件,该软件就是在windows上仿真linux操作系统 ,简言之,cygwin是一个在windows平台上运行的 linux模拟环境,使用一个Dll(动态链接库)来实现 这样,我们可以开发出Cygwin下的UNIX工具,使用这个DLL运行在Windows下。 **安装方法** **1、下载`cygwin`安装器** 下载地址:[官方地址](http://www.cygwin.com/) ![](https://img.kancloud.cn/6f/61/6f618d2eefc33b4d32f52f3d95b2afa8_1490x863.png) 然后就可以使用这个安装器进行安装了 **2、启动安装器进行安装** 安装器有三种安装模式可供选择: ``` ①Install from Internet,这种模式直接从Internet安装,适合网速较快的情况; ②Download Without Installing,这种模式只从网上下载Cygwin的组件包,但不安装; ③Install from Local Directory,这种模式与上面第二种模式对应,当你的Cygwin组件包已经下载到本地,则可以使用此模式从本地安装Cygwin ``` ***说明:当你安装过,在执行该安装程序可以选择本地安装,然后添加需要扩展的命令。*** 第一次安装使用第一种方式进行安装: ![](https://img.kancloud.cn/80/f4/80f4c5a3d44b5644b59f5ae0b5f39544_990x658.png) ![](https://img.kancloud.cn/5a/1c/5a1c49a634392115421375a9a3ad6ad2_990x646.png) **在下载的同时,建议将Cygwin安装组件也保存到了本地,以便以后能够再次安装,这一步选择安装过程中从网上下载的Cygwin组件包的保存位** ![](https://img.kancloud.cn/eb/a5/eba5b77b84a416d7aef02fd58dfca706_1001x664.png) **选择连接方式** 这一步选择连接的方式,选择你的连接方式,然后点击下一步,会出现选择下载站点的对话框,如下图所示 ![](https://img.kancloud.cn/0f/aa/0faa90208d28e288088db4cdaece16ef_975x590.png) ``` ①Use System Proxy Settings 使用系统的代理设置 ②Direct Connection 一般多数用户都是这种直接连接的网络,所以都是直接使用默认设置即可 ③Use HTTP/FTP Proxy 使用HTTP或FTP类型的代理。如果有需要,自己选择此项后,设置对应的代理地址和端口,即可 ``` **选择下载站点** 不同的镜像存放了不同的包,为了获得最快的下载速度,我们可以添加网易开源镜像`http://mirrors.163.com/cygwin/`或者 阿里云镜像`http://mirrors.aliyun.com/cygwin/` ![](https://img.kancloud.cn/53/88/5388cbe33030b3bc929c7ac22bae102b_984x648.png) **开始自动搜索** ![](https://img.kancloud.cn/45/70/4570fe9877badee7b71a9106b8c4726f_863x627.png) **选择需要下载安装的组件包** 这一步比较重要,为了之后更好的使用该软件,建议自己在这里的时候就选好需要使用的组件,或者说支持的命令。 最核心的,记住一定要安装Devel这个部分的模块,其中包含了各种开发所用到的工具或模块。 ***下面推荐推几个组件*** * fish:一个shell,具有良好的交互提示,强烈建议安装,后面的操作也和其相关 * lynx:命令安装组件的必须工具,强烈推荐安装此项,方便之后扩展命令 * 其他的自选,比如 gcc,curl,python,tclsh等。学习FPGA,建议安装tclsh **组件可以在search框输入后搜索,然后选中组件,在new列双击,当看到版本号后,安装就会将此组件安装上。** ![](https://img.kancloud.cn/c2/a3/c2a306141ee14dac3670cb959fc8f4d0_836x545.png) **确认并开始安装** ![](https://img.kancloud.cn/26/c2/26c22d4bcdf37609f73942af539a4c5f_835x553.png) **安装好之后,将cygwin安装路径下的bin目录添加到环境变量,方便使用** **为了让我们更舒服的使用,我们先把默认的shell设为fish,当然,若果没安装fish就算了** 当我们没配置fish shell,使用默认的shell时我们打开`cygwin`的终端是这样的 ![](https://img.kancloud.cn/89/76/8976ad6a0fc7525a859f6219111f2d1f_650x241.png) 在终端输入以下命令后下次重启就可以了。 ``` echo "fish" >> /etc/profile ``` 当然此时要直接切换到fish可以在终端直接输入`fish`,切换过来就是这样的了: ![](https://img.kancloud.cn/7d/e0/7de0e36d69f60e149761e61d1e185b9d_1143x512.png) ## step2:安装verilog小巧的仿真工具-iverilog 下载链接:[windows版本iverilog](http://bleyer.org/icarus/) ![](https://img.kancloud.cn/fa/8e/fa8e8d245cdb68b7cc76fa8946800a5d_963x633.png) 下载后直接安装,当然为了之后使用方便强烈建议安装好将安装路径下的`bin`目录和安装目录下的`gtkwave/bin`目录加入环境变量。 ## step3:主要工具准备完毕,在随意来个编辑器 编辑器在这里推荐使用`vscode`,后面的说明也都会基于这个编辑器。 下载链接:[vscode官网](https://code.visualstudio.com/) 注意,记住你的安装路径, ![](https://img.kancloud.cn/28/79/28792c4a1370c1a1525f1a446ad82411_1081x648.png) 我们打开他,同样为了方便使用,在这里先对其进行简单的配置: **首先安装几个必要的插件** ![](https://img.kancloud.cn/9d/3e/9d3e8b27e5124e62a34ad591d7917da8_697x418.png) 在这个里面搜索,为了支持中文,你可以搜索`chinese`,进行安装,之后又就是中文显示了。其他的插件可以暂时不用安装,之后遇到相应的文件后,软件会自动推荐你安装,我安装的插件如下: ![](https://img.kancloud.cn/78/95/7895cb2536cadf2227b0b4eb9950745d_425x1071.png) **关键步骤** ![](https://img.kancloud.cn/13/f1/13f1b9664807314f500a0b889e207a2d_785x815.png) 在搜索框搜索`term` ![](https://img.kancloud.cn/50/cc/50cced520fde49f539afb85ed2889fc3_1465x996.png) 然后配置一下: ![](https://img.kancloud.cn/20/5b/205bc3341a5dab4ca697f0a7a98ff388_1391x645.png) 主要就是这几个,大家最好把这几项先配置好,省的之后一项一项配置。 ``` { "terminal.integrated.shell.windows": "D:\\cygwin64\\bin\\fish.exe", "files.autoSave": "onFocusChange", "files.autoGuessEncoding": true, "editor.mouseWheelZoom": true } ``` > # 下载mini-mcu 下载地址:[mini-mcu](https://gitee.com/yuan_hp/mini-mcu) ![](https://img.kancloud.cn/f6/4d/f64d59df7dc7b1cb83cf75727867a189_1286x435.png) 如果你安装cygwin时也安装了git,那么在cygwin的终端中可以使用: ``` git clone https://gitee.com/yuan_hp/mini-mcu.git ``` 直接克隆。 然后我们使用`vscode`打开我们`mini-mcu`的文件夹,并在打开vscode的终端。 ![](https://img.kancloud.cn/97/e8/97e807fe802f534a1c9c7d4df3e7262f_1724x1075.png) 为了感受一下之后开发的方便,在终端中输入以下命令: ![](https://img.kancloud.cn/e1/46/e146276fd2093c36b91ae99e035cb21a_1745x1018.png) 该命令行会直接编译项目中`software`一级目录下的`.psm`文件,也就是我们的汇编代码文件,并生成对用的`rom.v`文件,同时裁剪`mini-mcu`,命令`./run`将会调用`iverilog`仿真项目并用`gtkwave`代开仿真的波形图 ![](https://img.kancloud.cn/7b/9f/7b9fe0bb4c274d3f5959e4f55e35a9f3_1974x1080.png) **特别注意:**当你想开发新的功能时,你可以先不关闭gtkwave,修改`software`下的代码后,执行以下命令 ![](https://img.kancloud.cn/54/7c/547c1c4e94ac38c5f3ac905393ec15c5_638x43.png) 刷新并行文件的数据,然后在gtkwave重新加载数据: ![](https://img.kancloud.cn/fc/5f/fc5f9a463ec82fd0aeb1a6d4b52b2805_559x516.png) > # 项目文件结构 ``` ├── head.v 用于裁剪mini-mcu的宏文件 ├── images 存放着图片 ├── mcu.v mini-mcu源码 ├── README.md ├── rom.v 编译汇编自动成成的程序存储器 ├── run 项目控制脚本 ├── run.sh ├── sim 生成的仿真文件 │ ├── wave │ └── wave.lxt2 ├── software 编写的汇编代码 │ ├── test.psm 脚本会编译的代码 │ ├── 第一个例子 │ │ └── start.psm │ ├── 简单按键检测 │ │ └── keycheck.psm │ ├── 流水灯程序 │ │ └── led_water.psm │ └── 数码管计数 │ └── seg_counter.psm ├── step_fpga 小脚丫fpga的历程项目,执行 ./run -g 会将文件拷贝到这个目录下 ├── tb.v 仿真testbech文件 ├── tmp 执行脚本时生成的临时文件夹 │ ├── kcpsm6.exe │ ├── KCPSM6_session_log.txt │ ├── ROM_form.v │ ├── test.fmt │ ├── test.hex │ ├── test.log │ ├── test.psm │ └── test.v ├── tools 仿真一些工具和脚本 │ ├── bin │ │ ├── compile │ │ ├── hex2rom │ │ └── msim │ └── kcpsm │ ├── kcpsm6.exe │ └── ROM_form.v ├── upCloud └── window.v 专门用来查看mcu内部变量的模块 ``` > # 已经支持的指令 * LOAD * JUMP * JUMP C * JUMP NC * JUMP Z * JUMP NZ * CALL C * CALL NC * CALL Z * CALL NZ * CALL * RETURN * RETURN C * RETURN NC * RETURN Z * RETURN NZ * AND * OR * XOR * INPUT * OUTPUT * ADD * ADDCY * SUB * SYBCY * COMPARE * TEST * SL0 * SL1 * RL * RR * SR0 * SR1 * SLA * SRA * ENABLE INTERRUPT * DISABEL INTERRUPT * RETURNI * JUMP @(sX,sY) * CALL @(sX,sY) ![](https://img.kancloud.cn/41/74/41744e4a3f75d2eaee430118f496b558_1226x762.png) > # 开发你的项目 ## step1:编写代码 脚本只会自动搜索`software`一级目录下的`.psm`文件! ``` start:     LOAD sA , 23; 加载寄存器A的值为 0x23 ADDsA,02;寄存区A的值加上 0x02 ``` ## step2:编译 执行命令`./run -c`编译文件 ![](https://img.kancloud.cn/fc/e5/fce523cb3f341206aabe06383371a3e2_987x792.png) ## step3:仿真verilog项目 执行命令:`./run` ![](https://img.kancloud.cn/ef/7c/ef7cd4cf1685a94d136227d55538b4f6_1044x580.png) ## step4:板上验证 拷贝项目目录下的`mcu.v, rom.v , head.v`到实际的FPGA实际项目的目录下,进行,并编写项目的顶层文件:参考如下: ``` module top ( input clk_in, //输入系统12MHz时钟 //4bit拨码开关输入 input [3:0] sw, input [3:0] key, //按键输入 //数码管 output [8:0] seg_led_1, output [8:0] seg_led_2, //rgb output reg[2:0]rgb, //led output led1, output led2, output led3, output led4, output led5, output led6, output led7, output led8 ); wire clk ,clko,rst; reg [7:0] out; assign {led8,led7,led6,led5,led4,led3,led2,led1} = out; assign clk = clk_in; reg rst_n_in; //复位信号 reg [17:0]cnt ; always @(posedge clk) begin if(cnt>=18'h3ffff)begin rst_n_in <= 1'b1; end else begin cnt <= cnt +1; rst_n_in <= 0; end end /* divide #( .N(1) ) u1 ( .clk(clko), .rst_n(rst_n_in), .clkout(clk) ); */ //----------- mini-mcu 相关------------ wire [11:0]address; wire [17:0] instruction; wire bram_enable, read_strobe, write_strobe; reg [7:0] in_port; wire [7:0] port_id, out_port; //----------- 数码管 相关------------ reg[3:0] seg_data_1, seg_data_2; //输出引脚 always @(posedge clk )begin if(write_strobe)begin case(port_id) 8'h00:{seg_data_1,seg_data_2} <= out_port;//bcd编码的2个数码管 8'h01:out <= out_port; //LED控制 8'h02:rgb <= out_port[2:0]; //rgb default:out <=out; endcase end else out <= out; end //输入引脚 always@(*)begin if(read_strobe) begin case(port_id) 8'h00: in_port = {key[3:0],sw[3:0]}; //按键 4bit拨码开关输入 endcase end end /********************************** * 例化mini-mcu **********************************/ mcu mcu( .clk(clk), //系统时钟 .rst_n( rst_n_in), //复位 0 --> 复位 .address( address), //程序取址地址 .instruction( instruction), //指令输入 .bram_enable( bram_enable), //程序rom使能 1-->使能 .in_port( in_port), //输入口 .read_strobe( read_strobe), //输入口使能 .port_id( port_id), //io口地址 .out_port( out_port), //输出口 .write_strobe( write_strobe) //输出口写使能 ); rom rom( .clk( clk), .address( address), //程序取址地址 .instruction( instruction), //指令输入 .enable( bram_enable) //程序rom使能 1-->使能 ); /********************************** *数码管显示 是bcd码 **********************************/ seg_display seg_display( .seg_data_1(seg_data_1), .seg_data_2(seg_data_2), .seg_led_1(seg_led_1), .seg_led_2(seg_led_2) ); endmodule ``` > # 几个实例 ## 流水灯 ``` ;系统时钟为倍频到120MHz ;目标硬件为 小脚丫FPGA step-maxo2-c,这个型号是U盘模式,流文件会下载到mcu,每次上电由mcu配置FPGA ;输入 constant sw_port,00 ;定义按键四段拨码开关 【按键 : 开关 】 ;输出 constant seg_port,00 ;定义数码管地址 constant led_port,01 ;定义led_port为常量01 constant rgb_port,02 ; rgb灯 start: load sA,FE ; led等控制 load sB,12 ; 初始化数码管显示 12 load sC,00000111'b ; ' rgb 灭 output sC,rgb_port ;rgb不量 input sD,sw_port ; 读一次io口 output sB, seg_port ;数码管显示 loop: output sA, led_port ;流水灯实现 RL sA ;循环左移 call delay_500ms jump loop ;循环 delay_500ms: LOAD s2, 09 ; 500000us / (1/1.2us) --> 计数次数 LOAD s1, 27 LOAD s0, c0 jump software_delay software_delay: LOAD s0, s0 ;pad loop to make it 10 clock cycles (5 instructions), if clk 12MHz --> 1/1.2 us SUB s0, 01 SUBCY s1, 00 SUBCY s2, 00 JUMP NZ, software_delay RETURN ``` ## 数码管计数器 ``` ;系统时钟为12MHz ;目标硬件为 小脚丫FPGA step-maxo2-c,这个型号是U盘模式,流文件会下载到mcu,每次上电由mcu配置FPGA ;输入 constant sw_port,00 ;定义按键四段拨码开关 【按键 : 开关 】 ;输出 constant seg_port,00 ;定义数码管地址 constant led_port,01 ;定义led_port为常量01 constant rgb_port,02 ; rgb灯 start: load sA,FF ; led等控制 output sA,led_port load sB,00 ; 初始化数码管显示 load sC,00000111'b ; ' rgb 灭 output sC,rgb_port ;rgb不量 input sD,sw_port ; 读一次io口 loop: output sB, seg_port ADD sB,01 load sE,sB ; SL0 sE SL0 sE SL0 sE SL0 sE COMPARE sE,A0 CALL Z,carry ;越界进位 LOAD sE,sB SR0 sE SR0 sE SR0 sE SR0 sE COMPARE sE,0A CALL Z,clear ;满了清零 call delay_500ms jump loop ;循环 carry: ;bcd码表示的低四位已经满了,整体加6 ADD sB,06 RETURN clear: ;到了99后,下一步归零 LOAD sB,00 RETURN delay_500ms: LOAD s2, 09 ; 500000us / (1/1.2us) --> 计数次数 LOAD s1, 27 LOAD s0, c0 jump software_delay software_delay: LOAD s0, s0 ;pad loop to make it 10 clock cycles (5 instructions), if clk 12MHz --> 1/1.2 us SUB s0, 01 SUBCY s1, 00 SUBCY s2, 00 JUMP NZ, software_delay RETURN ``` ## 8位乘法器 ``` ;系统时钟为12MHz ;目标硬件为 小脚丫FPGA step-maxo2-c,这个型号是U盘模式,流文件会下载到mcu,每次上电由mcu配置FPGA start: load s0 , 0a ; 加载寄存器0的值 load s1,08 ; 寄存器1的值 call mult ; s0 * s1 => s2 wait: jump wait ; 8位乘法 计算 s0 * s1 ==> s2 mult : load s2,00 mult_loop: add s2,s0 sub s1,01 jump nz,mult_loop return ;16位乘法 [s1,s0] * [s3,s2] => [s5,s4] mult16 : load s5,00 load s4,00 mult16_loop: add s4,s2 addcy s5,s3 sub s0,01 subcy s1,00 jump nz,mult16_loop return ``` > # 个人实验开发板 我做实验的开发板为小脚丫FPGA,型号为`STEM-MX02-C`,这是U盘模式的,芯片为Lattice的,项目下已经有对应的工程,就是`step_fpga`,如果你的开发板也是这个,同时也安了diamond,也将diamond的可执行路径加入了环境变量,那么可以执行命令`./run -g`,就会编译代码,拷贝文件,综合工程,下载到开发板了,你可能需要修改的是在`step_fpga`下的`run.tcl`脚本的最后一行。 ![](https://img.kancloud.cn/90/ce/90ce6e938196d2cf2122c92d8c97b2c3_1185x555.png) `pnmainc`是diamond工具的tcl命令工具! > # 总结 个人水平有限,中断部分过段时间在添加,对于实现简单传感器的采集,已经足够用了,导师抓得紧,牙缝里挤出的时间写的这个小项目,收获了很多,现在这个项目只是模型,之后会逐步完善!