UVM寄存器模型案例

  在之前的文章中,我们已经了解寄存器模型的相关概念以及如何访问寄存器。本文中将给出一个完整的例子,理解下如何写一个完成的寄存器模型、如何把寄存器模型集成到环境中以及如何进行读写操作。

RTL设计

下面的设计具可以通过APB接口可访问的寄存器和字段。该设计本质上为一个可以通过写入某些控制寄存器来配置的红绿灯控制器。

CTL寄存器包含启动模块的字段,以及将其配置为黄闪或红闪模式字段。状态寄存器是只读的,该寄存器会返回设计的当前状态——黄色、红色或绿色。两个计时器寄存器存储从每个状态转换之间的时间。配置文件位允许用户在两个定时器值之间进行选择。分别对应高峰和非高峰时间。
在这里插入图片描述
由于只需要针对如何使用UVM寄存器模型对设计的寄存器进行读/写示例,下列模块端口信号并不完整,所有的设计接口信号遵循APB协议。

module traffic (  input          pclk,
                  input          presetn,
                  input [31:0]   paddr,
                  input [31:0]   pwdata,
                  input          psel,
                  input          pwrite,
                  input          penable,

                  // Outputs
                  output [31:0]  prdata);

   reg [3:0]      ctl_reg;    // profile, blink_red, blink_yellow, mod_en RW
   reg [1:0]      stat_reg;   // state[1:0]
   reg [31:0]     timer_0;    // timer_g2y[31:20], timer_r2g[19:8], timer_y2r[7:0] RW
   reg [31:0]     timer_1;    // timer_g2y[31:20], timer_r2g[19:8], timer_y2r[7:0] RW

   reg [31:0]     data_in;
   reg [31:0]     rdata_tmp;

   // Set all registers to default values
   always @ (posedge pclk) begin
      if (!presetn) begin
         data_in <= 0;
         ctl_reg  <= 0;
         stat_reg <= 0;
         timer_0  <= 32'hcafe_1234;
         timer_1  <= 32'hface_5678;
      end
   end

   // Capture write data
   always @ (posedge pclk) begin
      if (presetn & psel & penable)
         if (pwrite)
            case (paddr)
               'h0   : ctl_reg <= pwdata;
               'h4   : timer_0 <= pwdata;
               'h8   : timer_1 <= pwdata;
               'hc   : stat_reg <= pwdata;
            endcase
   end

   // Provide read data
   always @ (penable) begin
      if (psel & !pwrite)
         case (paddr)
            'h0 : rdata_tmp <= ctl_reg;
            'h4 : rdata_tmp <= timer_0;
            'h8 : rdata_tmp <= timer_1;
            'hc : rdata_tmp <= stat_reg;
         endcase
   end

   assign prdata = (psel & penable & !pwrite) ? rdata_tmp : 'hz;

endmodule

Interface声明

  按照APB协议的规范声明一个接口,该接口作为虚接口传到测试平台以驱动设计。简单起见,接口中不声明时钟块和modport。

interface bus_if (input pclk);
   logic [31:0]   paddr;
   logic [31:0]   pwdata;
   logic [31:0]   prdata;
   logic          pwrite;
   logic          psel;
   logic          penable;
   logic          presetn;
endinterface

寄存器模型

上述的设计对应的寄存器模型如下所示。在实际使用中,寄存器模型的生成通常用是使用脚本读取特定的格式文件(如excel)等来自动转换为寄存器模型。然后可以根据需求将其嵌入到环境中或者其他组件。

// Register definition for the register called "ctl"
class ral_cfg_ctl extends uvm_reg;
	rand uvm_reg_field mod_en;      // Enables the module
	rand uvm_reg_field bl_yellow;   // Blinks yellow
	rand uvm_reg_field bl_red;      // Blinks red
    rand uvm_reg_field profile;     // 1 : Peak, 0 : Off-Peak

	`uvm_object_utils(ral_cfg_ctl)

	function new(string name = "traffic_cfg_ctrl");
		super.new(name, 32, build_coverage(UVM_NO_COVERAGE));
	endfunction: new

  // Build all register field objects
  virtual function void build();
    this.mod_en     = uvm_reg_field::type_id::create("mod_en",,   get_full_name());
    this.bl_yellow  = uvm_reg_field::type_id::create("bl_yellow",,get_full_name());
    this.bl_red     = uvm_reg_field::type_id::create("bl_red",,   get_full_name());
    this.profile    = uvm_reg_field::type_id::create("profile",,  get_full_name());

    // configure(parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand, individually_accessible);
    this.mod_en.configure(this, 1, 0, "RW", 0, 1'h0, 1, 0, 0);
    this.bl_yellow.configure(this, 1, 1, "RW", 0, 1'h0, 1, 0, 0);
    this.bl_red.configure(this, 1, 2, "RW", 0, 1'h0, 1, 0, 0);
    this.profile.configure(this, 1, 3, "RW", 0, 1'h0, 1, 0, 0);
  endfunction
endclass

// Register definition for the register called "stat"
class ral_cfg_stat extends uvm_reg;
  uvm_reg_field state;    // Current state of the design

  `uvm_object_utils(ral_cfg_stat)
  function new(string name = "ral_cfg_stat");
    super.new(name, 32, build_coverage(UVM_NO_COVERAGE));
  endfunction

  virtual function void build();
    this.state = uvm_reg_field::type_id::create("state",, get_full_name());

    // configure(parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand, individually_accessible);
    this.state.configure(this, 2, 0, "RO", 0, 1'h0, 0, 0, 0);
  endfunction
endclass

// Register definition for the register called "timer"
class ral_cfg_timer extends uvm_reg;
	uvm_reg_field timer;     // Time for which it blinks

	`uvm_object_utils(ral_cfg_timer)
	function new(string name = "traffic_cfg_timer");
		super.new(name, 32,build_coverage(UVM_NO_COVERAGE));
	endfunction

  virtual function void build();
     this.timer = uvm_reg_field::type_id::create("timer",,get_full_name());

    // configure(parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand, individually_accessible);
     this.timer.configure(this, 32, 0, "RW", 0, 32'hCAFE1234, 1, 0, 1);
     this.timer.set_reset('h0, "SOFT");
  endfunction
endclass

// These registers are grouped together to form a register block called "cfg"
class ral_block_traffic_cfg extends uvm_reg_block;
	rand ral_cfg_ctl    ctrl;       // RW
	rand ral_cfg_timer  timer[2];   // RW
         ral_cfg_stat   stat;       // RO

	`uvm_object_utils(ral_block_traffic_cfg)

	function new(string name = "traffic_cfg");
		super.new(name, build_coverage(UVM_NO_COVERAGE));
	endfunction

  virtual function void build();
    this.default_map = create_map("", 0, 4, UVM_LITTLE_ENDIAN, 0);
    this.ctrl = ral_cfg_ctl::type_id::create("ctrl",,get_full_name());
    this.ctrl.configure(this, null, "");
    this.ctrl.build();
    this.default_map.add_reg(this.ctrl, `UVM_REG_ADDR_WIDTH'h0, "RW", 0);


    this.timer[0] = ral_cfg_timer::type_id::create("timer[0]",,get_full_name());
    this.timer[0].configure(this, null, "");
    this.timer[0].build();
    this.default_map.add_reg(this.timer[0], `UVM_REG_ADDR_WIDTH'h4, "RW", 0);

    this.timer[1] = ral_cfg_timer::type_id::create("timer[1]",,get_full_name());
    this.timer[1].configure(this, null, "");
    this.timer[1].build();
    this.default_map.add_reg(this.timer[1], `UVM_REG_ADDR_WIDTH'h8, "RW", 0);

    this.stat = ral_cfg_stat::type_id::create("stat",,get_full_name());
    this.stat.configure(this, null, "");
    this.stat.build();
    this.default_map.add_reg(this.stat, `UVM_REG_ADDR_WIDTH'hc, "RO", 0);
  endfunction
endclass

// The register block is placed in the top level model class definition
class ral_sys_traffic extends uvm_reg_block;
  rand ral_block_traffic_cfg cfg;

	`uvm_object_utils(ral_sys_traffic)
	function new(string name = "traffic");
		super.new(name);
	endfunction

	function void build();
      this.default_map = create_map("", 0, 4, UVM_LITTLE_ENDIAN, 0);
      this.cfg = ral_block_traffic_cfg::type_id::create("cfg",,get_full_name());
      this.cfg.configure(this, "tb_top.pB0");
      this.cfg.build();
      this.default_map.add_submap(this.cfg.default_map, `UVM_REG_ADDR_WIDTH'h0);
	endfunction
endclass

寄存器环境

目前为止,我们已经有了寄存器模型,下一步就是连接寄存器模型到所有独立组件的环境中进行使用。连接好的环境将使寄存器嵌入到测试平台中,并在顶层中隐藏predictor、adapter和寄存器模型之间的连接。
由于适配器依赖于正在使用的总线协议,我们需要从uvm_reg_adapter派生一个自定义类,我们将其命名为reg2apb_adapter。这里定义了reg2bus和bus2reg两个函数,以便将寄存器项转换为APB总线数据包,反之亦然。

class reg2apb_adapter extends uvm_reg_adapter;
   `uvm_object_utils (reg2apb_adapter)

   function new (string name = "reg2apb_adapter");
      super.new (name);
   endfunction

   virtual function uvm_sequence_item reg2bus (const ref uvm_reg_bus_op rw);
      bus_pkt pkt = bus_pkt::type_id::create ("pkt");
      pkt.write = (rw.kind == UVM_WRITE) ? 1: 0;
      pkt.addr  = rw.addr;
      pkt.data  = rw.data;
      `uvm_info ("adapter", $sformatf ("reg2bus addr=0x%0h data=0x%0h kind=%s", pkt.addr, pkt.data, rw.kind.name), UVM_DEBUG)
      return pkt;
   endfunction

   virtual function void bus2reg (uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
      bus_pkt pkt;
      if (! $cast (pkt, bus_item)) begin
         `uvm_fatal ("reg2apb_adapter", "Failed to cast bus_item to pkt")
      end

      rw.kind = pkt.write ? UVM_WRITE : UVM_READ;
      rw.addr = pkt.addr;
      rw.data = pkt.data;
      `uvm_info ("adapter", $sformatf("bus2reg : addr=0x%0h data=0x%0h kind=%s status=%s", rw.addr, rw.data, rw.kind.name(), rw.status.name()), UVM_DEBUG)
   endfunction
endclass

// Register environment class puts together the model, adapter and the predictor
class reg_env extends uvm_env;
   `uvm_component_utils (reg_env)
   function new (string name="reg_env", uvm_component parent);
      super.new (name, parent);
   endfunction

   ral_sys_traffic                m_ral_model;         // Register Model
   reg2apb_adapter                m_reg2apb;           // Convert Reg Tx <-> Bus-type packets
   uvm_reg_predictor #(bus_pkt)   m_apb2reg_predictor; // Map APB tx to register in model
   my_agent                       m_agent;             // Agent to drive/monitor transactions

   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_ral_model          = ral_sys_traffic::type_id::create ("m_ral_model", this);
      m_reg2apb            = reg2apb_adapter :: type_id :: create ("m_reg2apb");
      m_apb2reg_predictor  = uvm_reg_predictor #(bus_pkt) :: type_id :: create ("m_apb2reg_predictor", this);

      m_ral_model.build ();
      m_ral_model.lock_model ();
      uvm_config_db #(ral_sys_traffic)::set (null, "uvm_test_top", "m_ral_model", m_ral_model);
   endfunction

   virtual function void connect_phase (uvm_phase phase);
      super.connect_phase (phase);
      m_apb2reg_predictor.map       = m_ral_model.default_map;
      m_apb2reg_predictor.adapter   = m_reg2apb;
   endfunction
endclass

APB Agent

AMBA APB是一种用于低带宽外设访问的总线协议,该协议的典型应用是对设备中控制寄存器的访问。下面所示的代码是APB agent的一个简单模型。它没有APB协议中处理的完整功能。

// Declare a sequence_item for the APB transaction
class bus_pkt extends uvm_sequence_item;
   rand bit [31:0]  addr;
   rand bit [31:0]  data;
   rand bit         write;

   `uvm_object_utils_begin (bus_pkt)
      `uvm_field_int (addr, UVM_ALL_ON)
      `uvm_field_int (data, UVM_ALL_ON)
      `uvm_field_int (write, UVM_ALL_ON)
   `uvm_object_utils_end

   function new (string name = "bus_pkt");
      super.new (name);
   endfunction

   constraint c_addr { addr inside {0, 4, 8};}
endclass

// Drives a given apb transaction packet to the APB interface
class my_driver extends uvm_driver #(bus_pkt);
   `uvm_component_utils (my_driver)

   bus_pkt  pkt;

   virtual bus_if    vif;

   function new (string name = "my_driver", uvm_component parent);
      super.new (name, parent);
   endfunction

   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      if (! uvm_config_db#(virtual bus_if)::get (this, "*", "bus_if", vif))
         `uvm_error ("DRVR", "Did not get bus if handle")
   endfunction

   virtual task run_phase (uvm_phase phase);
      bit [31:0] data;

      vif.psel <= 0;
      vif.penable <= 0;
      vif.pwrite <= 0;
      vif.paddr <= 0;
      vif.pwdata <= 0;
      forever begin
         seq_item_port.get_next_item (pkt);
         if (pkt.write)
            write (pkt.addr, pkt.data);
         else begin
            read (pkt.addr, data);
            pkt.data = data;
         end
         seq_item_port.item_done ();
      end
   endtask

   virtual task read (  input bit    [31:0] addr,
                        output logic [31:0] data);
      vif.paddr <= addr;
      vif.pwrite <= 0;
      vif.psel <= 1;
      @(posedge vif.pclk);
      vif.penable <= 1;
      @(posedge vif.pclk);
      data = vif.prdata;
      vif.psel <= 0;
      vif.penable <= 0;
   endtask

   virtual task write ( input bit [31:0] addr,
                        input bit [31:0] data);
      vif.paddr <= addr;
      vif.pwdata <= data;
      vif.pwrite <= 1;
      vif.psel <= 1;
      @(posedge vif.pclk);
      vif.penable <= 1;
      @(posedge vif.pclk);
      vif.psel <= 0;
      vif.penable <= 0;
   endtask
endclass

// Monitors the APB interface for any activity and reports out
// through an analysis port
class my_monitor extends uvm_monitor;
   `uvm_component_utils (my_monitor)
   function new (string name="my_monitor", uvm_component parent);
      super.new (name, parent);
   endfunction

   uvm_analysis_port #(bus_pkt)  mon_ap;
   virtual bus_if                vif;

   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      mon_ap = new ("mon_ap", this);
      uvm_config_db #(virtual bus_if)::get (null, "uvm_test_top.*", "bus_if", vif);
   endfunction

   virtual task run_phase (uvm_phase phase);
      fork
         forever begin
            @(posedge vif.pclk);
            if (vif.psel & vif.penable & vif.presetn) begin
               bus_pkt pkt = bus_pkt::type_id::create ("pkt");
               pkt.addr = vif.paddr;
               if (vif.pwrite)
                  pkt.data = vif.pwdata;
               else
                  pkt.data = vif.prdata;
               pkt.write = vif.pwrite;
               mon_ap.write (pkt);
            end
         end
      join_none
   endtask
endclass

// The agent puts together the driver, sequencer and monitor
class my_agent extends uvm_agent;
   `uvm_component_utils (my_agent)
   function new (string name="my_agent", uvm_component parent);
      super.new (name, parent);
   endfunction

   my_driver                  m_drvr;
   my_monitor                 m_mon;
   uvm_sequencer #(bus_pkt)   m_seqr;

   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_drvr = my_driver::type_id::create ("m_drvr", this);
      m_seqr = uvm_sequencer#(bus_pkt)::type_id::create ("m_seqr", this);
      m_mon = my_monitor::type_id::create ("m_mon", this);
   endfunction

   virtual function void connect_phase (uvm_phase phase);
      super.connect_phase (phase);
      m_drvr.seq_item_port.connect (m_seqr.seq_item_export);
   endfunction
endclass

Testbench 环境

现在我们已经有了寄存器环境和APB Agent,我们可以创建一个顶层环境来放置和连接这两者。

class my_env extends uvm_env;
   `uvm_component_utils (my_env)

   my_agent       m_agent;
   reg_env        m_reg_env;

   function new (string name = "my_env", uvm_component parent);
      super.new (name, parent);
   endfunction

   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_agent = my_agent::type_id::create ("m_agent", this);
      m_reg_env = reg_env::type_id::create ("m_reg_env", this);
      uvm_reg::include_coverage ("*", UVM_CVR_ALL);
   endfunction

   // Connect analysis port of monitor with predictor, assign agent to register env
   // and set default map of the register env
   virtual function void connect_phase (uvm_phase phase);
      super.connect_phase (phase);
      m_reg_env.m_agent = m_agent;
      m_agent.m_mon.mon_ap.connect (m_reg_env.m_apb2reg_predictor.bus_in);
      m_reg_env.m_ral_model.default_map.set_sequencer(m_agent.m_seqr, m_reg_env.m_reg2apb);
   endfunction

endclass

Sequences

Sequences有助于使DUT从复位中剥离。它从uvm_config_db获得虚拟接口句柄,并直接从sequence切换DUT复位输入。寄存器访问可以写在一个类似的sequence中,此处我们简单地从测试类调用它们。

class reset_seq extends uvm_sequence;
   `uvm_object_utils (reset_seq)
   function new (string name = "reset_seq");
      super.new (name);
   endfunction

   virtual bus_if    vif;

   task body ();
      if (!uvm_config_db #(virtual bus_if) :: get (null, "uvm_test_top.*", "bus_if", vif))
         `uvm_fatal ("VIF", "No vif")

      `uvm_info ("RESET", "Running reset ...", UVM_MEDIUM);
      vif.presetn <= 0;
      @(posedge vif.pclk) vif.presetn <= 1;
      @ (posedge vif.pclk);
   endtask
endclass

Test

  创建测试库首先要开发用于配置和启动环境的基础用例,其他所有特定的测试都可以通过这个基础的测试用例派生而来。

class base_test extends uvm_test;
   `uvm_component_utils (base_test)

   my_env         m_env;
   reset_seq      m_reset_seq;
   uvm_status_e   status;

   function new (string name = "base_test", uvm_component parent);
      super.new (name, parent);
   endfunction

   // Build the testbench environment, and reset sequence
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_env = my_env::type_id::create ("m_env", this);
      m_reset_seq = reset_seq::type_id::create ("m_reset_seq", this);
   endfunction

   // In the reset phase, apply reset
   virtual task reset_phase (uvm_phase phase);
      super.reset_phase (phase);
      phase.raise_objection (this);
      m_reset_seq.start (m_env.m_agent.m_seqr);
      phase.drop_objection (this);
   endtask
endclass

class reg_rw_test extends base_test;
   `uvm_component_utils (reg_rw_test)
   function new (string name="reg_rw_test", uvm_component parent);
      super.new (name, parent);
   endfunction

   // Note that main_phase comes after reset_phase, and is performed when
   // DUT is out of reset. "reset_phase" is already defined in base_test
   // and is always called when this test is started
   virtual task main_phase(uvm_phase phase);
      ral_sys_traffic   m_ral_model;
      uvm_status_e      status;
      int               rdata;

      phase.raise_objection(this);

      m_env.m_reg_env.set_report_verbosity_level (UVM_HIGH);

      // Get register model from config_db
      uvm_config_db#(ral_sys_traffic)::get(null, "uvm_test_top", "m_ral_model", m_ral_model);

      // Write 0xcafe_feed to the timer[1] register, and read it back
      m_ral_model.cfg.timer[1].write (status, 32'hcafe_feed);
      m_ral_model.cfg.timer[1].read (status, rdata);

      // Set 0xface as the desired value for timer[1] register
      m_ral_model.cfg.timer[1].set(32'hface);
      `uvm_info(get_type_name(), $sformatf("desired=0x%0h mirrored=0x%0h", m_ral_model.cfg.timer[1].get(), m_ral_model.cfg.timer[1].get_mirrored_value()), UVM_MEDIUM)

      // Predict that current value of timer[1] is 0xcafe_feed and check it is true
      m_ral_model.cfg.timer[1].predict(32'hcafe_feed);
      m_ral_model.cfg.timer[1].mirror(status, UVM_CHECK);

      // Set desired value of the field "bl_yellow" in register ctrl to 1
      // Then start bus transactions by calling "update" to update DUT with
      // desired value
      m_ral_model.cfg.ctrl.bl_yellow.set(1);
      m_ral_model.cfg.update(status);

	  // Attempt to write into a RO register "stat" with some value
      m_ral_model.cfg.stat.write(status, 32'h12345678);
      phase.drop_objection(this);
   endtask

  // Before end of simulation, allow some time for unfinished transactions to
  // be over
  virtual task shutdown_phase(uvm_phase phase);
    super.shutdown_phase(phase);
    phase.raise_objection(this);
    #100ns;
    phase.drop_objection(this);
  endtask
endclass

仿真log

ncsim> run                                                                                                                                                                  
----------------------------------------------------------------                                                                                                            
CDNS-UVM-1.1d (15.20-s044)                                                                                                                                                  
(C) 2007-2013 Mentor Graphics Corporation                                                                                                                                   
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------
UVM_INFO @ 0: reporter [RNTST] Running test reg_rw_test...
UVM_INFO ./tb/my_env.sv(215) @ 0: uvm_test_top.m_env.m_agent.m_seqr@@m_reset_seq [RESET] Running reset ...
UVM_INFO ./tb/test_lib.sv(80) @ 110: uvm_test_top [reg_rw_test] desired=0xface mirrored=0xcafefeed

--- UVM Report catcher Summary ---

在这里插入图片描述

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UVM(Universal Verification Methodology)寄存器模型是一用于验证芯片寄存器功能的标准方法。它提供了一个统一的、可重用的框架,用于建立和管理寄存器模型,以及执行寄存器访问和验证。 UVM寄存器模型的主要组成部分包括寄存器模型寄存器层次结构、寄存器操作和寄存器验证环境。 1. 寄存器模型UVM寄存器模型是一个抽象的表示,用于描述芯片内部的寄存器寄存器字段。它提供了一种结构化的方式来定义寄存器的属性、寄存器字段的位宽和访问权限等。 2. 寄存器层次结构:UVM寄存器模型支持多层级的寄存器结构,可以通过层级关系来描述芯片内部的寄存器模块和子模块。这样可以更好地组织和管理寄存器模型,并提供寄存器之间的相互作用和访问。 3. 寄存器操作:UVM提供了一系列的API,用于执行寄存器读写操作。通过这些API,可以向寄存器模型发送读写请求,并获取响应。同时,还可以对寄存器的访问进行配置和控制,如重置、写入默认值等。 4. 寄存器验证环境:UVM寄存器模型可以与其他验证环境进行集成,以验证寄存器功能的正确性。通过使用事务级建模(TLM)接口,可以将寄存器操作与其他验证组件进行交互,并进行功能验证、覆盖率分析和错误注入等。 总之,UVM寄存器模型提供了一种规范化的方法来描述和验证芯片寄存器功能。它具有可重用性、灵活性和扩展性,并能与其他验证组件进行集成,从而提高验证效率和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值