RISC-V ISA Simulator系列之fesvr<3>

深入解析 FESVR(Front-End Server)

url: https://github.com/riscv/riscv-isa-sim.git
commid: fcbdbe7946079650d0e656fa3d353e3f652d471f

目录

  1. FESVR 概述
  2. FESVR 代码结构分析
  3. ELF 加载机制

RISC-V ISA Simulator系列之fesvr<1-2>中我们已经完成了

1. FESVR 概述
2. FESVR 代码结构分析
   1. ELF 相关文件
   2. HTIF(Host-Target Interface)
     - `htif.h` / `htif.cc

内容的,下面我们继续完成下列内容。

2. HTIF(Host-Target Interface)

  • htif.h / htif.cc:HTIF 主接口,负责仿真器和目标程序的通信,如命令传递、寄存器/内存访问。
  • htif_pthread.h / htif_pthread.cc:HTIF 的多线程实现,用于并发仿真。
  • htif_hexwriter.h / htif_hexwriter.cc:是 HTIF 相关的 HEX 数据写入工具。
2.2 htif_pthread.h / htif_pthread.cc

htif_pthread.h

#ifndef _HTIF_PTHREAD_H
#define _HTIF_PTHREAD_H

#include "htif.h"
#include "context.h"
#include <deque>

class htif_pthread_t : public htif_t
{
 public:
  htif_pthread_t(int argc, char** argv);
  virtual ~htif_pthread_t();

  // target interface
  void send(const void* buf, size_t size);
  void recv(void* buf, size_t size);
  bool recv_nonblocking(void* buf, size_t size);

 protected:
  // host interface
  virtual ssize_t read(void* buf, size_t max_size);
  virtual ssize_t write(const void* buf, size_t size);

  virtual size_t chunk_align() { return 64; }
  virtual size_t chunk_max_size() { return 1024; }

 private:
  context_t host;
  context_t* target;
  std::deque<char> th_data;
  std::deque<char> ht_data;

  static void thread_main(void* htif);
};

#endif

下面对该头文件中定义的 htif_pthread_t 类进行详细分析,并对每个函数和数据成员的作用进行总结。


htif_pthread_t 继承自 htif_t,用于实现基于 POSIX 线程(pthread)的 HTIF 接口。它主要通过线程机制来实现主机与目标之间的数据传输,其内部维护两个数据缓冲区(分别用于主机与目标方向的数据传输)和两个上下文对象,用于线程间协作。


  1. 公有接口

1.1 构造函数:htif_pthread_t(int argc, char** argv)

初始化基于 pthread 的 HTIF 对象。通常在构造函数中会调用基类 htif_t 的构造函数完成参数解析和设备注册等工作,然后初始化线程相关的上下文等成员。

构造函数用于根据命令行参数初始化 htif_pthread_t 对象,并为后续线程通信做好准备。


1.2 析构函数:virtual ~htif_pthread_t()

析构函数负责释放由 htif_pthread_t 对象分配的资源,例如线程上下文或其他动态分配的资源。

确保对象销毁时,所有与线程通信相关的资源得以正确清理,避免内存泄漏或悬挂引用。


1.3 目标接口

1.3.1 void send(const void* buf, size_t size)

向目标发送数据。该函数实现了目标端向 host 端数据传输的发送接口,通常会将数据放入内部数据队列中,随后由线程负责传输。

用于把一段数据(大小为 size 的缓冲区 buf)发送到目标侧,可能通过内部队列 th_dataht_data 传递给目标上下文。


1.3.2 void recv(void* buf, size_t size)

从目标接收数据,阻塞直到接收到 size 字节数据为止。函数将数据复制到用户提供的缓冲区。

实现阻塞式接收数据接口,确保目标侧传来的数据能完整地读取到用户缓冲区中。


1.3.3 bool recv_nonblocking(void* buf, size_t size)

实现非阻塞接收接口,尝试从目标接收数据。如果队列中没有足够数据则立即返回 false,否则将数据复制到 buf 中并返回 true。

用于非阻塞式数据接收,可用于轮询方式检测目标是否有数据可读。


  1. 受保护的接口

2.1 主机接口

2.1.1 virtual ssize_t read(void* buf, size_t max_size)

该函数是针对主机数据读取的虚函数,用于从主机侧(host context)读取数据。通常在内部线程中被调用,实现实际的数据读取操作,返回实际读取的字节数。

提供主机侧数据读取接口,供线程或其他内部逻辑调用,实现从主机到目标的数据传输。


2.1.2 virtual ssize_t write(const void* buf, size_t size)

该虚函数用于向主机侧写入数据,通常由线程调用,将数据写入到内部数据缓冲区或直接传递到目标。返回写入的字节数。

实现主机侧数据写入接口,负责将数据传送到目标或内部队列中,用于后续数据传输。


2.2 内存块属性

2.2.1 virtual size_t chunk_align() { return 64; }

返回数据传输时内存块的对齐要求,这里返回 64 字节对齐。

指定数据块在传输过程中需要按照 64 字节对齐,以满足硬件或系统的要求。


2.2.2 virtual size_t chunk_max_size() { return 1024; }

返回单次传输数据块的最大大小,这里限制为 1024 字节。

限制每次数据传输的最大字节数,确保传输过程中的数据块大小在可控范围内。


  1. 私有成员变量

3.1 context_t host;

表示主机上下文,用于管理和调度主机侧的线程执行环境。

保存主机线程上下文,用于主机与目标线程之间的切换和数据传输协调。


3.2 context_t* target;

指向目标上下文,表示与目标线程相关的执行环境,通常用于线程切换时切换到目标线程。

用于保存目标线程的上下文信息,便于在主机与目标之间进行上下文切换。


3.3 std::deque<char> th_data;

一个双端队列,用于存储从目标到主机(Target-to-Host,简称 th)的数据。

作为内部数据缓冲区,存放目标发送给主机的数据,支持高效的队列操作。


3.4 std::deque<char> ht_data;

一个双端队列,用于存储从主机到目标(Host-to-Target,简称 ht)的数据。

内部缓冲区,存放主机发送给目标的数据,实现数据传输的异步处理。


3.5 static void thread_main(void* htif);

静态函数,用作线程的入口函数。该函数将作为新线程的主函数,负责执行主机与目标之间的数据通信逻辑或上下文切换。

为 pthread 提供入口函数,实现独立线程的运行逻辑,与主线程协作完成数据传输和上下文切换任务。


  • 类功能
    htif_pthread_t 类基于 pthread 实现了 HTIF 接口,专门用于在仿真环境中通过线程方式进行主机与目标的数据交换和上下文切换。
  • 公有接口
    • 构造和析构函数用于对象初始化和资源清理。
    • sendrecvrecv_nonblocking 提供目标接口,允许发送和接收数据。
  • 受保护接口
    • readwrite 是主机侧的虚函数,实现数据的实际传输。
    • chunk_alignchunk_max_size 定义了数据传输时的内存块对齐和大小限制。
  • 私有成员
    • 通过 context_t 类型的 hosttarget 管理线程上下文,实现主机与目标之间的协作。
    • 内部双端队列 th_dataht_data 用于缓存数据传输,支持异步和高效的数据交换。
    • 静态函数 thread_main 作为新线程的入口,负责实现线程主要执行逻辑。

该类的设计重点在于利用线程和上下文切换实现数据传输,并通过内部队列和虚函数接口抽象出数据传输的具体细节,为仿真器提供了灵活且高效的 HTIF 实现。

htif_pthread.cpp

下面对该文件中各个函数的实现进行详细分析,并总结每个函数的作用。


  1. 静态函数:thread_main
void htif_pthread_t::thread_main(void* arg)
{
  htif_pthread_t* htif = static_cast<htif_pthread_t*>(arg);
  htif->run();
  while (true)
    htif->target->switch_to();
}
  • 参数转换
    将传入的 void* 参数转换为 htif_pthread_t* 对象。

  • 执行 run()
    调用 htif->run() 启动 HTIF 仿真主循环,这里 run() 函数会处理主机与目标间的数据传输、命令处理等。

  • 无限循环切换
    当 run() 返回后,进入无限循环,每次调用 htif->target->switch_to()。
    这表示当前线程作为 host 执行完毕后,切换回目标上下文;从而保持线程在目标和主机之间交替执行,确保两边的数据交互能够持续进行。

thread_main 是新线程的入口函数,它首先启动 HTIF 仿真(调用 run()),之后不断地进行上下文切换,将执行权交还给目标上下文。这实现了主机与目标之间的协同运行。


  1. 构造函数:htif_pthread_t::htif_pthread_t(int argc, char** argv)
htif_pthread_t::htif_pthread_t(int argc, char** argv)
    : htif_t(argc, argv)
{
  target = context_t::current();
  host.init(thread_main, this);
}
  • 调用基类构造函数
    调用 htif_t(argc, argv) 完成命令行参数解析、设备注册等基础工作。

  • 保存当前上下文
    使用 context_t::current() 获取当前执行上下文,保存为 target。这里 target 表示目标(仿真程序)的执行上下文。

  • 初始化 host 上下文
    调用 host.init(thread_main, this),创建一个新的上下文,并以 thread_main 作为入口函数,传入当前对象作为参数。这样就建立了一个用于 host 端的独立执行上下文。

构造函数完成了 pthread 版 HTIF 的初始化工作,包括基类初始化、保存目标上下文以及为 host 创建新的上下文(线程入口为 thread_main)。


  1. 析构函数:htif_pthread_t::~htif_pthread_t()
htif_pthread_t::~htif_pthread_t()
{
}
  • 空析构
    目前析构函数没有进行额外的资源释放操作,假设所有需要释放的资源在基类中或其他地方已经处理。

析构函数没有特殊操作,依赖基类和上下文对象自行释放资源。


  1. 主机侧读取数据:ssize_t htif_pthread_t::read(void* buf, size_t max_size)
ssize_t htif_pthread_t::read(void* buf, size_t max_size)
{
  while (th_data.size() == 0)
    target->switch_to();

  size_t s = std::min(max_size, th_data.size());
  std::copy(th_data.begin(), th_data.begin() + s, (char*)buf);
  th_data.erase(th_data.begin(), th_data.begin() + s);

  return s;
}
  • 等待数据
    使用 while 循环检查内部队列 th_data 是否为空。如果为空,则调用 target->switch_to() 切换到目标上下文,等待目标发送数据。

  • 数据复制
    th_data 中有数据时,取出不超过 max_size 字节的数据,复制到 buf 缓冲区中。

  • 删除已读数据
    th_data 队列中已复制的数据删除。

  • 返回读取的字节数
    最后返回实际读取的数据字节数。

该函数实现了阻塞式读取:当内部队列中没有数据时,主动切换上下文等待数据;一旦有数据则读取并删除对应数据。主要用于从目标到主机的数据传输(Target-to-Host)。


  1. 主机侧写入数据:ssize_t htif_pthread_t::write(const void* buf, size_t size)
ssize_t htif_pthread_t::write(const void* buf, size_t size)
{
  ht_data.insert(ht_data.end(), (const char*)buf, (const char*)buf + size);
  return size;
}
  • 写入操作
    将 buf 中 size 字节数据插入到内部队列 ht_data 的末尾。
    ht_data 队列用于存储主机到目标(Host-to-Target)的数据。

  • 返回写入字节数
    返回写入的字节数 size。

该函数实现了主机数据写入接口,将数据缓冲到 ht_data 队列中,以便后续目标上下文读取。


  1. 目标侧发送数据:void htif_pthread_t::send(const void* buf, size_t size)
void htif_pthread_t::send(const void* buf, size_t size)
{
  th_data.insert(th_data.end(), (const char*)buf, (const char*)buf + size);
}
  • 数据插入
    将目标侧发送的数据 buf(size 字节)插入到内部队列 th_data 的末尾。
    该队列的数据将由主机侧通过 read() 接口读取。

实现目标侧发送数据接口,将数据保存到 th_data 队列中,以便主机侧读取。


  1. 目标侧接收数据:void htif_pthread_t::recv(void* buf, size_t size)
void htif_pthread_t::recv(void* buf, size_t size)
{
  while (!this->recv_nonblocking(buf, size))
    ;
}
  • 阻塞接收
    使用 while 循环不断调用 recv_nonblocking 接口,直到成功接收到 size 字节数据为止。
  • 无数据时等待
    如果 recv_nonblocking 返回 false,表示数据不足,继续循环等待。

实现阻塞式接收数据接口,直到从主机到目标的数据缓冲区(ht_data)中读取到所需大小的数据为止。


  1. 非阻塞接收数据:bool htif_pthread_t::recv_nonblocking(void* buf, size_t size)
bool htif_pthread_t::recv_nonblocking(void* buf, size_t size)
{
  if (ht_data.size() < size)
  {
    host.switch_to();
    return false;
  }

  std::copy(ht_data.begin(), ht_data.begin() + size, (char*)buf);
  ht_data.erase(ht_data.begin(), ht_data.begin() + size);
  return true;
}
  • 数据检查
    检查主机到目标队列 ht_data 中是否有足够数据。如果不足,则调用 host.switch_to() 切换到 host 上下文,并返回 false 表示未能非阻塞接收完整数据。

  • 数据复制
    如果数据充足,则将前 size 字节数据复制到 buf 缓冲区中。

  • 数据删除
    删除 ht_data 队列中已读取的数据。

  • 返回成功标识
    返回 true 表示成功非阻塞地接收到数据。

实现非阻塞式数据接收接口,从 ht_data 队列中读取指定大小的数据,如果数据不足则主动切换上下文等待数据,并返回 false;如果读取成功则返回 true。


总体总结

  • 线程入口函数 (thread_main)
    启动 HTIF 仿真主循环(run())并在仿真结束后持续进行上下文切换,保持与目标上下文的交替执行。

  • 构造函数
    调用基类构造,保存当前执行上下文作为目标,并初始化 host 上下文以启动独立线程(通过 thread_main)。

  • read() / write()
    分别实现从目标到主机的数据读取(阻塞式,依赖内部队列 th_data)和从主机到目标的数据写入(写入到内部队列 ht_data)。

  • send()
    目标侧发送数据,将数据插入 th_data 队列,供主机读取。

  • recv() / recv_nonblocking()
    实现目标侧接收数据接口,其中 recv() 是阻塞式(不断调用非阻塞版本),而 recv_nonblocking() 则在数据不足时通过 host.switch_to() 切换上下文,并返回 false。

该类利用内部双端队列和上下文切换实现了异步的数据交换机制,支持主机与目标之间的双向数据传输,并通过阻塞与非阻塞接口满足不同场景下的数据接收需求。整个实现基于线程(pthread)机制,在 host 与 target 两个上下文之间进行切换,从而完成仿真过程中数据通信的任务。

2.3 htif_hexwriter.h / htif_hexwriter.cc

htif_hexwriter.h

// See LICENSE for license details.

#ifndef __HTIF_HEXWRITER_H
#define __HTIF_HEXWRITER_H

#include <map>
#include <vector>
#include <stdlib.h>
#include "memif.h"

class htif_hexwriter_t : public chunked_memif_t
{
public:
  htif_hexwriter_t(size_t b, size_t w, size_t d);

protected:
  size_t base;
  size_t width;
  size_t depth;
  std::map<addr_t,std::vector<char> > mem;

  void read_chunk(addr_t taddr, size_t len, void* dst);
  void write_chunk(addr_t taddr, size_t len, const void* src);
  void clear_chunk(addr_t, size_t) {}

  size_t chunk_max_size() { return width; }
  size_t chunk_align() { return width; }

  friend std::ostream& operator<< (std::ostream&, const htif_hexwriter_t&);
};

#endif // __HTIF_HEXWRITER_H

下面对 htif_hexwriter_t 类的实现进行详细分析,并总结每个成员函数和数据成员的作用。


htif_hexwriter_t 继承自 chunked_memif_t(内存接口的分块抽象),主要用于将模拟内存以 HEX 格式输出。该类内部使用一个 std::map 存储仿真内存的内容,其键为地址(addr_t 类型),值为一个字符向量(存储该地址处的数据块)。同时,该类定义了一些与内存分块相关的接口函数,供 HEX 输出时按块读取和写入数据。


  1. 构造函数
htif_hexwriter_t::htif_hexwriter_t(size_t b, size_t w, size_t d);
  • 参数说明:

    • b:表示基地址(base),即 HEX 输出时的起始地址。
    • w:表示每个内存块的宽度(width),这通常也是一次写入的单位大小。
    • d:表示深度(depth),可能用于限定存储容量或总内存块数。
  • 作用:
    构造函数将传入参数保存到相应的保护成员变量 basewidthdepth。此外,它还会初始化内部的内存映射容器 mem(std::map),用于记录以地址为键的数据块内容。

构造函数用于初始化 HEX 输出所需的基本参数,包括内存基地址、数据块宽度和存储深度,并准备好内部的内存数据结构。


  1. 受保护的成员变量
  • size_t base;
    存储 HEX 输出的起始地址。

  • size_t width;
    存储数据块的宽度,决定了每次读写操作的单位大小。

  • size_t depth;
    存储深度,用于描述内存块的数量或总容量限制。

  • std::map<addr_t,std::vector<char> > mem;
    用于存储仿真内存的数据。每个键(地址)对应一个字符向量,表示从该地址开始的一块数据。通过这种方式,可以以分块方式管理内存数据,便于之后以 HEX 格式输出。

这些成员变量构成了 HEX 输出接口内部的基本数据结构,记录了内存的组织方式和输出格式的基本参数。


  1. 虚函数接口

htif_hexwriter_t 重写了以下虚函数,实现对内存块的读写操作:

3.1 void read_chunk(addr_t taddr, size_t len, void* dst);

  • 作用与实现(声明):
    该函数用于从内部存储的内存映射 mem 中读取从目标地址 taddr 开始、长度为 len 字节的数据,并复制到 dst 缓冲区中。
    (具体实现在 .cc 文件中,这里仅提供接口声明。)

实现内存读取操作,使得外部能够通过该接口读取特定地址范围内的数据。


3.2 void write_chunk(addr_t taddr, size_t len, const void* src);

  • 作用与实现(声明):
    该函数用于将从 src 指针处取得的 len 字节数据写入到内部存储结构 mem 中,地址从 taddr 开始。
    (具体实现同样在 .cc 文件中给出。)

实现内存写入操作,允许将数据写入到指定的仿真内存区域,为 HEX 输出构造数据提供支持。


3.3 void clear_chunk(addr_t, size_t) {}

  • 作用:
    该函数用于清空指定内存区域,但在此实现中为空(空实现)。
    可能表示该接口在 HEX 输出场景下不需要清除数据,或者清除操作由其他逻辑完成。

提供清零内存块接口,但在此类中不进行实际操作(空实现)。


  1. 内存块属性函数

4.1 size_t chunk_max_size() { return width; }

  • 作用:
    返回单个内存块的最大大小,这里定义为 width,与构造时传入的数据块宽度保持一致。

规定了数据块最大传输单位,便于按照固定大小进行内存分块操作。


4.2 size_t chunk_align() { return width; }

  • 作用:
    返回内存块的对齐要求,同样定义为 width
    这表示所有读写操作必须以 width 字节对齐。

规定了内存操作时的对齐要求,与数据块宽度一致,确保读写操作在块级别上是正确的。


  1. 友元运算符
friend std::ostream& operator<< (std::ostream&, const htif_hexwriter_t&);
  • 作用:
    定义了一个友元函数,用于支持 htif_hexwriter_t 对象直接输出到标准输出流(例如 std::cout),通常会以 HEX 格式输出内部存储的内存数据。

**
友元函数允许直接访问 htif_hexwriter_t 的保护成员(例如 mem、base、width 和 depth),从而格式化输出仿真内存内容。

通过重载输出运算符,实现方便的 HEX 格式数据导出接口,为仿真器生成 HEX 文件提供支持。


总结

  • 类功能
    htif_hexwriter_t 主要用于将仿真器中加载的内存数据以 HEX 格式输出。它实现了内存分块接口(继承自 chunked_memif_t),并通过内部的 std::map 管理数据块。

  • 构造函数
    初始化基本参数(基地址、块宽度和深度),为后续的读写操作做准备。

  • 虚函数接口

    • read_chunk()write_chunk() 分别实现内存读取和写入操作,允许按块管理仿真数据。
    • clear_chunk() 在此类中为空实现,因为 HEX 输出场景下可能不需要额外的清零操作。
    • chunk_max_size()chunk_align() 都返回 width,确保所有操作以相同的块宽度进行。
  • 数据成员

    • basewidthdepth 存储 HEX 输出的基本格式参数。
    • mem 用于保存仿真内存的各个数据块。
  • 友元输出运算符
    实现方便的输出接口,使得对象能够以 HEX 格式导出内存内容。

整体来说,该类为仿真器提供了一个简单的内存映射和导出接口,方便将加载到内存中的程序数据转换为 HEX 格式文件,从而用于 FPGA 下载、调试或其他目的。

htif_hexwriter.cpp
下面对代码中每个函数进行详细分析,并总结每个函数的作用。


  1. 构造函数
htif_hexwriter_t::htif_hexwriter_t(size_t b, size_t w, size_t d)
  : base(b), width(w), depth(d)
{
}
  • 参数 b、w、d:

    • b:表示内存映射的基地址(base)。
    • w:表示数据块宽度(width),也就是每个块的字节数。
    • d:表示内存深度(depth),即总共有多少个数据块。
  • 初始化列表:

    • 将参数分别赋值给成员变量 basewidthdepth
    • 内部数据成员 mem(std::map)在构造函数体内没有特殊初始化,利用默认构造函数构造空映射。

构造函数用于初始化 htif_hexwriter_t 对象的基本参数,设置 HEX 输出的基地址、每个数据块的宽度以及总数据块数量,为后续的读写操作做准备。


  1. 函数:read_chunk
void htif_hexwriter_t::read_chunk(addr_t taddr, size_t len, void* vdst)
{
  taddr -= base;

  assert(len % chunk_align() == 0);
  assert(taddr < width*depth);
  assert(taddr+len <= width*depth);

  uint8_t* dst = (uint8_t*)vdst;
  while(len)
  {
    if(mem[taddr/width].size() == 0)
      mem[taddr/width].resize(width,0);

    for(size_t j = 0; j < width; j++)
      dst[j] = mem[taddr/width][j];

    len -= width;
    taddr += width;
    dst += width;
  }
}
  • 地址调整:

    • taddr -= base; 将传入的目标地址转换为相对于基地址的偏移。
  • 断言检查:

    • assert(len % chunk_align() == 0); 保证读取长度是块对齐的,即长度必须是 width 的整数倍。
    • assert(taddr < width*depth); 确保起始地址在映射的范围内。
    • assert(taddr+len <= width*depth); 确保读取区域不超出预定内存范围。
  • 读取过程:

    • vdst 强制转换为 uint8_t* 指针,用于字节级复制。
    • 使用 while 循环,每次循环处理一个数据块(大小为 width 字节)。
    • 对于每个数据块:
      • 判断当前块是否已经存在数据(通过 mem[taddr/width].size() 检查),若为空则调用 resize(width, 0) 初始化该块(填充 0)。
      • 用 for 循环将当前块中所有 width 字节数据复制到目标缓冲区 dst 中。
    • 更新 len、taddr 和 dst 指针,继续处理下一个块,直到 len 为 0。

read_chunk 函数实现了从内部内存映射中按块读取数据。它确保读取区域对齐且在合法范围内,并逐块复制数据到提供的缓冲区中。如果某个块未初始化,则先将其初始化为全零。


  1. 函数:write_chunk
void htif_hexwriter_t::write_chunk(addr_t taddr, size_t len, const void* vsrc)
{
  taddr -= base;

  assert(len % chunk_align() == 0);
  assert(taddr < width*depth);
  assert(taddr+len <= width*depth);

  const uint8_t* src = (const uint8_t*)vsrc;
  while(len)
  {
    if(mem[taddr/width].size() == 0)
      mem[taddr/width].resize(width,0);

    for(size_t j = 0; j < width; j++)
      mem[taddr/width][j] = src[j];

    len -= width;
    taddr += width;
  }
}
  • 地址调整:

    • 与 read_chunk 类似,先通过 taddr -= base; 将传入地址转换为相对于基地址的偏移量。
  • 断言检查:

    • 检查写入长度是否对齐(len 必须为 width 的整数倍)。
    • 检查写入区域在合法范围内。
  • 写入过程:

    • 将 vsrc 转换为 const uint8_t* 指针,用于逐字节读取数据。
    • 使用 while 循环逐块写入数据,每块大小为 width 字节。
    • 如果当前块(mem[taddr/width])未初始化(size 为 0),则调用 resize(width, 0) 初始化为全 0 数组。
    • 对当前块中的每个字节,将对应数据从 src 复制到内部存储的向量中。
    • 更新 len 和 taddr,继续处理后续块。

write_chunk 函数实现了将数据按块写入到内部内存映射中。它确保写入区域对齐且合法,并对未初始化的块进行初始化,再逐块将数据从输入缓冲区复制到内部数据结构中。


  1. 友元输出运算符
std::ostream& operator<< (std::ostream& o, const htif_hexwriter_t& h)
{
  std::ios_base::fmtflags flags = o.setf(std::ios::hex, std::ios::basefield);

  for(size_t addr = 0; addr < h.depth; addr++)
  {
    std::map<addr_t, std::vector<char> >::const_iterator i = h.mem.find(addr);
    if(i == h.mem.end())
      for(size_t j = 0; j < h.width; j++)
        o << "00";
    else
      for(size_t j = 0; j < h.width; j++)
        o << ((i->second[h.width-j-1] >> 4) & 0xF) << (i->second[h.width-j-1] & 0xF);
    o << std::endl;
  }

  o.setf(flags);

  return o;
}
  • 设置格式:

    • 使用 o.setf(std::ios::hex, std::ios::basefield) 将输出流格式设置为十六进制。
    • 保存原始格式标志,便于后续恢复。
  • 逐块输出内存数据:

    • 循环遍历从 0 到 h.depth-1 的每个内存块地址。
    • 对于每个块:
      • h.mem 中查找是否存在该块数据:
        • 如果找不到(即该块未写入数据),则输出 h.width 个 “00”,表示该块全为 0。
        • 如果找到了,则以反序方式输出该块内每个字节的内容。
          • 注意:i->second[h.width-j-1] 表示反序输出,先输出高位,再输出低位。
          • (i->second[...] >> 4) & 0xF 提取高 4 位,(i->second[...] & 0xF) 提取低 4 位。
          • 直接使用 << 运算符输出十六进制数字。
    • 每个块结束后换行。
  • 恢复输出格式:

    • 调用 o.setf(flags); 恢复原始输出流格式标志。
  • 返回输出流:

    • 返回修改后的输出流对象,支持链式调用。

友元输出运算符实现了将 htif_hexwriter_t 对象中存储的内存数据以 HEX 格式输出。它逐块读取内存数据,如果某块未初始化则输出 “00”,否则反序输出每个字节的十六进制值,并恢复输出流原有格式。


总结

  • 构造函数
    初始化对象的基本参数(base、width、depth),为后续内存读写和 HEX 输出设置格式参数。

  • read_chunk
    根据基地址偏移,将内存数据按块读取到目标缓冲区中,保证读操作按 width 大小块进行,并确保未初始化的块先被初始化为全零。

  • write_chunk
    将输入数据按块写入到内部内存映射中,要求写入区域对齐且在合法范围内,并对未初始化的块进行初始化操作。

  • 友元输出运算符
    将内部存储的所有数据块以十六进制格式输出,每个数据块按宽度(width)输出,如果数据不存在则输出“00”,并以行分隔不同内存块,最后恢复输出流格式。

该类整体实现了一个简单的内存映射和数据输出接口,专门用于将仿真器中存储的内存数据格式化成 HEX 格式,便于保存为 HEX 文件或直接在控制台查看,常用于 FPGA 加载和调试等场景。

下一篇:RISC-V ISA Simulator系列之fesvr<4>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值