[笔记].浅析在Nios II中的两种寄存器映射方法的异同

摘要:
这里我以我编写的MAX7219为例,从HDL接口描述到C语言软件编程,分析两种表面上不同但本质上相同的寄存器映射方法,并找出它们之间的关系和区别。IOWR的三个输入参数是IP基地址、所用寄存器的偏移地址和分配给所用寄存器值。用于寄存器存储映射的偏移地址是HDL_address中的avs。

此处以我所写的MAX7219为范例,从HDL接口描述到C语言软件编程,分析两种表面不一样、但实质是一样的寄存器映射方法,找出其中联系与区别。

方法1 使用Altera提供的API

1. 使用HDL描述Avalon-MM接口
代码1 Amy_S_max7219_avalon_interface.v
/*-----版权声明-----
 *     艾米电子工作室——让开发变得更简单 
 *     网站:http://www.amy-studio.com
 *     淘宝:http://amy-studio.taobao.com
 *     QQ(邮箱):amy-studio@qq.com
 *-----文件信息-----
 *     文件名称:Amy_S_max7219_avalon_interface.v
 *     最后修改日期:3.20, 2010
 *     描述:Max7219的Avalon接口描述文件
 *------------------
 *     创建者:张亚峰
 *     创建日期:3.20, 2009
 *     版本:1.0
 *     描述:原始版本
 *------------------
 *     修改者:
 *     修改日期:
 *     版本:
 *     描述:
 *-------------------
 */

module Amy_S_max7219_avalon_interface(
  // Clcok Input
  input         csi_clk,
  input         csi_reset_n,
  // Avalon-MM Slave
  input         avs_chipselect,
  input [1:0]   avs_address,
  input         avs_write,
  input [31:0]  avs_writedata,
  // Conduit End 
  output reg    coe_din,
  output reg    coe_cs, 
  output reg    coe_clk
);

// write
always@(posedge csi_clk, negedge csi_reset_n)
begin
  if (!csi_reset_n)
  begin 
    coe_din <= 1'b0;
    coe_cs  <= 1'b0;
    coe_clk <= 1'b0;    
  end
  else if (avs_chipselect & avs_write)
  begin
    case (avs_address)
      0: coe_din <= avs_writedata[0];
      1: coe_cs  <= avs_writedata[0];
      2: coe_clk <= avs_writedata[0];
    endcase
  end
end

endmodule

<

;p>在这里,使用了3个寄存器,并通过avs_address来寻址。从50~52行,可以看出,这三个寄存器的偏移地址(Offset)分别是0、1和2。

2. 使用C语言编写寄存器映射文件
代码2 Amy_S_max7219.h 片段
//++++++++++++++++++++++++++++++++++++++
// 寄存器映射 开始
// 根据HDL编写
//++++++++++++++++++++++++++++++++++++++
#include 

#define IOWR_MAX7219_DIN(base, data)   IOWR(base, 0, data)
#define IOWR_MAX7219_CS(base, data)    IOWR(base, 1, data)
#define IOWR_MAX7219_CLK(base, data)   IOWR(base, 2, data)
//--------------------------------------
// 寄存器映射 结束
//--------------------------------------

注意:结尾那个</io.h>是发博客发出来的,不属于代码。

由于是使用ALtera的API——IOWR(),因此第5行,就得加上#include <io.h>。IOWR(base, offset, data)的3个输入参数,分别是IP的基地址,所使用寄存器的偏移地址,欲给所使用寄存器赋的值。寄存器的存储映射所使用的偏移地址,是有HDL中avs_address决定的。(avs avalon slave 阿窝龙从设备)

代码3 Amy_S_max7219.h 片段

代码描述:使用上面的已经映射好的函数

//++++++++++++++++++++++++++++++++++++++
// 基地址 开始
// 根据SOPC Builder设置编写
//++++++++++++++++++++++++++++++++++++++
#include "system.h"

#define max7219_addr MAX7219_BASE
//--------------------------------------
// 基地址 结束
//--------------------------------------


//++++++++++++++++++++++++++++++++++++++
// 寄存器映射 开始
// 根据HDL编写
//++++++++++++++++++++++++++++++++++++++
#include 

#define IOWR_MAX7219_DIN(base, data)   IOWR(base, 0, data)
#define IOWR_MAX7219_CS(base, data)    IOWR(base, 1, data)
#define IOWR_MAX7219_CLK(base, data)   IOWR(base, 2, data)
//--------------------------------------
// 寄存器映射 结束
//--------------------------------------


//++++++++++++++++++++++++++++++++++++++
// 管脚操作 开始
//++++++++++++++++++++++++++++++++++++++
#define SET_DIN IOWR_MAX7219_DIN(max7219_addr, 1)
#define CLR_DIN IOWR_MAX7219_DIN(max7219_addr, 0)
#define SET_CS  IOWR_MAX7219_CS(max7219_addr, 1)
#define CLR_CS  IOWR_MAX7219_CS(max7219_addr, 0)
#define SET_CLK IOWR_MAX7219_CLK(max7219_addr, 1)
#define CLR_CLK IOWR_MAX7219_CLK(max7219_addr, 0)
//--------------------------------------
// 管脚操作 结束
//--------------------------------------

注意:结尾那个</io.h>是发博客发出来的,不属于代码

代码4 Amy_S_max7219.c代码片段

代码描述:使用Altera API的具体操作

#include "Amy_S_max7219.h"

/*
 * 发送一个字节的子程序:
 * 上升沿发送数据,
 * MSB first
 */
void Max7219_WriteByte(alt_u8 byte)
{
  alt_u8 i;
  for (i=0; i<8; i++)
  {
    CLR_CLK;
    if(byte & 0x80)
      SET_DIN;
    else
      CLR_DIN;
    byte <<= 1;
    SET_CLK;
  }
}

至此,使用Altera的API来描述寄存器存储映射的方法,告一段落。

方法2 使用位域或结构体

其实这种方法,Altera的API的源代码有时也会用到。但是有一个地方需要注意,后面会提到。

1. 使用HDL描述Avalon-MM接口

如上。

2. 使用C语言编写寄存器映射文件
代码4 Amy_S_max7219.h 片段
//++++++++++++++++++++++++++++++++++++++
// 寄存器映射 开始
// 根据HDL编写
//++++++++++++++++++++++++++++++++++++++
#include "system.h"
#include "alt_types.h"

typedef struct
{
  alt_u32 DIN : 32;
  alt_u32 CS  : 32;
  alt_u32 CLK : 32;
}MAX7219_T;

#define m7219 ((MAX7219_T *)(MAX7219_BASE))
//--------------------------------------
// 寄存器映射 结束
//--------------------------------------

因为Nios II是32位的处理 器,所以之前定义了3个寄存器,都是32位的。此处为了表达这种关系,我们使用了位域。将这个位域(或结构体)重定义为一个类型,然后定义一个该类型的指针变量,起始地址是所需的基地址。这样做,就可以很好地为从基地址开始的连续的3x32位数据寻址(此处为3个寄存器,故数据总长3x32)。

代码5 Amy_S_max7219.c代码片段

代码描述:使用结构体指针寻址示例

/*
 * 发送一个字节的子程序:
 * 上升沿发送数据,
 * MSB first
 */
void Max7219_WriteByte(alt_u8 byte)
{
  alt_u8 i;
  for (i=0; i<8; i++)
  {
    m7219->CLK = 0;
    if(byte & 0x80)
      m7219->DIN = 1;
    else
      m7219->DIN = 0;
    byte <<= 1;
    m7219->CLK = 1;
  }
}

哈哈,是不是可以直接赋值了,更加像单片机了吧。其实Nios II就是单片机,32位的单片机。

做到这里,有些实验者在开发板上演练时,确实成功了;然而有些没有成功?这是为什么呢?我们先看参考资料1。

代码6 两种寄存器存储映射所对应的汇编
IOWR=32DIRECT(GPIO_LED_BASE, 0, 1); 
0x04000234 : movhi r3,2048 
0x04000238 : addi r3,r3,6144 
0x0400023c : movi r2,1 
0x04000240 : stwio r2,0(r3)

LED = 1; 
0x04000224 : movhi r3,2048 
0x04000228 : addi r3,r3,6144 
0x0400022c : movi r2,1 
0x04000230 : stw r2,0(r3)

看到没有,两种寄存器存储映射所对应的汇编不一样。看关键字,一个是stwio,一个是stw。接下来打开手册,Table 3-36。

表1 宽数据传输指令

表1 宽数据传输指令

手册上清楚地写到,I/O外设的数据传输应该使用ldwio和stwio;这两条指令在传输时,是没有cache和buffer的。那怎样让结构体指针的寄存器映射方式也能使用ldwio和stwio呢。接着看手册,在98页,Cache Memory小节,写到image 。那还有其他方法来实现cache bypass吗?第38页写到:

图1 cache bypass的方法

图1 Cache Bypass Method

图2 The Bit-31 Cache Bypass Method

图2 The Bit-31 Cache Bypass Method

好的,看代码。

代码7 system.h片段
#define ALT_MODULE_CLASS_max7219 Amy_S_max7219
#define MAX7219_BASE 0x1002020
#define MAX7219_IRQ -1
#define MAX7219_IRQ_INTERRUPT_CONTROLLER_ID -1
#define MAX7219_NAME "/dev/max7219"
#define MAX7219_SPAN 16
#define MAX7219_TYPE "Amy_S_max7219"

MAX7219_BASE=0x1002020,第31位是0;因此我们用结构体指针来寄存器存储映射时,传输的数据因数据缓存而出错。那我们就把这个Cache给Bypass(旁路)了。怎么办?把地址总线的第31位置一。

代码8 修改后的Amy_S_max7219.h片段
//++++++++++++++++++++++++++++++++++++++
// 寄存器映射 开始
// 根据HDL编写
//++++++++++++++++++++++++++++++++++++++
#include "system.h"
#include "alt_types.h"

typedef struct
{
  alt_u32 DIN : 32;
  alt_u32 CS  : 32;
  alt_u32 CLK : 32;
}MAX7219_T;

#define m7219 ((MAX7219_T *)(MAX7219_BASE | 1<<31))
//--------------------------------------
// 寄存器映射 结束
//--------------------------------------

在这里,我们直接通过或(1<<31)的方式,把第31位给置一了;这样从该基地址传输的数据就把数据缓存给旁路了;因为是I/O外设传输嘛。

好了,至此大功告成。我们可以自由切换喜欢的寄存器映射方式。

3. 一点其他内容

有时候我们所使用的寄存器并不一定都是32位的,这时要使用嵌套结构体来凑足32位,以防止寄存器寻址错误。

代码9 嵌套结构体示例
typedef struct{
  struct{
    alt_u8  DIN : 8;
    alt_u32 NC  : 24;
  }offset_0;  
  struct{
    alt_u8  CS  : 1;
    alt_u32 NC  : 31;
  }offset_1;  
  struct{
    alt_u8  CLK : 1;
    alt_u32 NC  : 31;
  }offset_2;
}MAX7219_T;

关于嵌套结构体,此处不解析,请读者自行分析。

对比

两种方法都不错,大家爱用什么就用什么。

参考

1. http://www.edaboard.com/ftopic354136.html

2. Altera.Nios II Processor Reference Handbook

http://www.altera.com/literature/hb/nios2/n2cpu_nii5v1.pdf

免责声明:文章转载自《[笔记].浅析在Nios II中的两种寄存器映射方法的异同》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇音乐播放xml之XSLT下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Host是如何与EC通信的

第一种方式:遵循ACPI规范完成通信,通过LPC的62h、64h偏移/端口。 此种方式适合host对EC进行读写操作。 EC提供256字节的可被系统读写的RAM空间,EC的资源在该RAM空间映射,通过访问对应偏移(0x00~0xFF),即可操作对应的资源。实际上就是外部RAM的前256字节。EC会将键盘、触摸板、电池、温度传感器等一些设备的状态信息保存在此...

时序分析之Arrival Time

首先要理解两个概念:launch edge 和latch edge launch edge 是源寄存器发送数据的时钟沿,是时序分析的起点。 latch edge是目的寄存器捕获数据的时钟沿,是时序分析的终点。 如图示,源寄存器在0ns时发送数据,目的寄存器在5ns时采样数据,两者刚好相差一个时钟周期。 Data Arrival Time :从launch...

usb2.0高速视频采集之68013A寄存器配置说明

  任何的固件编程离不开与与原理图参考,图纸中所采用的是USB的Slave_fifo传输方式,具体配置与图纸对应即可。 •USB_IFCLK:同步Slave_FIFO模式,输入频率范围5M-48M,在FPGA内部将此信号配置为CMOS摄头cmos_pclk,传感器像素时钟的输出端,作为数据采集时钟和 68013与FPGA通信的同步时钟。 assign US...

Java内存模型(JMM)总结

Java内存模型(JMM) 我们常说的JVM内存模型指的是JVM的内存分区;而Java内存模型是一种虚拟机规范。 Java虚拟机规范中定义了Java内存模型(Java Memory Model,JMM),用于屏蔽掉各种硬件和操作系统的内存访问差异,实现让Java程序在各种平台下都能达到一致的并发效果,JMM规范了Java虚拟机与计算机内存是如何协同工作的:...

Linux高级编程--04.GDB调试程序(查看数据)

查看栈信息 当程序被停住了,你需要做的第一件事就是查看程序是在哪里停住的。当你的程序调用了一个函数,函数的地址,函数参数,函数内的局部变量都会被压入“栈”(Stack)中。你可以用GDB命令来查看当前的栈中的信息。 下面是一些查看函数调用栈信息的GDB命令: backtrace / bt :打印当前的函数调用栈的所有信息。如: (gdb) bt #0...

linux 应用程序直接读写寄存器或物理内存

1.程序说明: 调试驱动程序时,经常遇到候需要查看或设置寄存器的情况,但是直接更改内核代码又不方便。 这里提供一个应用程序源码能在应用层访问底层寄存器。(网上找到的,进行过更改)。 这里只提供4字节数据的访问,如果需要其他字节宽度则需要更改代码。 line40 增加了O_DSYNC标志,防止cache导致数据写入不及时。 2.应用程序源码 1 #incl...