如何在FPGA上做一个没啥用的mcu

简介

近段时间做项目,涉及到一些传感器数据的采集,比如温度传感器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 

 

 

总结

个人水平有限,中断部分过段时间在添加,对于实现简单传感器的采集,已经足够用了,导师抓得紧,牙缝里挤出的时间写的这个小项目,收获了很多,现在这个项目只是模型,之后会逐步完善!

posted @ 2020-11-28 17:04  dlover  阅读(1071)  评论(0编辑  收藏  举报
levels of contents