2022年1月5日星期三

单发流水线里,带delay slot的转移指令不需要刷decode阶段的流水线寄存器

比如流水线分为F D E M W,

branch logic 放在D,包括取出imm和regidx。

计算放在E,计算出的target address直接回馈给F或者BF。

这样做其实是在F阶段不知道branch结果的时候默认取值pc+4。当branch指令走到E阶段时,这时下一条指令已经走到了D,这条正是delay slot instruction。 

所以如果指令集里是带delay slot,比如mips和sparc这样,branch taken的时候不需要刷前面的流水线,直接改变F阶段的pc。(要是有BF阶段,可能还不一样)

看chiplab时,gs232c_front()里给下一阶段的信号是3个port,每个port大概是这些信号:

    .o_allow          (de2_accept         ),
    .o_valid          ({de1_port2_valid,de1_port1_valid,de1_port0_valid}),
    .o_port0_pc       (de1_port0_pc       ),// O, 32
    .o_port0_inst     (de1_port0_inst     ),// O, 32
    .o_port0_taken    (de1_port0_br_taken ),// O, 1
    .o_port0_target   (de1_port0_br_target),// O, 30
    .o_port0_ex       (de1_port0_exception),// O, 1
    .o_port0_exccode  (de1_port0_exccode  ),// O, 5
    .o_port0_hint     (de1_port0_hint     ),
    .o_port1_pc       (de1_port1_pc       ),// O, 32
    .o_port1_inst     (de1_port1_inst     ),// O, 32
    .o_port1_taken    (de1_port1_br_taken ),// O, 1
    .o_port1_target   (de1_port1_br_target),// O, 30
    .o_port1_ex       (de1_port1_exception),// O, 1
    .o_port1_exccode  (de1_port1_exccode  ),// O, 5
    .o_port1_hint     (de1_port1_hint     ),
    .o_port2_pc       (de1_port2_pc       ),// O, 32
    .o_port2_inst     (de1_port2_inst     ),// O, 32
    .o_port2_taken    (de1_port2_br_taken ),// O, 1
    .o_port2_target   (de1_port2_br_target),// O, 30
    .o_port2_ex       (de1_port2_exception),// O, 1
    .o_port2_exccode  (de1_port2_exccode  ),// O, 5
    .o_port2_hint     (de1_port2_hint     ),
 

 没有刷D阶段流水线的信号,基本是取值后直接就发给后面的逻辑处理了,最多同时发出三条指令。o_valid[3]表示哪个port valid。

而LoongArch是没有delay slot的, 这里搞了半天一直在找刷流水线的信号。

因为br_cancel 1是表示branch taken(名字好像正好反了。。),br_target是目标地址。这俩信号是从后面回送给F阶段的。这段代码在gs232c_front()->gs232c_pipe_pc()里。

 

assign pc_next = wb_cancel ? wb_target :
                 br_cancel ? br_target :
                 pr_cancel ? pr_target :
                 bt_cancel ? bt_target : {pc_seq,2'h0};
 

 感觉这怎么可能不刷流水线就能这么发出去指令就不管了呢。

后来想在opensparc t1看看怎么刷流水线的。结果还没找到。原来sparc也带delay instruction,比mips规则更复杂点,还带个anull bit。

The instruction following a delayed control-transfer instruction is called a
delay instruction. Setting the annul bit in a conditional delayed control-
transfer instruction causes the delay instruction to be annulled (that is, to have no effect) if and only if the branch is not taken. Setting the annul bit in an
unconditional delayed control-transfer instruction (“branch always”) causes
the delay instruction to be always annulled. 

在tlu(trap logic unit)在得到中断或异常的时候刷后面整条的流水线,但还没仔细看这部分。

要想看annulled bit怎么工作的,可以追anull_next_e



题外话,看到了这么个bug fix

//bug6838,bug6989 - interrupt issued in annulled delay slot resets wm_other mask in e-stage; this
//                  reset causes switch logic to lose a long latency op(div) which set the wm_other mask
//                  in s-stage. Note that the div is issued to FPU. the ifu re-issues the interrupt -
//                  which results in flush. this kills the long latency op and div is lost
//
//                  fix is to detect interrupt in anulled delay slot followed by long latency op and
//                  not reset the wm_other mask.
//
//       10/07/04 - fix changed to delay setting of wm_other mask from d-cycle to e-cycle. hence
//                  removing the kill in killed_inst_done_e
//
//   assign killed_inst_done_e = (fcl_dtu_inst_vld_e  & swc_e | //sw inst
//                                fcl_dtu_intr_vld_e) &  // any intr
//                                 dtu_inst_anull_e;




那chiplab这个是怎么回事呢,结果是这个cpu并不是一个周期一条指令,具体说是我在看wave的时候是12个clock o_port0_pc走一条指令。不知道是不是配置的关系。

 
 
这样其实流水线都没意义了,ex阶段的信号返回给f也没关系,因为最终还要等很久才更新给is阶段的指令port。而且分支预测也不起作用了,因为总是等到ex的结果了。。。

2021年12月14日星期二

用dff还是dffe(with functional enable)是不是做流水线时的一种选择?

遇到这种情况,以前是用sram,所以一clock肯定能读出数据,所以后面的流水线都是一拍一拍的走,遇到转移指令就stall和清流水线。

 

reg  [31:0] pc_cur    ;
wire [31:0] pc_next   ;
wire        pc_next_en;
wire [29:0] pc_seq    ;
assign bt_pc   = pc_cur;
assign pc_next = wb_cancel ? wb_target :
                 br_cancel ? br_target :
                 pr_cancel ? pr_target :
                 bt_cancel ? bt_target : {pc_seq,2'h0};
assign pc_next_en = pc_go || pr_cancel || br_cancel || wb_cancel;
assign pc_seq     = pc_cur[4:2] >= 3'h4 ? {pc_cur[31:5] + 27'h0000001,3'h0} : {pc_cur[31:5],pc_cur[4:2] + 3'h4};
assign fe_target  = fe_valid ? fe_cur[31:2] : pc_cur[31:2];
assign inst_addr  = pc_cur;
always@(posedge clock)
begin
    if(reset)
    begin
        pc_cur<=pc_init;
    end
    else
    if(pc_next_en)
    begin
        pc_cur<=pc_next;
    end
end


比如龙芯里这段代码,pc_cur是reg

其实是一个带enable的dff, dffe_s()


只有当pc_next_en为1时,pc_cur寄存器才会更新。而pc_next_en需要等pc_go或者br_branch等其中任何一种情况发生才更新pc_cur,而不是每拍pc_cur都要走。




在OpenSPARC T1里也有这样的用法,只是不是在pc path里,而是在instruction path。


   // Thread Next Instruction Register
   wire   clk_nir0;
`ifdef FPGA_SYN_CLK_EN
`else

   bw_u1_ckenbuf_6x  ckennir0(.rclk (rclk),
                              .clk  (clk_nir0),
                              .en_l (fcl_fdp_thr_s1_l[0]),
                              .tm_l (~se));
`endif
`ifdef FPGA_SYN_CLK_DFF
   dffe_s #(33) t0nir_reg(.din (icd_fdp_topdata_s1[32:0]),
                                   .q    (t0nir),
                                   .en  (~(fcl_fdp_thr_s1_l[0])), .clk(rclk), .se(se), .si(), .so());
`else

   dff_s #(33) t0nir_reg(.din  (icd_fdp_topdata_s1[32:0]),
                                   .q    (t0nir),
                                   .clk  (clk_nir0), .se(se), .si(), .so());
`endif


这里还发现个有意思的事,如果是在fpga里,就用dffe。而正式版本里是用信号控制一个clk_nir0,然后这个clk再控制dff。

2021年12月5日星期日

龙芯补充源

 

  • bjlx源

也就是大佬的补充源,有更新,已经和一年前不一样了。这里有关于这个镜像的使用方法

首先下载公钥,并导入公钥:

sudo apt-key add bjlx.key

对于Debian6,修改 /etc/apt/sources.list.d/bjlx.list 为:

deb http://www.anheng.com.cn/bjlx squeeze main
deb-src http://www.anheng.com.cn/bjlx squeeze main

最后 apt update 即可

 

 

 

http://www.anheng.com.cn/bjlx/mirrors.html

 

本目录是对debian的龙芯补充源(不限于龙芯) 包含debian5-debian11
镜像:
安恒集团:www.anheng.com.cn/bjlx
中科大:mirrors.ustc.edu.cn/bjlx
清华大学:mirrors.tuna.tsinghua.edu.cn/bjlx
南京大学:mirrors.nju.edu.cn/bjlx
腾讯软件源:mirrors.cloud.tencent.com/bjlx
北京外国语大学:mirrors.bfsu.edu.cn/bjlx
原始rsync发布: rsync://rsync.anheng.com.cn/bjlx


安装方法:
首先安装bjlx的公钥:
下载/bjlx/bjlx.key
apt-key add bjlx.key
然后建立 /etc/apt/sources.d/bjlx.list
以debian10(buster)为例:
cat /etc/apt/sources.d/bjlx.list
src http://mirrors.cloud.tencent.com/bjlx buster main
src-src http://mirrors.cloud.tencent.com/bjlx buster main
然后 apt update;apt upgrade;
文件中的buster可以修改成squeeze wheezy jessie stretch buster bullseye
分别对应到 debian6 debian7 debian8 debian9 debian10 debian11

2009-2019 liushiwei@gmail.com

 

 

 

https://www.cnblogs.com/weilinfox/p/12238559.html

 

 

 

 

http://www.anheng.com.cn/loongson/install/

2021年11月2日星期二

emacs evel-mode

 Had a problem. When I C-x C-b calling the buffer menu,  the evil-mode also get enabled.


Never happened before.


To disable it,

 

;; Enable Evil
(require 'evil)
  (evil-mode 0)

;; not use evil-mode
(evil-set-initial-state 'fundamental-mode 'emacs)
(evil-set-initial-state 'Buffer-menu-mode 'emacs)
(evil-set-initial-state 'shell-mode 'emacs)
 

 

 

Also, to check what is the current major mode, do the following query: C-h v major-mode

2021年10月1日星期五

先改gs232c_front

gs232c_front gs232c_front(
    .clock           (clk                     ),
    .reset           (~resetn                 ),
    // .br_endline      (1'b0                    ),
    .pc_init         (`GRLEN'h1c000000            ),

    .br_hint         (bru_hint_input          ),
    .br_cancel       (bru_cancel_input        ),// I, 1
    .br_target       (bru_target_input        ),// I, 32
    .br_taken        (bru_taken_input         ),// I, 1
    .br_link         (bru_link_input          ),// I, 1
    .br_jrra         (bru_jrra_input          ),// I, 1
    .br_brop         (bru_brop_input          ),// I, 1
    .br_jrop         (bru_jrop_input          ),// I, 1
    .br_sign         (bru_sign_input          ),// I, 1
    .br_pc           (bru_pc_input            ),// I, 32
    .br_link_pc      (bru_link_pc_input[`GRLEN-1:2]),// I, 32

    .wb_cancel        (wb_cancel         ),// I, 1
    .wb_target        (wb_target         ),// I, 32
    .wb_link          (wb_link           ),// I, 1
    .wb_link_pc       (wb_link_pc[`GRLEN-1:2]),// I, 32
    .wb_jrra          (wb_jrra           ),// I, 1
    .wb_jrop          (wb_jrop           ),// I, 1
    .wb_brop          (wb_brop           ),// I, 1
    .wb_pc            (wb_pc             ),// I, 32

    // .wb_endline       (1'b0              ),
    .wb_taken         (wb_taken          ),

    .inst_req         (inst_req          ),// O, 1
    .inst_addr        (inst_addr         ),// O, 32
    .inst_cancel      (inst_cancel       ),
    .inst_addr_ok     (inst_addr_ok      ),// I, 1
    .inst_valid       (inst_valid        ),// I, 8
    .inst_count       (inst_count        ),
    .inst_rdata       (inst_rdata        ),
    .inst_uncache     (inst_uncache      ),// I, 1
    .inst_ex          (inst_exception    ),// I, 1
    .inst_exccode     (inst_exccode      ),// I, 5
    .o_allow          (de2_accept         ),
    .o_valid          ({de1_port2_valid,de1_port1_valid,de1_port0_valid}),
    .o_port0_pc       (de1_port0_pc       ),// O, 32
    .o_port0_inst     (de1_port0_inst     ),// O, 32
    .o_port0_taken    (de1_port0_br_taken ),// O, 1
    .o_port0_target   (de1_port0_br_target),// O, 30
    .o_port0_ex       (de1_port0_exception),// O, 1
    .o_port0_exccode  (de1_port0_exccode  ),// O, 5
    .o_port0_hint     (de1_port0_hint     ),
    .o_port1_pc       (de1_port1_pc       ),// O, 32
    .o_port1_inst     (de1_port1_inst     ),// O, 32
    .o_port1_taken    (de1_port1_br_taken ),// O, 1
    .o_port1_target   (de1_port1_br_target),// O, 30
    .o_port1_ex       (de1_port1_exception),// O, 1
    .o_port1_exccode  (de1_port1_exccode  ),// O, 5
    .o_port1_hint     (de1_port1_hint     ),
    .o_port2_pc       (de1_port2_pc       ),// O, 32
    .o_port2_inst     (de1_port2_inst     ),// O, 32
    .o_port2_taken    (de1_port2_br_taken ),// O, 1
    .o_port2_target   (de1_port2_br_target),// O, 30
    .o_port2_ex       (de1_port2_exception),// O, 1
    .o_port2_exccode  (de1_port2_exccode  ),// O, 5

 
    `LSOC1K_CONN_BHT_RAMS
);

 

gs232c_front里面有分支预测,这个可以先不管。还有指令队列,可以先实现个简单的,每次读2条指令,用2个寄存器存这 2条指令,比如inst, inst_next。模块输出有3个端口,可以先只用port0, 通过o_valid控制。

这个模块不需要接触cache,也不需要和内存axi总线交互。L1 cache可以先不动。


先要在代码里找到inst和pc的位置。gs232c_inst_queue inst_queue里负责维护这个。而gs232c_pipe_pc是综合分支预测决定pc的位置。











之前说的inst_addr,是在lsoc1000_mainpipe cpu里输出的,用来在cache里索引。inst_addr也是gs232c_inst_queue维护的。因为每一读近来都是一个cache line,这解释了以前遇到的为啥inst_addr不是4字节的增长,而是0x10 (128bit)。读进来的是inst_rdata。























开始那么多的clock,可能是读内存的时间。


打算先搞个一次存2指令, 而port先只用一个。

一次读2指令是想跟opensparc T1学。

试着用了下

先把DUMP_TRACE设为1,生成波形。

TIME_LIMIT并不是时钟周期,而是ps。

测试代码就几句

start.S

_start:
start:
    addi.w t0, t0, 1
    addi.w t0, t0, 2
    addi.w t0, t0, 3
    addi.w t0, t0, 4
    addi.w t0, t0, 5

一开始设置成20,结果波形没什么结果,但时钟周期已经经过了几个。后来设成2000,跑了995个clock,波形终于有点变化了。

DUMP_DELAY=0
DUMP_TRACE=1
TIME_LIMIT=2000


 
 
 

 

 

比如gs232c_front里的inst_addr, cpu reset后是从0x1c000000开始执行,但后面很多个clock,inst_addr没有变化,后面0x1c000010倒是有道理,因为一次读16字节。可能是axi总线设置里随即delay?可后面地址变化也没搞明白。

 

testbench/include/testbench.h里clock_total是clock数,可以在testbench里加个选项,运行几个clock。


2021年9月27日星期一

用verilator搭的测试框架大概流程

verilator testbench 和 run_func之间的关系

 

chiplab/sims/verilator/run_func下有Makefile_run

还有configure.sh,makefile,生成的文件有output等。

 

chiplab/software/下是测试代码,还有testbench代码

u@unamed:~/prjs/chiplab/software$ ls
coremark  dhrystone  func  generic  linux  my_program  random_boot

 

func下就是func_lab3这样的测试例子。

 

verilator模拟的时候是需要先编译verilog项目,再编译testbench cpp代码,然后运行编译好的二进制代码模拟。

testbench在sims/verilator目录下,include目录里是测试框架的实现部分。

u@unamed:~/prjs/chiplab/sims/verilator/testbench$ ls
include  sim_main.cpp  simu_top.v

比如include/testbench.h里是CpuTestbench的实现。run_func/Makefile_run里的参数例如DUMP_DELAY也都是传递给testbench cpp代码的。

        ../output ${RUN_FLAG} --dump-delay $(DUMP_DELAY) --dump-trace $(DUMP_TRACE) --time-limit $(TIME_LIMIT) --save-bp-time $(SAVE_BP_TIME) --ram-save-bp-file $(RAM_SAVE_BP_FILE) --top-save-bp-file $(TOP_SAVE_BP_FILE) --restore-bp-time $(RESTORE_BP_TIME) --ram-restore-bp-file $(RAM_RESTORE_BP_FILE) --top-restore-bp-file $(TOP_RESTORE_BP_FILE)


testbench编译出来就是这个output。

u@unamed:~/prjs/chiplab/sims/verilator/run_func$ ls
config-generator.mak  config-software.mak  log   log_script  Makefile_run  obj_dir  qemu_system_run.sh
config.log            configure.sh         logs  Makefile    obj           output   tmp

比如,software/func/func_lab3下面并没有编译生成的文件,而是在下面这个目录

u@unamed:~/prjs/chiplab/sims/verilator/run_func/obj/func/func_lab3_obj/obj$ ls
data_ram.coe  data_ram.mif  inst_ram.coe  inst_ram.mif  main.bin  main.data  main.elf  rom.vlog  test.s


main.elf就是func_lab3编译生成的longarch目标代码,通过func_lab3下的convert.c转换成main.bin, main.data,最终以指令内存和数据内存的形式(data_ram.coe data_ram.mif inst_ram.coe inst_ram.mif)由testbench的ouput程序加载。


测试程序运行的流程大概是这样:

chiplab/sims/verilator/run_func下运行make

在Makefile中分别编译testbench,run_func,生成文件复制到run_func下相应的目录

(可能还有golden trace)

然后Makefile里调用Makefile_run

Makefile_run里最终调用output(也就是testbench编译生成的),把编译好的目标代码以指令内存和数据内存文件的方式送给output。

 

 

要测某一条指令,可以在run_func下写个一句汇编(比如addi.w)的程序,编译以后生成inst_ram,运行testbench后检查寄存器。