lab2:SCPU
验收要求
实验目的:建立一个调试CPU模块的环境,后续lab4会对SCPU模块单独构造,然后替换lab2实验中的SCPU部分,再在本实验中搭建的环境中测试SCPU功能.即lab2只需要在顶层模块中组织各子模块即可,各子模块功能只需要大概了解即可.
验收需要在数码管和VGA上显示I_mem.coe的功能:
-
数码管上:
调整SW[8], SW[2] = 0, 1;观察单步时钟的结果;
调整SW[8], SW[2] = 1, 0;来回拨动SW[10]观察手动调试的结果;
通过调整SW[7:5],观察
alu_res,pc,inst等输出; -
VGA上:
主要观察内容:
alu_res的结果为alu输出;PC显示的是字节地址,每次增加4;inst为指令内容;逻辑图中VGA模块的输入包含被显示的内容,显示器上其他部分(如寄存器组)没有接入VGA因此显示全0.
注意PPT P89的复位按键,可以将PC置0,即demo程序从头开始运行.
参考文献:瓜豪的2024CO文档
如何使用Tcl Console进行综合
Synthesis:
reset_run synth_1
launch_runs synth_1 -jobs 4
wait_on_run synth_1
# 检查综合是否真的在运行
# 运行综合后,在 Tcl Console 中应该看到类似输出:
launch_runs synth_1 -jobs 4
[Sat Oct 18 XX:XX:XX 2025] Launched synth_1...
Run output will be captured here: ...
Implementation:
# 重置 implementation 运行
reset_run impl_1
# 运行 implementation
launch_runs impl_1 -jobs 4
# 等待完成
wait_on_run impl_1
实验吐槽
首先是犯下了傲慢之罪的Windows,得益于他们的更新,我的Vivado驱动被删除了;
其次是糟糕的Vivado版本兼容问题,我为了不踩坑,把曾经嫌弃的Vivado 2024.2下载回来了,然后IP Core全部都要重新生成一遍;
最后是设备,我没招了,经常连接不好……一个原本能10.14完成的lab硬是多拖了5天.
IP核集成SOC设计——建立CPU调试、测试与应用环境¶
添加文件¶
-
Seg7_Dev_0:添加IP Catalog中的IP核在Vivado中打开项目
OExp02-IP2SOC,在Flow Navigator中点击Settings选择IP → Repository,点击+按钮,浏览到OExp02-IP2SOC/IP/目录,选择包含IP核的Seg7_Dev_0文件夹(包含component.xml的目录).
点击Select,Vivado会自动扫描并识别IP核,点击OK完成添加.
然后在左边栏中点击IP Catalog,搜索并找到你添加的IP,右键选择Customize IP,点击Generate即可. -
顶层目录中的
.edf和.v文件:直接Add Design Sources就行. -
VGA模块:添加其中的srcs里面所有的
.v文件.
IP Core的生成与文件关联¶
我们需要生成以下模块:Distributed Memory Generator. 它会在下面部分的煎熬的连线中使用到.
只需要按照lab0中的ROM生成方法操作就行了.
添加顶层模块CSSTE.v并连线¶
写了一份demo,眼睛快花了,这个应该是没问题的版本:
CSSTE.v完整代码
module CSSTE(
input clk_100mhz,
input RSTN,
input [3:0] BTN_y,
input [15:0] SW,
output [3:0] Blue,
output [3:0] Green,
output [3:0] Red,
output HSYNC,
output VSYNC,
output [15:0] LED_out,
output [7:0] AN,
output [7:0] segment
);
wire [3:0] BTN_OK;
wire [15:0] SW_OK;
wire rst;
SAnti_jitter U9(
.clk(clk_100mhz),
.RSTN(RSTN),
.readn(1'b0),
.Key_y(BTN_y),
.Key_x(),
.SW(SW),
.Key_out(),
.Key_ready(Key_ready),
.pulse_out(),
.BTN_OK(BTN_OK),
.SW_OK(SW_OK),
.CR(),
.rst(rst)
);
wire [31:0] clkdiv;
wire Clk_CPU;
clk_div U8(
.clk(clk_100mhz),
.rst(rst),
.SW2(SW_OK[2]),
.SW8(SW_OK[8]),
.STEP(SW_OK[10] | BTN_OK[0]),
.clkdiv(clkdiv),
.Clk_CPU(Clk_CPU)
);
wire [31:0] Cpu_data4bus;
wire MemRW;
wire [31:0] Addr_out;
wire [31:0] Data_out;
wire [31:0] PC_out;
wire [31:0] inst_in;
SCPU U1(
.clk(Clk_CPU),
.rst(rst),
.Data_in(Cpu_data4bus),
.inst_in(inst_in),
.MIO_ready(1'b0),
.MemRW(MemRW),
.Addr_out(Addr_out),
.Data_out(Data_out),
.PC_out(PC_out),
.CPU_MIO()
);
wire [31:0] data_ram_we;
wire [9:0] ram_addr;
wire [31:0] ram_data_in;
wire [31:0] ram_data_out;
wire [31:0] douta;
wire [31:0] Peripheral_in;
wire counter_we;
wire [1:0] counter_set;
wire [31:0] counter_out;
wire counter0_OUT;
wire counter1_OUT;
wire counter2_OUT;
wire EN;
wire FN;
MIO_BUS U4(
.clk(clk_100mhz),
.rst(rst),
.BTN(BTN_OK),
.SW(SW_OK),
.mem_w(MemRW),
.Cpu_data2bus(Data_out),
.addr_bus(Addr_out),
.ram_data_out(ram_data_out),
.led_out(LED_out),
.counter_out(counter_out),
.counter0_out(counter0_OUT),
.counter1_out(counter1_OUT),
.counter2_out(counter2_OUT),
.Cpu_data4bus(Cpu_data4bus),
.ram_data_in(ram_data_in),
.ram_addr(ram_addr),
.data_ram_we(data_ram_we),
.GPIOf0000000_we(FN),
.GPIOe0000000_we(EN),
.counter_we(counter_we),
.Peripheral_in(Peripheral_in)
);
Counter_x U10(
.clk(~Clk_CPU),
.rst(rst),
.clk0(clkdiv[6]),
.clk1(clkdiv[9]),
.clk2(clkdiv[11]),
.counter_we(counter_we),
.counter_val(Peripheral_in),
.counter_ch(counter_set),
.counter0_OUT(counter0_OUT),
.counter1_OUT(counter1_OUT),
.counter2_OUT(counter2_OUT),
.counter_out(counter_out)
);
wire [7:0] LE_out;
wire [31:0] Disp_num;
wire [7:0] point_out;
Multi_8CH32 U5(
.clk(~Clk_CPU),
.rst(rst),
.EN(EN),
.Test(SW_OK[7:5]),
.point_in({clkdiv[31:0],clkdiv[31:0]}),
.LES(64'b0),
.Data0(Peripheral_in),
.data1({2'b0,PC_out[31:2]}),
.data2(inst_in),
.data3(counter_out),
.data4(Addr_out),
.data5(Data_out),
.data6(Cpu_data4bus),
.data7(PC_out),
.point_out(point_out),
.LE_out(LE_out),
.Disp_num(Disp_num)
);
Seg7_Dev_0 U6(
.disp_num(Disp_num),
.point(point_out),
.les(LE_out),
.scan({clkdiv[18],clkdiv[17],clkdiv[16]}),
.AN(AN),
.segment(segment)
);
SPIO U7(
.clk(~Clk_CPU),
.rst(rst),
.Start(clkdiv[20]),
.EN(FN),
.P_Data(Peripheral_in),
.counter_set(counter_set),
.LED_out(LED_out),
.led_clk(),
.led_sout(),
.led_clrn(),
.LED_PEN(),
.GPIOf0()
);
VGA U11(
.clk_25m(clkdiv[1]),
.clk_100m(clk_100mhz),
.rst(rst),
.pc(PC_out),
.inst(inst_in),
.alu_res(Addr_out),
.mem_wen(MemRW),
.dmem_o_data(ram_data_out),
.dmem_i_data(ram_data_in),
.dmem_addr(Addr_out),
.hs(HSYNC),
.vs(VSYNC),
.vga_r(Red),
.vga_g(Green),
.vga_b(Blue)
);
RAM_B U3(
.clka(~clk_100mhz),
.wea(data_ram_we),
.addra(ram_addr),
.dina(ram_data_in),
.douta(ram_data_out)
);
I_mem U2(
.a(PC_out[11:2]),
.spo(inst_in)
);
endmodule
VGA处理¶
打开VGAdisplay.v:
VGAdisplay.v代码
module VgaDisplay(
input wire clk,
input wire video_on,
input wire [9:0] vga_x,
input wire [8:0] vga_y,
output wire [3:0] vga_r,
output wire [3:0] vga_g,
output wire [3:0] vga_b,
input wire wen,
input wire [11:0] w_addr,
input wire [7:0] w_data
);
(* ram_style = "block" *) reg [7:0] display_data[0:4095];
initial $readmemh("D://vga_debugger.mem", display_data);
wire [11:0] text_index = (vga_y / 16) * 80 + vga_x / 8;
// I don't know why I need this '- (vga_y / 16)' ...
wire [7:0] text_ascii = display_data[text_index] - (vga_y / 16);
wire [2:0] font_x = vga_x % 8;
wire [3:0] font_y = vga_y % 16;
wire [11:0] font_addr = text_ascii * 16 + font_y;
(* ram_style = "block" *) reg [7:0] fonts_data[0:4095];
initial $readmemh("D://font_8x16.mem", fonts_data);
wire [7:0] font_data = fonts_data[font_addr];
assign { vga_r, vga_g, vga_b } = (video_on & font_data[7 - font_x]) ? 12'hfff : 12'h0;
always @(posedge clk) begin
if (wen) begin
display_data[w_addr] <= w_data;
end
end
endmodule
修改其中的两个路径:(比如我是这么存的)
···
initial $readmemh("E://Vivadodemo//lab2//OExp02-IP2SOC//vga_debugger.mem", display_data);
···
initial $readmemh("E://Vivadodemo//lab2//OExp02-IP2SOC//font_8x16.mem", fonts_data);
···
下板¶
按钮说明
SW[8]SW[2] 用于控制 CPU 时钟.
SW[8]SW[2]=00: CPU 全速时钟 100MHZ.
SW[8]SW[2]=01: CPU 自动单步时钟.
SW[8]SW[2]=1X: CPU 手动单步时钟.
SW[10] 用于手动时钟模式.此时拨一下 SW[10](即从 0 拨到 1)时钟增加一个周期,相应地我们也会执行一条指令.
SW[7:5] 用于控制七段数码管的显示.
SW[7:5]=001: 显示 CPU 指令字地址 PC_out[31:2],即我们下一条要执行的指令的地址.
SW[7:5]=010: 显示 ROM 指令输出 Inst_in,即我们正在执行的指令.
SW[7:5]=100: 显示 CPU 数据存储地址 addr_bus,在 Lab4 中你将会了解到 addr_bus 实际上就是 ALU 的运算结果.
SW[7:5]=101: 显示 CPU 数据输出 Cpu_data2bus,在 Lab4 中你将会了解到 Cpu_data2bus 实际上就是寄存器 B 的值(假设每个周期从寄存器堆中读取 A、B 寄存器).
SW[7:5]=110: 显示 CPU 数据输入 Cpu_data4bus,在 Lab4 中你将会了解到 Cpu_data4bus 实际上就是 RAM 的输出,即从内存中读出来的值.
SW[7:5]=111: 显示 CPU 指令字地址 PC_out.
这个部分看课程slides的最后部分就可以了.