harriszh

harriszh 查看完整档案

上海编辑南京大学  |  信号与信息处理 编辑cadence  |  Sr. Principal Solution Engineer 编辑 github.com/zhuzhzh 编辑
编辑

技术是愉悦自己的工具,是发现自我价值的手段

个人动态

harriszh 发布了文章 · 2020-11-28

构建riscv上运行的linux系统

  在qemu上启动linux kernel

 总述

最终目标还是要在RTL上跑linux系统,但做这个之前第一步先把系统工具链整清楚很重要,所以先在qemu上把相关的工具链,镜像搞定。

为了完全这项任务,我们需要安装几个工具, qemu for riscv,   linux kernel,  boot loader, toolbox

levelendcodingNameAbbreviation
000User/ApplicationU
101SupervisorS
210Reserved
311MachineM

如上图所示,  riscv包含3个特权等级,OS运行在Superviosr级上, bootloader和其他固件运行在Machine级上, 用户程序运行在User级上。

 工作目录

首先我们建立一个工作目录


mkdir riscv-linux

cd riscv-linux

 安装qemu for riscv

我们统一选项只安装64版本, 目录到和riscv toolschian相同目录里


git clone https://github.com/qemu/qemu

cd qemu

git checkout v5.0.0

./configure --target-list=riscv64-softmmu --prefix=${RISCV}

make

make install

编译生成linux kernel

回到工作目录riscv-linux


git clone https://github.com/torvalds/linux

cd linux

git checkout v5.4

make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- defconfig

make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu-

生成的kernel image在arch/riscv/boot/Image

SBI

SBI 是 RISC-V Supervisor Binary Interface的简称, 它是一个中间接口:

  • firmware和bootloader, OS之间的接口
  • 一个Hypervisor和bootloader或运行在VS-mode下的OS之间的接口

cd opensbi

make PLATFORM=generic CROSS_COMPILE=riscv64-unknown-linux-gnu- PLATFORM_RISCV_XLEN=64 FW_PAYLOAD_PATH=../linux/arch/riscv/boot/Image

生成的bin文件在./build/platform/generic/firmware/fw_jump.bin

 生成busybox并制作文件系统


git clone https://git.busybox.net/busybox

cd busybox

CROSS_COMPILE=riscv{{bits}}-unknown-linux-gnu- make menuconfig

# 打开配置菜单后进入第一行的 "Settings",在"Build Options"节中,选中 “Build static binary (no shared libs)”,设置好后退出保存配置

CROSS_COMPILE=riscv{{bits}}-unknown-linux-gnu- make

make install

# 源码目录 busyboxsource 下新出现一个 _install 目录 ,可以看到生成的东西

下面我们就要制作最小文件系统


qemu-img create rootfs.img  1g

mkfs.ext4 rootfs.img

mkdir rootfs

sudo mount -o loop rootfs.img  rootfs

cd rootfs

sudo cp -r ../busyboxsource/_install/* .

sudo mkdir proc sys dev etc etc/init.d

cd etc/init.d/

sudo touch rcS

sudo vi rcS

编译内容如下:


#!/bin/sh

mount -t proc none /proc

mount -t sysfs none /sys

/sbin/mdev -s

然后修改文件权限


sudo mod +x rcS

sudo umount rootfs

至此文件系统就制作完成了

 启动

回到riscv-linux目录, 执行下面命令立即就打印相关启动信息


qemu-system-riscv64 -M virt -m 256M -nographic -bios opensbi/build/platform/generic/firmware/fw_jump.bin -kernel ./linux/arch/riscv/boot/Image -drive file=./rootfs.img,format=raw,id=hd0  -device virtio-blk-device,drive=hd0 -append "root=/dev/vda rw console=ttyS0"

下面是打印的信息, 暂停时输入回车就进行了熟悉的shell命令行下。


# harriszh @ pc-harriszh2 in /trunk/branch/riscv [20:44:07] 

$ qemu-system-riscv64 -M virt -m 256M -nographic -bios opensbi/build/platform/generic/firmware/fw_jump.bin -kernel ./linux/arch/riscv/boot/Image -drive file=./rootfs.img,format=raw,id=hd0  -device virtio-blk-device,drive=hd0 -append "root=/dev/vda rw console=ttyS0"

OpenSBI v0.8-58-g781cafd

   ____                    _____ ____ _____

  / __                   / ____|  _ _   _|

 | |  | |_ __   ___ _ __ | (___ | |_) || |

 | |  | | '_  / _  '_  ___ |  _ < | |

 | |__| | |_) |  __/ | | |____) | |_) || |_

  ____/| .__/ ___|_| |_|_____/|____/_____|

        | |

        |_|

Platform Name             : riscv-virtio,qemu

Platform Features         : timer,mfdeleg

Platform HART Count       : 1

Firmware Base             : 0x80000000

Firmware Size             : 108 KB

Runtime SBI Version       : 0.2

Domain0 Name              : root

Domain0 Boot HART         : 0

Domain0 HARTs             : 0*

Domain0 Region00          : 0x0000000080000000-0x000000008001ffff ()

Domain0 Region01          : 0x0000000000000000-0xffffffffffffffff (R,W,X)

Domain0 Next Address      : 0x0000000080200000

Domain0 Next Arg1         : 0x0000000082200000

Domain0 Next Mode         : S-mode

Domain0 SysReset          : yes

Boot HART ID              : 0

Boot HART Domain          : root

Boot HART ISA             : rv64imafdcsu

Boot HART Features        : scounteren,mcounteren,time

Boot HART PMP Count       : 16

Boot HART PMP Granularity : 4

Boot HART PMP Address Bits: 54

Boot HART MHPM Count      : 0

Boot HART MHPM Count      : 0

Boot HART MIDELEG         : 0x0000000000000222

Boot HART MEDELEG         : 0x000000000000b109

[    0.000000] OF: fdt: Ignoring memory range 0x80000000 - 0x80200000

[    0.000000] Linux version 5.4.0 (harriszh@pc-harriszh2) (gcc version 10.2.0 (GCC)) #1 SMP Sat Nov 28 13:48:10 CST 2020

[    0.000000] initrd not found or empty - disabling initrd

[    0.000000] Zone ranges:

[    0.000000]   DMA32    [mem 0x0000000080200000-0x000000008fffffff]

[    0.000000]   Normal   empty

[    0.000000] Movable zone start for each node

[    0.000000] Early memory node ranges

[    0.000000]   node   0: [mem 0x0000000080200000-0x000000008fffffff]

[    0.000000] Initmem setup node 0 [mem 0x0000000080200000-0x000000008fffffff]

[    0.000000] software IO TLB: mapped [mem 0x8bc7a000-0x8fc7a000] (64MB)

[    0.000000] elf_hwcap is 0x112d

[    0.000000] percpu: Embedded 17 pages/cpu s30680 r8192 d30760 u69632

[    0.000000] Built 1 zonelists, mobility grouping on.  Total pages: 64135

[    0.000000] Kernel command line: root=/dev/vda rw console=ttyS0

[    0.000000] Dentry cache hash table entries: 32768 (order: 6, 262144 bytes, linear)

[    0.000000] Inode-cache hash table entries: 16384 (order: 5, 131072 bytes, linear)

[    0.000000] Sorting __ex_table...

[    0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off

[    0.000000] Memory: 181052K/260096K available (6167K kernel code, 387K rwdata, 1962K rodata, 213K init, 305K bss, 79044K reserved, 0K cma-reserved)

[    0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1

[    0.000000] rcu: Hierarchical RCU implementation.

[    0.000000] rcu:     RCU restricting CPUs from NR_CPUS=8 to nr_cpu_ids=1.

[    0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies.

[    0.000000] rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=1

[    0.000000] NR_IRQS: 0, nr_irqs: 0, preallocated irqs: 0

[    0.000000] plic: mapped 53 interrupts with 1 handlers for 2 contexts.

[    0.000000] riscv_timer_init_dt: Registering clocksource cpuid [0] hartid [0]

[    0.000000] clocksource: riscv_clocksource: mask: 0xffffffffffffffff max_cycles: 0x24e6a1710, max_idle_ns: 440795202120 ns

[    0.000084] sched_clock: 64 bits at 10MHz, resolution 100ns, wraps every 4398046511100ns

[    0.003039] Console: colour dummy device 80x25

[    0.004051] Calibrating delay loop (skipped), value calculated using timer frequency.. 20.00 BogoMIPS (lpj=40000)

[    0.004169] pid_max: default: 32768 minimum: 301

[    0.005060] Mount-cache hash table entries: 512 (order: 0, 4096 bytes, linear)

[    0.005089] Mountpoint-cache hash table entries: 512 (order: 0, 4096 bytes, linear)

[    0.027241] rcu: Hierarchical SRCU implementation.

[    0.028802] smp: Bringing up secondary CPUs ...

[    0.028883] smp: Brought up 1 node, 1 CPU

[    0.036075] devtmpfs: initialized

[    0.040712] random: get_random_u32 called from bucket_table_alloc.isra.0+0x4e/0x154 with crng_init=0

[    0.042384] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns

[    0.042546] futex hash table entries: 256 (order: 2, 16384 bytes, linear)

[    0.045024] NET: Registered protocol family 16

[    0.078034] vgaarb: loaded

[    0.078849] SCSI subsystem initialized

[    0.080125] usbcore: registered new interface driver usbfs

[    0.080352] usbcore: registered new interface driver hub

[    0.080466] usbcore: registered new device driver usb

[    0.086693] clocksource: Switched to clocksource riscv_clocksource

[    0.098120] NET: Registered protocol family 2

[    0.101335] tcp_listen_portaddr_hash hash table entries: 256 (order: 0, 4096 bytes, linear)

[    0.101409] TCP established hash table entries: 2048 (order: 2, 16384 bytes, linear)

[    0.101553] TCP bind hash table entries: 2048 (order: 3, 32768 bytes, linear)

[    0.101679] TCP: Hash tables configured (established 2048 bind 2048)

[    0.102935] UDP hash table entries: 256 (order: 1, 8192 bytes, linear)

[    0.103101] UDP-Lite hash table entries: 256 (order: 1, 8192 bytes, linear)

[    0.104159] NET: Registered protocol family 1

[    0.106058] RPC: Registered named UNIX socket transport module.

[    0.106109] RPC: Registered udp transport module.

[    0.106120] RPC: Registered tcp transport module.

[    0.106130] RPC: Registered tcp NFSv4.1 backchannel transport module.

[    0.106204] PCI: CLS 0 bytes, default 64

[    0.111668] workingset: timestamp_bits=62 max_order=16 bucket_order=0

[    0.120454] NFS: Registering the id_resolver key type

[    0.121100] Key type id_resolver registered

[    0.121132] Key type id_legacy registered

[    0.121218] nfs4filelayout_init: NFSv4 File Layout Driver Registering...

[    0.121748] 9p: Installing v9fs 9p2000 file system support

[    0.122715] NET: Registered protocol family 38

[    0.122915] Block layer SCSI generic (bsg) driver version 0.4 loaded (major 253)

[    0.123007] io scheduler mq-deadline registered

[    0.123065] io scheduler kyber registered

[    0.124868] pci-host-generic 30000000.pci: host bridge /soc/pci@30000000 ranges:

[    0.126286] pci-host-generic 30000000.pci:    IO 0x03000000..0x0300ffff -> 0x00000000

[    0.126630] pci-host-generic 30000000.pci:   MEM 0x40000000..0x7fffffff -> 0x40000000

[    0.128296] pci-host-generic 30000000.pci: ECAM at [mem 0x30000000-0x3fffffff] for [bus 00-ff]

[    0.128985] pci-host-generic 30000000.pci: PCI host bridge to bus 0000:00

[    0.129117] pci_bus 0000:00: root bus resource [bus 00-ff]

[    0.129205] pci_bus 0000:00: root bus resource [io  0x0000-0xffff]

[    0.129218] pci_bus 0000:00: root bus resource [mem 0x40000000-0x7fffffff]

[    0.129911] pci 0000:00:00.0: [1b36:0008] type 00 class 0x060000

[    0.176526] Serial: 8250/16550 driver, 4 ports, IRQ sharing disabled

[    0.181177] printk: console [ttyS0] disabled

[    0.181916] 10000000.uart: ttyS0 at MMIO 0x10000000 (irq = 10, base_baud = 230400) is a 16550A

[    0.194577] printk: console [ttyS0] enabled

[    0.196238] [drm] radeon kernel modesetting enabled.

[    0.208747] loop: module loaded

[    0.218802] virtio_blk virtio0: [vda] 2097152 512-byte logical blocks (1.07 GB/1.00 GiB)

[    0.237502] libphy: Fixed MDIO Bus: probed

[    0.238225] e1000e: Intel(R) PRO/1000 Network Driver - 3.2.6-k

[    0.238402] e1000e: Copyright(c) 1999 - 2015 Intel Corporation.

[    0.238741] ehci_hcd: USB 2.0 'Enhanced' Host Controller (EHCI) Driver

[    0.238903] ehci-pci: EHCI PCI platform driver

[    0.239112] ehci-platform: EHCI generic platform driver

[    0.239558] ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver

[    0.239747] ohci-pci: OHCI PCI platform driver

[    0.239967] ohci-platform: OHCI generic platform driver

[    0.240461] usbcore: registered new interface driver uas

[    0.240688] usbcore: registered new interface driver usb-storage

[    0.241550] mousedev: PS/2 mouse device common for all mice

[    0.242575] usbcore: registered new interface driver usbhid

[    0.242704] usbhid: USB HID core driver

[    0.244104] NET: Registered protocol family 10

[    0.248951] Segment Routing with IPv6

[    0.250166] sit: IPv6, IPv4 and MPLS over IPv4 tunneling driver

[    0.253011] NET: Registered protocol family 17

[    0.254283] 9pnet: Installing 9P2000 support

[    0.254605] Key type dns_resolver registered

[    0.289007] EXT4-fs (vda): mounted filesystem with ordered data mode. Opts: (null)

[    0.289554] VFS: Mounted root (ext4 filesystem) on device 254:0.

[    0.292116] devtmpfs: mounted

[    0.309109] Freeing unused kernel memory: 212K

[    0.309271] This architecture does not have kernel memory protection.

[    0.309452] Run /sbin/init as init process

Please press Enter to activate this console. 

/ # ls

bin         etc         lost+found  sbin        usr

dev         linuxrc     proc        sys

如果要退出系统,输入poweroff


/ # poweroff

/ # umount: devtmpfs busy - remounted read-only

[  112.461233] EXT4-fs (vda): re-mounted. Opts: (null)

swapoff: can't open '/etc/fstab': No such file or directory

The system is going down NOW!

Sent SIGTERM to all processes

Sent SIGKILL to all processes

Requesting system poweroff

[  114.487435] reboot: Power down
查看原文

赞 0 收藏 0 评论 1

harriszh 关注了用户 · 2020-10-31

李姗姗 @li_5f96478f7d930

关注 0

harriszh 发布了文章 · 2020-08-14

浮点乘法的硬件实现

用verilog实现浮点的乘法

总结

本实现采用的是面积换时间, 一拍出结果。如果为了后端实现,可以多拍出结果。在这个算法的基础上其实可以做些修改达到。核心逻辑本算法已经完成。

代码

在代码中已经添加简单的注释,相信阅读过IEEE754标准的人很容易看懂。对于结果的取舍,按照S家的标准共六种取舍,同时会输出status表明结果的精准度。

//
// Created by         :  Harris Zhu (zhuzhzh@163.com)
// Filename           :  HZ_fp_mult.v
// Author             :  Harris Zhu
// Created On         :  2020-08-14 11:36:49
// Last Modified      :  2020-08-14 11:36:49
// Update Count       :  1
// Tags               :   
// Description        :  z = a * b; 
// Conclusion         :  
// License            :  GPL
//                      
//=======================================================================


module HZ_fp_mult (a, b, rnd, z, status);

  parameter sig_width = 23;      
  parameter exp_width = 8;       
  parameter ieee_compliance = 0; 

  input  [exp_width + sig_width:0] a;
  input  [exp_width + sig_width:0] b;
  input  [2:0] rnd;
  output [exp_width + sig_width:0] z;
  output [7:0] status;



localparam [exp_width-1:0] ebias = ((1<<(exp_width-1)) - 1);  
localparam totalbit= (1 + sig_width + exp_width);
localparam einf = ((1<<(exp_width)) - 1);
localparam emax = (einf - 1);
localparam emin = 1;

localparam sexp_width = (exp_width + 1);

localparam frac_width = (sig_width + 1);
localparam spwidth = (2 * sig_width + 2);

localparam log_swidth = (sig_width+1>256)?9:
                        (sig_width+1>128)?8:
                        (sig_width+1>64)?7:
                        (sig_width+1>32)?6:
                        (sig_width+1>16)?5:
                        (sig_width+1>8)?4:
                        (sig_width+1>4)?3:
                        (sig_width+1>2)?2:1;       

localparam log_spwidth = (spwidth+1>256)?9:
                        (spwidth+1>128)?8:
                        (spwidth+1>64)?7:
                        (spwidth+1>32)?6:
                        (spwidth+1>16)?5:
                        (spwidth+1>8)?4:
                        (spwidth+1>4)?3:
                        (spwidth+1>2)?2:1;       

function [9:0] oneindex(input [2*sig_width+1:0] data);
    integer i;
    reg found;
begin
    oneindex = 0;
    found = 0;
    for(i=0;i<=2*sig_width+1;i=i+1)
    begin
        if((found == 0) && (data[2*sig_width+1-i] == 1))
        begin
            oneindex = i+1;
            found = 1;
        end
    end
end
endfunction

//status
reg [6:0] status_reg = 0;

// get sign, exp and sig
reg sign ;
reg [exp_width-1:0] aexp ;   // exponent of A
reg [exp_width-1:0] bexp ;  // exponent of B
reg [sexp_width-1:0] sign_aexp ;   // add 1 sign bit
reg [sexp_width-1:0] sign_bexp ;  // add 1 sign bit

reg [sig_width-1:0] asig;
reg [sig_width-1:0] bsig;
reg [frac_width-1:0] fracA;          // fraction of A, add 1 hidden bit
reg [frac_width-1:0] fracB;            // fraction of B, add 1 hidden bit

// exp calc
reg signed [exp_width+1:0] sign_ext_zexp;
reg signed [exp_width+1:0] z_exp_sh_full;
reg [exp_width-1:0] z_exp_sh;


// sig calc
wire [2*sig_width+1:0] fracAxB;        //the production of fracA and fracB
HZ_int_mult #(.A_width(sig_width+1), .B_width(sig_width+1)) u0 (fracA, fracB, fracAxB);

reg [2*sig_width+1:0] normFracAxB;        //normalized fracAxB
reg [log_spwidth:0]  lshift;                        // the index of highest 1 in fracAxB 
//oneindex #(.dwidth(2*sig_width+2), .iwidth(10)) u1 (fracAxB, lshift);
reg [sig_width-1:0] z_sig;                //the significand of Z

// status
reg isOf, isUf, isUUf;            //>maxnorm,  <minnorm, denormalize 
reg found =0;
integer i;

//NaN, inft
reg isANan, isAInft;
reg isBNan, isBInft;
reg isAZero, isBZero;
reg isADenorm, isBDenorm;


reg isAExpInf, isBExpInf;
reg isAExpZero, isBExpZero;
reg isASigZero, isBSigZero;

//Nan, Inft
reg [exp_width-1:0] NanInft_Exp = {(exp_width){1'b1}};
reg [sig_width-1:0] Nan_Sig =  {{(sig_width-1){1'b0}},1'b1};
reg [sig_width-1:0] Inft_Sig = {(sig_width){1'b0}};


always @(a or b or rnd or fracAxB)
begin
    status_reg = 7'b0;
    sign = a[totalbit-1] ^ b[totalbit-1];

    aexp = a[totalbit-2:sig_width];
    bexp = b[totalbit-2:sig_width];
    
    sign_aexp = {1'b0, aexp};
    sign_bexp = {1'b0, bexp};

    isAExpInf = (aexp == einf);
    isBExpInf = (bexp == einf);
    isAExpZero = (aexp == 0);
    isBExpZero = (bexp == 0);

    asig = a[sig_width-1:0];
    bsig = b[sig_width-1:0];
    
    isASigZero = (isAExpZero & isASigZero);
    isBSigZero = (isBExpZero & isBSigZero);

    isADenorm = (isAExpZero && (isASigZero == 0));
    isBDenorm = (isBExpZero && (isBSigZero == 0));

    isAZero = (a[totalbit-2:0] == 0);
    isBZero = (b[totalbit-2:0] == 0);

    isANan = (isAExpInf && (isASigZero == 0));
    isBNan = (isBExpInf && (isBSigZero == 0));

    isAInft = (isAExpInf && (isASigZero == 1));
    isBInft = (isBExpInf && (isBSigZero == 1));


    // parse out the fraction of A and B
    if(isADenorm)
        fracA = {1'b0,a[sig_width-1:0]};
    else
        fracA = {1'b1,a[sig_width-1:0]};
    if(isBDenorm)
        fracB = {1'b0,b[sig_width-1:0]};
    else
        fracB = {1'b1,b[sig_width-1:0]};


    if(isANan || isBNan)
    begin
        if(ieee_compliance)
        begin
            status_reg[2] = 1'b1;    // Invalid
            sign = 0;
            z_sig = {{(sig_width-1){1'b0}}, 1'b1};
            z_exp_sh = {exp_width{1'b1}};
        end else begin
            status_reg[1] = 1'b1;        //Infinity
            z_sig = {sig_width{1'b0}};
            z_exp_sh = {exp_width{1'b1}};
        end
    end else if((isAZero && isBInft)||(isBZero && isAInft))
    begin
        if(ieee_compliance)
        begin
            status_reg[2] = 1'b1;        //Invalid
            sign = 0;
            z_sig = {{(sig_width-1){1'b0}}, 1'b1};
            z_exp_sh = {exp_width{1'b1}};
        end else begin
            status_reg[1] = 1'b1;        //Infinity
            z_sig = {sig_width{1'b0}};
            z_exp_sh = {exp_width{1'b1}};
        end
    end else if(isBInft || isAInft)
    begin
            status_reg[1] = 1'b1;        //Infinity
            z_sig = {sig_width{1'b0}};
            z_exp_sh = {exp_width{1'b1}};
    end else if(isAZero || isBZero)
    begin
        status_reg[0] = 1'b1;        //Zero
        z_sig = {sig_width{1'b0}};
        z_exp_sh = {exp_width{1'b0}};
    end else if((ieee_compliance ==0)&& (isADenorm || isBDenorm))
    begin
        status_reg[0] = 1'b1;        //Zero
        z_sig = {sig_width{1'b0}};
        z_exp_sh = {exp_width{1'b0}};
    end else begin
        status_reg[5] = 1'b1;            //Inexact
//        fracAxB = fracA * fracB;      //put this multiply into dedicated module to use ALU
        lshift = 0;
        found = 0;
        for(i=0; i<=2*sig_width+1;i=i+1)
        begin
            if((found == 0) && (fracAxB[2*sig_width+1-i] == 1))
            begin
                lshift = i+1;
                found = 1;
            end
        end

        if(isADenorm)
            sign_aexp = 1;
        if(isBDenorm)
            sign_bexp = 1;

        sign_ext_zexp = sign_aexp + sign_bexp -ebias;

        normFracAxB = (fracAxB << lshift);
        z_exp_sh_full = sign_ext_zexp + 2 - lshift;

        isOf = (z_exp_sh_full > emax)? 1:0;
        isUf = (z_exp_sh_full < emin)? 1:0;

        if(ieee_compliance == 1)
        begin
            isUUf = (z_exp_sh_full <= 0)? 1:0;
        end

        // the result is denormal number
        if(isUUf)
        begin
            if(sign_ext_zexp==0)
            begin
                normFracAxB = (fracAxB << 1);
                z_exp_sh_full = 0;
            end else begin
                normFracAxB = (fracAxB >> (-1*sign_ext_zexp - 1));
                z_exp_sh_full = 0;
            end
        end

        // exp is bigger than emax
        if(isOf) 
        begin
            status_reg[4] = 1'b1;        //Huge
            case(rnd)
                // IEEE round to nearest (even)
                3'b000:
                begin
                    z_exp_sh = einf;           //{exp_width{1'b1}};
                    z_sig = {sig_width{1'b0}};
                    status_reg[1] = 1'b1;        //Infinity
                end
                // IEEE round to zero
                3'b001:
                begin
                    z_exp_sh = emax;
                    z_sig = {sig_width{1'b1}};
                end
                // IEEE round to positive infinity
                3'b010:
                begin
                    if(sign)
                    begin
                        z_exp_sh = emax;
                        z_sig = {sig_width{1'b1}};
                    end else begin
                        z_exp_sh = einf;        // {exp_width{1'b1}};
                        z_sig = {sig_width{1'b0}};
                        status_reg[1] = 1'b1;        //Infinity
                    end
                end
                // IEEE round to negative infinity
                3'b011:
                begin
                    if(sign)
                    begin
                        z_exp_sh = einf;    // {exp_width{1'b1}};
                        z_sig = {sig_width{1'b0}};
                        status_reg[1] = 1'b1;        //Infinity
                    end else begin
                        z_exp_sh = emax;
                        z_sig = {sig_width{1'b1}};
                    end
                end
                // round to nearest up
                3'b100:
                begin
                    z_exp_sh = einf;
                    z_sig = {sig_width{1'b0}};
                    status_reg[1] = 1'b1;        //Infinity
                end
                // round away from zero
                3'b101:
                begin
                    z_exp_sh = einf;
                    z_sig = {sig_width{1'b0}};
                    status_reg[1] = 1'b1;        //infinity
                end
            endcase
        end

        // exp is smaller than emin
        if(isUf) 
        begin
            status_reg[3] = 1'b1;        //Tiny
            if(ieee_compliance==0)
            begin
                case(rnd)
                    // IEEE round to nearest (Even)
                    3'b000:
                    begin
                        z_exp_sh = {exp_width{1'b0}};
                        z_sig = {sig_width{1'b0}};
                        status_reg[0] = 1'b1;        //Zero
                    end
                    // IEEE round to zero
                    3'b001:
                    begin
                        z_exp_sh = {exp_width{1'b0}};
                        z_sig = {sig_width{1'b0}};
                        status_reg[0] = 1'b1;    //Zero
                    end
                    // IEEE round to positive infinity
                    3'b010:
                    begin
                        if(sign)
                        begin
                            z_exp_sh = 0; 
                            z_sig = {sig_width{1'b0}};
                            status_reg[0] = 1'b1;        //Zero
                        end else begin
                            z_exp_sh = emin;
                            z_sig = {sig_width{1'b0}};
                        end
                    end
                    // IEEE round to negative infinity
                    3'b011:
                    begin
                        if(sign)
                        begin
                            z_exp_sh = emin;    
                            z_sig = {sig_width{1'b0}};
                        end else begin
                            z_exp_sh = 0;
                            z_sig = {sig_width{1'b0}};
                            status_reg[0] = 1'b1;        //Zero
                        end
                    end
                    // round to nearest up
                    3'b100:
                    begin
                        z_exp_sh = {exp_width{1'b0}};
                        z_sig = {sig_width{1'b0}};
                        status_reg[0] = 1'b1;        //Zero
                    end
                    // round away from zero
                    3'b101:
                    begin
                            z_exp_sh = emin;    
                            z_sig = {sig_width{1'b0}};
                    end
                endcase
            end
        end

        // rounding the fraction
        if (!((isOf)||(isUf&&(ieee_compliance==0))))
        begin
            case(rnd)
                // IEEE round to nearest(Even)
                3'b000:
                begin
                    if(normFracAxB[sig_width+1:0] > {1'b1, {(sig_width+1){1'b0}}})
                    begin
                        z_sig = normFracAxB[2*sig_width+1:sig_width+2] + 1;
                        if(z_sig == 0)
                        begin
                            z_exp_sh = z_exp_sh_full[exp_width-1:0] + 1;
                        end else begin
                            z_exp_sh = z_exp_sh_full[exp_width-1:0];
                        end
                    end else if(normFracAxB[sig_width+1:0] == {1'b1, {(sig_width+1){1'b0}}})
                    begin
                        if(normFracAxB[sig_width+2] == 1'b1)
                        begin
                            z_sig = normFracAxB[2*sig_width+1:sig_width+2] + 1;
                            if(z_sig == 0)
                            begin
                                z_exp_sh = z_exp_sh_full[exp_width-1:0] + 1;
                            end else begin
                                z_exp_sh = z_exp_sh_full[exp_width-1:0];
                            end
                        end else begin
                            z_sig = normFracAxB[2*sig_width+1:sig_width+2];
                            z_exp_sh = z_exp_sh_full[exp_width-1:0];
                        end
                    end else begin
                        z_sig = normFracAxB[2*sig_width+1:sig_width+2];
                        z_exp_sh = z_exp_sh_full[exp_width-1:0];
                    end
                end
                // IEEE round to zero
                3'b001:
                begin
                    z_sig = normFracAxB[2*sig_width+1:sig_width+2];
                    z_exp_sh = z_exp_sh_full[exp_width-1:0];
                end
                // IEEE round to positive infinity
                3'b010:
                begin
                    if(sign == 1'b0)
                    begin
                        z_sig = normFracAxB[2*sig_width+1:sig_width+2] + 1;
                        if(z_sig == 0)
                        begin
                            z_exp_sh = z_exp_sh_full[exp_width-1:0] + 1;
                        end else begin
                            z_exp_sh = z_exp_sh_full[exp_width-1:0] ;
                        end
                    end else begin
                        z_sig = normFracAxB[2*sig_width+1:sig_width+2];
                        z_exp_sh = z_exp_sh_full[exp_width-1:0];
                    end
                end
                // IEEE round to negative infinity
                3'b011:
                begin
                    if(sign == 1'b0)
                    begin
                        z_sig = normFracAxB[2*sig_width+1:sig_width+2];
                        z_exp_sh = z_exp_sh_full[exp_width-1:0];
                    end else begin
                        z_sig = normFracAxB[2*sig_width+1:sig_width+2] + 1;
                        if(z_sig == 0)
                        begin
                            z_exp_sh = z_exp_sh_full[exp_width-1:0] + 1;
                        end else begin
                            z_exp_sh = z_exp_sh_full[exp_width-1:0] ;
                        end
                    end
                end
                // round to nearest up
                3'b100:
                begin
                    if(normFracAxB[sig_width+1:0] > {1'b1, {(sig_width+1){1'b0}}})
                    begin
                        z_sig = normFracAxB[2*sig_width+1:sig_width+2] + 1;
                        if(z_sig == 0)
                        begin
                            z_exp_sh = z_exp_sh_full[exp_width-1:0] + 1;
                        end else begin
                            z_exp_sh = z_exp_sh_full[exp_width-1:0];
                        end
                    end else if(normFracAxB[sig_width+1:0] == {1'b1, {(sig_width+1){1'b0}}})
                    begin
                        if(sign == 1'b0)
                        begin
                            z_sig = normFracAxB[2*sig_width+1:sig_width+2] + 1;
                            if(z_sig == 0)
                            begin
                                z_exp_sh = z_exp_sh_full[exp_width-1:0] + 1;
                            end else begin
                                z_exp_sh = z_exp_sh_full[exp_width-1:0];
                            end
                        end else begin
                            z_sig=normFracAxB[2*sig_width+1:sig_width+2];
                        end
                    end else begin
                        z_sig = normFracAxB[2*sig_width+1:sig_width+2];
                        z_exp_sh = z_exp_sh_full[exp_width-1:0];
                    end
                end
                // round away from zero
                3'b101:
                begin
                    z_sig = normFracAxB[2*sig_width+1:sig_width+2] + 1;
                    if(z_sig == 0)
                    begin
                        z_exp_sh = z_exp_sh_full[exp_width-1:0] + 1;
                    end else begin
                        z_exp_sh = z_exp_sh_full[exp_width-1:0] ;
                    end
                end
                default:
                begin
                    z_sig = normFracAxB[2*sig_width+1:sig_width+2];
                    z_exp_sh = z_exp_sh_full[exp_width-1:0];
                end
            endcase
        end
        if ((z_sig == 0) && (z_exp_sh == 0))
        begin
            status_reg[0] = 1;        //Zero
        end
    end
end


assign z = {sign, z_exp_sh, z_sig};
assign status = status_reg;

  
endmodule


module oneindex(data, index);
parameter dwidth = 40;
parameter iwidth = 10;
input [dwidth-1:0] data;
output [iwidth-1:0] index;


reg [iwidth-1:0] index;

integer i;
reg found=0;

always @(data)
begin
    index = 0;
    found = 0;
    for(i=0;i<=dwidth;i=i+1)
    begin
        if((found == 0) && (data[dwidth-1-i] == 1))
        begin
            index = i+1;
            found = 1;
        end
    end
end
endmodule


module HZ_int_mult(A, B, Z);

parameter A_width = 32;
parameter B_width = 8;

input   [A_width-1:0]   A;
input   [B_width-1:0]   B;

output [A_width+B_width-1:0] Z;

assign Z = A * B;

endmodule

后序

如果要使用或引用本文代码,请保留本作者信息。

查看原文

赞 0 收藏 0 评论 0

harriszh 发布了文章 · 2020-07-30

RTL monitor的文件输出效率优化和研究

The performance comparison of output in RTL monitor

summary

最近在写RTL monitor, 发现如果频繁用$fdisplay写数据出来,性能会成为瓶颈。所以就研究用DPI-C把数据送出来,然后在C侧看看有什么优化手段。有几种优化方法, 一种是写raw data到C侧,C侧直接把raw data格式化输出。 另一种是把raw data存成文件后就返回到RTL侧。然后线下用一个进程把raw data进行格式化输出。线下的进程可以和输出进程同时跑,一旦有数据出来它就进行数据处理并输出。
为了研究这个问题,我直接写个纯C的producer-consumer模型,代码见下面。

conclusion

fwrite在绝大分场景下比mmap快, 当写入量为4GB左右时, fwriter用时10.5 sec, mmap用时29.6 sec.
用fread读入4GB binary数据, 用fwrite写出8.9GB数据时需要103.4 sec
用mmap读入4GB binary数据, 用fwrite写出8.9GB数据时需要88.1 sec
producer比较快, consumer比较慢, producer 2秒可以产生1GB数据,consumer需要20 sec左右才可以处理完。当两者并行跑时,收益并不高。

注意点

open mode

用fopen以写模式("w")打开文件时,再用mmap加载文件时,会因为文件权限不一致无法map成功。
也就是下面的代码虽然在编译时通过,但在运行时会报如下错误
不管如何设置PROT (PROT_WRITE, PROT_WRITE|PROT_READ, PROT_READ|PROT_EXEC), 或者如何设置flags(MAP_SHARED, MAP_PRIVATE), 都会在运行时报错:Permission denied

FILE* f = fopen(argv[1], "w"); // whatever use "w" or "wb"
int *map=(int*)mmap(0, totalbytes, PROT_READ|PROT_WRITE, MAP_SHARED, fileno(f), 0);

解决方法是使用"w+"打开文件, 原因如下:

fopen() modeopen() flags
rO_RDONLY
wO_WRONLY | O_CREAT | O_TRUNC
aO_WRONLY | O_CREAT | O_APPEND
r+O_RDWR
w+O_RDWR | O_CREAT | O_TRUNC
a+O_RDWR | O_CREAT | O_APPEND

write

在写之前需要先把文件设置成相应的大小。可以使用fnctl.h用的ftruncate

size_t totalbytes = 4 + iter*20 + 20 ;
ftruncate(fw, totalbytes);

原型

主要涉及mmap, fopen, open, fread, fwrite,ftruncate几个函数。

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);

#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *pathname, const char *mode, FILE *stream);

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
//The function fread() reads nmemb items of data, each size bytes long, from the stream pointed to by stream, storing them at the location given by ptr.

实现

fwrite

#include <cstdio>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <chrono>
#include <thread>
#include "timediff.hpp"

struct info {
    uint32_t pc_l;
    uint32_t pc_h;
    uint32_t instr;
    uint32_t time_l;
    uint32_t time_h;
};

int main(int argc, char** argv) {
    if(argc<2) {
        return 1;
    }
    TimerClock tc;

    FILE *f = std::fopen(argv[1], "wb");
    printf("size of info : %d\n", sizeof(info));
    uint32_t i=1;
    tc.start();
    int prec=-9;
    std::fwrite(&prec, sizeof(int), 1, f);
    while(i++<50000000) {
        info val = {(uint32_t)i, (uint32_t)i, (uint32_t)i, (uint32_t)i, (uint32_t)0};
        size_t len = std::fwrite(&val, sizeof(info), 1, f);
        if (len != 1) {
            std::cout << "the len is: " << len << "\n";
            info val = {0,0,0,0,0};
            size_t len = std::fwrite(&val, sizeof(info), 1, f);
            std::fclose(f);
            exit(1);
        } else {
            if ( i%200 == 0) {
                fflush(f);
            }
        }
        //std::this_thread::sleep_for(std::chrono::microseconds(1));
    }
    info val;
    std::memset(&val, 0, sizeof(info));
    size_t len = std::fwrite(&val, sizeof(info), 1, f);
    if (len != 1 ) {
        std::cout << "end error\n";
    }
    std::cout << "writer elapsed time: " << tc.getTimerMicroSec() << "us\n";
    std::fclose(f);
    std::cout << "writer done!\n";
    return 0;
}

fread

#include <cstdio>
#include <cstdint>
#include <iostream>
#include <chrono>
#include <thread>
#include <sys/stat.h>
#include "timediff.hpp"

struct info {
    uint32_t pc_l;
    uint32_t pc_h;
    uint32_t instr;
    uint32_t time_l;
    uint32_t time_h;
};

inline bool is_file_exist(const char* file) {
    struct stat buffer;
    if(stat(file, &buffer)) {
        return false;
    }
    if ( !S_ISREG(buffer.st_mode)) {
        return false;
    }
    return true;
}

int form_line(uint64_t time, int prec, uint64_t pc, uint32_t instr, char * fchar, size_t size) {
    switch (prec) {
    case -15:
        snprintf(fchar, size, "%lu %s PC=0x%016lx, 0x%08x \n", time, "fs", pc, instr );
        break;
    case -14:
        snprintf(fchar, size, "%lu0 %s PC=0x%016lx, 0x%08x \n", time, "fs", pc, instr );
        break;
    case -13:
        snprintf(fchar, size, "%lu00 %s PC=0x%016lx, 0x%08x \n", time, "fs", pc, instr );
        break;
    case -12:
        snprintf(fchar, size, "%lu %s PC=0x%016lx, 0x%08x \n", time, "ps", pc, instr );
        break;
    case -11:
        snprintf(fchar, size, "%lu0 %s PC=0x%016lx, 0x%08x \n", time, "ps", pc, instr );
        break;
    case -10:
        snprintf(fchar, size, "%lu00 %s PC=0x%016lx, 0x%08x \n", time, "ps", pc, instr );
        break;
    case -9:
        snprintf(fchar, size, "%lu %s PC=0x%016lx, 0x%08x \n", time, "ns", pc, instr );
        break;
    case -8:
        snprintf(fchar, size, "%lu0 %s PC=0x%016lx, 0x%08x \n", time, "ns", pc, instr );
        break;
    case -7:
        snprintf(fchar, size, "%lu00 %s PC=0x%016lx, 0x%08x \n", time, "ns", pc, instr );
        break;
    case -6:
        snprintf(fchar, size, "%lu %s PC=0x%016lx, 0x%08x \n", time, "us", pc, instr );
        break;
    case -5:
        snprintf(fchar, size, "%lu0 %s PC=0x%016lx, 0x%08x \n", time, "us", pc, instr );
        break;
    case -4:
        snprintf(fchar, size, "%lu00 %s PC=0x%016lx, 0x%08x \n", time, "us", pc, instr );
        break;
    case -3:
        snprintf(fchar, size, "%lu %s PC=0x%016lx, 0x%08x \n", time, "ms", pc, instr );
        break;
    case -2:
        snprintf(fchar, size, "%lu0 %s PC=0x%016lx, 0x%08x \n", time, "ms", pc, instr );
        break;
    case -1:
        snprintf(fchar, size, "%lu00 %s PC=0x%016lx, 0x%08x \n", time, "ms", pc, instr );
        break;
    case  0:
        snprintf(fchar, size, "%lu %s PC=0x%016lx, 0x%08x \n", time, "s", pc, instr );
        break;
    default:
        fprintf(stderr, "unknown precision %d\n", prec);
        return 1;
    }
    //printf("%s : %s\n", __FUNCTION, fchar);
    return 0;
}


int main(int argc, char**argv) {
    if(argc<3) return 1;
    TimerClock tc;
    int timeout=0;
    while ( (!is_file_exist(argv[1])) && (timeout< 600000)) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        timeout++;
    }
    std::cout << "start ...\n";

    FILE *r = std::fopen(argv[1], "rb");
    if (r==NULL) {
        std::cout<< "can't open " << argv[1] << "\n";
        exit(1);
    }
    FILE *w = std::fopen(argv[2], "w");
    if (w==NULL) {
        std::cout<< "can't open " << argv[2] << "\n";
        exit(1);
    }
    tc.start();
    long pos;
    timeout = 0;
    int prec;
    fread(&prec, sizeof(int), 1, r);
    while(1) {
        info val;
        pos = ftell(r);   
        int len = fread(&val, sizeof(info), 1, r);
        if (len!= 1) {
            if(ferror(r)) {
                perror("fread error\n");
                goto FINISHED;
            }
            if(timeout < 10000000) {
                fseek(r, pos, SEEK_SET);
                std::this_thread::sleep_for(std::chrono::microseconds(1));
                timeout++;
                continue;
            } else {
                goto FINISHED;
            }
        } else {
            if ((val.pc_h == 0)&&(val.pc_l==0) && (val.instr==0) && (val.time_h==0) && (val.time_l==0)) {
                break;
            }
        }
        char line[100];
        uint64_t pc = ((uint64_t)val.pc_h << 32) + val.pc_l;
        uint64_t time = ((uint64_t)val.time_h << 32) + val.time_l;
        int r = form_line(time, prec, pc, val.instr, line, sizeof(line));
        if (r==1) {
            return 1;
        }
        std::fputs(line, w);
    }
FINISHED:
    std::cout << "timeout: " << timeout << "\n";
    std::cout << "reader done\n";
    std::cout << "reader elapsed time: " << tc.getTimerMicroSec() << "us\n";
    std::fclose(r);
    std::fclose(w);
    return 0;
}

mmap write

#include <cstdio>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <chrono>
#include <thread>
#include <unistd.h>
#include <fcntl.h>
#include "sys/mman.h"
#include "timediff.hpp"

struct info {
    uint32_t pc_l;
    uint32_t pc_h;
    uint32_t instr;
    uint32_t time_l;
    uint32_t time_h;
};

const int iter = 50000000;

int main(int argc, char** argv) {
    if(argc<2) {
        return 1;
    }
    TimerClock tc;

    int f = open(argv[1], O_RDWR|O_CREAT|O_TRUNC, 0x0777);
    std::cout << "start to write bin file " << argv[1] << "\n";
    size_t totalbytes = 4+iter*20+20;
    ftruncate(f, totalbytes);
    tc.start();
    int prec=-9;
    int *map=(int*)mmap(0, totalbytes, PROT_WRITE, MAP_SHARED, f, 0);
    if (map == MAP_FAILED) {
        close(f);
        perror("error mapping");
        exit(1);
    }
    size_t wptr=0;
    map[wptr++] = prec;
    uint32_t i=0;
    while(i++<iter) {
        map[wptr++] = i;
        map[wptr++] = i;
        map[wptr++] = i;
        map[wptr++] = i;
        map[wptr++] = 0;
        //std::this_thread::sleep_for(std::chrono::microseconds(1));
    }
    map[wptr++] = 0;
    map[wptr++] = 0;
    map[wptr++] = 0;
    map[wptr++] = 0;
    map[wptr++] = 0;
    if(msync(map, totalbytes, MS_SYNC) == -1) {
        perror("could not sync the file to disk");
    }
    if(munmap(map, totalbytes) == -1) {
        close(f);
        perror("error unmap the file");
        exit(1);
    }
    close(f);
    std::cout << "writer elapsed time: " << tc.getTimerMicroSec() << "us\n";
    close(f);
    std::cout << "writer done!\n";
    return 0;
}

mmap read

#include <cstdio>
#include <cstdint>
#include <iostream>
#include <vector>
#include <unistd.h>
#include <fcntl.h>
#include <chrono>
#include <thread>
#include <sys/stat.h>
#include "timediff.hpp"
#include <sys/mman.h>

struct info {
    uint32_t pc_l;
    uint32_t pc_h;
    uint32_t instr;
    uint32_t time_l;
    uint32_t time_h;
};

inline bool is_file_exist(const char* file) {
    struct stat buffer;
    if(stat(file, &buffer)) {
        return false;
    }
    if ( !S_ISREG(buffer.st_mode)) {
        return false;
    }
    return true;
}

int form_line(uint64_t time, int prec, uint64_t pc, uint32_t instr, char * fchar, size_t size) {
    switch (prec) {
    case -15:
        snprintf(fchar, size, "%lu %s PC=0x%016lx, 0x%08x \n", time, "fs", pc, instr );
        break;
    case -14:
        snprintf(fchar, size, "%lu0 %s PC=0x%016lx, 0x%08x \n", time, "fs", pc, instr );
        break;
    case -13:
        snprintf(fchar, size, "%lu00 %s PC=0x%016lx, 0x%08x \n", time, "fs", pc, instr );
        break;
    case -12:
        snprintf(fchar, size, "%lu %s PC=0x%016lx, 0x%08x \n", time, "ps", pc, instr );
        break;
    case -11:
        snprintf(fchar, size, "%lu0 %s PC=0x%016lx, 0x%08x \n", time, "ps", pc, instr );
        break;
    case -10:
        snprintf(fchar, size, "%lu00 %s PC=0x%016lx, 0x%08x \n", time, "ps", pc, instr );
        break;
    case -9:
        snprintf(fchar, size, "%lu %s PC=0x%016lx, 0x%08x \n", time, "ns", pc, instr );
        break;
    case -8:
        snprintf(fchar, size, "%lu0 %s PC=0x%016lx, 0x%08x \n", time, "ns", pc, instr );
        break;
    case -7:
        snprintf(fchar, size, "%lu00 %s PC=0x%016lx, 0x%08x \n", time, "ns", pc, instr );
        break;
    case -6:
        snprintf(fchar, size, "%lu %s PC=0x%016lx, 0x%08x \n", time, "us", pc, instr );
        break;
    case -5:
        snprintf(fchar, size, "%lu0 %s PC=0x%016lx, 0x%08x \n", time, "us", pc, instr );
        break;
    case -4:
        snprintf(fchar, size, "%lu00 %s PC=0x%016lx, 0x%08x \n", time, "us", pc, instr );
        break;
    case -3:
        snprintf(fchar, size, "%lu %s PC=0x%016lx, 0x%08x \n", time, "ms", pc, instr );
        break;
    case -2:
        snprintf(fchar, size, "%lu0 %s PC=0x%016lx, 0x%08x \n", time, "ms", pc, instr );
        break;
    case -1:
        snprintf(fchar, size, "%lu00 %s PC=0x%016lx, 0x%08x \n", time, "ms", pc, instr );
        break;
    case  0:
        snprintf(fchar, size, "%lu %s PC=0x%016lx, 0x%08x \n", time, "s", pc, instr );
        break;
    default:
        fprintf(stderr, "unknown precision %d\n", prec);
        return 1;
    }
    //printf("%s : %s\n", __FUNCTION, fchar);
    return 0;
}


int main(int argc, char**argv) {
    if(argc<3) return 1;
    TimerClock tc;
    int timeout=0;
    while ( (!is_file_exist(argv[1])) && (timeout< 600000)) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        timeout++;
    }
    std::cout << "start ...\n";

    int r = open(argv[1], O_RDONLY, 0x0600);
    if (r==-1) {
        std::cout<< "can't open " << argv[1] << "\n";
        exit(1);
    }
    FILE *w = std::fopen(argv[2], "w");
    if (w==nullptr) {
        std::cout<< "can't open " << argv[2] << "\n";
        close(r);
        exit(1);
    }
    struct stat rfileInfo = {0};
    struct stat wfileInfo = {0};
    if(fstat(r, &rfileInfo) == -1) {
        exit(1);
    }
    if(fstat(fileno(w), &wfileInfo) == -1) {
        exit(1);
    }
    std::vector<int> segment;
    size_t cursize;
    size_t lastpos;
    size_t pagesize = sysconf(_SC_PAGE_SIZE);
    size_t readto;
    int prec;
    off_t pa_offset;

    timeout =0;
    while((rfileInfo.st_size == 0)&&(timeout<600000)) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        timeout++;
    }
    if (timeout >= 600000) {
        goto FINISHED;
    }
    printf("File size is %ji\n", (intmax_t)rfileInfo.st_size);

    cursize  = rfileInfo.st_size;
    lastpos = 0;
    if (cursize<24) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    readto = cursize - (cursize-4) % 20;

    tc.start();

    while(1) {

        pa_offset = lastpos & ~(pagesize-1);
        size_t size = readto-pa_offset;
        size_t pass_byte = lastpos-pa_offset;   
        std::cout << "size = " << size << "\n";
        std::cout << "lastpos =" << lastpos << "\n";
        char *rmap = (char*)mmap(0, size, PROT_READ, MAP_SHARED, r, pa_offset);
        if(rmap == MAP_FAILED) {
            perror("error mapping");
            goto FINISHED;
        }
        size_t rindex=0;
        if (lastpos==0) {
            prec = rmap[rindex++];
        }
        lastpos = readto;
        int * rptr = (int*)(rmap+pass_byte);
        int total = (size-pass_byte)/4;

        while(rindex<total) {
            info val;
            val.pc_l=rptr[rindex++];
            val.pc_h=rptr[rindex++];
            val.instr=rptr[rindex++];
            val.time_l=rptr[rindex++];
            val.time_h=rptr[rindex++];
            if ((val.pc_h == 0)&&(val.pc_l==0) && (val.instr==0) && (val.time_h==0) && (val.time_l==0)) {
                munmap(rmap, size);
                std::cout << "reach end\n";
                goto FINISHED;
            }
            char line[100];
            uint64_t pc = ((uint64_t)val.pc_h << 32) + val.pc_l;
            uint64_t time = ((uint64_t)val.time_h << 32) + val.time_l;
            int rlt = form_line(time, prec, pc, val.instr, line, sizeof(line));
            if (rlt==1) {
                goto FINISHED;
            }
            std::fputs(line, w);
        }
        munmap(rmap, size);
        if(fstat(r, &rfileInfo) == -1) {
            goto FINISHED;
        }
        cursize = rfileInfo.st_size;
        int watchdog =0;
        while ((cursize<(lastpos+20))||(watchdog++<300)) {
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
            if(fstat(r, &rfileInfo) == -1) {
                std::cout<< "can't get file size\n";
                goto FINISHED;
            }
            cursize = rfileInfo.st_size;
        }
        if (watchdog >= 30000) {
            goto FINISHED;
        }
        readto = cursize - (cursize-4) % 20;
        segment.push_back(readto);
    }
FINISHED:
    std::cout << "timeout: " << timeout << "\n";
    std::cout << "reader done\n";
    std::cout << "reader elapsed time: " << tc.getTimerMicroSec() << "us\n";
    close(r);
    std::fclose(w);
    std::cout << "the segment is :\n";
    for(auto it: segment) {
        std::cout <<  it << "\n";
    }
    return 0;
}

timer

#ifndef _TIMEDIFF_HPP_
#define _TIMEDIFF_HPP_

#include <iostream>
#include <chrono>

class TimerClock
{
public:
    TimerClock()
    {
        update();
    }

    ~TimerClock()
    {
    }

    void start()
    {
        _start = std::chrono::high_resolution_clock::now();
    }

    long long getTimerMicroSec()
    {
        return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - _start).count();
    }
private:
    std::chrono::time_point<std::chrono::high_resolution_clock>_start;
};

#endif

compile

CXXFLAGS = -g -std=c++11
gen: reader writer

FILES = test.bin test.log test1.log test2.log

reader: reader.cpp
    g++ ${CXXFLAGS} -o $@ $^ -I./

writer: writer.cpp
    g++ ${CXXFLAGS} -o $@ $^ -I./

writer2: writer2.cpp
    g++ ${CXXFLAGS} -o $@ $^ -I./

run: clean
    (./writer &) && (sleep 1) && (./reader &)

runw:
    rm -rf /tmp/test.txt
    time ./writer &

runr:
    rm -rf /tmp/out.txt /tmp/test.txt
    time ./reader &

clean:
    - rm -rf ${FILES} reader writer reader2 writer2 writer3

genbin:
    ./writer ../../tarmac.core0.log.bin
    ./writer ../../tarmac.core1.log.bin


diff:
    tail out.txt | diff golden.txt -

.PHONY: gen clean
查看原文

赞 0 收藏 0 评论 0

harriszh 关注了标签 · 2020-07-29

rust

一门赋予每个人构建可靠且高效软件能力的语言。

关注 472

harriszh 发布了文章 · 2020-06-14

SystemVerilog中关于DPI章节的翻译

35. Direct Programming Interface

需求

随着时代的发展,现在的芯片规模越来越大,哪怕模块级的验证环境也需要相当长的build时间,各种仿真工具也在改进编译和运行性能,还发明了增量编译。但无论如何turnaround的时间还是比较长,而且方法越复杂越容易出错。而DPI-C则比较简单,能够解决某些场景下的问题。

适用范围

DPI-C比较适用于SV和外部语言间的“简单数据“交互

翻译约定

subroutine == 子例程 == task and function
task == 子任务
function == 子函数
foreign language == 外部编程语言
在某些情况下会保留英文表述不做翻译

通用

下面章节主要包括:

  • DPI 子任务和子函数
  • DPI层
  • 导入和导出子函数
  • 导入和导出子任务
  • 禁用 DPI 子任务和子函数

35.2 总述

本章节主要描述了DPI以及接口中SV层的细节。
DPI是SV和外部编程语言的接口。它包含两个独立的层次: SV层 和 外部编程语言层。两侧是被完全隔离的,也就是说它们彼此是透明的, 外部编译语言和SV侧完全无关。无论是SV编译器还是外部编程语言编译器都不会分析对方的代码。不同编程语言应该都可以被一样的SV层支持。但目前SV只定义了对C的支持。

这个接口的动机是双面的。方法学上的要求是整个系统可以包含多种语言而不仅仅是SV,另一方面是已有的代码(比如C,C++)能够被很容易地集成进来。

DPI遵循black-box原则。描述和实现隔离。真正的实现方法对于系统的其他部分是透明的。 因为外部语言对SV来讲也是透明的,不过这个标准目前只定义了C链接语义。 SV和C通过子函数为基本单元来封装和交互。所以任何子函数都可以被当成黑盒子,它的实现或者在SV中,或者在外部编程语言中,它的实现修改了但无需修改调用侧。

35.2.1 tasks/functions

DPI允许语言之间的相互子函数调用。展开来讲就是,SV可以调用外部编程语言里定义和实现的子函数,对SV来讲就是imported functions; 外部编程语言可以调用SV里定义和实现的子函数,对SV来讲就是exported functions,需要在SV中作导出声明。DPI允许通过子函数的参数和结果来在两个域中传递数据。这个接口没有固有的开销。

外部语言代码也可调用SV task, 原生的SV代码也可能调用imported tasks.
imported tasks和原生的SV task在语义上是一样的:

  • 无返回值
  • 可以消耗仿真时间

所有DPI 子函数都是不消耗仿真时间的,DPI无法提供除数据交换和控制权转移以外的同步手段.

每个imported的subroutine都必须先声明。
import声明可以写在任何SV subroutine允许的地方。同一个外部subroutine可以被多处调用。
imported subroutine可以有0个可多个 input, output, inout 参数。
imported tasks只能返回void value; imported function可以返回值或void value.

35.2.2 Data types

SV 数据类型必须是能够跨越SV和外部语言的类型, 称为富类型:

  • void, byte, shortint, int, longint, real, shortreal, chandle, string
  • scalar value of bit and logic
  • packed arrays, structs, uniions composed of types bit and logic
  • emumeration types (interpreted as the associated real type)
  • types constructed from the following constructs:

    • struct
    • union(packed only)
    • unpacked array
    • typedef

下面是一些注意点:

  • enum 不能被直接支持,实际上会被解释为enum type关联的类型
  • 在exported DPI subroutine里, 声明形参为dynamic array容易出错
  • SV data的实际内存结构对于SV是透明的。

Function的返回值必须是small values:

  • void, byte, shortint, int, longint, real, shortreal, chandle, string
  • scalar value of bit and logic

而imported function的形参可以是open arrays

35.2.2.1 内存数据结构

DPI并没有对于数据在内存里的存在形式做任何约束, 所以这是平台相关的,和SV无关

35.3 DPI的两层

DPI由独立的两层组成: SV层和外部编译语言层。他们不相互依赖。虽然SV可以支持各种编译语言,但目前SV标准只对C做了详细定义。不同的外部编程语言可以要求SV实现使用适合的函数调用协议,参数传递,链接机制。但这对于用户来讲是透明的。SV标准只要求仿真器的实现者支持C协议和链接。

35.3.1 SV layer

不依赖于外面到底是什么语言。外部语言的实际函数调用规则和参数传递机制对于SV来讲是透明和无关的。SV同等看待所以外部语言接口。SV侧的接口语义和外部编译语言侧的接口无关。

35.3.2 foreign language layer

定义实参是如何被传递的, SV data(如logic, packed)是如何表示的。如何与类C的类型转换
对于不同的外部语言,SV端的代码应该是一样的

35.4 imported and exported functions的全局命名空间

每个imported subroutine最终最会有一个全局符号标志;而每个exported subroutine会定义一个全局符号标志。
它们必须唯一。它们遵循C的命名规则,必须以字母或_开头。
import和export声明时可以定义显式地一个全局名字。它必须是以开头以空格结尾

export "DPI-C" f_plus = function \f+ ; // "f+" exported as "f_plus"
export "DPI-C" function f; // "f" exported under its own name
import "DPI-C" init_1 = function void \init[1] (); // "init_1" is a linkage name
import "DPI-C" \begin = function void \init[2] (); // "begin" is a linkage name

相同C标识符的多个export 声明是允许,前提是它们在不同的scope里

35.5 imported tasks and functions

35.5.1 属性 (properties)

pure
如果一个function的返回值只依赖于它的输入参数,且没有副作用, 那么它可以声明为pure
imported task绝不能声明为pure
context
一个imported subroutine要调用exported subroutines, 或要获取SV data objects(e.g., via VIP calls)而不是它的实际值, 那么需要声明它为context。
如果没有描述,那么subroutine应该不访问SV data objects; 不过它可以进行一些有副作用的行为,比如写文件,操作一个全局变量
compiler不会对这些properties进行检查, 所以使用它们要小心

35.5.1.1 instant completion of imported functions

它们立即被执行,并且无耗时

35.5.1.2 input, output, inout

input: 不会被修改
ouput: output的初始值是不确定的,所以imported function不应该依赖于它
inout: imported function可以得到inout变量的初始值。imported function对于inout参数的修改可以被function外面看到

35.5.1.4 内存管理

外部语言和SV各自负责自己内存的申请和释放
一个比较复杂且允许的情况是一个imported function申请了一块内存并把handle传到SV里, 然后SV调用另一个imported function来释放它

35.5.1.5 重入 (Reentrancy of imported tasks)

对于imported task的调用可能会导致自身进程暂停。这种情况发生在imported task又调用带有delay或event wait的exported task时。
所以有可能一个imported C code把多个线程同时激活。关于标准的重入规则是由C端决定。可以使用一些标准的多线程安全库,或者静态变量来做控制

35.5.1.6 C++异常 (C++ exceptions)

可以使用C++,但前提是在language边界遵守C链接约定。
C++异常不应该传播到subroutine以外, 如果异常传递到SV里,那么这是一个未定义的行为

35.5.2 Pure functions

pure function特点之一是如果它的返回不需要那么可以把pure function调用去掉。或者它的输入没变,那么可以不用重新计算。
只有没有output和inout形参的nonvoid function可以被声明为pure
声明为pure的function没有任何副作用
返回只依赖于输入值
对于这种funciton的调用可以被SV compiler优化。或者当input没变时,可以直接使用之前的值
pure function应该不直接或间接(比如调用其他function)执行下面动作:

  • 文件操作
  • 对任何事物的读写,包括I/O, 环境变量,来自操作系统,程序,进程的对象, shared memory, socket等
  • 访问任何永久变量,比如全局的或静态的变量

35.5.3 context tasks and functions

当imported subroutine要求调用上下文需要被知道时,调用者要采取特别的指令来提供这样的上下文。特殊的指令比如创建一个指向目前实例的内部变量。
当有当context被显式指定时,才会做这样的instrumentation。

exported subroutine必须知道它被调用时的上下文, 包括当它们被imported subroutine调用时。
imported subroutine可以在调用exported subroutine前,先调用svSetScope来显式地设定上文。否则,exported sobroutine的上下文就是调用import subroutine时的实例所在上下文。由于一个imported subroutine会存在于多个实例化后的作用域(scope)里, 所以展开(elaboration)后会有多个exported subroutine的实例(instances).如果不调用svSetScope, 那么这些exported instances的上下文就是调用imported subroutine的作用域(instantiated scope)

外部语言可以通过一些其他接口(如 VPI callback)来调用svSetScope或其他DPI相关的scope APIs, 然后也可以在一个指定的作用域(instantiated scope)里调用exported subroutine。
DPI的scope相关APIs的行为和DPI exported subroutine的调用由各simulator决定,DPI spec不做规定

在SV里最开始调用imported subroutine的地方称为调用链初始点(root of the call chain)

上下文属性会从SV里应用到每一个imported subroutine. 这意味着根部的或调用链中间的imported call不一定能够把它的上下文环境传递到它的下一个import call.
所以一个无上下文环境的imported subroutine不能够调用一个SV exported subroutine. 这样的行为会导致出错

下面是关于imported call chain的一些特性:

  • 下面动作决定导入调用链(import call chain)的上下文值:

    • 当SV subroutine调用一个导入DPI subroutine, 一个导入声明所在的实例化作用域的上下文就为这个导入调用链(imprt call chain)创建好了
    • 当处于导入调用链(import call chain)中的一个routine调用svSetScope并传入一个合法参数时,调用链的上下文就被设置成svSetScope参数所示上下文
    • 当一个导入调用链中调用一个导出的SV subroutine完成并返回时,调用链的上下文回到调用前的值
  • 仿真器需要管理DPI的上下文,需要检测控制权如何在SV和外部语言间传递。如果用户代码里通过如C里的setjmp/longjmp等结构来从一个导出的调用链(SV)回到它的导入调用链的调用者(C), 那么它的结果是未定义的。(例如:CA -> SVA -> CB, CB里通过setjmp/longjmp回到CA)
  • 一个特定的import subroutine是否上下文相关是由它自身声明时的context属性决定的。这个属性不会传递到它的后续的import function (译者注:这个地方是function而不是subroutine?)
  • import call的上下文特性无法动态改变
  • 上下文特性和调用链绑定,而不是和imported subroutine; 因此,一个subroutine可以在一个调用链中是context,而在另一个调用链中non-context。

一个没有定义为context的imported subroutine只访问它的实现参数。所以不是仿真器优化的障碍。而context的imported subroutine能够通过VPI和export subroutine来访问任何SV data objects, 所以会造成SV compiler的优化障碍。

只有context imported subroutine是被特殊装置地,并只做保守的优化。只有这种subroutine可以在安全地其他subroutines, 包括VPI或exported SV subroutines; 否则调用VPI和exported SV subroutine的行为是不可预测的,如果被调用者需要的上下文没有普查正确设置,那么会造成崩溃。定义一个context import subroutine并不会使simulator的其他接口自动可用。比如VPI访问还是依赖于正确的实现机制。DPI 调用不会自动创建或提供任何句柄或特定环境给其他接口用。这个是用户的责任。

context imported subroutines总是隐性地有一个完整实例名的作用域(scope)。这个作用域定义了哪些SV subroutines可以被importored subroutine直接调用;也只有这此在相同作用域的exported subroutine可以被直接调用。如果要调用其他的exported SV subroutine, imported subroutine需要先修改它的目前作用域。

相关的DPI functions可以允许imported subroutines来获取或接口用它的作用域, 如svGetScope(), svSetScope, svGetNameFromScope, svGetScopeFromName。

35.5.4 Import declarations

所有imported subroutine都需要被声明。
imprted subroutine和SV的subroutine相似,可以有0个或多个input, output或inout形参。imported functions可以返回一个或0个(void)值。imported tasks总是返回一个int值,在外部语言里相当于一个int function。
(译者: 原文中说是作为DPI disable protocol的一部分,没搞明白)

dpi_import_export ::=                                                         // from A.2.6
        import dpi_spec_string [ dpi_function_import_property ] [ c_identifier = ] dpi_function_proto ;
        | import dpi_spec_string [ dpi_task_import_property ] [ c_identifier = ] dpi_task_proto ;
        | export dpi_spec_string [ c_identifier = ] function function_identifier ;
        | export dpi_spec_string [ c_identifier = ] task task_identifier ;
dpi_spec_string ::= "DPI-C" | "DPI"
dpi_function_import_property ::= context | pure
dpi_task_import_property ::= context
dpi_function_proto  ::= function_prototype
dpi_task_proto ::= task_prototype
function_prototype ::= function data_type_or_void function_identifier [ ( [ tf_port_list ] ) ]
task_prototype ::= task task_identifier [ ( [ tf_port_list ] ) ]             // from A.2.7

21) dpi_function_proto return types are restricted to small values, per 35.5.5
22) Formals of dpi_function_proto and dpi_task_proto cannot use pass by reference mode and class types cannot be
passed at all; see 35.5.6 for a description of allowed types for DPI formal arguments.

import 声明描述了subroutine名, function返回值类型,和它的形参的类型和方向。它也能够提供形参的默认值。形参名是可选的,除非需要按名称绑定参数。
imported function可以有contextpure属性; imported tasks可以有context属性

导入声明相当于定义一个subroutine名, 所以多个相同的subroutine名是不允许导入同一作用域的。

  • dpi_spec_string*可以取"DPI-C"或“DPI”。“DPI"是用于指示使用不建议用的sv packed array传输语义。这种语义下,参数是使用它在仿真器里的表示法来传递,而不是规一化的形式。
  • c_identifier*是这个subroutine在外部语言里的链接名(linkage name). 如果没有提供的话就和它的SV subroutine名相同。

对于一个c_identifier, 所有声明都必须使用相同的类型签名(type signature), 包括返回值类型, 每个参数的数目, 顺序, 方向, 类型。类型包括维数,数组的边界和维数。类型签名也包括pure/context限定符, 和dpi_spec_string的值。

在多个不同作用域中可以有同样imported或exported subroutine的多个不同声明;因此, 参数名和默认值可能不同。

正式的参数名要区别packed和unpacked数组维数。

限定符ref不能用在import声明中。实际的实现方法只决取于外部语言层。实现方法对于SV边是透明的。

下面是一些声明的例子:

import "DPI -C " function void myInit();

// from standard math library
import "DPI -C " pure function real sin(real);

// from standard C library: memory management
import "DPI -C " function chandle malloc(int size); // standard C function
import "DPI -C " function void free(chandle ptr); // standard C function

// abstract data structure: queue
import "DPI -C " function chandle newQueue(input string name_of_queue);

// Note the following import uses the same foreign function for
// implementation as the prior import, but has different SystemVerilog name
// and provides a default value for the argument.
import "DPI -C " newQueue=function chandle newAnonQueue(input string s=null);
import "DPI -C " function chandle newElem(bit [15:0]);
import "DPI -C " function void enqueue(chandle queue, chandle elem);
import "DPI -C " function chandle dequeue(chandle queue);

// miscellanea
import "DPI -C " function bit [15:0] getStimulus();
import "DPI -C ” context function void processTransaction(chandle elem, output logic [64:1] arr [0:63]);
import "DPI -C " task checkResults(input string s, bit [511:0] packet)

35.5.5 Function result

imported function的返回值类型被限定为"small values":

  • void , byte , shortint , int , longint , real , shortreal , chandle , and string
  • Scalar values of type bit and logic

同样的限定也适用于exported function的返回值类型上

35.5.6 Types of formal argument (形参类型)

SV中丰富的数据类型可以被作为形参用于imported和export的例程。通常,C兼容的类型,packed类型,用户定义的类型和他们的组合都可以用作DPI例程的形参。
下面是SV中允许的所有可以作为DPI例程形参的类型:

  • void, byte, shortint, int, longint, real, shortreal, chandle, time, integer, string
  • bit, logic
  • 由bit, logic组成的packed数组,结构体或联合体。在外部语言侧,所有packed类型数据都被译成1维的数组。
  • 枚举类型被解释成同枚举本身相同的类型
  • 新类型可以同下面几种方法构建

    • struct
    • union (packed only)
    • Unpacked array
    • typedef

下面是一些注意事项:

  • 枚举类型不能被直接支持。代替地,枚举数据的实际类型被使用
  • SV没有规定 packed或unpacked结构体,数组的实际内存存放结构。unpacked数据和它们的打包方式有管,而打包方式和C编译器有关。
  • 在导出的DPI例程中, 不允许使用动态数组做为形参
  • SV数据类型在内存中的实际存放结构对于SV语义来讲是透明的。它和外部语言有关。但我们可以大约知道SV数据类型是如何实现的。除了unpacked array,对导入例程的形参类型并没有什么限制。SV实现方式限制了哪些unpacked arrays会被当成固定大小的数组。(虽然它也可以被传输成非固定大小的open array). 实际的变量和实现相关,而open array提供了一种实现无关的方法。

35.5.6.1 Open arrays

无论是packed维,或是unpacked维,或者两者都可以保留为空。这样的数据就叫open array. Open array提供了一种针对不同数据大小的通用解决方案。
导入函数的形参可以被描述成open array. 而导出函数不能够使用它作为形参。open array是唯一的一种放松参数匹配规则的方法。实际参数就可以处理不同大小的数据,从而达到代码通用化的目标。
虽然数组的packed部分可以是任意大小的,形参的变长维只能有一个是packed维。 这不是非常严厉的, packed类型都等价于一个一维packed数组,所以只有一个是可变的就相当于整个都可变。而unpacked维则没有限制。
如果形参有一个变长的packed维, 那么它将匹配任何packed维的实参。如果形参的unpacked维是变长的,那么它要求实参拥有一样的维数,只不过对应的具体维上长度是可变的。
下面是一些合法形参的例子:

logic
bit [8:1]
bit []
bit [7:0] array8x10 [1:10]
logic [31:0] array32xN []
logic [] arrayNx3 [3:1]
bit [] arrayNxN []

下面是导入函数声明例子

import "DPI-C" function void f1(input logic [127:0]);
import "DPI-C" function void f2(logic [127:0] i []); //open array of 128-bit

下面是利用open array来匹配不同长度实参的例子

typedef struct {int i; ...} MyType;
import "DPI-C" function void f3(input MyType i [][]);

MyType a_10x5 [11:20][6:2];
MyType a_64x8 [64:1][-1:-8];

f3(a_10x5);
f3(a_64x8);

35.6 调用导入函数

调用导入函数和使用SV原生函数是一样的。所以导入函数声明和SV原生函数是一样的规范。特别地,带有默认值的参数在调用时可以省略;如果形参命名则可以用名字来做绑定。

35.6.1 变量传递

变量传递的规则是“所见即所得"。形参的计算顺序遵循SV通用规则。
变量的兼容性和强制转换都与SV原生函数一样。如果需要进行强制转换,那么会创建临时变量,并把它传递给实际变量。对于input和inout变量, 临时变量会被初始化成强制转换后的值。对于output和inout变量,实际值会被强制转换后然后赋给临时变量。临时变量和实际值之间的赋值遵循SV的赋值和强制转换通用规则。
在SV侧, 导入函数的输入值和调用者无关。形参输出值在开始是末定义的,然后被实际值赋值(可能发生强制转换)。导入函数不会改变输入变量的原始值。
在SV侧,变量传递的语义是: input变量是"copy-in", output变量是”copy-out", inout变量是"copy-in"和“copy-out"。 这里的"copy-in"和”copy-out"不是暗示它们的实际实现方式,而是指假想的赋值。
变量传递的真正实现对SV侧是透明的。SV并不清楚它实际上是值传递还是引用传递。它的实际实现方式是由外部语言定义的。

35.6.1.1 WYSIWYG原则

WYSIWYS(所见即所得)原则保证了导入函数的形参类型:实际变量被要求要和形参完全相同,除了open array. 形参只和导入函数的声明现场有关。
没有编译器(C或者SV)可以在形参和实参类型间做强制转换。调用者的形参是被定义在另外一种语言里,所以它们相互间无法可见。用户需要理解并保证它们是匹配的类型。
open array中的可变长维度会有实参的实际长度。形参中的变长,packed的维度会拥有实际参数的范围。变长, packed维度会被归一化 ([15:8] -> [8:0]). 变长范围在调用现场才能确定。其他信息都在函数声明中定义了。

因此, 如果一个形参被定义成bit [15:8] b [], 那么它描述了一个unpacked array, 它的每个元素也是一个packed bit array, 数据绑定范围是15到8. 实际参数的长度会被绑定到它的unpacked部分。
有时候允许传递一个动态数组会为导入function和task的实参。原则同SV里传递动态数组给原生function和task。

35.6.2 output和inout变量的值变化

仿真器负责处理output和inout变量的值变化。这样的值改变会被仿真器探测到,并被返回到SV代码。
对于output和inout变量, 值传播会立即发现一旦控制权从导入函数返回。如果有多个变量返回,那么值传播的顺序遵循SV的通用规则。

35.7 导出函数

DPI允许其他语言调用SV函数。函数和变量兼容性要求和导入函数一样。声明SV函数被导出并不改变它的语义和行为;没有什么副作用除了当它被用于DPI 调用链中(比如调用者又被SV导入并调用)。

可以被其他语言调用的SV函数需要被声明为export。 声明必须在函数被定义的作用域里。每个函数只能被export声明一次。
一个重要的限制是: 类的成员函数不能被导出, 其他都可以。
同导入声明一样, 卖出声明可以定义一个可选的C标识符(用于其他语言)

dpi_import_export ::=         //from A.2.6
     ...
     | export dpi_sepc_string [c_identifier=] function function_identifier;
     ...
dpi_spec_string ::= "DPI-C" | "DPI"

35.8 导出任务

SV允许外部语言可以调用tasks. 同函数一样, 这样的task被定义为导出taks。
上面对于导出函数的所有描述都可应用于导出task。包括合法的定义作用域和可选的C标识符。

从一个导入的函数里调用一个导出的task是非法的。这个语义和原生SV语义是一致的,函数不能调用task。

只有导入的task拥有context属性时才可以被导入的task调用。

导出task和导出函数的一个不同处是SV task没有返回值。导出task的返回值是一个int类型值,用来指示是否有disable用于当前线程。
相似的,导入task也会返回一个int类型值来的说明导入task是否收到一个disable.

35.9 禁用DPI task和function

使用disable语句可以禁用正在执行的DPI调用链。当导入的例程被禁用时,C代码会遵循一个简单的关闭协议。协议会允许C代码去执行必要的资源释放清理工程,比如关闭文件句柄,关闭VPI句柄,释放堆内存。

当一个disable语句作用于某个目标或它的父范围域,那么我们就称这个导入task或function在disable状态。一个导入task或function如果调用了一个导出的task或function, 那么在进入disable状态前我必须从调用中返回。这个协议的关键之处在于被关闭的导入task或function将确认他们被关闭了。task或function可以通过svIsDisabledState()来确定它是否在disable状态。

这个协议由如下部分组成:
a) 当一个导出的task被disable时,返回1, 否则返回0;
b) 当一个导入的task被disable时,返回1, 否则返回0
c) 当导入的function被disable时, 它将会先调用svAckDisabledState()
d) 一旦导入的task或function进入disable状态,它将不允许再调用任何导出的task或function

b), c), d)是强制行为。DPI的输写者需要确保正常的行为。
a)是由SV仿真器来保证的。此外仿真器也会检查b), c), d)是否被遵守。如果任何协议没被遵守,仿真器应该提示致命错误。

DPI的另外一侧包含一个禁用协议,该协议由用户代码与仿真器一起实现。禁用协议允许外部模型参与SystemVerilog禁用处理。参与方法是通过DPI task的特殊返回值和特殊API调用来完成。
特殊的返回值不需要更改SV代码中导入或导出DPI task的调用语法。虽然仿真器保证了导出task的返回值,但对于导入task,DPI另一侧必须确保返回正确的值。
对导入task的调用与对SV原生task的调用是无法区分的。同样,对代码中的导出task的调用与对非SV task的调用是无法区分的。
如果导出的task本身是禁用的目标,则当导出task返回时,其父项导入的task不被视为处于disable状态。在这种情况下,导出的任务应返回值0,并且对svIsDisabledState()的调用也应返回0。
当DPI导入的子例程由于被禁用而返回时,其输出和inout参数的值未定义。同样,当导入的函数由于禁用而返回时,函数返回值是不确定的。 C程序员可以从禁用的函数中返回值,而C程序员可以将任意值写入导入例程的output和inout参数位置。但是,如果禁用有效,SV仿真器没有义务将任何此值传播到调用SystemVerilog代码中。

查看原文

赞 0 收藏 0 评论 0

harriszh 发布了文章 · 2020-04-28

gdb深入学习手册

总述

gdb虽然只是一个调试器,但如何要用好它,必须深刻理解linux下程序是如何编译运行的,建议先深入了解相关内容再开始调试程序。
本文只局限对gdb的使用上,后面有机会再介绍关于linux下程序编译运行相关的知识。

调试的目的

关于调试目的,大家第一印象肯定是发现程序问题。这的确是最主要的目的。另外一点是帮助我们理解他人写的程序。对我来说这也非常有用。如果发现程序有问题,有时不防试试画一个流程图,边理解程序边调试问题。

基本知识

Stack(栈)

当调试的程序暂停时,你首先想知道的是它停在了什么地方,以及它是怎样到达那里的。

调试的程序每调用一个函数,就会产生关于这个调用的相关信息。这些信息包括调用前的位置、传递的参数、以及被调用函数的局部变量。这些信息保存在一个名为栈帧(stack frame)的数据块中。栈帧是在内存中分配的成为“调用栈”(call stack)的一块区域。

当程序暂停的时候,gdb中一些检查栈的命令允许你查看这些所有的信息。

gdb和gdb命令会选择其中的一个栈帧作为“当前栈帧”。不管什么时候用gdb来显示程序中的变量的值,这个变量总是指当前帧中的变量。有一些特殊的gdb命令可以让你选择你感兴趣的帧。

被调试的程序暂停时,gdb自动选择当前程序在其中运行的帧,并对其作一简短描述,类似于frame命令。

Frames(帧)

调用栈是被在内存中分割成一些连续的相邻的数据块,它们被称为“栈帧”,简称“帧”。每个帧都包含有调用一个函数的相关数据,包括被调用函数的局部变量和被调用函数的地址等等。

当程序运行时,栈里只有一个帧,即main函数的帧,它被称为初始化帧或者最层的帧。每调用一个函数就会产生一个新的帧,当函数返回时,相应的帧就会被删除。如果函数是递归调用的,则可能产生很多针对调用这个函数的帧。每次实际调用一次这个函数,产生的帧被成为“最内层的帧”。这是所有帧里最新产生的。

在程序中,帧是用地址来标识的,每个帧都有很多个字节,每个字节都有自己的地址。每种计算机都有各自不同的约定来选择一个代表帧地址的字节。通常这个地址是记录在名为“帧指针”$FP(Frame Pointer Register)的一个寄存器里。

gdb将每个存在的栈帧赋予了一个数字,从0开始,编号为0的帧代表最内层的帧,编号为1的帧代表调用它的帧,以此类推。这些数字编号实际上并不存在于被调试的程序中,它们只是由gdb赋予的值,以便在gdb命令里可以方便地标识帧。

一些编译器提供了一种使程序不使用栈帧的编译方法(例如gcc编译器使用-fomit-frame-pointer选项时就可以产生不使用帧的函数)。 这只是偶尔用在大量的库函数调用时的一种方法,以便程序可以节省建立帧的时间。如果最内层的帧没有栈帧的话,gdb还是可以像往常一样认为它有一个单独的帧并标识为编号为0,以便正确地对函数调用链进行跟踪。但是gdb没有办法对其它的函数调用帧进行标识。

一. 启动gdb

1.1 几种方法

gdb program
gdb program core
gdb program
gdb --args gcc -O2 -c foo.c
gdb --silent

上面的gdb --args gcc -O2 -c foo.c将调试gcc, 并把-O2 -c foo.c作为参数传递给gcc

1.2 参数

-symbol file
-s file

-exec file
-e file

-se file          #read symbol table from file file and use it as the executable file

-core file
-c file

-pid number
-p number

-command file
-x command

-eval-command command
-ex command         #execute a signal GDB command

-init-comamnd file
-ix file

-init-eval-command comand
-iex command

-directory directory
-d directory            # add directory to the path to search for the source and scrip files

1.3 shell command

如果要执行shell comands, 可以用 shell 开头就行

shell command-string
!command-string

如果是make, 则可以直接输入make make-args

1.4 log

如果你想把GDB命令输出到一个文件有,有几种方法控制

set logging on
set logging off
set logging file <filename>
set logging overwrite [on|off]     #默认会追加到logfile里
set logging redirect [on|off]        #默认GDB输出会在terminal和logfile里显示,用redirect让它只在logfile里显示
show logging

二. 使用帮助

主要有两个命令

2.1 help

如果知道命令名, 直接输入help <command_name>

2.2 apropos

如果不记得命令名,可以搜索关键字, apropos <regexp>

(gdb) apropos args
advance -- Continue the program up to the given location (same form as args for break command)
collect -- Specify one or more data items to be collected at a tracepoint
handle -- Specify how to handle a signal
info args -- Argument variables of current stack frame
l -- List specified function or line
list -- List specified function or line
r -- Start debugged program
run -- Start debugged program
set args -- Set argument list to give program being debugged when it is started
show args -- Show argument list to give program being debugged when it is started
u -- Execute until the program reaches a source line greater than the current
until -- Execute until the program reaches a source line greater than the current

三. 运行

包含三个命令:
run : 开始运行
start : 运行并停在main函数上
continue :继续运行
ignore : 忽略某函数或文件
checkpoint: 设置书签

2.1 start

后面可以跟参数, 如start 1 2 3

2.2 ignore

忽略某函数或文件
在调试程序中可能会不关心某个函数或文件,这时可以用skip来略过他们。
语法如下:

skip -file <file>
skip -gfile <file_glob_pattern>
skip -function <linespec>
skip -rfunction <regexp>
info skip [range]

<linespec>是一个冒号分隔的列表,它可以包括文件名, 函数名。(filename:linenum, function:label, filename:function, label)

示例如下:

(gdb) skip -rfu ^std::(allocator|basic_string)<.*>::~?\1 *\(

(gdb) skip -rfu ^std::([a-zA-z0-9_]+)<.*>::~?\1 *\(

第一个命令忽略std::string的构造和解构函数
第二个命令忽略所有来自std namespace的函数的构造和解构函数

2.3 checkpoint

gdb可以在程序执行的过程中保留快照(状态)信息,称之为checkpoint,可以在进来返回到该处再次查看当时的信息,比如内存、寄存器以及部分系统状态。通过设置checkpoint,万一调试的过程中错误发生了但是已经跳过了错误发生的地方,就可以快速返回checkpoint再开始调试,而不用重启程序重新来过。

2.3.1 checkpoint

对当前执行状态保存一个快照,gdb会自动产生一个对应的编号供后续标识使用。从提示信息看来,其实每建立一个checkpoint,都会fork出一个子进程,所以每个checkpoint都有一个唯一的进程ID所对应着。

2.3.2 info checkpoints

查看所有的checkpoint,包括进程ID、代码地址和当时的行号或者符号。

2.3.3 restart checkpoint-id

恢复程序状态到checkpoint-id指明的checkpoint,所有的程序变量、寄存器、栈都会被还原到那个状态,同时gdb还会将时钟信息回退到那个点。恢复过程中程序的状态和系统的部分状态是支持的,比如文件指针等信息,但是写入文件的数据、传输到外部设备的数据都无法被回退。

2.3.4 delete checkpoint checkpoint-id

删除指定的checkpoint。

此外,checkpoint的作用还在于断点、观测点不是什么情况下都可用的情况下,因为Linux系统有时候为了安全考虑,会随机化新进程的地址空间,这样重启调试程序会导致之前使用绝对地址设置的断点、观测点不可用。

四. 断点

在一个位置上设置断点,可以对应多个位置,gdb要自动在需要的位置插入断点。在动态库里也可以设置断点,不过其地址在加载后才能解析。
断点的设置有几种方法

break
b
break [Function Name]
break [File Name]:[Line Number]
break [Line Number]
break *[Address]
break [...] if [Condition]
break [...] thread [Thread-id]
b [...]
rbreak [regexp]
rbreak [File Name]:[regexp]
tbreak [args]

4.1 Function name和Address

不要混淆[Function name]和[Address], 比如你想在地址0x40138c上设定断点,如果用

gdb> break 0x4007d9

会失败, GDB会0x40138c解释成函数而不是地址。正确的做法是

(gdb) break *0x4007d9

可以用下面命令得到相应行的地址:

(gdb) i line gdbprog.cc:14
Line 14 of "gdbprog.cc" starts at address 0x4007d9 <InitArrays(int*)+17> and ends at 0x4007f7 <InitArrays(int*)+47>.

或者

(gdb) disas /m InitArrays
Dump of assembler code for function InitArrays(int*):
8    void InitArrays(int* array)
  0x00000000004007c8 <+0>:    push  %rbp
  0x00000000004007c9 <+1>:    mov    %rsp,%rbp
  0x00000000004007cc <+4>:    mov    %rdi,-0x18(%rbp)

9    {
10    
11    
12        for(int i = 0;i < 10;i++)
  0x00000000004007d0 <+8>:    movl  $0x0,-0x4(%rbp)
  0x00000000004007d7 <+15>:    jmp    0x40080a <InitArrays(int*)+66>
  0x0000000000400807 <+63>:    incl  -0x4(%rbp)
  0x000000000040080a <+66>:    cmpl  $0x9,-0x4(%rbp)
  0x000000000040080e <+70>:    jle    0x4007d9 <InitArrays(int*)+17>

13        {
14            ptrArray[i] = array + i;
  0x00000000004007d9 <+17>:    mov    -0x4(%rbp),%ecx
  0x00000000004007dc <+20>:    mov    -0x4(%rbp),%eax
  0x00000000004007df <+23>:    cltq  
  0x00000000004007e1 <+25>:    shl    $0x2,%rax
  0x00000000004007e5 <+29>:    mov    %rax,%rdx
  0x00000000004007e8 <+32>:    add    -0x18(%rbp),%rdx
  0x00000000004007ec <+36>:    movslq %ecx,%rax
  0x00000000004007ef <+39>:    mov    %rdx,0x6013e0(,%rax,8)

15            iArray[i] = i;
  0x00000000004007f7 <+47>:    mov    -0x4(%rbp),%eax
  0x00000000004007fa <+50>:    movslq %eax,%rdx
  0x00000000004007fd <+53>:    mov    -0x4(%rbp),%eax
  0x0000000000400800 <+56>:    mov    %eax,0x6013a0(,%rdx,4)

16        }
17    }
  0x0000000000400810 <+72>:    leaveq 
  0x0000000000400811 <+73>:    retq  

End of assembler dump.

4.2. Pending breakpoints

如果试图将断点设在位于还未加载的shared library代码内,那么就会显示类似下面warning

Make breakpoint pending on future shared library load? (y or [n])

但它可能永远不会工作,如果是下列情况:

  • shared library不包含任何debugging symbols (被用‘strip’命令移除掉)
  • GDB无法检测到library被加载 (比如,在android上使用GDB 6.x)
  • 你输入的是一个错误文件名或函数名

所以可以用下面几个命令来做调试

  • info sharedlibrary : 目前加载的shared library
  • info sources : 被GDB识别的源文件
  • info breakpoints : 创建的断点和它们的状态

4.3. 条件断点

大约有以下几种形式

break main if argc > 1
break 180 if (string == NULL && i < 0)
break test.c:34 if (x & y) == 1
break myfunc if i % (j+3) != 0
break 44 if strlen(mystring) == 0
b 10 if ((int)$gdb_strcmp(a,"chinaunix") == 0)
b 10 if ((int)aa.find("dd",0) == 0)

经常犯的一个错误就是 在if和后面的(之间没放空格
另外注意条件表达式的返回值类型是int

当设置了断点后, 后面可以使用condition和ignore来修改这个断点条件

4.4. condition

condition <break_list> (condition)

比如

cond 3 (i==4)

上面命令把breakpoint 3的条件修改成(i==4)

4.5. ignore

ignore <break_list> count

上面命令表示break_list指定的断点将被忽略count次
比如现在i=0, 设置i>4为条件,那么忽略3次后,它停在i=8, 忽略了i=5, 6, 7

4.6. commands

commands <break_list>

当断点被触发时,运行相应的命令
可以用info breakpoints来查看相应断点上附着的命令

(gdb) i b
Num    Type          Disp Enb Address            What
3      breakpoint    keep y  0x00000000004007d9 in InitArrays(int*) at gdbprog.cc:14
    stop only if (i>4)
    breakpoint already hit 5 times
        silent
        printf "hz: i=%d\n",i
        c
7      breakpoint    keep y  0x0000000000400962 in main() at gdbprog.cc:51
    breakpoint already hit 1 time

一个断点上只允许附着一条命令,再次调用commands 3后会覆盖掉前面的命令
因此如果要删除相应的命令,只需用空的commands 3就行了

4.7. define

用define可以录制宏,这些宏就像shell脚本一样,可以传入参数,依次是$arg0, $arg1, ...
可以把这些宏放在.gdbinit文件中
录制好宏后就可以在commands中使用
宏并不支持所有GDB命令,比如silent就不能用于宏中, 它会在运行时报错: Undefined commands: "silent"

(gdb) define printAndGo
Type commands for definition of "printAndGo".
End with a line saying just "end".
>printf "i=%d\n",$arg0
>cont
>end
(gdb) commands 3
Type commands for breakpoint(s) 3, one per line.
End with a line saying just "end".
>silent
>printAndGo
>end
(gdb) i b
Num    Type          Disp Enb Address            What
3      breakpoint    keep y  0x00000000004007d9 in InitArrays(int*) at gdbprog.cc:14
    stop only if (i>4)
        silent
        printAndGo i
7      breakpoint    keep y  0x0000000000400962 in main() at gdbprog.cc:51

如果要查看这些宏的定义,可以用show user来查看

(gdb) show user
User command "pbuf":
  set $i=0
  while ($i<strlen(netiring.supply))
    print netiring.supply[$i]
    set $i=$i+1
  end

User command "pobuf":
  set $i=0
  while ($i<strlen(netoring.consume))
    print netoring.consume[$i]
    set $i=$i+1
  end

4.8 tbreak

只生效一次临时断点

4.9 rbreak

在所有匹配regexp的函数名处都设置断点。它的regexp和grep相同

4.10 管理

clear localtion
location可以是function, file:func, linenum, file:linenum
delete [breakpoints] [range...]
[breakpoints]是可选项
disable|enable [breakpoints] [range...]
禁用|启用断点
enable [breakpoints] once range...
启用断点一次
enable [breakpoints] once range...
雇用断点cnt次
enable [breakpoints] delete range...
临时启用断点,一旦被激活就会把删除,和tbreak相似

五. 检查数据

pirnt
x
display
set
watch
catch
tracepoint

5.1 print

print接受表达式和计算它的值。任何该语言支持常值,变量和操作符都可以使用,像条件表达式,函数调用,类型转换,字符常量。GDB还支持数组常量,语法是{element, element...}, 比如print {1,2,3}.GDB支持还支持下面操作符

  • @

二进制操作符, 可以把momery当成数组。

int *array = (int*) malloc( len * sizeof(int));

可以使用下面命令来打印它的值:

(gdb) p *array@len
(gdb) p/x (short[2])0x12345678
$1 = {0x1234, 0x5678}

另外还可以自定义变量

set $i = 0
p dtab[$i++]->fv
RET
RET

上面的RET表示重复上面的表达式

  • ::

定义属于某个文件或函数的变量

  • {type} addr

把addr的变量按{type}类型解释

5.2 x

x/nfu addr
x addr
x

n, the repeat count
f, the display format (x, d, u, o, t, a, c, f, s, i)
u, the unit size (b: bytes, h: halfwords, w: words, g: giant words)

(gdb) x/5i $pc-6
  0x804837f <main+11>: mov    %esp,%ebp
  0x8048381 <main+13>: push  %ecx
  0x8048382 <main+14>: sub    $0x4,%esp
=> 0x8048385 <main+17>: movl  $0x8048460,(%esp)
  0x804838c <main+24>: call  0x80482d4 <puts@plt>

5.3 display

如果你发现你经常要打印某个表达式,你可以把它加入到"automatic display list"

display expr
display/fmt expr
display/fmt addr
undisplay <dnums>
delete display dnums
disable display dnums
enable display dnums

5.4 set

可以使用set <var>=<expr>来修改程序中变量的值

5.5 数值历史(value Hisitory)

通过使用print命令显示的值会被自动保存在GDB的数值历史当中,该值会一直被保留直到符号表被重新读取或者放弃的时候(比如使用file或symbol-file),此时所有的值历史将会被丢弃。在使用print打印值的时候,会将值编以整形的历史编号,然后可以使用&dollar;num的方式方便的引用,单个的&dollar;表示最近的数值历史,而&dollar;&dollar;表示第二新的数值历史,&dollar;&dollar;n表示倒数第n新的数值历史(所以可以推断&dollar;&dollar;0==&dollar;; &dollar;&dollar;1==&dollar;&dollar;;)。
  比如刚刚打印了一个指向结构体的指针,那么print $就可以显示结构体的信息,而命令print \$.nex甚至可以显示结构体中的某些字段。

5.6 Convenience变量

GDB允许自由创建便捷变量用于保存值,后续可以方便的引用该值,该类型的变量由GDB管理而与正在调试的程序没有直接的关联。便捷变量也是使用符号$打头的,可以使用任意名字(除了GDB使用的寄存器名)。
在任意时候使用一个便捷变量都会创建他,如果没有提供初始化值那么该变量就是void,直到给其进行赋值操作为止。便捷变量没有固定的类型,可以为普通变量、数组、结构体等,而且其类型可以在赋值过程中改变(为当前值的类型)。
show convenience|conv
显示道目前所有的便捷变量和便捷函数,以及其值。
init-if-undefined $variable = expr
如果该变量还没有被创建或初始化,则创建这个变量。如果该变量已经被创建了,则不会创建和初始化该变量,并且expr表达式也不会被求值,所以这种情况下也不会有expr的副作用发生。
便捷变量的一种经典用法,就是之前提到的连续查看变量时候用于计数作用:

set $i = 0
print bar[$i++]->contents

下面的一些便捷变量是GDB自动创建的:

  • &dollar;_exitcode

当调试程序终止的时候,GDB将会根据程序的退出值自动设置该变量,并且将&dollar;_exitsignal变量设置为void。

  • &dollar;_exitsignal

当调试中的程序因为一个未捕获信号而终止,此时GDB会自动将变量&dollar;_exitsignal设置为该信号,同时重置变量&dollar;_exitcode为void。

5.7 Convenience函数

便捷函数和便捷变量一样使用&dollar;打头引用,其可以像一个普通函数一样在表达式中使用,便捷函数只被GDB内部使用。`

(gdb) print $_isvoid ($_exitsignal)

&dollar;_isvoid (expr): 如果expr是void则返回1,否则返回0。
GDB还有很多的便捷函数支持,但是需要在编译GDB的时候提供Python的支持才可以使用。下面的这些函数意义显而易见,就不在啰嗦了。
&dollar;_memeq(buf1, buf2, length); &dollar;_regex(str, regex); &dollar;_streq(str1, str2); &dollar;_strlen(str)

5.8 watch

监视点是监视特定内存位置、特定表达式的值是否改变而触发程序暂停执行,而不用去关心该值到底在代码的哪个位置被修改的。监视的表达式可以是:某个变量的引用、强制地址解析(比如(int )0x12345678,你无法watch一个地址,因为地址是永远也不会改变的)、合理的表达式(比如a-b+c/d,gdb会检测其中引用的各个变量)。

  watch [-l|-location] expr [thread thread-id] [mask maskvalue]

thread-id可以设置特定线程改变expr的时候触发中断,默认情况下针对硬件模式的检测点所有的线程都会检测该表达式;-location会让gdb计算expr的表达式,并将计算的结果作为地址,并探测该地址上的值(数据类型由expr计算结果决定)。
很多平台都有实现监视点的专有硬件,如果没有GDB也会采用虚拟内存技术来实现监视点,而如果前面两种技术都不可用,则GDB会采用软件实现监事点本身。软件模式速度会非常慢,因为如果不支持硬件模式gdb会每次step并计算检测的表达式,x86构架支持硬件模式,而且监视点的作用也有限,只能当前单个线程能侦测其变化,虽然该值也可能会被其他线程修改。
监视点在跟踪那种变量不知道哪里被修改的情形特别的有效。不过监视点需要变量在当前上下文可访问的情况下才可以使用,所以在使用中会通常配合断点先到达事发的附近位置,然后再创建对应的监测点。
watch命令还存在两个变体:rwatch当expr被程序读的时候触发中断;awatch会在程序读取或者写入expr的时候被中断。rwatch和awatch只支持硬件模式的检测点

5.9 catch

可以让gdb在某些事件发生的时候暂停执行,比如C++抛出异常、捕获异常、调用fork()、加载动态链接库以及某些系统调用的时候,其格式为catch event,还有一个变体tcatch event设置临时捕获点,其中event的参数可以为:

throw|rethrow|catch|exec|fork|vfork|syscall|exception|unhandled|assert

在C++异常抛出、重新抛出、捕获的时候触发,可选使用regex参数限定特定的异常类型(在gcc-4.8开始支持),内置变量&dollar;_exception会记录在catchpoint激活时候的异常。
当异常发生时候,程序通常会停留在libstdc++的异常支持点,此时可以通过使用up命令切换帧跳转到用于异常代码处。
syscall如果不带参数,则捕获任意系统调用。

syscall [name | number | group:groupname | g:groupname] …

在进入和/或返回系统调用的时候触发。name可以指明catch的系统调用名(定义在/usr/include/asm/unistd.h,且gdb会帮助智能补全),group|g:groupname可以用来指定一组类别的系统调用,比如g:network,通过智能补全可以查看支持的group信息。

load|unload [regex] : 加载和卸载共享库时候触发,可选regex进行过滤。

signal [signal… | ‘all’]

可以在软件收到信号的时候触发。gdb本身会占用SIGTRAP和SIGINT两个信号,如果不添加额外参数,会catch除了这两个信号之外的所有信号。
使用info break命令,watchpoint的信息会被展示出来,可以像普通断点一样管理之。

五. Stack命令

主要包含以下几个

bt
frame
info frame
where

5.1 bt

bt
bt [Frame count]
bt full

可以使用负数, -1是第一层,-2是第二层

(gdb) backtrace 
#0 level0 () at recursion.cpp:5
#1 0x08048462 in test (level=0) at recursion.cpp:17
#2 0x0804845b in test (level=1) at recursion.cpp:14
#3 0x0804845b in test (level=2) at recursion.cpp:14
#4 0x0804845b in test (level=3) at recursion.cpp:14
#5 0x0804845b in test (level=4) at recursion.cpp:14
#6 0x0804845b in test (level=5) at recursion.cpp:14
#7 0x08048479 in main () at recursion.cpp:22
(gdb) backtrace -2
#6 0x0804845b in test (level=5) at recursion.cpp:14
#7 0x08048479 in main () at recursion.cpp:22

5.2 frame

frame是非常有用的命令,它可以用来显示当前帧的信息
基本语法是

frame
frame [Frame number]
f

如果没有参数,就是当前行的信息

(gdb) frame
#0 level0 () at recursion.cpp:5
5 printf("Reached level 0\n");
(gdb) info args
No arguments.
(gdb) frame 1
#1 0x08048462 in test (level=0) at recursion.cpp:17
17 level0();
(gdb) info args
level = 0
(gdb) frame 2
#2 0x0804845b in test (level=1) at recursion.cpp:14
14 test(prevLevel);
(gdb) info args
level = 1

可以用info line *$riplist *$rip获得类似当前行的信息

5.3 info frame

语法如下:

info frame
info frame [addr]

相比直接的frame, 这个命令输出更详细的stack frame信息,包括

  • frame的地址
  • 下一层frame的地址
  • 上一层frame的地址(caller)
  • 当前frame是用什么语言所写
  • frame的变量地址
  • frame的local变量
  • pc值
  • 在当前frame里保存的registers
(gdb) i frame
Stack level 0, frame at 0x7fffffffe250:
rip = 0x4009e0 in PrintArray (gdbprog.cc:40); saved rip 0x400a3f
called by frame at 0x7fffffffe280
source language c++.
Arglist at 0x7fffffffe240, args: 
Locals at 0x7fffffffe240, Previous frame's sp is 0x7fffffffe250
Saved registers:
  rbp at 0x7fffffffe240, rip at 0x7fffffffe248

(gdb) bt
#0  PrintArray () at gdbprog.cc:40
#1  0x0000000000400a3f in main () at gdbprog.cc:57
(gdb) i f 0x0000000000400a3f
Stack frame at 0x400a3f:
rip = 0x0; saved rip 0x7ffff7ff9720
called by frame at 0x7fffffffe240
Arglist at 0x7fffffffe228, args: 
Locals at 0x7fffffffe228, Previous frame's sp is 0x7fffffffe238
Saved registers:
  rip at 0x7fffffffe230

六. 程序信息

主要包含

info proc
info variables
info functions
info source
info sources
info sharedlibrary
info flies

6.1 info proc

info proc all可以打印出所有进程相关信息

info proc mappings显示正在运行的进程中映射的内存区域的列表。与 /prod/pid/maps 的输出相同

6.2 info functions

列出程序中的函数
语法如下:

info functions
info functions [Regex]

示例如下:

(gdb) info functions
All defined functions:

File gdbprog.cc:
int DoOperation(int**);
void InitArrays(int*);
void PrintArray();
int main();
static void __static_initialization_and_destruction_0(int, int);
static void __tcf_0(void*);
static void global constructors keyed to iArray();

Non-debugging symbols:
0x0000000000400660  _init
0x0000000000400688  std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
0x0000000000400688  _ZNSolsEi@plt
0x0000000000400698  std::ios_base::Init::Init()
...

6.3 info source

显示当前源文件名

info sources

显示加载的symbols涉及的源文件

(gdb) info sources
Source files for which symbols have been read in:

<<C++-namespaces>>, /home/harriszh/cpp/e1/gdbprog.cc, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/locale_facets.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/x86_64-redhat-linux/bits/ctype_base.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/stl_iterator.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/allocator.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/ext/new_allocator.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/stringfwd.h, /usr/include/bits/pthreadtypes.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/x86_64-redhat-linux/bits/gthr-default.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/locale_classes.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/basic_string.tcc, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/basic_string.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/limits, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/x86_64-redhat-linux/bits/atomic_word.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/ios_base.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/iosfwd, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/new, /usr/include/wctype.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/cwctype, /usr/include/stdlib.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/cstdlib, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/cwchar, /usr/include/time.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/ctime, /usr/include/locale.h, 
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/clocale, /usr/include/wchar.h, /usr/include/_G_config.h, 
/usr/include/bits/types.h, /usr/include/libio.h, /usr/include/stdio.h, 
...

6.4 info files

显示执行文件名称和加载的段

(gdb) i file
Symbols from "/home/harriszh/trunk/cpp/exercise/gdbprog".
Unix child process:
    Using the running image of child process 42226.
    While running this, GDB does not access memory from...
Local exec file:
    `/home/harriszh/trunk/cpp/exercise/gdbprog', file type elf64-x86-64.
    Entry point: 0x4007f0
    0x0000000000400200 - 0x000000000040021c is .interp
    0x000000000040021c - 0x000000000040023c is .note.ABI-tag
    0x000000000040023c - 0x0000000000400260 is .note.gnu.build-id
    0x0000000000400260 - 0x0000000000400294 is .gnu.hash
    0x0000000000400298 - 0x00000000004003e8 is .dynsym
    0x00000000004003e8 - 0x0000000000400592 is .dynstr
    0x0000000000400592 - 0x00000000004005ae is .gnu.version
    0x00000000004005b0 - 0x0000000000400600 is .gnu.version_r
    0x0000000000400600 - 0x0000000000400630 is .rela.dyn
    0x0000000000400630 - 0x0000000000400720 is .rela.plt
    0x0000000000400720 - 0x0000000000400738 is .init
    0x0000000000400738 - 0x00000000004007e8 is .plt
    0x00000000004007f0 - 0x0000000000400ba8 is .text
    0x0000000000400ba8 - 0x0000000000400bb6 is .fini
    0x0000000000400bb8 - 0x0000000000400bf4 is .rodata
    0x0000000000400bf4 - 0x0000000000400c40 is .eh_frame_hdr
    0x0000000000400c40 - 0x0000000000400d84 is .eh_frame
    0x0000000000601000 - 0x0000000000601018 is .ctors
    0x0000000000601018 - 0x0000000000601028 is .dtors
    0x0000000000601028 - 0x0000000000601030 is .jcr
    0x0000000000601030 - 0x00000000006011f0 is .dynamic
    0x00000000006011f0 - 0x00000000006011f8 is .got
    0x00000000006011f8 - 0x0000000000601260 is .got.plt
    0x0000000000601260 - 0x0000000000601264 is .data
    0x0000000000601280 - 0x0000000000601438 is .bss
    0x000000347b8001c8 - 0x000000347b8001ec is .note.gnu.build-id in /lib64/ld-linux-x86-64.so.2
    0x000000347b8001f0 - 0x000000347b8002a8 is .hash in /lib64/ld-linux-x86-64.so.2
...

6.5 info sharedlibrary

显示共享库及它们的地址
语法如下:

info sharedlibrary
info shared

示例如下:

(gdb) info sharedlibrary 
From To Syms Read Shared Object Library
0xb7fde820 0xb7ff6b9f Yes /lib/ld-linux.so.2
0xb7fd83a0 0xb7fd84c8 Yes /home/testuser/libtest/libTest.so
0xb7e30f10 0xb7f655cc Yes /lib/i386-linux-gnu/libc.so.6
...

6.6 set stop-on-solib-events command

当一个shared library加载或卸载时,是否暂停

set stop-on-solib-events 0
set stop-on-solib-events 1
show stop-on-solib-events

默认是不暂停, 如果想暂停设成1

七. 源文件

主要命令是

list
info line
disassemble
set disassemble-next-line
set disassemble-flavor

7.1 list

查看当前运行源代码
语法如下:

list <linenum>
list <first>, <last>
list <function>
list
list -
list *<addr>

示例如下:

(gdb) list *$pc
0x4009e0 is in PrintArray() (gdbprog.cc:40).
35    
36    void PrintArray()
37    {
38        int i;
39        
40        for(i = 0;i < 10;i++)
41        {
42            if (iArray[i] == 6)
43                cout << "This is an error\n";
44            else

7.2 info line

基本语法是

info line [File]:[Line]
info line [Function]
info line [File]:[Function]
info line *[Address]

比如:

(gdb) info line test.cpp:5
Line 5 of "test.cpp" starts at address 0x80483f0 <main(int, char**)+3> and ends at 0x80483f5 <main(int, char**)+8>.
(gdb) info line *0x80483f0
Line 5 of "test.cpp" starts at address 0x80483f0 <main(int, char**)+3> and ends at 0x80483f5 <main(int, char**)+8>.
(gdb) info line *0x80483f6
Line 6 of "test.cpp" starts at address 0x80483f5 <main(int, char**)+8> and ends at 0x80483f7.
(gdb) info line main
Line 4 of "test.cpp" starts at address 0x80483ed <main(int, char**)> and ends at 0x80483f0 <main(int, char**)+3>.

7.3 disassemble

显示某个函数或某函数中一段代码的汇编
语法如下:

disassemble
disassemble [Function]
disassemble [Address]
disassemble [Start],[End]
disassemble [Function],+[Length]
disassemble [Address],+[Length]
disassemble /m [...]
disassemble /r [...]

/m 表示 把源代码和汇编一一对应显示出来,在新版本中建议中/s, 它会考虑compiler优化
/r 表示 显示汇编指令的原生字节码

set disassemble-next-line

控制断点暂停或step后是否显示下一行的汇编,默认是off
语法如下:

set disassemble-next-line on
set disassemble-next-line off
show disassemble-next-line

示例如下:

(gdb) set disassemble-next-line on
(gdb) next
4 }
=> 0x080483f8 <func+11>: 5d pop %ebp
0x080483f9 <func+12>: c3 ret 
(gdb) next
main () at test.c:9
9 }
=> 0x08048414 <main+26>: c9 leave 
0x08048415 <main+27>: c3 ret

set disassemble-flavor

控制汇编和x命令的风格,有AT&T和intel两种
语法如下:

set disassembly-flavor att
set disassembly-flavor intel
show disassembly-flavor

八. record & replay

GDB提供加州功能,可以记录程序的执行过程(execution log),然后在后续时间上进行前进、后退的程序运行。在调试目标运行时,如果接下来的执行指令在log中存在,就会进入replay mode,该模式下不会真正执行指令,而是将事件、寄存器,内存值从日志中提取出来,如果接下来的执行指令不在log中,那么GDB就会在record mode下执行,所有指令将会按归照正常方式执行,GDB会记录执行。

8.1 Record

默认的方法是使用full recording, 该模式不支持non-stop mode和异步执行模式的多线程调试。只能调试正在执行的进程

record [full| btrace bts|btrace pt]
record stop
record goto [begin|start|end|n]
info record
record delete

8.1.1 record <method>

开启进程的R&RT模式

8.1.2 record stop

停止进程的R&RT, 并将所有的execution log删除。如果在record的中间位置执行本命令,那么会从这个点开始Live调试

8.1.3 record goto

中转到指定位置, n表示execution log中的instruction的序号。
可以用info record查看

8.1.4 record save|restore file

保存和恢复execution log

8.1.5 set record full insn-number-max limit|unlimited

默认最多200,000。

8.1.6 set record full stop-at-limit on|off

当保存指令数目超过限制的话,如果on(默认), 那么GDB会在首次超过限制的地方停止;如果off,则会自动删除最旧记录

8.1.7 record delete

在replay模式下执行该命令,会删除当前指令之后的所有log, 并开始新的record

8.2 Replay

reverse-continue [ignore-count]
rc [ignore-count]
reverse-step [count]
reverse-stepi [count]
reverse-next [count]
reverse-nexti [count]
reverse-finish
set exec-direction [reverse|forward]

它们的行为如他们名字一样,这里就不再多解释

九. 变量查看

9.1 generate-core-file

产生当前运行进程的一份memory image和它的process status(register values etc...)

gcore [file]

9.2 info registers

(gdb) i registers
rax            0x601280    6296192
rbx            0x0    0
rcx            0x400    1024
rdx            0x3487ae9d18    225614667032
rsi            0x7ffff7ff8000    140737354104832
rdi            0xffffffff    4294967295
rbp            0x7fffffffe240    0x7fffffffe240
rsp            0x7fffffffe230    0x7fffffffe230
r8            0x7ffff7ff8002    140737354104834
r9            0x7ffff7ff9720    140737354110752
r10            0xcccccccccccccccd    -3689348814741910323
r11            0x246    582
r12            0x4007f0    4196336
r13            0x7fffffffe350    140737488347984
r14            0x0    0
r15            0x0    0
rip            0x4009e0    0x4009e0 <PrintArray()+88>
eflags        0x206    [ PF IF ]
cs            0x33    51
ss            0x2b    43
ds            0x0    0
es            0x0    0
fs            0x0    0
gs            0x0    0

9.3 info variable

显示全局或静态的变量

info variables
info variables [Regex]

可以用i var <varName>来查看某个全局或静态变量是在哪个文件定义的

9.4 info locals

显示当前帧的本地变量, 配合frame, up和down来使用

9.5 info args

显示当前帧的函数的参数, 配合frame,up和down来使用

十. 编辑和搜索

10.1 编辑

可以直接打开编辑器编辑源文件

edit <number>
edit <function>

10.2 搜索

forward-search <regexp>
fo <regexp>
search <regexp>
reverse-search <regex>
rev <regexp>

可以通过directory dirname ...来添加源文件路径

10.3 内存查找

使用find可以对内存进行序列字节查找

find [/sn] start_addr, +len, val1 [, val2, …]
find [/sn] start_addr, end_addr, val1 [, val2, …]

该命令会在内存中搜索var1、var2…字符序,其范围从startaddr开始,以结束地址或者长度结束。
参数s表示搜索的大小,可以是b、h、w、g(跟之前的x命令一样),如果该参数没有指定,GDB会考虑当前调试程序的语言环境确定,比如0x43这种整数就默认为w(4字节)类型;n表示打印最大搜索命中数量,默认行为是全部打印。该命令还可以搜索字符串,字符串使用双引号括住。
对于查找结果,命中数目保存在变量$numfound中,而最后一个值命令的地址保存在**$**中。

void
hello ()
{
  static char hello[] = "hello-hello";
  static struct { char c; short s; int i; }
    __attribute__ ((packed)) mixed
    = { 'c', 0x1234, 0x87654321 };
  printf ("%s\n", hello);
}

可以进行如下查找

(gdb) find &hello[0], +sizeof(hello), "hello"
0x804956d <hello.1620+6>
1 pattern found
(gdb) find &hello[0], +sizeof(hello), 'h', 'e', 'l', 'l', 'o'
0x8049567 <hello.1620>
0x804956d <hello.1620+6>
2 patterns found.
(gdb) find &hello[0], +sizeof(hello), {char[5]}"hello"
0x8049567 <hello.1620>
0x804956d <hello.1620+6>
2 patterns found.
(gdb) find /b1 &hello[0], +sizeof(hello), 'h', 0x65, 'l'
0x8049567 <hello.1620>
1 pattern found
(gdb) find &mixed, +sizeof(mixed), (char) 'c', (short) 0x1234, (int) 0x87654321
0x8049560 <mixed.1625>
1 pattern found
(gdb) print $numfound
$1 = 1
(gdb) print $_
$2 = (void *) 0x8049560

十一. 多进程

GDB对fork产生新进程的调试没有特殊的支持,当使用fork产生子进程的时候,GDB会继续调试父进程,子进程不会被阻碍而是继续执行,而此时如果子进程遇到断点后,子进程会收到SIGTRAP信号,当在非调试模式下默认会导致子进程中止。如果要调试子进程,可以让子进程启动后睡眠一段时间(或者监测某个文件是否创建),在这个空隙中使用ps查看子进程的进程号,然后再启动gdb去attach到这个进程上面去调试。
通过catch命令可以让gdb在fork/vfork/exec调用的时候暂停执行。
影响fork多进程调试还有以下几个变量:
follow-fork-mode
可选值是parent或child,指明当正在被调试的进程fork出子进程的时候,如果该值是parent,那么父进程被调试子进程正常执行,这也是默认行为;而当其为child的时候,则新创建的子进程被调试,父进程正常执行。
detach-on-fork
可选值是on或off,知名当被调试的进程fork出子进程后,是否detach某个进程还是调试他们俩,如果是on,那么子进程(或父进程,取决于上面follow-fork-mode的值)将会被detach正常执行,这是默认行为;否则两个进程都会被gdb控制,其中的一个进程被正常调试,另外一个进程被suspend住。
follow-exec-mode
gdb对于exec调用的反应,其值可以为new或same,当值为new的时候,之前fork的子进程和exec后的进程都会被保留;如果是same,则exec的进程使用之前fork的进程,exec会用新的执行镜像替代原先的可执行程序,这是默认行为。

十二. 多线程

在多线程程序中,有两种工作模式: all-stop和non-stop
all-stop
当进程中任意线程因为某种原因停止执行的时候,所有其他线程也同时被GDB停止执行。好处是程序停止的时候所有线程的状态都被保留下来了,可以切换线程查看整个程序当时的所有状态(缺点是整个业务全部阻塞暂停了)。
有些操作系统支持OS调试锁定,可以修改GDB的默认行为

  • scheduler-locking

off:不会锁定,所有线程可以自由运行;
on:当程序resume的时候,只有当前线程可以运行;
step:该模式是为单步模式优化的模式,当跟踪的时候阻止其他线程抢占当前线程。当step的时候其他线程无法运行,而在使用continue、until、finish类似指令的时候其他线程可以自由运行。除非其他线程运行时候触发了断点,否则GDB不会切换当前调试的线程。

  • schedule-multiple

该设置是针对多进程情况下的调试。
off:只有当前线程所在的进程的所有线程允许恢复执行,该off为默认值。
on:所有进程的所有线程都可以执行。

non-stop
当进程中任意线程停止时,在跟踪检查停止线程的时候其他的线程任然继续执行,其好处就是对线上系统进行最小化的干扰入侵,整个程序还是可以继续响应外部请求的。
通过下面的设置可以开启non-stop模式,一般都是在gdb开始的时候或者连接调试目标的时候设置才会生效,且在调试过程中不能切换该模式,不是所有平台都支持non-stop模式,所以即使设置了GDB还是有可能fallback到all-stop模式。

set pagination off
set non-stop on

non-stop模式的验证也十分的简单,在一个多线程的网络程序中先设置断点,此时发送一个请求那么处理线程会被停止,接下来删除该断点,再次发送一个请求,看该请求是否得到正常响应。如果non-stop正常开启生效的话,此时的info threads会显示其他线程都是running的状态。
non-stop模式下的命令默认都是针对当前线程的,如果要针对所有线程,通常需要使用-a参数,比如continue -a

十三. 针对语言的支持

13.1 对C的特殊支持

C语言预处理宏
宏本身是个比较麻烦的东西,因为可以某些点定义、某些点取消定义、某些点重新定义,GDB支持对含有宏的表达式进行展开并显示展开后结果的功能。如果要让编译后的程序具有宏调试功能,需要额外的编译参数-gdwarf-2和-g3(-g是不够的)

 gdb gcc -gdwarf-2 -g3 sample.c -o sample

上面的gdwarf-2具有gdwarf-3、gdwarf-4等版本,推荐支持的情况下使用最新版本的调试格式。
macro exp|expand expression
显示expression中所有宏被展开后的结果。因为GDB只会进行宏展开而不会对表达式取值,所以这里的expression不需要是合法的表达式。
info macro [-a|-all] [–] macro
显示当前的或者全部的macro宏定义,同时显示该宏定义所在的源代码文件及其所在行号位置信息。其中的–表示参数列表结束,因为C中有些宏是可以使用-开头的,这里用于消除歧义作用。
info macros location
显示所有在location位置有效的宏定义及他们的位置信息。(显示结果有点多哦)
macro define macro replacement-list
macro define macro(arglist) replacement-list
自定义宏及其展开式,通过该命令创建的宏会在GDB求值的每个表达式中都会生效(只对GDB本身作用,与代码中使用的宏无关),直到其显式使用macro undef进行删除,同时该宏还会覆盖程序中同名宏的展开(有点诡异)。
macro undef macro
只会删除上面使用macro define定义的宏,而不会删除程序代码中定义的宏。
macro list
显示所有通过macro define定义的宏。
除了通常在源代码中执行宏定义,还可以在编译的时候在命令行中通过‘-Dname=value’的方式定义,这样的宏也支持使用info macro命令查看,不过其行号信息显示为0。

查看原文

赞 0 收藏 0 评论 0

harriszh 发布了文章 · 2020-02-27

grep里的正规表达式

选项

grep -[acinv] '搜索内容串' filename
-e: 一次只能有一个选项,比如 grep -e "abc" -e "def"
-E: 正则表达式
-v: 取反
-a 以文本文件方式搜索
-c 计算找到的符合行的次数
-i 忽略大小写
-n 顺便输出行号
-h 查询多文件时不显示文件名。
-l 查询多文件时只输出包含匹配字符的文件名。
-s 不显示不存在或无匹配文本的错误信息。
-w 选项表示匹配整词,即以模式的字面意思去解析它。
-x 选项是匹配整行,即只有当文件中有整行内容与模式匹配时,grep命令才输出改行结果。

正则元字符

^ 锚定行的开始 如:'^grep'匹配所有以grep开头的行。
$ 锚定行的结束 如:'grep$'匹配所有以grep结尾的行。
. 匹配一个非换行符的字符 如:'gr.p'匹配gr后接一个任意字符,然后是p。

  • 匹配零个或多个先前字符 如:'*grep'匹配所有一个或多个空格后紧跟grep的行。

.*一起用代表任意字符。
[] 匹配一个指定范围内的字符,如'[Gg]rep'匹配Grep和grep。
[^] 匹配一个不在指定范围内的字符,如:'[^A-FH-Z]rep'匹配不包含A-R和T-Z的一个字母开头,紧跟rep的行。
/(../) 标记匹配字符,如'/(love/)',love被标记为1。
/< 锚定单词的开始,
/> 锚定单词的结束,如'grep/>'匹配包含以grep结尾的单词的行。
x/{m/} 重复字符x,m次,如:'o/{5/}'匹配包含5个o的行。 x/{m,/} 重复字符x,至少m次,如:'o/{5,/}'匹配至少有5个o的行。
x/{m,n/} 重复字符x,至少m次,不多于n次,如:'o/{5,10/}'匹配5--10个o的行。
/w 匹配文字和数字字符,也就是[A-Za-z0-9_],如:'G/w*p'匹配以G后跟零个或多个文字或数字字符,然后是p。
/W /w的反置形式,匹配一个或多个非单词字符,如点号句号等。
/b 单词锁定符,如: '/bgrep/b'只匹配grep。

OR

-方法一

grep 'patterna\|patternb' filename

-方法二

grep -E 'patterna|patterb' filename

-方法三

grep -e patterna -e patternb filename

AND

-方法一

grep -E 'patterna.*patternb|patternb.*patterna' filename

-方法二

grep -E 'patterna' filename | grep -E 'patternb'

NOT

grep -v patterna filename
查看原文

赞 0 收藏 0 评论 0

harriszh 发布了文章 · 2020-02-27

[vim]自动更新修改时间和修改次数

目的

每次修改文件后,希望在文件头上的反应出最后修改时间,并更新修改次数

主要语法

submatch(n)

引用前面匹配的内容, submatch(0)是全部, submatch(1)是第一个

getpos(".")

得到当前光标位置
setpos(‘.', save_cursor)用来恢复位置

winsaveview()

保存整个layout

实际代码

function TimeStamp()
"    let save_cursor= getpos(".")
    let win_view = winsaveview()
    %s/\(Last Change\s*:\s*\)\(.*\)$/\=submatch(1).strftime("%Y-%m-%d %H:%M:%S")/ge
    %s/\(Last Modified\s*:\s*\)\(.*\)$/\=submatch(1).strftime("%Y-%m-%d %H:%M:%S")/ge
    %s/\(Update Count\s*:\s*\)\(\d\+\)/\=submatch(1).(submatch(2)+1)/ge
"    call setpos('.', save_cursor)
    call winrestview(win_view)
endfunction
command -nargs=0 TimeStamp call TimeStamp()

function AutoTimeStamp()
    augr tagdate
    au!
    au BufWritePost,FileWritePost * call TimeStamp()
    augr END
endfunction
command -nargs=0 AutoTimeStamp call AutoTimeStamp()

if !exists('*Preserve_view')
    function! Preserve_view(command)
        " Preparation: save last search, and cursor position.
        let win_view = winsaveview()
        let old_query = getreg('/')
        execute 'keepjumps ' . a:command
        " Clean up: restore previous search history, and cursor position
        call winrestview(win_view)
        call setreg('/', old_query)
    endfunction
endif

fun! CleanExtraSpaces()
    call Preserve_view('%s/\s\+$//ge')
endfunc
com! Clsspace :call CleanExtraSpaces()
查看原文

赞 0 收藏 0 评论 0

harriszh 发布了文章 · 2020-02-27

[verilog] FSM状态机的进一步思考 - 生成支持小数分频的UART Baud16信号

[verilog] 对于FSM状态机的进一步思考 - 生成支持小数分频的UART Baud16信号

前言

uart 的 baudrate 公式如下:

$$ baudrate = \frac{UART\_CLK}{16 \times Divisor} $$

baudrate generator需要产生一组脉冲,16组脉冲的宽度就是baud rate, 也就是rx和tx的最小边沿宽度

思路

如果只考虑分频系数为整数的情况,那么我们只需要用一个计数器从divisor-1倒数到0就行了
代码如下:

reg [15:0] dl;
reg [15:0] dlc;

reg baud16;

always @(posedge dclk or negedge rstn)
begin
        if (~rstn)
        begin
                dlc    <= 0;
        end else begin
                if (~(|dlc) && |dl)
                        dlc  <= dl - 1;               // preset counter
                else if(|dlc)
                        dlc  <= dlc - 1;              // decrement counter
        end
end

always @(posedge dclk or negedge rstn) begin
    if (~rstn)
    begin
        baud16 <= #1 1'b0;
    end else begin
        if (|dl & ~(|dlc))     // dl>0 & dlc==0
            baud16 <= #1 1'b1;
        else
            baud16 <= #1 1'b0;
    end
end

ARM pl011允许分频系数为小数, 所以一个baud rate可能包含任意N个uart clk。
比如分频系数是3.25, 那么一个baud rate包含3.25*16=52个uart clk.

对于上面的情况, baud16是需要在52个uart clk内产生16个脉冲。
我们的思路是前面15个脉冲都按照3来做分频,这样前面占掉3*15=45个cycle
最后一个脉冲占掉3+0.25*16=7个周期
图片.png

编码准备

状态机

因为要产生16个脉冲,所以我们自然可以把每个脉冲以及后面为低的周期当做一个状态。状态机如下:
Code_499sq6tBtgrub.png

把分频系数的整数部分称为dli, 小数部分乘以16后得到的整数称为dlf.
需要定义一个计数器称为rcnt, 每当进入一个状态就开始从0计数,当计数到阈值时就切换到下一个状态。

每个状态

在正常情况下,假设分频系数的整数部分为3, 那么high占1个固定周期,而low占剩下的3-1=2个周期。最后一个周期比较特殊,high占是占1个周期,而low占dli+dlf-1个周期。

边界条件

当分频系数的整数部分为1时,我们可以看到dli-1为0(low占的周期), 在这种状态下,每个状态只维持一个周期高就进入下一个状态,所以没有为低的时候。这是一个特殊情况,需要特殊处理。

产生测试激励

唯一的输入变化来自于分频系数,所以我们可以建一个类,分别产生分频系数的整数和小数分部。因为0,1,2属于比较敏感区,所以我们在这个区别的权重设置高一些。

总结

相较于通常的状态机,会有一个外界输入来控制状态切换。但我们需求的是当一个状态的事做完了就切换到下一个状态,如果在状态里加一个寄存器来表征本状态完成,代码如下:

case(rstate)
R1:
    ...
    if(rcnt == (dli=1)) begin
        rdone <= 1;
    end
...
endcase

上面这种实现会造成一个周期的延迟,整个时序就会出问题,特别是在dli为1时,根本没有一个周期来偷,所以就无法实现。

为了解决上在的问题,一种方法就是不使用三段式,也就是不用下面这样的代码。

always @(posedge dclk or negedge rstn)
begin
    if(~rstn) begin
        rstate <= 0;
        next_rstate <= 0;
    end else begin
        rstate <= next_rstate;
    end
end

直接使用state, 当一个状态做完,直接把state赋值成下一个状态。

教训

一开始的时候没有理清状态切换的判断条件,使用的是实现二中的idnum来做状态切换条件,而idnum是在上一个完成时就会切换成下一个状态的值,所以next_rstate就会变成下一个状态,但如果当前状态需要多个周期时,就会无法保留在当前状态上,被强制切换到下一个状态。

代码

实现一

正面的代码可以简化成两个状态r0, r15, r0代码非r15状态,r15代码第16个脉冲,但为了代码清晰,我们还是保留16个状态。(其实是17个状态,第0个状态为空闲状态)

//////////////////////////////////////////////////////////////////////////////////
//=======================================================================
// Created by         :  Harris Zhu
// Filename           :  baud16gen.sv
// Author             :  Harris Zhu
// Created On         :  2018-08-12 22:30:53
// Last Modified      :  2018-09-11 05:54:24
// Update Count       :  7
// Tags               :  
// Description        : 
// Conclusion         : 
//=======================================================================
//////////////////////////////////////////////////////////////////////////////////

`timescale 1ns/1ns
class c_dl #(
    DIVINT_WIDTH=16,
    DIVFRAC_WIDTH=6
);
    rand bit [DIVINT_WIDTH-1:0] dll;
    rand bit [DIVFRAC_WIDTH-1:0] dlh;

    constraint c_dll {
        dll dist {1:=3, [2:4]:=2, [5:20]:/1};
    }

    constraint c_dlh {
        dlh dist {[0:1]:/3, [2:4]:=2, [5:20]:/1};
    }

endclass


module baud16gen();

parameter ID=0;
parameter DIVFRAC_WIDTH=6; 

localparam DIVINT_WIDTH=16;
localparam DIV_WIDTH=DIVFRAC_WIDTH+DIVINT_WIDTH;
localparam ISGREATER4 = (DIVFRAC_WIDTH>4)?1:0;
localparam SUB4 = (ISGREATER4==1)?(DIVFRAC_WIDTH-4):(4-DIVFRAC_WIDTH);

localparam r0 =0;
localparam r1 =1;
localparam r2 =2;
localparam r3 =3;
localparam r4 =4;
localparam r5 =5;
localparam r6 =6;
localparam r7 =7;
localparam r8 =8;
localparam r9 =9;
localparam r10=10;
localparam r11=11;
localparam r12=12;
localparam r13=13;
localparam r14=14;
localparam r15=15;
localparam r16=16;

localparam low=0;
localparam high=1;


reg         rstn=0;  // low active,  really in the real UART interface, there is no reset ...
reg         dclk=0; // Maybe a free running cake clock for PD hybrid mode, else tie to tclk
reg  [DIV_WIDTH-1:0] dl={6'h4, 16'h1}; // Divisor for baudrate (x16)

always #1 dclk=~dclk;

c_dl #(DIVINT_WIDTH, DIVFRAC_WIDTH) div;

initial
begin
    rstn = 0;
    #100;
    rstn = 1;
    div = new();
    forever begin
        div.randomize();
        dl = {div.dlh, div.dll};
        #1000;
    end
end

reg enable; // this is the needed baud16

reg [15:0] rcnt;
reg clk_phase=1;
reg [15:0] p0cnt=0;

reg [4:0] rstate, next_rstate;

wire [DIVINT_WIDTH-1:0] dli=dl[DIVINT_WIDTH-1:0];
wire [DIVFRAC_WIDTH-1:0] dlh=dl[DIV_WIDTH-1:DIVINT_WIDTH];
reg [DIVFRAC_WIDTH-1:0] dlf;

always @(*) begin
    if(ISGREATER4) begin
        dlf = (dlh>>SUB4) + dlh[SUB4-1];
    end else begin
        dlf = (dlh<<SUB4);
    end
end

always @(posedge dclk or negedge rstn)
begin
    if(~rstn) begin
        rcnt <= 0;
        rstate <= 0;
        next_rstate <= 0;
    end else begin
        rstate <= next_rstate;
    end
end

always @(*)
begin
    case(rstate)
        r0:
            next_rstate = r1;
        r1:
            if(rcnt>=(dli-1)) begin
                next_rstate = r2;
            end
        r2:
            if(rcnt>=(dli-1)) begin
                next_rstate = r3;
            end
        r3:
            if(rcnt>=(dli-1)) begin
                next_rstate = r4;
            end
        r4:
            if(rcnt>=(dli-1)) begin
                next_rstate = r5;
            end
        r5:
            if(rcnt>=(dli-1)) begin
                next_rstate = r6;
            end
        r6:
            if(rcnt>=(dli-1)) begin
                next_rstate = r7;
            end
        r7:
            if(rcnt>=(dli-1)) begin
                next_rstate = r8;
            end
        r8:
            if(rcnt>=(dli-1)) begin
                next_rstate = r9;
            end
        r9:
            if(rcnt>=(dli-1)) begin
                next_rstate = r10;
            end
        r10:
            if(rcnt>=(dli-1)) begin
                next_rstate = r11;
            end
        r11:
            if(rcnt>=(dli-1)) begin
                next_rstate = r12;
            end
        r12:
            if(rcnt>=(dli-1)) begin
                next_rstate = r13;
            end
        r13:
            if(rcnt>=(dli-1)) begin
                next_rstate = r14;
            end
        r14:
            if(rcnt>=(dli-1)) begin
                next_rstate = r15;
            end
        r15:
            if(rcnt>=(dli-1)) begin
                next_rstate = r16;
            end
        r16:
            if(rcnt>=(dli+dlf-1)) begin
                next_rstate = r1;
            end
        default:
            next_rstate = r0;
    endcase
end

always @(posedge dclk or negedge rstn)
begin
    if (~rstn) begin
        clk_phase <= 1;
        p0cnt <= 0;
        enable <= 0;
        rstate <= r0;
    end else begin
        case(rstate)
            r0:
            begin
            end
            r1, r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15:
            begin
                if(dli==1) begin
                    rcnt <= 0;
                    enable <= 1;
                    clk_phase <= 1;
                end else begin
                    if(rcnt >= (dli-1)) begin
                        rcnt <= 0;
                    end else begin
                        rcnt <= rcnt + 1;
                    end
                    if(clk_phase) begin
                        enable <= 1;
                        clk_phase <= low;
                        p0cnt <= 0;
                    end else begin
                        enable <= 0;
                        if(p0cnt < (dli-2)) begin
                            p0cnt <= p0cnt + 1;
                        end else begin
                            clk_phase <= high;
                        end
                    end
                end
            end
            r16:
            begin
                if((dli==1)&&(dlf==0)) begin
                    rcnt <= 0;
                end else begin
                    if(rcnt >= (dli+dlf-1)) begin
                        rcnt <= 0;
                    end else begin
                        rcnt <= rcnt + 1;
                    end
                end
                if(dli==1) begin
                    if(dlf==0) begin
                        enable <= 1;
                    end else begin
                        if(clk_phase) begin
                            enable <= 1;
                            clk_phase <= low;
                            p0cnt <= 0;
                        end else begin
                            enable <= 0;
                            if(p0cnt < (dlf-1)) begin
                                p0cnt <= p0cnt + 1;
                            end else begin
                                clk_phase <= high;
                            end
                        end
                    end
                end else begin //dli>1
                    if(clk_phase) begin
                        enable <= 1;
                        clk_phase <= low;
                        p0cnt <= 0;
                    end else begin
                        enable <= 0;
                        if(p0cnt < (dli+dlf-2)) begin
                            p0cnt <= p0cnt + 1;
                        end else begin
                            clk_phase <= high;
                        end
                    end
                end // dli>1
            end // r15
            default:
            begin
                enable <= 1;
            end
        endcase
    end
end

endmodule

实现二

下面的代码比上面的更简洁,但中间的always块的代码更长,更难于理解。

//////////////////////////////////////////////////////////////////////////////////
//=======================================================================
// Created by         :  Harris Zhu
// Filename           :  baud16gen.sv
// Author             :  Harris Zhu
// Created On         :  2018-08-12 22:30:53
// Last Modified      :  2018-09-11 05:54:24
// Update Count       :  7
// Tags               :  
// Description        : 
// Conclusion         : 
//=======================================================================
//////////////////////////////////////////////////////////////////////////////////

`timescale 1ns/1ns
class c_dl #(
    DIVINT_WIDTH=16,
    DIVFRAC_WIDTH=6
);
    rand bit [DIVINT_WIDTH-1:0] dll;
    rand bit [DIVFRAC_WIDTH-1:0] dlh;

    constraint c_dll {
        dll dist {1:=3, [2:4]:=2, [5:20]:/1};
    }

    constraint c_dlh {
        dlh dist {[0:1]:/3, [2:4]:=2, [5:20]:/1};
    }

endclass


module baud16gen ();

parameter ID=0;
parameter DIVFRAC_WIDTH=6; 

localparam DIVINT_WIDTH=16;
localparam DIV_WIDTH=DIVFRAC_WIDTH+DIVINT_WIDTH;
localparam ISGREATER4 = (DIVFRAC_WIDTH>4)?1:0;
localparam SUB4 = (ISGREATER4==1)?(DIVFRAC_WIDTH-4):(4-DIVFRAC_WIDTH);

localparam r0=0;
localparam r15=1;
localparam low=0;
localparam high=1;

reg         rstn=0;  // low active,  really in the real UART interface, there is no reset ...
reg         dclk=0; // Maybe a free running cake clock for PD hybrid mode, else tie to tclk
reg  [DIV_WIDTH-1:0] dl={6'h4, 16'h1}; // Divisor for baudrate (x16)
reg  [7:0]  lcr=3;

always #1 dclk=~dclk;

c_dl #(DIVINT_WIDTH, DIVFRAC_WIDTH) div;

initial
begin
    rstn = 0;
    #100;
    rstn = 1;
    div = new();
    forever begin
        div.randomize();
        dl = {div.dlh, div.dll};
        #1000;
    end
end

// pragma protect
// pragma protect begin
// quickturn not_protect_output
`protect

reg        enable;

reg clk_phase=1;
reg [3:0] idnum=0;
reg [15:0] p0cnt=0;

reg [3:0] state;

wire [DIVINT_WIDTH-1:0] dli=dl[DIVINT_WIDTH-1:0];
wire [DIVFRAC_WIDTH-1:0] dlh=dl[DIV_WIDTH-1:DIVINT_WIDTH];
reg [DIVFRAC_WIDTH-1:0] dlf;

always @(*) begin
    if(ISGREATER4) begin
        dlf = (dlh>>SUB4) + dlh[SUB4-1];
    end else begin
        dlf = (dlh<<SUB4);
    end
end

function bit [3:0] next_idnum(bit [3:0] id, bit [3:0] max);
    if(id<max) begin
        next_idnum = id+1;
    end else begin
        next_idnum = 0;
    end
endfunction

always @(posedge dclk or negedge rstn)
begin
    if (~rstn) begin
        clk_phase <= 1;
        p0cnt <= 0;
        idnum <= 0;
        enable <= 0;
        state <= r0;
    end else begin
        case(state)
            r0:
                if (dli==1) begin
                    enable <= 1;
                    clk_phase <= 1;
                    if(idnum == 15) begin
                        idnum <= 0;
                        state <= r15;
                    end else begin
                        idnum <= idnum + 1;
                    end
                end else begin
                    if (clk_phase) begin
                        enable <= 1;
                        clk_phase <= low;
                        p0cnt <= 0;
                    end else begin
                        enable <= 0;
                        if(p0cnt < (dli-2)) begin
                            p0cnt <= p0cnt + 1;
                        end else begin
                            p0cnt <= 0;
                            clk_phase <= high;
                            if(idnum == 15) begin
                                idnum <= 0;
                                state <= r15;
                            end else begin
                                idnum <= idnum + 1;
                            end
                        end
                    end
                end
            r15:
            begin
                if (dli==1) begin
                    if(dlf==0) begin
                        enable <= 1;
                        idnum <= next_idnum(idnum, 15);
                        state <= r0;
                    end else begin
                        if (clk_phase) begin
                            enable <= 1;
                            clk_phase <= low;
                            p0cnt <= 0;
                        end else begin
                            enable <= 0;
                            if(p0cnt < (dlf-1)) begin
                                p0cnt <= p0cnt + 1;
                            end else begin
                                p0cnt <= 0;
                                clk_phase <= high;
                                idnum <= next_idnum(idnum, 15);
                                state <= r0;
                            end
                        end
                    end
                end else begin
                    if (clk_phase) begin
                        enable <= 1;
                        clk_phase <= low;
                        p0cnt <= 0;
                    end else begin
                        enable <= 0;
                        if(p0cnt < (dli+dlf-2)) begin
                            p0cnt <= p0cnt + 1;
                        end else begin
                            p0cnt <= 0;
                            clk_phase <= high;
                            idnum <= next_idnum(idnum, 15);
                            state <= r0;
                        end
                    end
                end
            end
            default:
            begin
            end
        endcase
    end
end

endmodule
查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 73 次点赞
  • 获得 7 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 7 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-08-07
个人主页被 2.6k 人浏览