简介
近段时间做项目,涉及到一些传感器数据的采集,比如温度传感器DHT11
,这种东西使用FPGA来做,为了实现他的时序,如果自己写的话那是真的不容易,但是对于项目来说,这个东西有需要做,怎么办?于是在FPGA或者CPLD中做一个占用资源可控,且能在各个平台下移植的可编程状态机就进入了我的视野。
说时迟那时快,花了一天时间写了一个简单的8位mcu,在功能上仅仅只有简单的输入输出功能,加减法,逻辑运算,支持跳转,调用,比较等指令。
主要实现功能
目前该mcu的主要情况如下:
- 内部16个寄存器,s0 ~ sF
- 设计一个深度为31的程序指针栈,用于支持CALL命令
- 支持8位加减法运算,通过设计的标志位可以实现16为,32位的加减法运算
- 支持逻辑运算,AND,OR,XOR
从实现功能上来看,似乎比较少,但是我们依旧可以使用这些有限的命令实现我们的初衷,就是实现与一些简单传感器的交互,甚至可以实现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
安装器
下载地址:官方地址
然后就可以使用这个安装器进行安装了
2、启动安装器进行安装
安装器有三种安装模式可供选择:
①Install from Internet,这种模式直接从Internet安装,适合网速较快的情况;
②Download Without Installing,这种模式只从网上下载Cygwin的组件包,但不安装;
③Install from Local Directory,这种模式与上面第二种模式对应,当你的Cygwin组件包已经下载到本地,则可以使用此模式从本地安装Cygwin
说明:当你安装过,在执行该安装程序可以选择本地安装,然后添加需要扩展的命令。
第一次安装使用第一种方式进行安装:
在下载的同时,建议将Cygwin安装组件也保存到了本地,以便以后能够再次安装,这一步选择安装过程中从网上下载的Cygwin组件包的保存位
选择连接方式
这一步选择连接的方式,选择你的连接方式,然后点击下一步,会出现选择下载站点的对话框,如下图所示
①Use System Proxy Settings 使用系统的代理设置
②Direct Connection 一般多数用户都是这种直接连接的网络,所以都是直接使用默认设置即可
③Use HTTP/FTP Proxy 使用HTTP或FTP类型的代理。如果有需要,自己选择此项后,设置对应的代理地址和端口,即可
选择下载站点
不同的镜像存放了不同的包,为了获得最快的下载速度,我们可以添加网易开源镜像http://mirrors.163.com/cygwin/
或者 阿里云镜像http://mirrors.aliyun.com/cygwin/
开始自动搜索
选择需要下载安装的组件包
这一步比较重要,为了之后更好的使用该软件,建议自己在这里的时候就选好需要使用的组件,或者说支持的命令。
最核心的,记住一定要安装Devel这个部分的模块,其中包含了各种开发所用到的工具或模块。
下面推荐推几个组件
- fish:一个shell,具有良好的交互提示,强烈建议安装,后面的操作也和其相关
- lynx:命令安装组件的必须工具,强烈推荐安装此项,方便之后扩展命令
- 其他的自选,比如 gcc,curl,python,tclsh等。学习FPGA,建议安装tclsh
组件可以在search框输入后搜索,然后选中组件,在new列双击,当看到版本号后,安装就会将此组件安装上。
确认并开始安装
安装好之后,将cygwin安装路径下的bin目录添加到环境变量,方便使用
为了让我们更舒服的使用,我们先把默认的shell设为fish,当然,若果没安装fish就算了
当我们没配置fish shell,使用默认的shell时我们打开cygwin
的终端是这样的
在终端输入以下命令后下次重启就可以了。
echo "fish" >> /etc/profile
当然此时要直接切换到fish可以在终端直接输入fish
,切换过来就是这样的了:
step2:安装verilog小巧的仿真工具-iverilog
下载链接:windows版本iverilog
下载后直接安装,当然为了之后使用方便强烈建议安装好将安装路径下的bin
目录和安装目录下的gtkwave/bin
目录加入环境变量。
step3:主要工具准备完毕,在随意来个编辑器
编辑器在这里推荐使用vscode
,后面的说明也都会基于这个编辑器。
下载链接:vscode官网
注意,记住你的安装路径,
我们打开他,同样为了方便使用,在这里先对其进行简单的配置:
首先安装几个必要的插件
在这个里面搜索,为了支持中文,你可以搜索chinese
,进行安装,之后又就是中文显示了。其他的插件可以暂时不用安装,之后遇到相应的文件后,软件会自动推荐你安装,我安装的插件如下:
关键步骤
在搜索框搜索term
然后配置一下:
主要就是这几个,大家最好把这几项先配置好,省的之后一项一项配置。
{ "terminal.integrated.shell.windows": "D:\cygwin64\bin\fish.exe", "files.autoSave": "onFocusChange", "files.autoGuessEncoding": true, "editor.mouseWheelZoom": true }
下载mini-mcu
下载地址:mini-mcu
如果你安装cygwin时也安装了git,那么在cygwin的终端中可以使用:
git clone https://gitee.com/yuan_hp/mini-mcu.git
直接克隆。
然后我们使用vscode
打开我们mini-mcu
的文件夹,并在打开vscode的终端。
为了感受一下之后开发的方便,在终端中输入以下命令:
该命令行会直接编译项目中software
一级目录下的.psm
文件,也就是我们的汇编代码文件,并生成对用的rom.v
文件,同时裁剪mini-mcu
,命令./run
将会调用iverilog
仿真项目并用gtkwave
代开仿真的波形图
**特别注意:**当你想开发新的功能时,你可以先不关闭gtkwave,修改software
下的代码后,执行以下命令
刷新并行文件的数据,然后在gtkwave重新加载数据:
项目文件结构
├── 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
- SRA
- LRA
开发你的项目
step1:编写代码
脚本只会自动搜索software
一级目录下的.psm
文件!
start: LOAD sA , 23; 加载寄存器A的值为 0x23 ADD sA,02;寄存区A的值加上 0x02
step2:编译
执行命令
./run -c
编译文件
step3:仿真verilog项目
执行命令:
./run
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
个人实验开发板
我做实验的开发板为小脚丫FPGA,型号为STEM-MX02-C
,这是U盘模式的,芯片为Lattice的,项目下已经有对应的工程,就是step_fpga
,如果你的开发板也是这个,同时也安了diamond,也将diamond的可执行路径加入了环境变量,那么可以执行命令./run -g
,就会编译代码,拷贝文件,综合工程,下载到开发板了,你可能需要修改的是在step_fpga
下的run.tcl
脚本的最后一行。pnmainc
是diamond工具的tcl命令工具!
几个实例
流水灯:
;系统时钟为倍频到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 ;实现按键检测,按一下key1,led1将会翻转一次,程序简单,因此注意手不要抖,正常按法按 ;输入 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,FF ; ' rgb 灭 output sC,rgb_port ;rgb input sD,sw_port ; 读一次io口 loop: input sF,sw_port AND sF,00010000'b ;' COMPARE sF,00 JUMP Z,keycheck jump loop ;循环 keycheck: CALL delay_200ms input sF,sw_port AND sF,00010000'b ;' COMPARE sF,00 CALL Z,led_sh JUMP loop led_sh: XOR sA,00010000'b ;' OUTPUT sA,led_port RETURN delay_200ms: LOAD s2, 03 ; LOAD s1, a9 LOAD s0, 80 jump software_delay 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
总结
个人水平有限,中断部分过段时间在添加,对于实现简单传感器的采集,已经足够用了,导师抓得紧,牙缝里挤出的时间写的这个小项目,收获了很多,现在这个项目只是模型,之后会逐步完善!