Reindeer-RISCV学习笔记(2)

本文详细记录了RISC-V Reindeer核心移植到Vivado及Zybo开发板的过程,涉及DDR内存接口调整、地址映射、AXI总线使用以及启动流程等关键步骤。在移植过程中,发现了原代码的一些问题,并提出了改进方案,最终实现了成功运行。
摘要由CSDN通过智能技术生成

移植到vivado上,使用zybo开发板。

文件目录

core

在这里插入图片描述
在这里插入图片描述

include

在这里插入图片描述
在这里插入图片描述

memery

寄存器组使用双口ram

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
双口ram看起来没有问题,可以直接移植。

ddr改用axi_hp总线

见后文

移植到zybo

注意需要改成sv文件,以及编码格式改成gbk。

先试一下core

在这里插入图片描述
综合成功:
在这里插入图片描述

需要加个顶层文件,我们使用原来的reindeer工程进行修改:
在这里插入图片描述
暂时先测试core能否运行,所以先不增加外设,只使用core和memery。memery之后添加。

添加memery

mem_addr地址范围

先看一下mem_addr这个信号是什么涵义:
在这里插入图片描述
在这里插入图片描述

mem_addr(或ext_dram_mem_addr)范围是0起始,每次增加1。也就是说,sdram使用的是相对地址,与cpu总线地址无关。计算公式为:

mem_addr = (pc addr - 0x80000000)/4;

注意,mem_addr对应的是32位数据地址。
代码中如下:
在这里插入图片描述

此外,在上一篇提到了:16位bus的sdram地址一次增加2字节,因此sdram的访问地址和pc地址有这样的关系:(/tb_RV/uut/sdram_controller_i/mem_addr

sdram addr = (pc addr - 0x80000000)/4 * 2;

在这里插入图片描述
在这里插入图片描述

这里说一下如何同时使用SRAM与SDRAM

代码中没有使用SRAM:
在这里插入图片描述
如果更改配置文件,那么可以自动将SRAM添加进来。此时,SRAM与SDRAM公用mem_addr地址,此时SDRAM起始地址不是0。但是源代码这里没有处理,不知道是怎么回事,
在这里插入图片描述
这里应该减去SRAM的地址:
在这里插入图片描述

不然SDRAM前一部分空间就没有用上了。

dram_rw_buffer是干嘛用的

写数据时直接写入SDRAM,并且只有一个时钟延迟(首先写入寄存器,然后再写入SDRAM)。读数据则直接连接SDRAM接口。
在这里插入图片描述
在这里插入图片描述

buf_mem是256大小的数组,里面保存的是{读写位,地址}
在这里插入图片描述
write_addr表示buf_mem写入地址(从0开始递增),其值表示core发起了多少次读写请求并且未被处理。
在这里插入图片描述
read_addr表示buf_mem读出地址(从0开始递增),其值表示SDRAM待处理的地址。
在这里插入图片描述

因此可以看出,buf_mem是一个读取请求队列。每来一个读写请求,write_addr+1,随后状态变化为WAIT,pending信号置高。直到收到dram_ack信号,pending信号置零。

外部SDRAM访问控制:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
buf_mem:
在这里插入图片描述

个人认为这块代码有问题,第一没有必要256的缓存,几个就够了,第二不用缓存应该也可以,反正就是请求时就挂起呗。猜测作者当初是准备实现一个更高级的缓存机制的,例如
1、读SDRAM阻塞core,写SDRAM缓存,不阻塞core,此时需要缓存写数据;
2、一级缓存,这个就比较复杂了,简单来说就是写不缓存,直接写入SDRAM与一级缓存,读数据时首先在一级缓存中查找,如果不存在,阻塞core,然后读取SDRAM。

sdram_controller

这个时我们移植时,更改DDR时需要更改的模块。
在这里插入图片描述
主要的信号线:
在这里插入图片描述
在这里插入图片描述
可以看到接口分为三部分:reset,mem接口(连接MCU),avalon接口(连接片外SDRAM),我们要替换掉avalon接口。

先看看原来的模块干了些啥

在这里插入图片描述
在这里插入图片描述
mem_cs置高后就开始访问SDRAM,读取完毕后,将两个16位数据拼接成32位返回,同时ctl_mem_ack拉高。
在这里插入图片描述
写数据:
在这里插入图片描述
读数据:
在这里插入图片描述

替换接口

sdram_av_byteenable_n信号(注意,在代码里面,这个信号低电平有效,因此上面的写数据时,sdram_av_byteenable_n=0b00):
在这里插入图片描述
转自:https://wenku.baidu.com/view/4fe27c6077232f60ddcca1b8.html

mem_byteenable则是高电平有效,符合上表内容。

其他信号比较明显,这里就不写了。

zybo使用AXI访问内存

这里使用AXI_FULL协议访问,使用黑金的代码,这里只需要会用就行了。

//
// Company: ALINX黑金
// Engineer: 老梅
// 
// Create Date: 2016/11/17 10:27:06
// Design Name: 
// Module Name: mem_test
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//
module mem_test
#(
	parameter MEM_DATA_BITS = 64,
	parameter ADDR_BITS = 32
)
(
	input rst,                                 /*复位*/
	input mem_clk,                               /*接口时钟*/
	output reg rd_burst_req,                          /*读请求*/
	output reg wr_burst_req,                          /*写请求*/
	output reg[9:0] rd_burst_len,                     /*读数据长度*/
	output reg[9:0] wr_burst_len,                     /*写数据长度*/
	output reg[ADDR_BITS - 1:0] rd_burst_addr,        /*读首地址*/
	output reg[ADDR_BITS - 1:0] wr_burst_addr,        /*写首地址*/
	input rd_burst_data_valid,                  /*读出数据有效*/
	input wr_burst_data_req,                    /*写数据信号*/
	input[MEM_DATA_BITS - 1:0] rd_burst_data,   /*读出的数据*/
	output[MEM_DATA_BITS - 1:0] wr_burst_data,    /*写入的数据*/
	input rd_burst_finish,                      /*读完成*/
	input wr_burst_finish,                      /*写完成*/

	output reg error
);
parameter IDLE = 3'd0;
parameter MEM_READ = 3'd1;
parameter MEM_WRITE  = 3'd2;
parameter BURST_LEN = 128;

(*mark_debug="true"*)reg[2:0] state;
(*mark_debug="true"*)reg[7:0] wr_cnt;
reg[MEM_DATA_BITS - 1:0] wr_burst_data_reg;
assign wr_burst_data = wr_burst_data_reg;
(*mark_debug="true"*)reg[7:0] rd_cnt;
reg[31:0] write_read_len;
//assign error = (state == MEM_READ) && rd_burst_data_valid && (rd_burst_data != {(MEM_DATA_BITS/8){rd_cnt}});

always@(posedge mem_clk or posedge rst)
begin
	if(rst)
		error <= 1'b0;
	else if(state == MEM_READ && rd_burst_data_valid && rd_burst_data != {(MEM_DATA_BITS/8){rd_cnt}})
		error <= 1'b1;
end

//一次写burst
always@(posedge mem_clk or posedge rst)
begin
	if(rst)
	begin
		wr_burst_data_reg <= {MEM_DATA_BITS{1'b0}};
		wr_cnt <= 8'd0;
	end
	else if(state == MEM_WRITE)
	begin
		if(wr_burst_data_req/*写数据信号*/)
			begin
				wr_burst_data_reg <= {(MEM_DATA_BITS/8){wr_cnt}};
				wr_cnt <= wr_cnt + 8'd1;
			end
		else if(wr_burst_finish/*写完成*/)
			wr_cnt <= 8'd0;
	end
end

//一次读burst
always@(posedge mem_clk or posedge rst)
begin
	if(rst)
	begin
		rd_cnt <= 8'd0;
	end
	else if(state == MEM_READ)
	begin
		if(rd_burst_data_valid/*读出数据有效*/)
			begin
				rd_cnt <= rd_cnt + 8'd1;
			end
		else if(rd_burst_finish)
			rd_cnt <= 8'd0;
	end
	else
		rd_cnt <= 8'd0;
end

//状态机
always@(posedge mem_clk or posedge rst)
begin
	if(rst)
	begin
		state <= IDLE;
		wr_burst_req <= 1'b0;
		rd_burst_req <= 1'b0;
		rd_burst_len <= BURST_LEN;
		wr_burst_len <= BURST_LEN;
		rd_burst_addr <= 0;
		wr_burst_addr <= 0;
		write_read_len <= 32'd0;
	end
	else
	begin
		case(state)
			IDLE:
			begin
				state <= MEM_WRITE;
				wr_burst_req <= 1'b1;
				wr_burst_len <= BURST_LEN;
				wr_burst_addr <='h2000000; //首地址为0x2000000<<3 = 0x10000000
				write_read_len <= 32'd0;
			end
			MEM_WRITE:
			begin
				if(wr_burst_finish)
				begin
					state <= MEM_READ;
					wr_burst_req <= 1'b0;
					rd_burst_req <= 1'b1;
					rd_burst_len <= BURST_LEN;
					rd_burst_addr <= wr_burst_addr;
					write_read_len <= write_read_len + BURST_LEN;
				end
			end
			MEM_READ:
			begin
				if(rd_burst_finish)
				begin
				    if(write_read_len == 32'h2000000)
				    begin
						rd_burst_req <= 1'b0;
						state <= IDLE;
				    end
				    else
				    begin
						state <= MEM_WRITE;
						wr_burst_req <= 1'b1;
						wr_burst_len <= BURST_LEN;
						rd_burst_req <= 1'b0;
						wr_burst_addr <= wr_burst_addr + BURST_LEN;//加128*64 bit
					end
				end
			end
			default:
				state <= IDLE;
		endcase
	end
end

endmodule

现在我们要改成一次读写32位数据,当然未来可以一次读取多一点,做一个一级缓存,毕竟AXI_FULL总线还是比较好用的。现在先看看一次读写32位数据,并且根据reindeer的mem接口进行归一化。

AXI_FULL Master
在这里插入图片描述
发现上面的代码没有把WSTRB引出来,而且位宽什么的都不好改,看来只能自己写一个AXI_MASTER module了。

参考:https://www.cnblogs.com/milianke/p/15145563.html

注意一下这几个信号的位宽
在这里插入图片描述

新的axi_ddr_controller module

要实现的功能:

  1. 一次读写32位数据
  2. 写数据可以设置字节有效位
  3. mem_addr地址范围是0 ~ 8MB/4,需要转换为vivado给AXI_HP分配的新地址
  4. 读写成功后的ACK信号

mem interface

在这里插入图片描述

因为现在可以直接访问32位数据了,所以这里改成21位地址
在这里插入图片描述
在这里插入图片描述
调试了两天我就想说,怎么官方的代码也是有问题的,value信号和data信号差一个周期,我也是醉了。看来啥都得自己写。

在这里插入图片描述
测试了一下是OK的。

复位信号和启动信号和阻塞信号

关注一下CPU复位信号和启动信号,之后用硬核启动cpu,而不是上电自动运行。此外还可以阻塞cpu运行。

start信号
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
目前没有看到直接阻塞流水线的信号,dram_rw_pending可以起到阻塞流水线作用,但不保证整个流水线阻塞。有待验证。

顶层文件

看一下step_cyc10做了什么

晶振输入信号倍频到需要的时钟信号。
在这里插入图片描述
然后是SDRAM部分
在这里插入图片描述
然后是外设接口
在这里插入图片描述

现在重点看一下boot loader和MCU部分。

boot loader

使用UART进行load,并且控制CPU启动:
在这里插入图片描述
在这里插入图片描述

debug_coprocessor_wrapper控制boot loader 的整个流程:首先在debug_coprocessor_wrapper启动,下载程序,下载结束后输出cpu_start信号,即开始运行CPU。

注意一下,何时开始boot loader是由串口的TX数据控制的:
在这里插入图片描述
在这里插入图片描述
因为仿真的过程没有load code过程,因此在时钟准备好后,CPU自动从DEFAULT_START_ADDR启动
在这里插入图片描述
在CPU运行的过程中,debug模块监视TX信息并控制状态(这个没有测试,只是猜测)。

MCU

在这里插入图片描述

        PulseRain_Reindeer_MCU PulseRain_Reindeer_MCU_i (
            .clk (clk_100MHz),
            .reset_n ((~cpu_reset) & pll_locked), //active low
            .sync_reset (1'b0),
            
            .INTx ({ADXL345_INT2, int0}),

            .ocd_read_enable (ocd_read_enable),
            .ocd_write_enable (ocd_write_enable),
            
            .ocd_rw_addr (ocd_rw_addr),
            .ocd_write_word (ocd_write_word),
            
            .ocd_mem_enable_out (ocd_mem_enable_out),
				//assign ocd_mem_word_out = mem_read_data;
            .ocd_mem_word_out (ocd_mem_word_out),        
        
            .ocd_reg_read_addr (5'd2),
            .ocd_reg_we (cpu_start),
            .ocd_reg_write_addr (5'd2),
            .ocd_reg_write_data (`DEFAULT_STACK_ADDR),
        
            .RXD (RXD),
            .TXD (uart_tx_cpu),
            
            .GPIO_IN (gpio_in),
            .GPIO_OUT(gpio_out),
            
            .sda_in (ADXL345_SDA),
            .scl_in (ADXL345_SCL),
            
            .sda_out (sda_out),
            .scl_out (scl_out),
    
            .start (actual_cpu_start),
            .start_address (actual_start_addr),
        
            .processor_paused (processor_paused),
    
            .dram_ack             (dram_ack),
            .dram_mem_read_data   (dram_mem_read_data),
            
            .dram_mem_addr        (mcu_dram_mem_addr),
            .dram_mem_read_en     (dram_mem_read_en),
            .dram_mem_write_en    (mcu_dram_mem_write_en),
            .dram_mem_byte_enable (mcu_dram_mem_byte_enable),
            .dram_mem_write_data  (mcu_dram_mem_write_data),
    
            .peek_pc (),
            .peek_ir (),
            .peek_mem_write_en   (),
            .peek_mem_write_data (),
            .peek_mem_addr       ());

这部分比较简单,主要就是reset、start、OCD、外设、dram
在这里插入图片描述

完整的移植代码

在这里插入图片描述

约束文件

原工程的约束文件在Reindeer_Step-1.1.2\build\par\constraints\step_cyc10.sdc里面,主要是对SDRAM的约束,由于zybo使用片上ddr,并且由zynq提供主时钟,因此不需要约束。

load代码和运行

我在这里主要测试了一下依次执行的指令是否正确,个人认为,取值都是对的话,那肯定cpu运行没问题啦,细节没有测试,之后慢慢研究。

在这里插入图片描述

上图对比,实际运行和仿真一致。

接下来准备给这个riscv软核加个协处理器接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

朽木白露

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

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

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

打赏作者

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

抵扣说明:

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

余额充值