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) 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: try:
if skip_signals:
raise KeyError
for x in yaml_obj['signals']: for x in yaml_obj['signals']:
self.signals[x['name'].format(**values)] =\ self.signals[x['name'].format(**values)] =\
(x['signal_type'].format(**values), (x['signal_type'].format(**values),
@ -238,6 +246,9 @@ class Component():
pass pass
try: try:
if skip_inputs:
raise KeyError
for x in yaml_obj['input_ports']: for x in yaml_obj['input_ports']:
self.ports['input'][x['name'].format(**values)] =\ self.ports['input'][x['name'].format(**values)] =\
(x['signal_type'].format(**values), (x['signal_type'].format(**values),
@ -246,6 +257,9 @@ class Component():
pass pass
try: try:
if skip_outputs:
raise KeyError
for x in yaml_obj['output_ports']: for x in yaml_obj['output_ports']:
self.ports['output'][x['name'].format(**values)] =\ self.ports['output'][x['name'].format(**values)] =\
(x['signal_type'].format(**values), (x['signal_type'].format(**values),

View File

@ -6,7 +6,7 @@ import yaml
from systemrdl.node import FieldNode, SignalNode from systemrdl.node import FieldNode, SignalNode
from systemrdl.component import Reg, Regfile, Addrmap, Root 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 # Local modules
from components.component import Component, TypeDef from components.component import Component, TypeDef
@ -46,8 +46,11 @@ class Field(Component):
# seperately in case of alias registers # seperately in case of alias registers
if not self.config['external']: if not self.config['external']:
self.__add_always_ff() self.__add_always_ff()
self.__add_hw_access()
self.__add_interrupt() # Only add normal hardware access if field is not an interrupt field
if not self.__add_interrupt():
self.__add_hw_access()
self.__add_combo() self.__add_combo()
self.__add_swmod_swacc() self.__add_swmod_swacc()
self.__add_counter() self.__add_counter()
@ -125,7 +128,7 @@ class Field(Component):
) )
else: else:
# If field spans multiple bytes, every byte shall have a seperate enable! # 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 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 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'): if self.obj.get_property('intr'):
self.intr = True 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 # Generate masked & enabled version of interrupt to be
# picked up by the register at the top level # picked up by the register at the top level
if mask := self.obj.get_property('mask'): if mask := self.obj.get_property('mask'):
@ -727,6 +805,8 @@ class Field(Component):
self.itr_masked = False self.itr_masked = False
self.itr_haltmasked = False self.itr_haltmasked = False
return self.intr
def __add_hw_access(self): def __add_hw_access(self):
# Mutually exclusive. systemrdl-compiler performs check for this # Mutually exclusive. systemrdl-compiler performs check for this
enable_mask_negl = '' enable_mask_negl = ''
@ -795,11 +875,15 @@ class Field(Component):
assignment = self.get_signal_name(self.obj.get_property('next')) assignment = self.get_signal_name(self.obj.get_property('next'))
skip_inputs = True
if self.we_or_wel: if self.we_or_wel:
self.logger.info("This field has a 'we' or 'wel' property and " self.logger.info("This field has a 'we' or 'wel' property and "
"uses the 'next' property. Make sure this is " "uses the 'next' property. Make sure this is "
"is intentional.") "is intentional.")
else: else:
skip_inputs = False
# No special property. Assign input to register # No special property. Assign input to register
assignment = \ assignment = \
self.process_yaml( self.process_yaml(
@ -819,7 +903,8 @@ class Field(Component):
'enable_mask_end': enable_mask_end_rtl, 'enable_mask_end': enable_mask_end_rtl,
'assignment': assignment, 'assignment': assignment,
'idx': enable_mask_idx, 'idx': enable_mask_idx,
'field_type': self.field_type} 'field_type': self.field_type},
skip_inputs = skip_inputs
) )
) )
else: else:
@ -952,10 +1037,13 @@ class Field(Component):
break break
# Check if there is a list that shall be unlooped # Check if there is a list that shall be unlooped
if isinstance(self.access_rtl[i], tuple): try:
access_rtl = [self.access_rtl[i]] if isinstance(self.access_rtl[i], tuple):
else: access_rtl = [self.access_rtl[i]]
access_rtl = self.access_rtl[i] else:
access_rtl = self.access_rtl[i]
except KeyError:
continue
for unpacked_access_rtl in access_rtl: for unpacked_access_rtl in access_rtl:
if len(unpacked_access_rtl[0]) == 0: if len(unpacked_access_rtl[0]) == 0:
@ -1113,10 +1201,17 @@ class Field(Component):
if self.rst['name']: if self.rst['name']:
self.resets.add(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 # 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'] = \ self.rst['value'] = \
'\'x' if not obj.get_property("reset") else\ '\'x' if obj.get_property("reset") == None else\
obj.get_property('reset') obj.get_property('reset')
# Define dict that holds all RTL # Define dict that holds all RTL
@ -1155,13 +1250,14 @@ class Field(Component):
# Handle always_ff # Handle always_ff
sense_list = 'sense_list_rst' if self.rst['async'] else 'sense_list_no_rst' 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( self.process_yaml(
Field.templ_dict[sense_list], Field.templ_dict[sense_list],
{'rst_edge': self.rst['edge'], {'rst_edge': self.rst['edge'],
'rst_name': self.rst['name']} 'rst_name': self.rst['name']}
) )
)
self.rtl_header.append(self.always_ff_header)
# Add actual reset line # Add actual reset line
if self.rst['name']: if self.rst['name']:
@ -1179,9 +1275,6 @@ class Field(Component):
self.rtl_header.append("begin") self.rtl_header.append("begin")
# Add name of actual field to Signal field
# TODO
def sanity_checks(self): def sanity_checks(self):
# If hw=rw/sw=[r]w and hw has no we/wel, sw will never be able to write # 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\ if not self.we_or_wel and\
@ -1195,8 +1288,6 @@ class Field(Component):
"write every cycle.") "write every cycle.")
# TODO: Counter & hw=r shouldn't work
# If hw=ro and the next property is set, throw a fatal # If hw=ro and the next property is set, throw a fatal
if self.obj.get_property('hw') == AccessType.r\ if self.obj.get_property('hw') == AccessType.r\
and self.obj.get_property('next'): and self.obj.get_property('next'):

View File

@ -469,4 +469,80 @@ external_wr_assignments:
signal_type: 'logic' signal_type: 'logic'
external_wr_mask_segment: external_wr_mask_segment:
rtl: |- rtl: |-
{{{width}{{b2r.byte_en[{idx}]}}}} {{{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: |- rtl: |-
// Register has at least one interrupt field with halt property set // Register has at least one interrupt field with halt property set
assign {path}_halt{genvars} = {list}; assign {path}_halt{genvars} = ({list});
output_ports: output_ports:
- name: '{path}_halt' - name: '{path}_halt'
signal_type: 'logic' 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;
};