qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[RISU PATCH 3/5] loongarch: Implement risugen module


From: Song Gao
Subject: [RISU PATCH 3/5] loongarch: Implement risugen module
Date: Sat, 17 Sep 2022 15:43:15 +0800

Signed-off-by: Song Gao <gaosong@loongson.cn>
---
 risugen_loongarch64.pm | 502 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 502 insertions(+)
 create mode 100644 risugen_loongarch64.pm

diff --git a/risugen_loongarch64.pm b/risugen_loongarch64.pm
new file mode 100644
index 0000000..693fb71
--- /dev/null
+++ b/risugen_loongarch64.pm
@@ -0,0 +1,502 @@
+#!/usr/bin/perl -w
+###############################################################################
+# Copyright (c) 2022 Loongson Technology Corporation Limited
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+#     based on Peter Maydell (Linaro) - initial implementation
+###############################################################################
+
+# risugen -- generate a test binary file for use with risu
+# See 'risugen --help' for usage information.
+package risugen_loongarch64;
+
+use strict;
+use warnings;
+
+use risugen_common;
+
+require Exporter;
+
+our @ISA    = qw(Exporter);
+our @EXPORT = qw(write_test_code);
+
+my $periodic_reg_random = 1;
+
+# Maximum alignment restriction permitted for a memory op.
+my $MAXALIGN = 64;
+
+my $OP_COMPARE = 0;        # compare registers
+my $OP_TESTEND = 1;        # end of test, stop
+my $OP_SETMEMBLOCK = 2;    # r4 is address of memory block (8192 bytes)
+my $OP_GETMEMBLOCK = 3;    # add the address of memory block to r4
+my $OP_COMPAREMEM = 4;     # compare memory block
+
+sub write_risuop($)
+{
+    my ($op) = @_;
+    insn32(0x000001f0 | $op);
+}
+
+sub write_set_fcsr($)
+{
+    my ($fcsr) = @_;
+    # movgr2fcsr r0, r0
+    insn32(0x0114c000);
+}
+
+# Global used to communicate between align(x) and reg() etc.
+my $alignment_restriction;
+
+sub set_reg_w($)
+{
+    my($reg)=@_;
+    # Set reg [0x0, 0x7FFFFFFF]
+
+    # $reg << 33
+    # slli.d  $reg, $reg, 33
+    insn32(0x410000 | 33 << 10 | $reg << 5 | $reg);
+    # $reg >> 33
+    # srli.d  $reg, $reg, 33
+    insn32(0x450000 | 33 << 10 | $reg << 5 | $reg);
+
+    return $reg;
+}
+
+sub align($)
+{
+    my ($a) = @_;
+    if (!is_pow_of_2($a) || ($a < 0) || ($a > $MAXALIGN)) {
+        die "bad align() value $a\n";
+    }
+    $alignment_restriction = $a;
+}
+
+sub write_sub_rrr($$$)
+{
+    my ($rd, $rj, $rk) = @_;
+    # sub.d rd, rj, rk
+    insn32(0x00118000 | $rk << 10 | $rj << 5 | $rd);
+}
+
+sub write_mov_rr($$$)
+{
+    my($rd, $rj, $rk) = @_;
+    # add.d rd, rj, r0
+    insn32(0x00108000 | 0 << 10 | $rj << 5 | $rd);
+}
+
+sub write_mov_positive_ri($$)
+{
+    # Use lu12i.w and ori instruction
+    my ($rd, $imm) = @_;
+    my $high_20 = ($imm >> 12) & 0xfffff;
+
+    if ($high_20) {
+        # lu12i.w rd, si20
+        insn32(0x14000000 | $high_20 << 5 | $rd);
+        # ori rd, rd, ui12
+        insn32(0x03800000 | ($imm & 0xfff) << 10 | $rd << 5 | $rd);
+    } else {
+        # ori rd, 0, ui12
+        insn32(0x03800000 | ($imm & 0xfff) << 10 | 0 << 5 | $rd);
+    }
+}
+
+sub write_mov_ri($$)
+{
+    my ($rd, $imm) = @_;
+
+    if ($imm < 0) {
+        my $tmp = 0 - $imm ;
+        write_mov_positive_ri($rd, $tmp);
+        write_sub_rrr($rd, 0, $rd);
+    } else {
+        write_mov_positive_ri($rd, $imm);
+    }
+}
+
+sub write_get_offset()
+{
+    # Emit code to get a random offset within the memory block, of the
+    # right alignment, into r4
+    # We require the offset to not be within 256 bytes of either
+    # end, to (more than) allow for the worst case data transfer, which is
+    # 16 * 64 bit regs
+    my $offset = (rand(2048 - 512) + 256) & ~($alignment_restriction - 1);
+    write_mov_ri(4, $offset);
+    write_risuop($OP_GETMEMBLOCK);
+}
+
+sub reg_plus_reg($$@)
+{
+    my ($base, $idx, @trashed) = @_;
+    my $savedidx = 0;
+    if ($idx == 4) {
+        # Save the index into some other register for the
+        # moment, because the risuop will trash r4.
+        $idx = 5;
+        $idx++ if $idx == $base;
+        $savedidx = 1;
+        write_mov_rr($idx, 4, 0);
+    }
+    # Get a random offset within the memory block, of the
+    # right alignment.
+    write_get_offset();
+
+    write_sub_rrr($base, 4, $idx);
+    if ($base != 4) {
+        if ($savedidx) {
+            write_mov_rr(4, $idx, 0);
+            write_mov_ri($idx, 0);
+        } else {
+            write_mov_ri(4, 0);
+        }
+    } else {
+       if ($savedidx) {
+            write_mov_ri($idx, 0);
+       }
+    }
+
+    if (grep $_ == $base, @trashed) {
+        return -1;
+    }
+    return $base;
+}
+
+sub reg_plus_imm($$@)
+{
+    # Handle reg + immediate addressing mode
+    my ($base, $imm, @trashed) = @_;
+
+    write_get_offset();
+    # Now r4 is the address we want to do the access to,
+    # so set the basereg by doing the inverse of the
+    # addressing mode calculation, ie base = r4 - imm
+    # We could do this more cleverly with a sub immediate.
+    if ($base != 4) {
+        write_mov_ri($base, $imm);
+        write_sub_rrr($base, 4, $base);
+        # Clear r4 to avoid register compare mismatches
+        # when the memory block location differs between machines.
+         write_mov_ri(4, 0);
+    }else {
+        # We borrow r1 as a temporary (not a problem
+        # as long as we don't leave anything in a register
+        # which depends on the location of the memory block)
+        write_mov_ri(1, $imm);
+        write_sub_rrr($base, 4, 1);
+    }
+
+    if (grep $_ == $base, @trashed) {
+        return -1;
+    }
+    return $base;
+}
+
+sub write_pc_adr($$)
+{
+    my($rd, $imm) = @_;
+    # pcaddi (si20 | 2bit 0) + pc
+    insn32(0x18000000 | $imm << 5 | $rd);
+}
+
+sub write_and($$$)
+{
+    my($rd, $rj, $rk)  = @_;
+    # and rd, rj, rk
+    insn32(0x148000 | $rk << 10 | $rj << 5 | $rd);
+}
+
+sub write_align_reg($$)
+{
+    my ($rd, $align) = @_;
+    # rd = rd & ~($align -1);
+    # use r1 as a temp register.
+    write_mov_ri(1, $align -1);
+    write_sub_rrr(1, 0, 1);
+    write_and($rd, $rd, 1);
+}
+
+sub write_jump_fwd($)
+{
+    my($len) = @_;
+    # b pc + len
+    my ($offslo, $offshi) = (($len / 4 + 1) & 0xffff, ($len / 4 + 1) >> 16);
+    insn32(0x50000000 | $offslo << 10 | $offshi);
+}
+
+sub write_memblock_setup()
+{
+    my $align = $MAXALIGN;
+    my $datalen = 8192 + $align;
+    if (($align > 255) || !is_pow_of_2($align) || $align < 4) {
+        die "bad alignment!";
+    }
+
+    # Set r4 to (datablock + (align-1)) & ~(align-1)
+    # datablock is at PC + (4 * 4 instructions) = PC + 16
+    write_pc_adr(4, (4 * 4) + ($align - 1)); #insn 1
+    write_align_reg(4, $align);              #insn 2
+    write_risuop($OP_SETMEMBLOCK);           #insn 3
+    write_jump_fwd($datalen);                #insn 4
+
+    for(my $i = 0; $i < $datalen / 4; $i++) {
+        insn32(rand(0xffffffff));
+    }
+}
+
+# Write random fp value of passed precision (1=single, 2=double, 4=quad)
+sub write_random_fpreg_var($)
+{
+    my ($precision) = @_;
+    my $randomize_low = 0;
+
+    if ($precision != 1 && $precision != 2 && $precision != 4) {
+        die "write_random_fpreg: invalid precision.\n";
+    }
+
+    my ($low, $high);
+    my $r = rand(100);
+    if ($r < 5) {
+        # +-0 (5%)
+        $low = $high = 0;
+        $high |= 0x80000000 if (rand() < 0.5);
+    } elsif ($r < 10) {
+        # NaN (5%)
+        # (plus a tiny chance of generating +-Inf)
+        $randomize_low = 1;
+        $high = rand(0xffffffff) | 0x7ff00000;
+    } elsif ($r < 15) {
+        # Infinity (5%)
+        $low = 0;
+        $high = 0x7ff00000;
+        $high |= 0x80000000 if (rand() < 0.5);
+    } elsif ($r < 30) {
+        # Denormalized number (15%)
+        # (plus tiny chance of +-0)
+        $randomize_low = 1;
+        $high = rand(0xffffffff) & ~0x7ff00000;
+    } else {
+        # Normalized number (70%)
+        # (plus a small chance of the other cases)
+        $randomize_low = 1;
+        $high = rand(0xffffffff);
+    }
+
+    for (my $i = 1; $i < $precision; $i++) {
+        if ($randomize_low) {
+            $low = rand(0xffffffff);
+        }
+        insn32($low);
+    }
+    insn32($high);
+}
+
+sub write_random_loongarch64_fpdata()
+{
+    # Load floating point registers
+    my $align = 16;
+    my $datalen = 32 * 16 + $align;
+    my $off = 0;
+    write_pc_adr(5, (4 * 4) + $align);       # insn 1  pcaddi
+    write_pc_adr(4, (3 * 4) + ($align - 1)); # insn 2  pcaddi
+    write_align_reg(4, $align);              # insn 3  andi
+    write_jump_fwd($datalen);                # insn 4  b pc + len
+
+    # Align safety
+    for (my $i = 0; $i < ($align / 4); $i++) {
+        insn32(rand(0xffffffff));
+    }
+
+    for (my $i = 0; $i < 32; $i++) {
+        write_random_fpreg_var(4); # double
+    }
+
+    $off = 0;
+    for (my $i = 0; $i < 32; $i++) {
+        my $tmp_reg = 6;
+        # r5 is fp register initial val
+        # r4 is aligned base address
+        # copy memory from r5 to r4
+        # ld.d r6, r5, $off
+        # st.d r6, r4, $off
+        # $off = $off + 16
+        insn32(0x28c00000 | $off << 10 | 5 << 5 | $tmp_reg);
+        insn32(0x29c00000 | $off << 10 | 4 << 5 | $tmp_reg);
+        $off = $off + 8;
+        insn32(0x28c00000 | $off << 10 | 5 << 5 | $tmp_reg);
+        insn32(0x29c00000 | $off << 10 | 4 << 5 | $tmp_reg);
+        $off = $off + 8;
+    }
+
+    $off = 0;
+    for (my $i = 0; $i < 32; $i++) {
+        # fld.d fd, r4, $off
+        insn32(0x2b800000 | $off << 10 | 4 << 5 | $i);
+        $off = $off + 16;
+    }
+}
+
+sub write_random_regdata()
+{
+    # General purpose registers, skip r2
+    write_mov_ri(1, rand(0xffffffff)); # init r1
+    for  (my $i = 3; $i < 32; $i++) {
+        write_mov_ri($i, rand(0xffffffff));
+    }
+}
+
+sub write_random_register_data($)
+{
+    my ($fp_enabled) = @_;
+
+    # Set fcc0 ~ fcc7
+    # movgr2cf $fcc0, $zero
+    insn32(0x114d800);
+    # movgr2cf $fcc1, $zero
+    insn32(0x114d801);
+    # movgr2cf $fcc2, $zero
+    insn32(0x114d802);
+    # movgr2cf $fcc3, $zero
+    insn32(0x114d803);
+    # movgr2cf $fcc4, $zero
+    insn32(0x114d804);
+    # movgr2cf $fcc5, $zero
+    insn32(0x114d805);
+    # movgr2cf $fcc6, $zero
+    insn32(0x114d806);
+    # movgr2cf $fcc7, $zero
+    insn32(0x114d807);
+
+    if ($fp_enabled) {
+        # Load floating point registers
+        write_random_loongarch64_fpdata();
+    }
+
+    write_random_regdata();
+    write_risuop($OP_COMPARE);
+}
+
+sub gen_one_insn($$)
+{
+    # Given an instruction-details array, generate an instruction
+    my $constraintfailures = 0;
+
+    INSN: while(1) {
+        my ($forcecond, $rec) = @_;
+        my $insn = int(rand(0xffffffff));
+        my $insnname = $rec->{name};
+        my $insnwidth = $rec->{width};
+        my $fixedbits = $rec->{fixedbits};
+        my $fixedbitmask = $rec->{fixedbitmask};
+        my $constraint = $rec->{blocks}{"constraints"};
+        my $memblock = $rec->{blocks}{"memory"};
+
+        $insn &= ~$fixedbitmask;
+        $insn |= $fixedbits;
+
+        if (defined $constraint) {
+            # User-specified constraint: evaluate in an environment
+            # with variables set corresponding to the variable fields.
+            my $v = eval_with_fields($insnname, $insn, $rec, "constraints", 
$constraint);
+            if(!$v) {
+                $constraintfailures++;
+                if ($constraintfailures > 10000) {
+                    print "10000 consecutive constraint failures for $insnname 
constraints string:\n$constraint\n";
+                    exit (1);
+                }
+                next INSN;
+            }
+        }
+
+        # OK, we got a good one
+        $constraintfailures = 0;
+
+        my $basereg;
+
+        if (defined $memblock) {
+            # This is a load or store. We simply evaluate the block,
+            # which is expected to be a call to a function which emits
+            # the code to set up the base register and returns the
+            # number of the base register.
+            # Default alignment requirement for ARM is 4 bytes,
+            # we use 16 for Aarch64, although often unnecessary and overkill.
+            align(16);
+            $basereg = eval_with_fields($insnname, $insn, $rec, "memory", 
$memblock);
+        }
+
+        insn32($insn);
+
+        if (defined $memblock) {
+            # Clean up following a memory access instruction:
+            # we need to turn the (possibly written-back) basereg
+            # into an offset from the base of the memory block,
+            # to avoid making register values depend on memory layout.
+            # $basereg -1 means the basereg was a target of a load
+            # (and so it doesn't contain a memory address after the op)
+            if ($basereg != -1) {
+                write_mov_ri($basereg, 0);
+            }
+            write_risuop($OP_COMPAREMEM);
+        }
+        return;
+    }
+}
+
+sub write_test_code($)
+{
+    my ($params) = @_;
+
+    my $condprob = $params->{ 'condprob' };
+    my $fcsr = $params->{'fpscr'};
+    my $numinsns = $params->{ 'numinsns' };
+    my $fp_enabled = $params->{ 'fp_enabled' };
+    my $outfile = $params->{ 'outfile' };
+
+    my %insn_details = %{ $params->{ 'details' } };
+    my @keys = @{ $params->{ 'keys' } };
+
+    open_bin($outfile);
+
+    # Convert from probability that insn will be conditional to
+    # probability of forcing insn to unconditional
+    $condprob = 1 - $condprob;
+
+    # TODO better random number generator?
+    srand(0);
+
+    print "Generating code using patterns: @keys...\n";
+    progress_start(78, $numinsns);
+
+    if ($fp_enabled) {
+        write_set_fcsr($fcsr);
+    }
+
+    if (grep { defined($insn_details{$_}->{blocks}->{"memory"}) } @keys) {
+        write_memblock_setup();
+    }
+    # Memblock setup doesn't clean its registers, so this must come afterwards.
+    write_random_register_data($fp_enabled);
+
+    for my $i (1..$numinsns) {
+        my $insn_enc = $keys[int rand (@keys)];
+        my $forcecond = (rand() < $condprob) ? 1 : 0;
+        gen_one_insn($forcecond, $insn_details{$insn_enc});
+        write_risuop($OP_COMPARE);
+        # Rewrite the registers periodically. This avoids the tendency
+        # for the VFP registers to decay to NaNs and zeroes.
+        if ($periodic_reg_random && ($i % 100) == 0) {
+            write_random_register_data($fp_enabled);
+        }
+        progress_update($i);
+    }
+    write_risuop($OP_TESTEND);
+    progress_end();
+    close_bin();
+}
+
+1;
-- 
2.31.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]