繁忙的一个学期终于过去啦!考完试在学校呆着这几天没什么事写个数电实验的博客
我们组为了赶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
软件部分不是我完成的, 所以没有贴出来。