NEMU PA2

RTFSC(2)

运行第一个客户程序

这一部分主要靠RTFM,我们需要根据反汇编的结果来查看缺少的指令:

Disassembly of section .text:

80000000 <_start>:
80000000:	00000413          	li	s0,0
80000004:	00009117          	auipc	sp,0x9
80000008:	ffc10113          	addi	sp,sp,-4 # 80009000 <_end>
8000000c:	00c000ef          	jal	ra,80000018 <_trm_init>

80000010 <main>:
80000010:	00000513          	li	a0,0
80000014:	00008067          	ret

80000018 <_trm_init>:
80000018:	ff010113          	addi	sp,sp,-16
8000001c:	00000517          	auipc	a0,0x0
80000020:	01c50513          	addi	a0,a0,28 # 80000038 <_etext>
80000024:	00112623          	sw	ra,12(sp)
80000028:	fe9ff0ef          	jal	ra,80000010 <main>
8000002c:	00050513          	mv	a0,a0
80000030:	00100073          	ebreak
80000034:	0000006f          	j	80000034 <_trm_init+0x1c>

注意一些伪指令如li是一些基础指令的组合实现的,需要添加那些基础指令才能实现伪指令的功能。

通过阅读代码可以发现,一些类型指令的添加并不困难,但是像J-type指令中的立即数实现还是值得注意的。
在这里插入图片描述
由于添加指令的流程基本相同,所以就以jal指令来简要介绍一下:
在这里插入图片描述
首先,添加J-type指令立即数的宏定义(类比于已给出immS()来写):

#define immJ() do { *imm = SEXT((BITS(i, 31, 31) << 19) | \      
(BITS(i, 30, 21)) | (BITS(i, 20, 20) << 10) | \
(BITS(i, 19, 12) << 11), 20) << 1; } while(0)

由于J-type指令并未用到源操作数,所以在对指令进行解析时,只需要解析立即数即可:

switch (type) {
    case TYPE_I: src1R();          immI(); break;
    case TYPE_U:                   immU(); break;
    case TYPE_S: src1R(); src2R(); immS(); break;
    case TYPE_J:                   immJ(); 
    ///for(int i = 0; i < 5; i++)
    //  printf("0x%05x\n", *imm); 
    break;
    case TYPE_Rtype: src1R(); src2R();     break;
    case TYPE_B: src1R(); src2R(); immB(); break;
  }

最后再添加jal指令的模式匹配就可以了。

//J-type
  INSTPAT("??????? ????? ????? ??? ????? 11011 11", jal    , J, R(rd) = s->pc + 4, s->dnpc = s->pc + imm);

正如PA指导书所说,我们在添加指令进行测试时,最好不要先把指令全部添加之后再进行统一测试,我们应该利用所给的测试用例,逐一测试,如遇到缺少的指令,再进行添加,这样在出错时,也能缩小出错指令的范围,节省debug时间。

实现更多的指令

根据以上流程,对所有测试用例逐一测试,添加缺少的指令。

程序, 运行时环境与AM

通过批处理模式运行NEMU

RTFSC(其实是直接查找的mode关键词)发现,在nemu/src/monitor.c已经指出批处理模式的启动是由-b参数控制的,所以我们需要在nemu.mk中添加NEMUFLAGS += -b来实现批处理模式运行nemu

实现字符串处理函数

这里面的函数实现还是需要注意的,很容易出现一些小问题,在这里只介绍几个易出错的函数。

strcpy

在测试check(strcmp( strcat(strcpy(str, str1), s[3]), s[4]) == 0)时,出现了问题,我当时认为出问题的可能性:strcat > strcmp > strcpy,结果最后逐一排查,是strcpy出现了问题。果然,正如jyy所说,往往你认为最不可能出问题的地方,bug就躺在那里

错误版:

char *strcpy(char *dst, const char *src) {
  //panic("Not implemented");
  assert(dst != NULL && src != NULL);

  while(*src != '\0')
  {
    *dst++ = *src++;
  }
  *dst = '\0';
  
  return dst;
}

正确版:

char *strcpy(char *dst, const char *src) {
  //panic("Not implemented");
  assert(dst != NULL && src != NULL);

  char *tmp = dst;

  while(*src != '\0')
  {
    *tmp++ = *src++;
  }
  *tmp = '\0';
  
  return dst;
}

问题所在:返回的指针指错了位置

strcat

注意,需要在末尾加上\0

char *strcat(char *dst, const char *src) {
  //panic("Not implemented");
  assert(dst != NULL && src != NULL);

  char *tmp = dst;
  
  while(*tmp != '\0') tmp++;

  while(*src != '\0') *tmp++ = *src++;

  *tmp = '\0';

  return dst;
}

memmove

关于这个函数讲解,有一篇很好的blog:memmove函数实现

memmove函数用于处理重叠拷贝的情况。

重叠情况:

在这里插入图片描述
解决方案:

在这里插入图片描述
但是呢,也不能一概而论,就全部都是从后往前拷贝,还是得分情况的,具体就是看destinationsource的位置关系。
在这里插入图片描述
根据以上分析,便可以实现此函数了:

void *memmove(void *dst, const void *src, size_t n) {
  //panic("Not implemented");
  assert(dst && src);

  char *tmp = dst;
  const char *s = src;

  if(dst > src)
  {
    s += n - 1;
    tmp += n - 1;
    while(n--)
    {
      *tmp-- = *s--;
    }
  }
  else
  {
    while(n--)
    {
      *tmp++ = *s++;
    }
  }

  return dst;
}

断言指针不为空是个好习惯

实现sprintf

想要实现一个功能比较全的sprintf函数,还是比较复杂的,由于测试用例hello-str在这里只用到了%s%d,所以目前只实现了一个简易的sprintf

static char *number(char *str, int num, int base)
{
  int tmp[100], i = 0;
  if(num < 0)
  {
    *str++ = '-';
    num = -num;
  }
  while(num)
  {
    tmp[i++] = num % base;
    num /= base;
  }
  
  i--;
  
  while(i >= 0) *str++ = tmp[i--] + '0';   //注意要转化为字符型数字
  
  return str;
}

int vsprintf(char *out, const char *fmt, va_list ap) {
  //panic("Not implemented");
  char *str = out;
  const char *s;
  int base;   
  int num;

  for(; *fmt != '\0'; ++fmt)
  {
    if(*fmt != '%')
    {
      *str++ = *fmt;
      continue;
    }

    //*fmt == '%'
    ++fmt;

    base = 10;  //默认为10进制

    switch(*fmt)
    {
      case 'c':
        *str++ = (unsigned char) va_arg(ap, int);
        continue;
      case 's':
        s = va_arg(ap, char *);

        /****    Plan A    ****/

        //for(int i = 0; s[i]; i++)
        //  *str++ = s[i];

        /****    Plan B    ****/
        strcat(str, s);
        str += strlen(s);
        continue;
      case 'd':
        break;      //用于跳出switch语句
      default:
        *str++ = '%';
        if(*fmt)
          *str++ = *fmt;
        else
          --fmt;    //防止溢出
        continue;
    }
    num = va_arg(ap, int);

    str = number(str, num, base);
  }
  *str = '\0';
  return str - out;  //返回写入的字符数
}

/*
stdarg中部分宏的解释
va_list 是用于存放参数列表的数据结构。
va_start 函数根据初始化last来初始化参数列表,last为固定参数中的最后一个固定参数.(原因在后面的宏解析中)
va_arg 函数用于从参数列表中取出一个参数,参数类型由type指定。
va_copy 函数用于复制参数列表。
va_end 函数执行清理参数列表的工作。
*/
int sprintf(char *out, const char *fmt, ...) {
  //panic("Not implemented");
  assert(out);

  va_list args;
  int i;

  va_start(args, fmt);
  i = vsprintf(out, fmt, args);
  va_end(args);

  return i;
}

基础设施(2)

实现iringbuf

nemu/src/utils下新建trace.c

#include <common.h>
#include <elf.h>
#include <device/map.h>

#define INST_NUM 16

// iringbuf
typedef struct
{
    word_t pc;
    uint32_t inst;
}InstBuf;

InstBuf iringbuf[INST_NUM];

int cur_inst = 0;
int func_num = 0;

void trace_inst(word_t pc, uint32_t inst)
{
    iringbuf[cur_inst].pc = pc;
    iringbuf[cur_inst].inst = inst;
    cur_inst = (cur_inst + 1) % INST_NUM; 
}

void display_inst()
{
    /*** 注意出错的是前一条指令,当前指令可能由于出错已经无法正常译码 ***/
    int end = cur_inst;
    char buf[128];
    char *p;
    int i = cur_inst;

    if(iringbuf[i+1].pc == 0) i = 0;

    do
    {
        p = buf;
        //if(i == end) p += sprintf(buf, "-->");
        p += sprintf(buf, "%s" FMT_WORD ":  %08x\t", (i + 1) % INST_NUM == end ? "-->" : "   ", iringbuf[i].pc, iringbuf[i].inst);

        void disassemble(char *str, int size, uint64_t pc, uint8_t *code, int nbyte);
        disassemble(p, buf + sizeof(buf) - p, iringbuf[i].pc, (uint8_t *)&iringbuf[i].inst, 4);

        puts(buf);
        i = (i + 1) % INST_NUM;
    } while (i != end);
     
}

trace_inst()放在取指令inst_fetch的后面,用于跟踪指令,将指令放入缓冲区中。

display_inst()函数放在assert_fail_msg()中,当程序出错时,展示寄存器的值与出错的指令。

void assert_fail_msg() {
  isa_reg_display();
  IFDEF(CONFIG_IRINGBUF,display_inst());
  statistic();
}

nemu/Kconfig中参考着ITRACE定义IRINGBUF,这样我们就可以在menuconfig中控制iringbuf的开启与关闭。

实现mtrace

同样的,先在nemu/Kconfig中定义MTRACE

实现mtrace是比较简单的,只需要在访问paddr_read()paddr_write()时进行记录即可,在paddr_read()paddr_write()中分别添加

IFDEF(CONFIG_MTRACE, display_memory_read(addr, len));
IFDEF(CONFIG_MTRACE, display_memory_write(addr, len, data));

然后在trace.c中添加这两个函数即可:

void display_memory_read(paddr_t addr, int len)
{
    printf(ANSI_FMT("read memory: ", ANSI_FG_BLUE) FMT_PADDR ", the len is %d\n", addr, len);
}

void display_memory_write(paddr_t addr, int len, word_t data)
{
    printf(ANSI_FMT("write memory: ", ANSI_FG_YELLOW) FMT_PADDR ", the len is %d, the written data is " FMT_WORD "\n", addr, len, data);
}

需要注意的是开启mtrace后会有大量的输出,如果开启时,运行红白机模拟器,可能会无法正常运行,所以在不使用mtrace时,我们可以将其关闭,或者将printf换为log_write,将这些信息输出到日志中。

实现ftrace

这个实现就很复杂了,首先在parse_args()添加相关代码,其实也就是添加一个-e的选项:

static int parse_args(int argc, char *argv[]) {
  const struct option table[] = {
    {"elf"      , required_argument, NULL, 'e'},
  };
  int o;
  while ( (o = getopt_long(argc, argv, "-bhl:d:p:e:", table, NULL)) != -1) {
    switch (o) {
      case 'e': elf_file = optarg; break;
      default:
        printf("Usage: %s [OPTION...] IMAGE [args]\n\n", argv[0]);
        printf("\t-e,--elf=FILE           parse the elf file\n");
        printf("\n");
        exit(0);
    }
  }
  return 0;
}
void init_monitor(int argc, char *argv[]) {
  /* Perform some global initialization. */
  parse_elf(elf_file);
}

nemu.mk中添加NEMUFLAGS += -e $(IMAGE).elf,用于开启解析elf文件功能,接下来,我们的目标就是获取到elf文件中的符号表,将其保存,供后续使用。elf文件的介绍网上有很多,所以STFW吧。

Elf文件详解
C语言编写elf文件解析器

typedef struct {
    char name[64];
    paddr_t addr;      //the function head address
    Elf32_Xword size;
} Symbol;

Symbol *symbol = NULL;  //dynamic allocate memory  or direct allocate memory (Symbol symbol[NUM])

void parse_elf(const char *elf_file)
{
    
    if(elf_file == NULL) return;
    
    FILE *fp;
    fp = fopen(elf_file, "rb");
    
    if(fp == NULL)
    {
        printf("failed to open the elf file!\n");
        exit(0);
    }
	
    Elf32_Ehdr edhr;
	//读取elf头
    if(fread(&edhr, sizeof(Elf32_Ehdr), 1, fp) <= 0)
    {
        printf("fail to read the elf_head!\n");
        exit(0);
    }

    if(edhr.e_ident[0] != 0x7f || edhr.e_ident[1] != 'E' || 
       edhr.e_ident[2] != 'L' ||edhr.e_ident[3] != 'F')
    {
        printf("The opened file isn't a elf file!\n");
        exit(0);
    }
    
    fseek(fp, edhr.e_shoff, SEEK_SET);

    Elf32_Shdr shdr;
    char *string_table = NULL;
    //寻找字符串表
    for(int i = 0; i < edhr.e_shnum; i++)
    {
        if(fread(&shdr, sizeof(Elf32_Shdr), 1, fp) <= 0)
        {
            printf("fail to read the shdr\n");
            exit(0);
        }
        
        if(shdr.sh_type == SHT_STRTAB)
        {
            //获取字符串表
            string_table = malloc(shdr.sh_size);
            fseek(fp, shdr.sh_offset, SEEK_SET);
            if(fread(string_table, shdr.sh_size, 1, fp) <= 0)
            {
                printf("fail to read the strtab\n");
                exit(0);
            }
        }
    }
    
    //寻找符号表
    fseek(fp, edhr.e_shoff, SEEK_SET);
    
    for(int i = 0; i < edhr.e_shnum; i++)
    {
        if(fread(&shdr, sizeof(Elf32_Shdr), 1, fp) <= 0)
        {
            printf("fail to read the shdr\n");
            exit(0);
        }

        if(shdr.sh_type == SHT_SYMTAB)
        {
            fseek(fp, shdr.sh_offset, SEEK_SET);

            Elf32_Sym sym;

            size_t sym_count = shdr.sh_size / shdr.sh_entsize;
            symbol = malloc(sizeof(Symbol) * sym_count);

            for(size_t j = 0; j < sym_count; j++)
            {
                if(fread(&sym, sizeof(Elf32_Sym), 1, fp) <= 0)
                {
                    printf("fail to read the symtab\n");
                    exit(0);
                }

                if(ELF32_ST_TYPE(sym.st_info) == STT_FUNC)
                {
                    const char *name = string_table + sym.st_name;
                    strncpy(symbol[func_num].name, name, sizeof(symbol[func_num].name) - 1);
                    symbol[func_num].addr = sym.st_value;
                    symbol[func_num].size = sym.st_size;
                    func_num++;
                }
            }
        }
    }
    fclose(fp);
    free(string_table);
}

需要注意符号表中的st_name(符号名称),它其实不是一个字符串,而是一个数值,代表的是目标文件中字符串表(.strtab节)中的一个索引值, 那里才真正存储着该符号的名称对应的字符串,也正是这个原因,我们需要读取字符串表的首地址,并将其存入string_table变量中。

通过上述代码,也可以看出,在了解了elf文件的一些结构体后,解析elf文件就主要靠fread函数与fseek函数,fseek函数用于不断调整fp文件指针的位置,然后fread再读取对应的节头表、字符串表、符号表等,并将其保存在所定义的变量中,最后再通过此变量,获取到我们需要的数据。

几个c语言相关函数:

int fread(void *buffer,int size,int count,FILE *fp);

fread()──从fp所指向文件的当前位置开始,一次读入size个字节,重复count次,并将读入的数据存放到从buffer开始的内存中; buffer是存放读入数据的起始地址(即存放何处)。

int fseek(FILE *stream, long offset, int fromwhere);

第一个参数file指针 第二个参数移动的偏移量 第三个参数移动到哪里 分别用3个宏
SEEK_SET 即0 文件开头
SEEK_CUR 即1 文件当前位置
SEEK_END 即2 文件结尾
fseek(fp,100L,SEEK_SET);把fp指针移动到离文件开头100字节处;
fseek(fp,100L,SEEK_CUR);把fp指针移动到离文件当前位置100字节处;
fseek(fp,100L,SEEK_END);把fp指针退回到离文件结尾100字节处。

接着,在jaljalr指令中识别函数调用和函数返回指令:

在这里插入图片描述

可以看出,函数返回指令的特点是rdx0rs1x1,这也是判断是否是函数返回指令的条件。

INSTPAT("??????? ????? ????? 000 ????? 11001 11", jalr   , I, R(rd) = s->pc + 4; s->dnpc = ((src1 + imm)) & ~1; 
                                                                IFDEF(CONFIG_FTRACE, if(rd == 1){
                                                                  display_call_func(s->pc, s->dnpc);
                                                                }
                                                                else if(rd == 0 && src1 == R(1)){
                                                                  display_ret_func(s->pc);
                                                                }));
INSTPAT("??????? ????? ????? ??? ????? 11011 11", jal    , J, R(rd) = s->pc + 4; s->dnpc = s->pc + imm;
                                                                IFDEF(CONFIG_FTRACE, if(rd == 1){
                                                                  display_call_func(s->pc, s->dnpc);
                                                                }));

最后,在trace.c中添加display_call_func()display_ret_func()就可以了。

int rec_depth = 1;
void display_call_func(word_t pc, word_t func_addr)
{
    /*for(int i = 0; i < func_num; i++)
    {
        printf("%s\t0x%08x\t%lu\n", symbol[i].name, symbol[i].addr, symbol[i].size);
    }
    exit(0);*/
    int i = 0;
    for(; i < func_num; i++)
    {
        if(func_addr >= symbol[i].addr && func_addr < (symbol[i].addr + symbol[i].size))
        {
            break;
        }
    }
    printf("0x%08x:", pc);

    for(int k = 0; k < rec_depth; k++) printf("  ");

    rec_depth++;

    printf("call  [%s@0x%08x]\n", symbol[i].name, func_addr);
}

void display_ret_func(word_t pc)
{
    int i = 0;
    for(; i < func_num; i++)
    {
        if(pc >= symbol[i].addr && pc < (symbol[i].addr + symbol[i].size))
        {
            break;
        }
    }
    printf("0x%08x:", pc);

    rec_depth--;

    for(int k = 0; k < rec_depth; k++) printf("  ");

    printf("ret  [%s]\n", symbol[i].name);
}

编写更多的测试

在这里发现了memcmp的实现出现了问题,记录一下:

错误实现:

int memcmp(const void *s1, const void *s2, size_t n) {
  //panic("Not implemented");
  assert(s1 && s2);
  
  while(n-- && s1 && s2 && *s1 == *s2)
  {
  	s1++;
  	s2++;
  }
  
  return *s1 - *s2;
}

如果n5的话,而且s1s25位相同,那么其实返回的是s1[5] - s2[5],也就是第六位的差值。

正确实现:

int memcmp(const void *s1, const void *s2, size_t n) {
  //panic("Not implemented");
  assert(s1 && s2);

  const char *tmp1 = s1;
  const char *tmp2 = s2;

  for(size_t i = 0; i < n; i++)
  {
    if(tmp1[i] < tmp2[i])
      return -1;
    else if(tmp1[i] > tmp2[i])
      return 1;
  }

  return 0;
}

实现DiffTest

nativeREF确实是一个很有力的工具,之前还认为测试用例有问题,后续也是通过native排除了这个错误的想法,后续native也派上了很大的用场。

ref_c的寄存器与dut_cpu的寄存器逐一进行比较即可。

bool isa_difftest_checkregs(CPU_state *ref_r, vaddr_t pc) {
  bool sign = true;
  int i = 0;
  for(; i < 32; i++)
  {
    if(cpu.gpr[i] != ref_r->gpr[i])
    {
      sign = false;
      break;
    }
  }
  if(sign && cpu.pc == ref_r->pc)
  {
    return true;
  }
  pc = ref_r->pc;
  return false;
}

一键回归测试

在一键回归测试时,又找到了两处错误,分别是load-store.cmul-longlong.c,发现是load指令和mulh指令的实现出现了问题。

load指令在取出数据时,要进行符号位拓展,存在寄存器中的数是有符号数。

INSTPAT("??????? ????? ????? 000 ????? 00000 11", lb     , I, R(rd) = SEXT(Mr(src1 + imm, 1), 8));  //注意要加SEXT进行符号位扩展
INSTPAT("??????? ????? ????? 001 ????? 00000 11", lh     , I, R(rd) = SEXT(Mr(src1 + imm, 2), 16));
INSTPAT("??????? ????? ????? 010 ????? 00000 11", lw     , I, R(rd) = SEXT(Mr(src1 + imm, 4), 32));

mulh指令表示的是两个有符号数相乘,将高字节存在寄存器中,但是不知道为什么将操作数转为有符号数时,要先转为32位有符号数,再转为64位有符号数,如果直接转为64位有符号数会出现问题。

INSTPAT("0000001 ????? ????? 001 ????? 01100 11", mulh   , Rtype, R(rd) = (((int64_t)(int32_t)src1 * (int64_t)(int32_t)src2) >> 32));

输入输出

实现printf

在实现sprintf时,已经实现了vsprintf,所以利用vsprintf实现printf也比较简单,之前实现的vsprintf比较简单,好多输出格式都未实现,还需要进一步完善。

int printf(const char *fmt, ...) {
  //panic("Not implemented");
  int i;
  char buf[256];

  memset(buf, 0, sizeof(buf));

  //va_list arg = (va_list)((char*)(&fmt) + 4);

  va_list args;
  va_start(args, fmt);
  i = vsprintf(buf, fmt, args);
  va_end(args);

  char *tmp = buf;

  while(*tmp != 0)
  {
    putch(*tmp);
    tmp++;
  }

  return i;
}

时钟(实现IOE)

参考riscv/spike/timer.c的实现:

void __am_timer_uptime(AM_TIMER_UPTIME_T *uptime) {
  uint32_t hi = inl(MMIO_BASE + 0x4c);
  uint32_t lo = inl(MMIO_BASE + 0x48);
  uint64_t time = ((uint64_t)hi << 32) | lo;
  uptime->us = time - boot_time;
}

在这里需要先读取高位,因为在rtc_io_handler中具有offset==4的判断,在读取高位时,offfset4,才会触发get_time()获取最新的时间,如果先读取低位的话,那么读取的是上次的时间,导致错误。

static void rtc_io_handler(uint32_t offset, int len, bool is_write) {
  assert(offset == 0 || offset == 4);
  if (!is_write && offset == 4) {
    uint64_t us = get_time();
    rtc_port_base[0] = (uint32_t)us;
    rtc_port_base[1] = us >> 32;
  }
}

看看NEMU跑多快

在虚拟机上得分为170多分,更改定时器差距很小。

实现dtrace

同样的,先在nemu/Kconfig中定义DTRACE

nemu/src/device/io/map.c中的map_readmap_wirte函数中分别添加

//map_read
IFDEF(CONFIG_DTRACE, display_device_read(addr, len, map));
//map_write
IFDEF(CONFIG_DTRACE, display_device_write(addr, len, data, map));

接着在trace.c中实现display_device_read()display_device_write()函数

// dtrace
void display_device_read(paddr_t addr, int len, IOMap *map)
{
    log_write(ANSI_FMT("read memory: ", ANSI_FG_BLUE) FMT_PADDR ", the len is %d, the read device is " 
                    ANSI_FMT(" %s ", ANSI_BG_BLUE) "\n", addr, len, map->name);
}

void display_device_write(paddr_t addr, int len, word_t data, IOMap *map)
{
    log_write(ANSI_FMT("write memory: ", ANSI_FG_YELLOW) FMT_PADDR ", the len is %d, the written data is " FMT_WORD 
                    ", the written device is "ANSI_FMT(" %s ", ANSI_BG_YELLOW)"\n", addr, len, data, map->name);
}

由于是根据内存访问来判断识别的是哪一个设备的,所以当开启dtrace时,输出的信息也很多,在这里使用log_write将其输出在log文件中,如果想要减少输出,继续使用printf,可以在输出前加一个判断,当上次输出的map->name与此次的map->name不同时,才输出信息,这样就可以减少大量的输出信息。

键盘(实现IOE)

参考native的实现即可。

void __am_input_keybrd(AM_INPUT_KEYBRD_T *kbd) {
  uint32_t code = inl(MMIO_BASE + 0x60);
  kbd->keydown = (code & KEYDOWN_MASK ? true : false);
  kbd->keycode = code & ~KEYDOWN_MASK;
}

VGA(实现IOE①)

和之前实现的方式类似,都是读取某一内存的值,然后进行赋值操作。

void __am_gpu_config(AM_GPU_CONFIG_T *cfg) {
  uint32_t space = inl(VGACTL_ADDR);
  int w = space >> 16;
  int h = space & 0x0000ffff;
  *cfg = (AM_GPU_CONFIG_T) {
    .present = true, .has_accel = false,
    .width = w, .height = h,
    .vmemsz = 0
  };
}

bug记录:当前在实现这个时,顺手写为了w = space & 0xffff0000,h = space & 0x0000ffff,一度未看出问题所在,space & 0xffff0000space >> 16的值肯定是不一样的。😫

硬件(NEMU)同步屏幕实现:

void vga_update_screen() {
  // TODO: call `update_screen()` when the sync register is non-zero,
  // then zero out the sync register
  if(vgactl_port_base[1])
  {
    update_screen();
    vgactl_port_base[1] = 0;
  }
}

AM_GPU_FBDRAW实现:

void __am_gpu_fbdraw(AM_GPU_FBDRAW_T *ctl) {
  int x = ctl->x, y = ctl->y, w = ctl->w, h = ctl->h;
  if (!ctl->sync && (w == 0 || h == 0)) return;
  uint32_t *pixels = ctl->pixels;
  uint32_t *fb = (uint32_t *)(uintptr_t)FB_ADDR;
  uint32_t screen_w = inl(VGACTL_ADDR) >> 16;
  for (int i = y; i < y+h; i++) {
    for (int j = x; j < x+w; j++) {
      fb[screen_w*i+j] = pixels[w*(i-y)+(j-x)];
    }
  }
  
  if (ctl->sync) {
    outl(SYNC_ADDR, 1);
  }
}

展示你的计算机系统

cpu-tests:

在这里插入图片描述
VGA:

在这里插入图片描述

slider:

在这里插入图片描述

mario:

在这里插入图片描述

游戏如何运行

NMEU:

解析instvaddr_read()paddr_read()mmio_read()map_read()host_read()return *(uint32_t *)addr
最终是map_read()(uint32_t *) *_ADDR映射到*_port_base*指的是各种设备(串口, 时钟, 键盘, VGA, 声卡, 磁盘, SD),write操作同理。

AM:

io_read()ioe_read()lut[]__am_timer_update()(举例) → inl()return *(volatile uint32_t *)addr

write操作同理。

本地系统 → NEMU(硬件) → AM(软件) → 程序

到这里,如何交互也显而易见,实质就是软件和硬件向同一个地址写/读数据!typing-game主要是通过io_read()获取屏幕大小与系统时间,通过io_write()来设置屏幕颜色,以及调整字母颜色。

PA2至此结束!

  • 21
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
NJU ICS课程实验PA(Programming Assignment),实现NEMU一个简化的i386模拟器。由C语言编写,以用户软件的形态运行,能够执行i386指令集程序。.zip 源码是经过本地编译可运行的,下载完成之后配置相应环境即可使用。源码功能都是经过老师肯定的,都能满足要求,有需要放心下载即可 源码是经过本地编译可运行的,下载完成之后配置相应环境即可使用。源码功能都是经过老师肯定的,都能满足要求,有需要放心下载即可 源码是经过本地编译可运行的,下载完成之后配置相应环境即可使用。源码功能都是经过老师肯定的,都能满足要求,有需要放心下载即可 项目资源具有较高的学习借鉴价值,也可直接拿来修改复现。可以在这些基础上学习借鉴进行修改和扩展,实现其它功能。可下载学习借鉴,你会有所收获。 项目资源具有较高的学习借鉴价值,也可直接拿来修改复现。可以在这些基础上学习借鉴进行修改和扩展,实现其它功能。可下载学习借鉴,你会有所收获。 项目资源具有较高的学习借鉴价值,也可直接拿来修改复现。可以在这些基础上学习借鉴进行修改和扩展,实现其它功能。可下载学习借鉴,你会有所收获。 可下载学习借鉴,你会有所收获。可下载学习借鉴,你会有所收获。可下载学习借鉴,你会有所收获。# 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。2. 部分字体以及插图等来自网络,若是侵权请联系删除。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值