Verilog+FPGA搭建单片机执行汇编程序实现LED闪烁

工程文件下载:
链接: https://pan.baidu.com/s/1LeeIV4nVPEQRLQqsdRvNxg
提取码: 1ywb

代码存放在\project中,\project\prj 存放Vivado工程。
仿真工程存放在\testbench中,打开Modelsim运行do tb_sim.do即可。

前言

通过Verilog代码,在FPGA上搭建单片机执行汇编程序实现LED闪烁。
实现结果:
LED灯闪烁

1.CPU

1.指令系统cpu_instr_dec.sv

目前只支持8种指令。

localparam [`CPU_DATA_WIDTH-1:0] MOV     = 'b0001_xxxx;
localparam [`CPU_DATA_WIDTH-1:0] SETB    = 'b0010_xxxx;
localparam [`CPU_DATA_WIDTH-1:0] CLR     = 'b0011_xxxx;
localparam [`CPU_DATA_WIDTH-1:0] AJMP    = 'b0100_xxxx;
localparam [`CPU_DATA_WIDTH-1:0] DJNZ    = 'b0101_xxxx;
localparam [`CPU_DATA_WIDTH-1:0] RET     = 'b0111_xxxx;
localparam [`CPU_DATA_WIDTH-1:0] LCALL   = 'b1000_xxxx;
localparam [`CPU_DATA_WIDTH-1:0] NOP     = 'b1111_xxxx;

CPU的指令系统为单片机最重要的部分,程序指令从ROM中读取出来后,CPU指令系统需要解析并判断进入下一状态。
状态机跳转如下图:
指令系统状态机

2.运算器cpu_arithmetic.sv

运算器主要执行+ - * /等运算操作,需要指令系统识别到指令后,将相应数据写入ACC或TMP,并操作ALU寄存器执行相应运算操作。

localparam [`CPU_DATA_WIDTH-1:0] ARITH_ADD   = 'b0000_0001;
localparam [`CPU_DATA_WIDTH-1:0] ARITH_SUB   = 'b0000_0010;
localparam [`CPU_DATA_WIDTH-1:0] ARITH_MUL   = 'b0000_0011;
localparam [`CPU_DATA_WIDTH-1:0] ARITH_DIV   = 'b0000_0100;
localparam [`CPU_DATA_WIDTH-1:0] ARITH_PLUS  = 'b0000_1000;
localparam [`CPU_DATA_WIDTH-1:0] ARITH_DEC   = 'b0000_1001;

localparam [`CPU_DATA_WIDTH-1:0] ARITH_AND   = 'b0001_0000;
localparam [`CPU_DATA_WIDTH-1:0] ARITH_OR    = 'b0001_0001;
localparam [`CPU_DATA_WIDTH-1:0] ARITH_XOR   = 'b0001_0010;

3.IO控制cpu_io.sv

IO控制主要控制P0,P1,P2,P3寄存器。

4.程序计数器cpu_pc.sv

程序计数器根据指令系统读取1条指令后发起的PC_PLUS信号进行+1操作,指令系统可对PC的值进行赋值,实现跳转,在LCALL、RET等指令是常用到。

5.片内存储器cpu_ram.sv

片内存储器主要存储IO、PC、ACC、TMP等以外的寄存器,用于指令系统操作。

2.程序

1.汇编程序

主要实现了LED闪烁的功能,如下是实现的汇编指令,通过延时180ms进行点亮和熄灭LED从而实现LED闪烁。LED接在P0.0端。

       ORG 0000H
       AJMP MAIN
       ORG 0050H
MAIN  :CLR P0.0
       LCALL DELAY
       AJMP L1
L1    :SETB P0.0
       LCALL DELAY
       AJMP MAIN

;delay 180ms in 10M clock
DELAY :MOV R0, #240
L3    :MOV R1, #240
L4    :NOP
       DJNZ R1, L4
       DJNZ R0, L3
       RET

2.指令内存映射qwic51_rom.sv

该部分代码主要存放机器能识别的代码,如MOV首先需要翻译成0b00010000,并在接下来的1个地址空间内存放操作的地址,再接下来的1个地址空间内存放操作的数据,这样才能被指令系统cpu_instr_dec正确识别。
翻译指令需要手动翻译,如MOV R0 #2需要被翻译成:
00H:0b00010000
01H:R0地址
02H:2

扫描二维码关注公众号,回复: 12458053 查看本文章

如下是将上述汇编代码翻译成机器能识别的数据完整内存映射,程序计数器PC即CPU_PC_ADDR,通过PC的值来得到程序指令。

localparam MAIN   = 8'h50;
localparam L1     = 8'h56;
localparam DELAY  = 8'h60;
localparam L3     = 8'h63;
localparam L4     = 8'h66;

always @(posedge CPU_CLK)
begin
   case(CPU_PC_ADDR)
         8'h00    : cpu_ir_reg_r <= RESET_CTRL;
         8'h01    : cpu_ir_reg_r <= AJMP_CTRL;
         8'h02    : cpu_ir_reg_r <= MAIN;
         MAIN     : cpu_ir_reg_r <= CLR_CTRL + 'b0000;
         MAIN + 1 : cpu_ir_reg_r <= P0;
         MAIN + 2 : cpu_ir_reg_r <= LCALL_CTRL;
         MAIN + 3 : cpu_ir_reg_r <= DELAY;
         MAIN + 4 : cpu_ir_reg_r <= AJMP_CTRL;
         MAIN + 5 : cpu_ir_reg_r <= L1;
         L1       : cpu_ir_reg_r <= SETB_CTRL + 'b0000;
         L1 + 1   : cpu_ir_reg_r <= P0;
         L1 + 2   : cpu_ir_reg_r <= LCALL_CTRL;
         L1 + 3   : cpu_ir_reg_r <= DELAY;
         L1 + 4   : cpu_ir_reg_r <= AJMP_CTRL;
         L1 + 5   : cpu_ir_reg_r <= MAIN;

         DELAY     : cpu_ir_reg_r <= MOV_CTRL;
         DELAY + 1 : cpu_ir_reg_r <= R0;
         DELAY + 2 : cpu_ir_reg_r <= 240;
         L3        : cpu_ir_reg_r <= MOV_CTRL;
         L3 + 1    : cpu_ir_reg_r <= R1;
         L3 + 2    : cpu_ir_reg_r <= 240;
         L4        : cpu_ir_reg_r <= NOP_CTRL;
         L4 + 1    : cpu_ir_reg_r <= DJNZ_CTRL;
         L4 + 2    : cpu_ir_reg_r <= R1;
         L4 + 3    : cpu_ir_reg_r <= L4;
         L4 + 4    : cpu_ir_reg_r <= DJNZ_CTRL;
         L4 + 5    : cpu_ir_reg_r <= R0;
         L4 + 6    : cpu_ir_reg_r <= L3;
         L4 + 7    : cpu_ir_reg_r <= RET_CTRL;
      default : cpu_ir_reg_r <= NOP_CTRL;
   endcase
end

3.系统时钟

系统时钟为10MHz,通过50MHz分频得到。

未完待续

更新日志:

  • 2019年12月4日:1.0
    新建目录。

猜你喜欢

转载自blog.csdn.net/u011239266/article/details/103395407