适配器模式
适配器模式(Adapter Pattern
)是结构性设计模式,其目的是协调不兼容的结构,把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
寄存器模型中的adapter
就是一个适配器,提供了reg2bus
和bus2reg
的接口,充当uvm_reg_map
和uvm_sequencer
中的转换器。uvm_reg_map
操作uvm_reg_bus_op
类型的item,而uvm_sequencer
操作uvm_sequence_item
类型的item。
adapter
继承uvm_reg_adapter
,重写reg2bus
和bus2reg
这两个pure virtual function。
class my_adapter extends uvm_reg_adapter;
string tID = get_type_name();
`uvm_object_utils(my_adapter)
function new(string name="my_adapter");
super.new(name);
endfunction : new
function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
bus_transaction tr;
tr = new("tr");
tr.addr = rw.addr;
tr.bus_op = (rw.kind == UVM_READ) ? BUS_RD: BUS_WR;
if (tr.bus_op == BUS_WR)
tr.wr_data = rw.data;
return tr;
endfunction : reg2bus
function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
bus_transaction tr;
if(!$cast(tr, bus_item)) begin
`uvm_fatal(tID,
"Provided bus_item is not of the correct type. Expecting bus_transaction")
return;
end
rw.kind = (tr.bus_op == BUS_RD) ? UVM_READ : UVM_WRITE;
rw.addr = tr.addr;
rw.byte_en = 'h3;
rw.data = (tr.bus_op == BUS_RD) ? tr.rd_data : tr.wr_data;
rw.status = UVM_IS_OK;
endfunction : bus2reg
endclass : my_adapter
在connect_phase中,adapter将uvm_reg_map和sequencer连接,实际就是调用set_sequencer
给uvm_reg_map中的成员变量m_sequencer
m_adapter
赋值:
function void base_test::connect_phase(uvm_phase phase);
super.connect_phase(phase);
......
rm.default_map.set_sequencer(env.bus_agt.sqr, reg_sqr_adapter);
rm.default_map.set_auto_predict(1);
endfunction
// uvm_reg_map.svh
function void uvm_reg_map::set_sequencer(uvm_sequencer_base sequencer,
uvm_reg_adapter adapter=null);
if (sequencer == null) begin
`uvm_error("REG_NULL_SQR", "Null reference specified for bus sequencer");
return;
end
if (adapter == null) begin
`uvm_info("REG_NO_ADAPT", {"Adapter not specified for map '",get_full_name(),
"'. Accesses via this map will send abstract 'uvm_reg_item' items to sequencer '",
sequencer.get_full_name(),"'"},UVM_MEDIUM)
end
m_sequencer = sequencer;
m_adapter = adapter;
endfunction
对adapter的调用发生在uvm_reg_map的内部,详细调用reg2bus
和bus2reg
的过程见下节:
寄存器模型
组成
uvm_reg_field: 包含uvm_reg_data_t
类型的三个成员变量 value
, m_mirrored
, m_desire
。
uvm_reg: 包含成员变量m_fields
,加入各个uvm_reg_field
。
uvm_reg_block: uvm_reg的集合,uvm_reg需要加入到uvm_reg_map
中,且属于同一个uvm_reg_block。每个uvm_reg_block至少包含一个uvm_reg_map。uvm_reg_block中也可以再添加子uvm_reg_block,同时子uvm_reg_block的uvm_reg_map也需要添加到父uvm_reg_block的uvm_reg_map中。
uvm_reg_map: 包含成员变量m_adapter
,m_sequencer
,负责FRONTDOOR发送item给driver。
uvm_reg_item: 继承于uvm_sequence_item,可以看作是寄存器访问的transaction。
uvm_reg_bus_op: 在uvm_reg_map中由uvm_reg_item
转化而来,是一个struct类型的变量。
ACCESS方式
reg write
- 创建uvm_reg_item实例
rw
,一般不用指定其uvm_sequence_base
型的parent。但是FRONTDOOR形式发送transaction需要通过sequence来协助完成,会自动创建一个sequence。 - 可以指定
map
,通过选定的map访问寄存器。举例:CPU的 IBUS 和 SBUS访问的地址范围不同,可以分别建立相对应的uvm_reg_map
,通过map的set_sequencer
设置对应BUS的sequencer。 - 调用
uvm_reg
的do_write(rw)
。
// uvm_reg.svh
task uvm_reg::write(output uvm_status_e status,
input uvm_reg_data_t value,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uvm_reg_map map = null,
input uvm_sequence_base parent = null,
input int prior = -1,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);
// create an abstract transaction for this operation
uvm_reg_item rw;
XatomicX(1);
set(value);
rw = uvm_reg_item::type_id::create("write_item",,get_full_name());
rw.element = this;
rw.element_kind = UVM_REG;
rw.kind = UVM_WRITE;
rw.value[0] = value;
rw.path = path;
rw.map = map;
rw.parent = parent;
rw.prior = prior;
rw.extension = extension;
rw.fname = fname;
rw.lineno = lineno;
do_write(rw);
status = rw.status;
XatomicX(0);
endtask
- local_map是uvm_reg,uvm_reg_filed,uvm_mem所在uvm_reg_map的指针。
- get_root_map获得最顶层的map,如果是套嵌的uvm_reg_block,uvm_reg_map也是套嵌的。
- 可以用户自己定义FORONTDOOR,一般使用内建的方式,调用
rw.local_map.do_write(rw)
。 - 一般设置
default_map.set_auto_predict(1);
,这样当寄存器访问结束时,会调用do_predict
,自动更新uvm_reg_filed中的三个变量值。
// uvm_reg.svh
// do_write
task uvm_reg::do_write (uvm_reg_item rw);
....
// EXECUTE WRITE...
case (rw.path)
// ...VIA USER BACKDOOR
UVM_BACKDOOR: begin
....
end
UVM_FRONTDOOR: begin
uvm_reg_map system_map = rw.local_map.get_root_map();
m_is_busy = 1;
// ...VIA USER FRONTDOOR
....
// ...VIA BUILT-IN FRONTDOOR
else begin : built_in_frontdoor
rw.local_map.do_write(rw);
end
m_is_busy = 0;
if (system_map.get_auto_predict()) begin
uvm_status_e status;
if (rw.status != UVM_NOT_OK) begin
sample(value, -1, 0, rw.map);
m_parent.XsampleX(map_info.offset, 0, rw.map);
end
status = rw.status; // do_predict will override rw.status, so we save it here
do_predict(rw, UVM_PREDICT_WRITE);
rw.status = status;
end
end
endcase
- 自动创建一个sequence,作为rw的parent。
- 使用寄存器模型一般采用加入adapter的方式,所以走
do_bus_write(rw,sequencer,adapter)
的分支。
// uvm_reg_map.svh
// do_write(uvm_reg_item rw)
task uvm_reg_map::do_write(uvm_reg_item rw);
uvm_sequence_base tmp_parent_seq;
uvm_reg_map system_map = get_root_map();
uvm_reg_adapter adapter = system_map.get_adapter();
uvm_sequencer_base sequencer = system_map.get_sequencer();
.....
if (rw.parent == null) begin
rw.parent = new("default_parent_seq");
tmp_parent_seq = rw.parent;
end
if (adapter == null) begin
.....
end
else begin
do_bus_write(rw, sequencer, adapter);
end
if (tmp_parent_seq != null)
sequencer.m_sequence_exiting(tmp_parent_seq);
endtask
// get_root_map
function uvm_reg_map uvm_reg_map::get_root_map();
return (m_parent_map == null) ? this : m_parent_map.get_root_map();
endfunction: get_root_map
// get_sequencer
function uvm_sequencer_base uvm_reg_map::get_sequencer(uvm_hier_e hier=UVM_HIER);
if (hier == UVM_NO_HIER || m_parent_map == null)
return m_sequencer;
return m_parent_map.get_sequencer(hier);
endfunction
// get_adapter
function uvm_reg_adapter uvm_reg_map::get_adapter(uvm_hier_e hier=UVM_HIER);
if (hier == UVM_NO_HIER || m_parent_map == null)
return m_adapter;
return m_parent_map.get_adapter(hier);
endfunction
- uvm_reg_map中的do_bus_write将
rw
转化成uvm_reg_bus_op
类型的rw_access
。然后在调用adapter.reg2bus(rw_access)
转化得到bus sequencer可以接受的transaction类型。 rw.parent.finish_item(bus_req)
rw.parent.finish_item(bus_req)
相当于平常调用sequence中的body()
函数,完成与driver的握手。- 可以手动设置response,也可以使用driver自动返回的response,根据
status
判断写状态。
// uvm_reg_map.svh
// do_bus_write
task uvm_reg_map::do_bus_write (uvm_reg_item rw,
uvm_sequencer_base sequencer,
uvm_reg_adapter adapter);
uvm_reg_addr_t addrs[$];
uvm_reg_map system_map = get_root_map();
int unsigned bus_width = get_n_bytes();
uvm_reg_byte_en_t byte_en = -1;
uvm_reg_map_info map_info;
int n_bits;
int lsb;
int skip;
int unsigned curr_byte;
int n_access_extra, n_access;
int n_bits_init;
Xget_bus_infoX(rw, map_info, n_bits_init, lsb, skip);
addrs=map_info.addr;
.........
foreach(addrs[i]) begin: foreach_addr
uvm_sequence_item bus_req;
uvm_reg_bus_op rw_access;
uvm_reg_data_t data;
data = (value >> (curr_byte*8)) & ((1'b1 << (bus_width * 8))-1);
`uvm_info(get_type_name(),
$sformatf("Writing 'h%0h at 'h%0h via map \"%s\"...",
data, addrs[i], rw.map.get_full_name()), UVM_FULL);
if (rw.element_kind == UVM_FIELD) begin
for (int z=0;z<bus_width;z++)
rw_access.byte_en[z] = byte_en[curr_byte+z];
end
rw_access.kind = rw.kind;
rw_access.addr = addrs[i];
rw_access.data = data;
rw_access.n_bits = (n_bits > bus_width*8) ? bus_width*8 : n_bits;
rw_access.byte_en = byte_en;
adapter.m_set_item(rw);
bus_req = adapter.reg2bus(rw_access);
adapter.m_set_item(null);
if (bus_req == null)
`uvm_fatal("RegMem",{"adapter [",adapter.get_name(),"] didnt return a bus transaction"});
bus_req.set_sequencer(sequencer);
rw.parent.start_item(bus_req,rw.prior);
if (rw.parent != null && i == 0)
rw.parent.mid_do(rw);
rw.parent.finish_item(bus_req);
bus_req.end_event.wait_on();
if (adapter.provides_responses) begin
uvm_sequence_item bus_rsp;
uvm_access_e op;
// TODO: need to test for right trans type, if not put back in q
rw.parent.get_base_response(bus_rsp);
adapter.bus2reg(bus_rsp,rw_access);
end
else begin
adapter.bus2reg(bus_req,rw_access);
end
if (rw.parent != null && i == addrs.size()-1)
rw.parent.post_do(rw);
rw.status = rw_access.status;
`uvm_info(get_type_name(),
$sformatf("Wrote 'h%0h at 'h%0h via map \"%s\": %s...",
data, addrs[i], rw.map.get_full_name(), rw.status.name()), UVM_FULL)
if (rw.status == UVM_NOT_OK)
break;
curr_byte += bus_width;
n_bits -= bus_width * 8;
end: foreach_addr
foreach (addrs[i])
addrs[i] = addrs[i] + map_info.mem_range.stride;
end: foreach_value
endtask: do_bus_write
reg read
read操作和write类似,寄存器模型对于一次读操作,会创建一个sequence,发起读命令,但是返回的读数据,是通过总线上的直接采样,利用driver中的item_done
将读数据返回,使得finish_item(bus_req)
中的bus_req包含读取的数据。(这一步骤对应下图中的红色虚线)。
API
寄存器模型中提供了丰富的API,详见:UVM — 寄存器模型相关的一些函数
封装API
寄存器模型内建的API,调用需要通过句柄的形式,对于包含多个uvm_reg_blcok或者field级别的访问,需要列出详细句柄结构,使用起来不够友好。代码如下:
p_sequencer.p_rm.gb_ins_invert.read(status,value,UVM_FRONTDOOR);
p_sequencer.p_rm.bb_ins.depth.write(status,value,UVM_FRONTDOOR);
封装原有API, 编写一个根据传入reg/field的string name来进行访问的函数,调用如下:
p_sequencer.p_rm.get_reg_value(value,"invert");
p_sequencer.p_rm.set_reg_value(value,"depth");
p_sequencer.p_rm.set_field_value(value,"fieldA");
实现源代码如下:
TODO
//..........................................................................
// helper wrapper functions and tasks for easy acces to registers and fileds
//..........................................................................
//..........................................................................
// resolve reg_block and register from field_name (which is the only
// compulsory parameter)
// @return 0 - OK
// 1 - ERROR
//..........................................................................
function bit get_field_identity(ref string block_name,
ref uvm_reg_block reg_block,
ref string register_name,
ref uvm_reg register,
input string field_name,
ref uvm_reg_field field);
if (block_name == "") begin
if (register_name == "") begin
// neither block_name or regsiter_name are known
field = this.get_field_by_name(field_name);
if (field == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register field %s", field_name))
return 1;
end else begin
register = field.get_parent();
if (register == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register for field %s", field_name))
return 1;
end
register_name = register.get_name();
reg_block = register.get_parent();
if (reg_block == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register block for %s.%s",register_name, field_name))
return 1;
end
block_name = reg_block.get_name();
end
end else begin
//register_name is known, block_name is unknown
register = this.get_reg_by_name(register_name);
if (register == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register%s", register_name))
return 1;
end else begin
reg_block = register.get_parent();
if (reg_block == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register block for register %s",register_name))
return 1;
end
block_name = reg_block.get_name();
field =register.get_field_by_name(field_name);
if (field == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register field %s.%s.%s",block_name, register_name, field_name))
return 1;
end
end
end
end
if (register_name == "") begin
reg_block = this.get_block_by_name(block_name);
if (reg_block == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register block %s",block_name))
return 1;
end else begin
field =reg_block.get_field_by_name(field_name);
if (field == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register field %s in block %s", field_name, block_name))
return 1;
end else begin
register = field.get_parent();
if (register == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register%s", register_name))
return 1;
end
register_name = register.get_name();
end
end
end
// this.get_field_by_name() can return the field at once, but in case there are two or more fields with
// the same name in different registers, we use block_name and register_name to pinpoint the desired register
reg_block = this.get_block_by_name(block_name);
if (reg_block == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register block %s",block_name))
return 1;
end else begin
register = reg_block.get_reg_by_name(register_name);
if (register == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register%s.%s", block_name, register_name))
return 1;
end else begin
field = register.get_field_by_name(field_name);
if (field == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register field %s.%s.%s",block_name, register_name, field_name))
return 1;
end
end
end
return 0; //field found
endfunction: get_field_identity
//......................................................................
// resolve reg_block and register from register_name (which is the
// only compulsory parameter)
// @return 0 - OK
// 1 - ERROR
//.....................................................................
function bit get_reg_identity(ref string block_name,
ref uvm_reg_block reg_block,
input string register_name,
ref uvm_reg register);
if (block_name == "") begin
register = this.get_reg_by_name(register_name);
if (register == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register%s", register_name))
return 1;
end
reg_block = register.get_parent();
if (reg_block == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register block for register %s",register_name))
return 1;
end
block_name = reg_block.get_name();
end else begin
reg_block = this.get_block_by_name(block_name);
if (reg_block == null) begin
`uvm_error(get_type_name(), $sformatf("Unable to locate register block %s",block_name))
return 1;
end else begin
register = reg_block.get_reg_by_name(register_name);
if (register == null) begin
`uvm_error(get_type_name(), $sformatf("Unabletolocate register%s.%s", block_name, register_name))
return 1;
end
end
end
return 0; //register found
endfunction: get_reg_identity
//...............................................................
// get value of specific register field. register_name and
// register_block are optional
//...............................................................
task get_field_value(ref uvm_reg_data_t data,
input string field_name,
input string register_name="",
input string block_name="" );
uvm_reg_block reg_block;
uvm_reg register;
uvm_reg_field field;
bit [7:0] lsb_pos;
bit [7:0] fld_width;
bit [31:0] mask;
if (get_field_identity(block_name, reg_block, register_name, register, field_name, field))
return; //field not found
lsb_pos = field.get_lsb_pos();
fld_width = field.get_n_bits();
mask = ((2**fld_width)-1) << lsb_pos;
register.read(status, data);
data = (data & mask) >> lsb_pos;
`uvm_info(get_type_name(),
$sformatf("Value of register field %s.%s.%s is 0x%4x (lsb_pos: %2d fld_width: %2d mask: 0x%4x)",
block_name, register_name, field_name, data, lsb_pos, fld_width, mask), UVM_DEBUG)
endtask: get_field_value
//............................................................
// get value of specific register field. If block_name or register_name
// is not specified, the code will try to guess them
//............................................................
task set_field_value(uvm_reg_data_t dataw,
string field_name,
string register_name="",
string block_name="" );
uvm_reg_block reg_block;
uvm_reg register;
uvm_reg_field field;
bit [7:0] lsb_pos;
bit [7:0] fld_width;
uvm_reg_data_t mask, data, old_data;
if (get_field_identity(block_name, reg_block, register_name, register, field_name, field))
return;//field not found
lsb_pos =field.get_lsb_pos();
fld_width = field.get_n_bits();
mask = ((2**fld_width)-1) << lsb_pos;
register.read(status, data);
old_data = data;
data = (data & ~mask) | ((dataw << lsb_pos) & mask);
register.write(status, data);
`uvm_info(get_type_name(),
$sformatf("Writing 0x%0x to register field %s.%s.%s (new data: 0x%8x old data: 0x%8x lsb_pos: %2d fld_width: %2d mask: 0x%8x)",
dataw, block_name, register_name, field_name, data, old_data, lsb_pos, fld_width, mask), UVM_DEBUG)
endtask: set_field_value
//......................................................................
// set register value
//......................................................................
task set_reg_value(uvm_reg_data_t dataw, string register_name, string block_name="");
uvm_reg_block reg_block;
uvm_reg register;
if (get_reg_identity(block_name, reg_block, register_name, register))
return; //register not found
register.write(status, dataw);
`uvm_info(get_type_name(),
$sformatf("Writing 0x%0x to register%s.%s",dataw,block_name,register_name), UVM_DEBUG)
endtask: set_reg_value
//......................................................................
// get register value
//......................................................................
task get_reg_value(ref uvm_reg_data_t data, input string register_name, string block_name="");
uvm_reg_block reg_block;
uvm_reg register;
if (get_reg_identity(block_name,reg_block,register_name,register))
return; //register not found
register.read(status, data);
`uvm_info(get_type_name(),
$sformatf("Read 0x%0x from register%s.%s",data, block_name, register_name), UVM_DEBUG)
endtask: get_reg_value
//......................................................................
// get mirrored register value
//......................................................................
function bit [31:0] get_mirrored_reg_value(string register_name, string block_name="");
uvm_reg_block reg_block;
uvm_reg register;
bit [31:0] ret;
if (get_reg_identity(block_name, reg_block, register_name, register))
return 0; //register not found
ret = register.get_mirrored_value();
`uvm_info(get_type_name(),
$sformatf("Read mirrored value 0x%0x from register %s.%s",ret, block_name, register_name), UVM_DEBUG)
return ret;
endfunction : get_mirrored_reg_value
//........................................................................
// predict a field value in register
//........................................................................
function void predict_field_value(uvm_reg_data_t value,
string field_name,
uvm_predict_e kind = UVM_PREDICT_READ,
string register_name="",
string block_name="");
uvm_reg_block reg_block;
uvm_reg_field field;
uvm_reg register;
if (get_field_identity(block_name, reg_block, register_name, register, field_name, field))
return;
assert(field.predict(.value(value), .kind(kind), .path(UVM_BACKDOOR)));
`uvm_info(get_type_name(),
$sformatf("Predicted value 0x%0x for field %s, in %s register", value, field_name, register_name), UVM_DEBUG)
endfunction : predict_field_value
predictor
对于只有一个总线访问寄存器且通过寄存器模型的方式,则使用reg_model.default_map.set_auto_predict(1)
可以发起read/write操作后自动更新uvm_reg_field成员变量m_mirrored
的值,完成预测。
如果不是通过寄存器模型访问总线则不能检测到,这时可以通过monitor检测总线,将采集到的tr
发送给uvm_reg_perdictor
。uvm_reg_predictor中调用adapter.bus2reg(tr,rw)
获得uvm_reg_bus_op
类型的rw
,rg = map.get_reg_by_offset(rw.addr, (rw.kind == UVM_READ));
获得reg,最终调用uvm_reg_field的do_predict
函数更新三个成员变量 value
, m_mirrored
, m_desire
。
env中集成perdictor需要在connect_pahes中添加两步:
- perdictor中的
map
,adapter
赋值 - moniotr与perdicotr TLM端口连接
另一种 后门访问
寄存器模型内置了不消耗时间的BACKDOOR后门访问。
寄存器模型后门访问时会调用UVM提供的两个API uvm_hdl_read
uvm_hdl_deposit
。这两个API是以DPI的方式调用VPI,实现通过传入字符串格式的hierarchy路径,来访问simulation database,完成对变量的读取/赋值。
好处:代码放在package中也可以编译通过,因为路径是字符串,不影响编译。
坏处:路径错误时编译阶段无法发现,只有仿真调用时才会发现错误。如果大量调用后门访问,DPI可能会影响仿真器的运行速度。
vcs 寄存器模型生成工具ralgen
,提供了一种以interface的形式来进行后门访问。
命令:ralgen -uvm -b -gen_vif_bkdir -t topname <RALF-FILE>
代码生成多了interface和后门调用的class内容:
//ral_host_reg_model_interface.sv:
interface ral_host_regmodel_intf;
import uvm_pkg::*;
initial uvm_resource_db#(virtual ral_host_regmodel_intf)::set("*", "uvm_reg_bkdr_if",
interface::self());
task ral_host_regmodel_HOST_ID_bkdr_read(uvm_reg_item rw, int index = 0);r
rw.value[0] = `HOST_REGMODEL_TOP_PATH.host_id;
endtask
task ral_host_regmodel_HOST_ID_bkdr_write(uvm_reg_item rw, int index = 0);
`HOST_REGMODEL_TOP_PATH.host_id[31:0] = rw.value[0][31:0];
endtask
endinterface
//ral_host_reg_model.sv:
class ral_reg_host_regmodel_HOST_ID_bkdr extends uvm_reg_backdoor;
virtual ral_host_regmodel_intf __reg_vif;
function new(string name);
super.new(name);
uvm_resource_db#(virtualral_host_regmodel_intf)::read_by_name(get_full_name(), "uvm_reg_bkdr_if", __reg_vif);
endfunction
virtual task read(uvm_reg_item rw);
do_pre_read(rw);
__reg_vif.ral_host_regmodel_HOST_ID_bkdr_read(rw);
rw.status = UVM_IS_OK;
do_post_read(rw);
endtask
virtual task write(uvm_reg_item rw);
do_pre_write(rw);
__reg_vif.ral_host_regmodel_HOST_ID_bkdr_write(rw);
rw.status = UVM_IS_OK;
do_post_write(rw);
endtask
endclass
这种方式避免了原有后门访问的缺点。
这种方法也可以应用到我们日常开发UVC中,因为package封装的UVC,不可以直接force
路径信号,但是可以在顶层做interface连接,然后config_db
传入interface供UVC内部处理。
寄存器模型生成脚本
TODO