zoukankan      html  css  js  c++  java
  • 如何在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 

    总结

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

  • 相关阅读:
    700. Search in a Binary Search Tree
    100. Same Tree
    543. Diameter of Binary Tree
    257. Binary Tree Paths
    572. Subtree of Another Tree
    226. Invert Binary Tree
    104. Maximum Depth of Binary Tree
    1、解决sublime打开文档,出现中文乱码问题
    移植seetafaceengine-master、opencv到ARM板
    ubuntu16.04-交叉编译-SeetaFaceEngine-master
  • 原文地址:https://www.cnblogs.com/yhpbook/p/14053204.html
Copyright © 2011-2022 走看看