2022北航计算机组成原理 Project 8

文章详细介绍了MIPS微系统的设计,包括CPU、UART、计时器等模块的实现细节,以及如何处理中断和数据传输。还提到了汇编程序的编写,尤其是针对UART的串口通信和计时器功能的实现。此外,文章强调了可综合性的实现,确保Verilog代码能够生成可烧录的.bit文件。
摘要由CSDN通过智能技术生成

MIPS微系统设计文档

如果你能看到并用到这篇文章,证明你已经是一个成熟的6系计组人了(笑
但我还是要说最后一次实验难度较大且考期紧张,适当的取舍也并非不可;如果你下决心要做,就不要被最后一次实验的困难打倒。


模块概览

整体架构

mips

TC0
Bridge
Key
Switch
LED
DTube
IM
DM

mips_uart

uart_rx
uart_rd

CPU

D_Controller
E_Controller
M_Controller
W_Controller

Blocker

D_Controller
E_Controller
M_Controller

E_MDU

MulDivUnit

MulUnit
DivUnit

F_PC
FD_REG
D_GRF
D_ext
D_cmp
D_NPC
DE_REG
E_ALU
EM_REG
M_DM_IN
M_DM_OUT
CP0
MW_REG

部分新增模块实现

TC0

管脚名位数in/out功能解释
clk1in系统时钟信号
reset1in复位信号
Addr30in地址输入
WE1in写使能信号
Din32in数据输入
Dout32out数据输出
IRQ1out中断请求信号

TC0即系统计时器,可使用写入指令控制计时模式和计时起点,在到达特定时间时,将置高 IRQ 向系统发出中断信号。

Bridge

管脚名位数in/out功能解释
m_data_addr32outDM或外设的读写地址
m_data_rdata32in从外置DM直接读取的待选用的数据
m_data_wdata32out写入DM或外设的数据
m_data_byteen4out经处理的字节使能信号
temp_m_data_addr32inDM或TC的读写地址
temp_m_data_rdata32out经过选择从DM或外设中读出的数据
temp_m_data_wdata32in写入DM或外设的数据
temp_m_data_byteen4in未经处理的字节使能信号
TC0_WE1outTC0写使能信号
TC0_Addr32outTC0读写地址
TC0_Din32out可能写入TC0的数据
TC0_Dout32in根据地址从TC0中直接读出的数据
UART_WE1outUART写使能信号
UART_Addr32outUART读写地址
UART_Din32out可能写入UART的数据
UART_Dout32in根据地址从UART中直接读出的数据
DTube_data_byteen4out数码管写字节使能信号
DTube_Addr32out数码管读写地址
DTube_Din32out可能写入数码管的数据
DTube_Dout32in根据地址从数码管中直接读出的数据
Switch_Addr1out两组拨码开关的选择信号
Switch_Dout32in从拨码开关读出的数据
Key_Dout32in从按键开关读出的数据
LED_data_byteen4outLED输出字节使能信号
LED_Din32out可能写入LED的数据

Bridge 即系统桥,其功能是根据读写地址在 CPU 和各项外设之间选择性开关数据通路。

Key

管脚名位数in/out功能解释
KeyInput8in按键的输入数据(外设按键信号)
KeyResult32out按键输出数据

Key 即按键开关,输出数据仅有低8位有效。

Switch

管脚名位数in/out功能解释
Addr1in两组拨码开关的选择信号
swift18in拨码开关输入数据
swift28in拨码开关输入数据
swift38in拨码开关输入数据
swift48in拨码开关输入数据
swift58in拨码开关输入数据
swift68in拨码开关输入数据
swift78in拨码开关输入数据
swift88in拨码开关输入数据
SwitchResult32out拨码开关输出数据

Switch 即拨码开关,共64个,分为2个大组,8个小组,通过Addr 来选择从哪个大组中读出数据。

LED

管脚名位数in/out功能解释
clk1in系统时钟信号
reset1in复位信号
LED_data_byteen4inLED字节使能信号
LEDInput32in输入LED的数据
LEDResult32outLED输出数据

LED 即控制开发板上 LED 灯的模块。

DTube

管脚名位数in/out功能解释
clk1in系统时钟信号
reset1in复位信号
Addr1in数码管组选择信号
DTube_data_byteen4in数码管字节使能信号
DTubeInput32in待输入数码管的数据
DTubeResult32out经Addr选择输入外置数码管的数据
DTube08out可能输入1-4号外置数码管的数据
DTube_sel04out1-4号外置数码管选择信号
DTube18out可能输入5-8号外置数码管的数据
DTube_sel14out5-8号外置数码管选择信号
DTube28out可能输入最高位外置数码管的数据
DTube_sel21out最高位外置数码管选择信号

DTube 即外置数码管的控制模块,主要通过写入的值更改数码管的输出,并以固定的时间间隔轮询刷新各个数码管。

IM/DM
按照教程的说法这两个模块是使用 ip_core 生成的,由于其同步读的特性,应该对流水线中的线路连通进行修改,大致工作如下:

  • 对于 DM 只需在 MW_REG 中将 W_dataOut 和 M_dataOut 短接并辅助特判即可:
      assign W_dataOut = (reset || flush || Req) ? 0 : M_dataOut;
    
  • 对于 IM 如法炮制,需要注意阻塞时的行为。
      reg [31:0] last_instr;
    
      assign D_instr = (reset || flush || Req) ? 0 :
                       (!en) ? last_instr : F_instr;
    
      always @(posedge clk) begin
          if(reset | flush | Req)begin
              last_instr <= 0;
              D_pc <= Req ? 32'h00004180 : 32'h00003000;
              D_ExcCode <= 0;
              D_BD <= 0;
          end
          else if(en)begin
              last_instr <= F_instr;
              D_pc <= F_pc;
              D_ExcCode <= F_ExcCode;
              D_BD <= F_BD;
          end
      end
    

mips_uart

管脚名位数in/out功能解释
clk1in系统时钟信号
reset1in复位信号
dataIn32inCPU 发往 UART 的数据
dataOut32outUART 发往 CPU 的数据
Addr2in读写数据地址
WE1inUART写使能信号
rxd1in外界发往UART的一字节数据
txd1outUART发往外界的一字节数据
UART_Int1out外界发送数据待接收的中断信号

mips_uart 即 uart_rx, uart_rd 的封装模块,主要维护 UART 的寄存器读写和rx, rd间的数据流和控制逻辑。重要控制信号和数据处理如下:

  assign UART_Int = rx_ready;

  assign LSR = {26'b0, tx_avai, 4'b0, rx_ready};

  assign rx_clear = WE;

  always @(posedge clk or posedge reset) begin
      if(reset)
          tx_start <= 0;
      else
          tx_start <= !tx_start && WE && (Addr == 0);
  end

可综合性的实现

由于本次实验需要生成可烧录的 .bit 文件,故 verilog 代码必须是可综合的;主要有两方面的工作,一时处理 IM、DM 的可综合性,二是优化乘除模块的可综合性。前者在上节已经介绍完毕,现介绍 MDU 模块的改进。

笔者将课程组提供的乘除模块封装入 P6 中自己构造的乘除模块中以减少主电路的改动。

    always @(*) begin
        if(Req)
            in_valid = 0;
        else begin      //译码阶段,根据不同的运算赋值控制信号。
            case (MDUCtrl)
                `MDUCtrl_mult:begin
                    in_op = 1;
                    in_sign = 1;
                    in_valid = 1;
                end
                `MDUCtrl_multu:begin
                    in_op = 1;
                    in_sign = 0;
                    in_valid = 1;
                end
                `MDUCtrl_div:begin
                    in_op = 2;
                    in_sign = 1;
                    in_valid = 1;
                end
                `MDUCtrl_divu:begin
                    in_op = 2;
                    in_sign = 0;
                    in_valid = 1;
                end
                default:begin
                    in_op = 0;
                    in_sign = 0;
                    in_valid = 0;
                end
            endcase
        end
        
    end

    MulDivUnit m_MulDivUnit(
        .clk(clk),
        .reset(reset),
        .in_src0(A),
        .in_src1(B),
        .in_op(in_op),
        .in_sign(in_sign),
        .in_ready(in_ready),
        .in_valid(in_valid),
        .out_ready(1),
        .out_valid(out_valid),
        .out_res0(lo),
        .out_res1(hi)
    );


    always @(posedge clk ) begin
        if(reset)begin
            HI <= 0;
            LO <= 0;
            Busy <= 0;
        end
        else if(!Req) begin
            if(MDUCtrl == `MDUCtrl_mthi)
                HI <= A;
            else if(MDUCtrl == `MDUCtrl_mtlo)
                LO <= A;
            
            if(out_valid)begin  //计算完毕将子模块的结果返回主乘除模块
                LO <= lo;
                HI <= HI;
            end

            Busy <= ~in_ready;

        end

    end

汇编程序的编写及思路

根据教程的提交要求,需要在我们编写的 CPU 上运行 mips 汇编程序以完成相应的任务;笔者把任务划分为三步:

  • 实现最基本的选择操作、计算、数码管及LED显示的控制。
  • 实现计时器功能。
  • 实现串口显示计算结果或计时时间,完善回写功能。

代码如下:
code_main

# Data Memory       0x0000_0000 - 0x0000_2fff
# Instr Memory		0x0000_3000 - 0x0000_6fff
# Timer0       		0x0000_7f00 - 0x0000_7f0b
# UART         		0x0000_7f30 - 0x0000_7f3f
# Digital Tube    	0x0000_7f50 - 0x0000_7f57
# Dip Switch     	0x0000_7f60 - 0x0000_7f67
# Button Key       	0x0000_7f68 - 0x0000_7f6b
# LED        		0x0000_7f70 - 0x0000_7f73
.text 0x3000

ori $t0, $0, 0xfc01
mtc0 $t0, $12

loop:

lbu $t0, 0x7f68($0)

lw $s0, 0x7f60($0) #B  /  preset
lw $s1, 0x7f64($0) #A

# 操作识别阶段
beq $t0, 1, TimerLEDPlus
nop
beq $t0, 3, TimerUARTPlus
nop
beq $t0, 5, TimerLEDMinus
nop
beq $t0, 7, TimerUARTMinus
nop
beq $t0, 4, AddLED
nop
beq $t0, 6, AddUART
nop
beq $t0, 8, SubLED
nop
beq $t0, 10, SubUART
nop
beq $t0, 16, MultLED
nop
beq $t0, 18, MultUART
nop
beq $t0, 32, DivLED
nop
beq $t0, 34, DivUART
nop
beq $t0, 64, AndLED
nop
beq $t0, 66, AndUART
nop
beq $t0, 128, OrLED
nop
beq $t0, 130, OrUART
nop
j End

#计时器指令
TimerLEDPlus:
	ori $t6, $0, 1
	
	li $t2, 25000000
	sw $t2, 0x7f04($0)

	li $t1, 11
	sw $t1, 0x7f00($0)


	li $t3, 0	#count from 0
	sw $t3, 0x7f50($0)
	sw $t3, 0x7f70($0)
	
	wait1:
		lbu $s3, 0x7f68($0)
		lw $s4, 0x7f60($0) #B  /  preset
		
		bne $s3, $t0, end1
		nop
		bne $s4, $s0, end1
		nop
		
		blt $t3, $s0, wait1
	nop
	
	end1:
	sw $0, 0x7f00($0)
	or $t6, $0, $0
	j End
	nop

TimerUARTPlus:
	ori $t6, $0, 1	
	
	li $t2, 25000000
	sw $t2, 0x7f04($0)

	li $t1, 11
	sw $t1, 0x7f00($0)
	
	or $t3, $0, $0	#count from 0
	or $t9, $0, $0
	
	wait3:
		lbu $s3, 0x7f68($0)
		lw $s4, 0x7f60($0) #B  /  preset
		
		bne $t3, $t9, Timer_UART
		nop
		return_TimerUARTPlus:
		add $t9, $t3, $0
		
		bne $s3, $t0, end3
		nop
		bne $s4, $s0, end3
		nop
		
		blt $t3, $s0, wait3
	nop
		
	end3:
	sw $0, 0x7f00($0)
	or $t6, $0, $0
	j End
	nop

TimerLEDMinus:
	ori $t6, $0, 1

	li $t2, 25000000
	sw $t2, 0x7f04($0)

	li $t1, 11
	sw $t1, 0x7f00($0)

	or $t3, $s0, $zero	#count from preset
	sw $t3, 0x7f50($0)
	sw $t3, 0x7f70($0)
	
	wait2:
		lbu $s3, 0x7f68($0)
		lw $s4, 0x7f60($0) #B  /  preset
		
		bne $s3, $t0, end2
		nop
		bne $s4, $s0, end2
		nop
		
		bnez $t3, wait2
		nop
		
	end2:
	sw $0, 0x7f00($0)
	or $t6, $0, $0
	j End
	nop

TimerUARTMinus:
	ori $t6, $0, 1	
	
	li $t2, 25000000
	sw $t2, 0x7f04($0)

	li $t1, 11
	sw $t1, 0x7f00($0)
	
	or $t3, $s0, $0	#count from preset
	or $t9, $s0, $0
	
	wait4:
		lbu $s3, 0x7f68($0)
		lw $s4, 0x7f60($0) #B  /  preset
		
		bne $t3, $t9, Timer_UART
		nop
		return_TimerUARTMinus:
		add $t9, $t3, $0
		
		bne $s3, $t0, end4
		nop
		bne $s4, $s0, end4
		nop
		
		bnez $t3, wait4
	nop
		
	end4:
	sw $0, 0x7f00($0)
	or $t6, $0, $0
	j End
	nop

#计算指令
AddLED:
	addu $t3, $s1, $s0
	sw $t3, 0x7f50($0)
	sw $t3, 0x7f70($0)
	j End
	nop

AddUART:
	addu $t3, $s1, $s0
	j Cal_UART
	nop

SubLED:
	subu $t3, $s1, $s0
	sw $t3, 0x7f50($0)
	sw $t3, 0x7f70($0)
	j End
	nop

SubUART:
	subu $t3, $s1, $s0
	j Cal_UART
	nop

MultLED:
	mult $s1, $s0
	mflo $t3
	sw $t3, 0x7f50($0)
	sw $t3, 0x7f70($0)
	j End
	nop

MultUART:
	mult $s1, $s0
	mflo $t3
	j Cal_UART
	nop

DivLED:
	div $s1, $s0
	mflo $t3
	sw $t3, 0x7f50($0)
	sw $t3, 0x7f70($0)
	j End
	nop

DivUART:
	div $s1, $s0
	mflo $t3
	j Cal_UART
	nop

AndLED:
	and $t3, $s1, $s0
	sw $t3, 0x7f50($0)
	sw $t3, 0x7f70($0)
	j End
	nop

AndUART:
	and $t3, $s1, $s0
	j Cal_UART
	nop

OrLED:
	or $t3, $s1, $s0 
	sw $t3, 0x7f50($0)
	sw $t3, 0x7f70($0)
	j End
	nop

OrUART:
	or $t3, $s1, $s0
	j Cal_UART
	nop

#针对计算指令的串口显示处理
Cal_UART:

	num3:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, num3
	nop
	srl $t5, $t3, 24
	sw $t5, 0x7f30($0)
	
	num32:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, num32
	nop
	srl $t5, $t3, 16
	sw $t5, 0x7f30($0)
	
	num2:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, num2
	nop
	srl $t5, $t3, 16
	sw $t5, 0x7f30($0)
	
	num21:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, num21
	nop
	srl $t5, $t3, 8
	sw $t5, 0x7f30($0)
	
	num1:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, num1
	nop
	srl $t5, $t3, 8
	sw $t5, 0x7f30($0)
	
	num10:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, num10
	nop
	srl $t5, $t3, 0
	sw $t5, 0x7f30($0)
	
	num0:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, num0
	nop
	srl $t5, $t3, 0
	sw $t5, 0x7f30($0)
	
	
	
	num_loop:
		lbu $s3, 0x7f68($0)
		lw $s4, 0x7f60($0) #B  /  preset
		lw $s5, 0x7f64($0)
		
		bne $s3, $t0, End
		nop
		bne $s4, $s0, End
		nop
		bne $s5, $s1, End
		nop
	j num_loop
	nop

#针对计时指令的串口显示处理
Timer_UART:
	time3:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, time3
	nop
	srl $t5, $t9, 24
	sw $t5, 0x7f30($0)
	
	time32:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, time32
	nop
	srl $t5, $t9, 16
	sw $t5, 0x7f30($0)
	
	time2:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, time2
	nop
	srl $t5, $t9, 16
	sw $t5, 0x7f30($0)
	
	time21:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, time21
	nop
	srl $t5, $t9, 8
	sw $t5, 0x7f30($0)
	
	time1:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, time1
	nop
	srl $t5, $t9, 8
	sw $t5, 0x7f30($0)
	
	time10:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, time10
	nop
	srl $t5, $t9, 0
	sw $t5, 0x7f30($0)
	
	time0:
	lw $t4, 0x7f34($0)	#LSR
	andi $t4, $t4, 32
	beqz $t4, time0
	nop
	srl $t5, $t9, 0
	sw $t5, 0x7f30($0)
	
	beq $t0, 3, return_TimerUARTPlus
	nop 
	beq $t0, 7, return_TimerUARTMinus
	nop

End:
	j loop
	nop

code_handle

.ktext 0x4180

nop
nop
nop

mfc0 $t7, $13
andi $t7, $t7, 4096 #判断是否为UART中断

check_tc0:
beqz $t6, check_uart    #判断是否为计时指令引起的中断
nop
	
	beq $t0, 1, plus
	nop
	beq $t0, 3, plus
	nop
	
    #更新时间并显示
	minus:
		subi $t3, $t3, 1
		sw $t3, 0x7f50($0)
		sw $t3, 0x7f70($0)
		j check_uart
		nop
		
	plus:
		addi $t3, $t3, 1
		sw $t3, 0x7f50($0)
		sw $t3, 0x7f70($0)
	
check_uart: 
beqz $t7, back_to_main
nop
    #回显功能的实现
	lw $a0, 0x7f30($0)
	sw $a0, 0x7f30($0)
	
back_to_main:
eret

问题与解决

就我本人来看,本次实验是我最为吃力的一次,遇到的主要问题和解决如下。

1.如何改动使ip核生成的 IM/DM 融入CPU?
一开始采用了简单的倍频方式,其后发现该方式存在一些难以理解的行为,且工程设计中并不会采用,故转用更加合理的修改数据通路的方式。

2.如何封装教程提供的UART子模块代码?
其实弄清楚这个草图可以应对本实验的要求:
在这里插入图片描述

3.如何实现串口发生一个字的数据?
串口以一个字节为一个单位发送数据,故可以在 mips 汇编代码中编写程序,对于一个字共发送4次,每次使用位移指令选择相应的位进行发送,在这期间要不断从 UART 的 LSR 中获取 UART 的工作状态,在上一个字节已经发送完毕后再进行发送。

4.如何实现回显功能?
为了不影响其他功能的实现,我采用的中断的方式进行处理,当UART接收到一个字节的数据后产生中断,进入 handle 程序执行回显指令。

5.如何实现计时器?
同样的,也采用 timer 提供中断信号的方式,用软件控制将 timer 设置成约1秒产生一次中断的工作模式,在中断中更新计时器的值即可。


最后的最后
一点感受:计组给我的锻炼到目前为止都是北航其他课程无法比拟的。
虽然每次课上实验都像一次“冒险”,但最终贯通整个架构,在 FPGA 上实现自己的 CPU 是一次十分难得的经历。
希望你也能体会到其中的乐趣 😃

彩蛋:
在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vanthon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值