NJUCS南京大学数电实验期末大作业-计算系统(单周期cpu实现)

这篇博客介绍了基于FPGA的CPU系统设计,包括CPU与外设的交互,如键盘、VGA显示器的连接,以及内存映射的实现。CPU通过读写不同内存地址与外设进行通信,例如键盘缓冲区用于CPU读取按键信息,VGA内存用于显示。此外,还详细说明了滚屏功能的实现和各种内存区域的定义,如数据存储器、指令存储器等。
摘要由CSDN通过智能技术生成

繁忙的一个学期终于过去啦!考完试在学校呆着这几天没什么事写个数电实验的博客
我们组为了赶ddl,做的功能很少,完成了基础功能,加上一些非常简单的拓展功能
基础功能有:
在这里插入图片描述
代码的压缩包会发在最后面,本文主要是对于代码的解释
一个顶层模块,连接着cpu模块,数据存储器模块,指令存储器模块,键盘,缓冲,vga等等模块

内存映射&读写方式

分为以下几种内存:(含其内存地址开头)

  • instruction_memory(IP核生成的ROM 0x000,cpu只读)
  • data_memory(IP核生成的双口RAM 0x001,cpu可读可写)
  • vga_memory (IP核生成的RAM 0x002,cpu只写,外设只读)
  • keyboard_buffer(手写0x003,cpu只读,外设只写)
  • color_mem(记录颜色的寄存器,手写,0x004,cpu只写)
  • timer(记录时间的寄存器,手写,0x005,cpu只读)
  • led_mem(记录led状态的寄存器,手写,0x006,cpu只写)
  • hex_mem(记录数码管状态的寄存器,手写,0x007,cpu只写)
  • vga_offset(用来实现滚屏功能,下面会详细解释,手写,0x008,cpu只写)

各部分组成

CPU

沿用exp11已经封装好的cpu模块

module cpu(
	input 	clock,
	input 	reset,
	output [31:0] imemaddr,
	input  [31:0] imemdataout,
	output 	imemclk,
	output [31:0] dmemaddr,
	input  [31:0] dmemdataout,
	output [31:0] dmemdatain,
	output 	dmemrdclk,
	output	dmemwrclk,
	output [2:0] dmemop,
	output	dmemwe
	);

指令存储器(IP核生成的RAM 0x000,cpu只读)

imem_rom my_imem(
.address(imemaddr[17:2]),
.clock(imemclk),
.q(imemdataout)
);

数据存储器(IP核生成的双口RAM 0x001,cpu可读可写)

生成一个byteenable信号,ram32768*32bit , 双口双时钟

module mem(
	input  [31:0] addr,
	output reg [31:0] dataout,
	input  [31:0] datain,
	input  rdclk,
	input  wrclk,
	input [2:0] memop,
	input we
);
wire [31:0] tempout;wire[14:0]address=addr[16:2];
wire [31:0]in_data=datain<<(8*addr[1:0]);
reg[3:0]byteena_a;
wire [31:0]out_data=tempout>>(8*addr[1:0]);
    
dmem_ram ram_1(.byteena_a(byteena_a),.data(in_data),
.rdaddress(address),.rdclock(rdclk),
.wraddress(address),.wrclock(wrclk),
.wren(we),.q(tempout));

always@(*)begin
	case(memop[1:0])
	2'b10:begin//字节
		case(addr[1:0])
		2'b00:byteena_a=4'b1111;
		default:byteena_a=4'b0000;
		endcase
	end
	2'b01:begin//半字节
		case(addr[1:0])
		2'b00:byteena_a=4'b0011;		
		2'b10:byteena_a=4'b1100;		
		default:byteena_a=4'b0000;
		endcase
	end
	2'b00:begin//四分之一字节
		case(addr[1:0])
		2'b00:byteena_a=4'b0001;
		2'b01:byteena_a=4'b0010;
		2'b10:byteena_a=4'b0100;
		2'b11:byteena_a=4'b1000;
		default:byteena_a=4'b0000;
		endcase
	end
	default:byteena_a=4'b0000;
	endcase
end

always@(*)begin
	case(memop)
	3'b000:dataout={{24{out_data[7]}},
	out_data[7:0]};			
	3'b001:dataout={{16{out_data[15]}},
	out_data[15:0]};		
	3'b100:dataout={24'b0,
	out_data[7:0]};			
	3'b101:dataout={16'b0,
	out_data[15:0]};			
	default:dataout=out_data;	
	endcase
end

endmodule 

外设-键盘

沿用实验7键盘模块,也就是键盘的底层部分加以简单处理,有按键时输出其asc2码(已经处理了大小写),没有按键时输出0。

module keyboard_basic(
	input 		          		clk,
	output [7:0] data,
	inout 		          		ps2_clk,
	inout 		          		ps2_data
);

外设-vga

沿用实验8vga模块

CPU - VGA 映射实现

vga显存由IP核生成,cpu只写,外设只读

下面这个模块实例化在vga模块,是vga显存

vga_ram ram1(
.rdaddress(block_addr),
.wraddress(writeaddress),
.rdclock(CLOCK_50),
.wrclock(vga_mem_clk),
.data(writedata),
.wren(we),
.q(asc2)
);

writedata,writeaddress,vga_mem_clk,we是从cpu传过来的,下面是顶层模块对vga模块的调用

vga vga_top(
//下面几行是exp8里面就有的,是vga底层模块需要的
.VGA_CLK(VGA_CLK),
.CLOCK_50(CLOCK_50),
.VGA_BLANK_N(VGA_BLANK_N),
.VGA_VS(VGA_VS),
.VGA_HS(VGA_HS),
.VGA_B(VGA_B),
.VGA_G(VGA_G),
.VGA_R(VGA_R),
//下面几行是新加的,color控制字符颜色,vga_offset是用来滚屏,其他是往显存里面写入的内容
.color(color_num),
.we(vgawe),
.writedata(dmemdatain[7:0]),
.writeaddress(dmemaddr[11:0]),
.vga_mem_clk(dmemwrclk),
.vga_offset(vga_offset)
); 
滚屏的实现

内存地址为0x00800000的存储单元里面存储vga_offset,vga显示模块从显存这一行作为起始行读,读到最后一行循环回第一行,直到读出全部30行

具体就体现在下面这一行

//以前是block_addr <= (vaddr >> 4) * 70 + haddr / 9;
//加上滚屏是
block_addr <= (((vaddr >> 4)+vga_offset)%30) * 70 + ((haddr) / 9);

CPU - 键盘 映射实现

cpu和键盘的交互由一个缓冲区来实现,这个缓冲区是cpu只读,外设只写

缓冲区是一个循环队列,由tail和head两个指针,head=tail表示缓冲区为空

对于外设来说:

keyboardata是由外设(键盘底层模块)给出的,在上面的键盘部分给出了含义,有按键时输出其asc2码(已经处理了大小写),没有按键时输出0。

在buffer模块内部,根据keyboarddata判断如果是输入了一个新的字符,就往缓冲区tail所指的地方写入字符,tail++

对于cpu来说:

buffer和数据存储器一样就是一个存储器,buffer根据cpu提供的时钟和读使能进行判断,如果buffer不为空(head!=tail),就返回对应的asc2码,head++,否则返回8’b0,在软件中对根据读到的东西进行判断

下面是buffer部分

module buffer(
input [7:0] keyboarddata,
input re,
input clock,
output reg [7:0] bufferout,
input rdclk
);

reg [7:0] head=0;
reg [7:0] tail=0; 
reg we=1;
reg [7:0] wrdata;
reg [7:0] buffer [7:0];
reg [7:0] lastinput;
//keyboard只写
always @ (posedge clock) begin
	buffer[tail][7:0]<=keyboarddata[7:0];
	if(keyboarddata!=lastinput && keyboarddata != 0) begin
		if(tail==3'b111)
			tail<=0;
		else
			tail<=tail+1;
	end
	lastinput=keyboarddata; 
end
//cpu只读
always @ (posedge rdclk) begin
	if(re) begin
 		if(head==tail) 
			bufferout<=8'b0;
		else begin
			bufferout<=buffer[head];
			if(head==3'b111)
				head<=0;
			else
				head<=head+1;
		end
	end
end
endmodule

CPU - 其他外设 映射实现

这几个比较简单,就不多赘述了,方法都是找一个内存单元储存相关的信息

  • LED
assign ledwe=(dmemaddr[31:20]==12'h006)?dmemwe:1'b0;
reg [9:0] leds [9:0];
always @ (posedge dmemwrclk) begin
	if(ledwe)
		leds[dmemaddr[3:0]][0]<=dmemdatain[0];
end
assign LEDR[0]=leds[0][0];
assign LEDR[1]=leds[1][0];
assign LEDR[2]=leds[2][0];
assign LEDR[3]=leds[3][0];
assign LEDR[4]=leds[4][0];
assign LEDR[5]=leds[5][0];
assign LEDR[6]=leds[6][0];
assign LEDR[7]=leds[7][0];
assign LEDR[8]=leds[8][0];
assign LEDR[9]=leds[9][0];
  • color(字符颜色)
assign colorwe=(dmemaddr[31:20]==12'h004)?dmemwe:1'b0;
color color_mem(
.we(colorwe),
.wrclk(dmemwrclk),
.wrdata(dmemdatain[3:0]),
.q(color_num)
);
  • 数码管
wire hexwe;
assign hexwe=(dmemaddr[31:20]==12'h007)?dmemwe:1'b0;
reg [7:0] hex [7:0];
always @ (posedge dmemwrclk) begin
	if(hexwe)
        hex[dmemaddr[3:0]][3:0]<=dmemdatain[3:0];
end
bcd7seg bcd0(hex[0][3:0],HEX0);
bcd7seg bcd1(hex[1][3:0],HEX1);
bcd7seg bcd2(hex[2][3:0],HEX2);
bcd7seg bcd3(hex[3][3:0],HEX3);
bcd7seg bcd4(hex[4][3:0],HEX4);
bcd7seg bcd5(hex[5][3:0],HEX5);

软硬件映射

硬件上,对于cpu来说,所有的模块其实都是一个个存储器,我不管你是数据存储器,指令存储器,显存还是键盘缓冲

在存储器写时钟上升沿到来的时候,我把要写入的数据,送给所有可以写的模块,但是高12位匹配的模块的写使能为1

在存储器读时钟上升沿到来的时候,所有可以读的模块,都把对应地址的内容发给我,我根据地址高12位选择我要的数据

assign datawe=(dmemaddr[31:20]==12'h001)?dmemwe:1'b0;
assign datawe=(dmemaddr[31:20]==12'h001)?dmemwe:1'b0;
assign keyre=(dmemaddr[31:20]==12'h003)?1'b1:1'b0;
assign colorwe=(dmemaddr[31:20]==12'h004)?dmemwe:1'b0;
assign ledwe=(dmemaddr[31:20]==12'h006)?dmemwe:1'b0;
assign hexwe=(dmemaddr[31:20]==12'h007)?dmemwe:1'b0;
assign vga_offset_we=(dmemaddr[31:20]==12'h008)?dmemwe:1'b0;
always @ (*) begin
	if(dmemaddr[31:20]==12'h001)
		ddata=dmemdataout;
	else if(dmemaddr[31:20]==12'h003)
		ddata=bufferout;
	else if(dmemaddr[31:20]==12'h005)
		ddata=time_cnt;
	else
		ddata=32'b0;
end

软件部分不是我完成的, 所以没有贴出来。

压缩包超链接

压缩包超链接

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值