📄 em_dma.pm
字号:
sub get_slave_port_register_indices{ my ($Options, @names) = @_; my @regs = get_slave_port_registers($Options); my @indices; for my $name (@names) { my $register_index = ''; my $i = 0; for my $reg (@regs) { if ($reg->[1] eq $name) { $register_index = $i; last; } $i++; } # Push the index if found, otherwise null string. push @indices, $register_index; } return @indices;}# Reads are always done at full slave data width - there's no analogy to the# byte enable signals of a write access. So, for cases like## 8/16-bit peripheral streaming to 32-bit memory# 32/16-bit memory streaming to 16/8 bit peripheral# # where the FIFO is written with data of the width of the transaction, that# is, the minimum size of read and write, we need a data mux.sub make_read_master_data_mux{ # If all slaves on the read side are non-latent, then the mux select is just # the low bits of the read address. If any slave is latent, then load a # 2-bit counter on read_address write, and increment it synchronously on # readdatavalid. What the heck, I'll throw this stuff in in all cases. # Future optimization: trim the unnecessary address counter if all read # slaves have latency=0. my ($Options, $module, $project) = @_; my $read_data_mux_module = e_module->new({ name => $module->name() . "_read_data_mux", }); my @contents = (); my $mux_select_bits = log2($Options->{readdatawidth} / 8); if (get_allowed_transactions() == 1 or $mux_select_bits == 0) { # Special case: only one possible transaction. Don't make a mux (it # would be a pretty simple mux, selected by the transaction-size bit # alone). I can make it even simpler: a plain old assignment. # Also, there's a degenerate case: readdatawidth (and fifodatawidth) are 8. # Only byte transactions are possible. No mux is necessary! push @contents, e_assign->new({ lhs => "fifo_wr_data[@{[$Options->{readdatawidth} - 1]} : 0]", rhs => "read_readdata[@{[$Options->{readdatawidth} - 1]} : 0]", }); } else { # General case: my $address_select_range = "0"; if ($mux_select_bits > 1) { $address_select_range = "@{[$mux_select_bits - 1]}: 0"; } push @contents, ( e_signal->new({name => "readdata_mux_select", width => $mux_select_bits,}), ); if ($Options->{max_read_latency} == 0) { push @contents, ( e_assign->new(["readdata_mux_select", "read_address[$address_select_range]",]), ); } else { # Grab the memory map. my @reg_info = get_slave_port_registers($Options); # Get the locations of the control, readaddress and length registers. my ($control_index, $readaddress_index, $length_index) = get_slave_port_register_indices($Options, "control", "readaddress", "length"); ribbit("No control register!") if ('' eq $control_index); ribbit("No readaddress register!") if ('' eq $readaddress_index); ribbit("No readaddress register!") if ('' eq $length_index); push @contents, ( e_assign->new([ e_signal->new(["control_write"]), $reg_info[$control_index]->[3] ]), ); push @contents, ( e_assign->new([ e_signal->new(["length_write"]), $reg_info[$length_index]->[3] ]), ); # Look up the bit number of the go bit in the control register. my ($go_bit_pos) = get_slave_port_bits("control", "go"); ribbit("no go bit!\n") if (!defined($go_bit_pos)); # Get the indices of the transaction size bits within the control register. my @trans_bits = get_transaction_size_bit_indices(); # Figure out the reset value of the bits of the readaddress register # which correspond to the transaction-size bits. my $readaddress_reset_val = $reg_info[$readaddress_index]->[7]; $readaddress_reset_val =~ s/([0-9]*)\'h/0x/g; $readaddress_reset_val = hex($readaddress_reset_val); if ($readaddress_reset_val) { # If non-zero reset value, AND against the mux select bits. $readaddress_reset_val .= sprintf(" & %d\'b%s", $mux_select_bits, '1' x $mux_select_bits); } # Right, so how about a loadable counter, incremented by the current # transaction size when readdatavalid is true? This counter resets # to the same value as the readaddress register, and is written with the # readaddress register contents whenever the go bit is written. # Whenever readdatavalid is true, the register increments by the same # value that the readaddress increments by. # SPR 170912: also reset the counter when the length register is written. push @contents, ( e_mux->new({ lhs => e_signal->new({name => "read_data_mux_input", width => $mux_select_bits,}), table => [ "control_write && dma_ctl_writedata[$go_bit_pos] || length_write", "readaddress[1:0]", "read_readdatavalid", "readdata_mux_select + readaddress_inc", ], default => "readdata_mux_select", type => "priority", }), e_register->new({ comment => " Reset value: the transaction size bits of the read address reset value.", out => "readdata_mux_select", in => "read_data_mux_input[$address_select_range]", async_value => $readaddress_reset_val, }), ); } # Define a read-data mux. This routes data from the read data # bus into the fifo. It's possible that the read data bus is # wider than the fifo, but the fifo cannot be wider than the # read data bus (the fifo data width is the minimum of the # read- and write-data bus widths). # The structure of the mux depends on: # $Options->{readdatawidth} # $Options->{fifodatawidth} # The mux select input is based on # readdata_mux_select and the current transaction size. # Loop over each allowed transaction size. # Optimization alert: disallow small transaction sizes to avoid generating # lots of mux logic. # # Optimization alert: generate <fifodatawidth> 1-bit muxes. The LSBs of the # current mux have many terms which the MSBs lack, which I try to express # as don't cares, but I'm not sure how well the synthesizer will take # this info into account. push @contents, e_signal->new({ name => "fifo_wr_data", width => $Options->{fifodatawidth}, }); # Get transaction bit names in least-significant order first. my @trans_names = reverse get_transaction_size_bit_names(); my $msb = $Options->{fifodatawidth} - 1; my $lsb = $Options->{fifodatawidth} / 2; while (1) { # Make a mux for fifo_wr_data[$msb : $lsb] my @mux_table; for my $trans_index (0 .. @trans_names - 1) { my $trans_name = $trans_names[$trans_index]; next if not is_transaction_allowed($trans_name); my $trans_size_in_bits = transaction_size_in_bits($trans_name); next if $trans_size_in_bits <= $lsb; my $multiple = $Options->{readdatawidth} / $trans_size_in_bits; my $mux_select_msb = log2($Options->{readdatawidth} / 8) - 1; my $mux_select_lsb = $trans_index; my $mux_select; if ($mux_select_msb >= $mux_select_lsb) { $mux_select = "readdata_mux_select[$mux_select_msb : $mux_select_lsb]"; } for my $i (0 .. $multiple - 1) { my $select = $trans_name; if ($mux_select) { $select .= " & ($mux_select == $i)"; } my $basic_selection = "read_readdata[@{[$i * $trans_size_in_bits + $msb]} : @{[$i * $trans_size_in_bits + $lsb]}]"; my $full_selection; my $excess_bits = $Options->{fifodatawidth} - $trans_size_in_bits; my $top_half = sprintf("read_readdata[%d : %d]", $Options->{fifodatawidth} - 1, $Options->{fifodatawidth} / 2); my $dont_care_part; my $dont_care_bits = $excess_bits - $Options->{fifodatawidth} / 2; if ($dont_care_bits > 0) { $dont_care_part = sprintf("{%d{1'b%s}}, ", $dont_care_bits, $::g_dont_care_value); } push @mux_table, ($select, $basic_selection); } } # If the mux table contains only one term, emit a straight assignment instead. if (@mux_table == 2) { push @contents, e_assign->new({ lhs => "fifo_wr_data[$msb : $lsb]", rhs => $mux_table[1], }); } else { # More than one term: make a real mux. push @contents, e_mux->new({ lhs => "fifo_wr_data[$msb : $lsb]", type => "and_or", table => \@mux_table, }); } # Get the msb and lsb for the next segment. This code is oddly complicated, # due to the fact that lsb = 0 is a weird special case in the sequence, which # goes 64, 32, 16, 8, ... -> 0 instead of 4 <- last if $lsb == 0; $msb -= $lsb; $lsb /= 2; $lsb = 0 if $lsb < 8; } } $read_data_mux_module->add_contents(@contents); return e_instance->new({module => $read_data_mux_module});}sub make_registers{ my ($module, $Options) = @_; my $burst_enable = $Options->{burst_enable};Progress("DMA burst enable state: $burst_enable") if $Options->{europa_debug}; # Grab the memory map. my @reg_info = get_slave_port_registers($Options); # Make a control register process for each register descriptor # in the list. my @write_regdesc; my $length_index = -1; for my $i (0 .. @reg_info - 1) { my $reg = $reg_info[$i]; # Special case: don't generate the status register: it's composed # of individual register bits, which are assigned to the signal # 'status' for export. if ($reg->[1] eq "status") { # We don't need a register for status (it's composed of various # register bits) but while we're here, make a handy status-write # signal. $module->add_contents( e_assign->new({ lhs => e_signal->new(["status_register_write"]), rhs => "dma_ctl_chipselect & ~dma_ctl_write_n & (dma_ctl_address == $i)", }), ); next; } # Special case: "reserved3" is just an alias of "control", and has # no hardware existence except for an extra address that decodes to # control. next if ($reg->[1] eq "reserved3"); # The writelength register isn't visible to the programmer, but it # behaves like an ordinary register in all other ways. It's very # similar to the length register, so make a copy of that # info when that register passes by. if ($reg->[1] eq "length") { $length_index = $i; @write_regdesc = @{$reg}; } $module->add_contents( control_register(@{$reg}) ); } ribbit ("length not found: can't make writelength register!\n") if (!@write_regdesc or ($length_index == -1)); # Now make some small modifications to writelength. $write_regdesc[0] = " write master length"; $write_regdesc[1] = "writelength"; $write_regdesc[5] = "inc_write && (!writelength_eq_0)"; $write_regdesc[6] = " - " . get_transaction_size_expression(); $module->add_contents(control_register(@write_regdesc)); # In the FSMs, I need to know one cycle ahead of time if the # length and writelength registers will be 0. Make handy signals # for that condition. $module->add_contents( e_assign->new({ lhs => e_signal->new({name => "p1_writelength_eq_0",}), rhs => "$write_regdesc[5] && ((writelength $write_regdesc[6]) == 0)", }), e_assign->new({ lhs => e_signal->new({name => "p1_length_eq_0",}), rhs => "$reg_info[$length_index]->[5] && " . "((length $reg_info[$length_index]->[6]) == 0)", }), ); # Create registered "length equals 0" and "writelength equals 0" signals. # Note: if the user writes 0 into the length register, length_eq_0 will # momentarily go false. # Ugh. As always, remember to convert those reset values from verilog constants # into perl numbers. my $length_reset_as_number; ($length_reset_as_number = $reg_info[$length_index]->[7]) =~ s/[0-9]*\'h/0x/g; $length_reset_as_number = eval($length_reset_as_number); my $writelength_reset_as_number; ($writelength_reset_as_number = $write_regdesc[7]) =~ s/[0-9]*\'h/0x/g; $writelength_reset_as_number = eval($writelength_reset_as_number); $module->add_contents( e_register->new({ out => "length_eq_0", async_value => 0 + ($length_reset_as_number == 0), sync_set => "p1_length_eq_0", sync_reset => $reg_info[$length_index]->[3], }), e_register->new({ out => "writelength_eq_0", async_value => 0 + ($writelength_reset_as_number == 0), sync_set => "p1_writelength_eq_0", sync_reset => $write_regdesc[3], }), ); # The increment next values of the read and write addresses are # determined as follows: # if the addr_constant bit is 1, the increment is 0. # otherwise, # if the increment register is 0, the # increment is determined by the control register bits # {word, hw, byte}. # otherwise # the increment value is set by the increment register. # Most users will never need to override the increment values # with readincov and writeincov. Therefore, if (read|write)incovwidth # is set to 0, that register doesn't get implemented. To handle both # cases and allow Leo to remove lots of logic, created a # (read|write)incov_eq_0 register which is # - constant 1, if the register has 0 width # - otherwise, latched with the appropriate value when the register is # written. if ($Options->{readincovwidth}) { my $is0_p1_rhs; my $readinc_index = -1; map { $readinc_index = $_ if ($reg_info[$_]->[1] eq "readincov") } (0 .. -1 + @reg_info); ribbit("no readincov register!") if $readinc_index == -1; my $incov_clken = $reg_info[$readinc_index]->[3]; my $reset_val; ($reset_val = $reg_info[$readinc_index]->[7]) =~ s/[0-9]*\'h/0x/g; $reset_val = eval($reset_val); my $async_val = $reset_val == 0 ? "1" : "0"; $is0_p1_rhs = sprintf("dma_ctl_writedata[%d:0] == 0", $Options->{readincovwidth} - 1); $module->add_contents( e_assign->new({ lhs => e_signal->new({ name => "p1_readincov_eq_0", never_export => 1, }), rhs => $is0_p1_rhs, }), e_register->new({ out => "readincov_eq_0", in => "p1_readincov_eq_0", enable => $incov_clken, async_value => $async_val, }), ); } if ($Options->{writeincovwidth}) { my $is0_p1_rhs; my $writeinc_index = -1; map { $writeinc_index = $_ if ($reg_info[$_]->[1] eq "writeincov") } (0 .. -1 + @reg_info); ribbit("no writeincov register!") if $writeinc_index == -1; my $incov_clken = $reg_info[$writeinc_index]->[3]; my $reset_val; ($reset_val = $reg_info[$writeinc_index]->[7]) =~ s/[0-9]*\'h/0x/g; $reset_val = eval($reset_val); my $async_val = $reset_val == 0 ? "1" : "0"; $is0_p1_rhs = sprintf("dma_ctl_writedata[%d:0] == 0", $Options->{writeincovwidth} - 1); $module->add_contents(
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -