Finish initial version of interrupt suport, closes #1

The software is now able to create most interrupt combinations of
Section 9.9 of the SystemRDL 2.0 LRM. It supports
stickybit/non-stickybit interrupts, it support posedge, negedge,
bothedge, and level interrupts, and it is able to generate all
surrounding logic.

This commit also fixes a reset-bug that caused registers that were reset
to 0 to be not reset (because 'if not reset_value' will return True if
the 'reset_value' is 0).
This commit is contained in:
Dennis Potter 2021-09-26 19:40:04 -07:00
parent c52e59abd0
commit 6359883c27
Signed by: Dennis
GPG Key ID: 186A8AD440942BAF
5 changed files with 257 additions and 20 deletions

View File

@ -228,8 +228,16 @@ class Component():
return ''.join(name)
def process_yaml(self, yaml_obj, values: dict = {}):
def process_yaml(self,
yaml_obj,
values: dict = {},
skip_signals: bool = False,
skip_inputs: bool = False,
skip_outputs: bool = False):
try:
if skip_signals:
raise KeyError
for x in yaml_obj['signals']:
self.signals[x['name'].format(**values)] =\
(x['signal_type'].format(**values),
@ -238,6 +246,9 @@ class Component():
pass
try:
if skip_inputs:
raise KeyError
for x in yaml_obj['input_ports']:
self.ports['input'][x['name'].format(**values)] =\
(x['signal_type'].format(**values),
@ -246,6 +257,9 @@ class Component():
pass
try:
if skip_outputs:
raise KeyError
for x in yaml_obj['output_ports']:
self.ports['output'][x['name'].format(**values)] =\
(x['signal_type'].format(**values),

View File

@ -6,7 +6,7 @@ import yaml
from systemrdl.node import FieldNode, SignalNode
from systemrdl.component import Reg, Regfile, Addrmap, Root
from systemrdl.rdltypes import PrecedenceType, AccessType, OnReadType, OnWriteType
from systemrdl.rdltypes import PrecedenceType, AccessType, OnReadType, OnWriteType, InterruptType
# Local modules
from components.component import Component, TypeDef
@ -46,8 +46,11 @@ class Field(Component):
# seperately in case of alias registers
if not self.config['external']:
self.__add_always_ff()
# Only add normal hardware access if field is not an interrupt field
if not self.__add_interrupt():
self.__add_hw_access()
self.__add_interrupt()
self.__add_combo()
self.__add_swmod_swacc()
self.__add_counter()
@ -125,7 +128,7 @@ class Field(Component):
)
else:
# If field spans multiple bytes, every byte shall have a seperate enable!
for j, i in enumerate(range(self.lsbyte, self.msbyte+1)):
for i in range(self.lsbyte, self.msbyte+1):
msb_bus = 8*(i+1)-1 if i != self.msbyte else obj.msb
lsb_bus = 8*i if i != self.lsbyte else obj.inst.lsb
@ -690,6 +693,81 @@ class Field(Component):
if self.obj.get_property('intr'):
self.intr = True
# Determine what causes the interrupt to get set, i.e.,
# is it a trigger that is passed to the module through an
# input or is it an internal signal
if next_val := self.obj.get_property('next'):
intr_trigger = self.get_signal_name(next_val)
else:
intr_trigger = \
self.process_yaml(
Field.templ_dict['interrupt_trigger_input'],
{'path': self.path_underscored}
)
if self.obj.get_property('stickybit'):
self.access_rtl['hw_write'] = ([
self.process_yaml(
Field.templ_dict['sticky_intr'],
{'path': self.path_underscored,
'genvars': self.genvars_str,
}
)
],
False)
# Create logic that contains condition for trigger
if self.obj.get_property('intr type') != InterruptType.level:
if self.rst['name']:
reset_intr_header = \
self.process_yaml(
Field.templ_dict['rst_intr_header'],
{'interrupt_trigger_input': intr_trigger,
'rst_name': self.rst['name'],
'rst_negl': "!" if self.rst['active'] == "active_low" else "",
'genvars': self.genvars_str,
}
)
else:
reset_intr_header = ""
self.rtl_footer.append(
self.process_yaml(
Field.templ_dict['always_ff_block_intr'],
{'interrupt_trigger_input': intr_trigger,
'always_ff_header': self.always_ff_header,
'reset_intr_header': reset_intr_header,
'genvars': self.genvars_str,
}
)
)
# This should be implemented as a match case statement later,
# but for now use the old if/elif/else construct to ensure
# compatibility with Python versions before 2021
self.rtl_footer.append(
self.process_yaml(
Field.templ_dict[str(self.obj.get_property('intr type'))],
{'interrupt_trigger_input': intr_trigger,
'path': self.path_underscored,
'genvars': self.genvars_str,
}
)
)
else:
self.access_rtl['hw_write'] = ([
self.process_yaml(
Field.templ_dict['nonsticky_intr'],
{'path': self.path_underscored,
'assignment': intr_trigger,
'genvars': self.genvars_str,
}
)
],
False)
# Generate masked & enabled version of interrupt to be
# picked up by the register at the top level
if mask := self.obj.get_property('mask'):
@ -727,6 +805,8 @@ class Field(Component):
self.itr_masked = False
self.itr_haltmasked = False
return self.intr
def __add_hw_access(self):
# Mutually exclusive. systemrdl-compiler performs check for this
enable_mask_negl = ''
@ -795,11 +875,15 @@ class Field(Component):
assignment = self.get_signal_name(self.obj.get_property('next'))
skip_inputs = True
if self.we_or_wel:
self.logger.info("This field has a 'we' or 'wel' property and "
"uses the 'next' property. Make sure this is "
"is intentional.")
else:
skip_inputs = False
# No special property. Assign input to register
assignment = \
self.process_yaml(
@ -819,7 +903,8 @@ class Field(Component):
'enable_mask_end': enable_mask_end_rtl,
'assignment': assignment,
'idx': enable_mask_idx,
'field_type': self.field_type}
'field_type': self.field_type},
skip_inputs = skip_inputs
)
)
else:
@ -952,10 +1037,13 @@ class Field(Component):
break
# Check if there is a list that shall be unlooped
try:
if isinstance(self.access_rtl[i], tuple):
access_rtl = [self.access_rtl[i]]
else:
access_rtl = self.access_rtl[i]
except KeyError:
continue
for unpacked_access_rtl in access_rtl:
if len(unpacked_access_rtl[0]) == 0:
@ -1113,10 +1201,17 @@ class Field(Component):
if self.rst['name']:
self.resets.add(self.rst['name'])
elif obj.get_property("reset") is not None:
self.logger.warning("Field has a reset value, but no reset "\
"signal was defined and connected to the "\
"field. Note that explicit connecting this "\
"is not required if a field_reset was defined.")
# Value of reset must always be determined on field level
# Don't use 'not obj.get_property("reset"), since the value
# could (and will often be) be '0'
self.rst['value'] = \
'\'x' if not obj.get_property("reset") else\
'\'x' if obj.get_property("reset") == None else\
obj.get_property('reset')
# Define dict that holds all RTL
@ -1155,13 +1250,14 @@ class Field(Component):
# Handle always_ff
sense_list = 'sense_list_rst' if self.rst['async'] else 'sense_list_no_rst'
self.rtl_header.append(
self.always_ff_header = \
self.process_yaml(
Field.templ_dict[sense_list],
{'rst_edge': self.rst['edge'],
'rst_name': self.rst['name']}
)
)
self.rtl_header.append(self.always_ff_header)
# Add actual reset line
if self.rst['name']:
@ -1179,9 +1275,6 @@ class Field(Component):
self.rtl_header.append("begin")
# Add name of actual field to Signal field
# TODO
def sanity_checks(self):
# If hw=rw/sw=[r]w and hw has no we/wel, sw will never be able to write
if not self.we_or_wel and\
@ -1195,8 +1288,6 @@ class Field(Component):
"write every cycle.")
# TODO: Counter & hw=r shouldn't work
# If hw=ro and the next property is set, throw a fatal
if self.obj.get_property('hw') == AccessType.r\
and self.obj.get_property('next'):

View File

@ -470,3 +470,79 @@ external_wr_assignments:
external_wr_mask_segment:
rtl: |-
{{{width}{{b2r.byte_en[{idx}]}}}}
interrupt_trigger_input:
rtl: |-
{path}_set_intr
input_ports:
- name: '{path}_set_intr'
signal_type: 'logic'
rst_intr_header:
rtl: |-
if ({rst_negl}{rst_name})
<<INDENT>>
{interrupt_trigger_input}_q{genvars} <= 1'b0;
<<UNINDENT>>
else
signals:
- name: '{interrupt_trigger_input}_q'
signal_type: 'logic'
always_ff_block_intr:
rtl: |-
// Flops to generate appropriate interrupt signal
{always_ff_header}
{reset_intr_header}
<<INDENT>>
{interrupt_trigger_input}_q{genvars} <= {interrupt_trigger_input}{genvars};
<<UNINDENT>>
signals:
- name: '{interrupt_trigger_input}_q'
signal_type: 'logic'
InterruptType.posedge:
rtl: |-
// Define signal that causes the interrupt to be set (posedge-type interrupt)
assign {path}_intr_latch{genvars} = !{interrupt_trigger_input}_q{genvars} && {interrupt_trigger_input}{genvars};
signals:
- name: '{path}_intr_latch'
signal_type: 'logic'
InterruptType.negedge:
rtl: |-
// Define signal that causes the interrupt to be set (negedge-type interrupt)
assign {path}_intr_latch{genvars} = {interrupt_trigger_input}_q{genvars} && !{interrupt_trigger_input}{genvars};
signals:
- name: '{path}_intr_latch'
signal_type: 'logic'
InterruptType.bothedge:
rtl: |-
// Define signal that causes the interrupt to be set (bothedge-type interrupt)
assign {path}_intr_latch{genvars} = ({interrupt_trigger_input}_q{genvars} && !{interrupt_trigger_input}{genvars}) || (!{interrupt_trigger_input}_q{genvars} && {interrupt_trigger_input}{genvars});
signals:
- name: '{path}_intr_latch'
signal_type: 'logic'
InterruptType.level:
rtl: |-
// Define signal that causes the interrupt to be set (level-type interrupt)
assign {path}_intr_latch{genvars} = {interrupt_trigger_input}{genvars};
signals:
- name: '{path}_intr_latch'
signal_type: 'logic'
sticky_intr:
rtl: |-
if ({path}_intr_latch{genvars})
begin
// Sticky interrupt. Keep value until software clears it
{path}_q{genvars} <= 1'b1;
end
signals:
- name: '{path}_intr_latch'
signal_type: 'logic'
nonsticky_intr:
rtl: |-
begin
// Non-sticky interrupt. Only keep value high if source keeps up
{path}_q{genvars} <= {assignment};
end

View File

@ -138,7 +138,7 @@ interrupt_halt:
rtl: |-
// Register has at least one interrupt field with halt property set
assign {path}_halt{genvars} = {list};
assign {path}_halt{genvars} = ({list});
output_ports:
- name: '{path}_halt'
signal_type: 'logic'

View File

@ -0,0 +1,56 @@
addrmap interrupts {
signal { activelow; async; field_reset;} field_reset_n;
reg {
field {sw=rw; hw=rw; intr; } intr1 [0:0] = 0;
field {sw=rw; hw=rw; bothedge intr; } intr2 [1:1] = 0;
field {sw=rw; hw=rw; negedge intr; } intr3 [2:2] = 0;
field {sw=rw; hw=rw; posedge intr; } intr4 [3:3] = 0;
field {sw=rw; hw=rw; } intr5 [4:4] = 0;
field {sw=rw; hw=rw; nonsticky intr;} intr6 [5:5] = 0;
} itrs_reg;
reg {
field {sw=rw; hw=r;} intr1 [0:0];
field {sw=rw; hw=r;} intr2 [1:1];
} itrs_mask;
reg {
field {sw=rw; hw=r;} intr5 [1:1];
} itrs_enable;
reg {
field {sw=rw; hw=r;} intr6 [1:1];
} itrs_next_assign;
itrs_reg.intr1->mask = itrs_mask.intr1;
itrs_reg.intr2->mask = itrs_mask.intr2;
itrs_reg.intr5->enable = itrs_enable.intr5;
itrs_reg.intr5->next = itrs_next_assign.intr6;
// HALT REGISTERS
reg {
field {sw=rw; hw=rw; intr;} intr1 [0:0];
field {sw=rw; hw=rw; intr;} intr2 [1:1];
field {sw=rw; hw=rw; intr;} intr3 [2:2];
field {sw=rw; hw=rw; } intr4 [3:3];
field {sw=rw; hw=rw; intr;} intr5 [4:4];
} itrs_halteable_reg;
reg {
field {sw=rw; hw=r;} intr1 [0:0];
field {sw=rw; hw=r;} intr2 [1:1];
} itrs_halt;
itrs_halteable_reg.intr1->haltmask = itrs_halt.intr1;
itrs_halteable_reg.intr2->haltmask = itrs_halt.intr2;
// USE INTERRUPT
reg {
field {sw=rw; hw=rw;} itrs_reg_next [0:0];
field {sw=rw; hw=rw;} itrs_halteable_next [1:1];
} itrs_next;
itrs_next.itrs_reg_next->next = itrs_reg->intr;
};