精华内容
下载资源
问答
  • 寄存器模型
    2022-03-27 16:44:27

    寄存器模型的概念

    寄存器配置总线:通过控制端口,配置DUT中的寄存器,DUT可以根据寄存器的值来改变其行为。

    uvm_reg_field:寄存器模型中最小的单位是具体存储寄存器数值的变量。
    uvm_reg:比uvm_reg_field高一个级别,但依然是比较小的单位。下图为uvm_reg_field与uvm_reg_的关系:
    在这里插入图片描述

    uvm_reg_block:一个比较大的单位,在其中可以加入许多的uvm_reg,也可以加入其他的uvm_reg_block,一个寄存器模型中至少包含一个uvm_reg_block。
    uvm_reg_map:每个寄存器在加入寄存器模型时都有其地址,uvm_reg_map就是存储这些地址,并将其转换成可以访问的物理地址。

    建造只有一个寄存器的寄存器模型

    class reg_invert extends uvm_reg;
    	rand uvm_reg_field reg_data;
    	virtual function void build();
    		reg_data = uvm_reg_field::type_id::create("reg_data");
    		reg_data.configure(this, 1, 0, "RW", 1, 0, 1, 1, 0);
    	endfunction
    	`uvm_object_utils(reg_invert)
    	function new(input string name = "reg_invert");
    		super.new(name, 16, UVM_NO_COVERAGE);
    	endfunction
    endclass
    
    • new函数中的位数一般与系统总线的宽度一致,另外一个参数为是否要加入覆盖率的支持,这里选择不支持。
    • 每一个派生自uvm_reg的类都有一个build,所有的uvm_reg_field都在这里实例化,当reg_data实例化后,要调用reg_data.configure函数来配置这个字段。
      定义好这个寄存器后,需要在一个由reg_block派生的类中将其实例化:
    class reg_model enxtends uvm_reg_block;
    	rand reg_invert invert;
    	virtual function void build();
    		default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);
    		invert = reg_invert::type_id::create("invert",  , get_full_name());
    		invert.configure(this, null, " ");
    		invert.build();
    		default_map.add_reg(invert, 'h9', "RW");
    	endfucntion
    	`uvm_object_utils(reg_model)
    	function new(input string name = "reg_model");
    		super.new(name, UVM_NO_COVERAGE);
    	endfunction
    endclass
    

    同reg_reg派生的类一样,每一个由uvm_reg_block派生的类也要定义一个build函数,一般在此函数中实现所有寄存器的例化。

    • 一个uvm_reg_block中一定要对应一个uvm_reg_map,通过调用uvm_reg_block的create_map在build中将其实例化。create_map的参数中,第一个参数是名字,第二个参数是基地址,第三个参数是系统总线的宽度(byte),第四个参数是大小端,最后一个参数表示是否能够按照byte进行寻址。
    • 随后实例化invert并调用invert.configure函数。这个函数的主要功能是指定寄存器进行后门访问操作时的路径。第一个参数是此寄存器所在uvm_reg_block指针,这里填写this;第二个参数是reg_file的指针;第三个参数是此寄存器的后门访问路径,这里暂且为空。当调用完configure时,需要手动调用invert的build函数,将invert中的域实例化。
    • 最后一步是将此寄存器加入default_map中,uvm_reg_map的作用是存储所有寄存器的地址,因此必须将实例化的寄存器加入default_map中,否则无法进行前门访问操作。add_reg函数的第一个参数是要加入的寄存器,第二个参数是寄存器的地址,第三个参数是此寄存器的存取方式。

    寄存器模型的集成

    寄存器模型的前门访问操作可以分成读和写两种。无论是读或写,寄存器模型都会通过sequence产生一个uvm_reg_bus_op的变量,此变量中存储着操作类型(读还是写)和操作的地址,如果是写操作,还会有要写入的数据。此变量中的信息要经过一个转换器(adapter)转换后交给bus_sequencer,随后交给bus_driver,由bus_driver实现最终的前门访问读写操作。因此,必须要定义一个adapter。
    在这里插入图片描述

    在adapter中需要定义两个函数:

    • reg2bus:将寄存器模型通过sequence发出的uvm_reg_bus_op型的变量转换成bus_sequencer能够接受的形式。
    • bus2reg:当监测到总线上有操作时,将收集来的transaction转换成寄存器模型能够接受的形式。
      定义好adapter后,在base_test中加入寄存器模型:
    class base_test extends uvm_test;
    	my_env 			env;
    	my_vsqr 		v_srq;
    	reg_model 		rm;
    	my_adapter 		reg_sqr_adapter;
    ...
    endclass
    function void base_test::build_phase(uvm_phase phase);
    	super.build_phase(phase);
    	env = my_env::type_id::create("env", this);
    	v_sqr = my_vsqr::typr_id::create("v_sqr", this);
    	rm = reg_model::typr_id::create("rm", this);
    	rm.configure(null, " ");
    	rm.build();
    	rm.lock_build();
    	rm.reset();
    	reg_sqr_adapter = new("reg_sqr_adapter");
    	env.p_rm = this.rm;
    endfunction
    
    function void base_test::connect_phase(uvm_phase phase);
    	super.connect_phase(phase);
    	v_sqr.p_my_sqr = env.i_agt.sqr;
    	v_sqr.p_bus_sqr = env.bus_agt.sqr;
    	v_sqr.p_rm = this.rm;
    	rm.default_map.set_sequencer(env.bus_agt.sqr, reg_sqr_adapter);
    	rm.default_map.set_auto_predoct(1);
    endfunction
    

    要将一个寄存器模型集成到base_test中,至少需要在base_test中定义reg_model和reg_sqr_adapter。将所有用到的类在build_phase中例化,在例化后reg_model还要做四件事:

    1. 调用configure函数,第一个参数是parent block,由于是最顶层的reg_block,因此填null;第二个参数是后门访问路径,这里传入一个空的字符串。
    2. 调用build函数,将所有的寄存器实例化。
    3. 调用lock_model函数,调用此函数后,reg_model中就不能再加入新的寄存器了。
    4. 调用reset函数,如果不调用此函数,那么reg_model中所有寄存器的值都是0。调用此函数后,所有寄存器的值都将变为设置的复位值。
      寄存器模型的前门访问操作最终都将由uvm_reg_map完成,因此在connect_phase中,需要将adapter和bus_sequencer通过set_sequencer函数告知reg_model的default_map,并将default_map设置为自动预测状态。

    寄存器模型的使用

    寄存器模型提供了read和write两个task,对于read:

    p_rm.invert.read(status, value, UVM_FRONTDOOR);
    

    read的第一个参数为uvm_status_e型的变量,作为一个输出,其用于表明读操作是否成功;第二个参数是读取的数值;第三个是读取的方式,可选UVM_FRONTDOOR和UVM_BACKDOOR。
    对于write:

    p_sequencer.p_rm.invert.write(status, 1, UVM_FRONTDOOR);
    

    第一个参数也是uvm_status_e型的变量,用于表明写操作是否成9功;第二个参数是要写的值;第三个参数是写操作的方式,同样可选UVM_FRONTDOOR和UVM_BACKDOOR。
    寄存器模型对sequence的transaction类型没有任何要求,所以可以在一个发送my_transaction的sequence中使用寄存器模型对寄存器进行读写操作。

    后门访问与前门访问

    • 前门访问:通过寄存器配置总线来对DUT进行操作。在这个过程中,仿真时间是一直往前走的。
      通过adapter的bus2reg及reg2bus,可以实现uvm_reg_item与目标transaction的转换,以读操作为例,完整的流程为:
    1. 参考模型调用寄存器模型的读任务。
    2. 寄存器模型产生sequence,并产生uvm_reg_item:rw。
    3. 产生driver能够接受的transaction:bus_req=adapter.reg2bus(rw)。
    4. 把bus_req交给bus_sequencer。
    5. driver得到bus_req后驱动它,得到读取的值,并将读取值放入bus_req中,调用item_done。
    6. 寄存器模型调用adapter.bus2reg(bus_req,rw)将bus_req中的读取值传递给rw。
    7. 将rw中的读数据返回参考模型。
    • 后门访问:不通过总线进行读写操作,而是直接通过层次化的引用来改变寄存器的值。所有的后门访问都是不消耗仿真时间而只消耗运行时间的。

      可以使用interface以及DPI+VPI的方式来进行后门访问。
      UVM中使用DPI+VPI的方式来进行后门访问操作,它大体的流程是:
      1)在建立寄存器模型时将路径参数设置好。
      2)在进行后门访问的写操作时,寄存器模型调用uvm_hdl_deposit函数。
      3)进行后门访问的读操作时,调用uvm_hdl_read函数,在C/C++侧,此函数内部会调用vpi_get_value函数来对DUT中的寄存器进行读操作,并将读取值返回。

      在使用寄存器模型的后门访问功能时,需要做如下准备:
      (1)在reg_block中调用uvm_reg的configure函数时,需要设置好第三个参数:

    class reg_model extends uvm_reg_block;
    	rand reg_invert invert;
    	rand reg_counter_high counter_high;
    	rand reg_counter_low counter_low;
    	virtual function void build();
    		invert.configure(this, null, "invert");
    		counter_high.configure(this, null, "counter[31:16]");
    		counter_low.configure(this, null, "counter[15:0]");
    	endfunction
    endclass
    

    (2)在将寄存器模型集成到验证平台时,需要设置好根路径hdl_root:

    function void base_test::build_phase(uvm_phase phase);
    ...
    	rm = reg_model::type_id::create("rm", this);
    	rm.configure(null, " ");
    	rm.build();
    	rm.lock_model();
    	rm.reset();
    	rm.set_hdl_path_root("top_tb.my_dut");
    ...
    endfunction
    

    UVM会提供两类后门访问的函数,一类是read和write,一类是peek和poke,区别在于:第一类在进行操作时会模仿DUT的行为,第二类则完全不管DUT的行为。例如,对一个只读寄存器进行写操作,第一类由于要模拟DUT的只读行为所以写不进去,这时就可以用第二类去写。

    p_sequencer.p_rm.counter_low.poke(status, 16'hFFFD);
    p_sequencer.p_rm.counter_low.peek(status, value);
    

    poke和peek的第一个参数表示操作是否成功,第二个参数表示读写的数据。

    复杂的寄存器模型

    层次化的寄存器模型

    一般只会在第一级的uvm_reg_block中加入寄存器,而第二级的uvm_reg_block通常只添加uvm_reg_block,这样从整体上就能呈现出如下图中比较清晰的结构。
    在这里插入图片描述
    例如,一个DUT分了三个子模块:用于控制全局的global模块、用于缓存数据的buf模块、用于接收发送以太网帧的mac模块。global模块寄存器的地址为0x0000~0x0FFF,buf部分的寄存器地址为0x1000~0x1FFF,mac部分的寄存器地址为0x2000~0x2FFF,那么可以按照如下方式定义寄存器模型:

    class reg_model extends uvm_reg_block;
    
    	rand global_blk gb_ins;
    	rand buf_blk bb_ins;
    	rand mac_blk mb_ins;
    	virtual function void build();
    		default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);
    		gb_ins = global_blk::type_id::create("gb_ins");
    		gb_ins.configure(this, "");
    		gb_ins.build();
    		gb_ins.lock_model();
    		default_map.add_submap(gb_ins.default_map, 16'h0);
    
    		bb_ins = buf_blk::type_id::create("bb_ins");
    		bb_ins.configure(this, "");
    		bb_ins.build();
    		bb_ins.lock_model();
    		default_map.add_submap(bb_ins.default_map, 16'h1000);
    
    		mb_ins = mac_blk::type_id::create("mb_ins");
    		mb_ins.configure(this, "");
    		mb_ins.build();
    		mb_ins.lock_model();
    		default_map.add_submap(mb_ins.default_map, 16'h2000);
    
    	endfunction
    
    	`uvm_object_utils(reg_model)
    
    	function new(input string name="reg_model");
    		super.new(name, UVM_NO_COVERAGE);
    	endfunction
    
    endclass
    

    要将一个子reg_block加入父reg_block中,第一步是先实例化子reg_block。第二步是调用子reg_block的configure函数。第三步是调用子reg_block的build函数。第四步是调用子reg_block的lock_model函数。第五步则是将子reg_block的default_map以子map的形式加入父reg_block的default_map中。

    reg_file

    uvm_reg_file的引入主要是为了区分不同的hdl路径。

    class regfile extends uvm_reg_file;
    	function new(string name = "regfile");
    		super.new(name);
    	endfunction
    
    	`uvm_object_utils(regfile)
    endclass
    
    class mac_blk extends uvm_reg_block;
    
    	rand regfile file_a;
    	rand regfile file_b;
    	rand reg_regA regA;
    	rand reg_regB regB;
    	rand reg_vlan vlan;
    
    	virtual function void build();
    		default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);
    		file_a = regfile::type_id::create("file_a", , get_full_name());
    		file_a.configure(this, null, "fileA");
    		file_b = regfile::type_id::create("file_b", , get_full_name());
    		file_b.configure(this, null, "fileB");
    		regA.configure(this, file_a, "regA");
    		regB.configure(this, file_b, "regB");
    	endfunction
    endclass
    

    如上所示,先从uvm_reg_file派生一个类,然后在mac_blk中实例化此类,之后调用其configure函数,此函数的第一个参数是其所在的reg_block的指针;第二个参数是假设此reg_file是另外一个reg_file的父文件,那么这里就填写其父reg_file的指针(由于这里
    只有这一级reg_file,因此填写null);第三个参数则是此reg_file的hdl路径。
    当把reg_file定义好后,在调用寄存器的configure参数时,就可以将其第二个参数设为reg_file的指针。

    存储器

    在寄存器模型中加入存储器的代码如下:

    class my_memory extends uvm_mem;
    	function new(string name="my_memory");
    		super.new(name, 1024, 16);
    	endfunction
    
    	`uvm_object_utils(my_memory)
    endclass
    
    class reg_model extends uvm_reg_block;
    …
    	rand my_memory mm;
    
    	virtual function void build();
    …
    		mm = my_memory::type_id::create("mm", , get_full_name());
    		mm.configure(this, "stat_blk.ram1024x16_inst.array");
    		default_map.add_mem(mm, 'h100);
    	endfunction
    …
    endclass
    
    • 首先由uvm_mem派生一个类my_memory,在其new函数中调用super.new函数。这个函数有三个参数,第一个是名字,第二个是存储器的深度,第三个是宽度。
    • 然后在reg_model的build函数中,将存储器实例化,调用其configure函数,第一个参数是所在reg_block的指针,第二个参数是此块存储器的hdl路径。
    • 最后调用default_map.add_mem函数,将此块存储器加入default_map中,从而可以对其进行前门访问操作。如果没有对此块存储器分配地址空间,那么这里可以不将其加入default_map中。在这种情况下,只能使用后门访问的方式对其进行访问。

    寄存器模型对DUT的模拟

    期望值与镜像值

    • 镜像值(mirrored value):寄存器模型中的一个专门的变量,用于最大可能地与DUT保持同步。
    • 期望值(desired value):除了DUT的镜像值外,寄存器模型中还有期望值。如目前DUT中invert的值为’h0,寄存器模型中的镜像值也为’h0,但是希望向此寄存器中写入一个’h1,此时’h1便是期望值。
      一种方法是直接调用write任务,将’h1写入,期望值与镜像值都更新为’h1;另外一种方法是通过set函数将期望值设置为’h1(此时镜像值依然为0),之后调用update任务,update任务会检查期望值和镜像值是否一致,如果不一致,那么将会把期望值写入DUT中,并且更新镜像值。
    • 通过get函数可以得到寄存器的期望值,通过get_mirrored_value可以得到其镜像值。
    value = p_sequencer.p_rm.invert.get();
    value = p_sequencer.p_rm.invert.get_mirrored_value();
    

    常用操作

    • read&write操作:无论通过后门访问还是前门访问的方式从DUT中读取或写入寄存器的值,在操作完成后,寄存器模型都会根据读写的结果更新期望值和镜像值(二者相等)。
    • peek&poke操作:在操作完成后,寄存器模型会根据操作的结果更新期望值和镜像值(二者相等)。
    • get&set操作:set操作会更新期望值,但是镜像值不会改变。get操作会返回寄存器模型中当前寄存器的期望值。
    • update操作:这个操作会检查寄存器的期望值和镜像值是否一致,如果不一致,那么就会将期望值写入DUT中,并且更新镜像值,使其与期望值一致。
    • randomize操作:寄存器模型提供randomize接口。randomize之后,期望值将会变为随机出的数值,镜像值不会改变。但是并不是寄存器模型中所有寄存器都支持此函数。如果不支持,则randomize调用后其期望值不变。

    其他用法

    reg_predictor

    在这里插入图片描述

    除了像上图中左边一样使用driver的返回值更新寄存器模型以外,还可以像右边一样由monitor将从总线上收集到的transaction交给寄存器模型。
    在使用右边这种方式更新数据时,需要例化一个reg_predictor,并为这个reg_predictor实例化一个adapter:

    class base_test extends uvm_test;reg_model rm;
    	my_adapter reg_sqr_adapter;
    	my_adapter mon_reg_adapter;
    
    	uvm_reg_predictor#(bus_transaction) reg_predictor;
    …
    endclass
    
    function void base_test::build_phase(uvm_phase phase);
    …
    	rm = reg_model::type_id::create("rm", this);
    	rm.configure(null, "");
    	rm.build();
    	rm.lock_model();
    	rm.reset();
    	reg_sqr_adapter = new("reg_sqr_adapter");
    	mon_reg_adapter = new("mon_reg_adapter");
    	reg_predictor = new("reg_predictor", this);
    	env.p_rm = this.rm;
    endfunction
    
    function void base_test::connect_phase(uvm_phase phase);
    …
    	rm.default_map.set_sequencer(env.bus_agt.sqr, reg_sqr_adapter);
    	rm.default_map.set_auto_predict(1);
    	reg_predictor.map = rm.default_map;
    	reg_predictor.adapter = mon_reg_adapter;
    	env.bus_agt.ap.connect(reg_predictor.bus_in);
    endfunction
    

    在connect_phase中,需要将reg_predictor和bus_agt的ap口连接在一起,并设置reg_predictor的adapter和map。只有设置了map后,才能将predictor和寄存器模型关联在一起。
    当总线上只有一个master时,则上图中的左边和右边是完全等价的。如果有多个主设备,则左边会漏掉某些transaction。

    mirror操作

    UVM提供mirror操作,用于读取DUT中寄存器的值并将它们更新到寄存器模型中。
    其有两种应用场景,一是在仿真中不断地调用它,使得到整个寄存器模型的值与DUT中寄存器的值保持一致,此时check选项是关闭的。二是在仿真即将结束时,检查DUT中寄存器的值与寄存器模型中寄存器的镜像值是否一致,这种情况下,check选项是打开的。

    (mirror更新的是寄存器模型,update更新的是DUT。)

    更多相关内容
  • 跟我学汇编(三)寄存器物理地址的形成

    千次阅读 多人点赞 2016-02-07 08:37:46
    一、通用寄存器对于一个汇编程序员来说,CPU中主要部件是寄存器寄存器是CPU中程序员可以用指令读写的部件。程序员通过改变各种寄存器的内容来实现对CPU的控制。不同的CPU,寄存器的个数、结构是不同的。8086CPU 有...

    一、通用寄存器

    对于一个汇编程序员来说,CPU中主要部件是寄存器。寄存器是CPU中程序员可以用指令读写的部件。程序员通过改变各种寄存器的内容来实现对CPU的控制。

    不同的CPU,寄存器的个数、结构是不同的。8086CPU 有14个寄存器,每个寄存器有一个名称。这些寄存器是:AX、BX、CX、DX、SI、BP、IP、CS、SS、DS、ES、PSW。在今后的学习中我们用到这些寄存器时就对这些寄存器进行介绍。

    AX、BX、CX、DX四个寄存器可以存放一般性的数据,所以这四个寄存器称为通用寄存器。在8086CPU中,寄存器都是16位的,可以存放两个字节的数据,所以表示的最大值是2^16-1。

    8086CPU的上一代CPU使用的是8位的寄存器,所以为了保证程序的兼容性,使的针对上一代CPU开发的汇编程序能够在8086CPU上运行,8086将通用寄存器分成两个8位的寄存器:

    • AX可以分为AH和AL
    • BX可以分为BH和BL
    • CX可以分为CH和CL
    • DX可以分为DH和DL

      这里写图片描述

    以AX为例,如上图所示。AX的0~7位为AL,8~15位为AH。对AX中存放的数据有两种解释方式,如果是看做16位的AX则表示20000,如果看做8位的寄存器,则AH表示78,AL表示32。

    二、几条汇编指令

    下面我们来看一下汇编中两个最常用的指令mov和add,下表展示了它们的用法。

    这里写图片描述

    注意:汇编指令是不区分大小写的,所以写成ADD AX,8和add ax,8的效果是一样的。

    我们结合着刚刚讲过的寄存器来看一下汇编指令是如何改变寄存器中的内容的。

    我们假定开始时,AX和BX中的数据都是0000H。

    这里写图片描述

    现在我们来分析一下最后的?是怎么回事。最后一条指令执行之前AX=8226H,BX=8226H,如果执行ADD操作,则A的值应该是1044CH,但是AX是16位的寄存器,最大值就为FFFFH,所以超出16位的部分就会被省略,所以AX=044CH。

    再来看看对AH和AL的操作实例:

    我们还是假设,开始时AX和BX中的数据为0000H。

    这里写图片描述

    下面来看一下最后的?应该是什么值,ADD AL,93H执行后AL的数值为158H,但是AL为8位寄存器最大只能存储FFH,所以超过8位的部分会自动省略,于是AX中数据就变成了0058H。

    三、物理地址的形成

    8086CPU是16位的结构,这就意味着字长是16位,寄存器的最大宽度为16位,运算器一次可以处理16位的数据,寄存器和运算器之间的数据通路为16位。

    然而8086CPU却有20位地址总线,可以传送20位地址,所以能够达到1MB的寻址空间,但是由于是16位架构的CPU,所以如果仅仅从CPU内部简单的将地址送出则只能形成16位的地址,寻址能力也只有64KB,这可咋办呢。

    为了解决上述问题,8086CPU采用了一种使用两个16位地址合成一个20位地址的方法来形成一个20位的物理地址,从而扩大了寻址能力。

    物理地址=段地址*16+偏移地址

    这里写图片描述

    地址加法器采用物理地址=段地址*16+偏移地址的方法用段地址和偏移地址合成物理地址。例如,8086CPU想要访问123C8H的内存单元,此时,加法器就利用1230H和00C8H两个地址形成123C8H这个地址。

    我们可以用一个简单的例子来描述一下这个思想,如下图所示,假如学校、体育馆和图书馆的位置如下:

    这里写图片描述

    假如你有一张可以写4位数的纸条,那么图书馆的位置可以被表述为2826,如果不幸你没有4位的纸条只有两张三位的纸条,那么图书馆的位置就必须借助上面的思想,我们可以用200和826两个三位数字来表示,图书馆的位置就在200*10+826=2826m上。

    四、段寄存器

    上面我们一直说段地址,但实际上内存中并没有分段,段的划分来自CPU,由于8086CPU用基础地址(段地址)*16+偏移地址=物理地址的方式给出内存的物理地址,使得我们可以用分段的方式来管理内存。我们可以认为10000H~100FFH的内存单元为一个段,段的起始地址是10000H,段地址为1000H,大小为100H;我们也可以认为10000H~1007FH、10080H~100FFH的内存单元组成两个段,它们的起始地址为10000H和10080H,段地址为1000H和1008H,段大小为80H。

    既然地址加法部件要用段地址和偏移地址形成物理地址,那么这两个地址就必须都被保存下来。8086CPU有四个段寄存器:CS、DS、SS、ES。

    CS和IP是8086CPU中两个关键的寄存器,他们指示了CPU当前要读取指令的地址。CS为代码段寄存器,IP为指令指针寄存器。如果CS中的内容为M,IP中的内容为N,那么CPU就将从内存M*16+N单元开始,读取一条指令并执行。也可以表述为如下:

    8086机中,任意时刻,CPU将CS:IP指向的内容当做指令执行。

    下面通过一组图的方式展示8086CPU读取、执行一条指令的过程。

    这里写图片描述

    初始状态,CS:2000H,IP:0000H。

    这里写图片描述

    地址加法器利用CS和IP中的地址形成物理地址。

    这里写图片描述

    地址加法器将物理地址送入输入输出控制电路。

    这里写图片描述

    输入输出电路将地址送上地址总线。

    这里写图片描述

    从内存20000H单元开始存放的机器指令B8 23 01通过数据总线被送入CPU

    这里写图片描述

    输入输出电路将机器指令送入指令缓冲器。

    这里写图片描述

    读取一条指令后,IP中值会自动增加,以使CPU可以读取下一条指令,因为当前读入的指令为3个字节,所以IP的值加3。

    这里写图片描述

    执行控制器执行指令。

    这里写图片描述

    AX中的内容被改变。

    后面的过程与这个过程是相同的,这里不再画出来了,因为文章的篇幅已经很长了。

    注意:在8086CPU加电启动后或复位后,CS和IP被设置为CS=FFFFH,IP=00000H,即8086CPU在刚启动时,CPU从内存FFFF0H单元中读取指令执行。

    五、修改CS、IP的指令

    那么我们是否能够通过指令改变CS和IP的值呢?答案是肯定的,但是不是通过MOV指令,8086提供了单独的指令来改变这两个寄存器的值。

    1、若想同时改变CS和IP的值,可以用“JMP 段地址:偏移地址”的指令来完成。

    例如:JMP 2AE3:3,执行后:CS=2AE3H,IP=0003H,CPU将从2AE33H单元读取指令。

    2、若想仅修改IP的内容,可以使用形如“JMP 某个合法寄存器”的指令来完成。

    例如:JMP AX,指令执行前:AX=1000H,CS=2000H,IP=00003H,指令执行后:AX=1000H,CS=20000H,IP=1000H

    展开全文
  • LDT 叫局部描述符表,是与 GDT 全局描述符表相对应的,内核态的代码用 GDT 里的数据段和代码段,而用户进程的代码用每个用户进程自己的 LDT 里得数据段和代码段。 分段 PCB里放的应该是一堆基址

    发现网上好像没人说这个问题,自己学习的时候总结了一下,适合有linux基础同学进阶加深理解

    流程

    程序放到内存中

    1、程序分成代码段,数据段,堆,栈,内存映射区域

    2、从内存中找到一段空闲,得到基地址,段载入到空闲中,将基地址写入到PCB当中

    3、执行过程中,每取出一条指令,进行重定位,地址翻译

    在这里插入图片描述

    概念

    LDT 叫局部描述符表,是与 GDT 全局描述符表相对应的,内核态的代码用 GDT 里的数据段和代码段,而用户进程的代码用每个用户进程自己的 LDT 里得数据段和代码段。
    在这里插入图片描述

    分段

    PCB里放的应该是一堆基址,分别代表不同的分段,代码段,数据段…
    每一段找到一个空闲位置,记录在LDT表里面
    在这里插入图片描述
    然后LDT表赋值给PCB
    这里可以看task_struct源码

    struct task_struct {
    /* these are hardcoded - don't touch */
    
      ......
      
      /* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
        struct desc_struct ldt[3];
      /* tss for this task */
        struct tss_struct tss;
    };
    

    重定位

    然后PC指针从0开始取指执行
    在每执行一条指令的时候,都查这个LDT表
    根据表中的基址和程序中的地址,得到线性地址

    具体怎么定位

    具体怎么定位(数据):<段号,段内偏移>
    下图为一张段表

    • MOV [DS:100], %eax ——即数据段偏移100,假设DS寄存器中的段号为0,那么,线性地址=180K+100
    • jmpi 100, CS ——即跳转到代码段偏移100位置,假设CS寄存器中的段号为1,那么,线性地址=360K+100

    在这里插入图片描述

    PS:进程切换的时候,LDT是跟着切换的

    他们在哪

    在这里插入图片描述
    CPU 通过 tr 寄存器找到当前进程的任务状态段信息,也就是上下文信息,以及通过 ldt 寄存器找到当前进程在用的局部描述符表信息。

    实例

    以哈工大李治军老师的课程为例,具体见我的博客哈工大操作系统实验—lab6:地址映射与共享

    首先以汇编级调试的方式启动Bochs,引导Linux 0.11,在0.11下编译和运行程序test.c
    效果:可以看到程序会一直空转,也就是陷入了死循环,永远不会主动退出程序

    #include <stdio.h>
    
    int i = 0x12345678;
    
    int main(void)
    {
        printf("LQD The logical/virtual address of i is 0x%08x", &i);
        fflush(stdout);
    
        while (i)
            ;
    
        return 0;
    }
    
    实验目的:

    就是一步步的找到这个变量i的物理地址,然后将它改成0,使得可以退出循环。

    实验原理:

    然后在调试器中通过查看程序(进程)的各项系数参数,从GDT表、逻辑地址、LDT表、线性地址、页表、最后计算出变量i的物理地址,最后通过直接修改物理内存的方式来让test.c进程退出。

    运行代码:

    Linux 0.11上编译test.c得到可执行文件,然后运行test.c:
    在这里插入图片描述
    后面的0x00003004就是这个程序中变量i的逻辑地址,这个值在任何机器上都是一样的,在同一个机器上运行多次当然也是一样的。

    The logical/virtual address of i is 0x00003004
    
    反汇编代码:

    使用反汇编指令u/7,显示从当前位置while(1)开始的7条指令的反汇编指令:
    在这里插入图片描述
    很容易分析出来变量i存放的位置就是ds:0x3004(就是上面输出的逻辑地址),
    然后将变量i0x00000000位置的值相比较:

    if 相等 (i==0)
        就退出循环(jz ...)
    else 不相等 (i!=0)
        就继续往下执行(jmp ...),也就是继续执行循环体的内容
    
    翻译过程:

    开始寻找和逻辑地址ds:0x3004对应的物理地址,也就是去跟踪地址转换过程。

    1、找段表(LDT表)

    LDT在哪里呢?LDTR寄存器是线索的起点,通过它可以在GDT中找到LDT的物理地址

    LDTR不是直接指向对应的LDT表的,而是存储GDT表对于这个LDT表的偏移。

    sreg命令可以显示所有寄存器的信息:
    在这里插入图片描述

    可以看到LDTR寄存器的值是0x0068=0000000001101000(二进制),表示LDT表存放在GDT表的1101(二进制)号位置

    注:段选择子是一个16位寄存器,它的各位的意义如下:
    在这里插入图片描述

    GDT表的位置也已经由GDTR寄存器给出,在物理地址0x00005cb8下。

    2、查看GDT表

    使用命令xp /32w 0x00005cb8查看GDT表:
    在这里插入图片描述
    GDT表中的每一项占64位(8个字节),所以要查找的项的地址是0x00005cb8 + 13*8,使用命令:xp /2w 0x00005cb8 + 13*8

    在这里插入图片描述
    0x52d00068 0x000082fd 将加粗的数字组合成:=> 0x00fd52d0
    这就是LDT表的物理地址(GDT表存放了所有进程的LDT表)。

    组合规则见段描述符结构

    在这里插入图片描述

    3、查段表

    段描述符
    在保护模式(32位)下,段寄存器有另外一个名字,叫做段选择子,因为它保存的信息主要是该段在段表里的索引值,用这个索引值可以从段表中选择出相应的段描述符。

    寄存器ds保存的就是段选择子
    先看看ds选择子里面的内容,还是使用命令sreg:
    在这里插入图片描述
    可以看到ds的值是0x0017,段选择子是一个16位寄存器,它的各位的函数如下:

    在这里插入图片描述
    其中RPL是请求特权级,当访问一个段时,处理器要检查RPLCPL(放在cs的位0和位1中,用来表示当前代码的特权级),即使程序有足够的特权级(CPL)来访问一个段,但如果RPL(如放在ds中,表示请求数据段)的特权级不足,则仍然不能访问,即如果RPL的数值大于CPL(数值越大,权限越小),则用RPL的值覆盖CPL的值。
    而段选择子中的TI是表指示标记,如果TI=0,则表示段描述符(段的详细信息)在GDT(全局描述符表)中,即去GDT中去查;
    而TI=1,则去LDT(局部描述符表)中去查。

    看看上面的ds0x0017=0000000000010111(二进制),所以RPL=11,可见是在最低的特权级(因为在应用程序中执行),TI=1,表示查找LDT表,索引值为10(二进制)= 2(十进制),表示找LDT表中的第3个段描述符(从0开始编号)。

    LDT和GDT的结构一样,每项占8个字节。
    所以第3项“0x00003fff 0x10c0f300”就是搜寻好久的ds的段描述符了。
    在这里插入图片描述

    4、得到线性地址

    段描述符是一个64位二进制的数,存放了段基址和段限长等重要的数据。其中位P(Present)是段是否存在的标记;位S用来表示是系统段描述符(S=0)还是代码或数据段描述符(S=1);四位TYPE用来表示段的类型,如数据段、代码段、可读、可写等;DPL是段的权限,和CPL、RPL对应使用;位G是粒度,G=0表示段限长以位为单位,G=1表示段限长以4KB为单位。
    在这里插入图片描述

    实际上需要的只有段基址这一项数据
    即段描述符0x00003fff 0x10c0f300 中加粗部分组合而成的 0x10000000
    这就是ds段在线性地址空间的起始地址(也就是该进程对应的段在虚拟内存中的起始地址),

    !!这里的段是代码段(因为while(1)执行在代码段),要找其他的段也是类似的方法。

    段基址 + 段内偏移 = 线性地址 —— 0x10000000 + 0x3004 = 0x10003004

    5、得到物理地址

    既然说到这了,顺带把物理地址计算也给说了把。这个内容网上很多,也可以参考其他博客内容

    从线性地址计算物理地址,需要查找页表,线性地址翻译成物理地址的过程如下:

    在这里插入图片描述

    页目录号 + 页表号 + 页内偏移,它们分别对应了32位线性地址10位 + 10位 + 12

    0x10003004的页目录号=64,页号=3,页内偏移=4

    6、查页表

    在IA-32(英特尔的32位CPU架构)下,页目录表的位置由CR3寄存器指引,用creg命令可以看到:
    在这里插入图片描述

    说明页目录表的基址为0,看看其内容用命令:xp /68w 0
    在这里插入图片描述

    页目录表和页表中的内容很简单,是1024个32位数 这32位中前20位是物理页框号,后面是一些属性信息(最重要的是最后一位P)
    页表项结构

    其中第65个页目录项就是要找的内容,(页表项大小4位),用命令查看: xp /w 0 + 64*4

    在这里插入图片描述
    页表所在的物理页框号为0x00fa7,即页表在物理内存为0x00fa7000处,从该位置开始查找3号页表项(每个页表项4个字节),用命令: xp /w 0x00fa7000 + 3*4
    在这里插入图片描述

    7、计算得到物理地址

    线性地址0x10003004对应的物理页框号为0x00fa6,和页内偏移0x004接到一起,得到0x00fa6004,这就是变量i的物理地址

    8、修改变量

    然后已经找到了物理地址,但是还有最后一步: 直接修改物理地址的值,使得变量i为0 命令: setpmem 0x00fa6004 4 0(表示从0x00fa3004地址开始设置4个字节都为0)
    在这里插入图片描述
    然后输入命令c(如上),继续从调试状态的程序运行下去,就会看到在Bochs中程序顺利退出:
    在这里插入图片描述

    总结一下过程

    • GDT (基地址 )+ LDT(偏移 )= 段表
    • 段表 (基地址 )+(假设代码段)ds段选择子 (偏移 )= 线性地址的基址(段基址)

    上面两层呢,是线程层面的描述

    ————————————————————————————————————————————
    下面呢,是线程正儿八经的地址转换

    • 段基址 + 偏移量 = 线性地址
    • 线性地址换算成二进制分别对应 页目录项DIR 页表项TABLE 偏移OFFSET
    • 寄存器CR3 + DIR(十进制)*4 = 页目录地址
    • 页目录地址 + TABLE(十进制)*4 = 页表地址
    • 页表 + OFFSET = 物理地址
    展开全文
  • 汇编语言(2)- 寄存器

    万次阅读 2021-09-23 17:00:23
    寄存器 1. 特点 一个典型的CPU是由运算器、控制器、寄存器(CPU工作原理)等器件构成,器件内部靠总线想连。前面说的相当于外部总线。CPU的不同,寄存器的个数、结构也是不同的 运算器进行信息处理 寄存器进行信息...

    寄存器

    1. 特点

    一个典型的CPU是由运算器、控制器、寄存器(CPU工作原理)等器件构成,器件内部靠总线想连。前面说的相当于外部总线。CPU的不同,寄存器的个数、结构也是不同的

    • 运算器进行信息处理
    • 寄存器进行信息存储:CPU中最主要的器件,程序员可以通过改变各种寄存器中的内容来实现对CPU的控制
    • 控制器控制各种器件进行工作
    • 内部总线连接各种器件,在它们之间进行数据传送

    1.1 通用寄存器

    在8086CPU中,所有寄存器都是16位,可以存放2个Byte。AX、BX、CX、DX这四个寄存器通常用来存放一般性的数据。可以称为通用寄存器。

    AX寄存器16位的逻辑结构图:

    在这里插入图片描述

    • AX可分为AH和AL
    • BX可分为BH和BL
    • CX可分为CH和CL
    • DX可分为DH和DL

    例子:数据 18,二进制表示 10010,在AX中存储:

    在这里插入图片描述

    例子:数据20,二进制 100111000100000

    在这里插入图片描述

    AX中,8080CPU的16位寄存器又分为两个8位寄存器,分别为高位和低位

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

    1.2 字在存储器中的存储

    • 字节:一个字节由8个big组成,可以存在8位寄存器中
    • 字:word,一个字由两个字节组成,这两个字节分别称为这个字节的高位字节和低位字节

    在这里插入图片描述

    在高位和低位所存储的数据可以分别作为两个独立的字节型数据。cpu中的寄存器可以存放N个 8位的数据,也就是说计算机中的数据大多数是由 1~N 个8位数据构成。

    1.3 汇编指令

    在这里插入图片描述

    例子:

    在这里插入图片描述

    分析:

    最后一条指令 add ax,bx ,将bx寄存器中的数据跟 ax中的数据进行相加,ax寄存器中的数据为 8226H,bx中的数据也为8226H,得出的结果为1044CH,因为ax为16位寄存器,所以最高位的1 就不能存在ax中,所以ax的数据为044CH。

    在这里插入图片描述

    分析:

    程序段中的最后一条指令为 add al,93H,在执行前,al中的数据为C5H,相加后为:158H,但al为8位寄存器,只能存放两位16进制数据,所以最高位1丢失(丢失,指的是进位制不能在8位寄存器中保存,但是CPU不会真的丢弃这个进位制)

    注意点:

    此时al是作为单独的8位寄存器来使用的,和ah没有关系,CPU执行时跟ah是没有任何关系的。执行运算操作时,两个操作的对象时,位数必须是一致的。而8位寄存器,最高也只能存放255的数据

    1.4 检测点

    在这里插入图片描述

    mov ax,62627  # 62627为十进制,转换 16进制为F4A3   ah:F4 al:A3  AX=F4A3H
    mov ah,31H    #  					                        AX=31A3H
    mov al,23H    #                                             AX=3123H
    add ax,ax     # 3123+3123                                   AX=6246H
    mov bx,826CH  #                                             BX=826CH
    mov cx,ax     #                                             CX=6246H
    mov ax,bx     # ax = bx                                     AX=826CH
    add ax,bx     # ax = ax + bx = 104D8                        AX=04D8H
    mov al,bh     # al = bh = 82                                AX=0482H
    mov ah,bl     # ah = bl = 6C                                AX=6C8CH
    add ah,ah     # ah = ah + ah                                AX=D88CH
    add al,6      # al = al + 6                                 AX=D806H
    add al,al     # al = al + al                                AX=D80CH
    mov ax,cx     # ax = cx = 6246H                             AX = 6246H
    

    在这里插入图片描述

    mov ax,0000H   # 赋值初始值
    add al,10H     # ax的低位为加上 10H,ax=0010H
    

    2. 物理地址

    cpu在访问内存单元时,需要给出内存地址,而内存单元的存储空间是一个一维的线性空间,每一个内存单元在这个空间都是唯一的地址,我们称这个地址为物理地址

    2.1 16位结构的CPU

    • 运算器一次最多可以处理16位的数据
    • 寄存器的最大宽度为16位
    • 寄存器和运算器之间的通路为16位

    8086是16位结构的CPU,在8086内部能够一次性处理、传输、暂时存储的信息的最大长度为16位。内存单元的地址在送上地址总线之前,必须在CPU中处理、传输、暂时存放

    2.2 8086CPU给出物理地址的方法

    8086地址总线有20根,而CPU又只能处理16位,如果从CPU内部简单地发出,那么只能送出16位的地址,表现力只有64KB。

    8086在内部采用一种用两个16位地址合成的方式来形成一个20位物理地址。

    在这里插入图片描述

    CPU的寻址功能是:基础地址(段地址 x 16)+偏移地址 = 物理地址

    2.3 “段”的概念

    并不是内存被划分成了一个一个的段。而是被CPU划分成的逻辑段,便于CPU就行管理。以后在编程时可以使用 段地址x16(十进制数据) 定位段的起始地址。一个段的起始地址必然是 16的倍数,偏移地址也为16位,16位的寻址能力为64KB,所以一个段的长度最大为64KB

    如果给定一个段地址,仅通过变化偏移地址来进行寻址,最多可以定位多少个内存单元?

    结论:偏移地址16位,变化的范围0 ~ FFFFFH(2^16 - 1 存储单元),用偏移地址来进行寻址最多寻找 64KB。如:给定段位地址 1000H,用偏移地址进行寻址:10000H ~ 1FFFFH

    在这里插入图片描述

    1. 0001H x 10 (16的十六进制) = 0010H 获得起始地址,偏移地址最大为64KB 
    00010H 到 1000FH
    2. 最小为 0,最大为 1000H。
    取0时:0000H * 10H = 00000H ~ FFFFH。取不到 20000H
    取1000H时:1000H * 10H = 10000H ~ 基本地址+偏移地址(1FFFFH)取不到20000H
    

    3. 段寄存器

    8086CPU在访问内存时需要由相关部件提供内存单元的段地址和偏移地址,送入地址加法合成物理地址。8086CPU共有4个段寄存器:CS、DS、SS、ES。CPU在访问内存单元时就由这四个寄存器提供段地址

    3.1 CS和IP

    CS和IP是8086CPU中两个最关键的寄存器,他们指示了CPU当前要读取指令的地址,CS为代码段寄存器,IP为指令指针寄存器。

    例如:设CS中的内容为M,IP中的内容为N,8086CPU内存就会从 M*16 + N单元开始,读取一条指令并执行。相当于,8086会去获取到CS:IP指向的内容当作指令执行

    在这里插入图片描述

    • CPU先将CS:IP 送入地址加法器确认需要读取的内存单元 (物理地址 = 段地址 * 16 + 偏移地址)
    • 地址加法器将数据送入 输入输出控制电路,然后读取 物理地址 20000H 存放的数据 :B8 23 01通过数据总线送入 输入输出控制电路中
    • CS:IP 这时读取的指令长度为 3 byte,就会自增存放下一条指令的地址,然后继续读取剩下的指令

    在内存中,指令和数据是没有任何区别的,都是二进制数据,CPU在工作时会将CS:IP 指向的内存地址看作是指令并且执行

    3.2 修改CS和IP的方法

    在8086CPU中大部分寄存器的值,都可以使用mov进行修改,mov指令被称为传送指令。但是在8086中mov不能设置CS:IP的值,因为不支持。所以改为了 jmp 命令来进行修改。

    jmp 2AE3:3 执行前:CS = 2AE3H, IP=00003H

    jmp 3:0B16 执行后:CS=0003H, IP=0B16H

    jmp ax 执行前:ax=1000H, cs=2000H,ip=0003H。执行后:ax=1000H,cs=1000H

    jmp命令相当于 mov IP,ax 指令

    3.3 代码段

    在编程时,可以根据需要,将一组内存单元定义为一个 “”。只需要将cs:ip的地址改为当前代码段的起始地址即可执行

    4. 实验

    查看CPU和内存,用机器指令和汇编指令编程

    Debug是提供给查询CPU各种寄存器中内容、内存的情况以及机器码级跟踪程序的运行

    在这里插入图片描述

    4.1 win10环境安装Debug指令

    • 安装 DOSBox:https://www.dosbox.com/

    • 找到DOSBox安装路径,双击打开其中 DOSBox 0.74 Options.bax

    • 添加 DEBUG.exe 的文件路径

    • 直接打开DOSBox.exe

    在这里插入图片描述

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kr1cxPKs-1632387604907)(images/image-20210923153811799.png)]

    在这里插入图片描述

    输入 r 查看寄存器中的内容

    在这里插入图片描述

    CS:IP 此时指向的地址是 073F:0100 , 而对应的汇编指令是 ADD [BX+SI],AL

    4.2 修改寄存器中值

    输入 “ r ax ” 出现 " : "作为提示,在后面输入想要写入的数据
    在这里插入图片描述

    4.3 查看内存中的值

    使用 -d 内存地址:偏移地址 可以展示内存中指定内存中 128个内存单元的数据

    在这里插入图片描述

    而其中 “ - ”只要是用来进行分割范围 比如 " - " 之前的范围 1000:10 ~ 1000:17 而后面则是 17 ~ 1000:1F的范围

    最左边存放的是起始地址,最右边则存放的是内存中数据对应的 ASCLL码

    不同的操作系统所展示出的数据不一样 直接使用 -d 可以展示出系统预设的地址

    在这里插入图片描述

    -d 1000:0 9 也可以查询范围的地址

    在这里插入图片描述

    4.4 改写内存中的数据

    可以使用 -E 数据…数据 的方式来修改内存中的地址

    输入 -e 1000:f 逗号后面输入需要改变的数据,如果空格就不修改,会自动跳到下一个内存单元。都修改完成了就使用回车
    在这里插入图片描述

    4.5 查看内存中机器码所对应的指令

    -u 段地址:偏移量地址

    就可以展示出对应内存地址中的汇编指令

    在这里插入图片描述

    4.6 执行内存中的命令

    使用 -t 命令可以执行CS:IP所指向内存的指令,但是在执行之前需要将CS:IP所指向的地址改为需要执行指令的地址

    4.7 写入汇编指令

    使用 -a 段地址:偏移量地址 就可以写入汇编代码

    在这里插入图片描述

    5. 操作显卡地址

    所有的物理设备都会被映射到某一串地址当中,对内存地址中的数据进行操作,实际就是对显卡进行操作

    5.1 实例

    -e B810:0000 01 01 02 02 03 03 04 04  #向内存地址中写入当前数据
    

    在这里插入图片描述

    e B810:0000 48 07 65 07 6c 07 6c 07 6f 07 20 07 57 07 6f 07 72 07 6c 07 64 07 #48对应ascall码中字符H,07代表颜色为白色
    

    在这里插入图片描述

    展开全文
  • 地址:http://www.eccn.com/tech_260_2013053014172226.htm 前言 按照Linux分层驱动思想,外设驱动与主机控制器的...这样思想要求应用程序不应当直接访问物理地址,而是应当通过驱动程序的调用来实现,以便保持应用
  • 嵌入式的寄存器操作

    2022-01-05 15:23:11
    通过一个按键的实例来看嵌入式中的位操作。 转载如下: a |= 1<<x //第x位写1 a &=~(1<<x) //第x位写0 (a &(1<<x)) == (1<<x) //判断1,等号左边括号不能省略 (a& (1
  • 实例七— 8位移位寄存器的设计

    万次阅读 2019-02-19 11:12:12
    实例七 8位移位寄存器的设计(基于Robei工具的8位移位寄存器的设计) 4.1.1. 本章导读 设计目的 要求掌握8位移位寄存器原理,并根据原理设计8位移位寄存器模块以及设计相关testbench,最后在Robei可视化仿真软件进行...
  • X86寄存器 (笔记)

    千次阅读 2020-07-10 19:16:07
    32位cpu2.1 通用寄存器2.2 系统表寄存器2.2.1 全局描述符表GDT(Global Descriptor Table)2.2.2 局部描述符表LDT(Local Descriptor Table)2.2.3 任务状态段TSS(Task State Segment)2.2.4 中断描述符表IDT...
  • 寄存器模型 — UVM

    千次阅读 2021-06-19 11:11:08
    文章目录基本概念寄存器模型的集成访问寄存器的方式1. 前门访问2. 后门访问3.前门访问和后门访问的比较寄存器模型的常规方法1. mirror、desired 和 actual value2. prediction的分类3. uvm_reg 的访问方法4. uvm_mem...
  • 在上一篇博文 LLVM 里的寄存器分配 - 准备工作(一) 里,我主要整理了 LLVM 在做寄存器分配前所做的准备工作,介绍了 LLVM 是在怎样的 MIR 上做的寄存器分配。接下来,就需要讲讲 LLVM 是如何做寄存器分配了。虽然...
  • component与component直接可以使用port进行通信,component与object...后门访问是可以直接层次引用,使得访问寄存器中比较简单。 二、register model的使用 三、 前门使用adapter转换,嫁给你trans转换 REG2BUS: ...
  • 转载 寄存器平衡实例

    2014-10-21 09:42:00
    《高级设计》一书中提供了一个简单代码实例用以展示这种技术。这里我们需要注意的是本文介绍的是基于代码级别的寄存器平衡技术,各个eda厂家提供的编译工具里也有一只选项叫做寄存器平衡,这...
  • C语言代码实例

    万次阅读 2016-10-25 20:06:37
     /* 函数指针的应用实例------回调函数(写一个通用的数比较的回调函数,应用于不同的数据类型) 转换表(简化袖珍式计算器不用switch) */  printf("--------------------------分割开来---------------------...
  • 从汇编的角度理解程序 —— 通过寄存器进行数据操作的指令流 程序其实就是对数据进行操作的有序指令流 有序的指令流能通过跳转实现程序分支和循环 还能通过跳转和 stack 内存模式实现函数调用 通过规范的内存布局和...
  • UVM--寄存器访问方法

    千次阅读 2020-12-16 21:56:16
    访问前的地址映射1.2.2.后门访问1.3.前/后门访问的比较 寄存器访问方法 在没有寄存器模型之前,只能启动 sequence 通过前门(FRONTDOOR)访问的方式来读取寄存器,局限较大,在 scoreboard(或者其他 component )中...
  • ARM Android内核虚拟地址物理地址的转换实例 (ARM Android kernel virtualaddress to physical address)
  • 寄存器(CPU工作原理): X86CPU的寄存器都是16位的,可以存放两个字节。 AX,BX,CX,DX通常用来存放一般性数据被称为通用寄存器 一个16位寄存器可以存放的最大的数字是2的16次方减一。 AX的低8位(07)构成了AL寄存器...
  • 本章将介绍CUDA的内存结构,通过实例展示寄存器和共享内存的使用。CUDA内存结构GPU的内存结构和CPU类似,但也存在一些区别,GPU的内存中可读写的有:寄存器(registers)、Local memory、共享内存(shared memory)和...
  • MIPS通用寄存器

    2018-11-02 23:03:00
    MIPS通用寄存器 MIPS有32个通用寄存器($0-$31),各寄存器的功能及汇编程序中使用约定如下: 下表描述32个通用寄存器的别名和用途 REGISTER NAME USAGE $0 $zero 常量0(constant value 0) $1 $at ...
  • 关键字:、立即寻址、寄存器寻址、存储器操作数寻址方式【直接寻址、寄存器间接寻址、基址寻址、变址寻址、基址加变址寻址】、标志寄存器
  • UVM- 寄存器模型 Register Model(八)

    千次阅读 2020-12-16 21:04:52
    寄存器模型 Register Model一、RAL : Register Abstraction Layer二...验证环境中实例化RAL模型并建立连接3.5.编写RAL的测试序列3.6.显式或隐式的运行RAL序列3.7.显式或隐式执行镜像预测四、总结 一、RAL : Register Ab
  • UVM中的寄存器模型

    千次阅读 2020-10-15 15:45:03
    寄存器模型简介 1. 通常来说,DUT中会有一组控制端口,通过控制端口,可以配置DUT中的寄存器,DUT可以根据寄存器的值来改变其行为。这组控制端口就是寄存器配置总线。 在没有寄存器模型之前,只能启动sequence通过...
  • modbus通讯协议详解Modbus协议可以说是工业自动化领域应用最为广泛的通讯协议,因为它的开放性、可扩充性和...目前Modbus规约主要使用的是ASCII, RTU, TCP等,并没有规定物理层。目前Modbus常用的接口形式主要有R...
  • 写过 verilog 硬件代码的同学应该都知道 DUT 会包含很多寄存器,它们是模块间交互的接口,其用途大致可以分为两类:a. 通过读出寄存器当前的值获取 DUT 当前的状态,该类寄存器称为状态寄存器;b. 通过对寄存器进行...
  • 目录复杂的寄存器模型*层次化的寄存器模型*reg_file的作用*多个域的寄存器*多个地址寄存器*加入存储器 复杂的寄存器模型 *层次化的寄存器模型 前面寄存器模型是一个最小、最简单的寄存器模型。在整个实现过程中...
  • 对于R13,R14来说,每个寄存器对应7个不同的物理寄存器,其中一个是用户模式与系统模式共用,另外6个物理寄存器对应其他6种不同的运行模式,并采用以下记号来区分不同的物理寄存器: R13_mode R14_mode 其中mode可为:usr,...
  • i2c控制器节点分析 i2c控制器是挂在aips2总线上的,对应到设备树中,i2c控制器节点挂在aips2节点上,描述代码如下: 以i2c1节点为例,标签是i2c1,节点名称是i2c,寄存器起始地址是0x021a0000,有如下属性: #...
  • 文章目录一、汇编语言二、指令数据传输指令栈操作指令pushpop运算指令位操作比较操作指令标志寄存器流控制指令三、伪指令.equ.rept.endr.lcomm.globl.type.ascii.byte.section变量四、X86_64寄存器五、常见汇编结构1...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,449
精华内容 10,579
关键字:

寄存器物理地址代码实例